[sql] 큰 Django QuerySet을 반복하는 데 많은 양의 메모리가 소비되는 이유는 무엇입니까?
문제의 테이블에는 대략 천만 개의 행이 있습니다.
for event in Event.objects.all():
print event
이로 인해 메모리 사용량이 4GB 정도까지 꾸준히 증가하여 행이 빠르게 인쇄됩니다. 첫 번째 행이 인쇄되기까지 오랜 시간이 지연되어 놀랐습니다. 거의 즉시 인쇄 될 것으로 예상했습니다.
나는 또한 Event.objects.iterator()
같은 방식으로 행동하는 것을 시도했습니다 .
Django가 메모리에로드하는 것이 무엇인지 또는 왜이 작업을 수행하는지 이해하지 못합니다. Django가 데이터베이스 수준에서 결과를 반복 할 것으로 예상했는데, 이는 결과가 대략 일정한 속도로 인쇄된다는 것을 의미합니다 (긴 기다린 후 한꺼번에 모두 인쇄하는 것이 아님).
내가 무엇을 오해 했습니까?
(관련성이 있는지는 모르겠지만 PostgreSQL을 사용하고 있습니다.)
답변
Nate C는 가까웠지만 정답은 아니 었습니다.
에서 워드 프로세서 :
다음과 같은 방법으로 QuerySet을 평가할 수 있습니다.
되풀이. QuerySet은 반복 가능하며 처음 반복 할 때 데이터베이스 쿼리를 실행합니다. 예를 들어, 이것은 데이터베이스에있는 모든 항목의 헤드 라인을 인쇄합니다.
for e in Entry.objects.all(): print e.headline
따라서 처음 해당 루프를 입력하고 쿼리 세트의 반복 형식을 얻을 때 천만 개의 행이 한 번에 검색됩니다. 당신이 경험하는 기다림은 Django가 데이터베이스 행을로드하고 실제로 반복 할 수있는 것을 반환하기 전에 각 행에 대한 객체를 만드는 것입니다. 그런 다음 모든 것을 메모리에 저장하고 결과가 쏟아집니다.
문서를 읽은 후 iterator()
QuerySet의 내부 캐싱 메커니즘을 우회하는 것 이상을 수행하지 않습니다. 일대일로하는 것이 합리적이라고 생각하지만, 반대로 데이터베이스에서 천만 건의 개별 적중이 필요합니다. 그다지 바람직하지 않을 수도 있습니다.
대규모 데이터 세트를 효율적으로 반복하는 것은 여전히 옳지 않은 일이지만, 귀하의 목적에 유용 할 수있는 몇 가지 스 니펫이 있습니다.
답변
더 빠르거나 효율적이지 않을 수도 있지만, 준비된 솔루션으로 여기에 문서화 된 django 코어의 Paginator 및 Page 개체를 사용하지 않는 이유는 다음과 같습니다.
https://docs.djangoproject.com/en/dev/topics/pagination/
이 같은:
from django.core.paginator import Paginator
from djangoapp.models import model
paginator = Paginator(model.objects.all(), 1000) # chunks of 1000, you can
# change this to desired chunk size
for page in range(1, paginator.num_pages + 1):
for row in paginator.page(page).object_list:
# here you can do whatever you want with the row
print "done processing page %s" % page
답변
Django의 기본 동작은 쿼리를 평가할 때 QuerySet의 전체 결과를 캐시하는 것입니다. 이 캐싱을 피하기 위해 QuerySet의 반복기 메서드를 사용할 수 있습니다.
for event in Event.objects.all().iterator():
print event
https://docs.djangoproject.com/en/dev/ref/models/querysets/#iterator
iterator () 메서드는 queryset을 평가 한 다음 QuerySet 수준에서 캐싱을 수행하지 않고 결과를 직접 읽습니다. 이 방법을 사용하면 한 번만 액세스하면되는 많은 수의 개체를 반복 할 때 성능이 향상되고 메모리가 크게 감소합니다. 캐싱은 여전히 데이터베이스 수준에서 수행됩니다.
iterator ()를 사용하면 메모리 사용량이 줄어들지 만 예상보다 여전히 높습니다. mpaf가 제안한 페이지 지정자 접근 방식을 사용하면 메모리가 훨씬 적게 사용되지만 테스트 사례에서는 2-3 배 느립니다.
from django.core.paginator import Paginator
def chunked_iterator(queryset, chunk_size=10000):
paginator = Paginator(queryset, chunk_size)
for page in range(1, paginator.num_pages + 1):
for obj in paginator.page(page).object_list:
yield obj
for event in chunked_iterator(Event.objects.all()):
print event
답변
이것은 문서에서 가져온 것입니다 :
http://docs.djangoproject.com/en/dev/ref/models/querysets/
쿼리 세트를 평가하기 전에는 실제로 데이터베이스 활동이 발생하지 않습니다.
따라서 print event
가 실행되면 쿼리가 실행되고 (명령에 따른 전체 테이블 스캔입니다.) 결과를로드합니다. 당신은 모든 물건을 요구하고 모든 물건을 얻지 않고는 첫 물건을 얻을 방법이 없습니다.
하지만 다음과 같이하면 :
Event.objects.all()[300:900]
http://docs.djangoproject.com/en/dev/topics/db/queries/#limiting-querysets
그런 다음 내부적으로 SQL에 오프셋과 제한을 추가합니다.
답변
많은 양의 레코드의 경우 데이터베이스 커서가 더 잘 수행됩니다. Django에서 원시 SQL이 필요합니다. Django 커서는 SQL cursur와 다른 것입니다.
Nate C가 제안한 LIMIT-OFFSET 방법이 귀하의 상황에 충분할 수 있습니다. 많은 양의 데이터의 경우 동일한 쿼리를 반복해서 실행해야하고 더 많은 결과를 건너 뛰어야하기 때문에 커서보다 느립니다.
답변
Django는 데이터베이스에서 큰 항목을 가져 오는 데 좋은 솔루션이 없습니다.
import gc
# Get the events in reverse order
eids = Event.objects.order_by("-id").values_list("id", flat=True)
for index, eid in enumerate(eids):
event = Event.object.get(id=eid)
# do necessary work with event
if index % 100 == 0:
gc.collect()
print("completed 100 items")
values_list 는 데이터베이스의 모든 ID를 가져온 다음 각 개체를 개별적으로 가져 오는 데 사용할 수 있습니다. 시간이 지남에 따라 큰 개체가 메모리에 생성되고 for 루프가 종료 될 때까지 가비지 수집되지 않습니다. 위의 코드는 매 100 번째 항목이 소비 된 후 수동 가비지 수집을 수행합니다.
답변
그런 식으로 전체 쿼리 세트의 개체가 한 번에 메모리에로드되기 때문입니다. 쿼리 세트를 더 작은 소화 가능한 비트로 청크해야합니다. 이를 수행하는 패턴을 숟가락 수유라고합니다. 다음은 간단한 구현입니다.
def spoonfeed(qs, func, chunk=1000, start=0):
''' Chunk up a large queryset and run func on each item.
Works with automatic primary key fields.
chunk -- how many objects to take on at once
start -- PK to start from
>>> spoonfeed(Spam.objects.all(), nom_nom)
'''
while start < qs.order_by('pk').last().pk:
for o in qs.filter(pk__gt=start, pk__lte=start+chunk):
yeild func(o)
start += chunk
이를 사용하려면 객체에 대해 작업을 수행하는 함수를 작성합니다.
def set_population_density(town):
town.population_density = calculate_population_density(...)
town.save()
쿼리 세트에서 해당 함수를 실행하는 것보다
spoonfeed(Town.objects.all(), set_population_density)
이것은 func
여러 객체에서 병렬 로 실행 되는 다중 처리를 통해 더욱 향상 될 수 있습니다 .