Na primeira parte do meu
artigo, falei sobre como criamos a primeira versão do sistema de patches no Badoo. Em resumo, precisávamos encontrar uma maneira de corrigir erros graves diretamente na produção, acessíveis a todos os desenvolvedores. No entanto, a primeira versão não deixou de ter suas desvantagens: usamos um método de layout peculiar, que não garantiu a atomicidade do layout dos patches e a consistência do código.
Nesta parte do artigo, falarei sobre uma nova maneira de definir o código que criamos ao tentar resolver nossos problemas e como nosso sistema de patches foi transformado com ele.
Imagem: fonteSolução Universal - Kit de Implantação Multiversional
Após outra revisão do nosso sistema Jura, o
youROCK Nasretdinov afirmou que tem uma idéia de como resolver todos os nossos problemas. Tudo o que ele pediu foi muito tempo para refazer o sistema de layout. Foi assim que surgiu o conceito do Multiversional Deployment Kit, ou, em pessoas comuns, o MDK (Jura o comparou com outras formas de layout de código em seu
relatório no HighLoad ++ ).
O novo sistema foi projetado para alterar nosso procedimento de layout. Para quem não leu minha primeira parte do artigo, explicarei brevemente como é o processo de implantação: primeiro coletamos todos os arquivos necessários em um diretório, depois salvamos e entregamos o estado do diretório aos servidores.
Antes da era do MDK, usamos dispositivos de bloco (ou seja, imagens do sistema de arquivos) chamados loops para armazenar e entregar. O diretório foi copiado para um loop vazio, foi arquivado e enviado aos servidores.
No novo sistema, estamos fazendo a versão não de todo o diretório, mas de cada arquivo individualmente, para que a versão do arquivo se correlacione claramente com seu conteúdo. Para diretórios, existem mapas (mapas) - arquivos especiais nos quais as versões de todos os arquivos no diretório são registradas. Esses cartões também são versionados, e tudo se parece com isso:

Parece familiar? É assim que os objetos são organizados no Git (você pode ler sobre isso
aqui , mas isso não é necessário para a compreensão do artigo).
Para controle de versão, usamos os oito primeiros caracteres do hash MD5 (a partir de sua representação hexadecimal, para ser mais preciso), retirados do conteúdo do arquivo. Esta versão é gravada no final do nome do arquivo ou no início do nome do mapa (para que você possa distinguir o arquivo do mapa do mapa de versão gerado):
A versão do código é a versão do mapa do diretório raiz www. Para encontrar o mapa atual, temos um link simbólico (link simbólico) current.map.
Por que não usar git?Apesar do MDK emprestar parcialmente idéias do Git, elas têm algumas diferenças. O mais importante é como os arquivos são armazenados no diretório de trabalho (ou seja, nas máquinas). Se o Git armazena apenas uma versão atual, o MDK mantém todas as versões disponíveis dos arquivos. Ao mesmo tempo, apenas um link simbólico current.map aponta para a versão atual do código, que usa o carregamento automático em seu trabalho e que pode ser alterado atomicamente. Para comparação, o Git usa o
git-checkout para alterar a versão, que altera os arquivos por sua vez e não é atômica.
Construir com MDK
O MDK é necessário para salvar o estado do diretório no final da montagem. Para fazer isso, temos um local especial, que chamamos de repositório, - um repositório de todas as versões de arquivos que são valiosas para nós (ou seja, que podemos querer decompor). Quando o novo conteúdo do diretório estiver pronto, calculamos as versões de todos os arquivos nele e relatamos os que estão faltando no repositório.
Layout com MDK
Durante o layout em cada um dos servidores de recebimento, executamos um script que verifica se todos os arquivos necessários estão no servidor e solicita os que estão faltando no repositório. Só podemos mudar a versão para uma nova alterando o link simbólico current.map.
Como deve resolver nossos problemas
Supunha-se que, se apenas alguns arquivos fossem alterados na nova versão, sua montagem e layout usando o novo sistema deveriam ser pelo menos comparáveis no tempo com o layout do patch como arquivos separados. Nesse caso, para cada patch, apenas geraremos uma nova versão.
Implementação MDK
O MDK teve uma desvantagem: nas máquinas finais, o nome de cada arquivo deve ter sua versão. É isso que permite armazenar muitas versões de um arquivo em um diretório de uma só vez, mas não permite incluir o user.php no código - você deve especificar uma versão específica. Adicione a isso os vários erros que poderiam permanecer no código do sistema de layout, o novo algoritmo de layout, que era mais complicado que o antigo, e ficará claro por que decidimos implementar o novo sistema em pequenas etapas. Começamos literalmente com um ou dois servidores e gradualmente expandimos sua lista, corrigindo simultaneamente os problemas que surgem.
Como a mudança para um novo sistema deveria levar muito tempo, tivemos que pensar em como nossos patches funcionariam durante o período de transição. Naquele momento, para o layout dos patches, usamos o utilitário auto-escrito mscp, que distribuía os arquivos um por vez. Ensinamos ela a substituir os arquivos atuais nos servidores pelo MDK antecipadamente, mas não foi possível adicionar um novo arquivo a esses servidores (porque eu tive que alterar o mapa do arquivo). Eu não queria apresentar uma solução intermediária muito complicada - porque estávamos indo para um futuro brilhante, onde o mscp não é necessário. Como resultado, tive que aturar esse problema. Em geral, durante o período de transição, os desenvolvedores conseguiram sofrer, mas agora parece-nos que valeu a pena.
Não confie em ninguém
Imagem: fonteProvavelmente, a pergunta será lógica, mas haverá uma colisão de versões no MDK (ou seja, uma situação em que dois arquivos com conteúdos diferentes recebem a mesma versão)?
De fato, estamos muito bem protegidos contra esse tipo de erro.
{ }.{}
arquivos assim:
{ }.{}
, o que significa que muito mais do que oito caracteres devem corresponder para que ocorra um erro.
Mas uma vez algo deu errado. Após o próximo cálculo, notamos um número crescente de erros com o código HTTP 404 (arquivo não encontrado). Uma pequena investigação mostrou que alguns dos arquivos estáticos estavam ausentes. Descobrimos que criamos um mapa estático muito antigo e fornecemos links para arquivos que ainda não deveriam estar nos servidores. Mas de onde veio esse cartão? Na primeira parte do artigo, observei que a estática é decomposta por um processo separado, e apenas o mapa da versão sai com o código PHP. Quando geramos uma nova versão do MDK, relatamos as versões ausentes dos arquivos para o repositório, das quais nada é excluído (há muito espaço, não nos importamos). E geralmente damos o melhor para a preparação, e, portanto, o mapa da versão estática é um daqueles arquivos que são alterados com mais frequência do que outros. Tudo isso levou ao fato de que fomos confrontados com uma colisão. Depois de verificar a versão, o MDK decidiu que estava tudo bem, porque já havia um arquivo dessa versão e o expôs nos servidores. É bom que descobrimos o erro rapidamente.
Agora, além da versão, verificamos o tamanho do arquivo: se é o mesmo no repositório, provavelmente é o mesmo arquivo. Na pior das hipóteses, teremos uma história para um novo artigo.
MDK - Ladrão de Natal
Imagem: fonteE quero falar sobre outro erro, porque é pelo menos engraçado. É fácil adivinhar que tivemos um processo de limpeza de versões antigas de arquivos nos servidores de destino. Na tentativa de resolver rapidamente um dos problemas, tomamos uma decisão fatal: defina o período de limpeza para um dia (em vez de sete, como era antes). Funcionou - e o problema se foi. Nós até vivemos por um tempo.
Por volta das cinco da manhã de domingo, um telefone tocou no meu quarto e o monitor de plantão tocou: “Os scripts não funcionam para nós. Eles dizem que você sabe qual é o problema. " Para mim, soou como “No escritório, o espremedor queimava. Eles dizem que você sabe qual é o problema. " Eu só conhecia os princípios de nossa estrutura de scripts a partir de artigos e histórias, não tinha nenhuma "relação pessoal" com ele e, mais ainda, nunca o consertei. Mas entrei nos servidores para descobrir o que estava acontecendo e descobri que o problema estava realmente “do nosso lado”: simplesmente não havia código nos servidores.
Carreguei o código novamente - e funcionou. O erro, a propósito, acabou sendo primitivo: no sábado, nenhuma nova versão do MDK foi apresentada, e o script de limpeza, como se viu, não fez nenhuma verificação para não excluir a versão atual. Como resultado, às cinco da manhã, ele (de acordo com a programação) excluiu o código de todos os servidores. Após essa história, percebemos que, com as configurações antigas, ela acontecia nos feriados de 7 dias, por exemplo, nos feriados de Ano Novo, apenas na véspera de Natal. “Cristo nasceu - o código se foi” - por muito tempo ouvimos essa piada.
Novo sistema de correção
No final, introduzimos um novo sistema de layout - e é hora de refazer o sistema de patches. Não havia mais necessidade de mscp nem de gerar novas versões. Primeiro, mudamos o ciclo de vida do patch. Agora, depois de confirmar as alterações, ele volta ao desenvolvedor, que decide quando o patch está pronto para o cálculo. Ele clica no botão Deploy, após o qual adicionamos o patch para dominar, gerar e implantar uma nova versão do MDK. O envolvimento do desenvolvedor nesse estágio não é mais necessário.
Alcançamos uma velocidade de layout muito boa: as alterações chegam aos servidores literalmente em um minuto. Para fazer isso, no entanto, tivemos que recorrer a alguns truques: por exemplo, ainda não geramos estatísticas ou traduções - em vez disso, pegamos a versão da última compilação decomposta. Por esse motivo, mantemos uma restrição nos patches para arquivos JS e CSS.
Os experimentos
Realmente conseguimos resolver todos os problemas que tínhamos antes. Você não precisa mais pensar em como formar corretamente as alterações, o que não causará dificuldades no layout arquivo a arquivo - apenas não toque na estática e tudo funcionará.
Mas uma nova dificuldade apareceu. Anteriormente, permitíamos que os desenvolvedores publiquem suas alterações em um ou mais servidores, apenas para garantir que tudo funcione com eles. Com o novo sistema, esse recurso desapareceu, porque o mestre agora se tornou a versão atual para todos os servidores, sem exceção.
Imagem: fontePor esse motivo, um novo requisito para o sistema de patches apareceu: você precisa verificar suas alterações em um pequeno número de servidores sem adicionar alterações ao mestre. Chamamos os novos experimentos de funcionalidade.
Para o desenvolvedor, o processo é mais ou menos assim: após receber a atualização, uma nova página fica disponível na interface do sistema de patches, onde você pode selecionar os servidores nos quais deseja experimentar. Pode ser um grupo de servidores, um servidor ou qualquer combinação que nosso sistema entenda. O sistema implanta o patch e notifica o desenvolvedor. Ao mesmo tempo, um log dos erros mais recentes dos servidores afetados aparece na página.
Não limitamos os desenvolvedores de forma alguma, eles podem criar experimentos nos mesmos servidores. Um pode experimentar em um cluster de 10%, o outro em todo o cluster.
Isso se tornou possível devido ao fato de termos a "versão dos patches" que nos faltava tanto. Esta é uma versão que, em teoria, pode ser exclusiva para cada servidor. Parece uma sequência de identificadores separados por vírgulas, por exemplo, "32,45,79". Isso significa que o servidor deve ter todas as alterações do assistente e dos patches numerados 32, 45 e 79. Para cada uma dessas versões, geramos nossa própria versão do MDK. Tomamos as alterações mais recentes da ramificação principal e, em seguida, aplicamos sequencialmente cada uma das correções. Se surgir um conflito durante a geração de uma das versões, simplesmente cancelamos o experimento para o patch mais recente e notificamos o desenvolvedor.
Arquivos gerados
Desde o primeiro dia da existência do sistema de patches, fomos ao truque: nos recusamos a gerar estática para que as alterações chegassem aos servidores o mais rápido possível. Obviamente, realmente queríamos ter a oportunidade de alterar o código JS da mesma maneira que alteramos o código PHP, mas todas as tentativas de criar esse processo não tiveram êxito.
Cerca de seis meses atrás, voltamos a este problema. Objetivo: você precisa alterar a estática, mas não pode sacrificar a velocidade do layout do código PHP. O principal problema: uma montagem completa leva oito minutos. O que fazer
Você precisa se comprometer. Começamos com o fato de que o código JS não pode ser definido como parte dos experimentos. Isso economiza muito tempo: basta manter uma versão das estáticas atualizada em vez de gerar dezenas de versões diferentes para diferentes grupos de máquinas. Mas ainda faz muito tempo. O que mais você pode salvar? Não descobrimos como reduzir o tempo, mas decidimos que não haveria problema se o assembly não bloqueasse o layout do código PHP.
Começamos a gerar estática de forma assíncrona. Com as alterações nos arquivos JS ou CSS, iniciamos um processo separado que cria um novo mapa das versões estáticas. O processo de montagem do código PHP no início do trabalho verifica se há um novo mapa estático e, se houver, o pega e o expõe em todos os servidores. Você resolveu o problema? Na prática. Com essa abordagem, optamos por uma nova limitação: você não pode alterar o código JS e PHP em um patch, porque decompomos essas alterações de forma assíncrona e não podemos garantir que elas estejam nas máquinas ao mesmo tempo.
Sumário
Estamos muito satisfeitos com a atualização. Não foi fácil para nós, mas tornou nosso sistema muito mais confiável. Os desenvolvedores encontraram um aplicativo alternativo para experimentos: com eles, você pode coletar facilmente logs específicos de um par de servidores sem adicionar suas alterações ao mestre.
Ainda temos idéias para melhorar o sistema, para cuja implementação ainda não há tempo suficiente. Por exemplo, queremos refazer o processo de criação de um patch e adicionar a capacidade de alterar os arquivos JS ao mesmo tempo que o código principal para se livrar das restrições mais recentes.
Todos os dias postamos cerca de 60 patches, às vezes há várias vezes mais, por exemplo, durante o desenvolvimento de algumas funcionalidades que estão disponíveis apenas para testadores até o momento. Cerca de um terço dos patches passam por experimentos antes de serem dispostos. No total, durante a existência do sistema, tivemos cerca de 46.000 patches para o mestre.