Workers
Un worker es un proceso tuyo que sirve al daemon los cuerpos de los pasos. Esta página es el protocolo que habla, y por qué está ahí cada llamada.
Qué es un worker
Un worker guarda los cuerpos de los pasos reclamables y le pide trabajo al daemon. Se ejecuta donde se ejecuta tu código, con tus imports, tu pool de conexiones y tus credenciales, y el daemon no llega a ver nada de eso. El daemon le entrega un lease con la entrada renderizada y los pasos anteriores; el worker ejecuta el cuerpo e informa de cómo fue. Toda la relación es esa.
Cada SDK trae uno. Registra los workflows que guarda, hace long polling para pedir leases, manda heartbeats mientras corre un cuerpo, y responde a cada rechazo según su motivo. El ejemplo de abajo es el mismo programa que el de la página de Workflows; esta es la parte que lo sirve.
servir los cuerpos
// Serve step bodies for as long as this process lives. The worker registers
// the definition, long-polls for leases, heartbeats while a body runs, and
// answers every 409 by its reason.
const worker = new Worker(mcpd, { concurrency: 8 }).register(support)
void worker.serve() # Serve step bodies for as long as this process lives. serve() blocks, so it
# runs on a thread of its own when the process does other work too.
worker = Worker(mcpd, concurrency=8).register(support)
threading.Thread(target=worker.serve, daemon=True).start() // Serve step bodies for as long as this process lives: register, long-poll
// for leases, heartbeat while a body runs, answer every 409 by its reason.
let worker = Worker::builder(&mcpd).concurrency(8).register(support).build();
let serving = {
let worker = worker.clone();
tokio::spawn(async move { worker.serve().await })
}; // Serve step bodies for as long as this process lives: register, long-poll
// for leases, heartbeat while a body runs, answer every 409 by its reason.
worker := mcpd.NewWorker(m, mcpd.WorkerConcurrency(8)).Register(support)
go func() { _ = worker.Serve(ctx) }() // Serve step bodies for as long as this process lives: register, long-poll
// for leases, heartbeat while a body runs, answer every 409 by its reason.
val worker = Worker(mcpd, concurrency = 8).register(support)
launch { worker.serve() } # Serve step bodies for as long as this node lives: register, long-poll for
# leases, heartbeat while a body runs, answer every 409 by its reason.
{:ok, worker} = StepWorker.start_link(mcpd, workflows: [support], concurrency: 8)
:ok = StepWorker.serve(worker) El protocolo
Cinco llamadas, y un worker que las hable se puede escribir contra la API HTTP sin ningún SDK. El worker de cada SDK es un cliente de estas cinco y nada más.
One lease
The daemon learns that a body is running from started, that the worker is alive from the heartbeats, and how the step ended from complete or fail. Miss the heartbeats and the lease is reclaimed; a later call about it is refused with a reason, never lost.
| Llamada | Qué hace |
|---|---|
POST /v1/api/workers/register | Anuncia el worker y almacena las definiciones que guarda. La respuesta lleva lease_ttl_ms y heartbeat_ms, los dos números con los que funciona el resto del protocolo. |
POST /v1/api/steps/claim | Ofrece hasta max pasos ejecutables, y mantiene la solicitud abierta hasta wait_ms cuando no hay ninguno. El daemon sostiene una reclamación como mucho cuatro quintos de su tiempo máximo de solicitud, 24 segundos con el valor por defecto, y responde en el acto cuando deja de sostenerla, así que una reclamación puede volver vacía antes de wait_ms: reclama otra vez. Una reclamación se puede limitar a una sola ejecución, que es como un script lleva su propia ejecución hasta el final. |
POST /v1/api/steps/{lease}/started | Declara que el cuerpo está corriendo. Siempre antes del cuerpo: es lo que le dice al daemon la diferencia entre ofrecido y en marcha. |
POST /v1/api/steps/{lease}/heartbeat | Extiende el lease. La única prueba que tiene el daemon de que un worker sigue vivo. |
POST /v1/api/steps/{lease}/complete, /fail | Termina el paso: la salida, un parche al estado de la ejecución y el consumo al que el daemon pone precio; o un error con la clase que decide si se reintenta. |
Leases
Un lease es un intento, en manos de un worker, por un tiempo acotado. Lleva la entrada renderizada, los pasos anteriores indexados por id, el estado y la correlación de la ejecución, una clave que es estable entre reintentos de este paso, y los ids de traza y de span del span de paso que abrió el daemon. Un cuerpo que registra sus propios spans a través del contexto del paso los cuelga bajo ese span, así que la llamada al modelo dentro de un paso aterriza en la traza de la ejecución a la profundidad correcta.
Un lease al que el worker deja de mandar heartbeats caduca. El daemon marca
el intento como huérfano y, pasada una ventana de recuperación, lo vuelve a
encolar bajo at_least_once o lo falla bajo
at_most_once. A un worker que vuelve de una pausa y publica
contra un lease ya recuperado no se le ignora: se le avisa.
Los cuatro rechazos
Una llamada de worker que el daemon no puede aceptar se rechaza con un 409 que lleva uno de cuatro motivos. La lista es cerrada, y el worker de cada SDK trata cada motivo por su nombre, porque un worker que los mete todos en la misma rama abandona pasos que ya había terminado o informa de un fallo donde hubo un éxito.
| Motivo | Qué significa | Qué hace el worker |
|---|---|---|
already_applied | El complete o el fail anteriores de este mismo lease sí llegaron, y se perdió su respuesta. | Lo toma como un éxito y no publica nada más. |
fenced | Existe un intento posterior, u otro lease terminó este. | Abandona el trabajo y no publica nada más por este lease. Un heartbeat rechazado así cancela además el cuerpo en marcha a través de su contexto. |
run_terminal | La ejecución ha terminado, sea por acabar, por fallar, por agotar su plazo o por haber sido cancelada. | Para, y deja de reclamar pasos de esa ejecución. |
not_started | Llegó un complete o un fail para un intento at_most_once cuyo started nunca llegó. | Lo denuncia sin disimulo como un error del worker. Los workers de los SDK no pueden producirlo; uno escrito a mano sí. |
El cuerpo
Un cuerpo recibe un contexto, su entrada renderizada y los pasos
anteriores. El contexto es donde informa de lo que el daemon debe guardar:
un parche al estado de la ejecución, que se fusiona en el
complete y donde las claves de primer nivel reemplazan; los
tokens que gastó, a los que el daemon pone precio con el mismo libro con el
que tarifa los spans; y una señal de cancelación que se dispara cuando se
pierde el lease o termina la ejecución, para que un cuerpo que se la pase a
su propia E/S pare cuando debe. Los tokens se suman solos: un span que el
cuerpo registre por el contexto, y todo span por debajo de él, añade su
consumo al del paso cuando acaba, así que un cuerpo que registra sus
llamadas al modelo como spans nunca suma a mano, y set_usage
es la anulación explícita para uno que no registra ninguno.
Un cuerpo acaba devolviendo su salida o lanzando un error. Lanza el error retryable del SDK para que se reintente dentro del presupuesto del paso; lanza un fallo de paso tipado para nombrar cualquier otra clase; lo demás es fatal. El mensaje se envía al daemon y se muestra en el diario. La traza de pila no.
Un lease de una definición, de una versión o de un paso que el worker no
guarda se falla como unservable y el cuerpo no llega a
ejecutarse. El intento vuelve a la cola para un worker que sí lo guarde,
sin gastar presupuesto de reintentos, y eso es lo que permite que dos
versiones de un worker convivan durante un despliegue.
Escalar en horizontal
Ejecuta tantos workers como quieras contra un mismo daemon. Cada uno reclama hasta su propia concurrencia, el daemon entrega cada intento a exactamente uno de ellos, y el fencing hace que un lease que dos workers sostengan a la vez, por lo que sea, se resuelva con un único ganador. El id del worker queda registrado en cada intento que tomó, así que el diario dice qué proceso ejecutó un paso. Dale un nombre a cada proceso, o deja que el SDK lo derive del host.
Una reclamación es un long poll, así que una flota inactiva le cuesta al daemon una solicitud abierta por worker y ningún tráfico. Mientras espera, una reclamación busca trabajo cuando arranca una ejecución, termina un paso, se dispara un plazo o un evento reanuda una espera, y en los demás casos como mucho dos veces por segundo. Una flota ocupada está acotada por la concurrencia de los propios workers, y un cuerpo que tarda una hora cuesta un heartbeat cada diez segundos y nada más.
Claves
Las llamadas de worker piden el ámbito worker; arrancar,
señalizar y cancelar ejecuciones piden execute. Los dos
recurren a las claves de escritura cuando no están definidos, y una clave
de administración tiene los dos. Un worker que solo sirve pasos no necesita
una clave capaz de arrancar una ejecución, y un webhook que solo publica
eventos no necesita una capaz de reclamar un paso. Consulta
Configuración para los flags.
Las llamadas de worker no quedan anotadas en el registro de auditoría, aunque un lease lleva la entrada del paso y las salidas de los pasos anteriores. Son un bucle de máquina que reclama muchas veces por minuto, y una fila por reclamación enterraría las lecturas que hizo una persona. Volver a leer una ejecución por los endpoints de ejecuciones sí queda anotado; consulta Seguridad.
A continuación
- Arrancar y seguir una ejecución, y responder a una espera: Ejecuciones.
- La definición que sirve el worker: Workflows.
- Todos los endpoints del protocolo de workers, generados a partir de la especificación: API HTTP, Workers.