Database
대량 데이터 처리 시 Upsert 패턴 활용법
대량 쓰기에서 Upsert를 안전하고 빠르게 적용하는 기준을 정리합니다.
대량 데이터 처리 시 Upsert 패턴 활용법
Upsert란
Upsert는 "없으면 INSERT, 있으면 UPDATE"를 한 문장으로 처리하는 방식입니다.
핵심은 원자성과 경합 감소입니다.
왜 SELECT 후 INSERT/UPDATE보다 유리한가
기존 패턴:
SELECT 1 FROM users WHERE id = 1;
-- 없으면 INSERT, 있으면 UPDATE
문제점:
- 왕복 쿼리 수 증가
- SELECT 이후 경쟁 트랜잭션 개입 가능
- 애플리케이션 분기 로직 복잡도 증가
Upsert는 이 분기를 DB 엔진으로 밀어 넣어 경합 구간을 줄입니다.
DB별 기본 문법
PostgreSQL
INSERT INTO users (id, name, email)
VALUES (1, 'Kim', 'kim@example.com')
ON CONFLICT (id) DO UPDATE
SET
name = EXCLUDED.name,
email = EXCLUDED.email,
updated_at = NOW();
MySQL
INSERT INTO users (id, name, email)
VALUES (1, 'Kim', 'kim@example.com')
ON DUPLICATE KEY UPDATE
name = VALUES(name),
email = VALUES(email),
updated_at = NOW();
배치 Upsert 패턴
INSERT INTO transactions (tx_id, amount, status)
VALUES
('tx_001', 100, 'pending'),
('tx_002', 200, 'confirmed'),
('tx_003', 150, 'pending')
ON CONFLICT (tx_id) DO UPDATE
SET
amount = EXCLUDED.amount,
status = EXCLUDED.status,
updated_at = NOW();
설계 체크리스트
- 고유키/유니크 인덱스가 명확한가.
- UPDATE가 정말 필요한 컬럼만 갱신하는가.
- 동일 데이터 재처리 시 결과가 동일한가(idempotency).
- 배치 크기가 락 경합을 폭증시키지 않는가.
실수하기 쉬운 지점
- 큰 TEXT/BLOB 컬럼까지 매번 갱신해서 쓰기 비용 급증.
- 트리거나 CDC가 UPDATE 이벤트 폭증으로 병목.
- 애플리케이션 타임스탬프와 DB 타임스탬프를 혼용해 정합성 깨짐.
운영 팁
- 배치 크기부터 고정하고 성능 기준선을 먼저 잡습니다.
EXPLAIN (ANALYZE, BUFFERS)로 실제 경로를 확인합니다.- 실패 재시도는 "같은 입력이면 같은 결과"가 되도록 설계합니다.
요약
대량 인덱싱/동기화 작업에서 Upsert는 사실상 기본 선택입니다. 다만 성능은 문법이 아니라 인덱스 설계 + 배치 전략 + 갱신 컬럼 최소화로 결정됩니다.