Loki Error Guide: 'Maximum active stream limit exceeded' — Cut Label Cardinality and Raise Stream Limits
Fix Loki's 'Maximum active stream limit exceeded': drop high-cardinality labels, raise max_global_streams_per_user, add ingesters, and stop label explosion from filling active streams.
- #loki
- #logging
- #troubleshooting
- #errors
Fixing errors like this? Get 500 free DevOps AI prompts
500 copy-paste AI prompts for the stack you actually run — one PDF, free.
Overview
Loki’s ingester rejects new streams with this error once a tenant has more active streams than its configured limit. Clients see it as an HTTP 429 from the push path:
Ingester.Push: rpc error: code = Code(429) desc = Maximum active stream limit exceeded, reduce the number of active streams (reduce labels or reduce label values), or contact your Loki administrator to see if the limit can be increased, user: 'fake'
An active stream is one unique label-value combination currently being written. Loki bounds active streams per tenant with max_global_streams_per_user (spread across ingesters) and, historically, max_streams_per_user (per ingester). This limit is the front-line defense against cardinality explosion. Hitting it almost always means a label with unbounded values — a request ID, a pod IP, a user ID — has multiplied streams far beyond what the index and ingesters can hold.
Symptoms
- New log streams stop appearing while existing ones keep flowing.
- Push clients log
429 Maximum active stream limit exceeded. - Ingester memory and
loki_ingester_memory_streamsclimb steeply then plateau at the cap. loki_discarded_samples_totalwith reasonstream_limitincreases.- The problem appears right after a deploy that added a new label.
Common Root Causes
- High-cardinality labels — labels whose values are effectively unbounded (IDs, IPs, timestamps, full URLs).
- Dynamic label values from log content promoted to stream labels via pipeline stages.
max_global_streams_per_usertoo low for a legitimately large service.- Too few ingesters so the global limit divides into a small per-ingester share.
- A misconfigured relabel rule turning a variable field into a label.
Diagnostic Workflow
Confirm the discard reason and current stream count on an ingester:
curl -s http://loki-ingester:3100/metrics | grep -E 'loki_ingester_memory_streams|stream_limit'
Find which label is exploding cardinality using the label API or a metric query:
# List label values; a huge count reveals the culprit
logcli labels
logcli labels pod # e.g. thousands of values = unbounded
Read the effective stream limits:
limits_config:
max_global_streams_per_user: 5000 # total active streams per tenant, cluster-wide
max_streams_per_user: 0 # 0 = use global only (per-ingester legacy cap)
max_label_names_per_series: 15
max_label_value_length: 2048
Check whether a pipeline stage is promoting a variable field to a label:
# promtail/alloy: a BAD label — request_id is unbounded
pipeline_stages:
- labels:
request_id: # remove this; keep it as a log field, not a label
Example Root Cause Analysis
After a release, a service started attaching request_id as a Loki label via a Promtail labels stage. Each request produced a unique value, so within an hour the tenant accumulated tens of thousands of active streams and slammed into max_global_streams_per_user: 5000. New requests could no longer create streams and their logs were discarded with Maximum active stream limit exceeded.
The correct fix was to stop labeling by request_id — it belongs in the log line, searchable with | json | request_id="...", not as a stream label. Removing that one labels stage collapsed active streams from ~40,000 back to ~800. As genuine growth headroom they raised max_global_streams_per_user to 10000 and added a fourth ingester so the global limit spread further. Streams stabilized and discards stopped. The lesson: raising the limit would only have delayed collapse; the real bug was cardinality.
Prevention Best Practices
- Keep labels bounded and low-cardinality: use them for
app,namespace,env,pod— never IDs, IPs, or URLs. - Search variable fields with LogQL filter/parser expressions instead of promoting them to labels.
- Cap
max_label_names_per_seriesandmax_label_value_lengthto catch accidental explosions early. - Set
max_global_streams_per_userto real need plus headroom, and scale ingesters so the per-ingester share stays healthy. - Review every new relabel/label pipeline stage for cardinality impact before rollout.
- Alert on
loki_ingester_memory_streamstrending toward the limit.
Quick Command Reference
# Active streams and stream-limit discards
curl -s localhost:3100/metrics | grep -E 'memory_streams|stream_limit'
# Spot the high-cardinality label
logcli labels
logcli labels <suspect_label> | wc -l
# Confirm running limit
curl -s http://loki:3100/config | grep max_global_streams_per_user
limits_config:
max_global_streams_per_user: 10000
max_label_names_per_series: 15
Conclusion
Maximum active stream limit exceeded is Loki protecting itself from cardinality explosion, and it is almost never solved by simply raising the number. Find the unbounded label — usually an ID or IP promoted to a stream label — and demote it to a searchable log field. Then set max_global_streams_per_user to real demand with headroom, scale ingesters, and alert on active stream count so the next label mistake is caught before it fills the cluster.
Get 500 Battle-Tested DevOps AI Prompts — Free
500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.
- 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
- Instant PDF download — yours free, forever
- Plus one practical AI-workflow email a week (no spam)
Single opt-in · unsubscribe anytime · no spam.