Incident Library
  • 원인
  • 구성 요소
  • 증상
  • 기여 요인
  • 사례

사례10건

10건 중 6~10번째

  1. 증상

    RDS 인스턴스의 BurstBalance 가 갑자기 빠르게 줄기 시작해 끝내 바닥났고, 데이터베이스 성능이 크게 떨어졌다. 인스턴스는 MySQL 5.7.44, db.t3.medium, 범용 SSD gp2 30GiB 였다.

    촉발

    복잡한 SELECT 문이 어느 시점부터 갑자기 임시 테이블에 대량으로 쓰기 시작해 WriteIOPS 가 늘었다. 그렇게 바뀐 이유는 아직 조사 중이다.

    원인
    구성 요소
    기여 요인
    후속 조치
    • 문제 쿼리를 튜닝하는 것을 검토한다. 인덱스를 다시 보거나, 서브쿼리를 없애고 JOIN 을 최적화하는 식으로 쿼리를 다시 쓰는 방안이다.
    • gp2 볼륨을 성능 특성을 더 예측하기 쉬운 gp3 로 옮기거나, 스토리지를 늘려 기준 IOPS 를 높이는 것을 검토한다.
    • 임시 테이블의 크기 한도를 정하는 tmp_table_size 와 max_heap_table_size 파라미터를 조정하는 것을 검토한다.
    • 더 성능이 높은 인스턴스 타입으로 옮기는 것을 검토한다.
    • CloudWatch 알림 같은 수단으로 BurstBalance 를 감시하고, Pingdom 같은 외부 모니터링을 두는 것을 검토한다.
    교훈

    RDS 에서 BurstBalance 가 바닥나면 예상하지 못한 성능 저하가 올 수 있다. 복잡한 쿼리는 I/O 에 큰 영향을 주므로, 데이터베이스 설계와 쿼리 최적화를 계속 고쳐 나가고 성능을 정기적으로 감시해야 한다.

  2. 증상

    패키지 매출 랭킹 기능을 배포한 뒤 버그 픽스를 몇 차례 더 배포하고 유저를 재접속시키자 Database 알람이 울렸고, Database CPU 그래프가 100%에서 내려오지 않았다. Database 접속에 문제가 생기면서 유저들이 게임에 정상적으로 접속하거나 플레이할 수 없었다.

    촉발

    새 패키지 매출 랭킹 기능의 집계 쿼리가 들어간 배포 뒤 버그 픽스를 추가로 배포하고 유저를 재접속시키면서, 캐시가 빈 서버들이 이 쿼리를 한꺼번에 실행했다.

    원인
    구성 요소
    기여 요인
    감지

    버그 픽스를 추가로 배포하고 유저를 재접속시킨 뒤 Database 알람이 울려 CPU 그래프를 보고 알았다.

    조치
    • 서버 점검을 걸었다.
    • 문제가 된 패키지 매출 랭킹 기능을 켜고 끌 수 있는 설정을 빠르게 추가해 꺼 둔 뒤 서비스를 정상화했다.
    • 판매 시작 시간을 공유하는 상품들의 product_id 조건을 하나로 묶고, 의미가 없는 현재 시각 조건을 지운 쿼리로 바꿔 바로 적용했다.
    • 모든 서버가 함께 쓰는 캐시를 스케줄러 서버에 따로 두었다. 각 서버는 Database 대신 원격 저장소의 결과를 읽고, 요청은 큐에 넣는다. 스케줄러 서버는 주기적으로 마지막 요청으로 쿼리해 결과를 원격 저장소에 캐싱한다. 이렇게 해서 쿼리 실행 횟수를 줄였다.
    • 쿼리 최적화와 캐시 사용 방식 개선을 적용한 뒤 패키지 매출 랭킹 기능을 다시 켰고, CPU가 문제 없이 유지되는 것을 확인했다.
    교훈

    무거운 쿼리와 분산 서버의 캐시 구조가 겹치면 예상하지 못한 문제가 생기므로, 문제가 될 만한 지점을 미리 파악해야 한다. 장애가 나면 원인 파악보다 서비스 정상화가 먼저다. 문제가 되는 기능을 끄고 켤 수 있는 안전장치를 미리 만들어 두면 서비스를 더 안정적으로 운영하는 데 도움이 된다. Production 환경의 online alter에는 Percona Toolkit 같은 유용한 툴을 잘 활용하는 것이 중요하다.

  3. 증상

    2022년 3월 16일 14:09 UTC, 최대 부하 시간대에 mysql1 데이터베이스의 부하가 늘면서 데이터베이스 프록시가 최대 연결 수에 닿았다. GitHub 의 모든 쓰기 작업이 동작하지 않았다.

    촉발

    최대 부하 시간대에 공용 MySQL 클러스터 mysql1 의 부하가 늘어 데이터베이스 프록시가 최대 연결 수에 닿았다.

    원인
    구성 요소
    기여 요인
    조치
    • 정상인 복제본으로 페일오버해 3월 16일 장애에서 복구했다.
    • 3월 17일 미리 한 페일오버 뒤 새 주 노드에 생긴 연결 문제를 풀려고 연결을 재설정했다.
    • 3월 17일 장애 중에 부하 패턴을 찾아 인덱스를 추가해 주된 성능 문제를 고쳤다.
    • 3월 22일 클라이언트 연결이 실패하자 다시 주 노드를 페일오버해 복구했다.
    • 3월 23일 같은 부하 특성으로 클라이언트 연결이 실패하자 다시 주 노드를 페일오버해 복구했다.
    • 3월 23일 부하를 줄이려고 웹훅 트래픽을 제한했다.
    후속 조치
    • 다른 대책을 더 찾는 동안 최대 부하 시간대에 웹훅 트래픽 제한을 계속 써서 같은 장애가 다시 나지 않게 한다.
    • 최대 부하 시간대의 mysql1 부하 패턴을 점검하고, 그 결과에 따라 성능 수정을 차례로 한다.
    • 부하를 줄이고 페일오버 시간을 줄이려고 mysql1 의 트래픽을 다른 데이터베이스로 옮긴다.
    • 변경 관리 절차를 검토한다. 특히 운영 환경에 부하가 높을 때의 모니터링과 변경을 살핀다.
    • 플랫폼 성장에 맞춰 데이터베이스 샤딩과 하드웨어 확장을 비롯한 인프라 확장을 이어 간다.
  4. 증상

    2022년 2월 22일 태평양 시간 오전 6시 직후 Slack 에 접속하지 못한다는 사용자 티켓과 내부 사용자의 문제 보고가 들어왔고, 여러 엔지니어링 팀에 호출과 알림이 거의 동시에 왔다. 많은 사용자가 Slack 에 접속하지 못했다. 클라이언트는 부팅 과정에서 채널 목록, 설정, 최근 대화를 서버에서 가져오는데, 부팅하지 못하면 Slack 은 오류 페이지를 보였다.

    촉발

    2월 22일 Consul 에이전트 업그레이드를 비율 단위로 롤아웃하는 단계에서 전체 에이전트의 25% 를 차례로 재시작했다. memcached 노드의 에이전트가 재시작될 때마다 캐시 제어 계층인 Mcrib 가 그 노드를 비어 있는 예비 노드로 바꾸거나 다시 들어온 노드를 비웠고, 하루 중 트래픽이 가장 많은 시간에 캐시 적중률이 떨어졌다.

    원인
    구성 요소
    기여 요인
    감지

    태평양 시간 오전 6시 직후 Slack 에 접속하지 못한다는 사용자 티켓, 내부 사용자의 문제 제기, 여러 엔지니어링 팀의 호출과 알림이 거의 동시에 들어왔다.

    조치
    • 대응 초기에 장애를 촉발한 Consul 에이전트 재시작 작업을 멈췄다. 그때는 재시작이 장애와 관련 있다고 의심하지 않았다.
    • 느린 부팅 요청이 데이터베이스 자원을 바닥내 이미 부팅한 사용자의 요청까지 막자 클라이언트 부팅 요청에 엄격한 속도 제한을 걸었다.
    • 처음 제한을 너무 많이 풀어 데이터베이스 부하와 실패한 요청이 다시 늘자 부팅 속도 제한을 다시 낮췄고, 그 뒤로는 작은 폭으로 올렸다.
    • 캐시 미스마다 모든 샤드를 읽던 쿼리를 memcached 에 없는 데이터만 Vitess 에서 읽도록 바꿨다.
    • 그룹 DM 데이터는 바뀌지 않아 조금 오래된 값을 써도 되므로 쿼리가 Vitess 프라이머리뿐 아니라 복제본에서도 읽도록 바꿨다.
    • 캐시가 다시 차고 적중률이 오르자 데이터베이스 처리량을 최대로 유지하면서 클라이언트 부팅 속도 제한을 천천히 정상 트래픽 수준까지 올렸다.
    후속 조치
    • Consul 롤아웃 절차를 바꿔 업그레이드 중에 같은 장애가 다시 촉발되지 않게 했다.
    • 캐시 노드에 짧은 네트워크 단절이 생겨도 같은 연쇄 장애가 나지 않도록 Mcrib 제어 루프의 해당 부분을 바꿔 배포한다.
    • 문제가 된 scatter query 를 채널 기준으로 샤딩한 테이블에서 읽도록 바꿨다.
    • 캐시 계층 뒤에 있는 다른 데이터베이스 쿼리를 분석해 비슷한 위험이 있는 대량 scatter query 가 있는지 찾는다.
    • 장기 프로젝트로 캐시 계층의 복원력을 높이는 다른 방법을 찾는다.
    교훈

    Slack 은 성능 병목, 연쇄 장애, 데워진 캐시에 기대는 구조, 촉발 요인이 된 변경이 장애에서 자주 보이는 패턴이라고 봤다. 이번 장애는 여러 시스템의 경계를 넘는 상호작용이었고 그중 일부 시스템은 최근에 바뀌었다. 큰 소프트웨어 시스템에서 복잡성과 변경은 피할 수 없고, 그만큼 시스템이 예측하지 못한 방식으로 움직일 위험도 커진다. Mcrib 는 틀리거나 비효율적이어서가 아니라 오히려 빠르고 효율적이어서 장애를 키웠다. 빠진 memcached 인스턴스를 더 빨리 찾아 설정을 고치면서 캐시 계층의 교체가 늘었고, 더 나은 구성 요소가 전체 시스템을 덜 안전하게 만들었다. 캐시 노드에 짧은 네트워크 단절이 생겨도 같은 연쇄 장애가 날 수 있다는 위험도 드러났다. 연쇄 장애에 빠진 시스템은 부하를 크게 줄이거나 용량을 늘리는 외부 개입 없이는 안정 상태로 돌아오지 않는다.

  5. 증상

    2022년 1월 3일부터 17일까지 이어진 100% 할인 이벤트 기간에, 인프런은 1월 3일, 4일, 7일, 17일 오전 9시와 오후 10시 무렵마다 쓸 수 없을 만큼 느려졌다. 트래픽은 평소의 5~20배였고, 매번 데이터베이스의 슬로우 쿼리와 부하가 함께 나타났다.

    촉발

    2022년 1월 강의 125개를 0원에 등록할 수 있는 100% 할인 이벤트가 크게 퍼지면서 평소 대비 5~20배의 트래픽이 들어왔다.

    원인
    구성 요소
    기여 요인
    감지

    모니터링과 슬로우 쿼리 알람으로 문제를 알아챘고, 문제가 되는 쿼리를 30분 안에 찾았다. 1월 17일에는 오후 10시부터 RDS 의 여러 수치가 요동치는 것을 보았다.

    조치
    • 관심회사 조회 API 의 쿼리 일부가 인덱스를 쓰지 않는 것을 로그와 실행계획으로 확인하고 바로 인덱스를 추가했다.
    • 용량이 큰 캐시 행의 캐시 초기화와 재생성 주기를 바꿨다.
    • 쓰기 DB 에 부하를 준 조회용 쿼리부터 읽기 DB 로 옮겼다.
    • 시간을 벌려고 데이터베이스를 r5.8xlarge 8대로 늘렸다.
    • 느린 강의 조회 쿼리에 인덱스를 추가하거나 쿼리를 바꿔 인덱스를 쓰게 했고, 개선할 수 없는 쿼리를 쓰는 기능은 껐다.
    • select * 로 읽던 강의 조회 쿼리에서 실제로 쓰지 않는 고용량 강의 소개 컬럼을 모두 뺐다.
    • IN 절에 담는 강의 ID 를 20개씩 나눠 Promise.all 로 처리하게 바꿔, 쿼리가 다시 인덱스를 쓰게 했다.
    • PostgreSQL 을 10.16 에서 11.13 으로 올렸다.
    • 5초 넘게 도는 쿼리를 강제로 끝내도록 쿼리 타임아웃을 5초로 걸었다.
    • Max Connection 을 늘렸다.
    • 강의 테이블에 수동으로 vacuum 을 돌려 Dead Tuple 을 정리했다.
    • 용량이 큰 강의 소개 컬럼을 강의 테이블에서 떼어 별도 테이블로 옮겼다.
    후속 조치
    • 한 테이블의 장애가 전체 서비스의 장애로 번지는 구조를 개선한다.
    • 성능 개선이 쉽지 않은 테이블과 캐시 구조를 개선한다.
    • 직관적이지 않은 SQL 코드를 개선한다.
    • 타입과 테스트 코드가 없어 안심하고 고치기 어려운 코드를 개선한다.
    • 배치 작업을 안정적으로 돌릴 환경을 갖춘다.
    • 모놀리식 구조는 알맞은 선에서 유지하고, 장애가 잘 번지는 일부 도메인만 따로 분리한다.
    교훈

    그동안 데이터베이스 병목이 모두 읽기 DB 의 슬로우 쿼리로 보여 팀은 읽기 DB 만 모니터링했다. 이 때문에 근본 원인인 쓰기 DB 강의 테이블의 Dead Tuple 을 늦게 찾았다. 원인 쿼리는 빨리 찾았지만, 무분별한 쿼리 추상화와 타입과 테스트 코드가 없는 코드 때문에 수정과 배포가 늦어졌다. 그만큼 장애 시간도 길어졌다. 첫 주 장애를 돌아보면 어느 페이지가 문제가 될지 예상할 수 없었고, 대부분의 페이지가 트래픽이 몰리면 언제든 장애가 날 수 있는 상태였다. 팀은 조직의 구조와 성숙도가 갖춰지지 않은 상태에서 마이크로서비스 아키텍처인 MSA 로 바로 옮기면 더 큰 문제가 생긴다고 보았다. 그래서 모놀리식 구조를 알맞은 선에서 유지하면서 일부 도메인만 분리하기로 했다.

내 클러스터에도 있는지 확인하기

이 10건에서 가장 많이 나오는 구성 요소: 데이터베이스, PostgreSQL vacuum. kubegap 은 Claude Code 나 Codex 에서 쿠버네티스 클러스터를 읽기만 하며 이런 틈을 점검합니다. 점검 항목의 근거는 이 라이브러리와 같은 공개 보고서입니다.

원인별3

배포 결함3건

  1. IL-0510 MIT Learn 운영 RDS PostgreSQL 의 저장 공간이 임시 파일로 가득 차 learn.mit.edu 가 세 시간 동안 503 과 504 를 냈다
  2. IL-0571 STORES 予約의 스마트리스트 검색 쿼리가 특정 테넌트에서 20분 넘게 DB 프로세스를 붙잡아 504 에러가 급증했다
    STORES2024
  3. IL-0567 쿠키런: 오븐브레이크의 패키지 매출 랭킹 기능을 배포한 뒤 Aurora MySQL CPU가 100%에 이르러 유저가 게임에 접속하지 못했다

쿼리 실행 계획 변경1건

조직
  1. IL-0317 Postgres 쿼리 플랜이 바뀌어 데이터베이스가 느려지고 Clerk 트래픽의 95% 넘게 429 를 받았다
    Clerk2026

용량 한계6건

  1. IL-0287 Atlas 와 Cloud Manager 의 모니터링을 받치는 내부 데이터베이스 클러스터의 샤드 하나가 과부하되어 지표 수집과 클러스터 작업이 늦어졌다
  2. IL-0551 Bitbucket Cloud 주 데이터베이스가 과부하로 장애 조치된 뒤 하루 가까이 지연과 간헐 오류가 이어졌다
  3. IL-0447 RDS MySQL 인스턴스의 EBS BurstBalance 가 바닥나 IOPS 가 기준 성능으로 묶이고 데이터베이스 성능이 크게 떨어졌다
    every2024
  4. IL-0302 GitHub 의 공용 MySQL 클러스터 mysql1 이 최대 부하 시간대에 연결이 바닥나 쓰기 작업이 네 번 멈췄다
    GitHub2022
  5. IL-0212 캐시 적중률이 떨어지며 채널 멤버십 Vitess 키스페이스가 과부하에 빠져 많은 사용자가 Slack 에 접속하지 못했다
    Slack2022
  6. IL-0456 100% 할인 이벤트 기간에 인프런 서비스가 Aurora PostgreSQL 병목으로 여섯 번 멈췄다
    Inflab2022