loki-best-practices
Production-grade Grafana Loki operations: diagnosis, performance, storage, HA, and observability. Assume the user is a senior engineer running Loki in Kubernetes — skip introductions to LogQL and chunks and go straight to evidence-driven SRE workflow.
When to use
Trigger on operational Loki tasks:
- Ingest incidents: HTTP 429/4xx from distributor,
loki_discarded_samples_total rising, push retries from Promtail/Alloy/OTel, "rate limit exceeded", "stream limit", "out of order", "too far behind", "line too long", "invalid labels".
- WAL & flush failures: ingester CrashLoopBackOff during replay,
loki_ingester_wal_replay_active stuck at 1, loki_ingester_chunks_flushed_total flat while ingest is hot, OOMKilled on startup, full WAL PVC.
- Deployment & config: Helm chart
singleBinary vs simple-scalable mode collisions, "schema period must be 24h", TSDB migration that hides pre-migration data, missing object-storage backend on scalable targets, replication-factor vs ingester-count mismatch.
- Integration breakage: Grafana datasource "No data" with active streams, multi-tenant
X-Scope-OrgID returning partial results, Promtail positions lost on restart, OTel structured metadata rejected, OTel endpoint misconfigured (/loki/api/v1/push/v1/logs 404), memberlist DNS failures, NetworkPolicies blocking gossip/HTTP/gRPC.
- Query performance: "too many outstanding requests",
max_query_length exceeded, max_query_series reached, high-cardinality label explosion, expensive |~ regex without prior |= line filter, sharding/parallelism tuning.
- Storage: S3
WebIdentityErr / IRSA failures, S3 lifecycle deleting active chunks, compactor stuck or running as two replicas, context deadline exceeded on object storage, index-gateway slow chunk reads.
- HA: UNHEALTHY ring entries after node loss, ungraceful shutdown leaving stranded chunks, schema bump during rolling upgrade, two compactors running simultaneously, ruler firing in duplicate or not at all, memberlist IP reuse / ghost members.
- Observability of Loki itself: which metrics to alert on, what to scrape, what cache hit ratios to target.
Do NOT trigger for:
- Greenfield "should we use Loki or Elasticsearch / OpenSearch" decisions — that's an architecture conversation, not an ops skill.
- Choosing between Promtail and Alloy for a new install (use the Grafana Agent migration guide).
- Generic LogQL syntax questions ("what does
unwrap do") with no operational context.
Instructions
1. Diagnose like an SRE
For any Loki incident, follow hypothesis → evidence → fix → verification. Never propose a fix without naming the metric or endpoint that proves the diagnosis first.
Always start at loki_discarded_samples_total — the write path tells you almost everything. Group by reason and the failure mode tells you what to fix:
sum by (reason) (rate(loki_discarded_samples_total[5m]))
If the rate is non-zero, the reason label is the entire diagnosis — map each reason to its root-cause class and fix path in references/command-reference.md. That file also carries the 10-command triage cheatsheet and the distroless-image caveat (run curls from a sidecar or kubectl port-forward svc/loki 3100:3100, never sh -c — the official image has no shell).
2. Triage by failure surface
The playbook breaks into six surfaces. Classify the symptom first, then look up the matching entry in references/triage-playbook.md — each entry has the symptoms, the proof to gather, and the fix. Don't skip classification; it saves the wrong-rabbit-hole tax.
- Ingestion (write path) — 4xx on
/loki/api/v1/push, loki_discarded_samples_total{reason} climbing; fix the label set before raising per-stream limits, persist Promtail positions, unstick WAL replay.
- Deployment & configuration — CrashLoopBackOff, Helm single-vs-scalable mode collision, 24h schema period, RF-vs-ingester-count math, missing object-storage backend.
- Integration — Grafana "No data" with a healthy querier, OTel
endpoint 404s / structured-metadata rejections, memberlist DNS, NetworkPolicy gaps on 7946/3100/9095.
- Query performance — "too many outstanding requests", "query length too large", cardinality explosions, expensive
|~ regex before a |= filter.
- Storage backend — S3
WebIdentityErr/IRSA, S3 lifecycle deleting live chunks, stuck/duplicate compactor, cross-region latency.
- High availability — UNHEALTHY ring entries after node loss, schema-boundary rolling upgrades, compactor singleton conflict, ruler HA, flush-before-forget.
3. Label design is the load-bearing decision
Most Loki problems trace back to one label that should never have been a label. Treat these as code-review red lines:
- Cap labels at ~10–15 per stream. More than that and the index grows linearly with stream count.
- Never label by: request ID, trace ID, user ID, session ID, full URL path, hash, IP, or anything else that takes more than ~100 distinct values per workload.
- Move variable values to structured metadata (requires Loki ≥ 3.0, schema v13,
allow_structured_metadata: true). It's queryable via | label_format at read time without bloating the index.
- Treat any non-zero
loki_discarded_samples_total{reason="invalid_labels"} rate as a config bug, not background noise.
4. Runtime overrides over restarts
Per-tenant limits hot-reload from overrides.yaml — no restart needed. Verify a change took via curl /runtime_config.
overrides:
my-tenant:
ingestion_rate_mb: 16
ingestion_burst_size_mb: 32
max_global_streams_per_user: 50000
Most Helm charts hot-reload the runtime ConfigMap via the loki-sc-rules sidecar or equivalent. Confirm before touching the StatefulSet.
5. Upgrade discipline
For Loki minor/patch upgrades:
- Read the upgrade guide for the target version, every time. Loki has historically been chatty about breaking changes in point releases.
- Validate both old and new configs with
loki -verify-config in CI.
- For HA: roll one ingester at a time, watch the ring, wait for the new pod to reach ACTIVE before moving on. PDB
maxUnavailable: 1.
For schema bumps:
- Always set
from: at least 24h in the future (next UTC midnight is the safe default). Never backdate.
- Roll out the schema change first, on the old binary. Then upgrade the binary 48h later. Two separate releases.
For object-storage migrations:
- Both old and new
period_config entries must point at the same chunk path or the old data becomes unreachable. Keep the legacy block until retention has aged out everything in it.
6. Observability of Loki itself
Bake these alerts in from day one:
rate(loki_discarded_samples_total[5m]) by (reason) > 0 — any reason except a brief blip is a config bug.
loki_ingester_wal_replay_active == 1 for 30m — stuck WAL.
rate(loki_ingester_chunks_flushed_total[10m]) == 0 and rate(loki_distributor_lines_received_total[10m]) > 0 — object storage broken.
time() - loki_compactor_apply_retention_last_successful_run_timestamp_seconds > 3600 — compactor stalled.
loki_ingester_memory_streams > <cap> — cardinality climbing toward limit.
cortex_query_frontend_queue_length > 100 — query saturation.
loki_ruler_rule_evaluation_failures_total > 0 — broken alert/recording rule.
Scrape Loki's own /metrics into the same Prometheus that scrapes the apps it ingests from. If Loki goes down, you still need to see why.
7. Output discipline
- Give complete, runnable commands — no placeholders for endpoint URLs, no
<your-bucket> stubs without a comment showing how to compute it.
- Call out destructive operations explicitly (
POST /ring?forget=, rm on WAL segments, disabling S3 lifecycle rules, helm rollback).
- For any limit-tuning recommendation, state the capacity math behind it (the
max_outstanding formula, the per-stream-on-one-ingester memory implication, the cross-region latency multiplier).
- When uncertain about a Loki version's behavior, say so and name the version where the behavior changed. Loki has moved fast between 2.x → 3.x → 3.7.
Most common pitfalls (top P1 hit list)
The seven things that take down clusters most often. Memorize these — they cover the bulk of real incidents.
- Helm chart deployment-mode collision —
singleBinary and simple-scalable replicas both set; install hard-fails. Pick one.
- Schema period not 24h on TSDB / modern boltdb-shipper — Loki refuses to start.
- S3 lifecycle policy shorter than retention — silent data loss; Loki never sees the deletes coming.
- Replication factor exceeds healthy ingester count after a node drain — writes 500 until the ring heals.
- IRSA / Workload Identity misconfigured — chunks never flush; WAL fills; ingester eventually OOMs.
- High-cardinality label leaks — request ID, trace ID, or full URL added as a label. Streams explode, queries melt.
- Compactor not actually running (or running as two replicas) — retention silently broken.
Diagnostic command cheatsheet
The 10 highest-value triage commands — discards-by-reason, ring health, resolved config, runtime overrides, service status, --analyze-labels, memberlist, ingester flush/shutdown, and a CPU pprof — live in references/command-reference.md. They cover ~80% of incident first-response.
Example prompts
- "Our distributor is logging
rate limit exceeded for user fake and Grafana is missing data. Walk me through the fix without dropping logs."
- "
loki_ingester_memory_streams jumped from 5k to 80k overnight. What broke and how do I find it?"
- "The Loki StatefulSet won't start after a Helm upgrade — pod logs say
active schema or upcoming schema are not set to a period of 24h. How do I recover?"
- "OTel collector is logging HTTP 404 with URL ending in
/loki/api/v1/push/v1/logs. What did I configure wrong?"
- "We added an S3 lifecycle rule and now queries return
NoSuchKey for chunks within retention. What do I do?"
- "Two ingesters showed up as UNHEALTHY in
/ring after a node drain. Writes are returning 500. Safe to forget them?"
- "How do I tell if my compactor is actually applying retention, and what alert should I have on it?"
- "Promtail keeps re-reading every log file after a restart, and we're getting flooded with
too_far_behind discards. Fix?"
Related skills
k8s-nextjs-deploy — Kubernetes deployment patterns (StatefulSets, PDBs, NetworkPolicies) Loki runs on top of
postgres-ops — Postgres slow-query logs are a common Loki ingestion source; see its observability section for the shipping pattern
1---2name: loki-best-practices3description: Operational Grafana Loki troubleshooting for already-deployed stacks — ingest rejections (rate/stream limits, out-of-order, line-too-long, invalid labels), WAL replay and chunk-flush failures, stuck compactors, ring/memberlist health, limits_config and per-tenant overrides, label cardinality, Promtail/Alloy/OTel push errors, slow LogQL queries, IRSA/object-storage breakage, and schema-period upgrades. Use whenever the user mentions Loki, LogQL, Promtail, Alloy, log ingestion or discards, `loki_discarded_samples_total`, the `/ring`, WAL replay, the Loki Helm chart, log cardinality, or any Loki incident, push failure, slow query, schema upgrade, or HA problem — even in a Grafana-stack log context that doesn't say "Loki". Do NOT use for greenfield log-tooling choices — this assumes Loki is already deployed. (Full trigger list and per-surface diagnostics are in the body's "When to use" section.)4---56# loki-best-practices78Production-grade Grafana Loki operations: diagnosis, performance, storage, HA, and observability. Assume the user is a senior engineer running Loki in Kubernetes — skip introductions to LogQL and chunks and go straight to evidence-driven SRE workflow.910## When to use1112Trigger on operational Loki tasks:1314- **Ingest incidents**: HTTP 429/4xx from distributor, `loki_discarded_samples_total` rising, push retries from Promtail/Alloy/OTel, "rate limit exceeded", "stream limit", "out of order", "too far behind", "line too long", "invalid labels".15- **WAL & flush failures**: ingester CrashLoopBackOff during replay, `loki_ingester_wal_replay_active` stuck at 1, `loki_ingester_chunks_flushed_total` flat while ingest is hot, OOMKilled on startup, full WAL PVC.16- **Deployment & config**: Helm chart `singleBinary` vs `simple-scalable` mode collisions, "schema period must be 24h", TSDB migration that hides pre-migration data, missing object-storage backend on scalable targets, replication-factor vs ingester-count mismatch.17- **Integration breakage**: Grafana datasource "No data" with active streams, multi-tenant `X-Scope-OrgID` returning partial results, Promtail positions lost on restart, OTel structured metadata rejected, OTel `endpoint` misconfigured (`/loki/api/v1/push/v1/logs` 404), memberlist DNS failures, NetworkPolicies blocking gossip/HTTP/gRPC.18- **Query performance**: "too many outstanding requests", `max_query_length` exceeded, `max_query_series` reached, high-cardinality label explosion, expensive `|~` regex without prior `|=` line filter, sharding/parallelism tuning.19- **Storage**: S3 `WebIdentityErr` / IRSA failures, S3 lifecycle deleting active chunks, compactor stuck or running as two replicas, `context deadline exceeded` on object storage, index-gateway slow chunk reads.20- **HA**: UNHEALTHY ring entries after node loss, ungraceful shutdown leaving stranded chunks, schema bump during rolling upgrade, two compactors running simultaneously, ruler firing in duplicate or not at all, memberlist IP reuse / ghost members.21- **Observability of Loki itself**: which metrics to alert on, what to scrape, what cache hit ratios to target.2223Do NOT trigger for:2425- Greenfield "should we use Loki or Elasticsearch / OpenSearch" decisions — that's an architecture conversation, not an ops skill.26- Choosing between Promtail and Alloy for a new install (use the Grafana Agent migration guide).27- Generic LogQL syntax questions ("what does `unwrap` do") with no operational context.2829## Instructions3031### 1. Diagnose like an SRE3233For any Loki incident, follow hypothesis → evidence → fix → verification. Never propose a fix without naming the metric or endpoint that proves the diagnosis first.3435**Always start at `loki_discarded_samples_total`** — the write path tells you almost everything. Group by `reason` and the failure mode tells you what to fix:3637```promql38sum by (reason) (rate(loki_discarded_samples_total[5m]))39```4041If the rate is non-zero, the `reason` label is the entire diagnosis — map each reason to its root-cause class and fix path in **[references/command-reference.md](references/command-reference.md#discard-reason--root-cause--fix)**. That file also carries the 10-command triage cheatsheet and the distroless-image caveat (run curls from a sidecar or `kubectl port-forward svc/loki 3100:3100`, never `sh -c` — the official image has no shell).4243### 2. Triage by failure surface4445The playbook breaks into six surfaces. Classify the symptom first, then look up the matching entry in **[references/triage-playbook.md](references/triage-playbook.md)** — each entry has the symptoms, the proof to gather, and the fix. Don't skip classification; it saves the wrong-rabbit-hole tax.4647- **Ingestion (write path)** — 4xx on `/loki/api/v1/push`, `loki_discarded_samples_total{reason}` climbing; fix the label set before raising per-stream limits, persist Promtail positions, unstick WAL replay.48- **Deployment & configuration** — CrashLoopBackOff, Helm single-vs-scalable mode collision, 24h schema period, RF-vs-ingester-count math, missing object-storage backend.49- **Integration** — Grafana "No data" with a healthy querier, OTel `endpoint` 404s / structured-metadata rejections, memberlist DNS, NetworkPolicy gaps on 7946/3100/9095.50- **Query performance** — "too many outstanding requests", "query length too large", cardinality explosions, expensive `|~` regex before a `|=` filter.51- **Storage backend** — S3 `WebIdentityErr`/IRSA, S3 lifecycle deleting live chunks, stuck/duplicate compactor, cross-region latency.52- **High availability** — UNHEALTHY ring entries after node loss, schema-boundary rolling upgrades, compactor singleton conflict, ruler HA, flush-before-forget.5354### 3. Label design is the load-bearing decision5556Most Loki problems trace back to one label that should never have been a label. Treat these as code-review red lines:5758- **Cap labels at ~10–15 per stream.** More than that and the index grows linearly with stream count.59- **Never label by:** request ID, trace ID, user ID, session ID, full URL path, hash, IP, or anything else that takes more than ~100 distinct values per workload.60- **Move variable values to structured metadata** (requires Loki ≥ 3.0, schema v13, `allow_structured_metadata: true`). It's queryable via `| label_format` at read time without bloating the index.61- **Treat any non-zero `loki_discarded_samples_total{reason="invalid_labels"}` rate as a config bug,** not background noise.6263### 4. Runtime overrides over restarts6465Per-tenant limits hot-reload from `overrides.yaml` — no restart needed. Verify a change took via `curl /runtime_config`.6667```yaml68overrides:69 my-tenant:70 ingestion_rate_mb: 1671 ingestion_burst_size_mb: 3272 max_global_streams_per_user: 5000073```7475Most Helm charts hot-reload the runtime ConfigMap via the `loki-sc-rules` sidecar or equivalent. Confirm before touching the StatefulSet.7677### 5. Upgrade discipline7879For Loki minor/patch upgrades:8081- Read the [upgrade guide](https://grafana.com/docs/loki/latest/setup/upgrade/) for the target version, every time. Loki has historically been chatty about breaking changes in point releases.82- Validate both old and new configs with `loki -verify-config` in CI.83- For HA: roll one ingester at a time, watch the ring, wait for the new pod to reach ACTIVE before moving on. PDB `maxUnavailable: 1`.8485For schema bumps:8687- **Always set `from:` at least 24h in the future** (next UTC midnight is the safe default). Never backdate.88- Roll out the schema change first, on the old binary. Then upgrade the binary 48h later. Two separate releases.8990For object-storage migrations:9192- Both old and new `period_config` entries must point at the same chunk path or the old data becomes unreachable. Keep the legacy block until retention has aged out everything in it.9394### 6. Observability of Loki itself9596Bake these alerts in from day one:9798- `rate(loki_discarded_samples_total[5m]) by (reason) > 0` — any reason except a brief blip is a config bug.99- `loki_ingester_wal_replay_active == 1 for 30m` — stuck WAL.100- `rate(loki_ingester_chunks_flushed_total[10m]) == 0 and rate(loki_distributor_lines_received_total[10m]) > 0` — object storage broken.101- `time() - loki_compactor_apply_retention_last_successful_run_timestamp_seconds > 3600` — compactor stalled.102- `loki_ingester_memory_streams > <cap>` — cardinality climbing toward limit.103- `cortex_query_frontend_queue_length > 100` — query saturation.104- `loki_ruler_rule_evaluation_failures_total > 0` — broken alert/recording rule.105106Scrape Loki's own `/metrics` into the same Prometheus that scrapes the apps it ingests from. If Loki goes down, you still need to see why.107108### 7. Output discipline109110- Give complete, runnable commands — no placeholders for endpoint URLs, no `<your-bucket>` stubs without a comment showing how to compute it.111- Call out destructive operations explicitly (`POST /ring?forget=`, `rm` on WAL segments, disabling S3 lifecycle rules, `helm rollback`).112- For any limit-tuning recommendation, state the capacity math behind it (the `max_outstanding` formula, the per-stream-on-one-ingester memory implication, the cross-region latency multiplier).113- When uncertain about a Loki version's behavior, say so and name the version where the behavior changed. Loki has moved fast between 2.x → 3.x → 3.7.114115## Most common pitfalls (top P1 hit list)116117The seven things that take down clusters most often. Memorize these — they cover the bulk of real incidents.1181191. **Helm chart deployment-mode collision** — `singleBinary` and `simple-scalable` replicas both set; install hard-fails. Pick one.1202. **Schema period not 24h** on TSDB / modern boltdb-shipper — Loki refuses to start.1213. **S3 lifecycle policy shorter than retention** — silent data loss; Loki never sees the deletes coming.1224. **Replication factor exceeds healthy ingester count after a node drain** — writes 500 until the ring heals.1235. **IRSA / Workload Identity misconfigured** — chunks never flush; WAL fills; ingester eventually OOMs.1246. **High-cardinality label leaks** — request ID, trace ID, or full URL added as a label. Streams explode, queries melt.1257. **Compactor not actually running** (or running as two replicas) — retention silently broken.126127## Diagnostic command cheatsheet128129The 10 highest-value triage commands — discards-by-reason, ring health, resolved config, runtime overrides, service status, `--analyze-labels`, memberlist, ingester flush/shutdown, and a CPU pprof — live in **[references/command-reference.md](references/command-reference.md#diagnostic-command-cheatsheet)**. They cover ~80% of incident first-response.130131## Example prompts132133- *"Our distributor is logging `rate limit exceeded for user fake` and Grafana is missing data. Walk me through the fix without dropping logs."*134- *"`loki_ingester_memory_streams` jumped from 5k to 80k overnight. What broke and how do I find it?"*135- *"The Loki StatefulSet won't start after a Helm upgrade — pod logs say `active schema or upcoming schema are not set to a period of 24h`. How do I recover?"*136- *"OTel collector is logging HTTP 404 with URL ending in `/loki/api/v1/push/v1/logs`. What did I configure wrong?"*137- *"We added an S3 lifecycle rule and now queries return `NoSuchKey` for chunks within retention. What do I do?"*138- *"Two ingesters showed up as UNHEALTHY in `/ring` after a node drain. Writes are returning 500. Safe to forget them?"*139- *"How do I tell if my compactor is actually applying retention, and what alert should I have on it?"*140- *"Promtail keeps re-reading every log file after a restart, and we're getting flooded with `too_far_behind` discards. Fix?"*141142## Related skills143144- [`k8s-nextjs-deploy`](../k8s-nextjs-deploy/SKILL.md) — Kubernetes deployment patterns (StatefulSets, PDBs, NetworkPolicies) Loki runs on top of145- [`postgres-ops`](../postgres-ops/SKILL.md) — Postgres slow-query logs are a common Loki ingestion source; see its observability section for the shipping pattern