사례3건
3건 중 1~3번째
- 증상
2025년 8월 7일 21시 4분 UTC부터 Storj US1 satellite 에서 데이터베이스 트랜잭션 잠금이 늘었다. 21시 14분에 당번 팀이 호출을 받았고, 21시 25분에 상태 페이지가 US1 의 업로드와 다운로드 실패를 조사 중이라고 알렸다.
- 촉발
같은 객체들을 겨냥한 동시 업로드가 유난히 많이 몰렸다.
- 원인
- 구성 요소
- 기여 요인
- –
- 감지
21시 14분에 당번 팀이 호출을 받고 조사를 시작했다.
- 조치
- 커넥션 상태를 주기적으로 초기화해 경합 관련 오류가 연쇄로 불어나지 않게 했다.
- 후속 조치
- 데이터베이스 트랜잭션 사이의 경합 원인을 찾아 코드를 고쳐 없앴다.
- 데이터베이스 잠금이 늘어나는 것을 찾아 팀에 알리는 모니터링과 알림을 더했다.
- 사고 분석으로 절차와 시스템에서 더 고칠 점을 찾았다. 수동 유지 보수를 줄이거나 없애는 도구를 만드는 일이 그중 하나다.
- 증상
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 는 이 시스템을 잘 알면서도 결정적인 단서가 없고 데이터베이스 내부를 깊이 들여다볼 수단이 없어 확신을 갖고 대응하지 못했다고 썼다. 그래서 문제 하나를 고치는 대신 위험을 줄이고 확신을 쌓아 가는 길을 택했다.
- 증상
LINE Data Labs 의 로그 수집 Hadoop 클러스터에서 HA 구성의 HDFS NameNode 가 오전 9시쯤 가끔씩 failover 됐다. failover 의 직접 원인은 ZooKeeper Failover Controller 의 NameNode 헬스 체크가 60초 타임아웃에 걸린 것으로 보였다.
- 촉발
오전 9시쯤 NameNode 가 .Trash 디렉터리에 쌓인 삭제된 로그 데이터 파일을 지우는 작업을 시작했다. fs.trash.interval 이 4320 분이어서 .Trash 아래 파일은 3일 간격으로 지워지고 있었다.
- 원인
- 구성 요소
- 기여 요인
- 조치
- fs.trash.interval 을 4320 에서 1440 으로 바꿔 .Trash 디렉터리 아래 파일을 3일이 아니라 1일 간격으로 지우게 했다. .Trash 파일을 지울 때 FSNamesystem write lock 이 잡히는 시간을 줄이려는 조치였다.
- dfs.namenode.servicerpc-address.mycluster.nn1=mynamenode1:8040 과 dfs.namenode.servicerpc-address.mycluster.nn2=mynamenode2:8040 을 추가해 ZooKeeper Failover Controller 의 헬스 체크가 RPC Client Port 대신 Service RPC port 를 쓰게 했다.
이 3건에서 가장 많이 나오는 구성 요소: 데이터베이스, 데이터베이스 장애 조치 관리자. kubegap 은 Claude Code 나 Codex 에서 쿠버네티스 클러스터를 읽기만 하며 이런 틈을 점검합니다. 점검 항목의 근거는 이 라이브러리와 같은 공개 보고서입니다.