사례3건
3건 중 1~3번째
- 증상
2022년 6월 27일 13:00 UTC 에 Basecamp 3 가 갑자기 아주 느려졌고 곧 오류 페이지를 보였다. 같은 30초 사이에 Basecamp 3 의 주 데이터베이스가 정상에서 완전한 정체 상태로 넘어갔다.
- 촉발
13:00 UTC 에 예약 알림을 대량으로 묶어 보내는 시각에 맞춰 부하가 큰 데이터베이스 쿼리가 한꺼번에 몰려들었고, 이 쿼리들이 주 데이터베이스에서 락 경합을 연쇄로 일으켰다.
- 원인
- 구성 요소
- 기여 요인
- 감지
37signals 는 13:00:00 과 13:00:30 UTC 사이에 Basecamp 3 의 주 데이터베이스가 완전히 정체되는 것을 보고 곧바로 대응을 시작했다.
- 조치
- 주 데이터베이스의 부하를 덜려고 Basecamp 3 의 일부 기능을 멈췄다. 진행 보고 이메일을 그날 늦게 보내도록 미룬 것이 그 예다.
- 막힌 주 데이터베이스에서, 이런 장애에 대비해 대기 중이던 복제본 데이터베이스로 절차대로 페일오버했다.
- 페일오버 뒤 데이터베이스가 다시 무너지자 Basecamp 3 를 곧바로 유지보수 모드로 바꿨다.
- 데이터베이스 쿼리 시간에 강제 상한을 걸었다.
- 쿼리 시간 상한을 안전장치로 두고 기능을 한 단계씩 다시 켜서 서비스를 모두 되살렸다.
- 후속 조치
- 데이터베이스 쿼리에 강제 시간 상한을 둔다. 평소 빠르던 쿼리가 일정 수준 넘게 느려지면 기다리며 쌓이게 두지 않고 실패하게 한다.
- 동시 실행 한도를 조정한다. 일정 수준을 넘으면 병렬로 도는 작업이 많을수록 시스템 전체가 느려지기 때문이다.
- 서비스 수준 기대치를 더 엄격하게 잡고, 예약 알림과 보고서를 몇 분 안에 대량으로 보내며 생기던 정시 데이터베이스 부하 급증을 더는 용인하지 않는다. 제품 설계부터 데이터베이스 내부까지 이 부분을 살핀다.
- 일부 예약 작업을 돌리는 병렬 워커 수를 줄여 동시 실행과 락 경합을 낮춘다.
- 일부 예약 작업을 한꺼번에 끝내지 않고 몇 분에 걸쳐 돌려 데이터베이스 부하를 시간에 나눈다.
- 한꺼번에 몰려드는 쿼리 다수를 주 데이터베이스에서 읽기 전용 복제본 데이터베이스로 옮긴다.
- 지난 1년의 데이터베이스 지표에서 찾은 성능 저하 여러 건을 하나씩 고친다. 이 저하는 알림을 울리지 않았지만 주 데이터베이스 부하를 서서히 늘렸다.
- 한꺼번에 몰려드는 다른 작업을 찾아 분산해, 데이터베이스의 특정 지점에 부하가 몰리지 않게 한다.
- 높은 부하와 높은 동시성에서 더 예측 가능하게 동작하도록 데이터베이스를 조정한다.
- 데이터베이스 내부의 락과 트랜잭션 상태를 더 잘 볼 수 있도록 관측성을 높인다.
- 장애 중에 부하가 큰 Basecamp 3 기능을 골라 끌 수 있는 더 쓰기 편한 도구를 만든다.
- 멈춘 데이터베이스에 대응하는 운영 훈련을 잡아 대응을 연습하고 다듬는다.
- 앞으로 몇 년의 Basecamp 성장에 맞춰 확장할 수 있도록 데이터베이스 소프트웨어와 하드웨어 업그레이드를 검토한다.
- Basecamp 3 를 받치는 구성 요소에 서비스 수준 목표, 곧 SLO 를 정해 데이터베이스 성능 변화를 더 일찍, 더 분명하게 알아챈다.
- 분기마다 성능과 부하를 검토해 눈에 띄지 않게 진행되는 성능 변화를 찾는다.
- 교훈
37signals 는 이번 장애에 원인 하나가 없었다고 밝혔다. 시스템이 한계를 조금만 넘어도 순식간에 안정에서 불안정으로 넘어가는 지점이 있고, 그 지점은 특별한 계기 없이 성장과 함께 서서히 다가온다. 막힌 쿼리를 종료하는 일은 트랜잭션 롤백 때문에 같은 락에서 다시 막혀 정체를 키울 수 있다. 37signals 는 이 시스템을 잘 알면서도 결정적인 단서가 없고 데이터베이스 내부를 깊이 들여다볼 수단이 없어 확신을 갖고 대응하지 못했다고 썼다. 그래서 문제 하나를 고치는 대신 위험을 줄이고 확신을 쌓아 가는 길을 택했다.
- 증상
2022년 3월 16일 14:09 UTC, 최대 부하 시간대에 mysql1 데이터베이스의 부하가 늘면서 데이터베이스 프록시가 최대 연결 수에 닿았다. GitHub 의 모든 쓰기 작업이 동작하지 않았다.
- 촉발
최대 부하 시간대에 공용 MySQL 클러스터 mysql1 의 부하가 늘어 데이터베이스 프록시가 최대 연결 수에 닿았다.
- 원인
- 구성 요소
- 기여 요인
- 조치
- 정상인 복제본으로 페일오버해 3월 16일 장애에서 복구했다.
- 3월 17일 미리 한 페일오버 뒤 새 주 노드에 생긴 연결 문제를 풀려고 연결을 재설정했다.
- 3월 17일 장애 중에 부하 패턴을 찾아 인덱스를 추가해 주된 성능 문제를 고쳤다.
- 3월 22일 클라이언트 연결이 실패하자 다시 주 노드를 페일오버해 복구했다.
- 3월 23일 같은 부하 특성으로 클라이언트 연결이 실패하자 다시 주 노드를 페일오버해 복구했다.
- 3월 23일 부하를 줄이려고 웹훅 트래픽을 제한했다.
- 후속 조치
- 다른 대책을 더 찾는 동안 최대 부하 시간대에 웹훅 트래픽 제한을 계속 써서 같은 장애가 다시 나지 않게 한다.
- 최대 부하 시간대의 mysql1 부하 패턴을 점검하고, 그 결과에 따라 성능 수정을 차례로 한다.
- 부하를 줄이고 페일오버 시간을 줄이려고 mysql1 의 트래픽을 다른 데이터베이스로 옮긴다.
- 변경 관리 절차를 검토한다. 특히 운영 환경에 부하가 높을 때의 모니터링과 변경을 살핀다.
- 플랫폼 성장에 맞춰 데이터베이스 샤딩과 하드웨어 확장을 비롯한 인프라 확장을 이어 간다.
- 증상
6월 3일 PagerDuty 알림 파이프라인의 성능이 떨어져 일부 알림이 늦게 갔다. 통합 API 로 보낸 이벤트의 약 3% 를 받지 못했고, 1% 에 못 미치는 일부 알림이 늦게 전달됐다.
- 촉발
6월 3일 Cassandra 클러스터의 노드 하나에서 repair 작업이 평소대로 시작됐고, 그때 걸린 높은 부하와 겹쳤다. 6월 4일에는 팀이 전날 미룬 repair 작업을 손으로 다시 시작했다.
- 원인
- 구성 요소
- 기여 요인
- 조치
- 6월 3일 Cassandra 클러스터의 부하를 줄였다.
- 6월 3일 부하를 줄이면서 repair 작업을 멈췄다.
- 클러스터를 안정시키려고 일부 노드 사이의 통신을 끊었고, 그 뒤 정상으로 돌아왔다.
- 6월 4일 알림 파이프라인에서 처리 중이던 데이터를 모두 지웠고, Cassandra 가 바로 회복했다.
- 새로 비운 상태와 데이터를 맞추도록 일부 하위 시스템을 수동으로 손봤다.
- 후속 조치
- 기존 Cassandra 노드를 더 크고 빠른 서버로 수직 확장한다. 6월 4일 이후 이 작업은 이미 끝냈다.
- Cassandra 클러스터를 여러 개로 나누고 작업량을 그 클러스터들에 나눠 싣는다. 부하와 사용 방식에 따라 클러스터를 나누는 작업이 진행 중이다.
- 시스템 부하 기준을 정해 그 기준에 닿으면 기존 Cassandra 클러스터를 미리 수평 확장한다.
- 기존 클러스터와 새 클러스터를 더 최근 버전의 Cassandra 로 업그레이드한다.
- 높은 부하에서도 Cassandra 를 통제할 수 있도록 로드 셰딩 기법을 더 넣는다.
- Cassandra 전문 인력을 사내에 더 둔다.
- 교훈
PagerDuty 는 Cassandra 클러스터의 규모가 모자랐고, 부하와 사용 방식이 서로 다른 너무 많은 서비스가 한 클러스터를 나눠 쓰고 있었다고 밝혔다. 비슷한 문제를 보고 개선을 계획했지만 효율을 기준으로 버전 업그레이드를 먼저 하려 했고, 확장을 먼저 했어야 했다고 적었다.
이 3건에서 가장 많이 나오는 구성 요소: 데이터베이스, 데이터베이스 프록시와 커넥션 풀러. kubegap 은 Claude Code 나 Codex 에서 쿠버네티스 클러스터를 읽기만 하며 이런 틈을 점검합니다. 점검 항목의 근거는 이 라이브러리와 같은 공개 보고서입니다.