58.5. Bloqueo de filas en adaptadores de datos externos #

Si el mecanismo de almacenamiento subyacente de un FDW tiene un concepto de bloqueo de filas individuales para evitar actualizaciones concurrentes de esas filas, por lo general vale la pena que el FDW realice el bloqueo a nivel de fila con una aproximación lo más cercana posible a la semántica utilizada en las tablas ordinarias de PostgreSQL. Hay múltiples consideraciones involucradas en esto.

Una decisión clave a tomar es si se realiza el bloqueo temprano (early locking) o el bloqueo tardío (late locking). En el bloqueo temprano, una fila se bloquea cuando se recupera por primera vez del almacenamiento subyacente, mientras que en el bloqueo tardío, la fila se bloquea solo cuando se sabe que debe ser bloqueada. (La diferencia surge porque algunas filas pueden ser descartadas por condiciones de restricción o de unión comprobadas localmente). El bloqueo temprano es mucho más simple y evita viajes de ida y vuelta adicionales a un almacenamiento remoto, pero puede causar el bloqueo de filas que no tendrían por qué haber sido bloqueadas, lo que resulta en una reducción de la concurrencia o incluso en bloqueos mutuos (deadlocks) inesperados. Además, el bloqueo tardío solo es posible si la fila a bloquear se puede volver a identificar de forma única más adelante. Preferiblemente, el identificador de fila debería identificar una versión específica de la fila, como lo hacen los TID de PostgreSQL.

Por defecto, PostgreSQL ignora las consideraciones de bloqueo cuando se comunica con los FDW, pero un FDW puede realizar un bloqueo temprano sin ningún soporte explícito del código principal. Las funciones de la API descritas en la Section 58.2.6, que se añadieron en PostgreSQL 9.5, permiten a un FDW utilizar el bloqueo tardío si lo desea.

Una consideración adicional es que en el modo de aislamiento READ COMMITTED, PostgreSQL puede necesitar volver a comprobar las condiciones de restricción y unión contra una versión actualizada de alguna tupla de destino. Volver a comprobar las condiciones de unión requiere volver a obtener copias de las filas que no son de destino que se unieron previamente a la tupla de destino. Al trabajar con tablas estándar de PostgreSQL, esto se hace incluyendo los TID de las tablas que no son de destino en la lista de columnas proyectada a través de la unión, y luego volviendo a obtener las filas que no son de destino cuando sea necesario. Este enfoque mantiene compacto el conjunto de datos de la unión, pero requiere una capacidad de reobtención económica, así como un TID que pueda identificar de forma única la versión de la fila a volver a obtener. Por lo tanto, por defecto, el enfoque utilizado con las tablas externas es incluir una copia de toda la fila obtenida de una tabla externa en la lista de columnas proyectada a través de la unión. Esto no impone demandas especiales al FDW, pero puede resultar en un rendimiento reducido de las uniones de tipo merge y hash. Un FDW que sea capaz de cumplir con los requisitos de reobtención puede optar por hacerlo de la primera manera.

Para un UPDATE o un DELETE en una tabla externa, se recomienda que la operación ForeignScan en la tabla de destino realice un bloqueo temprano en las filas que recupera, quizás a través del equivalente de SELECT FOR UPDATE. Un FDW puede detectar si una tabla es un objetivo de UPDATE/DELETE en tiempo de planificación comparando su relid con root->parse->resultRelation, o en tiempo de ejecución utilizando ExecRelationIsTargetRelation(). Otra posibilidad alternativa es realizar un bloqueo tardío dentro de la retrollamada ExecForeignUpdate o ExecForeignDelete, pero no se proporciona soporte especial para esto.

Para las tablas externas que se especifican para ser bloqueadas por un comando SELECT FOR UPDATE/SHARE, la operación ForeignScan puede nuevamente realizar un bloqueo temprano recuperando las tuplas con el equivalente de SELECT FOR UPDATE/SHARE. Para realizar un bloqueo tardío en su lugar, proporciona las funciones de retrollamada definidas en la Section 58.2.6. En GetForeignRowMarkType, selecciona la opción rowmark ROW_MARK_EXCLUSIVE, ROW_MARK_NOKEYEXCLUSIVE, ROW_MARK_SHARE o ROW_MARK_KEYSHARE según la fuerza de bloqueo solicitada. (El código principal actuará de la misma manera independientemente de cuál de estas cuatro opciones elijas). En otros lugares, puedes detectar si se especificó que una tabla externa se bloqueara por este tipo de comando utilizando get_plan_rowmark en el tiempo de planificación, o ExecFindRowMark en el tiempo de ejecución; debes comprobar no solo si se devuelve una estructura rowmark no nula, sino que su campo strength no sea LCS_NONE.

Por último, para las tablas externas que se utilizan en un comando UPDATE, DELETE o SELECT FOR UPDATE/SHARE pero no se especifica que se bloqueen por filas, puedes anular la opción por defecto de copiar filas completas haciendo que GetForeignRowMarkType seleccione la opción ROW_MARK_REFERENCE cuando vea la fuerza de bloqueo LCS_NONE. Esto hará que se llame a RefetchForeignRow con ese valor para markType; luego debería volver a obtener la fila sin adquirir ningún nuevo bloqueo. (Si tienes una función GetForeignRowMarkType pero no deseas volver a obtener filas desbloqueadas, selecciona la opción ROW_MARK_COPY para LCS_NONE).

Consulta src/include/nodes/lockoptions.h, los comentarios para RowMarkType y PlanRowMark en src/include/nodes/plannodes.h, y los comentarios para ExecRowMark en src/include/nodes/execnodes.h para obtener información adicional.