web-dev-qa-db-fra.com

Puis-je faire des transactions et des verrous dans CouchDB?

Je dois effectuer des transactions (début, validation ou annulation), des verrous (sélectionner pour la mise à jour) . Comment puis-je le faire dans une base de données de modèle de document?

Modifier:

L'affaire est la suivante:

  • Je veux exécuter un site de vente aux enchères.
  • Et je pense aussi à l'achat direct.
  • Lors d'un achat direct, je dois décrémenter le champ quantité dans l'enregistrement de l'article, mais uniquement si la quantité est supérieure à zéro. C'est pourquoi j'ai besoin de verrous et de transactions.
  • Je ne sais pas comment résoudre ce problème sans serrures et/ou transactions.

Puis-je résoudre ce problème avec CouchDB?

78
user2427

Non. CouchDB utilise un modèle de "concurrence optimiste". En termes simples, cela signifie simplement que vous envoyez une version du document avec votre mise à jour et que CouchDB refuse la modification si la version actuelle du document ne correspond pas à celle que vous avez envoyée.

C'est d'une simplicité trompeuse, vraiment. Vous pouvez recadrer de nombreux scénarios basés sur les transactions normales pour CouchDB. Cependant, vous devez en quelque sorte jeter vos connaissances de domaine SGBDR lors de l’apprentissage de CouchDB. Il est utile de traiter les problèmes d'un niveau supérieur, plutôt que d'essayer de transformer Couch dans un monde basé sur SQL.

Suivre l'inventaire

Le problème que vous avez décrit est avant tout un problème d’inventaire. Si vous disposez d'un document décrivant un élément et comprenant un champ "quantité disponible", vous pouvez gérer les problèmes de simultanéité de la manière suivante:

  1. Récupérez le document, notez la propriété _rev que CouchDB envoie avec
  2. Décrémenter le champ quantité s'il est supérieur à zéro
  3. Renvoyer le document mis à jour à l'aide de la propriété _rev
  4. Si le _rev correspond au numéro actuellement stocké, faites-le!
  5. En cas de conflit (lorsque _rev ne correspond pas), récupérez la version la plus récente du document.

Dans ce cas, deux scénarios de défaillance possibles sont à prendre en compte. Si la version de document la plus récente comporte une quantité de 0, vous la gérez comme dans un SGBDR et avertissez l'utilisateur qu'il ne peut pas acheter ce qu'il souhaitait acheter. Si la version la plus récente du document a une quantité supérieure à 0, il vous suffit de répéter l'opération avec les données mises à jour et de recommencer au début. Cela vous oblige à faire un peu plus de travail qu'un SGBDR, et pourrait devenir un peu gênant s'il y a des mises à jour fréquentes et conflictuelles.

Maintenant, la réponse que je viens de donner présuppose que vous ferez les choses dans CouchDB de la même manière que vous le feriez dans un SGBDR. Je pourrais aborder ce problème un peu différemment:

Je commencerais par un document "produit principal" contenant toutes les données du descripteur (nom, image, description, prix, etc.). J'ajouterais ensuite un document "ticket d'inventaire" pour chaque instance spécifique, avec des champs pour product_key et claimed_by. Si vous vendez un modèle de marteau et que vous en avez 20 à vendre, vous pouvez disposer de documents avec des clés telles que hammer-1, hammer-2, etc., pour représenter chaque marteau disponible.

Ensuite, je créerais une vue qui me donnerait une liste des marteaux disponibles, avec une fonction de réduction qui me permettrait de voir un "total". Celles-ci sont complètement improvisées, mais devraient vous donner une idée de ce à quoi une vue de travail ressemblerait.

Carte

function(doc) 
{ 
    if (doc.type == 'inventory_ticket' && doc.claimed_by == null ) { 
        emit(doc.product_key, { 'inventory_ticket' :doc.id, '_rev' : doc._rev }); 
    } 
}

Cela me donne une liste des "tickets" disponibles, par clé de produit. Je pourrais en saisir un groupe lorsque quelqu'un voudrait acheter un marteau, puis effectuer une itération en envoyant des mises à jour (en utilisant les variables id et _rev) jusqu'à ce que j'en revendique une (les tickets précédemment revendiqués entraîneront une erreur de mise à jour).

Réduire

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

Cette fonction de réduction renvoie simplement le nombre total d'éléments inventory_ticket non réclamés, ce qui vous permet de savoir combien de "marteaux" sont disponibles à l'achat.

Mises en garde

Cette solution représente environ 3,5 minutes de réflexion totale sur le problème particulier que vous avez présenté. Il y a peut-être de meilleures façons de le faire! Cela dit, cela réduit considérablement les mises à jour conflictuelles et la nécessité de réagir à un conflit par une nouvelle mise à jour. Sous ce modèle, plusieurs utilisateurs ne tenteront pas de modifier les données dans l'entrée du produit principal. Dans le pire des cas, plusieurs utilisateurs tenteront de réclamer un seul ticket. Si vous en avez capturé plusieurs, vous passez simplement au prochain ticket et réessayez.

Référence: https://wiki.Apache.org/couchdb/Frequent_asked_questions#How_do_I_use_transactions_with_CouchDB.3F

135
MrKurt

Développer la réponse de Mr Kurt. Dans de nombreux scénarios, il n'est pas nécessaire de racheter les billets de stock dans l'ordre. Au lieu de sélectionner le premier ticket, vous pouvez sélectionner au hasard parmi les tickets restants. Étant donné le nombre élevé de tickets et le nombre de requêtes simultanées, vous obtiendrez beaucoup moins de conflits sur ces tickets par rapport à tous ceux qui essaient d'obtenir le premier ticket.

24
Kerr

Un modèle de conception pour les transactions restfull consiste à créer une "tension" dans le système. Pour l'exemple de cas courant d'une transaction sur un compte bancaire, vous devez vous assurer de mettre à jour le total pour les deux comptes impliqués:

  • Créez un document de transaction "Transfert de 10 USD du compte 11223 vers le compte 88733". Cela crée la tension dans le système.
  • Pour résoudre toute tension, numérisez tous les documents de transaction et
    • Si le compte source n'est pas encore mis à jour, mettez à jour le compte source (-10 USD)
    • Si le compte source a été mis à jour mais que le document de transaction ne l'indique pas, mettez à jour le document de transaction (par exemple, définissez l'indicateur "source" dans le document).
    • Si le compte cible n'est pas encore mis à jour, mettez à jour le compte cible (+10 USD)
    • Si le compte cible a été mis à jour mais que le document de transaction ne l'indique pas, mettez à jour le document de transaction. 
    • Si les deux comptes ont été mis à jour, vous pouvez supprimer le document de transaction ou le conserver pour vérification.

L'analyse de la tension doit être effectuée dans un processus dorsal pour tous les "documents de tension" afin de réduire les temps de tension dans le système. Dans l'exemple ci-dessus, il y aura une incohérence anticipée de courte durée lorsque le premier compte a été mis à jour mais que le second n'est pas encore mis à jour. Ceci doit être pris en compte de la même manière que vous traiterez la cohérence éventuelle si votre Couchdb est distribué.

Une autre implémentation possible évite complètement le besoin de transactions: stockez simplement les documents de tension et évaluez l’état de votre système en évaluant chaque document de tension impliqué. Dans l'exemple ci-dessus, cela signifierait que le total d'un compte est uniquement déterminé comme la somme des valeurs dans les documents de transaction impliquant ce compte. Dans Couchdb, vous pouvez très bien modéliser ceci sous forme de vue carte/réduction.

20
ordnungswidrig

Non, CouchDB n'est généralement pas adapté aux applications transactionnelles car il ne prend pas en charge les opérations atomiques dans un environnement en cluster/répliqué.

CouchDB a sacrifié la capacité transactionnelle au profit de l'évolutivité. Pour avoir des opérations atomiques, vous avez besoin d'un système de coordination central, ce qui limite votre évolutivité.

Si vous pouvez garantir que vous ne possédez qu'une seule instance de CouchDB ou que toute personne modifiant un document particulier se connecte à la même instance de CouchDB, vous pouvez utiliser le système de détection de conflit pour créer une sorte d'atomicité à l'aide des méthodes décrites ci-dessus, mais si vous effectuez ultérieurement une mise à l'échelle vers un cluster ou utilisez un service hébergé comme Cloudant, il tombera en panne et vous devrez refaire cette partie du système.

Donc, ma suggestion serait d'utiliser autre chose que CouchDB pour les soldes de votre compte, ce sera beaucoup plus facile de cette façon.

5
Dobes Vandermeer

En réponse au problème du PO, Couch n'est probablement pas le meilleur choix ici. L'utilisation de vues est un excellent moyen de suivre l'inventaire, mais il est plus ou moins impossible de le bloquer à 0. Le problème étant la condition de concurrence critique lorsque vous lisez le résultat d'une vue, décidez que vous pouvez utiliser un élément "marteau-1", puis écrivez un document pour l'utiliser. Le problème est qu’il n’existe aucun moyen atomique d’écrire dans le doc pour utiliser le marteau si le résultat de la vue est qu’il existe plus de 0 marteau-1. Si 100 utilisateurs interrogent simultanément la vue et voient 1 marteau-1, ils peuvent tous écrire un document utilisant un marteau 1, ce qui donne -99 marteau-1. En pratique, la situation de concurrence critique sera relativement petite - vraiment petite si votre base de données exécute localhost. Mais une fois que vous avez fait évoluer votre système et que vous avez un serveur de base de données ou un cluster hors site, le problème devient beaucoup plus perceptible. Quoi qu’il en soit, il est inacceptable d’avoir une telle situation dans un système critique lié à l’argent.

Une mise à jour de la réponse de MrKurt (il se peut qu'elle soit simplement datée ou qu'il ignore peut-être certaines fonctionnalités de CouchDB)

Une vue est un bon moyen de gérer des éléments tels que les soldes/inventaires dans CouchDB.

Vous n'avez pas besoin d'émettre le docid et rev dans une vue. Vous obtenez ces deux éléments gratuitement lorsque vous récupérez les résultats de la vue. Les émettre, en particulier dans un format commenté comme un dictionnaire, ne fera que grossir inutilement votre vue.

Une vue simple permettant de suivre les soldes d’inventaire devrait ressembler davantage à ceci (même pour mémoire)

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

Et la fonction de réduction est encore plus simple

_sum

Ceci utilise une fonction de réduction intégrée qui additionne simplement les valeurs de toutes les lignes avec des clés correspondantes.

Dans cette vue, tout document peut avoir un membre "InventoryChange" qui mappe product_key à une modification de son inventaire total. c'est à dire.

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

Ajoutez 10 hammer_1234 et 25 saw_4321.

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

Brûlerait 5 marteaux de l'inventaire.

Avec ce modèle, vous ne mettez jamais à jour aucune donnée, vous ne faites que l'ajouter. Cela signifie qu'il n'y a aucune possibilité de conflit de mise à jour. Tous les problèmes transactionnels de mise à jour des données disparaissent :)

Ce modèle présente également un autre avantage intéressant: TOUS les documents de la base de données peuvent à la fois ajouter et soustraire des articles de l’inventaire. Ces documents peuvent contenir toutes sortes d'autres données. Vous pouvez avoir un document "Envoi" avec un tas de données sur la date et l'heure de réception, l'entrepôt, le destinataire, etc., et tant que ce document définit un InventoryChange, il met à jour l'inventaire. Tout comme un document "Sale", un document "DamagedItem", etc. Ils ont lu très clairement chaque document. Et la vue gère tout le travail difficile.

5
wallacer

En fait, vous pouvez en quelque sorte. Jetez un coup d’œil à l’API Document HTTP et faites défiler jusqu’à l'en-tête "Modifier plusieurs documents en une seule requête".

En gros, vous pouvez créer/mettre à jour/supprimer une série de documents en une seule demande de publication adressée à URI/{nombase}/_ bulk_docs et ils réussiront tous ou échoueront. Le document met en garde que ce comportement peut changer dans le futur, cependant.

EDIT: Comme prévu, à partir de la version 0.9, la documentation en bloc ne fonctionne plus de cette façon.

3
Evan

Utilisez simplement le type de solution légère SQlite pour les transactions. Lorsque la transaction est terminée, répliquez-la et marquez-la comme répliquée dans SQLite.

Table SQLite

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

Vous pouvez également supprimer les transactions répliquées avec succès.

0
Ravinder Payal