IntegrityError – 대량 저장 중 충돌 해결

2025. 4. 28. 13:51·python/Django
728x90

개발을 하다가 대량의 데이터를 한꺼번에 저장하는 기능이 필요한 적이 있었다.

 

Django ORM의 bulk_create()를 이용해 수천 건을 한 번에 insert하려고 했는데, 

간헐적으로 IntegrityError가 발생하면서 작업에 실패했었다.

 

특히,

  • Primary Key가 수동으로 지정되는 경우
  • Unique 제약조건이 걸린 필드가 중복되는 경우
  • ForeignKey로 연결된 테이블에 없는 값이 들어오는 경우

원인 분석

bulk_create()는 내부적으로 INSERT INTO ... VALUES (), () ... 식으로 한 번에 SQL을 보낸다.

트랜잭션을 걸지 않으면 한 레코드라도 오류 나면 전체가 실패한다.

트랜잭션을 걸어도, IntegriryError가 발생하면 모든 데이터가 롤백된다.

 

즉,

데이터가 하나라도 문제가 있으면 전체 작업 실패

문제가 뭔지 모르면 어디서 실패했는지 찾기도 어렵다.

 

위험성을 분석하게 되면

  • 한 번에 수천 건(그 이상) 저장 실패 → 전체 작업 롤백 → 대량 데이터 유실
  • 사용자는 성공/실패 여부를 알 수 없음 → 불안정한 UX
  • 어디서 실패했는지 디버깅이 어렵고 시간이 오래 걸림
  • 서버 리소스 낭비 (DB 커넥션, 트랜잭션 시간 낭비)

 

해결 방법

  • bulk_create + ignore_conflicts=True 사용
    • 충돌 나는 레코드는 무시하고 저장할 수 있음
from myapp.models import MyModel

MyModel.objects.bulk_create(
    objs=objects_to_insert, 
    ignore_conflicts=True
)

 - 장점: 빠르게 저장이 가능하다.

 - 단점: 충돌된 데이터가 무시되므로 사용자에게 알리지는 못함.

 

 

  • save()로 하나씩 저장 + try-except 적용
    • 실패한 레코드를 따로 로깅하거나 처리 가능.
from django.db import IntegrityError

for obj in objects_to_insert:
    try:
        obj.save()
    except IntegrityError as e:
        # 실패한 데이터 로깅
        logger.warning(f"IntegrityError 발생: {obj} - {e}")

 - 장점: 실패한 데이터를 로깅하거나 따로 처리 가능

 - 단점: 성능이 bulk보다 느림 (1건 1쿼리)

 

 

  • 사건 검증(Validation) 후 bulk_create
    • PK/Unique/ForeignKey 위반이 예상되는 레코드를 사전에 필터링해서 넣는다.
# 예시: 이미 존재하는 PK 목록 가져오기
existing_pks = set(MyModel.objects.filter(id__in=[obj.id for obj in objects_to_insert]).values_list('id', flat=True))

# 중복되지 않는 데이터만 저장
filtered_objects = [obj for obj in objects_to_insert if obj.id not in existing_pks]

MyModel.objects.bulk_create(filtered_objects)

 - 장점: 실패 확률 최소화

 - 단점: 사전 조회 비용이 들고, 실시간성이 필요한 경우엔 불리함.

 

 

  • transaction.atomic()으로 묶고 세밀하게 관리
    • 실패 시 savepoint 롤백해서 일부분만 복구 가능하게 한다.
from django.db import transaction

try:
    with transaction.atomic():
        MyModel.objects.bulk_create(objects_to_insert)
except IntegrityError:
    # 실패 시 전체 롤백
    logger.error("Bulk create 실패. 전체 트랜잭션 롤백됨.")

 

 


어떻게 해야 할까?

 

  • ignore_conflicts=True
    → 대량 insert에서 실패한 데이터 무시하고 빠르게 진행할 수 있다. (속도 우선)

 

  • 개별 save() + try-except
    → 실패한 데이터도 추적하고, 사용자가 어떤 데이터를 못 넣었는지 알 수 있다. (정확성 우선)

 

  • 사전 검증
    → 사전에 중복/오류를 차단해서 실패 자체를 줄인다. (안정성 우선)

 

  • transaction.atomic()
    → 데이터 일관성을 확보할 수 있다. (DB 무결성 우선)

 

bulk_create는 기본적으로 save()의 pre_save, post_save signal을 발생시키지 않는다.

즉, auto_now_add, auto_now 적용 안될 수 있음

 

대량 데이터 처리할 때는 항상 batch_size를 적절히 설정해야한다.

예를 들어 bulk_create(objs, batch_size=1000)

그리고 실패한 데이터는 무조건 별도 로깅하야합니다. 그렇지 않으면 장애 원인 추적이 불가능할 수도 있습니다.

 

 

Django로 대량 데이터를 저장할 때는 항상 IntegrityError를 대비해야 합니다.

실패를 무시할지, 로깅할지, 아예 실패로 막을지 상황에 맞는 전략을 세우고 코드를 작성하는게 중요하다고 생각합니다.

 

 

728x90
반응형
저작자표시 비영리 변경금지 (새창열림)

'python > Django' 카테고리의 다른 글

DoesNotExist vs MultipleObjectsReturned - ORM 예외처리  (0) 2025.05.08
OperationalError 해결 방법 – DB 커넥션 끊김 (MySQL)  (0) 2025.05.08
transaction.atomic() 트랙잭션 처리하기  (0) 2025.04.30
ValidationError – API/Form 검증  (0) 2025.04.28
DRF(Django REST Framework) 란?  (0) 2023.08.23
'python/Django' 카테고리의 다른 글
  • OperationalError 해결 방법 – DB 커넥션 끊김 (MySQL)
  • transaction.atomic() 트랙잭션 처리하기
  • ValidationError – API/Form 검증
  • DRF(Django REST Framework) 란?
Balang
Balang
음악 전공생의 개발일지
  • Balang
    Balang
    Balang
  • 전체
    오늘
    어제
  • 반응형
    • All Post (146)
      • python (45)
        • selenium (4)
        • algorithm (9)
        • Django (6)
        • Pandas | Numpy (22)
      • SQL (9)
      • Data Engineer (29)
      • Data Scientist (3)
      • Data Analysis (9)
      • Computer Science (35)
      • Why? (15)
      • 마음가짐 (1)
  • 인기 글

  • 최근 댓글

  • 최근 글

  • 250x250
  • hELLO· Designed By정상우.v4.10.3
Balang
IntegrityError – 대량 저장 중 충돌 해결
상단으로

티스토리툴바