Pergunta

Eu preciso fazer transações (begin, commit ou rollback), fechaduras (selecione para atualização). Como posso fazê-lo em um modelo de documento db?

Editar:

O caso é o seguinte:

  • Eu quero correr um site de leilões.
  • E eu acho que como a compra direta também.
  • Em uma compra direta eu tenho que diminuir o campo de quantidade no registro de item, mas apenas se a quantidade for maior que zero. É por isso que eu preciso de fechaduras e transações.
  • eu não sei como lidar com isso sem bloqueios e / ou transações.

Posso resolver isso com CouchDB?

Foi útil?

Solução

No. CouchDB usa um modelo de "concorrência otimista". Em termos mais simples, isso significa apenas que você envie uma versão do documento, juntamente com a sua atualização, e CouchDB rejeita a mudança se a versão atual do documento não corresponde ao que você enviou.

É extremamente simples, realmente. Você pode reformular muitos cenários baseados em transações normais para CouchDB. Você precisa fazer para classificar de jogar fora seu conhecimento de domínio RDBMS quando aprender CouchDB, no entanto. É útil para abordar os problemas de um nível superior, em vez de tentar moldar Sofá para um mundo baseado em SQL.

Manter o controle de inventário

O problema que você delineou é principalmente um problema de inventário. Se você tem um documento que descreve um item, e inclui um campo para "quantidade disponível", você pode lidar com problemas de concorrência como este:

  1. Recuperar o documento, tome nota da propriedade _rev que CouchDB envia ao longo
  2. diminuir o campo de quantidade, se é maior do que zero
  3. Enviar a volta documento atualizado, usando a propriedade _rev
  4. Se o _rev corresponde ao número atualmente armazenado, ser feito!
  5. Se houver um conflito (quando _rev não corresponder), recuperar a versão do documento mais recente

Neste caso, há dois cenários de falha possíveis para pensar. Se a versão mais recente documento tem uma quantidade de 0, você segurá-lo apenas como você faria em um RDBMS e alerta o usuário que eles não podem realmente comprar o que eles queriam compra. Se a versão mais recente documento tem uma quantidade maior que 0, você simplesmente repetir a operação com os dados atualizados, e começar a voltar ao início. Isso força você a fazer um pouco mais trabalho do que um RDBMS faria, e poderia ficar um pouco chato se não são frequentes, conflitantes atualizações.

Agora, a resposta que eu apenas deu pressupõe que você vai fazer as coisas de CouchDB da mesma maneira que você faria em um RDBMS. Eu poderia abordar este problema um pouco diferente:

Eu começaria com um documento de "produto mestre" que inclui todos os dados do descritor (nome, imagem, descrição, preço, etc). Então eu gostaria de acrescentar um documento "bilhete de inventário" para cada caso específico, com campos para product_key e claimed_by. Se você está vendendo um modelo de martelo, e têm 20 deles para vender, você pode ter documentos com teclas como hammer-1, hammer-2, etc, para representar cada martelo disponível.

Então, eu criar uma visão que me dá uma lista de martelos disponíveis, com uma reduzir a função que me permite ver um "total". Estes são completamente fora do manguito, mas deve dar-lhe uma idéia do que uma visão de trabalho seria semelhante.

Map

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

Isso me dá uma lista de "bilhetes" disponíveis, por chave de produto. Eu poderia pegar um grupo destes quando alguém quer comprar um martelo, então iterate através de envio de atualizações (usando o id e _rev) até que eu reclamar com sucesso um (bilhetes anteriormente reivindicadas irá resultar em um erro de atualização).

Reduzir

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

Esta reduzir a função simplesmente retorna o número total de itens inventory_ticket não reclamados, para que possa dizer quantos "martelos" estão disponíveis para compra.

Advertências

Esta solução representa cerca de 3,5 minutos de pensamento total para o problema específico que você apresentou. Pode haver melhores maneiras de fazer isso! Dito isto, reduz substancialmente conflitantes atualizações e reduz a necessidade de responder a um conflito com uma nova atualização. Sob este modelo, você não terá vários usuários que tentam alterar dados na entrada de produto primário. Na pior das hipóteses, você terá múltiplos usuários tentando reivindicar um único bilhete, e se você pegou vários deles a partir de seu ponto de vista, você simplesmente passar para a próxima passagem e tentativanovamente.

Referência: https://wiki.apache.org/couchdb/Frequently_asked_questions#How_do_I_use_transactions_with_CouchDB. 3F

Outras dicas

Expandindo resposta de MrKurt. Para os lotes de cenários que você não precisa ter bilhetes de ações resgatadas em ordem. Em vez de selecionar o primeiro bilhete, você pode selecionar aleatoriamente os bilhetes restantes. Dada uma grande bilhetes de número e um grande número de solicitações simultâneas, você vai ficar muito reduzida contenção nesses bilhetes, contra todos a tentar obter o primeiro bilhete.

Um padrão de design para transações Restfull é criar uma "tensão" no sistema. Para o caso exemplo uso popular de uma operação de conta bancária você deve garantir para atualizar o total para ambas as contas envolvidas:

  • Criar um documento de transação "transferência de USD 10 a partir da conta 11223 para a conta 88733". Isso cria a tensão no sistema.
  • Para resolver qualquer verificação de tensão para todos os documentos de transacção e
    • Se a conta de origem não é atualizado ainda atualizar a conta de origem (-10 USD)
    • Se a conta de origem foi atualizado, mas o documento de transação não mostra isso, então atualizar o documento de transação (por exemplo, bandeira conjunto "sourcedone" no documento)
    • Se a conta de destino não é atualizado ainda atualizar a conta de destino (10 USD)
    • Se a conta de destino foi atualizado, mas o documento de transação não mostra isso, então atualizar o documento de transação
    • Se ambas as contas foram atualizadas você pode excluir o documento de transação ou mantê-lo para auditoria.

A verificação de tensão deve ser feito em um processo de back-end para todos os "documentos de tensão" para manter os tempos de tensão no curto sistema. No exemplo acima, haverá um curto espaço de tempo inconsistência antecipado quando a primeira conta foi atualizada, mas o segundo ainda não é atualizado. Isso deve ser levado em conta da mesma forma que você vai lidar com consistência eventual se o seu Couchdb é distribuído.

Outra possível aplicação evita a necessidade de transações totalmente: apenas armazenar os documentos de tensão e avaliar o estado de seu sistema através da avaliação de todos os documentos tensão envolvida. No exemplo acima, isto significaria que o total para uma conta única é determinado como os valores de soma nos documentos de transacção em que este relato é envolvidas. Em Couchdb você pode modelar esta muito bem como um mapa / reduzir a vista.

Não, CouchDB, geralmente não é adequado para aplicações transacionais porque ele não suporta operações atômicas em um ambiente em cluster / replicados.

CouchDB sacrificado capacidade transacional em favor de escalabilidade. Para se ter operações atômicas você precisa de um sistema de coordenação central, o que limita a sua capacidade de expansão.

Se você pode garantir que você só tem uma instância CouchDB ou que todo mundo modificar uma determinada Ligações de documentos para a mesma instância CouchDB, então você pode usar o sistema de detecção de conflitos para criar uma espécie de atomicity usando métodos descritos acima, mas se você mais tarde ampliar a um cluster ou usar um serviço hospedado como Cloudant ele vai quebrar e você vai ter que refazer essa parte do sistema.

Assim, a minha sugestão seria usar algo diferente do CouchDB para os seus saldos de conta, será muito mais fácil dessa maneira.

Como uma resposta para o problema do OP, Couch não é provavelmente a melhor escolha aqui. Usando pontos de vista é uma ótima maneira de manter o controle de inventário, mas aperto para 0 é mais ou menos impossível. O problema é a condição de corrida quando você lê o resultado de um ponto de vista, decidir que está ok para usar um "hammer-1" item, e em seguida, escrever um documento para usá-lo. O problema é que não há nenhuma maneira atômica para escrever apenas o doc de usar o martelo se o resultado da opinião é que existem> 0 martelo-1. Se 100 usuários em consultar a exibição ao mesmo tempo e ver um hammer-1, todos eles podem escrever um documento para usar um martelo 1, resultando em -99 hammer-1 do. Na prática, a condição de corrida será relativamente pequeno - muito pequeno se o seu DB está em execução localhost. Mas uma vez que você escala, e tem um site off servidor DB ou cluster, o problema vai ficar muito mais perceptível. Independentemente disso, é inaceitável ter uma condição de corrida desse tipo de crítica -. Sistema relacionado dinheiro

Uma atualização para a resposta de MrKurt (só ele pode ser datado, ou ele pode ter tido conhecimento de algum CouchDB possui)

A vista é uma boa maneira de coisas punho como saldos / estoques no CouchDB.

Você não precisa emitir o docid e rev em uma exibição. Você ganha tanto daqueles para livre quando você recupera ver os resultados. Emitindo-los - especialmente em um formato detalhado como um dicionário -. Só vai crescer a sua visão desnecessariamente grande

Uma visão simples para seguir os saldos de estoque deve olhar mais como este (também fora do topo da minha cabeça)

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

E a função reduzir ainda mais simples

_sum

Isto usa um construído em reduzir a função que apenas soma os valores de todas as linhas com chaves correspondentes.

Neste ponto de vista, qualquer documento pode ter um membro "InventoryChange" que mapeia PRODUCT_KEY de que uma mudança no estoque total deles. ie.

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

acrescentaria 10 de hammer_1234 e 25 de saw_4321.

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

iria queimar 5 martelos do inventário.

Com este modelo, você nunca atualizar quaisquer dados, apenas acrescentando. Isto significa que não há oportunidade para conflitos de atualização. Todas as questões transacionais de dados de atualização ir embora:)

Outra coisa agradável sobre este modelo é que qualquer documento no DB pode tanto add e itens subtrair do inventário. Estes documentos podem ter todos os tipos de outros dados neles. Você pode ter um documento "Expedição" com um monte de dados sobre a data ea hora do recebimento, almoxarifado, recebimento empregado etc. e enquanto que doc define um InventoryChange, vai atualizar o inventário. Como poderia um doc "Sale", e um doc "DamagedItem" etc. Olhando para cada documento, eles lêem muito claramente. E a vista alças todo o trabalho duro.

Apenas uso SQlite tipo de solução leve para transações, e quando a transação for concluída com êxito replicar-lo e marcá-lo replicado em SQLite

tabela de SQLite

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

Você também pode excluir as transações que são replicadas com sucesso.

scroll top