Blogue ·
Atualizado às três da manhã, de propósito
A atualização automática do agente vem ligada às 03:00. O interessante era que situações consomem uma faixa e uma noite com duas vezes as três.
Por Provibr
O agente que corre na rede própria de um cliente recebe uma compilação nova de poucos em poucos dias. Até esta ronda, atualizá-lo era um botão que alguém tinha de carregar, o que na prática significava que acontecia quando alguém se lembrava de olhar. O desejo era uma frase: cada um pode definir no seu agente um horário de atualização, vários momentos por dia, ligado por omissão às três da manhã.
A frase é fácil. O que fez disto uma ronda é que um reinício agendado de infraestrutura tem muitas maneiras de estar silenciosamente errado, e quase todas parecem bem vistas de fora.
O valor por omissão está na coluna, e aí está todo o desejo
«Ligado por omissão para toda a gente» lê-se de duas maneiras. Escrito no código da aplicação vale para os agentes criados a partir de agora, o que exclui precisamente os clientes que já têm um. Escrito como valor por omissão de coluna na migração vale para cada linha existente numa única instrução.
É portanto um valor por omissão de coluna: um booleano que começa em verdadeiro e um vetor JSON que começa com uma só entrada 03:00. Medido depois de a migração ter corrido na base de dados de desenvolvimento: cada agente existente ligado, cada um às três, e um agente acabado de criar a que não foi passado nenhum dos dois valores ficou no mesmo estado. Essa última verificação é a que prova que o trabalho é feito pelo valor por omissão e não por um guião de sementeira.
Os momentos são hora de relógio no fuso da plataforma, não UTC. Não existe um fuso por organização e o painel mostra tudo nesse único fuso, portanto um horário guardado em UTC deslizaria uma hora no verão e quem tivesse escrito três horas seria interrompido às cinco.
Duas camadas sobre uma mesma expressão regular
Um momento é HH:MM numa grelha de cinco minutos, e esse padrão existe duas vezes: uma no validador de esquema, que recusa um campo de formulário com uma mensagem legível, e uma como restrição de base de dados, que recusa tudo o que nunca passou por esse formulário.
Uma restrição de verificação não pode conter uma subconsulta, portanto «cada elemento deste vetor é um momento válido» não pode ser um teste de existência sobre o vetor desdobrado. O que pode ser é um predicado de caminho JSON que seleciona precisamente os elementos errados, embrulhado num teste de que essa seleção está vazia.
A primeira versão desse predicado era subtilmente inútil, e parecia completa:
$[*] ? ( !(@ like_regex "^([01][0-9]|2[0-3]):(00|05|10|15|20|25|30|35|40|45|50|55)$") )A correspondência de uma expressão regular com algo que não é uma cadeia não é falsa em caminho JSON. É Unknown. E a negação de Unknown também é Unknown, e um filtro guarda apenas os elementos para os quais o predicado é mesmo verdadeiro. A seleção dos elementos errados voltava portanto vazia para um vetor de números, e a restrição aceitava-o. Medido contra o PostgreSQL 17.10, [3], [null] e [{"a":1}] passaram os três.
A correção é uma cláusula à frente: os elementos cujo tipo não é uma cadeia estão errados por definição. Os três são agora recusados. É exatamente a classe de falha que continua a custar-nos rondas: uma guarda que não apanha nada e parece completa, e nenhum teste que falhe, porque não havia nada contra o qual falhar.
Cinco passos, e a ordem é a ronda
Cada minuto um trabalhador pergunta quem tem horário, entrega as linhas a um planeador puro que decide que faixa está a haver, e depois percorre cinco passos por agente. A reserva é o passo quatro e não o primeiro, e cada passo anterior está lá por uma razão própria.
| Passo | Se a resposta for não |
|---|---|
| Há uma compilação mais recente? | A faixa é reservada na mesma, marcada como nada a fazer |
| O agente está ligado? | Nada é reservado, e o minuto seguinte tenta de novo |
| Já há uma atualização a decorrer? | Nada é reservado; a primeira ainda cumprirá a faixa |
| Reservar a faixa | Decide um índice único; quem perde não envia nada |
| Enviar o comando | A mesma função que o botão do painel |
As duas primeiras linhas são opostos deliberados, e essa assimetria é a decisão mais interessante da ronda.
Nada a fazer consome a faixa, porque a resposta já não pode mudar dentro de uma faixa: o manifesto da publicação em disco é o mesmo durante toda a janela, portanto voltar a julgar a cada minuto custa consultas e não traz nada. E dá ao cliente algo para ver. Uma página de agente que diz «03:00, já atualizado» distingue-se de um horário que não corre. Uma lista vazia não.
Estar desligado não consome nada, porque uma faixa reservada enquanto a máquina está a reiniciar fica consumida para sempre, e então esse cliente perde uma noite em silêncio. O minuto seguinte tenta de novo, até a janela de recuperação fechar; depois disso o agente toma simplesmente a faixa seguinte.
Quinze minutos, e não seis horas
As cópias de segurança têm nesta plataforma uma janela de recuperação de seis horas. Esta é de quinze minutos, e a diferença está no que corre mal quando se é generoso.
Uma cópia feita às nove da manhã em vez das três continua a ser uma cópia. Um agente que reinicia às nove da manhã em vez das três é uma interrupção a meio do dia de trabalho, e as três da manhã são precisamente o que o cliente escolheu para o evitar. Quinze minutos cobrem aquilo para que esta janela serve de facto, um portal acabado de reiniciar ou duas ou três rondas saltadas, e nada mais.
O planeador olha dois dias de calendário para trás e não um, e não é um descuido. Um momento das 23:55 com janela de recuperação cai, logo depois da meia-noite, no dia de calendário de ontem; olhando só para hoje, essa faixa nunca chegaria por construção.
Dois travões, por duas razões diferentes
Cinco agentes por organização em cada volta, e vinte e cinco no total.
O travão por organização diz respeito à largura de banda do cliente e não à nossa. O binário ronda os vinte e dois megabytes, um alojador com quarenta agentes tem-nos normalmente aos quarenta às 03:00 porque é o que vem definido, e quarenta descarregamentos em simultâneo saem pela mesma ligação. Cinco por minuto espalham uma frota de quarenta ao longo de oito minutos, folgadamente dentro da janela de quinze.
O travão global existe porque o primeiro age por organização: com cem organizações seriam quinhentas atualizações num minuto. O resto chega no minuto seguinte, ainda dentro da janela.
Não há nenhuma bandeira de implantação sobre nada disto, o que é invulgar em nós. O interruptor já está no cliente, na coluna; uma segunda bandeira, ao nível da plataforma, prometeria o que a coluna já promete. O que existe é um travão de emergência para nós: pôr o intervalo de sondagem a zero detém o temporizador sem mudar uma única definição de cliente.
A noite com dois trêses
No último domingo de outubro a hora de relógio 03:00 existe duas vezes, e no último domingo de março as 02:30 não existem de todo. Ambas são resolvidas por uma função auxiliar partilhada, não copiada para este módulo, precisamente porque um algoritmo de hora de verão mantido em dois sítios diverge um dia por ano.
Essa função responde a uma hora de relógio ambígua com o primeiro dos dois instantes, e a uma inexistente com o instante seguinte que existe. As consequências são as que se querem. Na noite longa, as 03:00 correm uma vez e não duas; medido: uma faixa. Na noite curta, as 02:30 passam simplesmente a 03:30 locais em vez de serem saltadas, porque não saltar é o lado seguro dessa moeda. Ambas ficam fixadas por testes.
Os dias são percorridos como dias de calendário e não somando um número fixo de milissegundos: somar vinte e quatro horas através de uma mudança salta um dia ou faz um duas vezes.
A quem se conta, e com que frequência
A reconciliação que reporta os desfechos vive no trabalhador e não na via de envio, e só o horário avisa. Um clique à mão já tinha alguém a olhar.
Os avisos são agrupados por organização, porque quarenta agentes no dia de uma publicação não devem ser quarenta linhas numa caixa. Uma falha é comunicada no máximo uma vez por agente e por dia de calendário. E a marca de tempo que regista que se ponderou avisar é escrita mesmo quando a decisão foi calar, para que o silêncio se distinga de um trabalhador que nunca lá chegou.
Uma atualização agendada não aparece de propósito como operação de raiz na gaveta de tarefas: às três da manhã ninguém espera, e uma gaveta cheia de linhas noturnas é inútil para aquilo para que existe. O rasto são uma linha de execução, um evento e um aviso. Esse evento é de um tipo próprio, porque a cronologia não deve afirmar que às três foi o cliente a arrancar algo em pessoa.
O que anotaríamos
- «Por omissão para toda a gente» é um valor por omissão de coluna. No código da aplicação significa «para toda a gente a partir de agora», e isso exclui precisamente as pessoas em que estava a pensar.
- Unknown não é falso. Em caminho JSON, um predicado sobre um valor do tipo errado não é nem verdadeiro nem falso, e negá-lo não ajuda. Verifique primeiro o tipo.
- Decida em cada situação se consome a faixa. Nada a fazer e inalcançável são respostas opostas, e ambas são invisíveis se as trocar.
- Uma janela de recuperação é uma promessa sobre a interrupção, não sobre a completude. Copie o número de um sistema com outro modo de falhar e ficará com o errado.
- Trave onde de facto se sente. Quarenta agentes numa rede de cliente partilham uma ligação, e é esse o limite, não a nossa rota de descarregamento.
- Resolva a hora de verão uma só vez. Duas cópias do mesmo raciocínio concordam até ao último domingo de outubro.
Para ser claro sobre o que isto é: o horário está em produção e ligado por omissão. Várias coisas não estão provadas. O trabalhador não foi exercitado dentro de um portal realmente em funcionamento, nenhum agente real executou ainda uma atualização agendada, o aviso de falha e a sua desduplicação diária não foram observados de ponta a ponta, e o ecrã foi revisto em duas das nove línguas.
Do trabalho de plataforma da Provibr. Porque é que um agente recusa uma atualização que não consegue verificar está em como uma atualização assinada do agente se recusa a si própria, e porque é que o agente liga para fora em vez de escutar está em um agente de saída em vez de reencaminhamento de portas.