Como lidar com restrições de chave estrangeira ao migrar do monólito para microsserviços?

18

Minha equipe está migrando de um aplicativo ASP.NET monolítico para o .NET Core e o Kubernetes. As alterações de código parecem estar indo tão bem quanto o esperado, mas onde minha equipe está encontrando muita discórdia é em torno do banco de dados.

Atualmente, temos um banco de dados do SQL Server bastante grande que abriga todos os dados de toda a empresa. Estou propondo que dividamos o banco de dados de maneira semelhante à divisão do código - dados de catálogo em um banco de dados (lógico), dados de inventário em outro, pedidos em outro, etc. - e cada microsserviço seria o guardião do seu banco de dados .

A implicação aqui é que as chaves estrangeiras que cruzam os limites dos microsserviços precisam ser removidas e os sprocs e visualizações que atingem os limites seriam proibidos. Todos os modelos de dados podem ou não residir no mesmo banco de dados físico, mas, mesmo que existam, não devem interagir diretamente entre si. Os pedidos ainda podem fazer referência aos itens do catálogo por ID, mas a integridade dos dados não seria rigorosamente aplicada no nível do banco de dados e esses dados deverão ser unidos no código, e não no SQL.

Eu vejo a perda dessas como compensações necessárias na mudança para o microsserviço e na obtenção dos benefícios de escalabilidade que acompanham. Enquanto escolhermos nossas costuras com sabedoria e desenvolvermos em torno delas, tudo ficará bem. Outros membros da equipe afirmam que tudo deve permanecer no mesmo banco de dados monolítico para que tudo possa ser ACID e ter integridade referencial preservada em todos os lugares.

Isso me leva à minha pergunta. Primeiro, a minha posição sobre restrições de chave estrangeira e a adesão é plausível? Em caso afirmativo, alguém conhece algum material de leitura confiável que eu possa oferecer aos meus colegas? A posição deles é quase religiosa e eles não parecem ser influenciados por nada menos que o próprio Martin Fowler dizendo que eles estão errados.

Raymond Saltrelli
fonte
5
A integridade referencial é extremamente valiosa. A escala do banco de dados é realmente o gargalo aqui? Você realmente precisa de escalabilidade no estilo de microsserviço? Você sabe melhor do que eu se essa alteração na arquitetura é apropriada para sua organização, mas considere que simplesmente não é uma boa opção para muitos casos de uso. Pode haver outras maneiras de escalar com trocas mais atraentes. Por exemplo, se as consultas ao banco de dados por segundo forem muito altas, talvez seja necessário replicar o banco de dados. E você pode dimensionar servidores da Web horizontalmente sem precisar usar microsserviços.
amon
Bons pontos. Estamos analisando algumas dessas opções para obter ganhos de curto prazo. A mudança para microsserviços é um jogo longo, no entanto. Na minha opinião, nos permitirá escalar por anos, em vez de meses.
Raymond Saltrelli 12/09
3
Tenho certeza de que seus clientes ficarão entusiasmados com o fato de o pedido que eles fizeram 0,05 ms mais rápido ser cancelado porque outra pessoa encomendou o mesmo produto quando restava apenas um em estoque.
Andy Andy
@amon Faça disso uma resposta e eu a votarei. Essa é uma boa pergunta e os prós e contras precisam ser representados de maneira justa.
Mcottle # 13/18
@mcottle ok, pronto!
amon

Respostas:

19

Não há uma solução clara, porque isso depende inteiramente do seu contexto - em particular, ao longo de quais dimensões seu sistema deve escalar e quais são seus problemas reais. O banco de dados é realmente o seu gargalo?

Essa resposta (infelizmente bastante longa) será um pouco parecida com “microsserviços ruins, monólitos para toda a vida!”, Mas essa não é minha intenção. O que quero dizer é que os microsserviços e os bancos de dados distribuídos podem resolver vários problemas, mas não sem ter alguns problemas próprios. Para apresentar um argumento forte para sua arquitetura, você deve mostrar que esses problemas não se aplicam, podem ser mitigados e que essa arquitetura é a melhor escolha para suas necessidades de negócios.

Dados distribuídos são difíceis.

A mesma flexibilidade que permite uma melhor escala é o outro lado das garantias mais fracas. Notavelmente, os sistemas distribuídos são muito mais difíceis de raciocinar.

Atualizações atômicas, transações, consistência / integridade referencial e durabilidade são extremamente valiosas e não devem ser dispensadas precipitadamente. Há pouco sentido em ter dados se estiverem incompletos, desatualizados ou totalmente errados. Quando você tem o ACID como um requisito comercial, mas está usando a tecnologia de banco de dados que não pode oferecê-lo imediatamente (por exemplo, muitos bancos de dados NoSQL ou uma arquitetura DB por microsserviço), seu aplicativo deve preencher a lacuna e fornecer essas garantias.

  • Isso não é impossível, mas complicado de acertar. Muito complicado. Especialmente em um ambiente distribuído, onde há vários gravadores para cada banco de dados. Essa dificuldade se traduz em uma grande chance de erros, possivelmente incluindo dados descartados, dados inconsistentes e assim por diante.

    Por exemplo, considere ler as análises Jepsen de sistemas de banco de dados distribuídos conhecidos , talvez começando com a análise de Cassandra . Não entendo metade dessa análise, mas o TL; DR é que os sistemas distribuídos são tão difíceis que até mesmo os projetos líderes do setor às vezes os entendem errado, de maneiras que podem parecer óbvias em retrospectiva.

  • Os sistemas distribuídos também implicam um maior esforço de desenvolvimento. Até certo ponto, há uma troca direta entre custos de desenvolvimento ou queda de dinheiro em hardware mais robusto.

Exemplo: referências pendentes

Na prática, você não deve considerar a ciência da computação, mas os requisitos da sua empresa para ver se e como o ACID pode ser relaxado. Por exemplo, muitos relacionamentos de chave estrangeira podem não ser tão importantes quanto parecem. Considere um relacionamento de categoria de produto n: m. Em um RDBMS, podemos usar uma restrição de chave estrangeira para que apenas produtos e categorias existentes possam fazer parte desse relacionamento. O que acontece se introduzirmos serviços separados de produtos e categorias, e um produto ou categoria for excluído?

Nesse caso, isso pode não ser um grande problema e podemos escrever nosso aplicativo para filtrar quaisquer produtos ou categorias que não existem mais. Mas existem vantagens e desvantagens!

  • Observe que isso pode exigir um nível de aplicativo JOINem vários bancos de dados / microsserviços, o que apenas move o processamento do servidor de banco de dados para seu aplicativo. Isso aumenta a carga total e precisa mover dados extras pela rede.

  • Isso pode mexer com paginação. Por exemplo, você solicita os próximos 25 produtos de uma categoria e filtra produtos indisponíveis dessa resposta. Agora, seu aplicativo exibe 23 produtos. Em teoria, uma página com zero produtos também seria possível!

  • Ocasionalmente, você desejará executar um script que limpe as referências pendentes, após cada alteração relevante ou em intervalos regulares. Observe que esses scripts são bastante caros porque precisam solicitar todos os produtos / categorias do banco de dados / microsserviço de backup para verificar se ele ainda existe.

  • Isso deve ser óbvio, mas para maior clareza: não reutilize IDs. As IDs no estilo de incremento automático podem ou não ser boas. GUIDs ou hashes oferecem mais flexibilidade, por exemplo, ao atribuir um ID antes que o item seja inserido em um banco de dados.

Exemplo: pedidos simultâneos

Agora, considere um relacionamento de pedido de produto. O que acontece com um pedido se um produto for excluído ou alterado? Ok, podemos simplesmente copiar os dados relevantes do produto na entrada do pedido para mantê-los disponíveis - trocando espaço em disco pela simplicidade. Mas e se o preço do produto mudar ou o produto ficar indisponível pouco antes de um pedido para esse produto ser feito? Em um sistema distribuído, os efeitos levam tempo para se propagar e o pedido provavelmente será executado com dados desatualizados.

Novamente, como abordar isso depende dos requisitos de negócios. Talvez o pedido desatualizado seja aceitável e, posteriormente, você poderá cancelar o pedido se não puder ser atendido.

Mas talvez isso não seja uma opção, por exemplo, para configurações altamente simultâneas. Considere 3.000 pessoas correndo para comprar ingressos para shows nos primeiros 10 segundos e vamos assumir que uma alteração na disponibilidade exigirá 10ms para se propagar. Qual é a probabilidade de vender o último ingresso para várias pessoas? Depende de como essas colisões são tratadas, mas, usando uma distribuição Poisson, λ = 3000 / (10s / 10ms) = 3temos uma P(k > 1) = 1 - P(k = 0) - P(k = 1) = 80%chance de colisão por intervalo de 10ms. Se a venda e o cancelamento posterior da maioria de seus pedidos são possíveis sem cometer fraudes, isso pode levar a uma conversa interessante com seu departamento jurídico.

Pragmatismo significa escolher as melhores características.

A boa notícia é que você não precisa mudar para um modelo de banco de dados distribuído, se isso não for necessário. Ninguém revogará a sua associação ao Microservice Club se você não fizer microsserviços “adequadamente”, porque não existe esse clube - e não existe uma maneira verdadeira de criar microsserviços.

O pragmatismo vence sempre, então misture e combine várias abordagens à medida que resolvem seu problema. Isso pode até significar microsserviços com um banco de dados centralizado. Realmente, não passe pelo sofrimento dos bancos de dados distribuídos se não precisar.

Você pode escalar sem microsserviços.

Os microsserviços têm dois benefícios principais:

  • O benefício organizacional de que eles podem ser desenvolvidos e implantados independentemente por equipes separadas (o que, por sua vez, exige que os serviços ofereçam uma interface estável).
  • O benefício operacional que cada microsserviço pode ser dimensionado independentemente .

Se o dimensionamento independente não for necessário, os microsserviços serão muito menos atraentes.

Um servidor de banco de dados já é um tipo de serviço que você pode escalar (um pouco) de forma independente, por exemplo, adicionando réplicas de leitura. Você mencionou procedimentos armazenados. Reduzi-los pode ter um efeito tão grande que qualquer outra discussão sobre escalabilidade é discutível.

E é perfeitamente possível ter um monólito escalável que inclua todos os serviços como bibliotecas. Você pode escalar iniciando mais instâncias do monólito, o que naturalmente exige que cada instância seja sem estado.

Isso tende a funcionar bem até que o monólito seja muito grande para ser razoavelmente implantado ou se alguns serviços tiverem requisitos de recursos especiais para que você possa escalá-los independentemente. Os domínios com problemas que envolvem recursos extras podem não envolver um modelo de dados separado.

Você tem um caso de negócios forte?

Você está ciente das necessidades comerciais da sua organização e, portanto, pode criar um argumento para uma arquitetura de banco de dados por microsserviço, com base em uma análise:

  • que uma certa escala é necessária e essa arquitetura é a abordagem mais econômica para obter essa escalabilidade, levando em consideração o maior esforço de desenvolvimento para essa configuração e soluções alternativas; e
  • que seus requisitos comerciais permitam relaxar as garantias relevantes do ACID, sem levar a vários problemas, como os discutidos acima.

Por outro lado, se você não conseguir demonstrar isso, principalmente se o design atual do banco de dados puder suportar uma escala suficiente no futuro (como seus colegas parecem acreditar), também terá sua resposta.

Há também um grande componente YAGNI na escalabilidade. Diante da incerteza, é uma decisão comercial estratégica de construir agora a escalabilidade (custos totais menores, mas envolve custos de oportunidade e pode não ser necessário) versus adiar algum trabalho de escalabilidade (custos totais mais altos, se necessário, mas você tem uma melhor idéia da escala real). Esta não é principalmente uma decisão técnica.

amon
fonte
Excelente resposta, obrigado. Você pode elaborar essa afirmação? Você quer dizer que reduzir o número de procedimentos pode ter um grande efeito no desempenho? Você mencionou procedimentos armazenados. Reduzi-los pode ter um efeito tão grande que qualquer outra discussão sobre escalabilidade é discutível.
quer
1
Os procedimentos armazenados do @alan podem ser usados ​​para sempre, mas apresentam dois problemas de desempenho: (1) consultas mais complexas são mais difíceis para o banco de dados otimizar. (2) Usar sprocs significa trabalhar mais no servidor de banco de dados. O OP deseja dividir o banco de dados para aumentar ainda mais a escala, mas evitar sprocs complicados já pode fornecer esse espaço. Obviamente, sprocs e consultas complexas também podem ser boas para desempenho, por exemplo, quando minimizam a quantidade de dados que devem ser transferidos para fora do banco de dados para uma resposta de consulta. A divisão do banco de dados tornaria esse problema pior quando são necessárias JOINs entre servidores.
amon
0

Eu acredito que ambas as abordagens são plausíveis. Você pode optar por obter escalabilidade sacrificando os benefícios dos bancos de dados ACID e monolíticos, além de manter a arquitetura atual e sacrificar a escalabilidade e agilidade de uma arquitetura mais distribuída. A decisão certa virá do atual modelo de negócios e da estratégia para os próximos anos. Puramente, do ponto de vista da tecnologia, existem dificuldades em mantê-lo monolítico e em mudar para uma abordagem mais distribuída. Eu analisaria o sistema e veria quais aplicativos / módulos / processos de negócios são mais críticos para escalar e avaliar os riscos, custos e benefícios para decidir os que devem esperar ou continuar na arquitetura monolítica.

brunofl
fonte
-1

Sua postura é plausível e correta.

Como convencer tingidos nos viciados em lã db é outra questão. Eu diria que você tem duas opções.

  1. Encontre um exemplo concreto em que o banco de dados atingiu seus limites. Você tem 'tabelas de arquivamento', por exemplo? Por que está tudo bem? Qual é o número máximo de pedidos por segundo que você pode receber? etc. Mostre que o banco de dados não atende aos requisitos e sua solução os corrige.

  2. Contrate empreiteiros caros para lhe oferecer a melhor solução. Por serem despesas e ter blogs, todos acreditarão neles

Ewan
fonte
1
Eu não sou o -1, mas para que essa seja uma boa resposta, eu perderia a importância do ponto 2 e expandiria quando seria apropriado precisar de vários bancos de dados. Não acho que as tabelas de arquivamento sejam necessariamente um antipadrão nos bancos de dados que não suportam o particionamento. O banco de dados que estou usando no momento tem cerca de 130 GB de dados com 26 tabelas> 10 milhões de linhas e o desempenho não é remotamente suficiente para ser necessário dividir o banco de dados; por isso, sou muito cético e gostaria de saber por que essa é uma boa ideia e quando precisa ser feita - essa resposta é a mais próxima que eu já vi até agora.
Mcottle # 14/18
bem. Menciono tabelas de arquivamento porque elas abafam as restrições do FK. é uma fenda na armadura. dividir por microsserviço não é do tamanho de uma coisa db, é manter seus microsserviços separados. Se você não pode desligar um e jogá-lo fora, não é realmente um microsserviço. re ponto 2. o OP menciona MF, eles poderiam literalmente contratá-lo / a ThoughtWorks para vir e dizer-lhes para dividir o db
Ewan
"Se você não pode desligar um e jogá-lo fora, não é realmente um microsserviço." Isso vale para o serviço em si, mas não necessariamente um argumento para o motivo pelo qual o serviço precisa de seu próprio banco de dados. Por fim, o próprio banco de dados é um serviço sendo usado pelo microsserviço. O microsserviço realmente não sabe nem se importa se os dados utilizados estão em um banco de dados separado ou em um banco de dados compartilhado. Você pode girar para cima ou para baixo cópias deste microsserviço e nada realmente muda.
Chris Pratt
O melhor argumento para o banco de dados por serviço é os limites de conexão. Não é incomum o uso do pool de conexões, portanto, cada microsserviço já requer várias conexões com a instância do banco de dados, então você pode ter várias instâncias de cada um desses microsserviços, cada um com seus próprios pools. Eventualmente, as coisas podem vir à tona, onde você simplesmente esgotou a capacidade do banco de dados de lidar com todas as conexões que ele está obtendo.
Chris Pratt