La tabla en cuestión contiene aproximadamente diez millones de filas.
for event in Event.objects.all():
print event
Esto hace que el uso de la memoria aumente constantemente a 4 GB aproximadamente, momento en el que las filas se imprimen rápidamente. La larga demora antes de que se imprimiera la primera fila me sorprendió: esperaba que se imprimiera casi instantáneamente.
También probé Event.objects.iterator()
que se comportó de la misma manera.
No entiendo qué está cargando Django en la memoria o por qué está haciendo esto. Esperaba que Django repitiera los resultados a nivel de la base de datos, lo que significaría que los resultados se imprimirían aproximadamente a una velocidad constante (en lugar de todos a la vez después de una larga espera).
¿Qué he entendido mal?
(No sé si es relevante, pero estoy usando PostgreSQL).
fuente
Respuestas:
Nate C estaba cerca, pero no del todo.
De los documentos :
Entonces, sus diez millones de filas se recuperan, todas a la vez, cuando ingresa por primera vez a ese bucle y obtiene la forma iterativa del conjunto de consultas. La espera que experimenta es que Django carga las filas de la base de datos y crea objetos para cada una, antes de devolver algo sobre lo que realmente puede iterar. Entonces tienes todo en la memoria y los resultados se derraman.
De mi lectura de los documentos,
iterator()
no hace nada más que eludir los mecanismos internos de almacenamiento en caché de QuerySet. Creo que podría tener sentido que haga una cosa de una en una, pero eso, a la inversa, requeriría diez millones de visitas individuales a su base de datos. Quizás no sea tan deseable.Iterar sobre grandes conjuntos de datos de manera eficiente es algo que aún no hemos hecho del todo bien, pero hay algunos fragmentos que pueden resultarle útiles para sus propósitos:
fuente
Puede que no sea el más rápido o el más eficiente, pero como una solución lista para usar, ¿por qué no usar los objetos Paginator y Page de django core documentados aquí?
https://docs.djangoproject.com/en/dev/topics/pagination/
Algo como esto:
fuente
Paginator
Ahora tiene unapage_range
propiedad para evitar repetición. Si busca una sobrecarga de memoria mínima, puede usar laobject_list.iterator()
que no llenará la caché del conjunto de consultas .prefetch_related_objects
luego se requiere para la captación previaEl comportamiento predeterminado de Django es almacenar en caché todo el resultado del QuerySet cuando evalúa la consulta. Puede utilizar el método iterador de QuerySet para evitar este almacenamiento en caché:
https://docs.djangoproject.com/en/dev/ref/models/querysets/#iterator
El método iterator () evalúa el conjunto de consultas y luego lee los resultados directamente sin realizar el almacenamiento en caché en el nivel del conjunto de consultas. Este método da como resultado un mejor rendimiento y una reducción significativa de la memoria cuando se itera sobre una gran cantidad de objetos a los que solo necesita acceder una vez. Tenga en cuenta que el almacenamiento en caché todavía se realiza a nivel de la base de datos.
El uso de iterator () reduce el uso de memoria para mí, pero aún es más alto de lo que esperaba. El uso del enfoque de paginador sugerido por mpaf usa mucha menos memoria, pero es 2-3 veces más lento para mi caso de prueba.
fuente
Esto es de los documentos: http://docs.djangoproject.com/en/dev/ref/models/querysets/
Entonces cuando el
print event
se ejecuta, la consulta se activa (que es un escaneo completo de la tabla de acuerdo con su comando) y carga los resultados. Estás pidiendo todos los objetos y no hay forma de obtener el primer objeto sin obtener todos.Pero si haces algo como:
http://docs.djangoproject.com/en/dev/topics/db/queries/#limiting-querysets
Luego agregará compensaciones y límites al sql internamente.
fuente
Para grandes cantidades de registros, un cursor de base de datos funciona aún mejor. Necesita SQL sin formato en Django, el cursor de Django es algo diferente a un cursur de SQL.
El método LIMIT - OFFSET sugerido por Nate C podría ser lo suficientemente bueno para su situación. Para grandes cantidades de datos, es más lento que un cursor porque tiene que ejecutar la misma consulta una y otra vez y tiene que saltar sobre más y más resultados.
fuente
Django no tiene una buena solución para recuperar elementos grandes de la base de datos.
value_list se puede usar para buscar todos los identificadores en las bases de datos y luego buscar cada objeto por separado. Con el tiempo, los objetos grandes se crearán en la memoria y no se recolectarán basura hasta que se salga del bucle for. El código anterior realiza la recolección manual de basura después de que se consume cada centésimo elemento.
fuente
Porque de esa manera los objetos de un conjunto de consultas completo se cargan en la memoria de una vez. Necesita dividir su conjunto de consultas en bits digeribles más pequeños. El patrón para hacer esto se llama alimentación con cuchara. Aquí hay una breve implementación.
Para usar esto, escribe una función que realiza operaciones en tu objeto:
y luego ejecutar esa función en su conjunto de consultas:
Esto se puede mejorar aún más con el multiprocesamiento para ejecutar
func
en múltiples objetos en paralelo.fuente
Aquí una solución que incluye len y count:
Uso:
fuente
Por lo general, uso una consulta sin procesar de MySQL en lugar de Django ORM para este tipo de tarea.
MySQL admite el modo de transmisión por secuencias para que podamos recorrer todos los registros de forma segura y rápida sin errores de memoria.
Árbitro:
fuente
queryset.query
para su ejecución.