대량 데이터 적재 시 Primary Key는 성능에 영향을 줄까?

글쓴이: Manni Wood
2025년 6월 13일

여러분께 퀴즈 하나 드릴게요.
Postgres에서 빈 테이블에 대량의 데이터를 가장 빠르게 적재하는 방법은 무엇일까요?

  1. 모든 인덱스와 제약 조건을 활성화한 상태로 데이터를 적재한다.
  2. 테이블의 모든 인덱스와 제약 조건을 먼저 삭제한 후 데이터를 적재하고, 이후 다시 제약 조건을 활성화하고 인덱스를 재구성한다.

2번을 고르셨다면, 아마 Postgres 공식 문서의 “Populating a Database” 페이지를 읽으셨거나, 그에 상응하는 실전 경험이 많으신 분일 겁니다.

저도 예전에 비슷한 경험이 있습니다. 두 개의 유사한 테이블에 데이터를 적재하는 일이 있었는데, 하나는 순식간에 끝났고 다른 하나는 터무니없이 느렸죠. 알고 보니 느렸던 테이블은 인덱스를 미처 제거하지 않은 상태였습니다. 그 실수를 고친 후 작업은 잘 마무리했지만, ‘제약 조건을 그대로 두면 성능이 엄청나게 떨어진다’는 교훈은 지금도 생생히 기억납니다.

그런데 얼마 전, 저희 EDB의 Data Migration Service에서도 사용하는 Debezium이라는 도구에서 흥미로운 권장사항을 발견했습니다.
“대량 데이터 적재 시 테이블의 모든 제약 조건을 제거하되, Primary Key만은 그대로 두라”는 것이었죠.

이게 정말 성능에 좋은 방법일까요? 저는 처음엔 ‘이건 말도 안 돼!’라는 생각부터 들었습니다.

하지만 기술은 늘 발전하고 있고, 선입견보다 실제 성능 수치를 확인하는 것이 중요하죠. 그래서 테스트를 직접 해봤습니다.


실험 환경 구성

저는 이미 노트북에 Postgres를 설치해두었고, 외장 USB 하드디스크도 보유하고 있어서 실험 환경은 금방 꾸려졌습니다.

대용량 테이블일수록 성능 차이가 더 명확하게 나타나기에, HammerDB를 사용해 TPC-C 기준의 order_line 테이블을 생성했고, 이 테이블은 약 3억 건의 데이터를 포함하고 있습니다. 그런 다음 psql\copy 명령어를 사용해 이 데이터를 복사하고, 이를 이용해 빈 order_line 테이블에 데이터를 다시 적재하는 실험을 진행했습니다.

참고로, order_line 테이블은 다음과 같은 구조입니다:

tpcc> \d order_line
Table "public.order_line"
┌────────────────┬──────────────────────────┬───────────┬──────────┬─────────┐
│ Column │ Type │ Collation │ Nullable │ Default │
├────────────────┼──────────────────────────┼───────────┼──────────┼─────────┤
│ ol_delivery_d │ timestamp with time zone │ │ │ │
│ ol_o_id │ integer │ │ not null │ │
│ ol_w_id │ integer │ │ not null │ │
│ ol_i_id │ integer │ │ not null │ │
│ ol_supply_w_id │ integer │ │ not null │ │
│ ol_d_id │ smallint │ │ not null │ │
│ ol_number │ smallint │ │ not null │ │
│ ol_quantity │ smallint │ │ not null │ │
│ ol_amount │ numeric(6,2) │ │ │ │
│ ol_dist_info │ character(24) │ │ │ │
└────────────────┴──────────────────────────┴───────────┴──────────┴─────────┘
Indexes:
"order_line_i1" PRIMARY KEY, btree (ol_w_id, ol_d_id, ol_o_id, ol_number)


테스트 1: Primary Key를 유지한 상태 vs 제거 후 재생성

조건 1: Primary Key를 제거한 뒤 데이터 적재 후 인덱스 재생성

  • 데이터 적재: 약 10분
  • 인덱스 생성: 약 20분
  • 총 소요 시간: 30분

조건 2: Primary Key를 유지한 상태로 데이터 적재

  • 데이터 적재: 약 20분
  • 인덱스 생성: 필요 없음
  • 총 소요 시간: 20분

놀랍게도, Primary Key를 유지한 상태가 더 빨랐습니다! 저도 이 결과는 예상하지 못했어요.


테스트 2: RAM에 다 안 들어가는 경우는?

혹시 데이터가 램에 전부 들어갔기 때문에 그런 걸까 싶어, 이번에는 9억 건의 order_line 데이터를 만들어 총 100GB가 넘는 데이터를 적재해 봤습니다. 제 노트북 램은 32GB이니, 이제는 성능 차이가 나겠지 했죠.

결과는?

  • Primary Key 유지: 약 1시간
  • Primary Key 제거 후 재생성: 약 1시간 30분

역시나 Primary Key를 그대로 유지한 쪽이 더 빨랐습니다.


그 외 고려한 변수들

  • maintenance_work_mem은 최대한 높게 조정
  • 테스트 중 브라우저 등 불필요한 프로그램은 전부 종료
  • 원본 데이터와 대상 테이블은 각각 다른 디스크에 저장 (SSD vs HDD)
  • 네트워크 전송 없이 로컬 버스에서 처리

심지어 WAL을 다른 디스크에 넣어야 하나, 테이블의 로우 폭이 달라지면 어떨까 등 다양한 가정도 고려했지만, 결과는 크게 달라지지 않았습니다.


결론: 테스트가 곧 진실이다

이 실험을 통해 얻은 교훈은 명확합니다.
“일반적인 권장 사항은 참고하되, 실제 환경에선 꼭 직접 측정해보자.”

많은 경우에서 제약 조건과 인덱스를 제거한 뒤 데이터를 적재하는 것이 더 빠르다고 알려져 있지만, 요즘의 하드웨어나 Postgres 성능 개선 덕분에 Primary Key는 유지해도 괜찮은 선택일 수 있습니다.

그리고 만약 여러분이 EDB의 Data Migration Service를 이용해 9억 건의 데이터를 Postgres에 옮기고자 한다면, Primary Key가 성능 병목이 될 일은 아마 없을 겁니다.

본문: The (Non-) Effect of Primary Keys on Bulk Data Load Performance

메일: salesinquiry@enterprisedb.com