Esta é a primeira parte do artigo em que falarei sobre como construímos o processo de trabalhar em um grande projeto de migração de banco de dados: sobre experiências seguras, planejamento de equipe e interação entre equipes. Nos artigos a seguir, falarei mais detalhadamente sobre os problemas técnicos que resolvemos: sobre dimensionamento e tolerância a falhas do PostgreSQL e testes de carga.

Por um longo tempo, o banco de dados principal no Miro (ex-RealtimeBoard) foi o Redis. Armazenamos nele todas as informações básicas: dados sobre usuários, contas, placas etc. Tudo funcionou rapidamente, mas tivemos vários problemas.
Problemas com Redis- Dependência da latência da rede. Agora, na nossa nuvem, são cerca de 20 horas em Moscou, mas quando você a aumenta, o aplicativo começa a funcionar muito lentamente.
- A falta de índices que precisamos no nível da lógica de negócios. Sua implementação independente pode complicar a lógica de negócios e levar à inconsistência dos dados.
- A complexidade do código também dificulta a manutenção da consistência dos dados.
- Intensidade de recurso de consultas com seleções.
Esses problemas, juntamente com um aumento na quantidade de dados nos servidores, causaram a migração do banco de dados.
Declaração do problema
A decisão sobre a migração foi tomada. O próximo passo é entender qual banco de dados é adequado para o nosso modelo de dados.
Realizamos um estudo para selecionar o banco de dados ideal para nós e resolvemos o PostgreSQL. Nosso modelo de dados se encaixa bem com um banco de dados relacional: o PostgreSQL possui ferramentas internas para garantir a consistência dos dados, existe um tipo JSONB e a capacidade de indexar certos campos no JSONB. Isso nos convém.
A arquitetura simplificada de nosso aplicativo tinha a seguinte aparência: existem servidores de aplicativos que acessam Redis e RiakKV através da camada de dados.
Nosso Application Server é um aplicativo Java monolítico. A lógica de negócios é escrita em uma estrutura adaptada ao NoSQL. O aplicativo possui seu próprio sistema transacional, que permite fornecer vários usuários em qualquer uma de nossas placas.
Usamos o RiakKV para armazenar dados de painéis de arquivo que não foram abertos por 7 dias.
Adicione o PostgreSQL a este esquema. Fazemos os servidores de aplicativos trabalharem com o novo banco de dados. Copie dados do Redis e RiakKV para o PostgreSQL. O problema está resolvido!
Nada complicado, mas existem nuances:- Temos 2,2 milhões de usuários registrados. Todos os dias, o Miro emprega 50 mil usuários, o pico de carga é de até 14 mil ao mesmo tempo. Os usuários não devem encontrar erros devido ao nosso trabalho, eles geralmente não devem perceber o momento de mudar para uma nova base.
- 1 TB de dados no banco de dados ou 410 milhões de objetos.
- Lançamento contínuo de novos recursos por outras equipes, cujo trabalho não devemos interferir.
Opções para resolver o problema
Enfrentamos uma escolha de duas opções para migração de dados:
- Interrompa o desenvolvimento do serviço → reescreva o código no servidor → teste a funcionalidade → inicie uma nova versão.
- Execute uma migração suave: transfira gradualmente partes do produto para um novo banco de dados, suportando o PostgreSQL e o Redis e sem interromper o desenvolvimento de novos recursos.
Interromper o desenvolvimento de um serviço é uma perda de tempo que poderíamos usar para o crescimento, o que significa perda de usuários e participação de mercado. Isso é fundamental para nós, por isso escolhemos a opção com migração suave. Apesar do fato de que, em complexidade, esse processo pode ser comparado à substituição de rodas em um carro enquanto estiver dirigindo.
Ao avaliar o trabalho, dividimos nosso produto em blocos principais: usuários, contas, quadros etc. Separadamente, foi realizado o trabalho para criar a infraestrutura do PostgreSQL. E eles colocam riscos na avaliação caso algo dê errado (do jeito que aconteceu).
Sprints e objetivos
O próximo passo é formar uma equipe de cinco pessoas para que todos se movam na velocidade certa para um objetivo comum.
Temos dois pontos: o início do trabalho na tarefa e o objetivo final. Ideal quando estamos caminhando em direção ao objetivo de maneira direta. Mas muitas vezes acontece que queremos seguir o caminho certo, mas acontece assim:

Por exemplo, devido a dificuldades e problemas que não podiam ser previstos com antecedência.
É possível uma situação em que não atingiremos a meta. Por exemplo, se formos refatorar profundamente ou reescrever o aplicativo inteiro.

Dividimos a tarefa em sprints semanais para minimizar as dificuldades descritas acima. Se a equipe repentinamente sair para o lado, ela poderá voltar rapidamente com perdas mínimas para o projeto, pois iterações curtas não permitem que você vá longe demais "de maneira errada".
Cada iteração tem seu próprio objetivo, que move a equipe para o grande resultado final.

Se uma nova tarefa aparecer durante o sprint, avaliaremos se sua implementação nos aproxima do objetivo. Sim - faça o próximo sprint ou altere as prioridades do atual, se não - não faça. Se aparecerem erros, damos a eles alta prioridade e os corrigimos rapidamente.
Acontece que os desenvolvedores dentro de um sprint devem executar tarefas em uma sequência estritamente definida. Ou, por exemplo, o desenvolvedor entrega a tarefa finalizada ao engenheiro de controle de qualidade para testes urgentes. No estágio de planejamento, tentamos criar relacionamentos semelhantes entre as tarefas para cada membro da equipe. Isso permite que toda a equipe veja quem fará o quê e quando, sem esquecer a dependência dos outros.
A equipe tem sincronizações diárias e semanais. Todas as manhãs, discutimos quem, o que e em que prioridade fará hoje. Após cada sprint, sincronizamos um com o outro para garantir que todos estejam se movendo na direção certa. Certifique-se de planejar lançamentos grandes ou complexos. Designamos desenvolvedores de plantão que, se necessário, estão presentes durante o lançamento e monitoramos se tudo está em ordem.
O planejamento e a sincronização dentro da equipe permitem envolver todos os participantes em todas as etapas do projeto. Planos e avaliações não nos chegam de cima, nós mesmos os fazemos. Isso aumenta a responsabilidade e o interesse da equipe em concluir as tarefas.
Este é um dos nossos sprints. Nós carregamos tudo no quadro Miro:

Modos e experiências seguras
Durante a migração, tivemos que garantir a operação estável do serviço em condições de combate. Para fazer isso, você precisa ter certeza de que tudo foi testado e não há erros em lugar algum. Para atingir esse objetivo, decidimos tornar nossa migração suave ainda mais suave.
A idéia era mudar gradualmente os blocos de produtos para um novo banco de dados. Para fazer isso, criamos uma sequência de modos.
No primeiro modo “Redis Read / Write”, somente o banco de dados antigo, Redis, funciona.
No segundo modo "Gravação passiva do PostgreSQL", podemos garantir que a gravação no novo banco de dados esteja correta e que os bancos de dados sejam consistentes.
O terceiro modo “Leitura / Gravação do PostgreSQL, Redis Passive Write” permite verificar a correção da leitura dos dados do PostgreSQL e ver como o novo banco de dados se comporta em condições de combate. Ao mesmo tempo, o Redis continua sendo a base principal, o que nos permitiu encontrar casos específicos de trabalho com placas que poderiam levar a erros.
No último modo "Leitura / Gravação do PostgreSQL", apenas o novo banco de dados está em execução.

O trabalho de migração pode afetar as principais funções do produto, por isso tínhamos 100% de certeza de que não quebraríamos nada e que o novo banco de dados funcionaria pelo menos tão lentamente quanto o antigo. Portanto, começamos a realizar experimentos seguros com os modos de comutação.
Começamos a alternar os modos em nossa conta corporativa, que usamos diariamente no trabalho. Depois de termos certeza de que não havia erros, começamos a alternar os modos em uma pequena seleção de usuários externos.
A linha do tempo das experiências de lançamento com os modos é a seguinte:
- Janeiro-fevereiro: Redis leitura / gravação
- Março-abril: gravação passiva do PostgreSQL
- Maio-junho: leitura / gravação do PostgreSQL, banco de dados principal - Redis
- Julho-agosto: leitura / gravação do PostgreSQL
- Setembro-dezembro: migração completa.
Se ocorrerem erros, tivemos a oportunidade de corrigi-los rapidamente, porque nós mesmos poderíamos fazer lançamentos em servidores nos quais os usuários que participavam do experimento trabalhavam. Como não dependíamos da versão principal, corrigimos os erros rapidamente e a qualquer momento.
Colaboração entre equipes
Durante a migração, geralmente nos cruzamos com equipes que lançavam novos recursos. Temos uma única base de código e, como parte de seu trabalho, as equipes podem alterar as estruturas existentes em um novo banco de dados ou criar novas. Ao mesmo tempo, podem ocorrer interseções de equipes para o desenvolvimento e retirada de novos recursos. Por exemplo, uma das equipes de produtos prometeu à equipe de marketing o lançamento de um novo recurso em uma data específica; a equipe de marketing planejou uma campanha publicitária para este período; Uma equipe de vendas está esperando por um recurso e uma campanha para começar a se comunicar com novos clientes. Acontece que todos dependem um do outro, e atrasar os prazos de uma equipe interrompe os planos da outra.
Para evitar tais situações, nós, juntamente com outras equipes, compilamos um único roteiro de compras, que foi sincronizado várias vezes por trimestre e com algumas equipes semanalmente.
Conclusões
O que aprendemos durante este projeto:
- Não tenha medo de assumir projetos complexos. Após a decomposição, avaliação e desenvolvimento de abordagens para o trabalho, projetos complexos deixam de parecer impossíveis.
- Não poupe tempo e esforço em estimativas preliminares, decomposição e planejamento. Isso ajuda a entender o problema mais profundamente antes de começar a trabalhar nele e a entender o volume e a complexidade do trabalho.
- Coloque riscos em projetos técnicos e organizacionais difíceis. No processo de trabalho, você certamente encontrará um problema que não foi levado em consideração no planejamento.
- Não migre, a menos que seja necessário.
Nos artigos a seguir, falarei mais sobre os problemas técnicos que resolvemos durante a migração.