InnoDB 기본키 UUIDv4가 느려지는 이유와 UUIDv7·BIGINT 비교
무작위 UUIDv4 기본키가 InnoDB 클러스터드 인덱스에 페이지 분할과 버퍼 풀 낭비를 일으키는 과정을 따라가고, UUIDv7·BIGINT와 같은 조건에서 삽입 속도와 인덱스 크기를 재 본 기록입니다.
SQL을 쓰고 데이터베이스를 다루며 막혔던 지점과 풀어낸 과정을 남기는 기록장입니다. 인덱스, 쿼리 튜닝, 트랜잭션, 스키마 설계를 직접 겪은 사례로 정리합니다.
무작위 UUIDv4 기본키가 InnoDB 클러스터드 인덱스에 페이지 분할과 버퍼 풀 낭비를 일으키는 과정을 따라가고, UUIDv7·BIGINT와 같은 조건에서 삽입 속도와 인덱스 크기를 재 본 기록입니다.
재고가 음수가 되는 lost update를 psql 두 창으로 재현하고, SELECT FOR UPDATE, 조건부 UPDATE, 버전 컬럼 낙관적 잠금을 같은 부하에서 비교했다. 처리량, 재시도, 코드 복잡도를 기준으로 언제 무엇을 쓰는지 적었다.
LOWER(email)이나 DATE(created_at) 조건이 인덱스를 못 타는 이유를 짚고, PostgreSQL 표현식 인덱스, MySQL 8 함수 인덱스와 생성 컬럼, SQLite 표현식 인덱스로 다시 인덱스를 태우는 방법과 함정을 정리했다.
n_dead_tup이 쌓이기만 하는 PostgreSQL 테이블에서 VACUUM VERBOSE의 'dead but not yet removable'을 읽고, pg_stat_activity의 backend_xid와 backend_xmin을 따라가 기준선을 붙잡은 트랜잭션을 찾은 기록이다.
MySQL 8.0에서 INSTANT 컬럼 추가가 되는 조건과 막히는 조건, INPLACE ALTER가 메타데이터 락에 걸려 서비스를 세우는 이유, gh-ost와 pt-online-schema-change를 꺼내는 기준을 마이그레이션 경험으로 정리했다.
같은 동시 업데이트를 PostgreSQL과 MySQL의 REPEATABLE READ에서 돌려 보면 PostgreSQL은 직렬화 오류를 내고 MySQL은 최신 값 위에 덮어쓴다. 두 DB가 스냅샷을 잡는 시점도 다르다. 실험 쿼리와 함께 차이를 짚는다.
인덱스가 있는데도 PostgreSQL이 Seq Scan을 고르는 이유를 비용 공식으로 직접 계산해 본다. 선택도, 테이블 크기, random_page_cost, correlation이 비용을 어떻게 바꾸는지, enable_seqscan=off가 왜 더 느릴 수 있는지 다룬다.
long_query_time=0으로 모든 쿼리를 기록할 때 기존 연결이 걸리는 함정, log_queries_not_using_indexes의 로그 폭주, pt-query-digest에서 Response time 비중과 Rows examine 비율을 읽는 법을 겪은 일로 정리했다.
PostgreSQL에서 CHECK NOT VALID, VALIDATE CONSTRAINT, SET NOT NULL을 차례로 써서 큰 테이블에 NOT NULL과 외래 키를 거는 절차입니다. 단계마다 잡는 잠금 수준과 lock_timeout 설정, 실제로 겪은 실수를 함께 적었습니다.
SHOW ENGINE INNODB STATUS의 교착 로그에 찍히는 lock_mode, rec but not gap, gap before rec, insert intention을 한 줄씩 풀고, 로그에 남지 않는 앞선 쿼리까지 거슬러 올라가 원인을 찾는다.
(status, created_at)과 (created_at, status) 인덱스에 같은 쿼리를 걸고 읽은 페이지 수를 비교했다. 범위 조건 뒤 컬럼이 탐색에 쓰이지 못하는 이유를 B-tree 구조로 풀었다.
pg_stat_statements에서 평균 시간이 아니라 total_exec_time과 호출 수로 정렬해야 하는 이유를 예시 숫자로 보여 주고, shared_blks_read 비율, 통계 리셋 주기, 쿼리 정규화 때문에 생기는 착시를 실무 기준으로 다룬다.