Django는 ORM을 통해 데이터를 생성하는 다양한 방법을 제공합니다. 일반적으로 create()
메서드를 사용해 데이터를 삽입하지만, 대량의 데이터를 효율적으로 처리해야 하는 경우에는 bulk_create()
메서드가 훨씬 더 유용합니다. 이 글에서는 bulk_create
의 사용법, 성능 비교, 발생할 수 있는 문제와 에러, 그리고 이를 해결하기 위한 방법을 종합적으로 다룹니다.
1. 데이터 생성 방법 비교
1.1 기본 데이터 생성
Django에서 데이터를 생성하는 일반적인 방법은 두 가지입니다.a. ORM의 create()
메서드 사용
pythonfrom myapp.models import MyModel MyModel.objects.create(name='Example', value=42)
b. 객체 생성 후 save()
호출
pythonfrom myapp.models import MyModel obj = MyModel(name='Example', value=42) obj.save()
1.2 대량 데이터 생성
대량의 데이터를 삽입해야 하는 경우, 위 방법은 비효율적입니다. 각 데이터 생성마다 DB 연결 및 쿼리 실행이 반복되기 때문에 성능이 저하됩니다. 이를 해결하기 위해 Django는bulk_create()
메서드를 제공합니다.bulk_create()
메서드
bulk_create()
는 한 번의 DB 연결로 여러 객체를 삽입하며, 다음과 같은 형식으로 사용됩니다:pythonModel.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()
사용
pythonimport 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()
사용
pythonimport 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 기본 예제
pythonfrom myapp.models import MyModel # 대량 데이터 생성 objs = [MyModel(name=f'Example {i}', value=i) for i in range(1000)] MyModel.objects.bulk_create(objs)
4.2 배치 크기 지정
대량의 데이터를 처리할 때, 배치 크기를 지정하여 메모리 사용량을 조절할 수 있습니다.pythonMyModel.objects.bulk_create(objects_to_insert, batch_size=500)
4.3 중복 데이터 무시
ignore_conflicts=True
를 설정하면 중복된 데이터가 있을 경우 무시하고 진행할 수 있습니다.pythonfrom 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를 반환하지 않습니다.해결 방법:
- PostgreSQL 사용 권장.
- 삽입 후 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개)에 제한이 있습니다.해결 방법:
batch_size
설정:pythonMyModel.objects.bulk_create(objects_to_insert, batch_size=500)
- 데이터 청크 처리: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로 인한 트랜잭션 롤백 문제
원인:
중복 데이터나 무결성 제약 조건 위반이 발생하면 전체 트랜잭션이 롤백됩니다.해결 방법:
ignore_conflicts=True
사용:pythonMyModel.objects.bulk_create(objects_to_insert, ignore_conflicts=True)
- 데이터 검증 추가: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 필드는 중간 테이블을 통해 관리되며, 이를 직접 처리해야 합니다.해결 방법:
- 중간 테이블 직접 조작: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)
.add()
메서드 활용:pythonobj = MyModel.objects.create(name="Example") obj.related_field.add(*related_objects)
5.5 메모리 누수 및 성능 문제
원인:
대량 데이터를 메모리에 적재하거나 비효율적인 로직으로 인해 메모리 사용량이 급증할 수 있습니다.해결 방법:
- 제너레이터 활용: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)
- 임시 저장소 활용 (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() 메서드 활용 |
메모리 사용량 증가 | 대량 데이터를 메모리에 적재 | 제너레이터 사용 또는 임시 저장소 활용 |
예외 처리 제한 | 개별 객체의 예외 처리가 불가능 | 개별 검증 추가 또는 트랜잭션 분리 |
bulk_create
는 대량 데이터 처리를 위한 강력한 도구지만, 위와 같은 문제점을 이해하고 적절히 대응해야 안전하고 효율적인 코드를 작성할 수 있습니다.