Introduce config parameter limit label expansion - #261
Conversation
ed6fde4 to
c8accbc
Compare
In the previous versions, there is no mechanism to limit label expansion. That causes a possibility of cardinality OOM DoS. To mitigate such situation, introduced the following parameters: * max_label_value_length: The maximum length of a label value. * max_series_per_metric: The maximum number of label sets a metric can hold. The above parameter is configurable for filter_prometheus and out_prometheus. For example, if about 8 million records are loaded without cardinality limitation, RSS increased from 64MB to 582MB. It might cause OOM DoS. In contrast to that case with cardinality limitation, RSS increased from 64MB to 84 MB in similar case. Then warning message is logged like this: 2026-08-06 14:06:04 +0900 [warn]: prometheus: dropped a label set because the metric reached max_series_per_metric. name="access_requests_total" max_series_per_metric=10000 Signed-off-by: Kentaro Hayashi <hayashi@clear-code.com>
c8accbc to
243d3cc
Compare
There was a problem hiding this comment.
Seems that once a record has raised an exception, valid records sent afterwards no longer show up in the metric.
Please try attached file to reproduce.
repro-261.tar.gz
Metric#labels registered a label set into @Series before the metric was actually instrumented. When the instrumentation failed afterwards, for example when the value of `key` is not a number and Counter#increment raises, the label set consumed max_series_per_metric even though nothing was recorded. Records which fail that way could therefore exhaust the limit and make the following valid label sets dropped. Split the responsibility: check_series_limit! only refuses an unknown label set once the limit is reached, and the new remember_series counts it after the instrumentation succeeded. Each metric type now goes through with_label_set, which calls them in that order. Signed-off-by: Kentaro Hayashi <hayashi@clear-code.com> Co-Authored-By: Claude <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Introduces configurable safeguards to bound Prometheus label cardinality and label value growth in filter_prometheus and out_prometheus, mitigating cardinality-driven memory exhaustion risks.
Changes:
- Add
max_label_value_length(truncate label values) andmax_series_per_metric(drop new label sets beyond a cap) with per-<metric>overrides. - Add shared
LogThrottleand use it to throttle repeated “dropped label set” warnings (and refactorin_prometheuserror throttling to use it). - Add/extend specs and documentation for the new limiting behavior.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| spec/fluent/plugin/prometheus/log_throttle_spec.rb | Adds unit tests for the new shared log throttling utility. |
| spec/fluent/plugin/filter_prometheus_spec.rb | Adds coverage for max_series_per_metric behavior and throttled warning logging in the filter plugin. |
| README.md | Documents new label expansion limiting parameters and behavior details. |
| lib/fluent/plugin/prometheus.rb | Implements label truncation, series limiting, label-set limit warnings, and shared LogThrottle. |
| lib/fluent/plugin/out_prometheus.rb | Passes plugin-level metric limit options into metric construction. |
| lib/fluent/plugin/in_prometheus.rb | Replaces bespoke throttling with shared LogThrottle. |
| lib/fluent/plugin/filter_prometheus.rb | Passes plugin-level metric limit options into metric construction. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| def with_label_set(record, expander) | ||
| label = labels(record, expander) | ||
| yield label | ||
| remember_series(label) | ||
| end |
| desc 'The maximum length of a label value. Longer values are truncated. 0 means unlimited.' | ||
| config_param :max_label_value_length, :integer, default: DEFAULT_MAX_LABEL_VALUE_LENGTH | ||
| desc 'The maximum number of label sets a metric can hold. Exceeding label sets are dropped. 0 means unlimited.' | ||
| config_param :max_series_per_metric, :integer, default: DEFAULT_MAX_SERIES_PER_METRIC | ||
| desc 'The interval to suppress the repeated same error log.' |
Both limits are on by default, so upgrading changes the exported metrics of every existing user — and the change is silent
Truncation merges label sets that used to be distinct Verified with the default configuration (no limit set anywhere): <filter test.**>
@type prometheus
<metric>
name test_truncated
type counter
desc test
key val
<labels>
path $.path
</labels>
</metric>
</filter>Feeding two records whose Label values longer than 256 characters are not exotic — URLs with query strings, Kubernetes annotations, SQL statements and exception messages all reach that length routinely. For those users, upgrading makes existing series disappear and a new merged series appear in their place. Prometheus sees the old series go stale, so recording rules, dashboards and alerts built on them break, and the counter values are wrong rather than merely missing. The cap drops records once a metric is saturated A deployment that legitimately runs above 10000 label sets today starts losing everything past the 10001st after the upgrade, with no configuration change on their side. Suggestion Please consider defaulting both to A dropped label set leaves almost no traceThis is what makes the previous point serious: when a record is dropped, there is essentially no way for an operator to find out.
In an earlier reproduction of a related problem, 161 dropped records produced exactly one log line while every Suggestion Self-instrument the drops so they are visible in Prometheus itself rather than only in logs — for example a counter such as If both limits are going to stay on by default, this feels like a prerequisite rather than a nice-to-have: the defaults are what make the loss possible, and this is what would make it noticeable. This comment was written by Claude (Claude Code). The behaviour described above was verified by running the plugin at 39a9ae3. |
In the previous versions, there is no mechanism to limit label expansion. That causes a possibility of cardinality OOM DoS.
To mitigate such situation, introduced the following parameters:
The above parameter is configurable for filter_prometheus and out_prometheus.