Usamos filas junto com um banco de dados: discussão de problemas, soluções possíveis

Filas são uma ótima ferramenta com escalabilidade quase perfeita. O ferro não aguenta? Acabamos de adicionar nós ao cluster. Quando uma fila está presente em um projeto, é tentador implementar mais e mais funcionalidades com sua ajuda.





Falaremos sobre as armadilhas desse caminho neste artigo.





Mais cedo ou mais tarde, ao usar filas, o usuário se depara com a questão de usá-las em conjunto com algum serviço, banco de dados, etc.





  • O pedido está concluído, você precisa enviar uma notificação por SMS para o usuário.





  • Um novo pedido chegou, você precisa enviar uma notificação push para os executores.





  • O trabalho está feito, você precisa dar baixa do dinheiro da conta do cliente.





Em todos os exemplos acima, as mudanças em uma entidade comercial são registradas no banco de dados (ou em um serviço com banco de dados) e há uma grande tentação de enviar notificações por meio de filas.





O que temos nesta situação? Estrutura de código inicial mais simples:





  • O serviço (nosso programa) registra alterações nos dados no banco de dados.





  • O serviço então coloca o trabalho na fila.





Na verdade, neste caso, você precisa implementar um gatilho de evento para alterar o registro de dados.





E no caso geral, verifica-se que aqui temos dois registros em dois bancos de dados diferentes: serviços e filas.





Agora vamos pular para o mundo real e considerar quais situações podem surgir:





  • Tudo está bem. DB está disponível, a fila DB está disponível;





  • , ;





  • , ;





  • , .





: , , .





, , ... . .





, .





, ( , , ), , :





  1. .





  2. .





  3. .





  4. .





, , .





:





  • , . 3 4 ( ).





  • . .





.





, . : , . , , .





, (, , http- /).





, .





/, ( queue



) .





, , ( ):





/*  */
UPDATE
   "orders"
SET
   "status" = 'complete'
WHERE
   "order_id" = $1
RETURNING
   *

      
      



/*  */
WITH "o" AS (
    UPDATE
       "orders"
    SET
       "status" = 'complete'
    WHERE
       "order_id" = $1
    RETURNING
       *
),
"q" AS (
   INSERT INTO
        "queue"
   (
      "key",
      "data"
   )

   SELECT
      "o.order_id",
      "o.status"
   FROM
      "o"
)
SELECT
   *
FROM
   "o"

      
      



: queue



, , orders



.





, queue



, , . , .





:





  • queue



    .





  • .





  • .





/

, , . , - ( , ..), :





  • .





, , , , O_APPEND



- .





  • ( ) ,





  • .





  • ( ) .





, , , , .





Como você pode ver, existem poucas opções para resolver o problema. Se quisermos manter o sistema simples (princípio KISS), a introdução de um daemon adicional e mensagens de cache / log no banco de dados ou arquivo / banco de dados local aumentará ligeiramente a complexidade. Ao mesmo tempo, é muito importante manter o handler idempotente, pois em caso de falhas no momento de transferência das tarefas do cache local para a fila geral, podem aparecer duplicatas.





Uma solução generalizada é usar um commit de duas fases.








All Articles