Como o Dark executa código em 50 ms

Como o Dark executa código em 50 ms

Quanto mais rápido é o processo de desenvolvimento, mais rapidamente se desenvolve a empresa de tecnologia.

Infelizmente, os aplicativos modernos trabalham contra nós — nossos sistemas devem ser atualizados em tempo real, sem atrapalhar ninguém e sem causar interrupções ou paralisações. A implantação em tais sistemas torna-se uma tarefa complexa e exige pipelines complexos de entrega contínua, mesmo em equipes pequenas.

Esses pipelines geralmente têm um escopo limitado, funcionam lentamente e não são muito confiáveis. Os desenvolvedores devem criá-los manualmente primeiro e depois gerenciá-los, e as empresas costumam contratar equipes inteiras de DevOps para isso.

A velocidade desses pipelines determina a velocidade de desenvolvimento. Para as melhores equipes, a implantação leva de 5 a 10 minutos, mas geralmente tudo demora muito mais, e uma única implantação pode levar várias horas.

No Dark, isso leva 50 ms. Cinquenta. Milissegundos.. Dark — é uma solução abrangente com uma linguagem de programação, um editor e infraestrutura, criada especificamente para entrega contínua, e todos os aspectos do Dark, incluindo a própria linguagem, são construídos com a visão de implantação instantânea e segura.

Por que os pipelines de entrega contínua são tão lentos?

Suponha que temos um aplicativo web em Python e já criamos um pipeline de entrega contínua notável e moderno. Para um desenvolvedor que está diariamente ocupado com este projeto, implantar uma pequena alteração parecerá mais ou menos assim:

Fazendo as alterações.

  • Criando uma nova branch no git.
  • Fazendo alterações por trás do interruptor de recursos.
  • Testes unitários para verificar as alterações com e sem o interruptor de recursos.

Pull request.

  • Commit das alterações.
  • Enviando alterações para o repositório remoto no GitHub.
  • Pull request.
  • A construção do CI é executada automaticamente em segundo plano.
  • Revisão de código.
  • Mais algumas revisões, se necessário.
  • Mesclando as alterações com o master do git.

O CI é executado no master.

  • Instalando dependências de frontend via npm.
  • Construindo e otimizando recursos HTML+CSS+JS.
  • Executando testes unitários e funcionais no frontend.
  • Instalando dependências de Python do PyPI.
  • Executando testes unitários e funcionais no backend.
  • Testando integração em ambas as extremidades.
  • Enviando recursos de frontend para o CDN.
  • Construindo o container para o programa Python.
  • Envio do contêiner para o registro
  • Atualização do manifesto do Kubernetes

Substituição do código antigo pelo novo

  • O Kubernetes inicia várias instâncias do novo contêiner
  • O Kubernetes aguarda para que as instâncias fiquem operacionais
  • O Kubernetes adiciona as instâncias ao balanceador de carga HTTP
  • O Kubernetes espera até que as instâncias antigas deixem de ser utilizadas
  • O Kubernetes interrompe as instâncias antigas
  • O Kubernetes repete essas operações até que todas as instâncias antigas sejam substituídas

Ativação do novo recurso

  • O novo código é ativado apenas para si mesmo, para garantir que tudo esteja funcionando corretamente
  • O novo código é ativado para 10% dos usuários, monitorando métricas operacionais e de negócios
  • O novo código é ativado para 50% dos usuários, monitorando métricas operacionais e de negócios
  • O novo código é ativado para 100% dos usuários, monitorando métricas operacionais e de negócios
  • Finalmente, você repete todo o procedimento para remover o código antigo e o recurso

O processo depende das ferramentas, da linguagem e do uso de arquiteturas orientadas a serviços, mas, em geral, se parece com isso. Não mencionei implantações com migrações de banco de dados, pois isso requer um planejamento cuidadoso, mas abaixo explicarei como a Dark lida com isso.

Há muitos componentes, e muitos deles podem facilmente causar lentidão, falhas, competição temporária ou derrubar o sistema operacional.

E como esses pipelines quase sempre são criados para casos específicos, é difícil confiar neles. Muitas vezes, há dias em que o código não pode ser implantado porque há problemas no Dockerfile, uma das dezenas de serviços falhou ou o especialista necessário está de férias.

Pior ainda, muitos desses passos não fazem nada útil. Eles eram necessários antes, quando implantávamos o código diretamente para os usuários, mas agora temos recursos para o novo código, e esses processos foram separados. Como resultado, a etapa que implanta o código (o antigo sendo substituído pelo novo) agora se tornou apenas um risco desnecessário.

Claro, este é um pipeline muito bem pensado. A equipe que o criou não hesitou em investir tempo e dinheiro para uma rápida implementação. Normalmente, os pipelines de implantação são muito mais lentos e menos confiáveis.

Implementação de entrega contínua na Dark

A entrega contínua é tão importante para a Dark que, desde o início, miramos em um tempo de resposta inferior a um segundo. Revisamos todas as etapas do pipeline para eliminar o que era excessivo e refinamos o restante. Veja como removemos etapas.

Jesse Frazelle (Jessie Frazelle) criou a nova palavra deployless (sem necessidade de implantação) na conferência Future of Software Development em Reykjavik.

Decidimos imediatamente que a Dark seria baseada no conceito de 'deployless' (obrigado Jesse Frazelle pelo neologismo). Deployless significa que qualquer código é implantado instantaneamente e está pronto para uso em produção. Claro, não ignoramos código com erros ou incompleto (descreverei os princípios de segurança abaixo).

Durante a demonstração da Dark, muitas vezes nos perguntavam como conseguimos acelerar tanto a implantação. Uma pergunta estranha. As pessoas provavelmente pensam que desenvolvemos alguma supertecnologia que compara código, compila, empacota em contêineres, inicia máquinas virtuais, inicia contêineres a frio e tudo mais em 50 ms. Isso é improvável. Mas criamos um mecanismo de implantação especial que não precisa de tudo isso.

A Dark executa interpretadores na nuvem. Suponha que você esteja escrevendo código em uma função ou manipulador HTTP ou de eventos. Enviamos a diferença para a árvore de sintaxe abstrata (a implementação do código que nossos editores e servidores usam internamente) para nossos servidores e, em seguida, executamos esse código quando as solicitações chegam. Assim, a implantação parece apenas uma simples entrada de banco de dados — instantânea e elementar. A implantação ocorre tão rapidamente porque envolve o mínimo possível.

No futuro, planejamos transformar a Dark em um compilador de infraestrutura que criará e executará a infraestrutura ideal para alta performance e confiabilidade de aplicativos. A implantação instantânea, é claro, não irá a lugar algum.

Implantação segura

Editor estruturado

O código na Dark é escrito no editor Dark. O editor estruturado não permite erros sintáticos. Essencialmente, na Dark não há mesmo um analisador. Enquanto você digita, trabalhamos diretamente com a árvore de sintaxe abstrata (AST), assim como Paredit, Sketch-n-Sketch, Tofu, Prune e MPS.

Qualquer código incompleto na Dark tem uma semântica de execução válida, semelhante a typed holes em Hazel. Por exemplo, se você alterar a chamada de uma função, nós armazenamos a função antiga até que a nova se torne utilizável.

Cada programa em Dark tem seu próprio propósito, portanto, um código não finalizado não impede o funcionamento do código finalizado.

Modos de Edição

Você escreve código em Dark em duas situações. Primeira: você está escrevendo um código novo e é o único usuário. Por exemplo, ele está no REPL e outros usuários nunca terão acesso a ele, ou é uma nova rota HTTP à qual você não se refere em nenhum lugar. Aqui você pode trabalhar sem nenhuma precaução, e é assim que você está operando no ambiente de desenvolvimento.

Segunda situação: o código já está em uso. Se há tráfego passando pelo código (funções, manipuladores de eventos, bancos de dados, etc.), precisa-se ter cuidado. Para isso, bloqueamos todo o código em uso e exigimos o uso de ferramentas mais estruturadas para sua edição. Sobre as ferramentas estruturadas, falarei a seguir: interruptores de funções para manipuladores HTTP e de eventos, uma plataforma poderosa de migração para bancos de dados e um novo método de controle de versão para funções e tipos.

Interruptores de Funções

Uma das maneiras de eliminar complexidade desnecessária em Dark é resolver vários problemas com uma única solução. Interruptores de funções realizam diversas tarefas: substituição do ambiente local de desenvolvimento, ramificações Git, implantação de código e, claro, o tradicional lançamento lento e controlado de novo código.

A criação e a implantação de um interruptor de função são realizadas em nosso editor em uma única operação. Ele cria um espaço vazio para o novo código e fornece controles de acesso ao código antigo e novo, além de botões e comandos para uma transição gradual para o novo código ou sua exclusão.

Interruptores de funções são integrados à linguagem Dark, e mesmo interruptores não finalizados realizam sua tarefa — se a condição no interruptor não for atendida, o código antigo bloqueado será executado.

Ambiente de desenvolvimento

Os switches de funcionalidades substituem o ambiente de desenvolvimento local. Hoje, as equipes têm dificuldade em garantir que todos estejam usando as mesmas versões de ferramentas e bibliotecas (ferramentas de formatação de código, linters, gerenciadores de pacotes, compiladores, pré-processadores, ferramentas de teste, etc.). Com o Dark, não é necessário instalar dependências localmente, gerenciar uma instalação local do Docker ou tomar outras medidas para garantir pelo menos uma certa equivalência entre o desenvolvimento e a produção. Considerando que tal equivalência é impossível, não vamos nem mesmo fingir que estamos buscando isso.

Em vez de criar um ambiente local clonado, os switches no Dark criam uma nova sandbox em produção que substitui o ambiente de desenvolvimento. No futuro, também planejamos criar sandboxes para outras partes do aplicativo (como clones instantâneos do banco de dados), embora isso ainda não pareça tão importante.

Branches e implantações

Atualmente, existem várias maneiras de introduzir novo código nos sistemas: branches git, etapas de implantação e switches de funcionalidades. Eles resolvem um problema em diferentes partes do fluxo de trabalho: o git — nas etapas antes da implantação, a implantação — no momento da transição do código antigo para o novo, e os switches de funcionalidades — para lançamentos controlados do novo código.

A maneira mais eficaz é através dos switches de funcionalidades (que também são a mais simples de entender e usar). Com eles, é possível abrir mão dos outros dois métodos. Especialmente é útil eliminar a etapa de implantação — se já estamos usando switches de funcionalidades para ativar o código, então o passo de transição dos servidores para o novo código apenas cria riscos desnecessários.

Git é difícil de usar, especialmente para iniciantes, e isso limita bastante, mas ele tem branches convenientes. Nós suavizamos muitas das desvantagens do git. O Dark é editado em tempo real e fornece a capacidade de colaboração no estilo do Google Docs, para que não seja necessário enviar código e para que o rebase e merge sejam feitos com menos frequência.

Os switches de funções são a base para uma implementação segura. Juntamente com as implementações instantâneas, eles permitem que você teste rapidamente conceitos em pequenos fragmentos com baixo risco, em vez de aplicar uma grande mudança que pode derrubar o sistema.

Versionamento

Para alterar funções e tipos, usamos versionamento. Se você deseja modificar uma função, o Dark cria uma nova versão dessa função. Depois, você pode chamar essa versão com um switch no manipulador HTTP ou de eventos. (Se a função estiver profundamente na árvore de chamadas, uma nova versão de cada função será criada ao longo do caminho. Pode parecer excessivo, mas as funções não interferem se você não as utilizar, então você nem vai notar.)

Pelos mesmos motivos, também versionamos tipos. Já falamos sobre nosso sistema de tipos em detalhes. no post anterior.

Graças ao versionamento de funções e tipos, você pode fazer alterações no aplicativo gradualmente. É possível verificar se cada manipulador individual funciona com a nova versão, sem precisar fazer todas as mudanças no aplicativo de uma só vez (mas temos ferramentas para fazer isso rapidamente, se você desejar).

Isso é muito mais seguro do que um deploy completo de tudo de uma vez, como acontece atualmente.

Novas versões de pacotes e da biblioteca padrão.

Quando você atualiza um pacote no Dark, não substituímos imediatamente o uso de cada função ou tipo em toda a base de código. Isso não é seguro. O código continua usando a mesma versão que estava usando, e você atualiza o uso de funções e tipos para a nova versão para cada caso específico utilizando switches.

Como o Dark executa código em 50 ms
Captura de tela de parte do processo automático no Dark, mostrando duas versões da função Dict::get. Dict::get_v0 retornava o tipo Any (do qual estamos nos afastando), enquanto Dict::get_v1 retorna o tipo Option.

Frequentemente, fornecemos uma nova função na biblioteca padrão e excluímos as versões antigas. Usuários com versões antigas em seu código ainda terão acesso a elas, mas novos usuários não conseguirão obtê-las. Pretendemos fornecer ferramentas para migrar usuários de versões antigas para novas em um único passo, novamente utilizando switches de funções.

A Dark também oferece uma oportunidade única: como executamos seu código, podemos testar novas versões, comparando as saídas para novas e antigas solicitações, para informá-lo sobre as mudanças. Assim, a atualização de pacotes, que muitas vezes é feita às cegas (ou requer testes rigorosos por razões de segurança), apresenta muito menos riscos e pode ocorrer automaticamente.

Novas versões do Dark

A transição do Python 2 para o Python 3 se estendeu por uma década e ainda é um problema. Como estamos desenvolvendo o Dark para entrega contínua, é necessário levar em consideração essas mudanças de linguagem.

Quando fazemos pequenas alterações na linguagem, criamos uma nova versão do Dark. O código antigo permanece na versão antiga do Dark, enquanto o novo código é utilizado na nova versão. Para mudar para a nova versão do Dark, pode-se usar switches ou versões de funcionalidades.

Isso é especialmente útil, considerando que o Dark é bastante recente. Muitas mudanças na linguagem ou na biblioteca podem acabar sendo frustrantes. A versionação gradual da linguagem nos permite fazer atualizações menores, ou seja, podemos não ter pressa e adiar muitas decisões de linguagem até que tenhamos mais usuários, e portanto mais informações.

Migrações de banco de dados

Para uma migração de banco de dados segura, existe uma fórmula padrão:

  • Reescrever o código para suportar os novos e antigos formatos
  • Converter todos os dados para o novo formato
  • Remover o antigo acesso aos dados

Como resultado, a migração de banco de dados se torna demorada e consome muitos recursos. E acumulamos esquemas obsoletos, já que até tarefas simples, como corrigir o nome de uma tabela ou coluna, não valem o esforço empregado.

O Dark possui uma plataforma de migração de banco de dados eficaz, que (esperamos) simplificará tanto o processo que você deixará de tê-lo como um medo. Todos os repositórios de dados no Dark (repositórios de pares "chave-valor" ou tabelas hash persistentes) têm tipo. Para transferir um repositório de dados, você simplesmente atribui a ele um novo tipo e uma função de reversão e aplicação para converter valores entre os dois tipos.

O acesso aos repositórios de dados no Dark é feito através de nomes de variáveis versionados. Por exemplo, o repositório de dados Users será inicialmente chamado de Users-v0. Quando uma nova versão com um tipo diferente é criada, o nome muda para Users-v1. Se os dados forem salvos via Users-v0 e você acessá-los através de Users-v1, aplica-se a função de aplicação. Se os dados forem salvos via Users-v1 e você acessá-los através de Users-v0, aplica-se a função de reversão.

Como o Dark executa código em 50 ms
A tela de migração da base de dados com os nomes dos campos da antiga base de dados, expressões de aplicação e reversão e instruções para habilitar a migração.

Use switches de features para direcionar as chamadas de Users-v0 para a versão Users-v1. Isso pode ser feito um manipulador HTTP por vez, para reduzir riscos, e os switches funcionam para usuários individuais, para que você possa verificar se tudo está funcionando como esperado. Quando não restar nenhum usuário de Users-v0, o Dark converterá todos os dados restantes em segundo plano do antigo formato para o novo. Você nem perceberá.

Testes

Dark é um linguagem de programação funcional com tipagem estática e valores imutáveis, portanto, a superfície de teste é significativamente menor em comparação com linguagens orientadas a objetos com tipagem dinâmica. Mas ainda assim, os testes são necessários.
No Dark, o editor executa automaticamente testes modulares em segundo plano para o código editável e, por padrão, executa esses testes para todos os switches de features. No futuro, queremos usar tipagem estática para executar fuzzing de código automaticamente para encontrar bugs.

Além disso, o Dark executa sua infraestrutura em produção, abrindo novas oportunidades. Automáticamente salvamos as requisições HTTP na infraestrutura do Dark (por enquanto, salvamos todas as requisições, mas depois queremos mudar para amostragens). Testamos novo código com base nelas e realizamos testes modulares, e, se desejar, você pode facilmente transformar requisições interessantes em testes modulares.

Do que nos livramos

Como não temos implantações, mas sim switches de features, cerca de 60% do pipeline de implantação fica de fora. Não precisamos de branches git ou pull requests, construir recursos de backend e containers, enviar recursos e containers para registros ou etapas de implantação no Kubernetes.

Como o Dark executa código em 50 ms
Comparação entre o pipeline padrão de entrega contínua (à esquerda) e a entrega contínua Dark (à direita). No Dark, a entrega consiste em 6 etapas e um ciclo, enquanto a versão tradicional inclui 35 etapas e 3 ciclos.

No Dark, há apenas 6 etapas e 1 ciclo no processo de implantação (etapas que se repetem várias vezes), enquanto o pipeline moderno de entrega contínua consiste em 35 etapas e 3 ciclos. No Dark, os testes são executados automaticamente, e você nem percebe; as dependências são instaladas automaticamente; nada relacionado ao git ou Github é mais necessário; não é necessário compilar, testar e enviar contêineres Docker; a implantação no Kubernetes não é mais necessária.

Mesmo os passos restantes no Dark se tornaram mais simples. Como os switches de função podem ser gerenciados com uma única ação, não é necessário passar novamente por todo o pipeline de implantação para remover código antigo.

Fizemos o possível para simplificar a entrega de código, reduzindo o tempo e os riscos da entrega contínua. Além disso, simplificamos significativamente a atualização de pacotes, migrações de bancos de dados, testes, gerenciamento de versões, instalação de dependências, igualdade entre o ambiente de desenvolvimento e produção e atualizações rápidas e seguras das versões da linguagem.

Estou respondendo perguntas sobre isso em HackerNews.

Para saber mais sobre o dispositivo Dark, leia a artigo sobre Dark, siga-nos no Twitter (ou em meu) ou inscreva-se para a versão beta e receba notificações sobre os próximos posts. Se você for ao StrangeLoop em setembro, venha nos ver no lançamento.

Fonte: habr.com

Compre hospedagem confiável para sites com proteção contra DDoS, servidores VPS VDS 🔥 Compre hospedagem confiável para sites com proteção contra DDoS, servidores VPS VDS | ProHoster