Alcançando implantação zero de inatividade

40

Estou tentando obter zero implantações de tempo de inatividade para poder implantar menos durante o horário de folga e mais durante o horário "mais lento" - ou a qualquer momento, em teoria.

Minha configuração atual, um pouco simplificada:

  • Servidor Web A (.NET App)
  • Servidor Web B (.NET App)
  • Servidor de banco de dados (SQL Server)

Meu processo de implantação atual:

  1. "Pare" os sites nos servidores A e B da Web
  2. Atualize o esquema do banco de dados para a versão do aplicativo que está sendo implantada
  3. Atualizar servidor Web A
  4. Atualizar servidor Web B
  5. Coloque tudo de volta online

Problema atual

Isso leva a uma pequena quantidade de tempo de inatividade a cada mês - cerca de 30 minutos. Eu faço isso fora do horário comercial, portanto não é um problema enorme - mas é algo que eu gostaria de evitar.

Além disso - não há como realmente voltar '. Geralmente, não faço scripts de reversão de banco de dados - apenas atualizamos scripts.

Aproveitando o Load Balancer

Eu adoraria poder atualizar um servidor Web por vez. Tire o servidor da Web A do balanceador de carga, atualize-o, coloque-o novamente online e repita para o servidor da Web B.

O problema é o banco de dados. Cada versão do meu software precisará ser executada em uma versão diferente do banco de dados - por isso estou meio que "preso".

Solução possível

Uma solução atual que estou considerando é adotar as seguintes regras:

  • Nunca exclua uma tabela de banco de dados.
  • Nunca exclua uma coluna do banco de dados.
  • Nunca renomeie uma coluna do banco de dados.
  • Nunca reordene uma coluna.
  • Todo procedimento armazenado deve ser versionado.
    • Significado - 'spFindAllThings' se tornará 'spFindAllThings_2' quando for editado.
    • Em seguida, torna-se 'spFindAllThings_3' quando editado novamente.
    • A mesma regra se aplica às visualizações.

Embora isso pareça um pouco extremo - acho que resolve o problema. Cada versão do aplicativo atingirá o banco de dados de maneira ininterrupta. O código espera certos resultados das exibições / procedimentos armazenados - e isso mantém esse 'contrato' válido. O problema é - apenas escoa desleixado. Sei que posso limpar os procedimentos armazenados antigos depois que o aplicativo é implantado por um tempo, mas parece sujo. Além disso - depende de todos os desenvolvedores seguirem essa regra, o que acontecerá principalmente, mas imagino que alguém cometa um erro.

Finalmente - minha pergunta

  • Isso é desleixado ou hacky?
  • Alguém mais está fazendo assim?
  • Como as outras pessoas estão resolvendo esse problema?
MattW
fonte
2
Onde está o seu plano de recuperação? Como você testa se tudo funciona e não há regressões?
Deer Hunter
3
Você não precisa de "nunca": você "apenas" precisa garantir que cada duas versões adjacentes possam ser executadas simultaneamente. Isso restringe seus caminhos de atualização, mas não tão severamente quanto nunca poder alterar significativamente o esquema do banco de dados.
Joachim Sauer
Obrigado Joachim ... Eu gosto de falar em absolutos para que a idéia básica seja clara - mas você está correto, poderíamos ter uma política de compatibilidade com versões anteriores de N releases; nesse ponto, podemos remover objetos de banco de dados desnecessários.
MattW
2
Você desejará ter um plano de reversão. Um dia você vai precisar.
Thorbjørn Ravn Andersen
11
Na minha experiência, para a maioria dos sites, sua solução possível é pior do que o problema que resolve. A complexidade que será adicionada será mais cara do que você pode antecipar agora. Talvez muitas vezes mais tempo / esforço para fazer alterações e adicionar recursos. Eu só considerá-lo para sites que absolutamente não pode qualquer têm tempo de inatividade, nunca .
precisa saber é o seguinte

Respostas:

14

Essa é uma abordagem muito pragmática para atualizações de software suportadas por banco de dados. Foi descrito por Martin Fowler e Pramod Sadalage em 2003 e, posteriormente, redigido em Refactoring Databases: Evolutionary Database Design .

Percebo o que você quer dizer quando diz que parece desleixado, mas quando feito intencionalmente e com prudência, e dedicando um tempo para refatorar estruturas não utilizadas da base de código e do banco de dados quando comprovadamente não são mais usadas, é muito mais robusto do que soluções mais simples baseadas em scripts de atualização e reversão.

Mike Partridge
fonte
5

O "tempo de inatividade zero" é apenas uma das muitas razões possíveis para esse tipo de abordagem. Manter um modelo de dados compatível com versões anteriores dessa maneira ajuda a lidar com muitos problemas diferentes:

  • se você tiver muitos pacotes de software acessando seu banco de dados, não precisará verificar todos eles se uma alteração de esquema os afetar (em organizações maiores com várias equipes escrevendo programas todos acessando o mesmo banco de dados, as alterações de esquema podem se tornar muito difíceis)

  • se for necessário, você pode verificar uma versão mais antiga de um de seus programas e ela provavelmente será executada novamente em um banco de dados mais recente (desde que você não espere que o programa antigo manipule corretamente qualquer coluna mais recente)

  • importar / exportar dados arquivados para a versão atual do banco de dados é muito mais fácil

Aqui está uma regra adicional para sua lista

  • cada nova coluna deve ser NULLable ou fornecer um valor padrão significativo

(isso garante que mesmo os programas mais antigos que não conhecem as novas colunas não quebrem nada quando criarem novos registros no seu banco de dados).

Obviamente, essa abordagem tem uma desvantagem real: a qualidade do seu modelo de dados pode diminuir com o tempo. E se você tiver controle total sobre todos os aplicativos que acessam seu banco de dados e poderá refatorar todos esses aplicativos facilmente quando renomear uma coluna, por exemplo, considere refatorar as coisas de uma maneira mais limpa.

Doc Brown
fonte
4

Isso varia de uma implantação para outra.

Claro, você nunca pode excluir uma tabela ou coluna. Você nunca poderia mudar nada que quebrasse a compatibilidade da interface. Você sempre pode adicionar uma camada de abstração. Mas então você precisa versionar essa abstração e a versão o versionamento.

A pergunta que você precisa se perguntar é: cada versão muda o esquema de tal maneira que não é compatível com versões anteriores?

Se muito poucas versões alteram o esquema dessa maneira, o problema do banco de dados é mudo. Basta fazer uma implantação contínua dos servidores de aplicativos.

As duas coisas que eu vi ajudar mais na implantação de tempo de inatividade mínimo são:

  1. Busque compatibilidade com versões anteriores - pelo menos em uma única versão. Você nem sempre o alcançará, mas posso apostar que você pode alcançá-lo em 90% ou mais dos seus lançamentos, especialmente se cada lançamento for pequeno.
  2. Tenha um script de banco de dados de pré e pós-lançamento. Isso permite que você lide com mudanças de nome ou alterações de interface criando seu novo objeto antes da implantação do código do aplicativo e eliminando o antigo após a implantação do código do aplicativo. Se você adicionar uma nova coluna não anulável, poderá adicioná-la como anulável no script de pré-lançamento com um gatilho que preenche um valor padrão. Então, no seu pós-lançamento, você pode soltar o gatilho.

Esperamos que o restante de suas implantações possa ser salvo para janelas de manutenção.

Outras idéias que podem ajudar a lidar com as poucas implantações que exigem tempo de inatividade:

  • Você pode criar compatibilidade com versões anteriores no seu código? Por exemplo, existe alguma maneira de seu código suportar vários tipos de conjuntos de resultados? Se você precisar alterar uma coluna de um int para um duplo, o código do aplicativo poderá lê-lo como uma sequência e analisá-lo. É meio hacky, mas se é um código temporário para você passar pelo processo de lançamento, pode não ser o fim do mundo.
  • Os procedimentos armazenados podem ajudar a isolar o código do aplicativo das alterações no esquema. Isso só pode ir tão longe, mas ajuda um pouco.
Brandon
fonte
2

Você poderia fazer isso dessa maneira por um pouco de esforço extra.

  1. Faça backup do banco de dados fazendo uma exportação
  2. Importe o backup, mas renomeie-o com uma versão de lançamento, por exemplo, myDb_2_1
  3. Executar a liberação do banco de dados no myDB_2_1
  4. "Interrompa" o pool de aplicativos no servidor Web A ou retire-o do balanceador de carga
  5. Atualize o Servidor da Web A, execute testes de pós-implementação e reversão, se necessário
  6. A sessão sangra o Servidor da Web B e coloca o Servidor da Web A em loop
  7. Atualize o servidor Web B e, em seguida, coloque-o novamente no balanceador de carga

Naturalmente, as atualizações da Web precisariam de novas entradas de configuração para apontar para o novo esquema Db. O problema é que você está lançando uma vez por mês e é uma equipe pequena quantas mudanças de banco de dados você realmente está fazendo que não são compatíveis com versões anteriores? Se você pode controlar isso testando, poderá obter uma implantação automatizada sem tempo de inatividade ou, no máximo, apenas 5 minutos de inatividade.

LeoLambrettra
fonte
11
E se (quando) o aplicativo no Servidor A gravar em seu banco de dados após o armazenamento do backup, mas antes de você parar o Servidor A? Sempre existe uma "janela de vulnerabilidade". Essas gravações serão perdidas, o que pode não ser aceitável.
sleske