python - Condiciones de carrera en django
database locking (6)
A partir de Django 1.1, puedes usar las expresiones F () de ORM para resolver este problema específico.
from django.db.models import F
user = request.user
user.points = F(''points'') + calculate_points(user)
user.save()
Para más detalles ver la documentación:
https://docs.djangoproject.com/en/1.8/ref/models/expressions/#django.db.models.F
Aquí hay un ejemplo simple de una vista django con una posible condición de carrera:
# myapp/views.py
from django.contrib.auth.models import User
from my_libs import calculate_points
def add_points(request):
user = request.user
user.points += calculate_points(user)
user.save()
La condición de carrera debe ser bastante obvia: un usuario puede realizar esta solicitud dos veces, y la aplicación podría ejecutar user = request.user
simultáneamente, haciendo que una de las solicitudes anule a la otra.
Supongamos que la función calculate_points
es relativamente complicada y hace cálculos basados en todo tipo de cosas raras que no se pueden colocar en una sola update
y sería difícil ponerlos en un procedimiento almacenado.
Así que aquí está mi pregunta: ¿Qué tipo de mecanismos de bloqueo están disponibles para django, para hacer frente a situaciones similares a esta?
Ahora, debes usar:
Model.objects.select_for_update().get(foo=bar)
Django select_for_update compatible con select_for_update , en versiones anteriores puede ejecutar consultas SQL sin procesar, por ejemplo, select ... for update
que dependiendo de la base de datos subyacente bloqueará la fila de las actualizaciones, puede hacer lo que quiera con esa fila hasta el final de la transacción. p.ej
from django.db import transaction
@transaction.commit_manually()
def add_points(request):
user = User.objects.select_for_update().get(id=request.user.id)
# you can go back at this point if something is not right
if user.points > 1000:
# too many points
return
user.points += calculate_points(user)
user.save()
transaction.commit()
El bloqueo de la base de datos es el camino a seguir aquí. Hay planes para agregar compatibilidad con "seleccionar para actualizar" a Django ( here ), pero por ahora lo más simple sería usar SQL sin formato para ACTUALIZAR el objeto del usuario antes de comenzar a calcular la puntuación.
El bloqueo pesimista ahora es compatible con el ORM de Django 1.4 cuando el DB subyacente (como Postgres) lo admite. Vea las notas de la versión de Django 1.4a1 .
Esto puede simplificar demasiado tu situación, pero ¿qué pasa con el reemplazo de un enlace de JavaScript? En otras palabras, cuando el usuario hace clic en el enlace o botón, envuelve la solicitud en una función de JavaScript que inmediatamente deshabilita / "pone gris" el enlace y reemplaza el texto con la información "Cargando ..." o "Enviar solicitud ..." o algo similar. ¿Eso funcionaría para ti?
Tienes muchas maneras de encauzar este tipo de cosas.
Un enfoque estándar es Actualizar primero . Usted hace una actualización que aprovechará un bloqueo exclusivo en la fila; entonces haz tu trabajo; y finalmente cometer el cambio. Para que esto funcione, debe omitir el almacenamiento en caché del ORM.
Otro enfoque estándar es tener un servidor de aplicaciones separado de un único subproceso que aísla las transacciones web del cálculo complejo.
Su aplicación web puede crear una cola de solicitudes de puntuación, engendrar un proceso separado y luego escribir las solicitudes de puntuación en esta cola. El spawn se puede colocar en
urls.py
de Django para que suceda en el inicio de la aplicación web. O puede colocarse en unmanage.py
administración demanage.py
separado. O puede hacerse "según sea necesario" cuando se intenta la primera solicitud de puntuación.También puede crear un servidor web WSGI separado utilizando Werkzeug que acepte solicitudes de WS a través de urllib2. Si tiene un número de puerto único para este servidor, las solicitudes están en cola por TCP / IP. Si su manejador WSGI tiene un hilo, entonces, ha logrado un enhebrado simple serializado. Esto es ligeramente más escalable, ya que el motor de puntuación es una solicitud de WS y se puede ejecutar en cualquier lugar.
Sin embargo, otro enfoque es tener algún otro recurso que deba adquirirse y mantenerse para hacer el cálculo.
Un objeto Singleton en la base de datos. Una sola fila en una tabla única se puede actualizar con una ID de sesión para tomar el control; actualizar con ID de sesión de
None
para liberar el control. La actualización esencial debe incluir un filtroWHERE SESSION_ID IS NONE
para asegurarse de que la actualización falla cuando otra persona mantiene el bloqueo. Esto es interesante porque es inherentemente libre de raza, es una actualización única, no una secuencia de SELECCIÓN DE ACTUALIZACIÓN.Un semáforo de jardín se puede usar fuera de la base de datos. Las colas (generalmente) son más fáciles de trabajar que un semáforo de bajo nivel.