Ir al contenido
mcpd
sin daemon

Relé y regiones

Un colector junto a tus agentes, y el enlace entre regiones recorrido con reintentos.

Qué es un relé

mcpd relay ejecuta el mismo binario como un colector que no almacena nada por sí mismo. Acepta todas las solicitudes HTTP de ingesta que acepta un daemon, escribe cada solicitud en un spool en disco, responde al emisor y después envía la solicitud a los daemons que nombra --upstream. No abre ningún almacén.

Ejecuta uno en cada región donde corren tus agentes. Así un emisor habla con un colector a pocos milisegundos, y el enlace entre regiones lo recorre el relé, con reintentos, en lugar de cada agente por su cuenta.

un relé en el borde con dos upstreams

mcpd relay \ --ingest-key $INGEST_KEY \ --upstream http://mcpd.eu.internal:4317 \ --upstream http://mcpd.us.internal:4317
relé
$ 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}

Qué responde

Acepta cualquier POST, en cualquier ruta que no sea una sonda. Las rutas OTLP /v1/traces, /v1/logs y /v1/metrics responden 200 con un partialSuccess de OTLP, que es lo que espera un exportador de OpenTelemetry. El resto de rutas responden 202 con la cabecera mcpd-relay: spooled y el cuerpo {"accepted":0,"duplicate":0,"rejected":0}: la solicitud está guardada y se entregará, y los recuentos reales son cosa del upstream, que todavía no la ha visto. Todos los SDK leen cualquier 2xx como éxito.

El relé comprueba --ingest-key por sí mismo, antes de escribir nada, y aplica --max-body-bytes y --request-timeout-seconds como lo hace un daemon. La solicitud ya está en el spool, <spool-dir>/relay, antes de enviar la respuesta.

Entrega y conmutación por error

Cuatro workers vacían el spool. Cada solicitud va al primer upstream, en el orden de --upstream, que no esté en pausa. Un fallo que admite reintento pone ese upstream en pausa, con espera exponencial y jitter o durante el tiempo que indique su Retry-After, y la solicitud va al siguiente upstream en el acto. Cuando acaba la pausa, se vuelve a probar primero el principal, así que el tráfico vuelve a él por sí solo.

Un rechazo definitivo, como un 400, no se envía al siguiente upstream: el siguiente rechazaría los mismos bytes. La solicitud se mueve a dead/ dentro del directorio de spool y se cuenta.

Sin contar nada dos veces

La entrega es al menos una vez, y un reenvío se almacena una sola vez. Cada solicitud lleva una cabecera Idempotency-Key: la del emisor si mandó una y, si no, relay- seguido de un resumen de la ruta, la codificación del contenido y el cuerpo. Todas las puertas de ingesta del daemon respetan la cabecera, y una clave dentro del cuerpo de un lote nativo tiene prioridad sobre ella. Una solicitud que el relé vuelve a enviar tras agotar el tiempo de espera, o que envía al secundario después de que el principal la aceptara y se perdiera la respuesta, se reconoce y se informa como duplicada.

Claves

Con --upstream-key, el relé presenta esa clave a cada upstream como token Bearer. Sin ella, la cabecera Authorization o x-mcpd-key del emisor se guarda con la solicitud en el spool, en un archivo que solo puede leer el usuario del relé, y se pasa tal cual. La segunda forma conserva el proyecto y el ámbito de cada emisor tal como los ve el upstream.

Contrapresión y disponibilidad

El spool está limitado por --spool-max-bytes, 1 GiB para el relé salvo que lo cambies. Cuando está lleno, una solicitud se rechaza con un 503 y Retry-After: 5. Todos los SDK y exportadores de OpenTelemetry lo reintentan, así que el emisor conserva los datos en lugar de que el relé los descarte.

/healthz responde siempre 200. /readyz responde:

  • 200 ok mientras algún upstream esté alcanzable;
  • 200 degraded, con un detail, cuando ningún upstream ha respondido a su /readyz ni aceptado una entrega en los últimos 30 segundos. Los upstreams se sondean cada 5 segundos;
  • 503 cuando el spool está lleno, o cuando ningún upstream está alcanzable y el spool está lleno al menos al 80%.

Por eso un balanceador de carga sigue enviando a un relé que está acumulando en el spool, y aparta el tráfico de uno que ya no puede aceptarlo.

Métricas del relé

MétricaTipoSignificado
mcpd_relay_accepted_totalcounterSolicitudes escritas en el spool
mcpd_relay_refused_full_totalcounterSolicitudes rechazadas con un 503 porque el spool estaba lleno
mcpd_relay_failovers_totalcounterSolicitudes entregadas a un upstream que no es el primero
mcpd_relay_dead_letter_totalcounterSolicitudes que un upstream rechazó de forma definitiva, movidas a dead/
mcpd_relay_spool_entriesgaugeSolicitudes que esperan en el spool
mcpd_relay_spool_bytesgaugeBytes que guarda el spool
mcpd_relay_spool_oldest_secondsgaugeAntigüedad de la solicitud en espera más vieja, o 0
mcpd_relay_delivered_totalcounterSolicitudes que aceptó un upstream, con las etiquetas upstream y order
mcpd_relay_failed_attempts_totalcounterIntentos que un upstream no aceptó, con las mismas etiquetas
mcpd_relay_upstream_upgauge1 cuando el upstream respondió en los últimos 30 segundos, 0 en otro caso
mcpd_uptime_secondsgaugeSegundos desde que arrancó este proceso

Un mcpd_relay_failovers_total que sube significa que el principal está fallando y el secundario lleva su tráfico. mcpd_relay_spool_oldest_seconds indica cuánto retraso lleva la entrega.

Varias regiones

Ejecuta varias réplicas del daemon, en una región o en varias, contra un mismo almacén Postgres o ClickHouse, con las ejecuciones en Postgres (--runs-data postgres://...) o desactivadas: un almacén de ejecuciones embebido pertenece a un solo proceso. El /readyz de cada réplica ya refleja si puede alcanzar el almacén. Pon un relé delante de ellas en cada región, con --upstream nombrando primero el daemon más cercano y en segundo lugar un daemon de otra región.

Cuatro ajustes se comportan de otra forma en cuanto hay más de una réplica:

  • Las claves de idempotencia se guardan en el almacén compartido, así que un reenvío que cae en otra réplica se sigue reconociendo. Postgres registra la clave en la misma transacción que el lote.
  • Los topes de cuota por minuto se cuentan por réplica. Divide el tope que quieres entre el número de réplicas.
  • La puntuación en vivo, el muestreo de cola y las alertas de los monitores se ejecutan en una sola réplica a la vez. Las réplicas se turnan mediante un lease en el almacén compartido: quien lo tiene lo renueva en cada pasada, lo suelta al apagarse, y otra réplica lo toma en tres pasadas como mucho si se detiene. Cada réplica evalúa sus monitores y objetivos, así que todas responden a la API de monitores, y solo quien tiene el lease envía el webhook. Configúralos igual en todas las réplicas.
  • El análisis de aspectos se ejecuta en cada réplica que lo tiene configurado. Configúralo en una sola réplica.

Despliegues de ejemplo

deploy/docker/docker-compose.multi-region.yml arranca Postgres, dos daemons y un relé en una sola máquina, que es la topología completa en una forma que puedes probar.

El chart de Helm acepta mode: relay. Tres archivos de valores en deploy/helm/examples/ describen una región cada uno: region-core.yaml, region-standby.yaml y region-edge-relay.yaml, este último para una región que solo ejecuta un relé.

A continuación