Criar filas de mensagens e microsserviços confiáveis em bancos de dados distribuídos na nuvem, como o Spanner, envolve desafios exclusivos. Este documento descreve os conceitos principais e os padrões de design usados para executar o processamento exatamente uma vez e o reconhecimento no máximo uma vez em filas do Spanner.
Principais conceitos e o dilema da idempotência
"Idempotência" significa que, não importa quantas vezes uma operação seja executada, o resultado é sempre o mesmo. Ao criar filas de mensagens ou sistemas de entrega de trabalho, há três métodos principais de entrega que abordam a idempotência:
- At-least-once::as mensagens têm entrega e processamento garantidos. Se ocorrerem falhas de rede, uma mensagem poderá ser entregue e processada várias vezes.
- At-most-once::as mensagens são entregues no máximo uma vez. O processamento duplicado é evitado, mas, se ocorrer uma falha, a mensagem poderá ser perdida ou descartada.
- Exatamente uma vez:cada mensagem é processada exatamente uma vez, sem ser perdida nem duplicada.
As filas do Spanner oferecem automaticamente entrega do tipo "pelo menos uma vez" e confirmação do tipo "no máximo uma vez". No entanto, é possível usar os padrões de design e os exemplos descritos nas seções a seguir para reduzir de forma programática o processamento e a nova entrega de duplicados.
O problema do status de confirmação desconhecido
Em um banco de dados de máquina única, as transações são bem-sucedidas ou falham. Em um banco de dados de nuvem distribuída, as transações podem ser perdidas à medida que os dados são replicados em vários data centers físicos.
Quando seu aplicativo processa uma mensagem e confirma um reconhecimento como
DELETE FROM MessageQueue WHERE message_id = '123' ASSERT_ROWS_MODIFIED 1, a
solicitação passa por vários saltos de rede:
[Your App] <--(gRPC)--> [Spanner API] <--(Google network)--> [Spanner Storage]
Quando o líder de armazenamento do Spanner recebe o commit, ele é sincronizado em todas as réplicas de armazenamento. Depois que o consenso é alcançado e os dados são gravados em disco, a transação é confirmada no banco de dados.
No entanto, uma falha de rede temporária (como uma reinicialização de proxy ou uma desconexão de rede) pode ocorrer depois que os dados são gravados no disco, mas antes que a confirmação de sucesso chegue ao seu aplicativo. Se isso acontecer, o aplicativo
vai receber um erro de tempo limite de rede (DEADLINE_EXCEEDED ou UNAVAILABLE).
Como a conexão foi interrompida fora da banda, o aplicativo não tem como saber se a conexão foi interrompida antes ou depois da confirmação. Isso é conhecido como o problema de "status de confirmação desconhecido".
Por que as novas tentativas automáticas podem ser perigosas
Se o aplicativo interceptar um erro de tempo limite de rede e tentar novamente a mesma transação sem remoção de duplicação, ele corre o risco de executar a lógica de negócios duas vezes. Por exemplo, se a transação envolver a cobrança de um cartão de crédito de um cliente ou o envio de um item, repetir uma transação bem-sucedida (mas não confirmada) resultará em uma cobrança ou envio duplicado.
Para proteger seus usuários, as bibliotecas de cliente oficiais do Google Cloud nunca repetem automaticamente transações que falham com erros de confirmação desconhecidos. Eles geram um erro explícito alertando que o resultado da transação é desconhecido, deixando para o código do aplicativo o tratamento da nova tentativa com segurança usando padrões de idempotência.
Estratégias do mundo real para processamento único
Para tentar novamente as transações com segurança e reduzir o processamento duplicado e a nova entrega de mensagens, implemente um dos seguintes padrões de design principais.
Declarar linhas modificadas
Se toda a sua lógica de negócios (como a atualização de outras tabelas) e o reconhecimento da mensagem da fila ocorrerem em uma única transação do Spanner, use ASSERT_ROWS_MODIFIED na sua instrução DELETE. Essa é a estratégia mais simples para o processamento único, porque não exige uma tabela de eliminação de duplicação separada.
Ao anexar ASSERT_ROWS_MODIFIED 1 à sua instrução, o reconhecimento só
será bem-sucedido se exatamente uma mensagem for excluída. Se ocorrer uma falha de rede e
o aplicativo tentar novamente a transação, a mensagem não vai mais existir na
fila. A nova tentativa exclui 0 linhas, e a instrução falha com OUT_OF_RANGE.
O erro OUT_OF_RANGE é permanente: a linha já foi excluída. Portanto, executar a instrução novamente sempre vai modificar 0 linhas e falhar de novo. Ele também é
no nível da instrução: apenas o DELETE falha. A transação permanece aberta,
e todas as gravações já feitas nela ainda estão em buffer e serão
confirmadas se você continuar. O Spanner não encerra nem reverter a transação para você. Para ter a proteção exatamente uma vez pretendida, é necessário
garantir que a transação seja revertida:
- Deixe o erro se propagar:se você usar uma API baseada em runner (como
ReadWriteTransactionem Go), deixe o erroOUT_OF_RANGEse propagar para fora da função de transação. A biblioteca de cliente vai abandonar a transação e fazer o rollback dela, garantindo que nenhuma gravação de lógica de negócios seja confirmada. - Não capture e continue:nunca capture o erro de asserção dentro da função de transação e continue. Se você fizer isso, o Spanner ainda vai confirmar suas outras instruções, causando a atualização duplicada exata que este padrão deve evitar.
- Rollbacks manuais:se você gerenciar transações manualmente (por exemplo, com
ReadWriteStmtBasedTransactionem Go ou as APIs REST/gRPC), chame explicitamente o método de rollback correspondente quando a asserção falhar.
Caixa de saída transacional
Se a lógica de negócios não puder ocorrer em uma única transação do Spanner (como fluxos de trabalho de várias etapas ou atualizações em vários sistemas) ou se o processamento de mensagens exigir a chamada de serviços externos (como o envio de um SMS ou o processamento de um pagamento), não será possível acoplar atomicamente a lógica de negócios ao reconhecimento da fila.
Nesses cenários, nunca chame APIs externas nem execute operações não transacionais diretamente em um bloco de transação do Spanner. Se o Spanner repetir ou cancelar a transação, seu aplicativo poderá executar essa lógica de negócios várias vezes.
Seu aplicativo precisa implementar a própria estratégia de idempotência, mas você pode usar os padrões a seguir para reduzir o trabalho duplicado:
Operações rápidas (concluídas no período de concessão padrão de 10 segundos): execute a lógica de negócios ou a chamada de API externa quando a mensagem for recebida. Confirme a mensagem somente depois que a operação for concluída. Se a operação falhar ou se o worker falhar antes da confirmação, não confirme a mensagem. O lease expira, e o Spanner entrega automaticamente a mensagem para nova tentativa.
Operações de longa duração (mais de 10 segundos): confirme o recebimento da mensagem e, na mesma transação, reenvie uma nova mensagem programada para entrega futura (usando um atraso de entrega maior que o tempo de execução estimado). Como alternativa, estenda periodicamente o tempo de concessão da mensagem usando a TVF
RENEWLEASE_QUEUE_NAME(). Quando a lógica de negócios ou a chamada externa for concluída, confirme a mensagem recém-enfileirada ou estendida.
Idempotência de sessão multiplexada
As sessões do Spanner podem ser multiplexadas. As sessões multiplexadas rastreiam estados de transação na memória em sessões compartilhadas. Isso protege contra falhas de rede do lado do cliente, permitindo que as bibliotecas de cliente se reconectem e resolvam status de confirmação desconhecidos de maneira mais confiável.
No entanto, as sessões multiplexadas não podem eliminar resultados de confirmação desconhecidos se um servidor de front-end falhar. Se o servidor específico que hospeda a tabela de transações da sua sessão for reinicializado imediatamente após um commit, o estado na memória será perdido, retornando um erro de resultado de transação desconhecido ao se reconectar. Para evitar esse risco, implemente os padrões de idempotência nesta página.
A seguir
- Saiba como usar filas do Spanner, incluindo práticas recomendadas e monitoramento.
- Confira mais cenários e exemplos de filas do Spanner.
- Configure o controle de acesso com o controle de acesso detalhado para filas.