운영 중인 PostgreSQL에 인덱스를 추가할 때 CONCURRENTLY를 붙이면 모든 영향이 사라질까? 이 옵션의 목적은 인덱스 생성 중 일반적인 쓰기를 막지 않는 데 있다. 작업 대기와 자원 부하, 실패 후 상태까지 없애 주는 것은 아니다.
2026년 10월 29일 확인한 PostgreSQL 18 공식 문서를 기준으로, 비파티션 일반 테이블에 새 인덱스를 추가하는 배포 점검법을 정리했다. 실제 서버 버전과 관리형 서비스의 제약은 별도로 확인해야 한다.
쓰기 잠금과 작업 부하는 따로 판단한다
일반 CREATE INDEX는 생성 중 해당 테이블의 쓰기를 막고 일반 읽기는 허용한다. 명시적 트랜잭션에 포함하면 잠금은 보통 그 트랜잭션 종료까지 유지된다. CREATE INDEX CONCURRENTLY는 INSERT·UPDATE·DELETE를 막는 잠금을 피하지만 두 번의 테이블 스캔과 관련 트랜잭션 종료 대기가 필요하다. 추가 CPU·I/O 부하로 다른 작업이 느려질 수 있다. 공식 CREATE INDEX 설명의 핵심 차이다.
따라서 “일반 생성은 짧고 위험, 동시 생성은 무조건 안전”이라는 선택표보다 우리 서비스가 감당할 쓰기 중단, 부하, 작업 창을 먼저 정하는 편이 낫다. 운영 제안으로는 테스트 환경에서 대표 쿼리와 쓰기 응답 시간을 기록하고, 운영 배포 전에 중단 판단 기준을 담당자와 합의하는 방법이 있다. 테스트 시간은 운영 완료 시간의 보장이 아니다.
CONCURRENTLY도 잠금이 아예 없는 것은 아니다. SHARE UPDATE EXCLUSIVE 잠금을 사용하므로 같은 테이블의 VACUUM·ANALYZE 등과 경합할 수 있다. 공식 잠금 문서를 함께 확인해야 한다.
배포 도구의 트랜잭션 설정부터 확인한다
CONCURRENTLY는 트랜잭션 블록 안에서 실행할 수 없다. 같은 테이블에서 동시에 진행할 수 있는 concurrent 인덱스 생성은 하나이며, 생성 중 테이블 스키마 변경에도 제한이 있다. 파티션 부모 테이블의 concurrent 생성은 지원되지 않으므로 여기의 일반 테이블 절차를 그대로 적용하면 안 된다.
팀 체크리스트에는 다음 세 항목을 넣자. 첫째, 마이그레이션 도구가 SQL 파일 전체를 자동 트랜잭션으로 감싸는지 확인한다. 둘째, 같은 테이블의 다른 배포 작업과 겹치지 않게 한다. 셋째, 인덱스 이름뿐 아니라 열 순서·조건·고유성 등 목표 정의를 기록한다. IF NOT EXISTS는 같은 이름의 기존 인덱스가 같은 정의라는 보증이 아니다.
진행률 50%는 전체 작업의 절반일까
pg_stat_progress_create_index는 실행 중인 인덱스 생성의 phase와 진행 정보를 보여 준다. blocks_done과 blocks_total은 현재 단계의 블록 수다. 단계에 따라 빌드, 검증, 이전 스냅샷 대기처럼 하는 일이 달라진다. 대기 중에는 current_locker_pid 등 해당 단계의 필드를 함께 확인한다. 공식 진행 상태 문서에 필드별 의미가 설명돼 있다.
가상 관측값으로, 블록 진행 정보를 제공하는 한 단계에서 전체 100,000블록 중 50,000블록을 처리했다고 하자. 계산은 50,000 ÷ 100,000 × 100 = 50%다. 이는 그 단계의 블록 처리 비율이다. 전체 인덱스 생성의 절반이 끝났다거나 남은 시간이 지금까지 걸린 시간과 같다는 뜻이 아니다.
운영 화면에 표시한다면 “전체 완료율” 대신 단계명과 “현재 단계 블록 비율”을 같이 쓰는 것이 합리적이다. 분모가 0이거나 해당 단계에서 블록 정보가 유효하지 않으면 비율을 만들지 않는다. 단계가 바뀌며 값이 변한 것을 곧바로 작업 역행으로 판단하지 말자. 이 해석과 가상 계산은 실측 성능 보고가 아니다.

실패 후에는 이름보다 유효 상태를 본다
동시 생성이 실패하면 INVALID 인덱스가 남을 수 있다. 조회에 쓰이지 않으면서 갱신 부담이 남는 경우가 있고, 고유 인덱스는 실패 단계에 따라 고유성 검사를 계속 강제할 수도 있다. “실패했으니 아무것도 남지 않았다”고 판단하면 안 된다.
pg_index.indisvalid는 쿼리에 유효한지를, indisready는 INSERT에 준비됐는지를 나타낸다. true 하나만 보고 두 상태를 같다고 읽지 말자. 공식 pg_index 카탈로그가 두 필드를 구분한다. 유효한 인덱스가 생겨도 모든 쿼리가 반드시 그것을 선택한다는 뜻은 아니다.
실무적으로는 명령 종료 결과, 대상 정의, 유효 상태, 대표 쿼리 동작을 각각 확인하는 완료 기준을 권한다. 진행 뷰에서 행이 사라진 것만으로 성공을 선언하지 않는다. 실패 흔적이 있으면 원인과 의존성을 확인한 뒤 복구 절차를 정하고, 같은 이름으로 무작정 재실행하거나 인덱스를 자동 삭제하지 않는다.
작은 팀을 위한 배포 기록 네 칸
- 대상: 서버 버전, 테이블, 인덱스 정의, 배포 도구의 트랜잭션 방식.
- 영향: 기존 응답 시간, 여유 자원, 허용 대기와 중단 기준.
- 관측: 시작 시각, 현재 단계, 대기 원인, 해당 단계 진행 수치.
- 완료: 명령 결과, 인덱스 유효성, 대표 쿼리 확인, 실패 시 담당자.
공간 회수 작업과 인덱스 생성은 별도로 계획하자. 관련 운영 차이는 PostgreSQL VACUUM과 FULL 점검 글에서도 설명했다. 이 글은 인덱스 추가만으로 응답 시간이나 비용이 개선된다고 보장하지 않는다.
공식 출처와 계산을 AI 에이전트가 검토했다. 사람 검수나 독자 서버에서의 실행 경험을 주장하지 않는다. 요금·통화·세금·약정 수치는 없으며 50% 예시는 처리 단계 해석을 위한 가정이다.
