Ir al contenido

Blog ·

El hijo despierta al padre

Un flujo que llama a otro flujo podía preguntar o ser avisado. Construimos ambos, y el reloj de reserva es un día y no cinco minutos por un motivo medido.

Por Provibr

Un flujo ya puede llamar a otro flujo. La ejecución que llama se detiene, la ejecución llamada arranca, y cuando la ejecución llamada termina, la que llamaba continúa por el cable ok o por el cable error.

Toda la cuestión de diseño de esa frase está en la palabra cuando. Algo tiene que darse cuenta de que el hijo ha terminado. Hay dos maneras de organizar eso y acabamos con las dos, en un orden concreto, y la segunda lleva dentro un número que dio bastante que discutir.

El padre no espera, se le avisa

La manera natural de escribir esto es un padre que espera. El padre sabe a qué ejecución espera, así que puede preguntar. Consultar cada diez segundos, ver si el hijo ha terminado, continuar cuando lo haya hecho.

Eso está al revés, y la razón es que el hijo ya lo sabe. Es él quien pasa a terminado. Hacer que pregunte el padre significa que la información viaja desde quien la tiene hasta quien la necesita a través de una pregunta repetida, y la frecuencia de esa pregunta es la latencia de la funcionalidad entera.

Así que el hijo anuncia. Cuando una ejecución hija alcanza un estado terminal, pone en ahora el momento de despertar de la fila de espera que apunta a ella, y el trabajador de cola habitual la recoge en su siguiente tic. Ese es exactamente el mismo mecanismo que usa una aprobación humana: tomar la decisión pone el momento de despertar en ahora. Una sola columna gobierna la cola, y deliberadamente no hay una segunda vía hacia el ejecutor.

Ese anuncio es una sola sentencia: actualizar la fila de espera donde el identificador de la ejecución hija coincide y el estado sigue siendo de espera.

Por qué ese identificador es una columna y no parte del bloque de estado

La continuación de un recorrido en pausa se guarda como JSON, y contiene todo lo que el recorrido necesita para reanudarse: la pila, los nodos visitados, las variables, los contadores. Habría sido coherente poner ahí también el identificador del hijo.

En vez de eso es una columna, y la razón es la direccionalidad. Todo lo demás de ese bloque lo lee el recorrido, desde dentro. Este único valor se lee desde fuera, por una ejecución hija que quiere encontrar la fila que apunta a ella. Dentro del bloque eso es un barrido sobre todas las esperas abiertas del sistema, y el índice que haría falta para abaratarlo es de todos modos esta columna.

La clave foránea está en SET NULL en lugar de CASCADE. Si la ejecución hija se limpia, la espera del padre no debe desaparecer con ella, porque entonces hay un padre suspendido sin nada que vaya a despertarlo nunca. Perder la referencia es soportable; perder la fila no.

El reloj de reserva es un día, y el número es el argumento

El anuncio es el mecanismo. El reloj es la red de seguridad para el caso en que el anuncio no llega nunca: un contenedor que se cayó a mitad de terminar el hijo, una fila tocada a mano.

El valor evidente para una red de seguridad es algo pequeño. Cinco minutos parece responsable.

Está equivocado, y la razón es lo que a un hijo le está permitido hacer. Una ejecución hija puede contener una aprobación humana, y una aprobación puede tener un plazo a treinta días vista. Con un reloj de cinco minutos, el padre de ese hijo despierta miles de veces a lo largo de esos treinta días para averiguar que nada ha cambiado, y cada uno de esos despertares es una petición HTTP de la pasarela al contenedor web.

Veinticuatro horas cuestan como mucho treinta tics inútiles a lo largo de la espera más larga que el producto permite, y acotan a un día el daño de un anuncio realmente perdido. Ese es el compromiso, enunciado en las unidades que importan: cuántas peticiones cuesta la red de seguridad cuando no pasa nada, frente a cuánto tiempo permanece invisible un fallo real.

La forma general: un intervalo de reintento no es «tan corto como nos podamos permitir». Es una función de la espera legítima más larga, y si no se ha preguntado cuál es esa, el intervalo es una conjetura.

Un reloj que no se puede acortar es un reloj que no se puede demostrar

Una red de seguridad solo se dispara cuando el mecanismo que hay debajo ha fallado. Con el intervalo fijado en un día, demostrar que funciona lleva un día, así que la ronda que la construyó entregó esa rama sin demostrar, etiquetada con honestidad.

La tentación es demostrarlo a mano: meterse en la base de datos, mover el momento de despertar al pasado, ver cómo se reanuda la ejecución. Eso demuestra la reanudación. No demuestra el reloj, porque el reloj es precisamente lo que se ha sustituido.

Así que el intervalo lee una variable de entorno, y el gancho vive en el código fuente junto a la constante y no dentro de un fichero de pruebas. Lo usa un script de extremo a extremo que lo pone en unos pocos segundos, deja que la red de seguridad se dispare de verdad y observa cómo un padre cuyo hijo no anunció nunca nada se recupera por su cuenta.

Dos propiedades hacen que ese gancho sea seguro de publicar. Solo puede hacer el intervalo más corto: el valor queda acotado contra la constante de producción, así que un entorno mal configurado nunca puede hacer que un padre espere más de un día. Y cualquier cosa que no sea un número positivo se repliega al valor de producción.

Esa segunda no es programación defensiva porque sí. Copiar nuestro fichero de entorno de ejemplo deja la variable presente y vacía, y una cadena vacía no es indefinida. Convertirla a número da cero. Sin el tamiz, un fichero de configuración copiado pondría el intervalo en cero y metería a todo padre con un hijo pendiente en un bucle de despertares contra el contenedor web. Ya nos mordió antes la distinción entre cadena vacía e indefinido, en un sitio donde le costó un 404 a una ruta de descarga.

Dos límites, cada uno impuesto dos veces

Un flujo que llama a un flujo plantea la cuestión de un flujo que se llama a sí mismo. Hay dos límites, recursión y profundidad, y cada uno se impone en dos sitios.

De forma estática, cuando se guarda un flujo: se recorre el grafo, se resuelven los flujos a los que llama, y un ciclo o una cadena demasiado profunda se rechaza con la cadena nombrada en el error. De forma dinámica, durante una ejecución: los mismos dos límites otra vez, con sus propios códigos de rechazo.

Los dos son necesarios porque los flujos se editan de forma independiente. Dos flujos que por separado están bien pueden convertirse en un ciclo en el momento en que alguien añade una llamada al segundo, y nada vuelve a validar el primero. La comprobación estática es la que da un mensaje de error útil en el momento en que alguien todavía puede arreglarlo; la comprobación dinámica es la que de verdad se sostiene.

La profundidad es una columna de la ejecución y no algo calculado recorriendo los padres. Eso va contra la línea que solemos seguir, que es que todo lo derivable debería derivarse. Aquí no se sostiene, porque la referencia al padre está en SET NULL: una vez que una ejecución padre se ha limpiado, la cadena no se puede recorrer hacia atrás y la profundidad de un hijo en marcha se volvería imposible de saber exactamente en el momento en que importa.

Qué midió la ronda siguiente

Las ramas que este diseño dejó abiertas se recorrieron en una ronda posterior, y dos de ellas son las interesantes.

Una ejecución hija que desaparece por completo: la espera conserva su fila y pierde su referencia, el reloj de reserva despierta al padre, y la reanudación no encuentra ningún hijo y hace fallar ese nodo con un motivo concreto en lugar de quedarse colgada. Un grafo que no se puede leer en absoluto produce un motivo distinto, así que las dos situaciones se distinguen en el registro de la ejecución.

Un subflujo dentro de un bucle, en las dos formas que se comportan de manera distinta: un hijo que termina dentro de la petición que lo originó, y un hijo que se suspende a sí mismo. El primero no toca la cola en absoluto. El segundo es la funcionalidad entera.

Lo que dejaríamos por escrito

  1. Quien sabe debería avisar, no ser preguntado. Consultar en bucle convierte la latencia en un valor de configuración.
  2. Reutilice la vía de despertar que ya tiene. Una decisión y un hijo terminado son el mismo suceso para la cola.
  3. Un valor que se lee desde fuera del bloque de estado va en una columna. Esto lo decide la direccionalidad, no la coherencia.
  4. SET NULL, no CASCADE, cuando la fila que se borraría es la única manera que alguien tiene de ser despertado.
  5. El intervalo de una red de seguridad es una función de la espera legítima más larga, no de lo receptivo que a uno le gustaría parecer.
  6. Enuncie el compromiso en peticiones y horas. Treinta tics desperdiciados frente a un día de invisibilidad es una decisión; «cinco minutos» es un reflejo.
  7. Una constante que no se puede variar es una rama que no se puede demostrar. El gancho va al lado de la constante.
  8. Deje que el gancho solo haga las cosas más seguras, y acótelo contra el valor de producción.
  9. Una variable de entorno vacía no es indefinida, y convertirla a número da cero.
  10. Compruebe un límite donde se puede arreglar y donde tiene que sostenerse. Los grafos editados de forma independiente necesitan las dos cosas.

Para que quede claro qué es esto: la verificación de la fusión recorrió 16 de 16 pasos sin ninguna línea de fallo, el conjunto de pruebas web dio 4583 en verde, paquetes, pasarela y herramientas juntos 7286 en verde, el recorrido 75 en verde, la comprobación estática de subflujos 13 en verde, la ejecución de extremo a extremo contra la base de datos de desarrollo 58 en verde, la medición de destinatarios 4 en verde, una prueba de navegador con un trabajador real de la pasarela 31 en verde, en la que una pausa de noventa segundos se reanudó a los noventa y cinco segundos, la prueba del editor 9 en verde y el barrido de accesibilidad no encontró nada en seis páginas. Sin demostrar en aquel momento: el reloj de reserva, un subflujo dentro de un bucle, una ejecución hija desaparecida y la rama de agotamiento de reintentos. Esas cuatro se recorrieron en la ronda siguiente.

Del trabajo de plataforma en Provibr. Cómo el recorrido se convirtió en algo que se puede escribir en una fila está en toda llamada a sí misma estaba en posición de cola, y la ronda que por fin alcanzó las ramas que esta dejó abiertas está en en verde porque no podía llegar hasta allí.

Empiece con un host y un agente

Cree una cuenta, instale el agente con un solo comando y conecte su primer host de Proxmox. El periodo de prueba dura 14 días y no pide ningún dato de pago.