※ The reason why parallel INSERT doesn't work in PostgreSQL.
안녕하세요. 듀스트림입니다.
개발자분들에게 가장 많이 받는 질문 중 하나죠. "PG는 병렬 인서트 안 되나요?"에 대한 대답입니다.
PostgreSQL은 9.6 버전부터 병렬 쿼리를 도입하여 Parallel Seq Scan, Parallel Hash Join, Parallel Aggregate 등 읽기 연산에서 큰 성능 향상을 가져왔습니다.
하지만 2025년 기준 최신 릴리즈인 PostgreSQL 17에서도 INSERT, UPDATE, DELETE와 같은 DML은 여전히 병렬로 실행되지 않습니다.
이유를 요약하자면 아래와 같습니다.
- XID 생성이 직렬화(XidGenLock)되어 병렬로 안전하게 나눌 수 없음.
- heap_insert()가 단일 트랜잭션 컨텍스트와 WAL 동기화를 가정하고 설계됨.
- 플래너가 트리거/제약조건 등 병렬 안전성을 평가하기 어려움.
- WAL 쓰기, 버퍼 캐시 관리 등 공유 자원 접근이 병렬 처리에 적합하지 않음.
이제 자세히 알아볼까요?
1. 트랜잭션 ID(XID) 관리의 구조적 제약
1.1 XID 생성 방식과 XidGenLock
모든 쓰기 트랜잭션은 고유한 트랜잭션 ID(XID)를 필요로 하며, 이는 PostgreSQL 내부 함수 GetNewTransactionId()에서 생성됩니다.
- 관련 소스 코드: GetNewTransactionId()
XID는 공유 배열 MainLWLockArray[3]에 위치한 XidGenLock을 통해 직렬화되어 할당됩니다.
src/include/storage/lwlocklist.h 내의 매크로 정의를 통해 관리합니다.
- 관련 소스 코드: PG_LWLOCK(3, XidGen)
1.2 병렬 환경에서의 XID 공유 한계
병렬 실행 시 각 워커 프로세스가 별도 프로세스로 트랜잭션 컨텍스트를 복제하여 실행되는데 XID는 공유되지 않습니다.
따라서 병렬 워커들이 각기 다른 XID를 사용하거나 단일 트랜잭션의 상태를 제대로 반영하지 못하는 문제가 생깁니다.
트랜잭션 일관성을 유지하려면 모든 INSERT/UPDATE 작업이 동일한 XID를 갖고 heap_insert() 또는 heap_update()를 호출해야 하지만 현재 구조에서는 병렬 워커 간에 이를 안전하게 공유할 수 없습니다.
2. heap_insert() 구조와 WAL 동기화 문제
heap_insert()는 현재 트랜잭션의 XID를 튜플의 xmin 필드에 기록하고 변경 내용을 WAL(Write-Ahead Logging)에 기록합니다.
병렬 환경에서 각 워커가 개별적으로 WAL을 생성하고 공유 버퍼를 갱신하면 WAL 순서 꼬임, 버퍼 충돌, visibility 문제 등 심각한 정합성 오류가 발생할 수 있습니다.
이러한 이유로 PostgreSQL은 모든 쓰기 연산을 단일 back-end 프로세스에서 실행되도록 강제하고 있습니다.
- 관련 소스 코드: heap_insert()
3. 병렬 안전성 판단의 어려움 (Parallel Safety)
PostgreSQL의 플래너는 병렬 실행의 안전성을 아래 3가지 수준으로 판단합니다.
- parallel safe
- parallel restricted
- parallel unsafe
병렬 쓰기 연산이 제한되는 주요 이유는 다음과 같습니다.
제약 요소 | 설명 |
Parallel-unsafe triggers | 트리거는 side effect를 가질 수 있어 병렬 워커에서 실행하면 예측 불가능 |
Parallel-unsafe default expressions | 기본값에 now(), random() 같은 병렬 unsafe 함수 포함 가능 |
Parallel-unsafe check/domain constraints | 제약 조건 평가 중 병렬 안전하지 않은 함수 호출 가능성 |
Parallel-unsafe index expressions | 표현식 기반 인덱스가 병렬에서 불안정하게 동작할 수 있음 |
이러한 요소들은 플래너가 병렬로 쿼리를 분할하기 어렵게 만들며 INSERT 또는 UPDATE 대상 테이블의 모든 제약 조건과 트리거를 안전하게 병렬로 평가하기가 구조적으로 어렵습니다.
그래서 이런 구조 때문에 대량 INSERT에 대한 성능이 매우 안 나옵니다.
물론 우회 방법은 있습니다 :)
오늘은 여기까지~
'PostgreSQL' 카테고리의 다른 글
PostgreSQL: Streaming Replication (0) | 2025.06.10 |
---|---|
PostgreSQL: ROLE (4) | 2025.06.09 |
PostgreSQL: EXPLAIN (0) | 2025.05.29 |
PostgreSQL: postgres_fdw vs dblink (0) | 2025.05.28 |
PostgreSQL: work_mem (0) | 2025.05.27 |