63.4. Consideraciones sobre el bloqueo de índices #

Los métodos de acceso a índices deben manejar las actualizaciones concurrentes del índice por parte de múltiples procesos. El sistema central de PostgreSQL obtiene AccessShareLock en el índice durante un escaneo de índice, y RowExclusiveLock al actualizar el índice (incluyendo el VACUUM simple). Dado que estos tipos de bloqueo no entran en conflicto, el método de acceso es responsable de manejar cualquier bloqueo detallado que pueda necesitar. Se tomará un bloqueo ACCESS EXCLUSIVE en el índice en su conjunto solo durante la creación del índice, la destrucción o el REINDEX (en su lugar se toma SHARE UPDATE EXCLUSIVE con CONCURRENTLY).

La construcción de un tipo de índice que admita actualizaciones concurrentes suele requerir un análisis exhaustivo y sutil del comportamiento requerido. Para los tipos de índice b-tree y hash, puedes leer sobre las decisiones de diseño involucradas en src/backend/access/nbtree/README y src/backend/access/hash/README.

Aparte de los propios requisitos de coherencia interna del índice, las actualizaciones concurrentes plantean problemas sobre la coherencia entre la tabla padre (el montón o heap) y el índice. Debido a que PostgreSQL separa los accesos y las actualizaciones del montón de los del índice, existen ventanas en las que el índice podría ser incoherente con el montón. Manejamos este problema con las siguientes reglas:

Sin la tercera regla, es posible que un lector de índice vea una entrada de índice justo antes de que sea eliminada por VACUUM, y luego llegue a la entrada del montón correspondiente después de que esta fuera eliminada por VACUUM. Esto no plantea problemas graves si ese número de elemento sigue sin usarse cuando el lector llega a él, ya que una ranura de elemento vacía será ignorada por heap_fetch(). Pero, ¿y si un tercer backend ya ha reutilizado la ranura de elemento para otra cosa? Cuando se utiliza una captura instantánea compatible con MVCC, no hay problema porque el nuevo ocupante de la ranura es seguro que es demasiado nuevo para pasar la prueba de la captura instantánea. Sin embargo, con una captura instantánea no compatible con MVCC (como SnapshotAny), sería posible aceptar y devolver una fila que de hecho no coincide con las claves de escaneo. Podríamos defendernos contra este escenario exigiendo que las claves de escaneo se vuelvan a comprobar contra la fila del montón en todos los casos, pero eso es demasiado costoso. En su lugar, utilizamos un anclaje en una página de índice como un proxy para indicar que el lector podría estar todavía en vuelo desde la entrada del índice a la entrada del montón coincidente. Hacer que ambulkdelete se bloquee en dicho anclaje garantiza que VACUUM no pueda eliminar la entrada del montón antes de que el lector haya terminado con ella. Esta solución cuesta poco en tiempo de ejecución, y añade sobrecarga de bloqueo solo en los raros casos en que realmente hay un conflicto.

Esta solución requiere que los escaneos de índices sean síncronos: tenemos que recuperar cada tupla del montón inmediatamente después de escanear la correspondiente entrada de índice. Esto es costoso por varias razones. Un escaneo asíncrono en el que recopilamos muchos TIDs del índice, y solo visitamos las tuplas del montón algún tiempo después, requiere mucho menos sobrecarga de bloqueo del índice y puede permitir un patrón de acceso al montón más eficiente. Según el análisis anterior, debemos usar el enfoque síncrono para las capturas instantáneas no compatibles con MVCC, pero un escaneo asíncrono es viable para una consulta que utiliza una captura instantánea MVCC.

En un escaneo de índice amgetbitmap, el método de acceso no mantiene un anclaje de índice en ninguna de las tuplas devueltas. Por lo tanto, solo es seguro usar tales escaneos con capturas instantáneas compatibles con MVCC.

Cuando la bandera ampredlocks no está establecida, cualquier escaneo que use ese método de acceso al índice dentro de una transacción serializable adquirirá un bloqueo de predicado no bloqueante en todo el índice. Esto generará un conflicto de lectura-escritura con la inserción de cualquier tupla en ese índice por parte de una transacción serializable concurrente. Si se detectan ciertos patrones de conflictos de lectura-escritura entre un conjunto de transacciones serializables concurrentes, una de esas transacciones puede ser cancelada para proteger la integridad de los datos. Cuando la bandera está establecida, indica que el método de acceso al índice implementa un bloqueo de predicado más detallado, lo que tenderá a reducir la frecuencia de tales cancelaciones de transacciones.