{"id":"IL-0044","org":"Saifeddine Rajhi","date":"2024-06-14","docs":[],"title":{"en":"Prometheus OOMKilled in a loop while replaying its write-ahead log after a kube-prometheus-stack upgrade","ko":"kube-prometheus-stack 업그레이드 뒤 Prometheus 가 WAL 재생 중 메모리를 50Gi 넘게 써서 시작할 때마다 OOMKilled 로 죽었다"},"domain":["kubernetes","observability"],"impact":{"scope":"Prometheus monitoring in the cluster unavailable while the pod crash-looped","users":"unknown","region":"unknown"},"review":{"written_by":"session","reviewed_at":null,"reviewed_by":null},"lessons":{"en":"Size Prometheus for its restart, not only for steady state: WAL replay can need far more memory than normal running, and a Prometheus that already runs near its limit can be OOM-killed during the replay. Much of Prometheus memory use comes from cardinality, which promtool tsdb analyze can measure even when Prometheus itself is down.","ko":"Prometheus 를 메모리 제한에 가깝게 운영하면 WAL 재생 중의 작은 오버헤드로도 OOM 이 난다. 가비지 컬렉션은 들쭉날쭉하고 카디널리티나 쿼리는 갑자기 늘 수 있으므로 메모리에 여유를 둔다.\n\nPrometheus 메모리 문제의 상당 부분은 카디널리티에서 온다. Prometheus 가 떠 있지 않을 때는 promtool tsdb analyze 로 카디널리티가 높은 지표를 찾는다.\n\n메모리 제한만 올려서는 답이 되지 않았다. 메모리가 치솟는 것은 재생 단계뿐이었기 때문이다."},"related":[],"sources":[{"url":"https://seifrajhi.github.io/blog/prometheus-manage-memory-overload/","kind":"blog","license":"unknown","archived":null}],"summary":{"en":"When Prometheus restarts it rebuilds its in-memory state by replaying the write-ahead log, the on-disk record of recent samples. In this cluster the replay needed two to three times the memory Prometheus used while running, more than 50Gi against about 30Gi in steady state, so each start ended in an OOMKill before Prometheus became ready. The problem showed up while the team was upgrading the kube-prometheus-stack chart.\n\nThe author links the problem to an upstream GitHub issue open since 2020 and to two common situations: scraping too many series, and running Prometheus close to its memory limit so that any replay overhead pushes it over. Both come down to label cardinality. With Prometheus down, PromQL could not be used to find the expensive metrics, so the author ran promtool tsdb analyze inside the Prometheus pod with kubectl exec; it pointed to high-cardinality metrics such as haproxy_server_http_responses_total. Reducing that metric, giving Prometheus more memory, or moving it to a node group with more memory are the fixes the author proposes; the write-up does not say which one ended the crash loop.","ko":"글쓴이의 팀은 2024년 6월 14일 kube-prometheus-stack 헬름 차트를 업그레이드하다가 Prometheus 파드가 Ready 가 되지 않는 것을 발견했다. Prometheus 는 재시작하면 디스크에 먼저 써 둔 로그인 WAL 을 읽어 데이터를 복구한다. 이 WAL 재생 과정이 메모리를 많이 쓴다. 평소 30Gi 정도로 돌던 Prometheus 가 WAL 재생 중에는 50Gi 넘게 요구했고, 시작할 때마다 OOMKilled 로 죽었다.\n\n글쓴이는 이 문제가 GitHub 에 2020년부터 열려 있는 오래된 이슈와 같다고 보았다. 재생 중 메모리 오버헤드가 300% 까지 오르면 평소 메모리 제한의 70% 로 돌던 Prometheus 도 쉽게 죽는다. 반면 Google 의 최근 벤치마크에서는 재생 중 CPU 사용량은 두 배로 늘지만 메모리 오버헤드는 1% 에서 5% 정도였다. 글쓴이는 재생 중 OOM 이 더 큰 문제가 드러난 증상일 수 있다고 보았고, 흔한 경우로 두 가지를 들었다. 시계열이나 샘플을 너무 많이 수집하는 것, 그리고 메모리 제한에 너무 가깝게 운영하는 것이다. 이런 문제는 대개 지표의 레이블 값 조합 수, 곧 카디널리티와 얽혀 있다.\n\nPrometheus 가 떠 있지 않으면 PromQL 로 카디널리티를 볼 수 없다. 그래서 글쓴이는 Prometheus 파드에 kubectl exec 로 들어가 promtool tsdb analyze . 를 실행해 haproxy_server_http_responses_total 처럼 카디널리티가 높은 지표를 찾았다. 그런 지표를 고치거나 줄이면 메모리 부담이 줄어든다. 글쓴이는 메모리를 더 주거나 메모리가 넉넉한 노드 그룹에 Prometheus 를 배포하는 방법도 함께 제시했다."},"trigger":{"en":"During the kube-prometheus-stack Helm chart upgrade, Prometheus had to replay its write-ahead log on startup, and the replay needed more memory than its limit allowed.","ko":"kube-prometheus-stack 헬름 차트를 업그레이드하던 중 Prometheus 가 시작하면서 WAL 을 재생했고, 이 과정에서 메모리 사용량이 메모리 제한을 넘었다."},"severity":"unknown","symptoms":["oom-killed","readiness-probe-failures","monitoring-down"],"timeline":{"detected_after":null,"resolved_after":null},"date_kind":"incident","detection":{"en":"The team noticed pods not ready after the Helm chart upgrade and found the OOMKilled termination with kubectl describe pod.","ko":"팀은 kube-prometheus-stack 헬름 차트를 업그레이드하던 중 Prometheus 파드를 포함한 여러 파드가 Ready 가 되지 않는 것을 보았다. kubectl describe pod 와 Prometheus 로그로 OOMKilled 를 확인했다."},"components":["prometheus","memory-limit"],"root_cause":"capacity","remediation":[{"text":{"en":"ran promtool tsdb analyze inside the Prometheus pod to find high-cardinality metrics such as haproxy_server_http_responses_total","ko":"Prometheus 파드에 kubectl exec 로 들어가 promtool tsdb analyze . 를 실행해 haproxy_server_http_responses_total 처럼 카디널리티가 높은 지표를 찾았다."}}],"action_items":[{"text":{"en":"Verify that the memory overhead during WAL replay stays within acceptable limits, for example 10 to 15%, and do not run Prometheus close to its memory limit","ko":"WAL 재생 중 메모리 오버헤드가 10% 에서 15% 정도의 허용 범위 안에 있는지 확인하고, Prometheus 를 메모리 제한에 가깝게 운영하지 않는다."}},{"text":{"en":"Optimise storage, scraping and remote write configuration and upgrade to newer Prometheus releases","ko":"Prometheus 의 저장, 스크레이프, remote write 설정을 정기적으로 최적화하고, 최적화가 들어간 새 릴리스로 업그레이드한다."}},{"text":{"en":"Recover automatically from OOM crash loops, for example by deleting the WAL on OOM events","ko":"OOM 크래시 루프에서 자동으로 복구하는 장치를 만든다. 예를 들어 OOM 이벤트가 나면 WAL 을 자동으로 지운다."}},{"text":{"en":"Enforce scrape limits so Prometheus stops scraping targets when memory use passes a threshold","ko":"메모리 사용량이 일정 기준을 넘으면 스크레이프를 멈추는 강제 스크레이프 제한을 둔다."}}],"contributing":["limit-unknown","no-spare-capacity"],"recurrence_of":null,"symptoms_text":{"en":"After an upgrade of the kube-prometheus-stack Helm chart, several pods stayed not ready. Among them was prometheus-prometheus-operator-prometheus-0, showing 3/4 Running and a recent termination.\n\nThe Prometheus log contained: level=info component=tsdb msg=\"Deletion of corrupted mmap chunk files failed, discarding chunk files completely\" err=\"cannot handle error: iterate on on-disk chunks: out of sequence m-mapped chunk for series ref 946594555 ...\". kubectl describe pod showed the container in State: Running but with Last State: Terminated and Reason: OOMKilled.\n\nPrometheus was being killed while replaying its write-ahead log (WAL) at startup. In normal operation it used around 30Gi of memory, but the replay needed more than 50Gi. Raising the memory limit alone was not seen as a fix, because the extra memory was needed only during the replay phase.","ko":"kube-prometheus-stack 헬름 차트를 업그레이드하는 중에 여러 파드가 Ready 가 되지 않았다. 그중 prometheus-prometheus-operator-prometheus-0 파드는 3/4 Running 상태였고 최근에 OOMKilled 로 종료된 기록이 있었다.\n\nPrometheus 로그에는 다음 줄이 남아 있었다.\nts=2024–06–14T09:49:10.383Z caller=head.go:840 level=info component=tsdb msg=\"Deletion of corrupted mmap chunk files failed, discarding chunk files completely\" err=\"cannot handle error: iterate on on-disk chunks: out of sequence m-mapped chunk for series ref 946594555, last chunk: [1718071739971, 1718075459971], new: [1718049629971, 1718053199971]\"\n\nkubectl describe pod 로 보면 파드는 Running 상태에 머물러 Ready 가 되지 않았고, 직전 상태는 Terminated 에 Reason 은 OOMKilled 였다.\nState: Running\n Started: Tue, 14 Jun 2024 10:04:03 +0200\nLast State: Terminated\n Reason: OOMKilled\n\nPrometheus 는 평소 30Gi 정도의 메모리로 문제없이 돌았지만, 재시작 후 WAL 을 재생하는 동안에는 50Gi 넘게 요구했다. 처음에는 메모리를 더 주는 방법을 생각했지만, 메모리가 치솟는 것은 재생 단계뿐이었으므로 메모리 제한만 올려서는 답이 되지 않았다."},"updated_at":"2026-11-10T00:00:38.528759Z","url":"https://benchlet.com/incidentlib/app/cases/IL-0044","org_slug":"saifeddine-rajhi"}