Pregunta

Necesito hacer transacciones (comenzar, confirmar o revertir), bloqueos (seleccionar para actualizar). ¿Cómo puedo hacerlo en un modelo de documento db?

Editar:

El caso es este:

  • Quiero ejecutar un sitio de subastas.
  • Y pienso cómo dirigir la compra también.
  • En una compra directa, tengo que disminuir el campo de cantidad en el registro del artículo, pero solo si la cantidad es mayor que cero. Por eso necesito bloqueos y transacciones.
  • No sé cómo abordar eso sin bloqueos y / o transacciones.

¿Puedo resolver esto con CouchDB?

¿Fue útil?

Solución

No. CouchDB utiliza una "concurrencia optimista" modelo. 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 ha enviado.

Es engañosamente simple, de verdad. Puede replantear muchos escenarios basados ??en transacciones normales para CouchDB. Sin embargo, es necesario deshacerse de su conocimiento de dominio RDBMS al aprender CouchDB. Es útil abordar los problemas desde un nivel superior, en lugar de intentar moldear 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 la "cantidad disponible", puede manejar problemas de concurrencia como este:

  1. Recupere el documento, tome nota de la propiedad _rev que CouchDB envía junto
  2. Disminuya el campo de cantidad, si es mayor que cero
  3. Enviar el documento actualizado de vuelta, utilizando la propiedad _rev
  4. Si el _rev coincide con el número almacenado actualmente, ¡listo!
  5. 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 para pensar. Si la versión de documento más reciente tiene una cantidad de 0, la 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 del documento más reciente tiene una cantidad mayor que 0, simplemente repita la operación con los datos actualizados y comience de nuevo desde el principio. Esto lo obliga a hacer un poco más de trabajo que un RDBMS, y podría ser un poco molesto si hay actualizaciones frecuentes y conflictivas.

Ahora, la respuesta que acabo de dar presupone que vas a hacer cosas en CouchDB de la misma manera que lo harías en un RDBMS. Podría abordar este problema un poco diferente:

Comenzaría con un " producto maestro " documento que incluye todos los datos del descriptor (nombre, imagen, descripción, precio, etc.). Luego agregaría un "ticket de inventario" documento para cada instancia específica, con campos para product_key y Claim_by . Si está vendiendo un modelo de martillo y tiene 20 para vender, puede tener documentos con claves como hammer-1 , hammer-2 , etc., para representar cada martillo disponible.

Entonces, 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 del alcance, pero deberían darle una idea de cómo serí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 iterar enviando actualizaciones (usando el id y _rev ) hasta que reclame uno con éxito (boletos previamente reclamados dará como resultado un error de actualización).

Reduce

function (keys, values, combine) {
    return values.length;
}

Esta función de reducción simplemente devuelve el número total de elementos Inventory_ticket no reclamados, para que pueda saber cuántos martillos " están disponibles para su compra.

Caveats

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 conflictivas y reduce la necesidad de responder a un conflicto con una nueva actualización. Bajo este modelo, no tendrá múltiples usuarios intentando cambiar los datos en la entrada principal del producto. En el peor de los casos, tendrás varios usuarios que intentan reclamar un solo boleto, y si has tomado varios de esos de tu vi

Otros consejos

Ampliando la respuesta de MrKurt. Para muchos escenarios, no es necesario canjear los tickets de stock en orden. En lugar de seleccionar el primer boleto, puede seleccionar al azar de los boletos restantes. Dado un gran número de tickets y una gran cantidad de solicitudes simultáneas, obtendrá una contención muy reducida en esos tickets, en comparación con todos los que intentan obtener el primer ticket.

Un patrón de diseño para transacciones relajantes 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 de ambas cuentas involucradas:

  • Crear un documento de transacción " transferir USD 10 desde la cuenta 11223 a la cuenta 88733 " ;. Esto crea la tensión en el sistema.
  • Para resolver cualquier escaneo de tensión para todos los documentos de transacción y
    • Si la cuenta de origen aún no está actualizada, actualice la cuenta de origen (-10 USD)
    • Si la cuenta de origen se actualizó pero el documento de la transacción no muestra esto, actualice el documento de la transacción (por ejemplo, establezca el indicador " sourcedone " en el documento)
    • Si la cuenta de destino no se actualiza todavía, actualice la cuenta de destino (+10 USD)
    • Si la cuenta de destino se actualizó pero el documento de transacción no muestra esto, actualice el documento de transacción
    • Si ambas cuentas se han actualizado, puede eliminar el documento de la transacción o guardarlo para la auditoría.

El escaneo en busca de tensión debe realizarse en un proceso de fondo para todos los "documentos de tensión". para mantener cortos los tiempos de tensión en el sistema. En el ejemplo anterior, habrá una breve inconsistencia anticipada 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á con la consistencia eventual si su Couchdb se distribuye.

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 donde está involucrada esta cuenta. En Couchdb puede modelar esto muy bien como un mapa / vista reducida.

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 en favor de la escalabilidad. Para tener operaciones atómicas, necesita un sistema de coordinación central, que limite 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 podría usar el sistema de detección de conflictos para crear una especie de atomicidad utilizando los métodos descritos anteriormente, pero si luego escala a un clúster o use un servicio alojado como Cloudant, se descompondrá y tendrá que rehacer esa parte del sistema.

Entonces, mi sugerencia sería usar algo distinto de CouchDB para los saldos de sus cuentas, será mucho más fácil de esa manera.

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 el ajuste a 0 es más o menos imposible. El problema es la condición de carrera cuando lees el resultado de una vista, decides que estás bien para usar un "martillo-1" elemento, y luego escriba 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 martillo-1's. Si 100 usuarios consultan la vista al mismo tiempo y ven 1 hammer-1, todos pueden escribir un documento para usar un hammer 1, lo que da como resultado -99 hammer-1. En la práctica, la condición de carrera será bastante pequeña, muy pequeña si su DB está ejecutando localhost. Pero una vez que escale y tenga un servidor o clúster de base de datos fuera del sitio, el problema será mucho más notable. De todos modos, es inaceptable tener una condición de raza de ese tipo en un sistema crítico relacionado con el dinero.

Una actualización de la respuesta de MrKurt (puede que esté fechada o que no haya tenido conocimiento de algunas características de CouchDB)

Una vista es una buena manera de manejar cosas como saldos / inventarios en CouchDB.

No necesita emitir el docid y rev en una vista. Obtiene ambos de forma gratuita cuando recupera los resultados de la vista. Emitirlos, especialmente en un formato detallado como un diccionario, solo hará que su vista sea innecesariamente grande.

Una vista simple para rastrear saldos de inventario debería verse más así (también fuera de mi cabeza)

function( doc )
{
    if( doc.InventoryChange != undefined ) {
        for( product_key in doc.InventoryChange ) {
            emit( product_key, 1 );
        }
    }
}

Y la función de reducción es aún más simple

_sum

Esto utiliza una función de reducción integrada que solo suma los valores de todas las filas con teclas 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.

{
    "_id": "abc123",
    "InventoryChange": {
         "hammer_1234": 10,
         "saw_4321": 25
     }
}

Agregaría 10 hammer_1234 y 25 saw_4321.

{
    "_id": "def456",
    "InventoryChange": {
        "hammer_1234": -5
    }
}

Quemaría 5 martillos del inventario.

Con este modelo, nunca actualiza datos, solo agrega. Esto significa que no hay oportunidad para conflictos de actualización. Todos los problemas transaccionales de la actualización de datos desaparecen :)

Otra cosa buena de este modelo es que CUALQUIER documento de la base de datos puede sumar y restar elementos del inventario. Estos documentos pueden tener todo tipo de otros datos en ellos. Es posible que tenga un "Envío" documento con un montón de datos sobre la fecha y hora de recepción, almacén, empleado receptor, etc. y siempre que ese documento defina un Cambio de inventario, actualizará el inventario. Como podría una "Venta" doc, y un "DamagedItem" doc, etc. Mirando cada documento, leen muy claramente. Y la vista maneja todo el trabajo duro.

En realidad, puedes de alguna manera. Eche un vistazo a la API de documentos HTTP y desplácese hacia abajo hasta el encabezado & Modificar documentos múltiples con una solicitud única " ;.

Básicamente, puede crear / actualizar / eliminar un grupo de documentos en una sola solicitud posterior a URI / {dbname} / _ bulk_docs y todos tendrán éxito o todos 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.

Simplemente use el tipo de solución ligera SQlite para las transacciones, y cuando la transacción se complete, repítala con éxito y márquela replicada en SQLite

Tabla SQLite

txn_id    , txn_attribute1, txn_attribute2,......,txn_status
dhwdhwu$sg1   x                    y               added/replicated

También puede eliminar las transacciones que se replicaron correctamente.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top