[Python] Django model instance 변수를 리스트에 담을 때 주의할 점

데이터베이스에 신규 데이터를 넣어주기 위해서 엑셀 파일을 읽어 데이터베이스에 넣어주는 작업 중에 마주한 문제인데, 메모리및 IO 작업을 최소화하기 위해 chunk size 만큼 리스트에 Django model instance 변수를 담고 .bulk_create를 수행했다. 당연히 chunk size에 도달하여 리스트를 초기화해주면 메모리가 해제될 거 같지만 그렇지 않았고, 메모리가 가득차서 곧 서버 장애로 이어졌다.

import pandas as pd


data = pd.read_excel(...)
models = []
for index, row in data.iterrows():
    models.append(get_model_instance(model_class, row)
    if len(models) == chunk_size:
        model_class.objects.bulk_create(models)
        models = []
else:
    if models:
        model_class.objects.bulk_create(models)

테스트 코드

In [1]: User.__del__ = lambda x: print(id(x))
In [2]: l = []
In [3]: u = User.objects.first()
In [4]: id(u)
Out[4]: 4833674432
In [5]: l.append(u)
In [6]: u = User.objects.first()
In [7]: id(u)
Out[7]: 4848304192
In [8]: l.append(u)
In [9]: u = User.objects.first()
In [10]: id(u)
Out[10]: 4848129456
In [11]: l.append(u)
In [12]: l = []
In [13]: del u
In [14]: u = User.objects.first()
In [15]: u = None
4848386112

일반 오브젝트에서는 문제 없음

>>> class Test:
...     def __del__(self):
...         print(id(self))
...
>>> l = []
>>> t = Test()
>>> id(t)
4378831936 (1)
>>> l.append(t)
>>> t = Test()
>>> id(t)
4379975536 (2)
>>> l.append(t)
>>> t = Test()
>>> id(t)
4378420512 (3)
>>> l.append(t)
>>> l = []
4379975536 (2)
4378831936 (1)

(3)은 아직 변수에 할당되어 있기 때문에 해제되지 않음

해결 방법

변수 스코프를 완전히 끊어버리면 된다.

In [1]: User.__del__ = lambda x: print("삭제!", id(x))

In [2]: def process():
   ...:     l = []
   ...:     for u in range(3):
   ...:         u = User.objects.first()
   ...:         print("생성!", id(u))
   ...:         l.append(u)
   ...:     print("함수 안녕!")
   ...:

In [3]: process()
생성! 5015568544
생성! 5015570848
생성! 5029692560
함수 안녕!
삭제! 5015570848
삭제! 5015568544
삭제! 5029692560

추가로 알게된 점

  • 변수가 스택에 할당되어 있어서 그런지 나중에 생성된 놈이 먼저 메모리에서 해제된다. 여기서도 Django model instance는 예외다 ㅎ

Written by@EHX
Software Developer, Back-End Engineer

GitHubFacebook