본문 바로가기
Artificial Intelligence/60. Python

[PYTHON] 대량 INSERT 시 bulk_create와 일반 루프의 100배 성능 차이 및 해결 방법

by Papa Martino V 2026. 3. 20.
728x90

bulk_create
bulk_create

 

 

파이썬으로 데이터 수집기(Collector)나 마이그레이션 스크립트를 작성할 때, 가장 빈번하게 마주치는 병목 지점은 바로 데이터베이스 INSERT 작업입니다. 단순히 for 루프를 돌려 1만 개의 데이터를 하나씩 저장하는 방식은 개발 초기에는 간편해 보이지만, 실제 서비스 환경에서는 DB 커넥션 오버헤드와 트랜잭션 처리 비용으로 인해 시스템 전체의 성능을 저하시키는 주범이 됩니다. 오늘 이 글에서는 Django와 SQLAlchemy 등 주요 파이썬 ORM에서 제공하는 bulk_create의 내부 메커니즘을 분석하고, 일반 루프와 비교했을 때 발생하는 극적인 성능 차이와 이를 최적화하는 구체적인 해결 방법을 제시합니다.


1. 데이터 삽입 방식에 따른 아키텍처적 차이 분석

일반 루프를 통한 삽입과 대량 삽입(Bulk Insert)의 근본적인 차이는 네트워크 왕복(Round-trip) 횟수트랜잭션 커밋 전략에 있습니다.

비교 항목 일반 for 루프 (.save()) Bulk Create (대량 삽입)
쿼리 생성 방식 N개의 독립된 INSERT 쿼리 1개(또는 수 개)의 거대한 복합 쿼리
네트워크 비용 N번의 요청과 응답 발생 (매우 높음) 1번의 요청으로 모든 데이터 전송 (매우 낮음)
트랜잭션 관리 매 루프마다 자동 커밋(Auto-commit) 발생 가능 단일 트랜잭션 내에서 모든 작업 처리
성능 수치 (1만 건 기준) 수 분 이상 소요 1~3초 내외 완료

2. 왜 bulk_create가 100배 이상 빠른가? (내부 동작 원리)

데이터베이스 엔진 관점에서 보면, 하나의 INSERT 문을 처리하기 위해 파싱(Parsing), 실행 계획 수립, 락(Lock) 획득, 로그 기록 등의 과정을 거칩니다. 일반 루프 방식은 이 과정을 1만 번 반복하지만, bulk_create는 단 한 번의 과정으로 수천 개의 레코드를 적재합니다.

성능 향상의 핵심 3요소

  • SQL 오버헤드 감소: INSERT INTO table (col) VALUES (val1), (val2), ... (val1000) 형태로 쿼리를 구성하여 구문 분석 횟수를 최소화합니다.
  • 트랜잭션 오버헤드 제거: 데이터베이스는 데이터를 물리적 디스크에 기록할 때 가장 많은 시간을 소모합니다. 대량 삽입은 단일 커밋으로 디스크 I/O 횟수를 획기적으로 줄입니다.
  • DB 드라이버 최적화: 파이썬 DB API(psycopg2, mysqlclient 등) 수준에서 바이너리 데이터를 한꺼번에 전송하여 파이썬 인터프리터의 부하를 줄입니다.

3. 실무 적용 시 주의해야 할 3가지 제약 사항과 해결 방법

무조건 bulk_create가 만능은 아닙니다. 사용 전 반드시 고려해야 할 해결 과제들이 있습니다.

(1) Primary Key 반환 문제

일부 데이터베이스(SQLite 등)와 구형 ORM 버전에서는 대량 삽입 후 생성된 객체들의 ID(PK) 값을 즉시 참조할 수 없는 경우가 있습니다.
해결 방법: PostgreSQL과 같이 RETURNING 구문을 지원하는 DB를 사용하거나, 최신 Django 버전에서 제공하는 ignore_conflicts=True 옵션을 검토하십시오.

(2) 신호(Signals) 미발생

Django의 pre_savepost_save 시그널은 bulk_create 시 호출되지 않습니다.
해결 방법: 시그널에 의존하는 로직이 있다면, 삽입 작업 완료 후 수동으로 해당 로직을 실행하는 별도의 함수를 구현해야 합니다.

(3) 메모리 사용량 폭증

한 번에 수십만 건을 리스트로 만들어 전달하면 파이썬 프로세스의 메모리가 고갈될 수 있습니다.
해결 방법: Batch Size(배치 크기) 설정을 활용하여 데이터를 500~1,000건 단위로 쪼개어 삽입하십시오.


4. [Sample Example] Django에서의 최적화 구현 예제

단순 삽입과 배치 처리를 결합한 가장 안정적인 형태의 파이썬 코드 샘플입니다.


from myapp.models import Product

# 1. 삽입할 대량 데이터 준비 (리스트 컴프리헨션 활용)
data_list = [
    Product(name=f"상품_{i}", price=10000 + i) 
    for i in range(10000)
]

# 2. 최적화된 Bulk Create 실행
# batch_size를 지정하여 메모리 관리와 속도 사이의 균형을 맞춤
Product.objects.bulk_create(
    data_list, 
    batch_size=1000, 
    ignore_conflicts=True  # 중복 키 발생 시 무시 (필요시 사용)
)

print("10,000건의 데이터가 성공적으로 적재되었습니다.")

5. 전문적인 데이터 엔지니어의 성능 제언

성능을 극한으로 끌어올려야 하는 데이터 마이그레이션 프로젝트라면 다음의 3단계를 고려하십시오.

  1. 인덱스 일시 정지: 대량 삽입 전 인덱스를 드롭(Drop)하고, 삽입 완료 후 재구성(Rebuild)하면 삽입 속도가 수 배 더 빨라집니다.
  2. DB 로그 최소화: WAL(Write Ahead Logging) 설정을 조정하여 일시적으로 로그 기록 부하를 줄일 수 있습니다. (운영 환경 주의 요망)
  3. COPY 명령어 활용: ORM 수준을 넘어선 초고속 삽입이 필요하다면, PostgreSQL의 COPY나 MySQL의 LOAD DATA INFILE 기능을 파이썬에서 직접 호출하는 것이 최고의 해결 방법입니다.

6. 내용의 출처 및 참고 문헌

  • Django Project Documentation: "QuerySet API reference - bulk_create"
  • SQLAlchemy 2.0 Docs: "ORM-Enabled Insert Statements (Bulk Operations)"
  • PostgreSQL Manual: "Populating a Database - Disable Autocommit"
  • High Performance Python (Micha Gorelick, Ian Ozsvald): "Database Optimization Techniques"
728x90