설치·접속: PostgreSQL 설치와 접속
부제: 실서비스 테이블에 인덱스를 걸어야 하는데, 일반 CREATE INDEX가 쓰기를 통째로 막아 장애가 날 때
-- (일반) 테이블에 ShareLock → INSERT/UPDATE/DELETE 대기
BEGIN;
CREATE INDEX idx_lock_demo ON m5_orders(product_id);
SELECT mode, granted FROM pg_locks l JOIN pg_class c ON c.oid=l.relation
WHERE c.relname='m5_orders' AND locktype='relation';
ROLLBACK;
-- (무중단) 트랜잭션 밖에서, 약한 잠금으로
CREATE INDEX CONCURRENTLY idx_cc_pid ON m5_orders(product_id);
-- 일반 CREATE INDEX 가 잡는 잠금:
mode | granted
-----------------+---------
ShareLock | t ← RowExclusiveLock(=INSERT/UPDATE/DELETE)와 충돌 → 쓰기 전면 대기
-- CONCURRENTLY 빌드 중 다른 세션에서 확인:
ShareUpdateExclusiveLock | t ← 쓰기 잠금과 충돌 안 함
INSERT 0 1
write succeeded during CONCURRENTLY build ← 빌드 도중에도 INSERT 성공일반 CREATE INDEX는 테이블에 ShareLock을 건다. 이 잠금은 쓰기가 필요로 하는 RowExclusiveLock과 충돌하므로, 인덱스가 다 만들어질 때까지 그 테이블의 모든 INSERT/UPDATE/DELETE가 줄줄이 멈춘다 — 2백만 행이면 수 초, 큰 테이블이면 수 분간 서비스 정지다. CONCURRENTLY는 대신 ShareUpdateExclusiveLock만 잡는다. 이건 쓰기 잠금과 충돌하지 않아서, 위 출력처럼 인덱스를 만드는 도중에도 다른 세션의 INSERT가 그대로 성공한다. 대가는 셋이다 — (1) 테이블을 두 번 스캔하므로 느리다(같은 인덱스가 3초 vs 일반은 더 빠름), (2) 트랜잭션 블록 안에서 못 쓴다(그래서 BEGIN 없이 단독 실행), (3) 중간에 실패하면 INVALID 상태의 껍데기 인덱스가 남는다. 운영 원칙은 하나 — 실서비스 테이블 인덱스는 무조건 CONCURRENTLY. 시간이 더 걸려도 쓰기를 안 멈추는 게 압도적으로 중요하다.
이렇게도 쓴다
빌드가 실패해 남은 INVALID 인덱스를 찾아 정리한다. (조합: pg_index.indisvalid)
SELECT indexrelid::regclass FROM pg_index WHERE NOT indisvalid;
-- 나온 것을 DROP INDEX CONCURRENTLY 로 제거 후 재시도
지울 때도 잠금 없이 동시 삭제한다. (조합: DROP INDEX CONCURRENTLY)
DROP INDEX CONCURRENTLY idx_cc_pid;
기존 인덱스를 잠금 없이 새 것으로 교체(재구축)한다. (조합: REINDEX CONCURRENTLY)
REINDEX INDEX CONCURRENTLY idx_m5_us;
빌드 진행률을 실시간으로 지켜본다. (조합: pg_stat_progress_create_index)
SELECT phase, blocks_done, blocks_total FROM pg_stat_progress_create_index;
빌드 도중 실제로 쓰기가 되는지 다른 세션에서 확인한다. (조합: 동시 INSERT)
-- 빌드가 도는 동안
INSERT INTO m5_orders(id,user_id,product_id,status,amount,created_at) VALUES (...); -- 성공
어떤 세션이 무슨 잠금을 잡고 있는지 들여다본다. (조합: pg_locks)
SELECT mode, granted FROM pg_locks WHERE relation='m5_orders'::regclass;'명령어 > DB' 카테고리의 다른 글
| [PostgreSQL] 격리수준 Read Committed vs Repeatable Read 같은 트랜잭션 안에서 값이 바뀔 때 (0) | 2026.07.23 |
|---|---|
| [PostgreSQL] 인덱스가 있는데 안 타는 이유 — 형변환·함수·OR·낮은 선택도 (0) | 2026.07.23 |
| [PostgreSQL] GIN vs GiST — 전문검색·배열은 GIN, 범위·좌표·최근접은 GiST (0) | 2026.07.23 |
| [PostgreSQL] 표현식 인덱스(expression index)로 LOWER(email) 검색을 태운다 (0) | 2026.07.23 |
| [PostgreSQL] 데드락 두 트랜잭션이 서로의 잠금을 기다리다 하나가 강제 중단될 때 (0) | 2026.07.23 |