Fantásticas cerraduras de asesoramiento y dónde viven

PostgreSQL tiene un mecanismo muy conveniente para bloqueos de aviso , también son bloqueos de aviso . En Tensor los usamos en muchos lugares del sistema, pero pocas personas entienden en detalle cómo funcionan exactamente y qué problemas se pueden obtener si se maltratan .



Leer más sobre cerraduras

Ventajas de las cerraduras de recomendación


La diferencia fundamental entre este mecanismo y los bloqueos de nivel de tabla / página / registro "ordinarios" es que hay varias características clave.

Bloqueo de ID personalizado


Los bloqueos "normales" en PG siempre están vinculados a un objeto de base de datos específico (tabla, registro, página de datos) y al proceso que sirve la conexión. Los bloqueos de aviso también son un proceso, pero en lugar de un objeto real, se puede establecer un identificador abstracto como (bigint) o como (entero, entero) .

Por cierto, adjuntar cada bloqueo a un proceso significa que al nombrarlo pg_terminate_backend(pid)o completar correctamente la conexión desde el lado del cliente, puede deshacerse de todos los bloqueos impuestos por él.

Comprobación de CAS para captura de bloqueo


CAS es un sistema de comparación y configuración , es decir, la verificación de la capacidad de captura y la captura de la cerradura en sí tienen lugar como una operación atómica, y obviamente nadie puede separarse entre ellas.

Es decir, si primero realiza una solicitud de verificación para pg_locks , mira el resultado, luego decide si bloquear o no, entonces nadie garantiza que entre estas operaciones nadie logrará tomar el objeto que necesita. Pero si usa pg_try_advisory_lock , recibirá inmediatamente este bloqueo o la función simplemente regresará FALSE.

Sin captura sin excepciones y expectativas


Existen bloqueos "normales" en el modelo "Si ya solicitó un bloqueo, espere. Si no desea esperar ( NOWAIT, statement_timeout, lock_timeout) - aquí es una excepción " . Este enfoque interfiere en gran medida dentro de la transacción, porque entonces debe implementar un bloque BEGIN-EXCEPTION-ENDpara procesar o revertir ( ROLLBACK) la transacción.

La única forma de evitar este comportamiento es usar el diseño SELECT ... SKIP LOCKEDque viene con la versión 9.5. Desafortunadamente, con este método, las opciones "no tenían en absoluto qué bloquear" y "estaba, pero ya estaba bloqueado" se vuelven indistinguibles.

Los bloqueos recomendados causados ​​por las funciones de prueba simplemente regresan TRUE/FALSE.
No confunda pg_advisory_locky - la primera función esperará hasta que reciba un bloqueo, y la segunda simplemente regresará de inmediato si es imposible capturarla "en este momento".pg_try_advisory_lockFALSE

Bloqueos dentro y después de la transacción.


Como mencioné anteriormente, los bloqueos de objetos están "adjuntos" al proceso y existen solo como parte de la transacción actual en él. Incluso para imponer, no tendrá éxito:

LOCK TABLE tbl;
-- ERROR:  LOCK TABLE can only be used in transaction blocks

En consecuencia, al final de la transacción, se eliminan todos los bloqueos impuestos. Por el contrario, los bloqueos de aviso se diseñaron originalmente con la capacidad de mantener bloqueos y fuera del alcance de una transacción :

SELECT pg_advisory_lock(1);
SELECT * FROM pg_locks WHERE pid = pg_backend_pid() AND locktype = 'advisory';

-[ RECORD 1 ]------+--------------
locktype           | advisory
database           | 263911484
relation           |
page               |
tuple              |
virtualxid         |
transactionid      |
classid            | 0 <--  int4 #1    int8
objid              | 1 <--  int4 #2    int8
objsubid           | 1
virtualtransaction | 416/475768
pid                | 29264
mode               | ExclusiveLock
granted            | t
fastpath           | f

Pero ya desde la versión 9.1, han aparecido versiones xact de funciones de asesoramiento que le permiten implementar el comportamiento de bloqueos "ordinarios" que se liberan automáticamente cuando se completa la transacción que los impuso.

Ejemplos de uso en VLSI


En realidad, como cualquier otra cerradura, el asesoramiento sirve para garantizar la singularidad del procesamiento de un recurso. En nuestro país, dichos recursos suelen ser la tabla completa o un registro específico de la tabla, que por alguna razón no quiere "bloquearse".

Monoprocesamiento de trabajadores


Si la necesidad de procesar algunos datos en la base de datos se desencadena por un evento externo, pero el procesamiento multiproceso es redundante o puede conducir a una condición de carrera , es razonable permitir que solo un proceso trabaje en estos datos a la vez .

Para hacer esto, intentaremos bloquear con el identificador de la tabla como primer parámetro y la identificación de procesamiento de la aplicación específica como el segundo:

SELECT pg_try_advisory_lock(
  'processed_table'::regclass::oid
, -1 --   worker'
);

Si regresamos FALSE, entonces alguien más ya está sosteniendo ese bloqueo, y específicamente este proceso no necesita hacer nada, pero es mejor que termine en silencio. Así es como, por ejemplo, funciona el proceso de cálculo dinámico del costo de los bienes en un almacén , de forma aislada para cada esquema de cliente individual.

Procesamiento simultáneo de colas


Ahora la tarea es "lo opuesto": queremos que las tareas en alguna tabla de cola se procesen lo más rápido posible, multiproceso, tolerante a fallas e incluso desde diferentes lógicas comerciales (bueno, carecemos del poder de una), por ejemplo, como hace nuestro operador sobre la transferencia de informes electrónicos a agencias gubernamentales o al servicio OFD .

Dado que los BL de "procesamiento" son servidores diferentes, ya no se puede colgar mutex. No es seguro señalar a un coordinador de proceso especial de distribución de tareas, "morir", y todo aumentará. Y resulta que es más eficiente distribuir tareas directamente en el nivel de la base de datos, y ese método existe: en la antigüedad, Dmitry Koterov espiaba honestamente el modelo y luego lo modificaba creativamente.

En este caso, imponemos un bloqueo en la tabla ID y PK de un registro particular:

SELECT
  *
FROM
  queue_table
WHERE
  pg_try_advisory_lock('queue_table'::regclass::oid, pk_id)
ORDER BY
  pk_id
LIMIT 1;

Es decir, el proceso recibirá de la mesa el primer registro que aún no ha sido bloqueado por sus hermanos competidores.

Sin embargo, si PK no consiste en (entero) , sino en (entero, entero) (como en el mismo cálculo de costo, por ejemplo), puede imponer un bloqueo directamente en este par; es poco probable que ocurra una intersección con el "competidor".

¡Importante! ¡No olvide mantener periódicamente su tabla de colas correctamente !

Procesamiento exclusivo de documentos


Se utiliza en todas partes en soluciones de gestión de documentos . De hecho, en un sistema web distribuido, se puede abrir el mismo documento para que lo vean diferentes usuarios al mismo tiempo, pero solo se puede procesar (cambiar su estado, etc.) en cualquier momento por uno solo.

Problemas tradicionales


Donde sin ellos! Casi todo se reduce a una cosa : no desbloquearon lo que bloquearon .

Superposición múltiple de una cerradura de aviso


RTFM , como dicen:
Si se reciben varias solicitudes de bloqueo a la vez, se acumulan, por lo que si un recurso se ha bloqueado tres veces, debe desbloquearse tres veces para estar disponible en otras sesiones.

Pon demasiadas cerraduras a la vez




Miles de ellos! Lea el manual nuevamente :
Tanto los bloqueos de aviso como los normales se almacenan en el área de memoria compartida, cuyo tamaño está determinado por los parámetros de configuración max_locks_per_transactiony max_connections. Es importante que esta memoria sea suficiente, porque de lo contrario el servidor no podrá emitir ningún bloqueo . Por lo tanto, el número de bloqueos recomendados que puede emitir un servidor generalmente se limita a decenas o cientos de miles, según la configuración del servidor.

En general, si surge una situación en la que desea imponer varios miles de bloqueos de aviso (incluso si los elimina correctamente más adelante), piense muy bien dónde se ejecutará cuando se levante el servidor.

Fugas al filtrar registros


Aquí tomamos la solicitud anterior y agregamos una condición inofensiva, como la ID de verificación de paridad AND pk_id % 2 = 0. ¡Ambas condiciones para cada entrada serán verificadas ! Como resultado, se pg_try_advisory_lockcompletó, el bloqueo se superpuso y luego el registro se filtró por paridad.



O una opción del manual:
SELECT pg_advisory_lock(id) FROM foo WHERE id = 12345; -- ok
SELECT pg_advisory_lock(id) FROM foo WHERE id > 12345 LIMIT 100; -- !

Eso es todo: el bloqueo permanece , pero no somos conscientes de esto. Se trata con las solicitudes correctas, en el peor de los casos pg_advisory_unlock_all.

Oh confundido!


Clásicos del género ...

Confundir y preguntarse por qué funciona durante tanto tiempo. Pero porque la versión sin prueba está esperando . Confunda y pregúntese a dónde se fue la cerradura, y "terminó" con la transacción. Y la transacción consistió en una solicitud, porque en ninguna parte se declaró "explícitamente", sí.pg_try_advisory_lockpg_advisory_lock

pg_try_advisory_lockpg_try_advisory_xact_lock

Trabaja a través de pgbouncer


Esta es una fuente separada de dolor para muchos cuando, en aras del rendimiento, trabajar con la base de datos pasa por pgbouncer en modo de transacción .

Esto significa que dos de sus transacciones vecinas que se ejecutan en la misma conexión a la base de datos (que en realidad pasa por el pgbouncer) se pueden ejecutar en diferentes conexiones "físicas" en el lado base. Y tienen sus propias cerraduras ... Cada



una tiene algunas opciones:

  • o ir a trabajar a través de una conexión directa a la base de datos
  • o invente un algoritmo para que todos los bloqueos de aviso solo estén dentro de la transacción (xact)

Eso es todo por ahora.

All Articles