Documentation for a newer release is available. View Latest

Comprender Passivation, Remembering Entities y Schedulers

Cuando los flujos de IPF están en progreso (es decir, aún no en un estado terminal), el comportamiento por defecto de IPF es retenerlos en memoria hasta que terminen. Los flujos se distribuyen más o menos uniformemente por el clúster con Akka Cluster Sharding.

Este artículo explica los conceptos de passivation, remembering entities y cómo reiniciar flujos fallidos.

Remembering Entities

Si un nodo muere o se detiene de forma ordenada (o si todos los nodos se detienen y la aplicación arranca en frío), entonces todos los flujos en curso (shards) se cargan de nuevo en memoria en el siguiente arranque. A esto se le llama remembering entities.

La forma en que Akka determina si un flujo está o no completado es almacenando también este estado en algún tipo de almacén. Las opciones para esto son eventsourced (el valor por defecto de IPF) o ddata. La opción eventsourced es el valor por defecto de IPF porque es más eficiente y es resiliente a reinicios totales del clúster sin configuración adicional.

Cualquier dato almacenado en ddata se guarda en disco por defecto, por lo que no puede sobrevivir a un reinicio total del clúster en un entorno containerizado como Kubernetes. En tales entornos tendrás que configurar un Persistent Volume para almacenar este estado y cambiar la configuración de IPF para apuntar la aplicación a ese volumen persistente.

Más información sobre los distintos almacenes de remember entities (y cómo configurarlos) aquí.

Passivation y Passivation automática

Por defecto, un flujo se considera "detenido" cuando el flujo determina que ha alcanzado un estado terminal y se detiene a sí mismo. A esto se le llama passivation.

Otras formas de detener un flujo —incluso si no ha terminado— son:

Si remembering entities está habilitado, la passivation automática no puede habilitarse también

Action Revival

Cuando un flujo se carga nuevamente en memoria con remembering entities después de que otro nodo se haya ido (o haya fallado), el flujo de IPF es notificado de esto y reacciona enviándose a sí mismo un ActionRecoveryCommand. Este comando especial intenta “empujar” al flujo para que realice la siguiente acción que necesita en función del último estado conocido. Más información sobre el tema está disponible aquí.

Comportamiento por defecto de IPF

El comportamiento por defecto de IPF es:

  • Recordar entidades con eventsourced como almacén

  • Passivation automática desactivada (debe estar desactivada si remember entities está activado)

  • Akka Scheduler

Este comportamiento por defecto es adecuado para flujos que encajan en el siguiente perfil:

  • De corta duración

  • Cualquier tipo de volumen (alto o bajo)

Sin embargo, este enfoque puede volverse problemático cuando hay muchos flujos de larga duración: si hay un paso que espera horas o días, entonces puede haber un gran número de flujos aparcados consumiendo memoria cuando no se necesitarán durante mucho tiempo.

Si este es el caso, podría valer la pena cambiar cómo se comporta el clúster:

  • Desactivando remembering entities

  • Habilitando passivation automática

  • Usando el Quartz Scheduler persistente de IPF para programar reintentos

Si se dispusieran en una tabla, las opciones serían:

ID Remember entities Auto-passivation Scheduler implementation Good for How node failures handled?

1

Enabled

Disabled

Akka (not persistent)

High- or low-volume flows that complete quickly (e.g. within a minute)

Flows are always held in memory and ActionRevival process (see above) is triggered when entities are restarted on another node after a failure or shutdown

2

Disabled

Enabled

Quartz (persistent)

Long-running flows

Retrying of tasks is managed separately to the flow itself and is also resilient against failures

Disparadores perdidos en el pasado

Si se usa la combinación 2 (remember entities desactivado/auto passivation activado/Quartz) entonces la implementación del Quartz Scheduler gestionará los reintentos de cualquier tarea relacionada con los flujos. Esto es resiliente porque también utiliza un Cluster Singleton para gestionar este trabajo y, en caso de fallo del nodo que mantiene el Cluster Singleton, el singleton se migra al siguiente nodo más antiguo y los trabajos se vuelven a encolar para reintento.

Este proceso solo debería tardar unos pocos segundos y, por lo tanto, para procesos de larga duración generalmente no es un gran problema. En el improbable caso de que un job se dispare mientras el Cluster Singleton está siendo migrado, actualmente se omite y se registra una advertencia para alertar al usuario de que se perdió un disparador durante la migración. Esta funcionalidad podría ser conmutable en el futuro si así se desea.

Consideraciones sobre uso de base de datos

Al arrancar y con remember entities habilitado, IPF leerá todas las entidades en curso leyendo todos sus eventos para construir el estado actual de ese flujo en particular. Esta es una operación intensiva en lectura y, para entornos donde el uso de base de datos se mida de tal manera (p. ej., Azure Cosmos DB), podría ser mejor idea usar la Combinación 2 para evitar este coste alto de arranque.

Configuración

Para configurar la Combinación 2 (no por defecto), establece las siguientes claves de configuración en ipf-impl.conf o application.conf:

# Disable remembering entities
akka.cluster.sharding.remember-entities = off
# Set the passivation strategy to be the default i.e. a maximum of active entities at any one time
akka.cluster.sharding.passivation.strategy = default-strategy

# Change the below if you want to use a separate MongoDB instance for persisting scheduled tasks
ipf-scheduler {
  mongodb = {
    uri = ${ipf.mongodb.uri}
    database-name = "scheduled"
  }
}

Retardo de passivation al alcanzar estado terminal

Cuando los ESB alcanzan un estado terminal se pasivan (se descargan de memoria). Sin embargo, en algunas circunstancias (como un comando retrasado o una consulta usando getAggregate en el controlador REST) un ESB será rehidratado. En estos casos, necesitamos asegurarnos de que el ESB sea pasivado para que no resida en memoria innecesariamente. Esto se hace programando una solicitud para pasivar el ESB; queremos retrasar la passivation para que, si se reciben algunos comandos en un corto período de tiempo, cada uno no requiera passivation y posterior rehidratación. Este retraso es configurable mediante:

application.conf

ipf.behaviour.config.final-state-passivate-delay=10s