2009-06-23 17 views
7

Estoy haciendo una aplicación web de Django que permite a un usuario crear un conjunto de cambios en una serie de GET/POST antes de enviarlos a la base de datos (o revertirlos) con un POST final . Tengo que mantener las actualizaciones aisladas de cualquier usuario de base de datos concurrente hasta que se confirmen (esto es un front-end de configuración), descartando el compromiso después de cada POST.Transacciones por sesión en Django

Mi solución preferida es usar una transacción por sesión. Esto mantiene todos los problemas para recordar lo que ha cambiado (y cómo afecta las consultas posteriores), junto con la implementación de confirmación/restitución, en la base de datos a la que pertenece. El bloqueo y bloqueos de larga duración no son un problema, ya que debido a restricciones externas solo puede haber un usuario que configure el sistema en un momento dado, y se comporta bien.

Sin embargo, no puedo encontrar documentación sobre cómo configurar el ORM de Django para usar este tipo de modelo de transacción. He reunido un mínimo parche de mono (¡ew!) Para resolver el problema, pero no me gusta esa solución tan frágil. ¿Alguien más ha hecho esto antes? ¿Me he perdido algo de documentación en alguna parte?

(Mi versión de Django es 1.0.2 Final, y estoy usando una base de datos Oracle.)

Respuesta

9

, transacciones, sesión de escala concurrentes múltiples generalmente conducen a callejones sin salida o peor (peor == livelock, a largo retrasos mientras los bloqueos se mantienen en otra sesión.)

Este diseño no es la mejor política, por lo que Django lo desalienta.

La mejor solución es la siguiente.

  1. Diseño una clase Memento que registra el cambio del usuario. Esto podría ser una copia guardada de su entrada de formulario. Es posible que necesite registrar información adicional si los cambios de estado son complejos. De lo contrario, una copia de la entrada del formulario puede ser suficiente.

  2. Acumula la secuencia de Memento objetos en su sesión. Tenga en cuenta que cada paso de la transacción implicará recuperaciones de los datos y la validación para ver si la cadena de recuerdos aún "funcionará". A veces no funcionarán porque alguien más cambió algo en esta cadena de recuerdos. ¿Ahora que?

  3. Cuando presenta el mensaje '¿listo para comprometerse?' página, ha reproducido la secuencia de Mementos y está bastante seguro de que funcionarán. Cuando envíe "Commit", debe volver a reproducir Mementos una última vez, con la esperanza de que todavía vayan a funcionar. Si lo hacen, genial. Si no lo hacen, alguien cambió algo, y vuelves al paso 2: ¿y ahora qué?

Esto parece complejo.

Sí, lo hace. Sin embargo, no tiene bloqueos, lo que permite una velocidad impresionante y pocas oportunidades de estancamiento. La transacción se limita a la función de vista "Confirmar" que realmente aplica la secuencia de Mementos a la base de datos, guarda los resultados y realiza un compromiso final para finalizar la transacción.

La alternativa: mantener bloqueada mientras el usuario sale para tomar una taza de café en el paso n-1 de n - no es viable.

Para obtener más información sobre Memento, vea this.

+0

Deadlock y los usuarios que salen para tomar café no son un problema (habrá un controlador y, por diseño, toda la actualización se realiza bajo un solo bloqueo). Corrígeme si me equivoco, pero los Mementos en realidad no funcionarán con el ORM, ¿o sí? –

+0

Memento - como patrón de diseño - funciona con todo. La transacción de múltiples pasos de larga duración con bloqueos que se acumulan lentamente conducirá a un punto muerto. La única forma de evitar el punto muerto es tener un solo usuario. –

+0

El ORM de Django proporciona una asignación automática de tablas a formularios y viceversa. ¿Puedo conectar esto al patrón Memento, o tendré que dejar de usar esta característica? –

1

En caso de que alguien más tenga exactamente el mismo problema que yo (espero que no), aquí está mi monopatch. Es frágil y feo, y cambia los métodos privados, pero afortunadamente es pequeño. Por favor, no lo uses a menos que realmente tengas que hacerlo. Como lo mencionaron otros, cualquier aplicación que lo use evita de manera efectiva que varios usuarios realicen actualizaciones al mismo tiempo, bajo pena de interbloqueo. (En mi aplicación, puede haber muchos lectores, pero múltiples actualizaciones concurrentes se excluyen deliberadamente.)

Tengo un objeto "usuario" que persiste en una sesión de usuario y contiene un objeto de conexión persistente. Cuando valido una interacción HTTP particular es parte de una sesión, también almaceno el objeto usuario en django.db.connection, que es thread-local.

def monkeyPatchDjangoDBConnection(): 
    import django.db 
    def validConnection(): 
     if django.db.connection.connection is None: 
      django.db.connection.connection = django.db.connection.user.connection 
     return True 
    def close(): 
     django.db.connection.connection = None 
    django.db.connection._valid_connection = validConnection 
    django.db.connection.close = close 
monkeyPatchDBConnection() 

def setUserOnThisThread(user): 
    import django.db 
    django.db.connection.user = user 

Esta última se llama automáticamente al inicio de cualquier método anotado con @login_required, por lo que el 99% de mi código está aislado de las características específicas de este corte.

2

Se me ocurrió algo similar al patrón Memento, pero lo suficientemente diferente como para que tenga una publicación. Cuando un usuario inicia una sesión de edición, duplico el objeto de destino a un objeto temporal en la base de datos. Todas las operaciones de edición posteriores afectan el duplicado. En lugar de guardar el estado del objeto en un recuerdo en cada cambio, almaceno operación objetos. Cuando aplico una operación a un objeto, devuelve la operación inversa, que almaceno.

Las operaciones de guardado son mucho más baratas para mí que los recuerdos, ya que las operaciones se pueden describir con algunos elementos de datos pequeños, mientras que el objeto que se está editando es mucho más grande. También aplico las operaciones a medida que avanzo y guardo el desbloqueo, para que el temporal en el archivo db siempre se corresponda con la versión en el navegador del usuario. Nunca tengo que volver a reproducir una colección de cambios; el temporal siempre está a solo una operación de la próxima versión.

Para implementar "deshacer", saco el último objeto de deshacer de la pila (por así decirlo, recuperando la última operación para el objeto temporal de la base de datos) aplicarlo al temporal y devolver el temporal transformado. También podría presionar la operación resultante en una pila de rehacer si me importaba implementar rehacer.

Para implementar "guardar cambios", es decir, confirmar, desactivo y sello de tiempo el objeto original y activar el temporal en su lugar.

Para implementar "cancelar", es decir, deshacer, ¡no hago nada! Podría eliminar el temporal, por supuesto, porque no hay forma de que el usuario lo recupere una vez que termine la sesión de edición, pero me gusta mantener las sesiones de edición canceladas para poder ejecutar estadísticas sobre ellas antes de borrarlas con un trabajo cron. .

Cuestiones relacionadas