레이블이 create인 게시물을 표시합니다. 모든 게시물 표시
레이블이 create인 게시물을 표시합니다. 모든 게시물 표시

20250103

Django bulk_create 사용법, 성능 비교, 문제점 및 해결 방법

 Django는 ORM을 통해 데이터를 생성하는 다양한 방법을 제공합니다. 일반적으로 create() 메서드를 사용해 데이터를 삽입하지만, 대량의 데이터를 효율적으로 처리해야 하는 경우에는 bulk_create() 메서드가 훨씬 더 유용합니다. 이 글에서는 bulk_create의 사용법, 성능 비교, 발생할 수 있는 문제와 에러, 그리고 이를 해결하기 위한 방법을 종합적으로 다룹니다.

1. 데이터 생성 방법 비교

1.1 기본 데이터 생성

Django에서 데이터를 생성하는 일반적인 방법은 두 가지입니다.

a. ORM의 create() 메서드 사용

python
from myapp.models import MyModel MyModel.objects.create(name='Example', value=42)

b. 객체 생성 후 save() 호출

python
from myapp.models import MyModel obj = MyModel(name='Example', value=42) obj.save()
두 방식 모두 내부적으로 동일하게 동작하며, 단일 데이터 생성에는 적합합니다.

1.2 대량 데이터 생성

대량의 데이터를 삽입해야 하는 경우, 위 방법은 비효율적입니다. 각 데이터 생성마다 DB 연결 및 쿼리 실행이 반복되기 때문에 성능이 저하됩니다. 이를 해결하기 위해 Django는 bulk_create() 메서드를 제공합니다.

bulk_create() 메서드

bulk_create()는 한 번의 DB 연결로 여러 객체를 삽입하며, 다음과 같은 형식으로 사용됩니다:
python
Model.objects.bulk_create(objs, batch_size=None, ignore_conflicts=False)
  • objs: 삽입할 객체들의 리스트
  • batch_size: 한 번에 처리할 객체 수 (기본값은 전체 객체)
  • ignore_conflicts: 중복된 데이터가 있을 경우 무시 여부 (기본값: False)

2. 성능 비교: create() vs bulk_create()

아래는 5000개의 데이터를 삽입할 때 두 메서드의 성능을 비교한 코드입니다.

2.1 create() 사용

python
import datetime from myapp.models import MyModel start = datetime.datetime.now() for i in range(5000): MyModel.objects.create(name=f'Example {i}', value=i) time_taken = datetime.datetime.now() - start print(f'create(): {time_taken}')
결과:
create() : 약 22초 소요

2.2 bulk_create() 사용

python
import datetime from myapp.models import MyModel start = datetime.datetime.now() objs = [MyModel(name=f'Example {i}', value=i) for i in range(5000)] MyModel.objects.bulk_create(objs) time_taken = datetime.datetime.now() - start print(f'bulk_create(): {time_taken}')
결과:
bulk_create() : 약 0.07초 소요

3. 왜 bulk_create가 더 빠른가?

3.1 DB 연결 횟수

  • create(): 각 객체마다 DB 연결 → 저장 → 연결 해제를 반복합니다.
  • bulk_create(): 한 번의 DB 연결로 모든 데이터를 삽입합니다.

3.2 SQL 쿼리

  • create(): 각 객체마다 INSERT 쿼리를 실행합니다.
  • bulk_create(): UNION ALL을 사용하여 한 번에 INSERT 쿼리를 실행합니다.

4. 실용적인 예제

4.1 기본 예제

python
from myapp.models import MyModel # 대량 데이터 생성 objs = [MyModel(name=f'Example {i}', value=i) for i in range(1000)] MyModel.objects.bulk_create(objs)

4.2 배치 크기 지정

대량의 데이터를 처리할 때, 배치 크기를 지정하여 메모리 사용량을 조절할 수 있습니다.
python
MyModel.objects.bulk_create(objects_to_insert, batch_size=500)

4.3 중복 데이터 무시

ignore_conflicts=True를 설정하면 중복된 데이터가 있을 경우 무시하고 진행할 수 있습니다.
python
from myapp.models import MyModel objs = [ MyModel(name='Duplicate Example', value=1), MyModel(name='Duplicate Example', value=2), ] MyModel.objects.bulk_create(objs, ignore_conflicts=True)

5. 발생 가능한 문제와 에러

5.1 ID/Primary Key가 업데이트되지 않는 문제

원인:

일부 데이터베이스(특히 SQLite 등)는 bulk_create 호출 후 삽입된 객체의 ID를 반환하지 않습니다.

해결 방법:

  1. PostgreSQL 사용 권장.
  2. 삽입 후 ID를 재조회:
    python
    objs = MyModel.objects.bulk_create(objects_to_insert) ids = MyModel.objects.filter(name__in=[obj.name for obj in objects_to_insert]).values_list('id', flat=True)

5.2 "Too Many SQL Variables" 에러 (SQLite)

원인:

SQLite는 쿼리당 허용되는 변수의 개수(기본값 999개)에 제한이 있습니다.

해결 방법:

  1. batch_size 설정:
    python
    MyModel.objects.bulk_create(objects_to_insert, batch_size=500)
  2. 데이터 청크 처리:
    python
    from itertools import islice def chunked_queryset(queryset, chunk_size): for i in range(0, len(queryset), chunk_size): yield queryset[i:i + chunk_size] for chunk in chunked_queryset(objects_to_insert, 500): MyModel.objects.bulk_create(chunk)

5.3 IntegrityError로 인한 트랜잭션 롤백 문제

원인:

중복 데이터나 무결성 제약 조건 위반이 발생하면 전체 트랜잭션이 롤백됩니다.

해결 방법:

  1. ignore_conflicts=True 사용:
    python
    MyModel.objects.bulk_create(objects_to_insert, ignore_conflicts=True)
  2. 데이터 검증 추가:
    python
    unique_objects = [obj for obj in objects_to_insert if not MyModel.objects.filter(name=obj.name).exists()] MyModel.objects.bulk_create(unique_objects)

5.4 Many-to-Many 관계 처리 불가 문제

원인:

Many-to-Many 필드는 중간 테이블을 통해 관리되며, 이를 직접 처리해야 합니다.

해결 방법:

  1. 중간 테이블 직접 조작:
    python
    relations = [ MyModel.related_field.through(model_id=obj.id, related_id=related_obj.id) for obj in objects_to_insert for related_obj in related_objects ] MyModel.related_field.through.objects.bulk_create(relations)
  2. .add() 메서드 활용:
    python
    obj = MyModel.objects.create(name="Example") obj.related_field.add(*related_objects)

5.5 메모리 누수 및 성능 문제

원인:

대량 데이터를 메모리에 적재하거나 비효율적인 로직으로 인해 메모리 사용량이 급증할 수 있습니다.

해결 방법:

  1. 제너레이터 활용:
    python
    def generate_objects(): for i in range(100000): yield MyModel(name=f"Example {i}", value=i) MyModel.objects.bulk_create(generate_objects(), batch_size=500)
  2. 임시 저장소 활용 (CSV 파일 등):
    대규모 데이터를 임시 저장소에 기록한 후 배치로 DB에 삽입합니다.

6. 요약 및 권장 사항

문제점원인해결 방법
ID/Primary Key 미반영일부 DB에서 ID 반환 미지원PostgreSQL 사용 또는 ID 재조회
"Too Many SQL Variables" 에러SQLite 변수 제한batch_size 설정 또는 청크로 나누기
IntegrityError로 인한 트랜잭션 롤백중복 데이터 또는 무결성 제약 위반ignore_conflicts=True 사용 또는 사전 검증
Many-to-Many 관계 처리 불가중간 테이블 직접 조작 필요중간 테이블 조작 또는 .add() 메서드 활용
메모리 사용량 증가대량 데이터를 메모리에 적재제너레이터 사용 또는 임시 저장소 활용
예외 처리 제한개별 객체의 예외 처리가 불가능개별 검증 추가 또는 트랜잭션 분리
Django의 bulk_create는 대량 데이터 처리를 위한 강력한 도구지만, 위와 같은 문제점을 이해하고 적절히 대응해야 안전하고 효율적인 코드를 작성할 수 있습니다.