python - queryset - select_related django example
Limitar el uso de memoria en un*Large*Django QuerySet (6)
¿Qué hay de usar los objetos Paginator y Page de django core documentados aquí?
https://docs.djangoproject.com/en/dev/topics/pagination/
Algo como esto:
from django.core.paginator import Paginator
from djangoapp.models import SomeModel
paginator = Paginator(SomeModel.objects.all(), 1000) # chunks of 1000
for page_idx in range(1, paginator.num_pages):
for row in paginator.page(page_idx).object_list:
# here you can do what you want with the row
print "done processing page %s" % page_idx
Tengo una tarea que debo ejecutar en la mayoría de los objetos de mi base de datos una vez cada cierto período de tiempo (una vez al día, una vez a la semana, lo que sea). Básicamente, esto significa que tengo una consulta que se parece a esto ejecutándose en su propio hilo.
for model_instance in SomeModel.objects.all():
do_something(model_instance)
(Tenga en cuenta que en realidad es un filtro () no todos (), pero de todos modos, todavía termino seleccionando un conjunto muy grande de objetos).
El problema con el que me estoy topando es que, después de ejecutarme por un tiempo, mi proveedor de hospedaje elimina el subproceso porque estoy usando demasiada memoria. Supongo que todo este uso de memoria está ocurriendo porque aunque el objeto QuerySet
devuelto por mi consulta inicialmente tiene una huella de memoria muy pequeña, termina creciendo a medida que el objeto QuerySet
almacena en caché cada QuerySet
cuando los QuerySet
.
Mi pregunta es: "¿cuál es la mejor manera de recorrer de manera SomeModel
en casi todos los SomeModel
de mi base de datos?" o tal vez mi pregunta sea "¿cómo puedo ''desaguar'' instancias de modelos de un queryset django?"
EDITAR: En realidad estoy usando los resultados del queryset para construir una serie de nuevos objetos. Como tal, no termino actualizando los objetos buscados.
Continúo investigando y parece que quiero hacer el equivalente a un DESPLAZAMIENTO y LÍMITE de SQL, lo que de acuerdo con Django Doc''s en Limiting Querysets significa que quiero usar la sintaxis de segmento, por ejemplo, SomeModel.objects.all()[15:25]
Así que ahora estoy pensando que tal vez algo como esto es lo que estoy buscando:
# Figure out the number of objects I can safely hold in memory
# I''ll just say 100 for right now
number_of_objects = 100
count = SomeModel.objects.all().count():
for i in xrange(0,count,number_of_objects):
smaller_queryset = SomeModel.objects.all()[i:i+number_of_objects]
for model_instance in smaller_queryset:
do_something(model_instance)
Según mis cálculos, esto lo haría de modo que el smaller_queryset
más smaller_queryset
nunca crecería demasiado.
Entonces, lo que realmente terminé haciendo es compilar algo en lo que puedes "envolver" un QuerySet. Funciona al hacer una copia en profundidad del QuerySet, usando la sintaxis de la porción, por ejemplo, some_queryset[15:45]
pero luego se crea otra copia en profundidad del QuerySet original cuando la división se ha iterado completamente. Esto significa que solo el conjunto de Objetos devueltos en ''este'' segmento particular se almacenan en la memoria.
class MemorySavingQuerysetIterator(object):
def __init__(self,queryset,max_obj_num=1000):
self._base_queryset = queryset
self._generator = self._setup()
self.max_obj_num = max_obj_num
def _setup(self):
for i in xrange(0,self._base_queryset.count(),self.max_obj_num):
# By making a copy of of the queryset and using that to actually access
# the objects we ensure that there are only `max_obj_num` objects in
# memory at any given time
smaller_queryset = copy.deepcopy(self._base_queryset)[i:i+self.max_obj_num]
logger.debug(''Grabbing next %s objects from DB'' % self.max_obj_num)
for obj in smaller_queryset.iterator():
yield obj
def __iter__(self):
return self
def next(self):
return self._generator.next()
Así que en lugar de ...
for obj in SomeObject.objects.filter(foo=''bar''): <-- Something that returns *a lot* of Objects
do_something(obj);
Tu harías...
for obj in MemorySavingQuerysetIterator(in SomeObject.objects.filter(foo=''bar'')):
do_something(obj);
Tenga en cuenta que la intención de esto es ahorrar memoria en su intérprete de Python . Esencialmente lo hace haciendo más consultas de base de datos. Por lo general, las personas están tratando de hacer exactamente lo contrario, es decir, minimizar las consultas de la base de datos tanto como sea posible sin tener en cuenta el uso de memoria. Esperemos que alguien encuentre esto útil sin embargo.
Hay un fragmento de django para esto:
http://djangosnippets.org/snippets/1949/
Se itera sobre un conjunto de consultas al producir filas de "fragmentos" más pequeños del queryset original. Termina usando una cantidad de memoria significativamente menor, mientras que le permite ajustar la velocidad. Lo uso en uno de mis proyectos.
Muchas soluciones implementan sql OFFSET
y LIMIT
través de la división del conjunto de consultas. Como apunta stefano, con conjuntos de datos más grandes esto se vuelve muy ineficiente. La forma correcta de manejar esto es usar cursores del lado del servidor para realizar un seguimiento de la COMPENSACIÓN.
El soporte del cursor del lado del servidor nativo está en las obras para django . Hasta que esté listo, aquí hay una implementación simple si está usando postgres con el back-end de psycopg2:
def server_cursor_query(Table):
table_name = Table._meta.db_table
# There must be an existing connection before creating a server-side cursor
if connection.connection is None:
dummy_cursor = connection.cursor() # not a server-side cursor
# Optionally keep track of the columns so that we can return a QuerySet. However,
# if your table has foreign keys, you may need to rename them appropriately
columns = [x.name for x in Table._meta.local_fields]
cursor = connection.connection.cursor(name=''gigantic_cursor'')) # a server-side
# cursor
with transaction.atomic():
cursor.execute(''SELECT {} FROM {} WHERE id={}''.format(
'', ''.join(columns), table_name, id))
while True:
rows = cursor.fetchmany(1000)
if not rows:
break
for row in rows:
fields = dict(zip(columns, row))
yield Table(**fields)
Vea esta publicación del blog para una gran explicación de los problemas de memoria de las consultas grandes en django.
No puedes simplemente usar Model.objects.all (). Iterator () porque capturará todos los elementos en tu tabla a la vez. Tampoco puede ir simplemente con la manera Model.objects.all () [offset: offset + pagesize], porque capturará sus resultados. Cualquiera de esos excederá tu límite de memoria.
He intentado mezclar ambas soluciones, y funcionó:
offset = 0
pagesize = 1000
count = Model.objects.all().count()
while offset < count:
for m in Model.objects.all()[offset : offset + pagesize].iterator:
do_something with m
offset += pagesize
Cambie el tamaño de la página para que se ajuste a sus requisitos, y opcionalmente cambie el idioma [offset: offset + pagesize] al [offset * pagesize: (offset + 1) * pagesize] si le corresponde mejor. También, por supuesto, reemplaza Modelo por el nombre de tu modelo real.