IL-0100 PostHog · 2025-10-21kubernetesaws
PostHog 기능 플래그 서비스는 2025-10-21 부터 10-30 까지 열흘 동안 장애를 네 번 겪었다. 21일 103분, 24일 72분, 28일 123분, 29일에서 30일 사이 7시간 9분이었고, 오류나 심한 지연이 이어진 시간을 합치면 14시간이 넘었다. 네 번 중 세 번은 원인이 같았다. 노드가 파드의 CPU 요청에 비해 작아서 쿠버네티스가 노드 하나에 파드를 너무 많이 넣었고, 노드 CPU 사용률이 90% 를 넘었다.
CPU 가 포화되자 두 가지 일이 벌어졌다. 첫째, 요청이 느려지면 로드밸런서와 서비스 사이의 Envoy 가 재시도했다. 재시도할 때마다 새 요청이 생겼고, 요청마다 Redis 를 여러 번 동시에 읽었다. 느려진 요청 하나가 수십 개의 Redis 작업으로 불어났다. 캐시 미스가 나면 플래그와 팀 상태 전체를 Postgres 에서 동기로 읽어 Redis 에 썼기 때문에 Redis 에 쓰기가 한꺼번에 몰렸다. 둘째, 파드마다 Postgres 연결 풀을 따로 두는데, 풀을 만들 때 하는 TLS 핸드셰이크와 인증과 연결 수립은 CPU 를 많이 쓴다. CPU 가 90% 를 넘자 새 파드가 20초 시작 타임아웃 안에 풀을 만들지 못해 크래시 루프에 빠졌고, 정상 파드 수가 줄었다.
21일에는 타임아웃 오류를 줄이려던 배포가 이 연쇄를 일으켰다. 2분 안에 롤백했지만 Postgres 에서 Redis 로 가는 대량 전송과 동시 연결 급증은 멈추지 않았고, Redis 메모리가 바닥났다. 팀은 23:00 UTC 부터 연결과 Redis 쓰기를 조절해 23:28 UTC 에 복구했다. 28일에는 일상 배포가 US 플래그 파드를 롤아웃시키면서 같은 일이 되풀이됐다. 팀은 롤아웃을 멈추고 US 파드를 0개로 줄여 Redis 가 안정되기를 기다린 뒤, 파드를 차례로 다시 올려 21:31 UTC 에 복구했다. 두 번 모두 Redis 를 같이 쓰는 PostHog 본 애플리케이션도 영향을 받았다.
29일에서 30일 사이에는 CPU 포화가 곧바로 느린 쿼리로 나타났다. Redis 와 Postgres 는 정상이었지만 노드 CPU 압박 때문에 연결과 응답이 느려졌다. 하드웨어 변경을 롤백하자 오류는 대부분 가라앉았지만 지연은 남았다. 플래그 서비스의 파드 리소스 요청을 늘려 노드당 파드 수를 줄이자 노드 CPU 사용률이 내려가 서비스가 정상으로 돌아왔다.
24일 장애는 원인이 달랐다. 21일 장애 뒤 보호 장치로 /flags 엔드포인트에 IP 기반 레이트 리밋을 넣었다. 그런데 이 레이트 리밋을 맡은 Rust 미들웨어 tower-governor 는 기본값으로 X-Forwarded-For 헤더를 믿지 않아서, 모든 트래픽을 로드밸런서 IP 하나에서 온 것으로 봤다. 레이트 리밋은 서비스에 직접 요청을 보내는 시험에서만 확인했고 운영 로드밸런서 뒤에서는 시험하지 않았다. 18:01 UTC 부터 대부분 요청이 429 를 받았고, 19:12 UTC 에 레이트 리밋을 끄자 복구됐다.
2025-10-21 21:45 UTC, 타임아웃 오류를 줄이려는 배포 직후 자동 모니터링이 기능 플래그 서비스의 오류율 상승을 잡았다. 2분 만에 롤백했지만 오류율은 내려가지 않았고, US 데이터센터에서 평가 요청의 약 38% 가 오류를 냈다.
21일에는 ElastiCache 의 Redis 메모리가 바닥났고, Postgres 연결이 급증해 연결 풀이 감당하지 못했다. 이어서 Postgres 에서 Redis 로 대량의 데이터가 옮겨지고 있는 것이 보였다. 같은 Redis 를 쓰는 PostHog 본 애플리케이션까지 영향을 받았다.
10월 28일에는 플래그 코드를 바꾸지 않은 일상 배포가 US 리전의 플래그 파드 롤아웃을 일으켰다. 새 파드는 20초 시작 타임아웃 안에 Postgres 에 연결하지 못해 크래시 루프에 빠졌다. Redis 쓰기가 폭증해 키 축출이 일어나면서 캐시를 사실상 쓸 수 없게 됐고, US 요청의 약 34% 가 실패했다. 팀은 처음에 21일과 같은 대응을 시도했다.
10월 24일에는 IP 기반 레이트 리밋을 배포한 직후 전 세계 평가 요청의 약 97% 가 429 오류를 받았다. 429 경보가 없어서 고객 신고로 알았다. 10월 29일 22:30 UTC 부터 30일 05:39 UTC 까지는 오류율과 지연이 올라갔지만 Redis 와 Postgres 지표는 정상이었다. 실제 원인은 노드 CPU 사용률이 90% 를 넘은 것이었고, 응답 시간이 평소의 몇 배로 늘었다. CPU 경보가 아예 없어서 7시간 넘게 원인을 잡지 못했다.
원인 ? 용량 한계
21일에는 배포 2분 뒤 자동 모니터링이 오류율 상승을 잡았고, 28일에는 온콜이 높은 오류 수 경보를 받았다. PostHog 는 대부분의 경우 2분 안에 감지했다고 정리했다. 24일에는 429 오류 경보가 없어서 18:10 UTC 부터 들어온 고객 신고로 알았고, 감지까지 62분이 걸렸다. CPU 경보가 아예 없어서 공통 원인인 CPU 포화는 오래 드러나지 않았고, 29일에서 30일 사이 장애는 7시간 넘게 이어졌다.
가장 큰 교훈은 CPU 크기 산정이었다. 노드가 파드 요청에 비해 작으면 노드 하나에 파드가 너무 많이 몰리고, CPU 포화가 과도한 병렬 처리, 연결 풀 고갈, 느린 쿼리처럼 서로 다른 모습으로 나타난다. 증상만 고친 수정 때문에 21일의 장애가 28일에 되풀이됐다. PostHog 는 CPU 를 넉넉히 받은 파드를 적은 수로 돌리는 편이 낫다고 정리했다.
기능 플래그 서비스와 본 애플리케이션이 Redis 를 같이 쓰면 한쪽 과부하가 다른 쪽을 쓰러뜨리는 단일 장애 지점이 된다. CPU 지표는 처음부터 경보로 걸어야 하고, Postgres 에서 Redis 로 가는 전송 급증도 경보 대상이다. 재시도, 캐시 채우기, 연결 생성에 한도가 없으면 장애가 커진다. 과부하 패턴은 운영 트래픽에서만 드러났으므로 부하 시험이 필요하고, 중요한 설정은 배포 없이 바꿀 수 있어야 한다.
같은 원인 · 공통 애플리케이션 데이터베이스, 인메모리 캐시
같은 원인 · 공통 애플리케이션 데이터베이스, 파드 스케줄링과 배치
같은 원인 · 공통 노드 풀
같은 원인 · 공통 노드 풀
같은 원인 · 공통 인메모리 캐시
같은 원인 · 공통 노드 풀
같은 원인 · 공통 노드 풀
같은 원인 · 공통 노드 풀
같은 원인 · 공통 파드 스케줄링과 배치
같은 원인 · 공통 애플리케이션 데이터베이스
같은 원인 · 공통 노드 풀
같은 원인 · 공통 애플리케이션 데이터베이스
같은 원인 · 공통 노드 풀
같은 원인 · 공통 애플리케이션 데이터베이스
같은 원인 · 공통 파드 스케줄링과 배치