Chapter 46. Procesos de soporte en segundo plano (Background Workers)

PostgreSQL puede extenderse para ejecutar código suministrado por el usuario en procesos independientes. Dichos procesos son iniciados, detenidos y supervisados por postgres, lo que les permite tener un ciclo de vida estrechamente vinculado al estado del servidor. Estos procesos están conectados al área de memoria compartida de PostgreSQL y tienen la opción de conectarse a las bases de datos internamente; también pueden ejecutar múltiples transacciones de forma secuencial, al igual que un proceso de servidor normal conectado a un cliente. Además, al enlazarse con libpq, pueden conectarse al servidor y comportarse como una aplicación cliente normal.

Warning

Existen riesgos considerables de robustez y seguridad al utilizar procesos de soporte en segundo plano porque, al estar escritos en el lenguaje C, tienen acceso ilimitado a los datos. Los administradores que deseen habilitar módulos que incluyan procesos de soporte en segundo plano deben extremar las precauciones. Solo se debe permitir la ejecución de procesos en segundo plano a módulos cuidadosamente auditados.

Los procesos en segundo plano pueden inicializarse en el momento en que se inicia PostgreSQL incluyendo el nombre del módulo en shared_preload_libraries. Un módulo que desee ejecutar un proceso en segundo plano puede registrarlo llamando a RegisterBackgroundWorker(BackgroundWorker *worker) desde su función _PG_init(). Los procesos en segundo plano también se pueden iniciar después de que el sistema esté funcionando llamando a RegisterDynamicBackgroundWorker(BackgroundWorker *worker, BackgroundWorkerHandle **handle). A diferencia de RegisterBackgroundWorker, que solo se puede llamar desde dentro del proceso postmaster, RegisterDynamicBackgroundWorker debe llamarse desde un backend normal u otro proceso en segundo plano.

La estructura BackgroundWorker se define así:

typedef void (*bgworker_main_type)(Datum main_arg);
typedef struct BackgroundWorker
{
    char        bgw_name[BGW_MAXLEN];
    char        bgw_type[BGW_MAXLEN];
    int         bgw_flags;
    BgWorkerStartTime bgw_start_time;
    int         bgw_restart_time;       /* in seconds, or BGW_NEVER_RESTART */
    char        bgw_library_name[MAXPGPATH];
    char        bgw_function_name[BGW_MAXLEN];
    Datum       bgw_main_arg;
    char        bgw_extra[BGW_EXTRALEN];
    pid_t       bgw_notify_pid;
} BackgroundWorker;

bgw_name y bgw_type son cadenas que se utilizarán en los mensajes de log, listados de procesos y contextos similares. bgw_type debería ser el mismo para todos los procesos en segundo plano del mismo tipo, de modo que sea posible agrupar dichos procesos en un listado de procesos, por ejemplo. bgw_name, por otro lado, puede contener información adicional sobre el proceso específico. (Típicamente, la cadena para bgw_name contendrá el tipo de alguna manera, pero esto no es estrictamente obligatorio).

bgw_flags es una máscara de bits (con operación OR a nivel de bits) que indica las capacidades que requiere el módulo. Los valores posibles son:

BGWORKER_SHMEM_ACCESS

Solicita acceso a la memoria compartida. Este flag es obligatorio.

BGWORKER_BACKEND_DATABASE_CONNECTION

Solicita la capacidad de establecer una conexión a la base de datos a través de la cual pueda ejecutar posteriormente transacciones y consultas. Un proceso en segundo plano que utilice BGWORKER_BACKEND_DATABASE_CONNECTION para conectarse a una base de datos también debe conectar la memoria compartida mediante BGWORKER_SHMEM_ACCESS, o de lo contrario el inicio del proceso fallará.

bgw_start_time es el estado del servidor durante el cual postgres debe iniciar el proceso; puede ser uno de BgWorkerStart_PostmasterStart (iniciar tan pronto como postgres haya terminado su propia inicialización; los procesos que solicitan esto no son elegibles para conexiones a bases de datos), BgWorkerStart_ConsistentState (iniciar tan pronto como se haya alcanzado un estado consistente en un hot standby, permitiendo que los procesos se conecten a las bases de datos y ejecuten consultas de solo lectura), y BgWorkerStart_RecoveryFinished (iniciar tan pronto como el sistema haya entrado en el estado normal de lectura y escritura). Ten en cuenta que los dos últimos valores son equivalentes en un servidor que no es un hot standby. Ten en cuenta que esta configuración solo indica cuándo deben iniciarse los procesos; no se detienen cuando se alcanza un estado diferente.

bgw_restart_time es el intervalo, en segundos, que postgres debe esperar antes de reiniciar el proceso en caso de que se caiga. Puede ser cualquier valor positivo, o BGW_NEVER_RESTART, lo que indica que no se debe reiniciar el proceso en caso de caída.

bgw_library_name es el nombre de la biblioteca en la que se debe buscar el punto de entrada inicial para el proceso en segundo plano. La biblioteca especificada será cargada dinámicamente por el proceso y bgw_function_name se utilizará para identificar la función a llamar. Si se llama a una función en el código del núcleo (core), debe establecerse en "postgres".

bgw_function_name es el nombre de la función que se utilizará como punto de entrada inicial para el nuevo proceso en segundo plano. Si esta función está en una biblioteca cargada dinámicamente, debe estar marcada como PGDLLEXPORT (y no como static).

bgw_main_arg es el argumento Datum para la función principal del proceso en segundo plano. Esta función principal debe tomar un único argumento de tipo Datum y devolver void. Se pasará bgw_main_arg como argumento. Además, la variable global MyBgworkerEntry apunta a una copia de la estructura BackgroundWorker pasada en el momento del registro; al proceso puede resultarle útil examinar esta estructura.

En Windows (y en cualquier otro lugar donde esté definido EXEC_BACKEND) o en procesos en segundo plano dinámicos, no es seguro pasar un Datum por referencia, solo por valor. Si se requiere un argumento, lo más seguro es pasar un int32 u otro valor pequeño y usarlo como índice en un array asignado en la memoria compartida. Si se pasa un valor como cstring o text, el puntero no será válido desde el nuevo proceso en segundo plano.

bgw_extra puede contener datos adicionales para pasar al proceso en segundo plano. A diferencia de bgw_main_arg, estos datos no se pasan como argumento a la función principal del proceso, pero se puede acceder a ellos a través de MyBgworkerEntry, como se mencionó anteriormente.

bgw_notify_pid es el PID de un proceso backend de PostgreSQL al que el postmaster debe enviar SIGUSR1 cuando el proceso se inicia o finaliza. Debe ser 0 para los procesos registrados en el momento del inicio del postmaster, o cuando el backend que registra el proceso no desea esperar a que este se inicie. De lo contrario, debe inicializarse con MyProcPid.

Una vez en funcionamiento, el proceso puede conectarse a una base de datos llamando a BackgroundWorkerInitializeConnection(char *dbname, char *username, uint32 flags) o BackgroundWorkerInitializeConnectionByOid(Oid dboid, Oid useroid, uint32 flags). Esto permite al proceso ejecutar transacciones y consultas utilizando la interfaz SPI. Si dbname es NULL o dboid es InvalidOid, la sesión no se conecta a ninguna base de datos en particular, pero se puede acceder a los catálogos compartidos. Si username es NULL o useroid es InvalidOid, el proceso se ejecutará como el superusuario creado durante initdb. Si se especifica BGWORKER_BYPASS_ALLOWCONN como flags, es posible omitir la restricción de conectarse a bases de datos que no permiten conexiones de usuario. Si se especifica BGWORKER_BYPASS_ROLELOGINCHECK como flags, es posible omitir la comprobación de inicio de sesión para el rol utilizado para conectarse a las bases de datos. Un proceso en segundo plano solo puede llamar a una de estas dos funciones, y solo una vez. No es posible cambiar de base de datos.

Los procesos bloquean inicialmente las señales cuando el control llega a la función principal del proceso en segundo plano, y deben ser desbloqueadas por esta; esto es para permitir que el proceso personalice sus manejadores de señales, si es necesario. Las señales se pueden desbloquear en el nuevo proceso llamando a BackgroundWorkerUnblockSignals y bloquear llamando a BackgroundWorkerBlockSignals.

Si el parámetro bgw_restart_time para un proceso en segundo plano está configurado como BGW_NEVER_RESTART, o si finaliza con un código de salida de 0 o es terminado por TerminateBackgroundWorker, el postmaster lo desregistrará automáticamente al salir. De lo contrario, se reiniciará después del período de tiempo configurado a través de bgw_restart_time, o inmediatamente si el postmaster reinicializa el clúster debido a un fallo del backend. Los backends que necesiten suspender la ejecución solo temporalmente deben usar una espera interrumpible en lugar de salir; esto se puede lograr llamando a WaitLatch(). Asegúrate de que el flag WL_POSTMASTER_DEATH esté establecido al llamar a esa función, y verifica el código de retorno para una salida rápida en el caso de emergencia de que postgres mismo haya terminado.

Cuando se registra un proceso en segundo plano utilizando la función RegisterDynamicBackgroundWorker, es posible que el backend que realiza el registro obtenga información sobre el estado del proceso. Los backends que deseen hacer esto deben pasar la dirección de un BackgroundWorkerHandle * como segundo argumento a RegisterDynamicBackgroundWorker. Si el proceso se registra con éxito, este puntero se inicializará con un manejador opaco que posteriormente se puede pasar a GetBackgroundWorkerPid(BackgroundWorkerHandle *, pid_t *) o a TerminateBackgroundWorker(BackgroundWorkerHandle *). GetBackgroundWorkerPid se puede utilizar para sondear el estado del proceso: un valor de retorno de BGWH_NOT_YET_STARTED indica que el proceso aún no ha sido iniciado por el postmaster; BGWH_STOPPED indica que se ha iniciado pero ya no está en ejecución; y BGWH_STARTED indica que se está ejecutando actualmente. En este último caso, el PID también se devolverá a través del segundo argumento. TerminateBackgroundWorker hace que el postmaster envíe SIGTERM al proceso si se está ejecutando, y que lo desregistre tan pronto como deje de hacerlo.

En algunos casos, un proceso que registra un proceso en segundo plano puede desear esperar a que este se inicie. Esto se puede lograr inicializando bgw_notify_pid con MyProcPid y luego pasando el BackgroundWorkerHandle * obtenido en el momento del registro a la función WaitForBackgroundWorkerStartup(BackgroundWorkerHandle *handle, pid_t *). Esta función se bloqueará hasta que el postmaster haya intentado iniciar el proceso en segundo plano, o hasta que el postmaster muera. Si el proceso en segundo plano se está ejecutando, el valor de retorno será BGWH_STARTED, y el PID se escribirá en la dirección proporcionada. De lo contrario, el valor de retorno será BGWH_STOPPED o BGWH_POSTMASTER_DIED.

Un proceso también puede esperar a que un proceso en segundo plano se apague, utilizando la función WaitForBackgroundWorkerShutdown(BackgroundWorkerHandle *handle) y pasando el BackgroundWorkerHandle * obtenido al registrarse. Esta función se bloqueará hasta que el proceso en segundo plano salga o el postmaster muera. Cuando el proceso en segundo plano sale, el valor de retorno es BGWH_STOPPED, si el postmaster muere devolverá BGWH_POSTMASTER_DIED.

Los procesos en segundo plano pueden enviar mensajes de notificación asíncronos, ya sea utilizando el comando NOTIFY a través de SPI, o directamente a través de Async_Notify(). Dichas notificaciones se enviarán al confirmar la transacción. Los procesos en segundo plano no deben registrarse para recibir notificaciones asíncronas con el comando LISTEN, ya que no existe infraestructura para que un proceso consuma dichas notificaciones.

El módulo src/test/modules/worker_spi contiene un ejemplo funcional que demuestra algunas técnicas útiles.

El número máximo de procesos en segundo plano registrados está limitado por max_worker_processes.