-
Notifications
You must be signed in to change notification settings - Fork 93
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(cardinality): Implement Redis set based cardinality limiter #2745
Conversation
4ebfe3c
to
1935241
Compare
1935241
to
ec47e21
Compare
b5b77f1
to
8bc9bdb
Compare
flush_partitions: Option<u64>, | ||
) -> BTreeMap<Option<u64>, Vec<Bucket>> { | ||
let flush_partitions = match flush_partitions { | ||
None => return BTreeMap::from([(None, buckets)]), | ||
None => return BTreeMap::from([(None, buckets.into_iter().collect())]), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I checked with godbolt this is optimized away when passing a vector already
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like the look of this! Just a bunch of random comments for now, will do a more in-depth review later.
if !enable_cardinality_limiter { | ||
return Ok(Box::new(buckets.into_iter())); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: Why not check the flag outside of this function? Seems more straight-forward than passing a boolean that makes the function behave like a noop (but this might just be my personal preference).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I implemented the function pretty much only for this check, so I can early return, otherwise it becomes very complex with the cfg(feature = "processing")
fn check_cardinality_limits( | ||
&self, | ||
enable_cardinality_limiter: bool, | ||
_organization_id: u64, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: Should make this function #[cfg(feature = "processing")]
?
_organization_id: u64, | |
organization_id: u64, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unfortunately this is needed for non-processing, the entire function just exists to handle the if
case with an early return to make the code somewhat readable and not a pure if else cfg mess.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it still a mess with the if_processing!
macro?
90e2a3e
to
9121915
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's ship it! Before opting in all orgs we should transition from copying entire redis sets to a Lua script, as you suggested.
@@ -1,4 +1,4 @@ | |||
//! Metrics Cardinality Limiter | |||
//! Relay Cardinality Module |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not necessarily for this PR, but since this is a new crate, we should take a look at what make doc-rust
produces and whether we're happy with the documentation. Other crates have a summary of what they do in the crate-level docs, e.g.
relay/relay-metrics/src/lib.rs
Lines 1 to 64 in a9e7ef5
//! Metric protocol, aggregation and processing for Sentry. | |
//! | |
//! Metrics are high-volume values sent from Sentry clients, integrations, or extracted from errors | |
//! and transactions, that can be aggregated and queried over large time windows. As opposed to rich | |
//! errors and transactions, metrics carry relatively little context information in tags with low | |
//! cardinality. | |
//! | |
//! # Protocol | |
//! | |
//! Clients submit metrics in a [text-based protocol](Bucket) based on StatsD. See the [field | |
//! documentation](Bucket#fields) on `Bucket` for more information on the components. A sample | |
//! submission looks like this: | |
//! | |
//! ```text | |
#![doc = include_str!("../tests/fixtures/buckets.statsd.txt")] | |
//! ``` | |
//! | |
//! The metric type is part of its signature just like the unit. Therefore, it is allowed to reuse a | |
//! metric name for multiple metric types, which will result in multiple metrics being recorded. | |
//! | |
//! # Metric Envelopes | |
//! | |
//! To send one or more metrics to Relay, the raw protocol is enclosed in an envelope item of type | |
//! `metrics`: | |
//! | |
//! ```text | |
//! {} | |
//! {"type": "statsd", ...} | |
#![doc = include_str!("../tests/fixtures/buckets.statsd.txt")] | |
//! ... | |
//! ``` | |
//! | |
//! Note that the name format used in the statsd protocol is different from the MRI: Metric names | |
//! are not prefixed with `<ty>:` as the type is somewhere else in the protocol. If no metric | |
//! namespace is specified, the `"custom"` namespace is assumed. | |
//! | |
//! Optionally, a timestamp can be added to every line of the submitted envelope. The timestamp has | |
//! to be a valid Unix timestamp (UTC) and must be prefixed with `T`. If it is omitted, the | |
//! `received` time of the envelope is assumed. | |
//! | |
//! # Aggregation | |
//! | |
//! Relay accumulates all metrics in [time buckets](Bucket) before sending them onwards. Aggregation | |
//! is handled by the [`Aggregator`], which should be created once for the entire system. It flushes | |
//! aggregates in regular intervals, either shortly after their original time window has passed or | |
//! with a debounce delay for backdated submissions. | |
//! | |
//! **Warning**: With chained Relays submission delays accumulate. | |
//! | |
//! Aggregate buckets are encoded in JSON with the following schema: | |
//! | |
//! ```json | |
#![doc = include_str!("../tests/fixtures/buckets.json")] | |
//! ``` | |
//! | |
//! # Ingestion | |
//! | |
//! Processing Relays write aggregate buckets into the ingestion Kafka stream. The schema is similar | |
//! to the aggregation payload, with the addition of scoping information. Each bucket is sent in a | |
//! separate message: | |
//! | |
//! ```json | |
#![doc = include_str!("../tests/fixtures/kafka.json")] | |
//! ``` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I will definitely do this in a follow up, I also want to get a proper documentation in for how the limiter works!
Introduces a new
relay-cardinality
module, initially it is only housing the cardinality limiter, but at some point there may be more functionality related to cardinality (e.g. smart detection of high cardinality tags).Implements a very basic cardinality limiter using one Redis set per organization and namespace to track cardinality.
API is still very much work in progress, this works for now but will definitely need to be changed in the future when the requirements for the cardinality limiter change. Outcomes, Tag erasure etc.
Epic: #2717