{"id":"IL-0193","org":"Grafana Labs","date":"2019-12-07","docs":[],"title":{"en":"A GCP persistent disk degradation took down a Cassandra StatefulSet whose OrderedReady policy blocked recovery, and Metrictank could not reconnect to the recreated pods, causing a 23-hour Graphite query outage","ko":"GCP SSD 영구 디스크 장애로 Cassandra 파드가 볼륨을 붙이지 못했고, OrderedReady 정책 때문에 나머지 파드도 다시 뜨지 못한 데다 Metrictank 가 바뀐 Cassandra IP 로 다시 연결하지 못해, Grafana Cloud Graphite 쿼리 일부가 최대 23시간 실패했다."},"domain":["kubernetes","gcp","observability"],"impact":{"scope":"Graphite queries needing data not in Metrictank's in-memory cache failed, peaking at 20% of queries, for 6 to 23 hours","users":"Grafana Cloud Graphite customers in the US-East cluster","region":"US-East"},"review":{"written_by":"session","reviewed_at":null,"reviewed_by":null},"lessons":{"en":"An OrderedReady StatefulSet cannot recover past a single pod whose volume will not attach; when every pod is down, the ordering that protects normal start-up blocks the whole cluster. A client that learns peer addresses only from live peers needs a way to go back to the service name when all peers are replaced at once.\n\nA long-tolerated bug with a config workaround can surface at the worst time; this one turned out to have a simple fix. Writing incoming data to a Kafka write-ahead log first meant all data could still be ingested after recovery, and Kafka on non-SSD disks escaped the disk incident. With both Metrictank bugs fixed, the team expects a similar event to take less than 3 hours to recover from instead of 23.","ko":"받은 데이터를 처리하기 전에 쓰기 선행 로그인 Kafka 에 먼저 저장해 둔 덕분에, Cassandra 를 되살린 뒤 그동안 받은 데이터를 모두 처리해 저장할 수 있었다. Kafka 가 SSD 가 아닌 디스크를 쓴 것은 운이 좋았다. 그렇지 않았다면 Kafka 도 Cassandra 와 같은 장애를 겪었을 수 있다. 23시간 동안 파라과이, 알바니아, 호주에 있는 세 사람이 시간대를 나눠 대응했다. 두 버그를 고친 지금은 같은 상황이 와도 복구가 23시간이 아니라 3시간 안에 끝날 것으로 본다. 오래 미룬 문제도 새로 보면 생각보다 간단히 풀릴 수 있다. 장애가 주말에 나서 고객 부하가 대부분 캐시 데이터를 읽는 알림 쿼리뿐이었던 것도 운이 좋았다."},"related":[],"sources":[{"url":"https://grafana.com/blog/how-a-gcp-persistent-disk-incident-snowballed-into-a-23-hour-outage-and-taught-us-some-important-lessons","kind":"blog","license":"unknown","archived":null}],"summary":{"en":"Grafana Cloud's hosted Graphite service runs Metrictank on Kubernetes, and in the US-East cluster Metrictank stores data in Cassandra, also on Kubernetes as a StatefulSet. On December 7, a GCP SSD persistent disk incident caused kernel deadlocks and filesystem corruption on the nodes, the nodes rebooted, and some could not reattach Cassandra's volumes afterwards. All Cassandra pods failed. The StatefulSet used the OrderedReady pod management policy, so Kubernetes started the pods one at a time, and the second pod's volume was still unavailable after GCP said most volumes were working, which blocked every pod after it. The team deleted and recreated the StatefulSet with a different pod management policy; all pods but one started, which was enough for Cassandra to work.\n\nMetrictank then could not reach the recovered cluster. It discovers Cassandra node IPs through the nodes it is already connected to, and because every pod had a new IP it had no connection left and never re-resolved the service hostname; this failure mode had never been considered. All Metrictank instances had to be restarted, which took more than 20 hours. Kafka retention was raised so no data was lost, and extra nodes were added so the restarts would not be starved of CPU.\n\nWhile replaying the larger-than-usual backlog from Kafka, some Metrictank instances hit an old deadlock: when the write queue to Cassandra is full the ingester blocks while holding an internal lock, and the callback that runs after a successful write needs the same lock, so the queue can never drain. The bug had appeared once or twice over many years and had been worked around by raising the queue size. Customers could not run these queries for 6 to 23 hours.","ko":"Grafana Labs 는 호스팅 Graphite 서비스를 여러 지역의 쿠버네티스 클러스터에서 돌린다. 클러스터마다 Metrictank 클러스터가 하나 이상 있고, 대부분은 역시 쿠버네티스 위에서 도는 Cassandra 를 저장소로 쓴다.\n\n12월 7일 GCP SSD 영구 디스크 장애로 노드에서 커널 교착과 파일 시스템 손상이 생겼고 노드가 자동으로 재부팅됐다. 다시 올라온 노드 일부는 Cassandra 파드의 볼륨을 붙이지 못했다. Cassandra StatefulSet 의 Pod Management Policy 는 OrderedReady 였다. 이 정책은 파드를 하나씩 순서대로 띄우고, 앞 파드가 정상이 되어야 다음 파드를 띄운다. 모든 Cassandra 파드가 죽은 상태에서 두 번째 파드의 볼륨이 여전히 붙지 않았고, 그 뒤 파드는 하나도 뜨지 못했다. Google 은 대부분의 볼륨이 정상이라고 알렸지만 이 볼륨 하나가 전체 복구를 막았다.\n\nGCP 장애가 풀린 뒤 팀은 StatefulSet 을 지우고 정책을 바꿔 다시 만들었다. 파드 하나를 빼고 모두 떴고, Cassandra 클러스터는 정상으로 돌아왔다. 그런데 Metrictank 는 다시 연결하지 못했다. Metrictank 는 Cassandra 노드 하나에라도 연결되어 있으면 그 노드에서 다시 만들어진 노드의 IP 를 받아 온다. 이번에는 모든 Cassandra 노드가 새 IP 로 다시 만들어져 연결이 완전히 끊겼다. 아무도 이런 장애를 미리 생각해 두지 않았다.\n\n연결을 되살리려고 모든 Metrictank 인스턴스를 재시작했고, 이 작업은 20시간 넘게 걸렸다. 긴 장애 동안 데이터를 잃지 않으려고 Kafka 토픽 보존 기간을 늘려 두었기 때문에, 재시작한 인스턴스는 평소보다 많은 데이터를 다시 읽어야 했다. 이 과정에서 오래된 Metrictank 버그가 복구를 더 늦췄다. Cassandra 쓰기 큐가 가득 찬 상태에서 이미 큐에 항목이 있는 시리즈의 데이터가 들어오면 교착이 생겼다. 수집기는 큐가 차면 일부러 멈추는데, 멈춘 자리에서 시리즈 상태를 지키는 내부 잠금을 쥐고 있었다. 큐가 비려면 Cassandra 에 저장한 뒤 부르는 콜백이 같은 잠금을 얻어야 했다. 결국 수집기는 큐가 비기를 기다리고, 큐는 잠금이 풀리기를 기다렸다. 팀은 이 버그를 고치려면 큰 리팩터링이 필요하다고 여겨 미뤄 왔고, 보통은 쓰기 큐 크기를 넉넉히 늘리는 설정 변경으로 피해 왔다."},"trigger":{"en":"A Google Cloud incident in which SSD persistent disks performed so poorly that many I/O operations failed for an extended period.","ko":"GCP SSD 영구 디스크의 성능이 크게 떨어져 많은 I/O 가 오랫동안 실패했고, 그 때문에 노드가 자동으로 재부팅됐다."},"severity":"unknown","symptoms":["high-error-rate","node-not-ready","pod-pending","service-unavailable"],"timeline":{"detected_after":null,"resolved_after":"23h"},"date_kind":"incident","detection":{"en":"An alert that Metrictank write instances could not flush their write queue to Cassandra. Error rates alone rose too slowly to notice quickly, because most queries were served from memory.","ko":"팀은 Metrictank 쓰기 인스턴스가 쓰기 큐를 비우지 못한다는 알림을 받고 장애를 알았다. Metrictank 가 대부분의 쿼리를 메모리에서 계속 처리해서 오류율은 아주 천천히 올라갔기 때문에, 이 알림이 없었다면 발견이 훨씬 늦었을 것이다."},"components":["persistent-volume","database","node-disk"],"root_cause":"cloud-provider","remediation":[{"text":{"en":"Added compute nodes hoping new nodes could attach the disks, which did not work","ko":"새 노드라면 디스크를 붙일 수 있을 것이라 보고 클러스터의 컴퓨트 노드를 늘렸지만 효과가 없었다."}},{"text":{"en":"Deleted and recreated the Cassandra StatefulSet with an updated pod management policy, so all pods but one started","ko":"GCP 장애가 풀린 뒤 Cassandra StatefulSet 을 지우고 Pod Management Policy 를 바꿔 다시 만들었다. 파드 하나를 빼고 모두 떠서 Cassandra 클러스터가 정상으로 돌아왔다."}},{"text":{"en":"Raised Kafka topic retention to avoid data loss during the long downtime","ko":"긴 장애 동안 데이터를 잃지 않도록 Kafka 토픽 보존 기간을 늘렸다."}},{"text":{"en":"Temporarily added nodes so Metrictank restarts were not slowed by CPU starvation","ko":"Metrictank 재시작이 CPU 부족으로 느려지지 않도록 클러스터에 노드를 임시로 더 붙였다."}},{"text":{"en":"Restarted all Metrictank instances, which took more than 20 hours, so they connected to the new Cassandra IPs","ko":"모든 Metrictank 인스턴스를 재시작해 새 Cassandra IP 로 연결되게 했다. 재시작은 20시간 넘게 걸렸다."}}],"action_items":[{"text":{"en":"Re-instantiate the gocql connection in Metrictank when it is detected dead, so it reconnects even when all Cassandra IPs change","ko":"모든 Cassandra IP 가 바뀌면 Metrictank 가 다시 연결하지 못하는 버그를 고쳤다. 연결이 죽은 것을 감지하면 gocql 연결을 새로 만든다."}},{"text":{"en":"Fix the Metrictank write-queue deadlock by updating an internal data structure with atomics instead of a lock","ko":"쓰기 큐가 가득 찬 상태에서 새 데이터가 들어오면 생기던 교착을 고쳤다. 내부 자료 구조 하나를 잠금으로 보호하는 대신 atomic 연산으로 갱신한다."}}],"contributing":["overlapping-failures","known-bug-unpatched","assumption-unverified"],"recurrence_of":null,"symptoms_text":{"en":"An alert fired because some Metrictank instances could not drain their write queue, which meant they could not write to Cassandra. Google had not yet published an incident, but a team member's Discord server, also hosted on GCP, was down, which pointed at GCP; Google confirmed an SSD persistent disk incident about 90 minutes after it began.\n\nOn the compute nodes the degraded SSD volumes caused kernel deadlocks and mild filesystem corruption, and node monitoring processes rebooted the nodes. After reboot some nodes could not attach the volumes the Cassandra pods needed, which led to more automatic reboots. Writes to Cassandra either failed or took more than 2 seconds. Adding compute nodes, in the hope that new nodes could attach the disks, did not help.\n\nFor customers, Graphite queries that needed data not held in Metrictank's in-memory cache failed; recent hours of data are always served from memory, so the error rate rose slowly and peaked at 20% of queries. Once GCP recovered, the Cassandra StatefulSet still did not come back: with the OrderedReady pod management policy each pod waits for the previous one to become healthy, and the second pod's volume was still unavailable, so no later pod started.\n\nAfter Cassandra was running again, no Metrictank instance could reconnect to it. Every Cassandra pod had been recreated with a new IP address, and Metrictank, which learns new node IPs only from nodes it is still connected to, did not resolve the service hostname again.","ko":"Metrictank 인스턴스 일부가 쓰기 큐를 비우지 못한다는 알림이 왔다. Metrictank 가 Cassandra 에 쓰지 못하고 있다는 뜻이었다.\n\n그 무렵 Google 은 아직 장애 보고를 올리지 않았다. 팀원 한 명이 자기 Discord 채팅 서버가 죽은 것을 알아챘는데, 그 서버도 GCP 에 있었기 때문에 GCP 쪽 문제라고 짐작했다. 장애가 시작되고 약 90분 뒤 Google 이 장애 보고를 올려 짐작이 맞았음을 확인했다.\n\nSSD 영구 볼륨의 성능이 심하게 떨어져 I/O 가 오랫동안 실패했다. 노드에서 커널 교착과 가벼운 파일 시스템 손상이 생겼고, 노드의 모니터링 프로세스가 자동으로 재부팅을 걸었다. 다시 올라온 노드 일부는 Cassandra 파드가 쓸 디스크 볼륨을 붙이지 못했고, 그 때문에 다시 재부팅됐다. Cassandra 쓰기는 완전히 실패하거나 2초 넘게 걸렸다. 새 노드라면 디스크를 붙일 수 있을 것이라 보고 컴퓨트 노드를 늘려 봤지만 소용없었다.\n\n사용자 쪽에서는 US-East 클러스터의 Grafana Cloud Graphite 고객이 메모리 캐시에 없는 데이터를 찾는 쿼리를 실행하지 못했다. 최근 몇 시간치 데이터는 설계상 항상 메모리에 있어서 그 범위의 쿼리는 계속 성공했다. 실패한 쿼리는 가장 많을 때 20%였고, 오류율은 아주 천천히 올라갔다.\n\nCassandra 를 되살린 뒤에도 Metrictank 인스턴스는 하나도 새 클러스터에 다시 연결하지 못했다. Metrictank 에 설정된 서비스 호스트 이름은 맞았지만, Metrictank 가 그 이름을 다시 조회해 새 IP 를 얻지 않았다."},"updated_at":"2026-11-10T00:00:40.322953Z","url":"https://benchlet.com/incidentlib/app/cases/IL-0193","org_slug":"grafana-labs"}