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.