A tabela em questão contém cerca de dez milhões de linhas.
for event in Event.objects.all():
print event
Isso faz com que o uso da memória aumente continuamente para 4 GB ou mais, momento em que as linhas são impressas rapidamente. O longo atraso antes da impressão da primeira linha me surpreendeu - eu esperava que fosse imprimir quase instantaneamente.
Eu também tentei Event.objects.iterator()
qual se comportou da mesma maneira.
Não entendo o que o Django está carregando na memória ou por que está fazendo isso. Eu esperava que o Django iterasse os resultados no nível do banco de dados, o que significaria que os resultados seriam impressos em uma taxa aproximadamente constante (ao invés de todos de uma vez após uma longa espera).
O que eu entendi mal?
(Não sei se é relevante, mas estou usando PostgreSQL.)
fonte
Respostas:
Nate C estava perto, mas não exatamente.
Dos documentos :
Portanto, seus dez milhões de linhas são recuperadas, todas de uma vez, quando você entra naquele loop pela primeira vez e obtém a forma iterativa do queryset. A espera que você experimenta é o Django carregando os registros do banco de dados e criando objetos para cada um, antes de retornar algo que você possa realmente iterar. Aí você tem tudo na memória e os resultados surgem.
Pela minha leitura dos documentos,
iterator()
nada mais faz do que contornar os mecanismos de cache interno do QuerySet. Acho que pode fazer sentido fazer um por um, mas isso exigiria, ao contrário, dez milhões de acessos individuais em seu banco de dados. Talvez não seja tão desejável.A iteração em grandes conjuntos de dados com eficiência é algo que ainda não entendemos muito bem, mas existem alguns trechos que podem ser úteis para seus objetivos:
fonte
Pode não ser o mais rápido ou eficiente, mas como uma solução pronta, por que não usar os objetos Paginator e Page do core django documentados aqui:
https://docs.djangoproject.com/en/dev/topics/pagination/
Algo assim:
fonte
Paginator
agora tem umapage_range
propriedade para evitar boilerplate. Se estiver em busca de overhead mínimo de memória, você pode usar oobject_list.iterator()
que não irá preencher o cache do queryset .prefetch_related_objects
é então necessário para préO comportamento padrão do Django é armazenar em cache todo o resultado do QuerySet quando ele avalia a consulta. Você pode usar o método iterador do QuerySet para evitar este armazenamento em cache:
https://docs.djangoproject.com/en/dev/ref/models/querysets/#iterator
O método iterator () avalia o queryset e então lê os resultados diretamente, sem fazer o cache no nível QuerySet. Este método resulta em melhor desempenho e uma redução significativa na memória ao iterar sobre um grande número de objetos que você só precisa acessar uma vez. Observe que o armazenamento em cache ainda é feito no nível do banco de dados.
Usar iterator () reduz o uso de memória para mim, mas ainda é maior do que eu esperava. Usar a abordagem do paginador sugerida pelo mpaf usa muito menos memória, mas é 2-3x mais lento para o meu caso de teste.
fonte
Isso é dos documentos: http://docs.djangoproject.com/en/dev/ref/models/querysets/
Portanto, quando o
print event
é executado, a consulta é acionada (que é uma varredura completa da tabela de acordo com seu comando) e carrega os resultados. Você está pedindo todos os objetos e não há como obter o primeiro objeto sem obter todos eles.Mas se você fizer algo como:
http://docs.djangoproject.com/en/dev/topics/db/queries/#limiting-querysets
Em seguida, ele adicionará offsets e limites ao sql internamente.
fonte
Para grandes quantidades de registros, um cursor de banco de dados desempenho ainda melhor. Você precisa de SQL puro no Django, o cursor do Django é algo diferente de um cursor SQL.
O método LIMIT-OFFSET sugerido por Nate C pode ser bom o suficiente para sua situação. Para grandes quantidades de dados, é mais lento do que um cursor porque tem que executar a mesma consulta indefinidamente e saltar mais e mais resultados.
fonte
Django não tem uma boa solução para buscar itens grandes do banco de dados.
values_list pode ser usado para buscar todos os ids nos bancos de dados e então buscar cada objeto separadamente. Com o tempo, objetos grandes serão criados na memória e não serão coletados como lixo até que o loop seja encerrado. O código acima faz a coleta de lixo manual após cada centésimo item ser consumido.
fonte
Porque dessa forma os objetos de um queryset inteiro são carregados na memória de uma só vez. Você precisa dividir seu queryset em pedaços menores de digestão. O padrão para fazer isso é chamado de alimentação com colher. Aqui está uma breve implementação.
Para usar isso, você escreve uma função que executa operações em seu objeto:
e execute essa função no seu queryset:
Isso pode ser melhorado ainda mais com multiprocessamento para executar
func
em vários objetos em paralelo.fonte
Aqui, uma solução incluindo len e count:
Uso:
fonte
Eu geralmente uso a consulta bruta do MySQL em vez do Django ORM para esse tipo de tarefa.
O MySQL oferece suporte ao modo de streaming para que possamos percorrer todos os registros com segurança e rapidez, sem erros de falta de memória.
Ref:
fonte
queryset.query
para em sua execução.