남기다
홈 › 전체 글

전체 글

모두 21편

InnoDB 기본키 UUIDv4가 느려지는 이유와 UUIDv7·BIGINT 비교

무작위 UUIDv4 기본키가 InnoDB 클러스터드 인덱스에 페이지 분할과 버퍼 풀 낭비를 일으키는 과정을 따라가고, UUIDv7·BIGINT와 같은 조건에서 삽입 속도와 인덱스 크기를 재 본 기록입니다.

스키마 설계 ·

재고 차감 동시성: FOR UPDATE, 조건부 UPDATE, 버전 컬럼 비교

재고가 음수가 되는 lost update를 psql 두 창으로 재현하고, SELECT FOR UPDATE, 조건부 UPDATE, 버전 컬럼 낙관적 잠금을 같은 부하에서 비교했다. 처리량, 재시도, 코드 복잡도를 기준으로 언제 무엇을 쓰는지 적었다.

트랜잭션과 잠금 ·

WHERE에 함수를 씌우면 인덱스가 죽는다: 표현식 인덱스와 생성 컬럼

LOWER(email)이나 DATE(created_at) 조건이 인덱스를 못 타는 이유를 짚고, PostgreSQL 표현식 인덱스, MySQL 8 함수 인덱스와 생성 컬럼, SQLite 표현식 인덱스로 다시 인덱스를 태우는 방법과 함정을 정리했다.

인덱스와 성능 ·

dead tuple이 안 줄 때 xmin horizon 추적하기

n_dead_tup이 쌓이기만 하는 PostgreSQL 테이블에서 VACUUM VERBOSE의 'dead but not yet removable'을 읽고, pg_stat_activity의 backend_xid와 backend_xmin을 따라가 기준선을 붙잡은 트랜잭션을 찾은 기록이다.

운영과 도구 ·

MySQL 온라인 DDL, INSTANT부터 gh-ost까지 고르는 법

MySQL 8.0에서 INSTANT 컬럼 추가가 되는 조건과 막히는 조건, INPLACE ALTER가 메타데이터 락에 걸려 서비스를 세우는 이유, gh-ost와 pt-online-schema-change를 꺼내는 기준을 마이그레이션 경험으로 정리했다.

스키마 설계 ·

PostgreSQL과 MySQL의 REPEATABLE READ가 다른 이유

같은 동시 업데이트를 PostgreSQL과 MySQL의 REPEATABLE READ에서 돌려 보면 PostgreSQL은 직렬화 오류를 내고 MySQL은 최신 값 위에 덮어쓴다. 두 DB가 스냅샷을 잡는 시점도 다르다. 실험 쿼리와 함께 차이를 짚는다.

트랜잭션과 잠금 ·

PostgreSQL이 인덱스 두고 Seq Scan 고르는 이유, 숫자로 따라가기

인덱스가 있는데도 PostgreSQL이 Seq Scan을 고르는 이유를 비용 공식으로 직접 계산해 본다. 선택도, 테이블 크기, random_page_cost, correlation이 비용을 어떻게 바꾸는지, enable_seqscan=off가 왜 더 느릴 수 있는지 다룬다.

인덱스와 성능 ·

MySQL 슬로 쿼리 로그와 pt-query-digest로 병목 쿼리 순위 매기기

long_query_time=0으로 모든 쿼리를 기록할 때 기존 연결이 걸리는 함정, log_queries_not_using_indexes의 로그 폭주, pt-query-digest에서 Response time 비중과 Rows examine 비율을 읽는 법을 겪은 일로 정리했다.

운영과 도구 ·

대용량 테이블에 NOT NULL·외래 키를 무중단으로 추가하는 순서

PostgreSQL에서 CHECK NOT VALID, VALIDATE CONSTRAINT, SET NOT NULL을 차례로 써서 큰 테이블에 NOT NULL과 외래 키를 거는 절차입니다. 단계마다 잡는 잠금 수준과 lock_timeout 설정, 실제로 겪은 실수를 함께 적었습니다.

스키마 설계 ·

InnoDB 교착 로그 읽기: LATEST DETECTED DEADLOCK

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로 서버를 가장 많이 잡아먹는 쿼리 찾기

pg_stat_statements에서 평균 시간이 아니라 total_exec_time과 호출 수로 정렬해야 하는 이유를 예시 숫자로 보여 주고, shared_blks_read 비율, 통계 리셋 주기, 쿼리 정규화 때문에 생기는 착시를 실무 기준으로 다룬다.

운영과 도구 ·

PostgreSQL ALTER TABLE, 다시 쓰는 DDL과 안 쓰는 DDL

PostgreSQL 11부터 ADD COLUMN DEFAULT가 순식간에 끝나는 원리와, int를 bigint로 바꾸는 것처럼 여전히 테이블 전체를 다시 쓰는 타입 변경을 구분합니다. 수천만 건 테이블에서 파일 번호와 잠금을 직접 확인하는 방법도 담았습니다.

스키마 설계 ·

없는 행에 SELECT FOR UPDATE를 걸면 MySQL이 교착되는 이유

MySQL REPEATABLE READ에서 없는 행에 SELECT ... FOR UPDATE를 걸면 두 트랜잭션이 같은 갭 락을 나눠 잡고 서로의 삽입 의도 잠금을 막는다. 재현 순서와 세 가지 해법의 장단점을 비교한다.

트랜잭션과 잠금 ·

OFFSET 페이지네이션이 느려지는 이유와 키셋 전환기

OFFSET 100000이 실제로 읽고 버리는 행 수를 EXPLAIN ANALYZE로 재 보고, (created_at, id) 행 값 비교와 인덱스 방향을 맞춘 키셋 페이지네이션으로 옮긴 과정과 페이지 번호를 포기한 대가를 기록한다.

인덱스와 성능 ·

재귀 CTE 무한 루프, 순환 데이터를 막는 DB별 방법

WITH RECURSIVE로 카테고리 트리를 펼치다 순환 데이터 때문에 쿼리가 끝나지 않을 때, 경로 배열과 PostgreSQL 14 CYCLE 절, 깊이 제한으로 막는 방법을 PostgreSQL·MySQL·SQLite별로 비교한다.

쿼리와 SQL ·

COUNT(*), COUNT(1), COUNT(컬럼)의 차이와 성능 통념 검증

COUNT(*), COUNT(1), COUNT(컬럼)의 결과가 갈리는 NULL의 경우와 성능 통념을 MySQL과 PostgreSQL 문서로 검증한다. InnoDB에서 COUNT(*)가 느린 이유와 PostgreSQL에서 근삿값을 구하는 방법도 함께 다룬다.

쿼리와 SQL ·

LAST_VALUE가 마지막 값을 안 주는 이유: 윈도 프레임 기본값

ORDER BY를 붙인 윈도 함수에는 RANGE 기본 프레임이 따라붙어 누적합과 LAST_VALUE가 틀려 보입니다. 동점 행에서 ROWS와 RANGE가 갈라지는 지점을 실제 쿼리로 짚습니다.

쿼리와 SQL ·

그룹별 최신 1건 뽑기: SQL 네 가지 방식 비교

사용자별 마지막 주문 같은 greatest-n-per-group 문제를 상관 서브쿼리, ROW_NUMBER, DISTINCT ON, LATERAL로 풀어 본다. PostgreSQL·MySQL·SQLite에서 되는 문법과 인덱스에 따른 성능 차이, 동점 처리를 정리했다.

쿼리와 SQL ·

LEFT JOIN에 WHERE를 걸면 INNER JOIN이 되는 이유

LEFT JOIN에서 오른쪽 테이블 조건을 ON에 둘 때와 WHERE에 둘 때 결과가 갈리는 까닭을 예제 데이터로 따라가고, PostgreSQL·MySQL·SQLite가 외부 조인을 내부 조인으로 바꾸는 모습을 EXPLAIN으로 확인한다.

쿼리와 SQL ·

NOT IN 서브쿼리에 NULL 하나만 섞여도 결과가 0건이 되는 이유

NOT IN 서브쿼리에 NULL 하나만 섞여도 결과가 0건이 되는 이유를 SQL 3값 논리로 한 단계씩 따라갑니다. NOT EXISTS와 LEFT JOIN IS NULL로 바꿨을 때의 결과와 실행 계획을 PostgreSQL, MySQL, SQLite에서 비교합니다.

쿼리와 SQL ·