Como evitar condições de corrida em um aplicativo da web?

31

Considere um site de comércio eletrônico, onde Alice e Bob estão editando as listagens de produtos. Alice está melhorando as descrições, enquanto Bob está atualizando os preços. Eles começam a editar o Acme Wonder Widget ao mesmo tempo. Bob termina primeiro e salva o produto com o novo preço. Alice demora um pouco mais para atualizar a descrição e, quando termina, ela salva o produto com sua nova descrição. Infelizmente, ela também substitui o preço pelo preço antigo, que não era o pretendido.

Na minha experiência, esses problemas são extremamente comuns em aplicativos da web. Alguns softwares (por exemplo, software wiki) têm proteção contra isso - geralmente o segundo salvamento falha com "a página foi atualizada enquanto você estava editando". Mas a maioria dos sites não tem essa proteção.

Vale a pena notar que os métodos do controlador são seguros por thread. Geralmente eles usam transações de banco de dados, o que as torna seguras no sentido de que, se Alice e Bob tentarem salvar exatamente no mesmo momento, isso não causará corrupção. A condição de corrida surge de Alice ou Bob terem dados obsoletos em seu navegador.

Como podemos evitar essas condições de corrida? Em particular, eu gostaria de saber:

  • Quais técnicas podem ser usadas? por exemplo, rastreando a hora da última alteração. Quais são os prós e contras de cada um.
  • O que é uma experiência útil para o usuário?
  • Em quais estruturas essa proteção foi incorporada?
paj28
fonte
Você já deu a resposta: rastreando a data da mudança de objetos e comparando-a com a idade dos dados que outras alterações tentam atualizar. Deseja saber de mais alguma coisa, por exemplo, como fazê-lo com eficiência?
Kilian Foth
@KilianFoth - Eu adicionei algumas informações sobre o que eu particularmente gosto de saber
paj28
1
Sua pergunta não é de forma alguma especial para aplicativos da Web; os aplicativos de desktop podem ter exatamente o mesmo problema. As estratégias de solução típicas são descritas aqui: stackoverflow.com/questions/129329/…
Doc Brown
2
FYI, a forma de bloqueio que você menciona na sua pergunta é conhecido como " controle de concorrência otimista "
TehShrike
Alguma discussão relacionada ao Django aqui
paj28 26/11/14

Respostas:

17

Você precisa "ler suas gravações", o que significa que, antes de anotar uma alteração, é necessário ler o registro novamente e verificar se alguma alteração foi feita desde a última vez que você o leu. Você pode fazer isso campo a campo (granulação fina) ou com base em um carimbo de data / hora (granulação grossa). Enquanto você faz essa verificação, precisa de um bloqueio exclusivo no registro. Se nenhuma alteração foi feita, você pode anotá-las e liberar o bloqueio. Se o registro tiver sido alterado, você interrompe a transação, libera a trava e notifica o usuário.

Phil
fonte
Isso soa como a abordagem mais prática. Você conhece alguma estrutura que implementa isso? Acho que o maior problema com esse esquema é que uma simples mensagem de "conflito de edição" frustrará os usuários, mas é difícil tentar mesclar os conjuntos de alterações (manual ou automaticamente).
paj28
Infelizmente, não conheço nenhuma estrutura que suporte isso imediatamente. Não acho que uma mensagem de erro de edição de conflito seja frustrante, desde que não seja frequente. Por fim, depende da carga do usuário do sistema, se você apenas verificar o carimbo de data / hora ou implementar uma função de mesclagem mais complexa.
26414 Phil
Eu mantive um produto de banco de dados distribuído por PC que usava a abordagem refinada (contra a cópia local do banco de dados): se um usuário alterava o preço e o outro alterava a descrição - não há problema! Assim como na vida real. Se dois usuários alteraram o preço - o segundo usuário pede desculpas e tenta a alteração novamente. Sem problemas! Isso não requer bloqueios, exceto durante o momento em que os dados estão sendo gravados no banco de dados. Não importa se um usuário vai almoçar enquanto a alteração está na tela e a envia posteriormente. Para alterações remotas no banco de dados, foi baseado em registros de data e hora do registro.
1
O Dataflex tinha uma função chamada "reler ()", que faz o que você descreve. Nas versões posteriores, era seguro em um ambiente multiusuário. E, de fato, era a única maneira de obter essas atualizações intercaladas para o trabalho.
você pode dar um exemplo de como fazer isso com o servidor sql? \
l --''''''--------- '' '' '' '' '' '23 de
10

Eu vi duas maneiras principais:

  1. Adicione o carimbo de data e hora da última atualização da página que o uso está editando em uma entrada oculta. Ao confirmar o carimbo de data e hora, é verificado o atual e, se não corresponderem, foi atualizado por outra pessoa e retorna um erro.

    • pro: vários usuários podem editar partes diferentes da página. A página de erro pode levar a uma página de diferenças, na qual o segundo usuário pode mesclar suas alterações na nova página.

    • con: às vezes, grandes partes do esforço são desperdiçadas durante grandes edições simultâneas.

  2. Quando um usuário começa a editar a página, bloqueie-a por um período de tempo razoável; quando outro usuário tenta editar, ele recebe uma página de erro e precisa esperar até que o bloqueio expire ou o primeiro usuário seja confirmado.

    • pro: os esforços de edição não são desperdiçados.

    • con: um usuário sem escrúpulos pode bloquear uma página indefinidamente. Uma página com um bloqueio expirado ainda poderá confirmar, a menos que seja tratado de outra forma (usando a técnica 1)

catraca arrepiante
fonte
7

Use o controle de concorrência otimista .

Adicione uma coluna versionNumber ou versionTimestamp à tabela em questão (o número inteiro é mais seguro).

O usuário 1 lê o registro:

{id:1, status:unimportant, version:5}

O usuário 2 lê o registro:

{id:1, status:unimportant, version:5}

O usuário 1 salva o registro, isso incrementa a versão:

save {id:1, status:important, version:5}
new value {id:1, status:important, version:6}

O usuário 2 tenta salvar o registro que lê:

save {id:1, status:unimportant, version:5}
ERROR

O Hibernate / JPA pode fazer isso automaticamente com a @Versionanotação

Você precisa manter o estado do registro de leitura em algum lugar, geralmente na sessão (isso é mais seguro do que em uma variável de formulário oculta).

Neil McGuigan
fonte
Obrigado ... é particularmente útil saber sobre o @Version. Uma pergunta: por que é seguro armazenar o estado na sessão? Nesse caso, eu ficaria preocupado que o uso do botão Voltar pudesse confundir as coisas.
paj28
A sessão é mais segura que um elemento de formulário oculto, pois o usuário não poderá alterar o valor da versão. Se isso não é uma preocupação, então ignorar a parte sobre sessão
Neil McGuigan
Esta técnica é chamada de Bloqueio Offline Otimista e está em SQLAlchemy bem
paj28
@ paj28 - esse link SQLAlchemynão indica nada sobre bloqueios offline otimistas, e não consigo encontrá-lo nos documentos. Você teve um link mais útil ou apenas apontou pessoas para o SQLAlchemy em geral?
dwanderson
Dwanderson @ eu quis dizer a versão contrária parte desse link.
paj28
1

Alguns sistemas ORM (Object Relational Mapping) detectam quais campos de um objeto foram alterados desde o carregamento do banco de dados e constroem a instrução de atualização SQL para definir apenas esses valores. O ActiveRecord para Ruby on Rails é um desses ORM.

O efeito líquido é que os campos que o usuário não alterou não são incluídos no comando UPDATE enviado ao banco de dados. As pessoas que atualizam campos diferentes ao mesmo tempo não substituem as alterações umas das outras.

Dependendo da linguagem de programação que você está usando, pesquise quais ORMs estão disponíveis e verifique se alguma delas atualizará apenas as colunas do banco de dados marcadas como "sujas" em seu aplicativo.

Greg Burghardt
fonte
Oi Greg. Infelizmente, isso não ajuda com esses tipos de condições de corrida. Se você considerar meu exemplo original, quando Alice salvar, o ORM verá a coluna de preço como suja e a atualizará - mesmo que a atualização não seja desejada.
paj28
1
@ paj28 O ponto principal na resposta de Greg é " campos que o usuário não alterou ". Alice não alterou o preço, para que o ORM não tentasse salvar o valor "price" no banco de dados.
Ross Patterson
@ RossPatterson - como o ORM sabe a diferença entre os campos que o usuário alterou e os dados obsoletos do navegador? Não é, pelo menos sem fazer algum rastreamento extra. Se você deseja editar a resposta de Greg para incluir esse rastreamento ou enviar outra resposta, isso seria útil.
paj28
@ paj28 alguma parte do sistema precisa saber o que o usuário fez e armazenar apenas as alterações feitas pelo usuário. Se o usuário alterou o preço, alterou novamente e enviou, isso não deve ser considerado "algo que o usuário alterou", porque não mudou. Se você possui um sistema que requer esse nível de controle de simultaneidade, precisa construí-lo dessa maneira. Se não, não.
@nocomprende - Alguma parte, com certeza - mas não o ORM como esta resposta diz
paj28