transacciones tipos terminar soporta pueden postgres pgsql iniciar create control begin postgresql triggers transactions notifications deferred-execution

postgresql - tipos - rollback postgres



Ejecutar un activador DESPUÉS de la finalización de una transacción (2)

En PostgreSQL, ¿los desencadenadores DEFERRED se ejecutan antes (dentro) de la finalización de la transacción o justo después?

La documentación dice:

DEFERRABLE
NOT DEFERRABLE

Esto controla si la restricción se puede aplazar. Una restricción que no es diferible se verificará inmediatamente después de cada comando. La verificación de las restricciones que son diferibles se puede posponer hasta el final de la transacción (utilizando el comando SET CONSTRAINTS ).

No especifica si todavía está dentro de la transacción o fuera. ¡Mi experiencia personal dice que está dentro de la transacción y necesito que esté afuera!

¿Se ejecutan disparadores DEFERRED (o INITIALLY DEFERRED ) dentro de la transacción? Y si lo son, ¿cómo puedo posponer su ejecución hasta el momento en que se completa la transacción?

Para darle una pista de lo que estoy pg_notify , estoy usando pg_notify y RabbitMQ ( PostgreSQL LISTEN Exchange ) para enviar mensajes. Proceso esos mensajes en una aplicación externa. En este momento tengo un disparador que notifica a la aplicación externa de los registros recién insertados al incluir la identificación del registro en el mensaje. Pero de forma no determinista, de vez en cuando, cuando trato de seleccionar un registro por su id en la mano, no se puede encontrar el registro. Esto se debe a que la transacción aún no está completa y el registro en realidad no se agregó a la tabla. Si solo puedo posponer la ejecución del desencadenante después de la finalización de la transacción, todo saldrá bien.

Para obtener mejores respuestas permítanme explicar la situación aún más cerca del mundo real. El escenario real es un poco más complicado de lo que expliqué antes. El código fuente se puede encontrar aquí si alguien está interesado. Debido a razones que no voy a investigar, tengo que enviar la notificación desde otra base de datos para que la notificación se envíe como:

PERFORM * FROM dblink(''hq'',''SELECT pg_notify('''''' || channel || '''''', '''''' || payload || '''''')'');

Lo que estoy seguro hace que la situación sea mucho más complicada.


Estoy publicando esto como una respuesta, suponiendo que el problema real que intentas resolver es posponer la ejecución de un proceso externo hasta que se complete la transacción (en lugar del "problema" XY que intentas resolver usando el disparador Kung Fu) .

Hacer que la base de datos le diga a una aplicación que haga algo es un patrón roto. Está roto porque:

  1. No hay ninguna alternativa si la aplicación no recibe el mensaje, por ejemplo, porque está inactivo, la red explota, lo que sea. Incluso la aplicación que responde con un acuse de recibo (que no puede), no solucionaría este problema (consulte el siguiente punto)
  2. No hay una manera sensata de volver a intentar el trabajo si la aplicación recibe el mensaje pero no lo completa (por muchas razones)

Por el contrario, el uso de la base de datos como cola persistente y que la aplicación lo sondee para el trabajo, y retire el trabajo de la cola cuando se completa el trabajo, no tiene ninguno de los problemas anteriores.

Hay muchas formas de lograr esto. El que prefiero es tener algún proceso (generalmente desencadenar en insertar, actualizar y eliminar) poner los datos en una tabla de "cola". Haga otro sondeo de proceso para que la haga la tabla y elimínelo de la tabla cuando el trabajo esté completo.

También agrega algunos otros beneficios:

  • La producción y el consumo del trabajo están desacoplados, lo que significa que puede matar y reiniciar su aplicación de forma segura (lo que debe suceder de vez en cuando, por ejemplo, implementar): la tabla de cola crecerá felizmente mientras la aplicación está inactiva y se agotará cuando la aplicación está de vuelta. Incluso puedes reemplazar la aplicación por una completamente nueva
  • Si por algún motivo desea iniciar el procesamiento de ciertos elementos, puede simplemente insertar filas manualmente en la tabla de cola. Usé esta técnica yo mismo para iniciar el procesamiento de todos los elementos en una base de datos que necesitaba inicialización poniéndolos en la cola una vez. Es importante destacar que no tuve que hacer una actualización superficial para cada fila solo para disparar el disparador
  • Al llegar a su pregunta, se puede introducir un pequeño retraso agregando una columna de marca de tiempo a la tabla de cola y haciendo que la consulta de sondeo solo seleccione filas que son más antiguas que (digamos) 1 segundo, lo que da tiempo a la base de datos para completar su transacción
  • No puedes sobrecargar la aplicación. La aplicación solo leerá todo el trabajo que pueda manejar. Si su cola está creciendo, necesita una aplicación más rápida o más aplicaciones. Si hay varios consumidores operando, la concurrencia puede resolverse (por ejemplo) agregando una columna "token" a la tabla de cola.

Las colas que están respaldadas por tablas de bases de datos son la base de cómo se implementan las colas persistentes en las plataformas basadas en colas de calidad comercial, por lo que el patrón está bien probado, utilizado y comprendido.

Deje la base de datos para hacer lo que mejor hace, y lo único que hace bien: Administrar datos. No intente convertir su servidor de base de datos en un servidor de aplicaciones.


Disparadores (incluyendo todo tipo de desencadenantes diferidos) disparan dentro de la transacción.

Pero ese no es el problema aquí, porque las notificaciones se entregan de todas maneras.

El manual de NOTIFY :

NOTIFY interactúa con las transacciones de SQL de algunas formas importantes. En primer lugar, si se ejecuta una NOTIFY dentro de una transacción, los eventos de notificación no se entregan hasta y a menos que la transacción se haya comprometido. Esto es apropiado, ya que si se cancela la transacción, todos los comandos dentro de ella no han tenido ningún efecto, incluido NOTIFY . Pero puede ser desconcertante si se espera que los eventos de notificación se entreguen de inmediato. En segundo lugar, si una sesión de escucha recibe una señal de notificación mientras está dentro de una transacción, el evento de notificación no se entregará a su cliente conectado hasta justo después de que se complete la transacción (confirmada o cancelada). Una vez más, el razonamiento es que si se entregó una notificación dentro de una transacción que luego se anuló, uno desearía que la notificación se deshaga de alguna manera, pero el servidor no puede "recuperar" una notificación una vez que la ha enviado al cliente. Entonces los eventos de notificación solo se entregan entre transacciones. El resultado de esto es que las aplicaciones que utilizan NOTIFY para la señalización en tiempo real deben tratar de mantener sus transacciones cortas.

Negrita énfasis mío.

pg_notify() es simplemente una función de contenedor conveniente para el comando SQL NOTIFY .

Si no se pueden encontrar algunas filas después de recibir una notificación, debe haber una causa diferente. Ve a buscarlo. Posibles candidatos:

  • Las transacciones concurrentes interfieren
  • Dispara haciendo algo más o diferente de lo que crees que hacen.
  • Todo tipo de errores de programación

De cualquier manera, como sugiere el manual, es una buena idea mantener cortas las transacciones que envían notificaciones.

dblink

En cuanto a su adición posterior:

PERFORM * FROM dblink(''hq'',''SELECT pg_notify('''''' || channel || '''''', '''''' || payload || '''''')'');

... que debería reescribirse con format() para simplificar y asegurar la sintaxis:

PRERFORM dblink(''hq'', format(''NOTIFY %I, %L'', channel, payload));

dblink cambia el juego aquí, porque abre una transacción separada en la otra base de datos. Esto a veces se usa para falsificar transacciones autónomas .

dblink() espera a que termine el comando remoto. Entonces, la transacción remota probablemente se comprometerá primero. El manual :

La función devuelve la (s) fila (s) producida (s) por la consulta.

Si puede enviar notificaciones de la misma transacción , esa sería una solución limpia .

Solución para dblink

Si las notificaciones se tienen que enviar desde una transacción diferente, hay una solución con dblink_send_query() :

dblink_send_query envía una consulta que se ejecutará de forma asíncrona, es decir, sin esperar inmediatamente el resultado.

DO -- or plpgsql function $$ BEGIN -- do stuff PERFORM dblink_connect (''hq'', ''your_connstr_or_foreign_server_here''); PERFORM dblink_send_query(''con1'', format(''SELECT pg_sleep(3); NOTIFY %I, %L '', ''Channel'', ''payload'')); PERFORM dblink_disconnect(''con1''); END $$;

Si hace esto justo antes del final de la transacción, su transacción local obtiene 3 segundos ( pg_sleep(3) ) para comenzar a comprometerse. Elija una cantidad adecuada de segundos.

Existe una incertidumbre inherente a este enfoque, ya que no aparece ningún mensaje de error si algo sale mal. Para una solución segura necesita un diseño diferente. Sin embargo, después de enviar con éxito el comando, las posibilidades de que falle aún son extremadamente escasas. La posibilidad de que se pierdan las notificaciones exitosas parece mucho más alta, pero eso ya está incorporado en su solución actual.

Alternativa segura

Una alternativa más segura sería escribir en una tabla de cola y sondearla como se discutió en la respuesta de @Bohemian . Esta respuesta relacionada demuestra cómo sondear de forma segura: