Incident Library

IL-0436 freee · 2025-08-27 (게시일)databaseaws

Aurora MySQL 에서 INSERT 가 supremum 락에 막혀 커넥션이 고갈되고 freee 웹 서비스 전체가 다운되었다

freee 의 한 서비스에서 Aurora MySQL 접속 실패가 잦아지다가 웹 페이지를 열 수 없는 상태로 번졌다. Writer 인스턴스의 CPU 와 메모리는 정상이었지만 BlockedTransactions 와 DatabaseConnections 가 급증해 커넥션이 상한에 닿았다. Database Insights 에서 특정 테이블의 INSERT 가 행 락 대기에 막힌 것을 찾았고, SHOW ENGINE INNODB STATUS 에서 롱 트랜잭션이 Primary Index 의 supremum 에 배타 락을 잡고 있는 것을 확인했다. supremum 은 마지막 레코드 뒤의 갭을 나타내는 가상의 상한값이다. auto_increment 기본 키를 쓰는 테이블의 INSERT 는 이 supremum 에 insert intention lock 을 잡아야 하므로, 모든 INSERT 가 막혔다. 많은 스레드가 이 락이 풀리기를 기다리며 쌓여 커넥션이 고갈되고 서비스가 멈췄다.

문제의 롱 트랜잭션에서 그 테이블에 락을 잡는 쿼리는 INSERT 뿐이었고, 그 밖에는 다른 테이블의 SELECT 와 UPDATE, SAVEPOINT 와 ROLLBACK TO SAVEPOINT 가 있었다. DBRE 팀은 트랜잭션 시작, 아무 읽기 쿼리, SAVEPOINT, INSERT, ROLLBACK TO SAVEPOINT 순서로 supremum 락을 재현했다. INSERT 는 레코드를 넣은 뒤 그 레코드에 암묵적 배타 락을 잡는다. ROLLBACK 은 undo 로 레코드를 지우지만 락은 풀지 않고, 지운 레코드의 락을 다음 갭으로 넘긴다. 그래서 마지막에 추가한 레코드의 락은 supremum 으로 넘어갔다. MySQL 8.0 코드에서도 클러스터드 인덱스에 들어간 행을 undo 할 때 lock_rec_inherit_to_gap() 이 명시적으로 불렸다.

Rails 의 Active Record 에서 ActiveRecord::Base.transaction 에 requires_new: true 를 주면 내부에서 SAVEPOINT 를 쓰고, #create_or_find_by 와 #find_or_create_by 도 내부에서 이를 부른다. 사내에서도 이렇게 SAVEPOINT 롤백을 모르고 쓰는 곳이 여럿 있었다. supremum 락만으로는 문제가 되지 않았지만, 롱 트랜잭션과 겹치면서 행 하나의 롤백이 웹 서비스 전체를 다운시켰다.

심각도
–
영향 범위
A whole freee web service was down: its web pages could not be opened
영향 사용자
–
지역
–
감지까지
–
복구까지
–

증상

Aurora MySQL 에 접속 실패가 잦아지다가 결국 웹 페이지를 열 수 없게 되어 서비스 전체가 다운되었다. 웹 애플리케이션은 DB 가 응답하지 않아 타임아웃이 잇따랐고 새 DB 접속을 맺지 못했다.

Aurora MySQL Writer 인스턴스의 CPUUtilization 과 FreeableMemory 에는 이상이 없어 고부하 상태로 볼 수 없었다. 반면 락 때문에 기다리는 트랜잭션 수인 BlockedTransactions 가 급증했고, DatabaseConnections 가 급증해 상한에 닿았다.

Database Insights 에서 SQL 문을 보니 장애 시각 앞뒤로 특정 테이블의 INSERT 가 wait/synch/cond/innodb/row_lock_wait 를 일으켜 레코드 추가가 막혀 있었다. 기본 키가 AUTO_INCREMENT 값이라, 새 레코드의 INSERT 를 막는 갭 락이 잡히는 일은 보통 생각하기 어려웠다.

SHOW ENGINE INNODB STATUS 의 로그에는 Primary Index 의 supremum 에 배타 락을 잡은 롱 트랜잭션이 있었다. 그 락은 index PRIMARY of table `XXXX`.`XXXX` trx id XXXXXXXXXXXX lock_mode X 줄과 0: len 8; hex XXXXXXXXXXXXXXXX; asc supremum;; 줄로 찍혀 있었다.

증상 잠금 경합과 교착서비스 중단

원인

촉발
롱 트랜잭션 안에서 SAVEPOINT 를 만들고 대상 테이블에 INSERT 한 뒤 ROLLBACK TO SAVEPOINT 를 실행했고, 이 롤백이 그 테이블의 supremum 에 배타 락을 남겼다.

감지

–

조치

–

후속 조치

교훈

supremum 락 하나로는 문제가 되지 않지만 롱 트랜잭션과 겹치면 서비스 정지로 이어진다. ORM 프레임워크는 중첩 트랜잭션을 만들려고 내부에서 SAVEPOINT 를 쓰는 일이 많아 개발자가 모르고 쓰게 된다. 롱 트랜잭션이 잡은 락은 영향 범위가 좁아 막히는 작업이 적으면 지나치기 쉽지만, 이번 일로 그 잠재 위험이 드러났다.

출처

관련 사례

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

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

AI 도구와 스크립트용

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