Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions changelog.d/tag_cardinality_limit_tracking_scope.enhancement.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
The `tag_cardinality_limit` transform gained two new top-level settings:

- **`tracking_scope`** controls how tag tracking state is partitioned across metrics:
- `global` (default — preserves existing behavior): all metrics share a single
tracking bucket, and the global `value_limit` caps the combined set of tag values
across them.
- `per_metric`: every distinct metric gets its own tracking bucket, providing tag
cardinality limiting for each metric in isolation at the cost of higher memory.

- **`max_tracked_keys`** caps the total number of distinct (metric, tag-key) pairs
tracked across the entire transform. When the cap is reached, additional pairs are
not allocated and tag values for those pairs pass through unchecked (they are
*not* dropped). Operators can detect this via the new
`tag_cardinality_untracked_events_total` counter (incremented once per event with at
least one untracked tag) and the `tag_cardinality_tracked_keys` gauge (current size
of the cardinality cache). Defaults to unlimited, preserving existing behavior.

authors: ArunPiduguDD
4 changes: 4 additions & 0 deletions lib/vector-common/src/internal_event/metric_name.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ pub enum CounterName {
StaleEventsFlushedTotal,
StartedTotal,
StoppedTotal,
TagCardinalityUntrackedEventsTotal,
TagValueLimitExceededTotal,
ValueLimitReachedTotal,
WebsocketBytesSentTotal,
Expand Down Expand Up @@ -190,6 +191,7 @@ pub enum GaugeName {
ActiveClients,
MemoryEnrichmentTableObjectsCount,
MemoryEnrichmentTableByteSize,
TagCardinalityTrackedKeys,
}

impl GaugeName {
Expand Down Expand Up @@ -220,6 +222,7 @@ impl GaugeName {
Self::ActiveClients => "active_clients",
Self::MemoryEnrichmentTableObjectsCount => "memory_enrichment_table_objects_count",
Self::MemoryEnrichmentTableByteSize => "memory_enrichment_table_byte_size",
Self::TagCardinalityTrackedKeys => "tag_cardinality_tracked_keys",
}
}
}
Expand Down Expand Up @@ -304,6 +307,7 @@ impl CounterName {
Self::StaleEventsFlushedTotal => "stale_events_flushed_total",
Self::StartedTotal => "started_total",
Self::StoppedTotal => "stopped_total",
Self::TagCardinalityUntrackedEventsTotal => "tag_cardinality_untracked_events_total",
Self::TagValueLimitExceededTotal => "tag_value_limit_exceeded_total",
Self::ValueLimitReachedTotal => "value_limit_reached_total",
Self::WebsocketBytesSentTotal => "websocket_bytes_sent_total",
Expand Down
27 changes: 25 additions & 2 deletions src/internal_events/tag_cardinality_limit.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use vector_lib::{
NamedInternalEvent, counter,
internal_event::{ComponentEventsDropped, CounterName, INTENTIONAL, InternalEvent},
NamedInternalEvent, counter, gauge,
internal_event::{ComponentEventsDropped, CounterName, GaugeName, INTENTIONAL, InternalEvent},
};

#[derive(NamedInternalEvent)]
Expand Down Expand Up @@ -80,3 +80,26 @@ impl InternalEvent for TagCardinalityValueLimitReached<'_> {
counter!(CounterName::ValueLimitReachedTotal).increment(1);
}
}

#[derive(NamedInternalEvent)]
pub struct TagCardinalityLimitUntracked;

impl InternalEvent for TagCardinalityLimitUntracked {
fn emit(self) {
debug!(
message = "max_tracked_keys reached; one or more tags on this event are passing through untracked."
);
counter!(CounterName::TagCardinalityUntrackedEventsTotal).increment(1);
}
}

#[derive(NamedInternalEvent)]
pub struct TagCardinalityTrackedKeys {
pub count: usize,
}

impl InternalEvent for TagCardinalityTrackedKeys {
fn emit(self) {
gauge!(GaugeName::TagCardinalityTrackedKeys).set(self.count as f64);
}
}
37 changes: 37 additions & 0 deletions src/transforms/tag_cardinality_limit/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,24 @@ pub struct Config {
#[serde(flatten)]
pub global: Inner,

/// Controls how tag tracking state is partitioned across metrics.
#[configurable(derived)]
#[serde(default)]
pub tracking_scope: TrackingScope,

/// Maximum number of distinct (metric, tag-key) pairs to track across the entire
/// transform. When this cap is reached, additional tag keys on new metrics or new
/// tag keys on existing metrics are not tracked, and tag values for those pairs
/// pass through unchecked. Operators can detect this via the
/// `tag_cardinality_untracked_events_total` counter and the
/// `tag_cardinality_tracked_keys` gauge.
///
/// When unset (default), there is no cap and the transform tracks all pairs it
/// encounters.
#[configurable(derived)]
#[serde(default)]
pub max_tracked_keys: Option<usize>,

/// Tag cardinality limits configuration per metric name.
#[configurable(
derived,
Expand All @@ -44,6 +62,23 @@ pub struct Config {
pub per_metric_limits: HashMap<String, PerMetricConfig>,
}

/// Controls how tag tracking state is partitioned across metrics.
#[configurable_component]
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
#[serde(rename_all = "snake_case")]
pub enum TrackingScope {
/// All metrics share a single tracking bucket. Tag values pool across metrics,
/// and the global `value_limit` caps the combined set. Lower memory but
/// cross-metric pollution.
#[default]
Global,

/// Every distinct metric gets its own tracking bucket, providing tag
/// cardinality limiting for each metric in isolation at the cost of higher
/// memory.
PerMetric,
}

/// Configuration for the `tag_cardinality_limit` transform for a specific group of metrics.
#[configurable_component]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
Expand Down Expand Up @@ -150,6 +185,8 @@ impl GenerateConfig for Config {
limit_exceeded_action: default_limit_exceeded_action(),
internal_metrics: InternalMetricsConfig::default(),
},
tracking_scope: TrackingScope::default(),
max_tracked_keys: None,
per_metric_limits: HashMap::default(),
})
.unwrap()
Expand Down
Loading
Loading