python - template - Control de concurrencia en el modelo Django.
inclusion tags django (3)
¿Cómo manejo la concurrencia en un modelo de Django? No quiero que los cambios en el registro sean sobrescritos por otro usuario que lea el mismo registro.
Estoy de acuerdo con la explicación introductoria de Joe Holloway.
Quiero contribuir con un fragmento de trabajo relativo a la última parte de su respuesta ("En términos de Django, se puede implementar un control de concurrencia optimista al anular el método de guardado en su clase de modelo ...")
Puedes usar la siguiente clase como ancestro para tu propio modelo
Si está dentro de una transacción de base de datos (por ejemplo, mediante transaction.atomic en un ámbito externo), las siguientes declaraciones de Python son seguras y consistentes
En la práctica a través de un solo disparo, las declaraciones filter + update proporcionan una especie de test_and_set en el registro: verifican la versión y adquieren un bloqueo implícito de nivel de db en la fila. Por lo tanto, el siguiente "guardar" puede actualizar los campos del registro, por lo que es la única sesión que opera en esa instancia del modelo. La confirmación final (por ejemplo, ejecutada automáticamente por __exit__ en transaction.atomic) libera el bloqueo implícito de nivel de db en la fila
class ConcurrentModel(models.Model):
_change = models.IntegerField(default=0)
class Meta:
abstract = True
def save(self, *args, **kwargs):
cls = self.__class__
if self.pk:
rows = cls.objects.filter(
pk=self.pk, _change=self._change).update(
_change=self._change + 1)
if not rows:
raise ConcurrentModificationError(cls.__name__, self.pk)
self._change += 1
super(ConcurrentModel, self).save(*args, **kwargs)
La respuesta corta, esto realmente no es una pregunta de Django como se presenta.
El control de concurrencia a menudo se presenta como una cuestión técnica, pero en muchos aspectos es una cuestión de requisitos funcionales. ¿Cómo quieres / necesitas tu aplicación para trabajar? Hasta que sepamos eso, será difícil dar algún consejo específico de Django.
Pero, tengo ganas de divagar, así que aquí va ...
Hay dos preguntas que tiendo a hacerme cuando me enfrento a la necesidad del control de concurrencia:
- ¿Qué tan probable es que dos usuarios necesiten modificar simultáneamente el mismo registro?
- ¿Cuál es el impacto para el usuario si se pierden sus modificaciones a un registro?
Si la probabilidad de colisiones es relativamente alta, o el impacto de perder una modificación es grave, entonces es posible que esté observando alguna forma de bloqueo pesimista. En un esquema pesimista, cada usuario debe adquirir un bloqueo lógico antes de abrir el registro para su modificación.
El bloqueo pesimista viene con mucha complejidad. Debe sincronizar el acceso a los bloqueos, considerar la tolerancia a fallos, la caducidad del bloqueo, los superusuarios pueden anular los bloqueos, los usuarios pueden ver quién tiene el bloqueo, etc.
En Django, esto podría implementarse con un modelo de bloqueo separado o algún tipo de clave externa de "usuario de bloqueo" en el registro bloqueado. El uso de una tabla de bloqueo le da un poco más de flexibilidad en cuanto al almacenamiento cuando se adquirió el bloqueo, el usuario, las notas, etc. Si necesita una tabla de bloqueo genérica que se pueda usar para bloquear cualquier tipo de registro, entonces eche un vistazo a django.contrib.contenttypes framework , pero rápidamente esto puede convertirse en síndrome de astronauta de abstracción.
Si las colisiones son improbables o las modificaciones perdidas se recrean de manera trivial, entonces puede salirse funcionalmente con técnicas de concurrencia optimista. Esta técnica es simple y más fácil de implementar. Básicamente, solo debe hacer un seguimiento de un número de versión o marca de tiempo de modificación y rechazar cualquier modificación que detecte como fuera de lugar.
Desde el punto de vista de diseño funcional, solo debe considerar cómo se presentan estos errores de modificación simultáneos a sus usuarios.
En términos de Django, se puede implementar un control de concurrencia optimista anulando el método de guardado en su clase de modelo ...
def save(self, *args, **kwargs):
if self.version != self.read_current_version():
raise ConcurrentModificationError(''Ooops!!!!'')
super(MyModel, self).save(*args, **kwargs)
Y, por supuesto, para que cualquiera de estos mecanismos de concurrencia sea robusto, debe considerar el control transaccional . Ninguno de estos modelos es completamente funcional si no puede garantizar las propiedades ACID de sus transacciones.
No creo que ''mantener un número de versión o marca de tiempo'' funcione.
Cuando self.version == self.read_current_version()
es True
, todavía existe la posibilidad de que el número de versión haya sido modificado por otras sesiones justo antes de llamar a super().save()
.