Incident Library

IL-0589 Kickstarter · 2013-03-07databaseaws

Kickstarter 의 모든 MySQL 복제본이 중복 키 오류로 복제를 멈췄다

Kickstarter 는 관리형 MySQL 로 Amazon RDS 를 쓰고, RDS 의 기본 복제 모드는 MIXED 다. MIXED 모드는 평소에는 쿼리 자체를 넘기는 statement 기반 복제를 쓰다가, 복제본마다 결과가 달라질 수 있는 unsafe 쿼리를 만나면 잠시 행의 새 값 전체를 넘기는 row 기반 복제로 바꾼다.

Kickstarter 는 후원자가 후원을 마치면 프로젝트 안에서 겹치지 않고 차례로 늘어나는 순번을 매긴다. 경합 상태와, 중복 키 때문에 생기는 불필요한 롤백과 재시도를 피하려고, 백그라운드 작업이 사용자 정의 카운터 변수로 최근 후원의 순번을 한 번에 매기는 UPDATE 를 실행했다. 순서는 ORDER BY pledged_at ASC, id ASC 로 정했다. 데이터가 어긋난 시기에는 backings 테이블에 쓴 뒤 락을 기다리며 멈춘 트랜잭션들이 있었고, 이 트랜잭션들은 순서가 뒤바뀌어 끝났다. MySQL 은 트랜잭션의 쿼리를 COMMIT 때 binlog 에 쓰므로, primary 가 디스크에 쓴 행의 순서와 복제본이 binlog 를 다시 실행하며 쓴 순서가 달라졌다. 여기에 MySQL 이 가끔 ORDER BY 절을 무시하는 버그가 겹쳐, primary 와 복제본이 각자의 암묵적 순서로 순번을 매겼고 값이 어긋났다.

어긋난 데이터는 며칠에서 한 달 넘게 복제를 깨지 않았다. 나중에 unsafe 쿼리가 row 기반 복제를 일으키자 Update_rows 이벤트가 복제본의 고유 키와 충돌했고, 3월 7일 모든 복제본이 Error_code: 1062 로 멈췄다. 엔지니어들은 몇 시간에 걸쳐 사이트를 안정시키고 복구했지만, 그 뒤 같은 일이 다시 일어났다.

심각도
–
영향 범위
All MySQL replicas stopped updating; most of Kickstarter's traffic is served from the replicas
영향 사용자
–
지역
–
감지까지
–
복구까지
–

증상

3월 7일 목요일, Kickstarter 트래픽 대부분을 받는 MySQL 복제본들이 갱신을 멈췄다. 복제본 하나만 멈췄다면 그 복제본을 갈아 끼우면 됐지만, 모든 복제본이 같은 상태였다.

SHOW SLAVE STATUS\G 의 출력에는 처음 보는 오류가 있었다. Last_Error: Could not execute Update_rows event on table kickstarter.backings; Duplicate entry '123456-789' for key 'index_backings_on_project_id_and_sequence', Error_code: 1062; handler error HA_ERR_FOUND_DUPP_KEY; the event's master log mysql-bin-changelog.169933, end_log_pos 12969124

오류가 가리킨 곳을 보니 primary 와 복제본의 값이 서로 다른 행 범위가 있었다. 이 데이터는 며칠 전부터 어긋나 있었고, 범위를 넓혀 찾자 한 달이 넘은 불일치도 나왔다. 어긋난 값은 몇몇 후원자 순번이었다.

엔지니어들은 몇 시간 동안 사이트를 안정시키고, 오래된 복제본 데이터의 영향을 줄이고, 사용자에게 알리고, 복구했다. 정상이라고 알린 뒤 같은 일이 처음부터 다시 일어났다.

증상 복제 지연

원인

촉발
데이터가 이미 어긋나 있는 상태에서, 나중에 실행된 unsafe 쿼리가 MIXED 모드 복제를 row 기반 복제로 바꿨다. 복제본이 그 행의 새 값을 그대로 쓰려다 고유 키가 겹쳐 복제가 멈췄다.

감지

트래픽 대부분을 받는 복제본들이 모두 갱신을 멈춘 것을 보고 알았고, SHOW SLAVE STATUS\G 에서 중복 키 복제 오류를 확인했다.

조치

–

후속 조치

–

교훈

데이터베이스는 복잡하다. Rails 커뮤니티에서는 데이터베이스를 ActiveRecord 와 ActiveRelation 뒤에 숨은 단순한 데이터 저장소로 여기기도 하지만, 데이터베이스가 요청받은 일을 어떻게 처리하는지 더 깊이 알아야 한다.

출처

관련 사례

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

kubegap 은 Claude Code 나 Codex 에서 쿠버네티스 클러스터를 읽기만 하며 이 사례와 같은 상태를 점검합니다. 이 사례의 구성 요소: 데이터베이스, 데이터베이스 복제. 점검 항목의 근거는 이 사례 같은 공개 보고서입니다.

AI 도구와 스크립트용

API 는 키가 필요 없습니다. MCP 서버는 search_incidents, get_incident, list_incidents, get_topic, vocab 도구를 내놓습니다. 사용법은 API 와 MCP.