Search
moon
sun

Aurora MySQL 인덱스 DDL이 Reader 버퍼풀을 비운다?

URL
생성 일시
2026/10/06 11:07
최종 편집 일시
2026/10/06 11:07
태그
당근
파일과 미디어
|| 미사용 인덱스를 정리하다 만난 Aurora 엔진 결함, 그리고 3.14 릴리즈까지 안녕하세요. 당근 DB팀에서 근무하고 있는 Eden(이든)이에요. 당근 DB팀에서는 Aurora MySQL / Aurora PostgreSQL 및 MongoDB 를 운영하고 있어요. 이번에 Aurora MySQL 을 운영하며 경험했던 이슈를 소개해 드리려고 해요. “인덱스를 INVISIBLE 로 바꾸는 건 메타데이터만 건드리는 작업이니까 DB 에 부하를 주지 않는다.” DBA 라면 누구나 이렇게 말할 거예요. 저도 그랬고요. 그런데 Aurora MySQL 에서는 이 문장이 반쪽만 맞았어요. Writer 는 정말 아무 일도 없었지만, Reader 에서는 버퍼풀이 통째로 무효화되면서 CPU 와 ReadIOPS 가 동시에 튀었거든요. 오늘은 미사용 인덱스를 정리하다가 이 현상을 발견하고, 버퍼풀 안에서 무슨 일이 벌어지는지 직접 들여다보고, DDL 종류별로 재현해서 AWS 에 리포트하고, 결국 엔진 결함으로 확인되어 다음 릴리즈에 fix 가 실리기까지의 과정을 정리해 보려고 해요. Aurora MySQL 을 운영하는 분들께 실질적인 도움이 되면 좋겠어요. Aurora 는 Reader 의 캐시를 어떻게 동기화할까 먼저 배경부터 짧게 짚고 갈게요. Aurora 는 인스턴스와 스토리지가 분리되어 있어요. Writer 는 데이터 페이지를 스토리지에 쓰지 않고 redo 로그만 보내고, 스토리지 계층이 그 로그로 페이지를 만들어요. Reader 는 같은 스토리지를 바라보지만 자기만의 버퍼풀을 갖고 있고요. Writer 가 어떤 페이지를 바꾸면 그 redo 로그가 Reader 에게 전달되고, Reader 는 버퍼풀에 그 페이지가 있으면 갱신해요. 이 구조에서 제가 알고 있는 규칙은 이랬어요. DDL 중에서 COPY 또는 rebuild 를 동반하는 INPLACE 알고리즘이 사용되는 경우(컬럼 타입 변경, NOT NULL 변경 등)에는 내부적으로 새 테이블을 만들어 데이터를 옮기고 옛 테이블을 지워요. InnoDB 에서는 tablespace 가 새로 생기는 것이라 (SPACE id 가 바뀜), Reader 가 갖고 있던 옛 tablespace 의 캐시 페이지는 전부 의미가 없어져요. 즉 Reader 버퍼풀에서 해당 테이블의 캐시된 페이지는 소멸하게 돼요. DDL 중에서 INSTANT 또는 rebuild 없는 INPLACE 알고리즘이 사용되는 경우에는 기존 테이블의 데이터·인덱스 페이지를 손대지 않아요. 인덱스 INVISIBLE 이나 컬럼 DEFAULT 변경처럼 데이터 딕셔너리만 고치거나, CREATE INDEX 처럼 새 B-tree 를 하나 더 만들 뿐이에요. 그래서 Reader 버퍼풀에서 해당 테이블의 캐시된 페이지는 그대로 유지돼야 해요. 두 번째 줄이 오늘 이야기의 전제이자, 깨진 가정이에요. 발단: 미사용 인덱스 6개 당근의 한 서비스는 Aurora MySQL 3.10.x 클러스터를 Writer 1대 + Reader 3대로 운영하고 있어요. 이 클러스터에는 인덱스가 20개 넘게 달린 테이블이 있었어요. 인덱스가 많으면 쓰기 비용이 늘고, 옵티마이저가 실제 쿼리 실행에 사용되지 않는 인덱스까지 평가하느라 실행 계획을 세우는 비용도 커져요. 그래서 안 쓰는 인덱스를 정리하기로 했어요. 미사용 판정은 performance_schema.table_io_waits_summary_by_index_usage 를 썼어요. 이 테이블은 인스턴스가 시작된 이후 인덱스별 읽기/쓰기 횟수를 누적하니까, 일주일 간격으로 두 번 스냅샷을 떠서 그 사이에 count_read 가 늘지 않은 인덱스를 골라냈어요. SELECT object_name, index_name, count_star, count_read, count_write FROM performance_schema.table_io_waits_summary_by_index_usage WHERE object_name = 'store_reviews'; (여기서 한 가지 팁. 이 조회는 반드시 모든 노드에