Ir para o conteúdo
mcpd
sem daemon

Relay e regiões

Um coletor ao lado dos seus agentes, e o trecho entre regiões percorrido com novas tentativas.

O que é um relay

mcpd relay roda o mesmo binário como um coletor que não armazena nada. Ele aceita toda requisição HTTP de ingestão que um daemon aceita, grava a requisição em um spool no disco, responde ao remetente e depois envia a requisição aos daemons indicados em --upstream. Ele não abre nenhum armazenamento.

Rode um em cada região onde os seus agentes rodam. Um remetente passa a falar com um coletor a poucos milissegundos de distância, e o trecho entre regiões é percorrido pelo relay, com novas tentativas, e não por cada agente sozinho.

um relay de borda com dois upstreams

mcpd relay \ --ingest-key $INGEST_KEY \ --upstream http://mcpd.eu.internal:4317 \ --upstream http://mcpd.us.internal:4317
relay
$ MCPD_UPSTREAMS=http://mcpd.eu.internal:4317,http://mcpd.us.internal:4317 mcpd --data /data/mcpd.db config | grep ^relayrelay           2 upstream(s) in order, http://mcpd.eu.internal:4317, http://mcpd.us.internal:4317; each sender's key passed on; spool at /data/mcpd.spool/relay, 1024 MiB$ curl -s -D - -o /dev/null localhost:4317/v1/ingest -H "content-type: application/json" -d @batch.json | grep -e ^HTTP -e mcpd-relayHTTP/1.1 202 Acceptedmcpd-relay: spooled$ curl -s localhost:4317/v1/ingest -H "content-type: application/json" -d @batch.json{"accepted":0,"duplicate":0,"rejected":0}

O que ele responde

Qualquer POST, em qualquer caminho que não seja o das sondas, é aceito. Os caminhos OTLP /v1/traces, /v1/logs e /v1/metrics respondem 200 com um partialSuccess OTLP, que é o que um exportador OpenTelemetry espera. Todos os outros caminhos respondem 202 com o cabeçalho mcpd-relay: spooled e o corpo {"accepted":0,"duplicate":0,"rejected":0}: a requisição está guardada e será entregue, e as contagens reais pertencem ao upstream, que ainda não a viu. Todo SDK lê qualquer 2xx como sucesso.

O próprio relay confere --ingest-key antes de gravar qualquer coisa, e aplica --max-body-bytes e --request-timeout-seconds como um daemon faz. A requisição já está no spool, <spool-dir>/relay, antes de a resposta ser enviada.

Entrega e failover

Quatro workers esvaziam o spool. Cada requisição vai para o primeiro upstream, na ordem de --upstream, que não esteja em pausa. Uma falha que admite nova tentativa coloca aquele upstream em pausa, com espera exponencial e variação aleatória ou pelo tempo que o Retry-After dele indicar, e a requisição vai na hora para o upstream seguinte. Quando a pausa termina, o primário volta a ser tentado primeiro, então o tráfego retorna a ele por conta própria.

Uma recusa definitiva, como um 400, não é enviada ao upstream seguinte: ele recusaria os mesmos bytes. A requisição é movida para dead/ no diretório do spool e contada.

Sem contagem dupla

A entrega é pelo menos uma vez, e um reenvio é armazenado uma única vez. Toda requisição leva um cabeçalho Idempotency-Key: o do remetente, quando ele enviou um, ou então relay- seguido de um resumo criptográfico do caminho, da codificação do conteúdo e do corpo. Toda porta de ingestão do daemon respeita o cabeçalho, e uma chave no corpo de um lote nativo tem prioridade sobre ele. Uma requisição que o relay reenvia depois de um timeout, ou que envia ao secundário depois que o primário a aceitou e a resposta se perdeu, é reconhecida e relatada como duplicada.

Chaves

Com --upstream-key, o relay apresenta essa chave a todo upstream como token Bearer. Sem ela, o cabeçalho Authorization ou x-mcpd-key do remetente é guardado junto com a requisição no spool, em um arquivo que só o usuário do relay consegue ler, e repassado. A segunda forma preserva o projeto e o escopo de cada remetente do jeito que o upstream os vê.

Contrapressão e prontidão

O spool é limitado por --spool-max-bytes, 1 GiB para o relay, a menos que você defina outro valor. Quando ele está cheio, uma requisição é recusada com um 503 e Retry-After: 5. Todo SDK e todo exportador OpenTelemetry tentam de novo nesse caso, então o remetente segura os dados em vez de o relay descartá-los.

/healthz sempre responde 200. /readyz responde:

  • 200 ok enquanto algum upstream estiver alcançável;
  • 200 degraded, com um detail, quando nenhum upstream respondeu ao próprio /readyz nem aceitou uma entrega nos últimos 30 segundos. Os upstreams são sondados a cada 5 segundos;
  • 503 quando o spool está cheio, ou quando nenhum upstream está alcançável e o spool está com pelo menos 80% de ocupação.

Um balanceador de carga, portanto, continua enviando para um relay que está acumulando, e desvia o tráfego de um que não consegue mais recebê-lo.

Métricas do relay

MétricaTipoSignificado
mcpd_relay_accepted_totalcounterRequisições gravadas no spool
mcpd_relay_refused_full_totalcounterRequisições recusadas com 503 porque o spool estava cheio
mcpd_relay_failovers_totalcounterRequisições entregues a um upstream que não era o primeiro
mcpd_relay_dead_letter_totalcounterRequisições que um upstream recusou de vez, movidas para dead/
mcpd_relay_spool_entriesgaugeRequisições esperando no spool
mcpd_relay_spool_bytesgaugeBytes guardados no spool
mcpd_relay_spool_oldest_secondsgaugeIdade da requisição mais antiga em espera, ou 0
mcpd_relay_delivered_totalcounterRequisições que um upstream aceitou, com os rótulos upstream e order
mcpd_relay_failed_attempts_totalcounterTentativas que um upstream não aceitou, com os mesmos rótulos
mcpd_relay_upstream_upgauge1 quando o upstream respondeu nos últimos 30 segundos, 0 caso contrário
mcpd_uptime_secondsgaugeSegundos desde que este processo iniciou

Um mcpd_relay_failovers_total que sobe significa que o primário está falhando e o secundário está levando o tráfego dele. mcpd_relay_spool_oldest_seconds mostra o quanto a entrega está atrasada.

Várias regiões

Rode várias réplicas do daemon, em uma ou mais regiões, sobre um único armazenamento Postgres ou ClickHouse, com as execuções no Postgres (--runs-data postgres://...) ou desligadas: um armazenamento de execuções embutido pertence a um processo. O /readyz de cada réplica já reflete se ela alcança o armazenamento. Coloque um relay em cada região na frente delas, com --upstream listando primeiro o daemon mais próximo e, em segundo, um daemon de outra região.

Quatro ajustes se comportam de outro jeito quando há mais de uma réplica:

  • As chaves de idempotência ficam no armazenamento compartilhado, então um reenvio que cai em outra réplica continua sendo reconhecido. O Postgres registra a chave na mesma transação que o lote.
  • Os tetos de cota por minuto são contados por réplica. Divida o teto que você quer pelo número de réplicas.
  • A avaliação ao vivo, a amostragem de cauda e os alertas de monitores rodam em uma réplica de cada vez. As réplicas se revezam por meio de um lease no armazenamento compartilhado: quem o detém o renova a cada passada, o libera quando desliga, e outra réplica assume em até três passadas se ele parar. Toda réplica avalia seus monitores e objetivos, então cada uma responde à API de monitores, e só quem detém o lease envia o webhook. Configure-os do mesmo jeito em todas as réplicas.
  • A análise de aspectos roda em toda réplica que a tem configurada. Configure-a em uma única réplica.

Implantações de exemplo

deploy/docker/docker-compose.multi-region.yml sobe o Postgres, dois daemons e um relay em uma máquina, que é a topologia inteira em uma forma que você pode experimentar.

O chart Helm aceita mode: relay. Três arquivos de valores em deploy/helm/examples/ descrevem uma região cada: region-core.yaml, region-standby.yaml e region-edge-relay.yaml, este último para uma região que roda somente um relay.

A seguir