variable template tag framework cache borrar python django caching

python - template - install memcached django



¿Cómo obligo a Django a ignorar cualquier caché y volver a cargar datos? (6)

Estoy usando los modelos de la base de datos Django de un proceso que no se llama desde una solicitud HTTP. Se supone que el proceso debe sondear los datos nuevos cada pocos segundos y procesarlos. Tengo un bucle que duerme durante unos segundos y luego obtiene todos los datos no controlados de la base de datos.

Lo que veo es que después de la primera recuperación, el proceso nunca ve datos nuevos. Ejecuté algunas pruebas y parece que Django está almacenando los resultados del almacenamiento en caché, a pesar de que estoy construyendo nuevos QuerySets cada vez. Para verificar esto, hice esto desde un shell de Python:

>>> MyModel.objects.count() 885 # (Here I added some more data from another process.) >>> MyModel.objects.count() 885 >>> MyModel.objects.update() 0 >>> MyModel.objects.count() 1025

Como puede ver, agregar nuevos datos no cambia el recuento de resultados. Sin embargo, llamar al método update () del administrador parece solucionar el problema.

No puedo encontrar ninguna documentación sobre ese método de actualización () y no tengo idea de qué otras cosas malas podría hacer.

Mi pregunta es, ¿por qué estoy viendo este comportamiento de almacenamiento en caché, que contradice lo que dicen los Django ? ¿Y cómo evito que suceda?


Habiendo tenido este problema y encontrado dos soluciones definitivas, pensé que valía la pena publicar otra respuesta.

Este es un problema con el modo de transacción predeterminado de MySQL. Django abre una transacción al inicio, lo que significa que de manera predeterminada no verá los cambios realizados en la base de datos.

Demuestra así

Ejecuta un shell django en la terminal 1

>>> MyModel.objects.get(id=1).my_field u''old''

Y otro en la terminal 2

>>> MyModel.objects.get(id=1).my_field u''old'' >>> a = MyModel.objects.get(id=1) >>> a.my_field = "NEW" >>> a.save() >>> MyModel.objects.get(id=1).my_field u''NEW'' >>>

Regrese a la terminal 1 para demostrar el problema: todavía leemos el valor anterior de la base de datos.

>>> MyModel.objects.get(id=1).my_field u''old''

Ahora en la terminal 1 demostrar la solución

>>> from django.db import transaction >>> >>> @transaction.commit_manually ... def flush_transaction(): ... transaction.commit() ... >>> MyModel.objects.get(id=1).my_field u''old'' >>> flush_transaction() >>> MyModel.objects.get(id=1).my_field u''NEW'' >>>

Los nuevos datos ahora se leen

Aquí está ese código en un bloque fácil de pegar con docstring

from django.db import transaction @transaction.commit_manually def flush_transaction(): """ Flush the current transaction so we don''t read stale data Use in long running processes to make sure fresh data is read from the database. This is a problem with MySQL and the default transaction mode. You can fix it by setting "transaction-isolation = READ-COMMITTED" in my.cnf or by calling this function at the appropriate moment """ transaction.commit()

La solución alternativa es cambiar my.cnf para MySQL para cambiar el modo de transacción predeterminado

transaction-isolation = READ-COMMITTED

Tenga en cuenta que esa es una característica relativamente nueva para Mysql y tiene algunas consecuencias para el registro / esclavización binario . También podría poner esto en el preámbulo de conexión django si lo desea.

Actualización 3 años después

Ahora que Django 1.6 ha activado el compromiso automático en MySQL, esto ya no es un problema. El ejemplo anterior ahora funciona bien sin el código flush_transaction() ya sea que MySQL esté en REPEATABLE-READ (el modo de aislamiento de transacción REPEATABLE-READ (predeterminado) o READ-COMMITTED .

Lo que sucedía en las versiones anteriores de Django que se ejecutaba en modo no autocommit era que la primera instrucción de select abría una transacción. Como el modo predeterminado de MySQL es REPEATABLE-READ esto significa que no se leerán las actualizaciones de la base de datos mediante declaraciones select posteriores, de ahí la necesidad del código flush_transaction() que detiene la transacción e inicia una nueva.

Sin embargo, todavía hay razones por las que es posible que desee utilizar el aislamiento de transacción READ-COMMITTED . Si pusiera el terminal 1 en una transacción y quisiera ver las escrituras desde el terminal 2, necesitaría READ-COMMITTED .

El código flush_transaction() ahora produce una advertencia de desaprobación en Django 1.6, por lo que le recomiendo que lo elimine.


Hemos luchado un poco forzando a django a actualizar el "caché", que resultó ser en realidad una caché, pero un artefacto debido a las transacciones. Es posible que esto no se aplique a su ejemplo, pero ciertamente en las vistas de django, de manera predeterminada, hay una llamada implícita a una transacción, que mysql luego aísla de cualquier cambio que ocurra de otros procesos posteriores al inicio.

utilizamos el decorador @transaction.commit_manually y llamamos a transaction.commit() justo antes de cada ocasión en que necesita información actualizada.

Como digo, esto definitivamente se aplica a las vistas, no estoy seguro de si se aplicaría al código django que no se ejecuta dentro de una vista.

información detallada aquí:

http://devblog.resolversystems.com/?p=439


No estoy seguro de que lo recomiende ... pero puedes matar el caché tú mismo:

>>> qs = MyModel.objects.all() >>> qs.count() 1 >>> MyModel().save() >>> qs.count() # cached! 1 >>> qs._result_cache = None >>> qs.count() 2

Y aquí hay una técnica mejor que no depende de manipular las entrañas del QuerySet: recuerde que el almacenamiento en caché está ocurriendo dentro de un QuerySet , pero refrescar los datos simplemente requiere que la Query subyacente se vuelva a ejecutar. QuerySet es realmente solo una API de alto nivel que envuelve un objeto Query, más un contenedor (¡con caché!) Para los resultados de Query. Por lo tanto, dado un conjunto de consultas, aquí hay una forma general de forzar una actualización:

>>> MyModel().save() >>> qs = MyModel.objects.all() >>> qs.count() 1 >>> MyModel().save() >>> qs.count() # cached! 1 >>> from django.db.models import QuerySet >>> qs = QuerySet(model=MyModel, query=qs.query) >>> qs.count() # refreshed! 2 >>> party_time()

¡Muy fácil! Por supuesto, puede implementar esto como una función auxiliar y usar según sea necesario.


Parece que el count() va a la memoria caché después de la primera vez. Esta es la fuente de django para QuerySet.count:

def count(self): """ Performs a SELECT COUNT() and returns the number of records as an integer. If the QuerySet is already fully cached this simply returns the length of the cached results set to avoid multiple SELECT COUNT(*) calls. """ if self._result_cache is not None and not self._iter: return len(self._result_cache) return self.query.get_count(using=self.db)

update parece estar haciendo bastante trabajo adicional, además de lo que necesitas.
Pero no puedo pensar en otra forma mejor de hacerlo, salvo escribir tu propio SQL para el recuento.
Si el rendimiento no es muy importante, simplemente haría lo que está haciendo, llamando a la update antes de count .

QuerySet.update:

def update(self, **kwargs): """ Updates all elements in the current QuerySet, setting all the given fields to the appropriate values. """ assert self.query.can_filter(), / "Cannot update a query once a slice has been taken." self._for_write = True query = self.query.clone(sql.UpdateQuery) query.add_update_values(kwargs) if not transaction.is_managed(using=self.db): transaction.enter_transaction_management(using=self.db) forced_managed = True else: forced_managed = False try: rows = query.get_compiler(self.db).execute_sql(None) if forced_managed: transaction.commit(using=self.db) else: transaction.commit_unless_managed(using=self.db) finally: if forced_managed: transaction.leave_transaction_management(using=self.db) self._result_cache = None return rows update.alters_data = True


Si agrega .all .all() a un queryset, forzará una relectura desde el DB. Pruebe MyModel.objects.all().count() lugar de MyModel.objects.count() .


También puede usar MyModel.objects._clone().count(). Todos los métodos del QuerySet llaman a _clone() antes de realizar cualquier trabajo, lo que garantiza que cualquier memoria caché interna sea invalidada.

La causa raíz es que MyModel.objects es la misma instancia cada vez. Al clonarlo, está creando una nueva instancia sin el valor en caché. Por supuesto, siempre puede acercarse e invalidar la memoria caché si prefiere usar la misma instancia.