Loki ingester: replay_memory_ceiling unset (4GB default vs 1Gi limit) makes WAL-replay OOM a self-sustaining crash-loop
Nobody has claimed this yet.
Assessment
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Newbie friendliness
- 74/100
- Issue type
- Bug
- Clarity
- Mostly clear
- Activity status
- Active
- Tech stack
- helm, kubernetes, yaml
- Domain
- infrastructure, observability
Research direction
Start with helmfile.d/snippets/defaults.yaml and inspect how the rendered Loki ConfigMap and ingester settings are produced. Confirm the WAL replay ceiling is set below the 1Gi container limit, then render the configuration and verify the resulting ingester includes the expected wal setting without changing the other defaults.
Written by the indexing model from the issue text.
Description
Summary
The shipped Loki ingester defaults make WAL replay OOM-prone, and because the WAL survives container restarts the resulting crash-loop is self-sustaining — an ingester that OOMs once never recovers without manual intervention.
Two of three ingesters on an LKE-E cluster sat in CrashLoopBackOff for 16 days (3120 and 4498 restarts) before anyone noticed. With replication_factor: 1 there is no quorum fallback, so the distributor returned 500 on every push routed to a dead ingester — log ingestion was silently dropped for the whole period.
Environment
| apl-core | v6.0.0 |
| Kubernetes | v1.34.2 (LKE-E) |
| Loki chart | loki-6.55.0, Loki 3.6.7 |
| Mode | deploymentMode: Distributed, object storage enabled |
| Resources | shipped defaults, unmodified |
Root cause
ingester.wal.replay_memory_ceiling is never set. The rendered loki ConfigMap contains no wal block at all:
ingester:
chunk_encoding: snappy
So it falls back to Loki's default ceiling of 4GB, while helmfile.d/snippets/defaults.yaml gives the container:
ingester:
requests: { cpu: 250m, memory: 512Mi }
limits: { cpu: 500m, memory: 1Gi }
Loki is therefore permitted to consume 4× the container limit during WAL replay before it begins flushing to relieve memory pressure. Any ingester with a non-trivial WAL is guaranteed to be OOMKilled before replay completes.
Why it never recovers
The ingester data volume is an emptyDir (no volumeClaimTemplates on the StatefulSet). An emptyDir survives container restarts within the same pod, so:
OOM during WAL replay → container restarts in place → same WAL is still there
→ replay OOMs again → never completes → never flushes → WAL never truncates
→ repeat forever
Restarting the container cannot break the loop; only deleting the pod (discarding the emptyDir) does. That is not a discoverable remedy — it looks like data destruction, and with replication_factor: 1 it genuinely is for anything unflushed.
Evidence that this is a limit/ceiling problem, not workload sizing
From the same cluster, the one surviving ingester while carrying all three shares of traffic:
- steady state: ~790Mi — comfortably under the 1Gi limit
- OOMKilled at ~892Mi during WAL replay (exit 137, ~10s in, immediately after
msg="recovering from WAL")
Steady-state ingestion fits the limit. Replay does not. That is exactly the gap replay_memory_ceiling exists to close.
The two dead ingesters had accumulated large WALs, so every replay attempt blew the limit; the survivor lived only because it had flushed recently. After deleting the two crash-looping pods, all three came back at 55–137Mi and ingestion recovered immediately — confirming the workload itself fits comfortably.
Impact
- Silent, indefinite loss of log ingestion (
POST /loki/api/v1/push→500) replication_factor: 1means a single dead ingester drops every stream hashed to it, with no fallback- Self-sustaining: the failure cannot self-heal, and the fix is non-obvious
- Cascade risk: when one ingester dies its streams rehash onto the others, raising their memory and their replay cost — which is a plausible path from one restart to a whole-cluster outage
Suggested fixes
1. Set ingester.wal.replay_memory_ceiling relative to the container limit (Loki's own guidance is a fraction of available memory — commonly ~50%). This is the targeted fix: it makes replay flush early instead of OOMing, so the loop cannot start. With the current defaults, ~512Mi against the 1Gi limit.
2. Raise the ingester memory limit. 1Gi leaves ~10% headroom over observed steady state before replay is even considered. Worth revisiting regardless of (1).
3. Consider persistence for the ingester. With an emptyDir the WAL is neither durable nor inspectable, and replication_factor: 1 means unflushed data has no second copy. If the emptyDir is intentional, that is a reasonable trade — but it deserves to be explicit, because it interacts badly with (1) being unset.
(1) alone would have prevented this outage.
Workaround for anyone hitting this
kubectl -n monitoring delete pod loki-ingester-<n> # NOT a container restart
Deleting the pod discards the emptyDir and its poisoned WAL, so the ingester starts clean with no replay. Safe while at least one other ingester is healthy. Unflushed data in that WAL is lost — but with replication_factor: 1 and a replay that has already failed thousands of times, it was unrecoverable anyway.
- Dominant language
- Go Template
- Stars
- 2.3k
- Forks
- 186
- Avg merge
- 3d 11h
- Merged PRs (30d)
- 66
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from linode/apl-core
-
Difficulty 5/5 Over a week Newbie friendliness 35/100
-
ready-for-agent
Difficulty 5/5 Over a week Newbie friendliness 35/100
-
ready-for-agent
Difficulty 5/5 Over a week Newbie friendliness 30/100
-
Everything-is-an-app cutover: Monitoring group (prometheus, grafana, promtail, alertmanager, otel) Openready-for-agent
Difficulty 4/5 3-5 days Newbie friendliness 45/100
-
Everything-is-an-app cutover: Storage & Database group (harbor, gitea, loki, kubeflow-pipelines) Openready-for-agent
Difficulty 4/5 3-5 days Newbie friendliness 48/100
Similar issues
-
Difficulty 1/5 Under an hour Newbie friendliness 84/100
-
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
sympozium-ai/sympozium#627 ·
-
repo:raw-data
Difficulty 2/5 1-3 hours Newbie friendliness 76/100
hotosm/raw-data-api#316 · 1 comment ·
-
agentic-workflows cascade-suspected
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
-
willow
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
openedx/openedx-authz#460 ·