Ir para o conteúdo

Blogue ·

Projetar, nunca substituir

Uma vista de progresso sobre migrações, cópias, reposições e reinstalações, sem se tornar um segundo registo. Duas falhas foram completamente silenciosas.

Por Provibr

Um painel de alojamento acumula trabalho de longa duração. Uma migração entre dois painéis de controlo, uma cópia de segurança, uma reposição para uma máquina nova, um contentor que é substituído. Cada um criou a sua tabela, o seu vocabulário de estados e o seu ecrã, e isso aguenta até alguém fazer a pergunta óbvia: o que está a correr agora na minha conta, e se alguma coisa está encravada.

A resposta tentadora é um sistema de tarefas. Uma tabela, uma lista de estados, tudo o resto escreve lá dentro. A resposta tentadora é também o caminho para duas fontes de verdade sobre o mesmo facto, e para uma conversa de apoio um ano depois em que a tarefa diz falhou e a tabela de cópias diz que o arquivo está no armazenamento.

Por isso a vista de operações é uma projeção. Lê as linhas existentes e escreve um resumo por cima. Nunca vai nada no sentido inverso: nem uma função do módulo de espelho toca nas execuções de cópia, nas de reposição, nas de migração ou nas reinstalações. Essas tabelas continuam a ser a verdade, e ao resumo é permitido estar desatualizado, errado ou ausente sem que se parta nada a jusante.

Um espelho por tabela, e um teste que lê o esquema

Quatro tabelas assentam nesta costura, e cada uma tem exatamente uma função que a projeta.

Tabela de origemColuna de estadoTipo de operação
backup_runsstatusbackup.run
backup_restore_runsstatusbackup.restore
migration_runsstatusmigration.run
container_reinstall_intentsstatecontainer.reinstall

A última linha é a razão pela qual os nomes de coluna estão escritos como texto e não são supostos. Essa tabela chama à coluna state, e um espelho que lesse status não recebe nada e cai sem se queixar no ramo de falha.

Essa tabela não é documentação. Um teste deduz do esquema da base de dados quais dessas tabelas têm coluna de estado, exige uma entrada aqui para cada uma, exige que a função nomeada exista mesmo e exige, e esta é a parte que faz o trabalho a sério, que todo o ficheiro que escreva uma dessas colunas nomeie também o seu espelho. Uma quinta tabela que chegue à costura sem projeção é assim um teste vermelho em vez de uma gaveta que deixa de se mexer em silêncio.

Tanta severidade é precisa porque os escritores estão espalhados: uma execução de cópia muda de estado em seis pontos de um único ficheiro, três deles no caminho que trata as respostas da reconciliação que corre de noite. Deixar cada escritor invocar a projeção por si significa que o sexto se esquece, e esquecer é invisível. A linha está certa. Apenas o ecrã nunca se atualiza.

O coletor que existia duas vezes

O espelho vive no pacote a quem pertencem as escritas na base de dados, porque tanto uma ação de servidor da aplicação web como um componente do gateway chamam as mesmas funções. Esse pacote não pode publicar um evento em direto: depende da base de dados e de código partilhado e de mais nada, e o barramento de mensagens é um objeto diferente de cada lado.

Expõe portanto um coletor. Cada aplicação regista um, e o pacote não sabe quem o preencheu. Se ninguém o fizer, a escrita na base de dados prossegue na mesma e não sai qualquer evento em direto, e a gaveta atualiza-se no carregamento de página seguinte. É degradação honesta, e a função nunca lança de propósito, porque um registo em falta não pode derrubar uma cópia de segurança.

O coletor era uma variável de módulo, e isso estava errado de uma forma que não produzia erro nenhum. O Next executa o seu ficheiro de instrumentação como um pacote próprio, separado dos processos de rota. A variável existia por isso duas vezes: o registo caía num exemplar e a função que emite lia o outro. As linhas estavam certas, os estados avançavam, a gaveta ficava parada, e não havia uma única linha em nenhum registo.

Isto não foi encontrado a raciocinar. Foi encontrado pondo um ouvinte no barramento de mensagens e contando: chegou um evento onde deviam estar três. A reparação é um encaixe em globalThis por trás de um Symbol.for, que sobrevive a uma fronteira de empacotamento dentro do mesmo processo, mesma forma e mesma razão que a ligação partilhada à base de dados e a do Redis.

Duas lições que valem mais do que a falha. Uma falha sem linha de erro exige um instrumento fora do código que se suspeita. E «este módulo tem um exemplar» é uma suposição sobre um empacotador, não uma propriedade da linguagem.

A mesma ronda encontrou um segundo caso silencioso. Concluir uma operação e bloqueá-la deixavam ambas a coluna de etapa intacta, pelo que uma cópia perfeitamente bem-sucedida ficava para sempre em «a arquivar». Não falhava nada; simplesmente o último passo nunca chegava.

Cancelar é um pedido, e uma única escrita é a prova

É no cancelamento que uma projeção mais tentação tem de mentir. A gaveta conhece a operação, a operação tem um botão de cancelar, e a implementação mais barata põe uma marca e fecha o painel.

Por isso cada tipo declara se cancelar sequer faz sentido. Um vzdump a decorrer não o podemos cancelar: o comando está no agente, a tarefa no hipervisor, e ninguém sabe o que representa meio arquivo no armazenamento do cliente. Uma reposição é igual, com uma razão a mais: a linha de reposição mantém uma reserva sobre o identificador de destino enquanto estiver aberta, e fechá-la cedo liberta esse identificador enquanto talvez esteja a aterrar lá um convidado. Ambas são declaradas não suportadas, e não há caminho que o contorne.

Onde cancelar é possível, decide na mesma a linha. Uma migração pode ser cancelada enquanto está planeada e já não quando um programa auxiliar está a criar servidores no anfitrião de outra pessoa. Uma reinstalação de contentor pode ser cancelada até à pré-visualização e já não depois de o agente ter começado a substituir.

Quando resulta, a prova de que nada aconteceu é uma única escrita condicionada: pôr o comando em fila como cancelado onde o seu estado ainda seja pendente. Se já tiver sido enviado, essa instrução toca zero linhas e o pedido fica honestamente em «pedido». Consultar e depois escrever seriam duas instruções e portanto duas ocasiões para alguém se meter pelo meio.

E então a frase conta. O evento diz que o comando foi retirado da fila antes de o agente o receber, e não que foi cancelado. Não se interrompeu nada, porque nada tinha começado. Essa linha vai para o registo geral de eventos e não para o progresso da operação, porque o progresso é limpo ao fim de noventa dias e «uma pessoa interveio na infraestrutura» não é progresso.

Quatro bloqueios, e nenhum é uma falha

Quatro situações colocam uma operação em precisa de atenção em vez de falhada.

Um vzdump comunicou OK e o arquivo não está no índice. Uma tentativa de cópia esgotou o seu tempo e não sabemos se o arquivo existe. Uma reposição não é relegível ou o convidado obtido não pôde ser registado. Uma reinstalação de contentor parou depois de o contentor antigo já ter desaparecido.

Chamar falhada a qualquer uma delas seria uma mentira com consequência. Falhada convida o cliente a repetir, e repetir uma cópia são mais dezenas de gigabytes no armazenamento de alguém para um arquivo que talvez já lá esteja. Repetir uma reinstalação é um segundo contentor. O ciclo de reconciliação continua a olhar até o índice se pronunciar, e entretanto a operação fica visivelmente encravada, que é a dose certa de alarme.

O bloqueio indica também de quem é. Três deles são nossos; um comando que falhou mesmo do lado do cliente di-lo.

Um ensaio termina no planeamento, e isso não é uma lacuna

Uma migração em ensaio tem cinco passos; uma real tem oito, sendo os três adicionais a escrita e a recontagem. A lista de etapas tem as seis, pelo que um ensaio bem-sucedido mostra as duas últimas como não feitas.

Isso parece um defeito numa barra de progresso e é a verdade: um ensaio não escreve e não reconta. As alternativas eram um segundo tipo, que duplica nove entradas de catálogo em nove línguas para o mesmo trabalho, ou uma lista de etapas que varia por linha, o que faria a gaveta e a página de detalhe divergirem sobre quantos passos existem. Mostrar a verdade saía mais barato do que ambas.

O que deixaríamos escrito

  1. Uma projeção projeta. Se a sua camada de resumo consegue reescrever na origem, é um segundo registo e agora tem dois.
  2. Ponha a costura numa tabela que um teste consiga ler, derivada do esquema, e faça-o verificar que todo o escritor de uma coluna de estado nomeia o seu espelho. Uma invocação esquecida não dá erro.
  3. Não assuma que um módulo tem um exemplar. Uma estrutura que executa parte do seu código como pacote separado dar-lhe-á dois, e o sintoma é o silêncio.
  4. Meça fora do suspeito. Um ouvinte no barramento encontrou em minutos o que a leitura não encontrou de todo.
  5. Diga o que aconteceu mesmo. «Retirado da fila antes de o agente o receber» é mais longo do que «cancelado» e é a única versão exata.
  6. Reserve falhada para a falha. Os desfechos desconhecidos precisam de um estado próprio, senão as pessoas repetem operações caras que talvez já tenham resultado.

Para que fique claro o que isto é: os tipos aqui descritos estão por trás de interruptores de lançamento desligados por omissão, pelo que migrações e cópias de cliente não são compráveis hoje. Várias coisas ficam por provar. O evento de progresso em direto foi medido apenas para migrações. O lado do gateway do registo do coletor não foi exercitado com um gateway realmente em funcionamento. A cadeia do agente foi reproduzida com SQL. E cancelar uma reinstalação de contentor a partir do ecrã de operações nunca foi conduzido num navegador.

Do trabalho de plataforma em Provibr. Porque é que uma barra de progresso se recusa a estimar uma hora de fim está em uma barra de progresso que se recusa a adivinhar, e porque é que uma migração que saltou em silêncio é pior do que uma que falhou está em a migração que nunca correu.

Comece com um host e um agente

Crie uma conta, instale o agente com um único comando e ligue o seu primeiro host Proxmox. O período experimental dura 14 dias e não pede quaisquer dados de pagamento.