Experimente 1440 migrações de banco de dados



Imagine o Oracle DBA. Ele já tem mais de trinta anos, está um pouco acima do peso, veste um colete, possui um token de acesso secreto a todas as bases penduradas no pescoço e em um resumo de meia página das certificações aprovadas. Sábado Grande dia de lançamento. Climax. Hora de rolar as alterações no banco de dados. Ele digita sqlplus, pressiona ENTER e, em algum lugar acima da tela preta no vazio, quilômetros de comandos SQL correm. Assim como em star wars. Cinco minutos depois, está tudo pronto. Uma hora depois, o lançamento está completo. O trabalho está feito, o dia foi um sucesso. Agora você pode tomar algumas cervejas.

Outra coisa é segunda-feira. Acontece que alguns comandos não foram executados devido a erros, que, no entanto, não pararam o script em sua busca desenfreada pelo vazio preto. A tarefa já difícil de descobrir o que está quebrado é complicada por alguma pressão da liderança. Em geral, segunda-feira não deu certo.

Claro, esta é uma história fictícia. Isso nunca aconteceu com ninguém. Pelo menos, não teria acontecido se o trabalho de alteração do esquema do banco de dados tivesse sido organizado por meio de migrações.

O que é uma ferramenta de migração de banco de dados?


A idéia de gerenciar alterações no esquema do banco de dados por meio de migrações é extremamente simples:

  1. Cada alteração é emitida como um arquivo de migração separado.
  2. O arquivo de migração inclui alterações diretas e reversas.
  3. A aplicação de migrações para o banco de dados é realizada por um utilitário especial.

O exemplo mais simples de migração:
-- 20180618152059: create sequence for some_table CREATE SEQUENCE some_table_seq; --//@UNDO DROP SEQUENCE some_table_seq; 

Essa abordagem oferece muitas vantagens sobre a organização de alterações em um arquivo SQL comum. A mera ausência de conflitos de mesclagem vale a pena.

É ainda mais surpreendente que a própria abordagem tenha ganhado popularidade relativamente recentemente. Parece que a estrutura do Ruby on Rails, na qual a ferramenta de migração foi originalmente criada, foi a principal fama da abordagem, foi o final de 2005. Um pouco antes, Martin Fowler, escreveu sobre a abordagem em 2003. Provavelmente, o ponto principal é que o desenvolvimento começou a adaptar ativamente o uso do sistema de controle de versão apenas no início deste século. Em 2000, o primeiro parágrafo do teste de Joel Spolsky era "Você usa o controle de origem?" - isso sugere que nem todos usavam sistemas de controle de versão naquele momento. Mas nós estávamos distraídos.

Oito anos com MyBatis Migrations


No Wrike, começamos a usar a abordagem de mudança de banco de dados por meio de migrações em 2010, 29 de março, às doze e meia. Desde então, implementamos 1.440 migrações, contendo 6.436 mudanças diretas e 5.015 reversas. Em geral, adquirimos alguma experiência usando a ferramenta Migrações MyBatis em conjunto com o PostgreSQL .

Em suma, nunca nos arrependemos. Se acontecer que você não está usando Migrações ou algo semelhante, é hora de começar. Sim, o Pentium 4 também está desatualizado.

Mas é chato falar sobre os méritos de qualquer coisa, vamos imediatamente para as dificuldades.

Especificidades do PostgreSQL


Talvez não haja dificuldades em escrever migrações para o Postgres, exceto por duas:
  • Você não pode criar índices,
  • Você não pode adicionar colunas NOT NULL.

Não, na verdade é possível, mas não de uma maneira totalmente óbvia. Ao criar um índice, você sempre deve especificar CREATE INDEX CONCURRENTLY ; caso contrário, interromperá a produção, porque o Postgres bloqueará a tabela pela duração da criação do índice, e isso pode levar um longo tempo. Obviamente, os desenvolvedores esquecem uma vez, você sempre deve ter essa sutileza em mente. Aqui alguém poderia escrever um teste. Mas isso é apenas um pequeno inconveniente.

Criar colunas NOT NULL é mais complicado, aqui você precisa fazer uma alteração em quatro etapas:
  1. Crie uma coluna NULL (no Postgres é grátis).
  2. Defina a coluna PADRÃO para um valor.
  3. Em um loop, atualize incrementalmente os valores NULL em DEFAULT.
  4. Defina SET NOT NULL.

O maior problema aqui é o terceiro parágrafo. Os valores NULL precisam ser atualizados em partes, porque UPDATE some_table SET some_column='' WHERE some_column IS NULL ; irá bloquear a tabela, como é o caso do índice, com as mesmas consequências. E as migrações só podem executar comandos SQL; portanto, esses scripts precisam ser lançados na produção manualmente. Prazer abaixo da média. Agora, se um ciclo pudesse ser escrito em Migrações, não haveria problema. Talvez isso seja implementado através de ganchos .

Criar um índice UNIQUE e alterar uma PRIMARY KEY também requer alguma habilidade, mas essas operações são relativamente raras.

Específicos de cluster


A ferramenta de gerenciamento de migração de banco de dados é válida desde que você tenha um banco de dados. Ainda mais divertido se você tiver várias bases. Especialmente se você tiver vários tipos de bancos de dados, cada um com várias instâncias.

Como resultado, após o git pull desenvolvedor deve rolar as alterações para a primeira instância do primeiro banco de dados, depois para a segunda instância, depois para a primeira instância do segundo banco de dados e assim por diante - esse princípio. Aqui é justo escrever um utilitário para gerenciar o utilitário de gerenciamento de migração de banco de dados. Automação total.

Malabarismo de funções


Não é segredo que funções como entidades não vivem no nível de um banco de dados separado, mas no nível de todo o servidor de banco de dados, pelo menos no Postgres. Nesse caso, pode ser necessário especificar REVOKE INSERT ON some_table FROM some_role ; Ainda é possível esperar que as funções sejam pré-configuradas na produção, mas para o desenvolvimento ou preparação isso já é difícil. Ao mesmo tempo, no desenvolvimento, é claro, todos os bancos de dados existem no mesmo servidor local, portanto, você simplesmente não pode escrever CREATE ROLE na migração, e IF NOT EXISTS não há suporte. Tudo é resolvido simplesmente:
 DO $$ BEGIN IF NOT EXISTS (SELECT * FROM pg_roles WHERE rolname = 'some_role') THEN CREATE ROLE "some_role" NOLOGIN; END IF; END; $$; 

Assista! Eu pego e atiro, apanho e atiro, é tão simples.

Um pouco da realidade do desenvolvimento


Os desenvolvedores cometem erros e, mesmo nas migrações de SQL, isso acontece. Geralmente, os erros podem ser notados na revisão, mas também pode ser incomum. Se falamos de mudanças diretas, os batentes ainda não atingem a produção - há muitos estágios de verificação. Mas com as mudanças inversas, incidentes podem surgir. Para evitar erros na migração UNDO, ao testar a migração, você precisa executar não apenas ./migrate up , mas ./migrate up , depois ./migrate down e novamente ./migrate up . Isso não é nada complicado, você só precisa garantir que quarenta desenvolvedores sempre façam isso. De uma maneira boa, o utilitário pode executar essa combinação automaticamente para o ambiente do desenvolvedor.

Ambientes de teste


Se o ambiente de teste tiver vida curta: digamos que você crie um contêiner, inicialize o banco de dados e execute testes de integração, não haverá problemas. ./migrate bootstrap , depois ./migrate up , e pronto. É quando o número de migrações excede mil, esse processo pode ser adiado. É uma pena que o banco de dados seja inicializado por mais tempo do que os testes executados. Temos que nos esquivar.

Em ambientes de vida longa, ainda é mais difícil. Controle de qualidade, você sabe, eles não gostam de ver um banco de dados impecavelmente limpo quando chegam ao trabalho. Não sei por que isso é assim, mas fato é fato. Portanto, o estado das bases usadas nos testes manuais deve ser mantido em integridade. E isso nem sempre é fácil.

A sutileza é que, se a migração for aplicada ao banco de dados, o identificador da migração será gravado nele. E se o código de migração for alterado posteriormente, o banco de dados não será afetado. Se as alterações não forem críticas, o código poderá chegar à produção com sucesso. Rssynchron. Claro, isso é uma vergonha. O primeiro princípio de trabalhar com migrações nunca é alterar migrações escritas, mas sempre criar novas. Mas às vezes tenho vontade de me atrapalhar - vou mudar um pouco aqui, nada vai quebrar, porque a verdade é. Claro! Vá em frente!

Se as migrações fossem assinadas após a revisão, seria possível proibir a aplicação de rascunhos para preparação. E seria possível salvar não apenas o identificador de migração no changelog , mas também a checksum - também útil.

Volte como estava


Uma virada particularmente insidiosa acontece quando uma tarefa é cancelada: eles fizeram, fizeram e mudaram de idéia. É uma situação normal. Quando o código não for mais necessário, a ramificação deverá ser excluída. E houve migração ... e ela já está encenando ... ah, ... opa. Um bom motivo para verificar se você pode restaurar o backup do repositório. Embora lembrando que talvez tenha sido mais fácil.

Ao mesmo tempo, a migração é texto. E seria possível salvar esse texto lá, no changelog . Então, se a migração do código acabar, não importa por que motivos, ela sempre poderá ser revertida. E mesmo automaticamente.

Desfazer novamente


A seção UNDO é definitivamente necessária. Mas por que escrever? Obviamente, existem casos cativantes, mas a maioria das alterações é CREATE TABLE ou ADD COLUMN ou CREATE INDEX . Para eles, o utilitário poderia gerar operações reversas automaticamente, diretamente usando o código SQL. Claro, há uma especificidade. CREATE TABLE ${name} - esta é uma equipe tão especial que, de repente, não é padrão. Sim, e para gerar DROP TABLE ${name} , você precisa analisar a expressão até a terceira palavra. Embora, em geral, essa seja uma tarefa técnica totalmente viável. Pode estar fora da caixa.

Conclusão


Claro, acho falha. O MyBatis Migrations foi concebido como um utilitário simples e universal, minimamente ligado às especificidades dos bancos de dados. E ela é mais do que se justificar. Mas parece que algumas pequenas melhorias o tornariam muito melhor, principalmente quando usadas em longas distâncias.
-
Dmitry Mamonov / Wrike

Source: https://habr.com/ru/post/pt414441/


All Articles