No. CouchDB utiliza un modelo de "concurrencia optimista". En los términos más simples, esto solo significa que envía una versión del documento junto con su actualización, y CouchDB rechaza el cambio si la versión actual del documento no coincide con lo que envió.
Es engañosamente simple, de verdad. Puede replantear muchos escenarios normales basados en transacciones para CouchDB. Sin embargo, es necesario descartar su conocimiento del dominio RDBMS al aprender CouchDB. Es útil abordar los problemas desde un nivel superior, en lugar de intentar adaptar Couch a un mundo basado en SQL.
Seguimiento del inventario
El problema que describió es principalmente un problema de inventario. Si tiene un documento que describe un artículo e incluye un campo para "cantidad disponible", puede manejar problemas de simultaneidad como este:
- Recupere el documento, tome nota de la
_rev
propiedad que envía CouchDB
- Disminuir el campo de cantidad, si es mayor que cero
- Devuelva el documento actualizado, utilizando la
_rev
propiedad
- Si
_rev
coincide con el número almacenado actualmente, ¡listo!
- Si hay un conflicto (cuando
_rev
no coincide), recupere la versión más reciente del documento
En este caso, hay dos posibles escenarios de falla en los que pensar. Si la versión más reciente del documento tiene una cantidad de 0, lo maneja como lo haría en un RDBMS y alerta al usuario de que en realidad no puede comprar lo que quería comprar. Si la versión más reciente del documento tiene una cantidad mayor que 0, simplemente repita la operación con los datos actualizados y comience desde el principio. Esto le obliga a hacer un poco más de trabajo que lo que haría un RDBMS, y podría resultar un poco molesto si hay actualizaciones frecuentes y conflictivas.
Ahora, la respuesta que acabo de dar presupone que va a hacer las cosas en CouchDB de la misma manera que lo haría en un RDBMS. Podría abordar este problema de manera un poco diferente:
Comenzaría con un documento de "producto maestro" que incluye todos los datos del descriptor (nombre, imagen, descripción, precio, etc.). Luego agregaría un documento de "ticket de inventario" para cada instancia específica, con campos para product_key
y claimed_by
. Si usted está vendiendo un modelo de martillo, y tienen 20 de ellos para vender, es posible que tenga documentos con teclas como hammer-1
, hammer-2
, etc., para representar cada martillo disponibles.
Luego, crearía una vista que me da una lista de martillos disponibles, con una función de reducción que me permite ver un "total". Estos están completamente fuera de lugar, pero deberían darle una idea de cómo se vería una vista de trabajo.
Mapa
function(doc)
{
if (doc.type == 'inventory_ticket' && doc.claimed_by == null ) {
emit(doc.product_key, { 'inventory_ticket' :doc.id, '_rev' : doc._rev });
}
}
Esto me da una lista de "tickets" disponibles, por clave de producto. Podría tomar un grupo de estos cuando alguien quiera comprar un martillo, luego repetir el envío de actualizaciones (usando id
y _rev
) hasta que reclame con éxito uno (los boletos reclamados anteriormente darán como resultado un error de actualización).
Reducir
function (keys, values, combine) {
return values.length;
}
Esta función de reducción simplemente devuelve el número total de inventory_ticket
artículos no reclamados , para que pueda saber cuántos "martillos" están disponibles para su compra.
Advertencias
Esta solución representa aproximadamente 3,5 minutos de pensamiento total para el problema particular que ha presentado. ¡Puede haber mejores formas de hacer esto! Dicho esto, reduce sustancialmente las actualizaciones en conflicto y reduce la necesidad de responder a un conflicto con una nueva actualización. Bajo este modelo, no tendrá varios usuarios intentando cambiar datos en la entrada del producto principal. En el peor de los casos, tendrá varios usuarios intentando reclamar un solo boleto, y si ha tomado varios de ellos desde su vista, simplemente pase al siguiente boleto e intente nuevamente.
Referencia: https://wiki.apache.org/couchdb/Frequency_asked_questions#How_do_I_use_transactions_with_CouchDB.3F
Ampliando la respuesta de MrKurt. Para muchos escenarios, no es necesario que los boletos de acciones se canjeen en orden. En lugar de seleccionar el primer boleto, puede seleccionar al azar entre los boletos restantes. Dada una gran cantidad de tickets y una gran cantidad de solicitudes simultáneas, obtendrá una contención mucho menor en esos tickets, en comparación con todos los que intentan obtener el primer ticket.
fuente
Un patrón de diseño para transacciones tranquilas es crear una "tensión" en el sistema. Para el caso de uso de ejemplo popular de una transacción de cuenta bancaria, debe asegurarse de actualizar el total para ambas cuentas involucradas:
El escaneo de tensión debe realizarse en un proceso de backend para todos los "documentos de tensión" para mantener cortos los tiempos de tensión en el sistema. En el ejemplo anterior, se anticipará una incoherencia breve cuando la primera cuenta se haya actualizado pero la segunda aún no se haya actualizado. Esto debe tenerse en cuenta de la misma manera que tratará la coherencia eventual si se distribuye su Couchdb.
Otra posible implementación evita la necesidad de transacciones por completo: simplemente almacene los documentos de tensión y evalúe el estado de su sistema evaluando cada documento de tensión involucrado. En el ejemplo anterior, esto significaría que el total de una cuenta solo se determina como los valores de suma en los documentos de transacción en los que esta cuenta está involucrada. En Couchdb puede modelar esto muy bien como una vista de mapa / reducción.
fuente
No, CouchDB generalmente no es adecuado para aplicaciones transaccionales porque no admite operaciones atómicas en un entorno agrupado / replicado.
CouchDB sacrificó la capacidad transaccional a favor de la escalabilidad. Para tener operaciones atómicas, necesita un sistema de coordinación central, lo que limita su escalabilidad.
Si puede garantizar que solo tiene una instancia de CouchDB o que todos los que modifican un documento en particular se conectan a la misma instancia de CouchDB, entonces puede usar el sistema de detección de conflictos para crear una especie de atomicidad usando los métodos descritos anteriormente, pero si luego escala a un clúster. o utilice un servicio alojado como Cloudant, se estropeará y tendrá que rehacer esa parte del sistema.
Entonces, mi sugerencia sería usar algo diferente a CouchDB para los saldos de su cuenta, será mucho más fácil de esa manera.
fuente
Como respuesta al problema del OP, Couch probablemente no sea la mejor opción aquí. El uso de vistas es una excelente manera de realizar un seguimiento del inventario, pero limitarlo a 0 es más o menos imposible. El problema es la condición de carrera cuando lee el resultado de una vista, decide que está bien usar un elemento "hammer-1" y luego escribe un documento para usarlo. El problema es que no hay una forma atómica de escribir solo el documento para usar el martillo si el resultado de la vista es que hay> 0 martillos-1. Si 100 usuarios consultan la vista al mismo tiempo y ven 1 martillo-1, todos pueden escribir un documento para usar un martillo 1, lo que da como resultado -99 martillo-1. En la práctica, la condición de carrera será bastante pequeña, realmente pequeña si su base de datos está ejecutando localhost. Pero una vez que escala y tiene un servidor de base de datos o un clúster externo, el problema se hará mucho más notorio.
Una actualización de la respuesta de MrKurt (puede que solo esté fechada o que no conozca algunas de las características de CouchDB)
Una vista es una buena manera de manejar cosas como saldos / inventarios en CouchDB.
No es necesario emitir el docid y rev en una vista. Obtienes ambos de forma gratuita cuando recuperas los resultados de la vista. Emitirlos, especialmente en un formato detallado como un diccionario, hará que su vista aumente innecesariamente.
Una vista simple para rastrear los saldos de inventario debería verse más como esta (también fuera de mi cabeza)
Y la función de reducción es aún más simple
Esto usa una función de reducción incorporada que solo suma los valores de todas las filas con claves coincidentes.
En esta vista, cualquier documento puede tener un miembro "InventoryChange" que asigna product_key's a un cambio en el inventario total de ellos. es decir.
Agregaría 10 hammer_1234's y 25 saw_4321's.
Quemaría 5 martillos del inventario.
Con este modelo, nunca actualiza ningún dato, solo agrega. Esto significa que no hay posibilidad de conflictos de actualización. Todos los problemas transaccionales de actualizar datos desaparecen :)
Otra cosa buena de este modelo es que CUALQUIER documento en la base de datos puede sumar y restar artículos del inventario. Estos documentos pueden contener todo tipo de datos. Es posible que tenga un documento de "Envío" con un montón de datos sobre la fecha y la hora de recepción, el almacén, el empleado de recepción, etc. y siempre que ese documento defina un Cambio de inventario, actualizará el inventario. Al igual que un documento de "Venta" y un documento de "DamagedItem", etc. Al mirar cada documento, se leen muy claramente. Y la vista maneja todo el trabajo duro.
fuente
De hecho, puedes de alguna manera. Eche un vistazo a la API de documentos HTTP y desplácese hacia abajo hasta el título "Modificar varios documentos con una sola solicitud".
Básicamente, puede crear / actualizar / eliminar un montón de documentos en una sola solicitud de publicación a URI / {dbname} / _ bulk_docs y todos tendrán éxito o fallarán. Sin embargo, el documento advierte que este comportamiento puede cambiar en el futuro.
EDITAR: Como se predijo, a partir de la versión 0.9, los documentos masivos ya no funcionan de esta manera.
fuente
Simplemente use el tipo de solución liviana SQlite para transacciones, y cuando la transacción se complete con éxito, repítela y márquela como replicada en SQLite
Tabla SQLite
También puede eliminar las transacciones que se replican correctamente.
fuente