- O GOV.UK Notify reduziu o downtime para cerca de 11 segundos ao migrar um banco de dados PostgreSQL 11 de 400 GB para o PostgreSQL 15 no RDS, em sua própria conta AWS, em razão do encerramento do PaaS
- No banco de destino, foram criadas primeiro apenas as tabelas; os dados foram copiados com DMS full load e os índices e restrições de chave foram aplicados depois, reduzindo o tempo de carga em larga escala
- O banco de origem tinha cerca de 1,3 bilhão de linhas, 85 tabelas, 185 índices e 120 chaves estrangeiras; em dias úteis, recebia cerca de 1.000 inserções/atualizações por segundo e um volume semelhante de leituras
- Por causa da restrição de que uma nova implantação da aplicação levava cerca de 5 minutos, a equipe preparou antecipadamente as mesmas credenciais e uma troca por pesos no DNS do Route53 para reduzir o tempo real de transição
- O DMS foi escolhido por ser fácil de obter suporte do PaaS e da AWS, mas fica a possibilidade de que alternativas como pglogical fossem mais simples para migrações entre PostgreSQL
Migração do banco de dados do Notify em razão do encerramento do PaaS
- O GOV.UK Notify estava hospedado no GOV.UK Platform as a Service e, com o encerramento do PaaS, precisou mover toda a infraestrutura para sua própria conta AWS
- O banco de dados do Notify era um AWS RDS PostgreSQL na conta AWS do PaaS e armazenava desde dados de envio de notificações até o conteúdo de centenas de milhares de modelos usados pelas equipes de serviço
- Na migração, o banco de dados existente foi tratado como source database, e o novo banco como target database
- O principal desafio não era criar um novo banco PostgreSQL, mas minimizar o downtime durante a migração de todos os dados e a troca do destino de conexão da aplicação
Escala do banco de dados de origem e restrições do serviço
- O banco de dados de origem era um PostgreSQL 11 de cerca de 400 GB
- Cerca de 1,3 bilhão de linhas
- 85 tabelas
- 185 índices
- 120 chaves estrangeiras
- Em um dia útil típico, ocorriam cerca de 1.000 inserções ou atualizações por segundo, além de um número semelhante de leituras
- O GOV.UK Notify envia diariamente milhões de notificações importantes e sensíveis ao tempo, como alertas de enchente e atualizações sobre o andamento de solicitações de passaporte
- Como todo envio de notificação exige acesso ao banco de dados, era necessário manter o tempo de indisponibilidade do serviço curto
Estrutura de carga inicial e replicação contínua criada com DMS
- A equipe do PaaS ofereceu uma abordagem de migração de banco de dados usando o AWS Database Migration Service
- O DMS é responsável por mover dados do banco de dados de origem para o banco de dados de destino e pode ser executado na conta AWS de origem ou de destino
- A tarefa do DMS é dividida em duas etapas
- full load: copia, tabela por tabela, todos os dados existentes até um determinado momento
- replicação contínua: reproduz no banco de destino as novas transações do banco de origem para manter os dois bancos sincronizados
- A equipe do Notify ficou responsável por trocar a aplicação para usar o banco de destino em vez do banco de origem
Preparação do banco de destino e full load
- A instância do DMS foi criada na conta AWS de origem
- Como a equipe do PaaS já havia configurado uma instância do DMS dentro da conta, foi possível preparar tudo rapidamente
- A instância do DMS precisava de credenciais para se conectar aos bancos PostgreSQL de origem e de destino
- Como a instância do DMS e o banco de dados de destino estavam em VPCs diferentes, foi configurado VPC peering para que o tráfego do DMS na VPC do PaaS fosse roteado para a VPC própria sem passar pela internet pública
- A instância RDS de destino foi criada na própria conta AWS e, como o fim do suporte ao PostgreSQL 11 estava se aproximando, o novo banco foi configurado com PostgreSQL 15
- O esquema do banco de dados de origem foi exportado com
pg_dumppara criar um arquivo SQL de recriação do esquema; inicialmente, apenas as declarações de tabela foram aplicadas ao banco de destino - As chaves estrangeiras não foram aplicadas no momento do full load
- Isso porque o DMS full load não copia os dados respeitando a ordem das restrições de chave estrangeira
- As chaves primárias e os índices também não foram criados antes do full load
- Cada inserção exigiria atualização de índices, o que poderia aumentar muito o tempo total ao inserir bilhões de linhas
- Era mais rápido copiar todos os dados primeiro e adicionar os índices depois
- A tarefa de full load copiou os dados existentes no momento em que o botão de início foi pressionado
- Novos dados ou atualizações ocorridos depois disso não foram incluídos no full load
- A conclusão do full load levou cerca de 6 horas
- Após o full load, o restante do arquivo de esquema foi aplicado para adicionar índices e restrições de chave, e essa etapa levou cerca de 3 horas
Manutenção da replicação por 10 dias e troca de tráfego
- Depois do full load, o banco de dados de destino correspondia aos dados do banco de origem no momento de início do full load, mas inserções, atualizações e exclusões continuaram ocorrendo no banco de origem depois disso
- A replicação contínua do DMS, ou seja, a tarefa de change data capture, foi iniciada para enviar ao banco de destino as transações do transaction log do banco de origem ocorridas após o início do full load
- O processo de replicação levou algumas horas para alcançar o ritmo, e depois a latência de replicação do DMS foi monitorada para confirmar o estado de sincronização
- A replicação do DMS rodou em segundo plano por cerca de 10 dias, mantendo os dois bancos sincronizados até o momento de migração de tráfego previamente comunicado aos usuários
- O procedimento de troca de tráfego foi escrito antecipadamente como scripts em Python
- Interromper o tráfego da aplicação para o banco de dados de origem
- Confirmar que a replicação havia alcançado completamente a origem
- Permitir que a aplicação se conectasse ao banco de dados de destino
- Era preciso evitar um estado em que algumas aplicações usassem o banco de origem e outras o banco de destino
- Como mudanças feitas no banco de destino não seriam refletidas no banco de origem, os usuários poderiam ver dados inconsistentes
- Os scripts foram criados para serem mais explícitos, repetíveis e rápidos do que operações manuais, e foram usados pelo menos 40 vezes em testes e ensaios prévios
- A meta de downtime era menos de 5 minutos, e a migração foi agendada para um sábado à noite relativamente tranquilo, evitando o meio da madrugada
Troca de DNS que gerou downtime de 11 segundos
- A interrupção do tráfego para o banco de dados de origem foi feita chamando
pg_terminate_backendnas conexões da aplicação, e levou menos de 1 segundo - A senha do usuário PostgreSQL também foi alterada para impedir que a aplicação se reconectasse ao banco de origem, causando erro de autenticação em novas tentativas de conexão
- O DMS criou uma tabela de estado de replicação no banco de destino e a atualizava a cada minuto; o script de migração usava essa tabela para verificar o atraso entre origem e destino
- Como salvaguarda adicional, depois que a aplicação parava de acessar o banco de origem, o script gravava um único registro no banco de origem e esperava até que ele chegasse ao banco de destino
- As informações de conexão da aplicação ao banco de dados eram fornecidas pela variável de ambiente
SQLALCHEMY_DATABASE_URI- O formato existente era
postgresql://...@random-identifier.eu-west-1.rds.amazonaws.com:5432, incluindo nome de usuário, senha e localização do RDS - Para alterar a localização do banco de dados ou as credenciais, era necessário reimplantar a aplicação, e a reimplantação levava cerca de 5 minutos
- O formato existente era
- Para evitar downtime adicional causado pela reimplantação, duas medidas foram preparadas antes da migração
- Criar usuários com o mesmo nome de usuário e senha nos bancos de dados de origem e de destino
- Criar um registro DNS
database.notifications.service.gov.ukno AWS Route53 e definir o TTL como 1 segundo
- Inicialmente, o registro DNS foi configurado com peso de 100% para a origem e 0% para o destino
- A URI da aplicação foi alterada antecipadamente para usar o nome de usuário, a senha e o novo nome de domínio comuns
- No momento da troca real, o script alterou os pesos de DNS da AWS para 100% no destino e esperou o TTL de 1 segundo expirar
- No momento da troca, na noite de sábado, 4 de novembro de 2023, o atraso entre o banco de destino e o banco de origem era de apenas alguns segundos
- Como resultado da execução do script de migração, a aplicação deixou de acessar o banco de origem e passou a usar o novo banco de destino, com downtime de cerca de 11 segundos
Avaliação da escolha do DMS e próximos passos
- O DMS foi escolhido porque tinha bom suporte no GOV.UK PaaS e também permitia obter suporte da AWS
- Caso façam novamente uma migração entre bancos PostgreSQL no futuro, a equipe pretende avaliar melhor ferramentas alternativas como pglogical
- É possível que o DMS tenha adicionado mais complexidade e um processo de replicação menos familiar do que outras ferramentas
- Após a migração do banco de dados, a próxima etapa é a migração da aplicação
- A aplicação GOV.UK Notify será movida para o AWS Elastic Container Service, e o andamento do processo será compartilhado posteriormente
1 comentários
Opiniões do Hacker News
Também fizemos uma migração parecida com AWS RDS Blue-Green Deployments; o banco de dados era um pouco maior, mas o downtime foi de cerca de 20 segundos e o trabalho envolvido foi bem menor. Acho surpreendente que isso ainda não tenha sido mencionado nesta thread
Basicamente, você sobe uma nova implantação Blue/Green com as alterações desejadas, e enquanto a configuração blue existente continua atendendo ao tráfego, a AWS sincroniza a implantação green usando replicação lógica
No green, desde que você não faça escritas, é possível modificar, testar e até fazer teste de carga; as escritas continuam indo para o blue em produção e depois são replicadas para o green
Quando estiver pronto, você executa o comando de switch, e a AWS cuida de verificar a sincronização, interromper escritas e conexões, esperar a replicação alcançar, renomear os bancos de dados e retomar conexões e escritas
No nosso caso, o downtime foi menor que 20 segundos, e toda a configuração, incluindo o primary e várias réplicas de leitura, foi alternada sem problemas. A AWS também troca a URL do banco de dados, então não foi necessário mudar a configuração; o green vira o blue, o blue antigo vira old blue, e você pode apagá-lo depois
Recomendo muito, mas há limitações, como talvez não funcionar em casos de migração entre contas: https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/blue-...
É preciso ler e reler a documentação, especialmente as limitações. Vale a pena executar testes com carga no ambiente de desenvolvimento e repetir também em staging
Ou simplesmente mandar para produção no modo YOLO e talvez tudo fique bem
Mas aprendi do jeito difícil que o RDS Blue/Green não serve para qualquer alteração arbitrária. No nosso caso, descobrimos que dava para usá-lo apenas para subir a versão do engine, não para descer
No MySQL 8.0, uma stored procedure falhava muito de vez em quando, então avaliamos a opção de voltar para o 5.7, mas isso não era possível
O trade-off foi que algumas requisições ficaram mais lentas por alguns segundos
A combinação de DNS Groups com retry é um mecanismo bem útil para esse tipo de operação
Ferramenta usada: https://github.com/shayonj/pg_easy_replicate
Há várias formas de “pausar” consultas Postgres de entrada; por exemplo, usando pgbouncer para atrasá-las, em vez de fazê-las falhar, até que a replicação alcance, e então continuar no novo banco de dados
Se algo der errado e a replicação não conseguir alcançar, é só desfazer a pausa e deixar essas consultas executarem no banco de dados antigo
Isso transforma 11 segundos de downtime em 0 a 11 segundos de tempo adicional de carregamento de página. Mais importante: entre milhares de usuários do banco de dados que nunca viram uma consulta falhar, se houver caminhos de tratamento de erro com bugs ou jobs em lote inteiros que quebram por causa de uma única consulta com falha, o dano colateral fica muito menor
É interessante comparar com https://knock.app/blog/zero-downtime-postgres-upgrades. A discussão relacionada aconteceu em https://news.ycombinator.com/item?id=38616181
Grande parte da discussão na época acabava em “não é complexo demais só para evitar alguns minutos de downtime?”. Este caso parece uma prova disso, e dá a impressão de que basta usar o AWS Data Migration Service, trocar o registro DNS para passar para produção e aceitar 11 segundos de downtime
Alguns tipos de dados especiais talvez nem sejam tratados. Depois da migração, também é preciso atualizar as sequências; caso contrário, podem ocorrer erros de chave primária duplicada
Se não houver uma chave primária adequada, também podem surgir problemas porque ele nem sempre copia a linha inteira de uma vez
Se os bancos de dados estiverem na mesma conta AWS e você puder aceitar 4 a 5 minutos de downtime, é bem provável que uma replicação em nível de hardware usando banco de dados global ou snapshots seja mais fácil
Recentemente migrei um banco de dados PostgreSQL de 3 TB auto-hospedado da versão 12 para a 16, e também troquei do Ubuntu 18 para o Ubuntu 22. Ao mesmo tempo, precisei atualizar várias extensões, especialmente o Timescale, para o qual não havia uma versão compatível que satisfizesse todas as combinações
Fizemos isso atualizando uma réplica somente leitura: começamos com PG12, Ubuntu 18 e TS2.9, e primeiro criamos uma réplica somente leitura no Ubuntu 22 mantendo PG12 e TS2.9
Depois entramos em modo de manutenção, paramos todos os serviços, destacamos a réplica somente leitura e então, no Ubuntu 22, atualizamos o PG12 para PG15 mantendo o TS2.9
Em seguida, atualizamos de TS2.9 para TS2.13 com PG15, e por fim atualizamos de PG15 para PG16 no Ubuntu 22 mantendo o TS2.13
Por último, reconectamos os serviços ao novo servidor de banco de dados, retomamos todos os serviços e saímos do modo de manutenção
Todas as etapas de upgrade do banco de dados foram bem testadas e automatizadas com Ansible, mas surgiu um problema que não apareceu nos testes, aumentando o downtime para cerca de 30 minutos. Para o nosso uso, foi perfeitamente aceitável
Se tivéssemos usado replicação lógica, talvez pudéssemos ter reduzido os problemas inesperados no último momento, e pretendemos avaliar essa abordagem no próximo ciclo de upgrade
Também avaliamos replicação lógica para reduzir o downtime do upgrade, mas, como o schema do banco de dados e comandos DDL não são replicados, pareceu não ser recomendável quando o Timescale está envolvido
As mudanças de schema subjacentes que o Timescale precisa fazer internamente provavelmente são, em grande parte, uma função do tamanho dos chunks das hypertables e do padrão de escritas recebidas, então talvez fosse possível planejar ou sincronizar isso, mas achamos que a complexidade e o risco potenciais eram grandes demais em comparação com abrir uma janela curta de manutenção enquanto o pg_upgrade terminava
O inimigo dessas migrações com baixo downtime ou sem downtime são as consultas de longa duração
Por exemplo, se houver uma única consulta
updateque leva 30 minutos, você precisa matar essa consulta e fazer rollback, ou aceitar 30 minutos de perda de disponibilidadeAté onde sei, não há como migrar consultas em andamento
statement_timeouté sua amigaSe houver transações extremamente longas, é bem provável que você consiga fazer a troca evitando o período em que elas rodam. Espero que sejam resultado de algo como jobs agendados, e não ocorrências aleatórias
Combinando limites de tempo de transação, configuração de failover — por exemplo, forçando a falha do primary antigo — e algo como pgbouncer, dá para controlar com bastante precisão o período de lentidão em vez de downtime
Sinceramente, o que me preocupa mais é se toda a stack e os servidores DNS de cache externos dos quais ela depende respeitam corretamente o TTL do DNS
Mas, em geral, evitar uns 10 segundos de downtime em uma aplicação não é crítico, então faz sentido escolher a solução que for mais simples para você
updatede 30 minutos não tenha sido escrita de forma extremamente ineficiente ou não seja uma migração pontual de dados em grande escalaClaro que o primeiro caso existe bastante no mundo real. Já me diverti bastante transformando tarefas de minutos ou horas em tarefas de milissegundos
Que tipo de dados e que tipo de pessoas lidam com essas escritas de banco de dados a ponto de dependerem do motor do banco de dados trabalhando por tanto tempo, em vez de quebrar melhor isso em uma fila na camada superior?
A parte estranha é que eles criaram um registro DNS no AWS Route53 para
database.notifications.service.gov.ukcom TTL de 1 segundo e, depois que o script de migração alterou o peso do DNS da AWS para enviar 100% ao local do banco de dados de destino, esperaram 1 segundo até o TTL expirar.Eles disseram que, assim, na próxima vez que a aplicação tentasse consultar o banco de dados, ela consultaria o banco de destino. Isso quer dizer que o ORM deles ou o comportamento padrão do Python faz uma consulta DNS a cada query e fica bloqueado nisso?
Quer dizer que eles nem cacheiam o endereço resolvido por algum tempo, nem usam pooling ou reutilização de conexões?
getaddrinfoougethostnamedo SO. O Python praticamente não reimplementa chamadas de nível de sistema, então depende da configuração do sistema.Se o TTL de 1 segundo foi respeitado, teria ficado em cache por 1 segundo, mas não é raro que bibliotecas de resolução DNS — e especialmente servidores DNS com cache — não respeitem totalmente o TTL. Sinceramente, isso também poderia explicar parte do downtime observado.
Legal. Acabamos de migrar no RDS três clusters Postgres com cerca de 2 TB de dados e 8 bancos de dados, de Postgres 14 para 16. Ficamos fora do ar das 00:00 às 04:00.
Primeiro ativamos um “modo de manutenção”, que era um site substituto bem leve rodando no Cloudflare Workers, e reduzimos para 0 via Terraform todas as aplicações que escrevem no DB.
Na interface web da AWS, clicamos no botão de upgrade para fazer 14→15 com
pg_upgradee esperamos terminar; depois clicamos de novo para fazer 15→16.Esperamos até o banco de dados começar a aceitar conexões; parecia que ele aceitava conexões mesmo antes de aparecer como ready, e a AWS parecia fazer algo além do
pg_upgrade.Depois iniciamos
VACUUM ANALYZE; REINDEX DATABASE CONCURRENTLY. A intenção era evitar problemas de performance entre versões e aproveitar as melhorias de performance da nova versão.Começamos a subir as aplicações de novo e, depois de esperar que todas tivessem alguns contêineres rodando, passamos a receber tráfego, desligamos o site de manutenção e fomos dormir.
O
REINDEX CONCURRENTLYcontinuou rodando por mais 18 horas no maior DB, mas não bloqueou nada.Na próxima vez, pretendemos usar AWS Blue/Green deployments para evitar downtime. Desta vez não pudemos usar, porque não estávamos na versão menor mínima do 14 compatível com Blue/Green, que é a 14.9.
Se eu fosse fazer por conta própria, sem pagar o custo da AWS, montaria meu próprio Blue/Green com replicação lógica e um balanceador de carga.
pg_upgrade --hardlinkspara um upgrade in-place.Em instâncias Postgres próprias on-premises, já fiz isso com DB de 2 TB em menos de 1 minuto.
O GOV.UK Notify faz parte de um conjunto de serviços que o GDS oferece a órgãos públicos do Reino Unido. GOV.UK Pay e GOV.UK PaaS também fazem parte, e originalmente isso era conhecido como Government As A Platform.
DMS é uma ferramenta de migração péssima. Depois de quase um mês brigando com vários problemas de migração, desistimos.
Ele não conseguia migrar tipos text e json, e o suporte da AWS também não conseguiu apresentar uma solução.
Usamos o AWS Blue/Green na fase inicial de testes, e graças a isso um upgrade praticamente sem downtime se tornou viável.
Está completamente quebrado.
É interessante, mas não entendo por que o governo usa AWS para começo de conversa. Não é uma startup hackeando para encontrar product-market fit, nem uma situação em que não previram um pico de tráfego impulsionado por marketing e precisam reagir
Eles sabem que esse serviço será necessário no longo prazo, e os padrões de uso também são bastante previsíveis
Poderiam criar uma nuvem para o setor público ou adotar uma abordagem on-premises razoável. Isso exigiria verba, coordenação e liderança técnica, mas no longo prazo economizaria uma quantia enorme para os contribuintes
A TI do setor público em geral é uma bagunça, mas sei que também há bons engenheiros trabalhando nela
Uma hora correndo atrás de discos rígidos e drivers, ou das versões disso na nuvem, como storage e Ansible, é uma hora que deixa de ser usada para construir o que os clientes precisam
Por que seria diferente para o governo?
Ninguém espera que o governo fabrique seus próprios carros; espera-se que compre da Volkswagen ou da Renault. Mesmo que o governo claramente tenha necessidades de transporte. Então não entendo por que insistem que a infraestrutura de TI seja feita internamente
E também há coisas que surgem de forma totalmente inesperada, como a pandemia. O fato de ter sido possível escalar para atender à demanda durante a pandemia foi uma das principais demonstrações de por que o setor público deve usar nuvens públicas comerciais
Recomendo assistir à apresentação de setembro sobre várias iterações do Gov.UK e migrações entre nuvens: https://youtube.com/watch?v=mpY1lxkikqM&pp=ygUOUmljaGFyZCB0b...
Pelo menos no governo britânico, por causa dos requisitos de compras, é preciso recolocar no mercado, a cada poucos anos, uma cotação baseada no uso
Por exemplo, se a Oracle Cloud custar um décimo do preço, ela provavelmente vai ganhar o contrato; então será necessário migrar para a Oracle durante a vigência do contrato e, se depois aparecer outra nuvem mais barata, talvez seja preciso migrar de novo
Foi uma das piores coisas que já vi na vida. E olha que já trabalhei bastante com legados em VB.NET, Web Forms, SharePoint antigo, Basic e até aplicações inteiras que eram um enorme monte de stored procedures
AWS, Azure e Google Cloud pelo menos foram feitas pensando no usuário final, isto é, o desenvolvedor. Já a nuvem governamental foi projetada e construída pelo licitante de menor preço, cujo objetivo principal era cortar custos em todos os lugares possíveis
Por outro lado, também já conheci profissionais de infraestrutura e operações realmente excelentes que operavam datacenters internos de instituições de saúde do governo alemão. O problema ali não era a tecnologia nem as pessoas, mas 100% a gestão e os processos, que tentavam virar gargalo em toda interação entre a equipe de infraestrutura e a equipe de engenharia