

# Cómo evitar problemas de rendimiento relacionados con REPLICA IDENTITY FULL en Aurora PostgreSQL
<a name="PostgreSQL.ReplicaIdentityFull"></a>

La replicación lógica de PostgreSQL requiere que cada tabla publicada tenga una *identidad de réplica* para que el suscriptor pueda localizar la fila correcta para actualizarla o eliminarla. De forma predeterminada, la clave principal sirve como identidad de réplica. Cuando una tabla no tiene una clave principal o un índice único adecuado, puede establecer la identidad de réplica en `FULL`, lo que hace que PostgreSQL utilice toda la fila como clave.

Si bien `REPLICA IDENTITY FULL` resuelve el problema inmediato de replicación de tablas sin claves principales, puede provocar graves problemas de rendimiento tanto para el publicador como para el suscriptor. Comprender estos impactos es importante para cualquiera que utilice la replicación lógica con Aurora PostgreSQL, incluidas las características que se basan en la replicación lógica interna, como, por ejemplo, las implementaciones azul/verde.

## Por qué REPLICA IDENTITY FULL provoca problemas
<a name="PostgreSQL.ReplicaIdentityFull.WhyProblems"></a>

### Aumento del volumen de WAL del publicador
<a name="PostgreSQL.ReplicaIdentityFull.WALVolume"></a>

El ajuste `REPLICA IDENTITY` controla la información que PostgreSQL escribe en el registro de escritura anticipada (WAL) para identificar las filas que se actualizan o se eliminan. Con la identidad de réplica predeterminada (clave principal), solo las columnas clave se registran como la identidad de fila anterior. Con `FULL`, PostgreSQL registra los valores antiguos *de* cada columna para cada valor `UPDATE` y `DELETE`. Esto tiene varias consecuencias:
+ **El tamaño de WAL aumenta considerablemente.** En el caso de las actualizaciones, el tamaño de cada registro de WAL se duplica aproximadamente, ya que se registran los valores antiguos y nuevos de cada columna. Si la tabla incluye valores grandes almacenados mediante [TOAST](https://www.postgresql.org/docs/current/storage-toast.html), el aumento puede ser mucho mayor, ya que los valores almacenados mediante TOAST deben buscarse y escribirse en el WAL incluso si la actualización no los ha modificado.
+ **Mayor uso de E/S y de la CPU en el publicador.** Las escrituras de WAL adicionales consumen más ancho de banda de E/S del disco y ciclos de CPU, especialmente para cargas de trabajo que requieren mucha escritura.
+ **Se envían más datos a los suscriptores.** El publicador debe transmitir registros de WAL de mayor tamaño a través de la red a cada suscriptor, lo que aumenta el consumo de ancho de banda.

### Búsquedas lentas en las filas del suscriptor
<a name="PostgreSQL.ReplicaIdentityFull.SlowLookups"></a>

Cuando el suscriptor recibe un registro de `UPDATE` o `DELETE`, debe buscar la fila que coincida en su copia local de la tabla. Con `REPLICA IDENTITY FULL`, el suscriptor busca una fila que coincida con *todos* los valores de columna de la imagen de fila anterior.

La forma en que PostgreSQL realiza esta búsqueda varía según la versión principal de PostgreSQL:
+ **Antes de PostgreSQL 16:** si la tabla no tiene una clave principal ni un índice de identidad de réplica configurado explícitamente, el suscriptor realiza un escaneo secuencial de toda la tabla para cada operación `UPDATE` o `DELETE`. En tablas grandes, esto hace que el rendimiento de la aplicación sea extremadamente lento.
+ **PostgreSQL 16 y versiones posteriores:** el suscriptor puede usar un índice hash o de btree para las búsquedas de filas, incluso si ese índice no se encuentra establecido explícitamente como la identidad de réplica. Sin embargo, el suscriptor no evalúa qué índice es el más eficiente. [A partir de la versión 16](https://git.postgresql.org/gitweb/?p=postgresql.git;a=commitdiff;h=89e46da5e), PostgreSQL selecciona [el primer índice adecuado](https://www.postgresql.org/docs/18/logical-replication-publication.html#LOGICAL-REPLICATION-PUBLICATION-REPLICA-IDENTITY) que encuentra, y el usuario no tiene control sobre esta elección. Si el índice seleccionado tiene una selectividad baja (por ejemplo, un índice en una columna de estado o de tipo booleano), la búsqueda de filas puede ser casi tan lenta como un escaneo secuencial. Por este motivo, confiar en la selección implícita de índices con `REPLICA IDENTITY FULL` no es fiable y debe considerarse una configuración alternativa, no una configuración recomendada.

### Cómo REPLICA IDENTITY FULL provoca un retraso de la replicación
<a name="PostgreSQL.ReplicaIdentityFull.ReplicationLag"></a>

Los dos problemas que se han descrito anteriormente (un mayor WAL en el publicador y una búsqueda de filas más lenta en el suscriptor) se combinan para provocar un retraso de la replicación.

De forma predeterminada, la replicación lógica de PostgreSQL utiliza un único proceso de *trabajo de aplicación* por suscripción para recibir los cambios del publicador y aplicarlos a las tablas del suscriptor. El trabajo de aplicación procesa los cambios en serie, una fila a la vez, por orden de confirmación. Esto significa que el rendimiento del suscriptor está limitado por la rapidez con la que puede aplicar cada cambio individual.

Cuando la opción `REPLICA IDENTITY FULL` se encuentra establecida en una tabla sin un índice adecuado, es necesario realizar un escaneo secuencial de toda la tabla en cada `UPDATE` y `DELETE` para encontrar la fila que coincida. Si la tabla tiene millones de filas, cada una de estas operaciones puede tardar unos segundos o más. El resultado es un problema en cascada:

1. **El publicador genera los cambios de forma más rápida que el suscriptor puede aplicarlos.** La carga de trabajo de escritura del publicador continúa a una velocidad normal, pero el trabajo de aplicación del suscriptor sufre un cuello de botella por los escaneos secuenciales o los índices seleccionados de forma deficiente en cada búsqueda de filas.

1. **El WAL se acumula en el publicador y puede agotar el espacio de almacenamiento.** PostgreSQL no puede recuperar los segmentos de WAL hasta que el suscriptor confirme que los ha aplicado. A medida que el suscriptor se queda rezagado, el publicador acumula el WAL en el disco. En Aurora PostgreSQL, esto se manifiesta como un aumento en el valor `OldestReplicationSlotLag` en CloudWatch. En casos graves, esto puede consumir todo el almacenamiento disponible y provocar que el publicador deje de aceptar escrituras.

1. **El retraso se retroalimenta.** A medida que el suscriptor se queda atrás, la tabla de suscriptores sigue aumentando gracias a las inserciones replicadas, lo que hace que cada escaneo secuencial sea aún más lento. Sin intervención, el retraso aumenta sin límites.

Este problema es especialmente grave en el caso de las tablas que reciben operaciones frecuentes `UPDATE` o `DELETE`. Las operaciones `INSERT` no se ven afectadas, ya que no requieren una búsqueda en las filas del suscriptor.

**nota**  
A partir de PostgreSQL 16, el trabajo de aplicación puede utilizar la aplicación paralela para grandes transacciones de streaming, lo que puede mejorar el rendimiento. Sin embargo, sigue existiendo el cuello de botella fundamental de la búsqueda de filas para `REPLICA IDENTITY FULL` sin índices, ya que aún es necesario realizar un escaneo en las filas individuales para localizarlas.

### Impacto en las implementaciones azul/verde
<a name="PostgreSQL.ReplicaIdentityFull.BlueGreen"></a>

Las implementaciones azul/verde en Amazon Aurora utilizan la replicación lógica de forma interna para mantener el entorno verde sincronizado con el entorno azul mediante la configuración de una única suscripción por base de datos. El *proceso de aplicación* de replicación lógica en el entorno verde es de un solo subproceso. Un único proceso de trabajo de aplicación recibe todos los cambios del entorno azul y los aplica de uno en uno, por orden de confirmación. No hay ninguna aplicación paralela en la ruta de replicación azul/verde.

Este diseño de un solo subproceso significa que la capacidad del entorno verde para mantenerse sincronizado con el entorno azul depende completamente de la rapidez con la que un trabajo de aplicación pueda procesar cada cambio individual. Cuando las tablas utilizan `REPLICA IDENTITY FULL` sin una clave principal o un índice adecuado, el impacto en el trabajo de aplicación depende de la versión de PostgreSQL. En las versiones anteriores a la 16, cada operación `UPDATE` y `DELETE` de esas tablas obligan al trabajo de aplicación a realizar un escaneo secuencial de toda la tabla para encontrar la fila que coincide. En las versiones 16 y versiones posteriores, PostgreSQL utilizará un [índice adecuado](https://git.postgresql.org/gitweb/?p=postgresql.git;a=commitdiff;h=89e46da5e) si hay uno disponible, pero si no existe ningún índice válido, el trabajo de aplicación seguirá recurriendo a un escaneo secuencial. Mientras el trabajo de aplicación escanea una tabla grande en busca de una fila, todos los demás cambios pendientes en todas las tablas se ponen en cola y esperan.

Las consecuencias para las implementaciones azul/verde son importantes:
+ **El retraso de replicación aumenta de forma continua.** Si el entorno azul genera tráfico de escritura de forma más rápida de lo que puede procesarlo un único trabajo de aplicación, el entorno verde se queda cada vez más rezagado. Dado que el trabajo de aplicación de un solo subproceso, no hay forma de paralelizar la recuperación.
+ **La transición se puede bloquear.** Una transición entre azul y verde requiere que el entorno verde esté completamente sincronizado con el entorno azul. Si el retraso de replicación es demasiado alto, la transición no podrá completarse dentro del periodo de tiempo de espera.
+ **Es posible que el entorno verde nunca se recupere.** En el caso de cargas de trabajo que requieren mucha escritura con tablas grandes mediante `REPLICA IDENTITY FULL` y sin índices, la tasa de aplicación puede ser tan lenta que el entorno verde se queda rezagado de forma permanente, lo que hace imposible la transición sin resolver primero la configuración de la identidad de réplica.
+ **El WAL se acumula en el entorno azul.** Mientras que el entorno verde se queda rezagado, el entorno azul conserva los segmentos de WAL para la ranura de replicación. Esto aumenta el uso del almacenamiento en el entorno azul (producción) y puede afectar al rendimiento de la producción.

Para evitar estos problemas, asegúrese de que todas las tablas tengan una clave principal o un índice único adecuado configurado de forma explícita como identidad de réplica mediante `ALTER TABLE ... REPLICA IDENTITY USING INDEX` *antes de* crear una implementación azul/verde. No confíe en `REPLICA IDENTITY FULL` con la selección de índices implícita en PostgreSQL 16\+, ya que el suscriptor podría elegir un índice seleccionado de forma deficiente o recurrir a escaneos secuenciales. Pruebe la implementación con una carga de trabajo de escritura representativa para confirmar que el entorno verde es capaz de recuperarse.

Para obtener más información sobre las limitaciones de la implementación azul/verde, consulte [Limitaciones y consideraciones para implementaciones azul/verde de Amazon Aurora](blue-green-deployments-considerations.md). Para ver las prácticas recomendadas, consulte [Prácticas recomendadas de Aurora PostgreSQL para las implementaciones azul/verde](blue-green-deployments-best-practices.md#blue-green-deployments-best-practices-postgres).

## Cómo identificar las tablas con REPLICA IDENTITY FULL
<a name="PostgreSQL.ReplicaIdentityFull.Identify"></a>

Ejecute la siguiente consulta para buscar todas las tablas con `REPLICA IDENTITY FULL`:

```
SELECT n.nspname AS schema, c.relname AS table_name, c.relreplident
FROM pg_catalog.pg_class c
JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
WHERE c.relkind = 'r'
  AND c.relreplident = 'f'
  AND n.nspname NOT IN ('pg_catalog', 'information_schema')
ORDER BY n.nspname, c.relname;
```

Los valores de la columna `relreplident` son:
+ `d`: valor predeterminado (clave principal)
+ `n`: ninguno
+ `f`: completo (fila completa)
+ `i`: un índice específico

## Soluciones alternativas y prácticas recomendadas
<a name="PostgreSQL.ReplicaIdentityFull.Workarounds"></a>

### Adición de una clave principal siempre que sea posible
<a name="PostgreSQL.ReplicaIdentityFull.AddPrimaryKey"></a>

La solución más eficaz consiste en añadir una clave principal a las tablas que no la tengan. Cuando existe una clave principal, PostgreSQL la usa como identidad de réplica predeterminada, lo que proporciona búsquedas eficaces en las filas del suscriptor y reduce al mínimo la sobrecarga de WAL para el publicador.

```
ALTER TABLE my_table ADD COLUMN id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY;
```

**importante**  
Esta instrucción adquiere un bloqueo `ACCESS EXCLUSIVE` y reescribe toda la tabla, ya que la expresión del valor predeterminado utiliza `nextval()`, que es volátil. Todas las lecturas y escrituras de la tabla se bloquean durante la reescritura. En el caso de tablas grandes, esto puede provocar un considerable tiempo de inactividad. Planifique este cambio durante un periodo de mantenimiento o plantéese métodos alternativos, como, por ejemplo, crear primero la columna como anulable y, a continuación, rellenar y añadir la restricción en distintos pasos.

Si no es posible añadir una clave principal debido a las restricciones de la aplicación, plantéese añadir un índice único en un conjunto de columnas `NOT NULL` y configurarlo como la identidad de réplica:

```
CREATE UNIQUE INDEX my_table_replica_idx ON my_table (col1, col2);
ALTER TABLE my_table REPLICA IDENTITY USING INDEX my_table_replica_idx;
```

**nota**  
Para evitar el bloqueo de escritura mientras se crea el índice, utilice la cláusula [https://www.postgresql.org/docs/current/sql-createindex.html#SQL-CREATEINDEX-CONCURRENTLY](https://www.postgresql.org/docs/current/sql-createindex.html#SQL-CREATEINDEX-CONCURRENTLY): `CREATE UNIQUE INDEX CONCURRENTLY my_table_replica_idx ON my_table (col1, col2);`

**nota**  
El índice utilizado para la identidad de réplica debe ser único, no debe ser parcial, no debe ser diferible y debe incluir solo columnas con restricciones `NOT NULL`.

### No se puede confiar en la selección de índices implícita (PostgreSQL 16\+)
<a name="PostgreSQL.ReplicaIdentityFull.SubscriberIndexes"></a>

A partir de PostgreSQL 16, el trabajo de aplicación del suscriptor puede usar índices hash o de btree para las búsquedas de filas cuando la identidad de la réplica esté establecida en `FULL`, incluso si estos índices no se han configurado de forma explícita como la identidad de réplica. Si bien esto impide los escaneos secuenciales en algunos casos, confiar en este comportamiento implícito es contrario a los patrones recomendados por las siguientes razones:
+ **No se puede controlar el índice que se elige.** PostgreSQL selecciona el primer índice válido que encuentra en orden de catálogo, no el más selectivo ni el más eficaz. Si la tabla tiene varios índices válidos, es posible que el elegido tenga una selectividad baja, lo que se traduce en un rendimiento de búsqueda deficiente.
+ **El comportamiento es frágil.** Añadir, eliminar o volver a crear índices puede cambiar el índice que utiliza el trabajo de aplicación, lo que podría provocar regresiones inesperadas del rendimiento en la replicación.
+ **Enmascara el problema subyacente.** Las tablas sin una clave principal o una identidad de réplica explícita son intrínsecamente arriesgadas para la replicación lógica. Confiar en la selección de índices implícita pospone el problema en lugar de resolverlo.

En su lugar, configure de forma explícita la identidad de réplica para cada tabla replicada:
+ **Mejor opción:** añada una clave principal. Esta es la identidad de réplica más fiable y eficaz.
+ **Alternativa:** utilice `ALTER TABLE ... REPLICA IDENTITY USING INDEX` para designar un índice específico único, no parcial ni diferible solo con las columnas `NOT NULL`. Esto le da un control explícito sobre qué columnas se utilizan para la identificación de filas.

Reserve `REPLICA IDENTITY FULL` solo para las tablas en las que ninguna de las dos opciones sea factible y comprenda que el rendimiento depende de factores que escapan a su control directo.

### Supervisión del retraso de la replicación
<a name="PostgreSQL.ReplicaIdentityFull.MonitorLag"></a>

Al utilizar `REPLICA IDENTITY FULL`, supervise detenidamente el retraso de la replicación para detectar las ralentizaciones de aplicación de los suscriptores antes de que se vuelvan críticas.

**En el publicador**, compruebe el retraso entre la posición actual del WAL y lo que el suscriptor ha confirmado:

```
SELECT slot_name, confirmed_flush_lsn, pg_current_wal_lsn(),
       (pg_current_wal_lsn() - confirmed_flush_lsn) AS lag_bytes
FROM pg_replication_slots
WHERE slot_type = 'logical';
```

Un aumento constante del valor `lag_bytes` indica que el suscriptor se está quedando atrás. La vista `pg_stat_replication_slots` proporciona estadísticas adicionales sobre el uso de cada ranura de replicación.

**En el suscriptor**, la vista `pg_stat_subscription` muestra el estado de cada trabajo de aplicación, incluida la última ubicación del WAL recibida y comunicada:

```
SELECT subname, received_lsn, latest_end_lsn,
       last_msg_send_time, last_msg_receipt_time
FROM pg_stat_subscription;
```

**nota**  
En PostgreSQL 16 y versiones posteriores, también puede seleccionar `worker_type` para distinguir entre el trabajo de aplicación principal y los trabajos de aplicación paralelos.

Una gran brecha entre `received_lsn` y `latest_end_lsn`, o una marca de tiempo obsoleta en `last_msg_send_time`, puede indicar que el trabajo de aplicación tiene dificultades para recuperarse. La vista `pg_stat_subscription_stats` también realiza un seguimiento de los errores y conflictos de aplicación que podrían contribuir al retraso.

**En el caso de Aurora PostgreSQL**, también puede supervisar la métrica `OldestReplicationSlotLag` de CloudWatch, lo que realiza un seguimiento del retraso en bytes de la ranura de replicación más rezagada. El aumento de un valor es una señal de alerta temprana de un retraso de la replicación. Para obtener más información, consulte [Supervisión de la memoria caché de escritura indirecta y de las ranuras lógicas para la replicación lógica de Aurora PostgreSQL](AuroraPostgreSQL.Replication.Logical-monitoring.md).

**Comprobación de las tablas que podrían estar utilizando un índice subóptimo durante la aplicación**

En el suscriptor, puede identificar las tablas en las que el trabajo de aplicación realiza lecturas de pila excesivas, lo que puede indicar que la tabla no tiene un índice eficaz para las búsquedas de filas durante la aplicación. Ejecute la siguiente consulta en el suscriptor:

```
SELECT relname, heap_blks_read, heap_blks_hit,
       idx_blks_read, idx_blks_hit,
       heap_blks_read + heap_blks_hit AS total_heap_access
FROM pg_statio_user_tables
WHERE heap_blks_read > 0
ORDER BY heap_blks_read DESC
LIMIT 10;
```

Una tabla con un valor alto `heap_blks_read` con respecto a `idx_blks_read` puede indicar que el trabajo de aplicación no está utilizando un índice eficaz para localizar las filas de operaciones `UPDATE` y `DELETE`. Se trata de un origen común del retraso de replicación cuando `REPLICA IDENTITY FULL` se está utilizando.

**nota**  
Esta consulta requiere que el parámetro [https://www.postgresql.org/docs/current/runtime-config-statistics.html#GUC-TRACK-COUNTS](https://www.postgresql.org/docs/current/runtime-config-statistics.html#GUC-TRACK-COUNTS) se encuentre habilitado en el suscriptor. Este parámetro está activado de forma predeterminada.

### Evaluación de si es necesaria la opción REPLICA IDENTITY FULL
<a name="PostgreSQL.ReplicaIdentityFull.Evaluate"></a>

Antes de configurar `REPLICA IDENTITY FULL`, plantéese si realmente necesita esta opción. Entre las razones más comunes para utilizarse se incluyen:
+ La tabla no tiene una clave principal ni un índice único.
+ Se necesita la imagen anterior completa de las filas para los consumidores de captura de datos de cambios (CDC).
+ Se necesita incluir valores de columna almacenados mediante TOAST en los eventos de replicación para las actualizaciones que no modifiquen esas columnas.

Si el único motivo es la falta de una clave principal, añadir una es casi siempre la mejor opción. Si necesita imágenes anteriores completas para CDC, considere si el consumidor de CDC puede volver a crear las filas completas mientras mantiene el estado de forma externa, lo que evita la sobrecarga de `REPLICA IDENTITY FULL` en WAL y los suscriptores.

## Resumen de recomendaciones
<a name="PostgreSQL.ReplicaIdentityFull.Summary"></a>


| Escenario | Recomendación | 
| --- | --- | 
| La tabla tiene una clave principal. | Utilice la identidad de réplica predeterminada (no es necesario realizar ninguna acción). | 
| La tabla tiene un índice NOT NULL único. | Establezca ese índice como la identidad de réplica con ALTER TABLE ... REPLICA IDENTITY USING INDEX. | 
| La tabla no tiene ninguna clave adecuada (PostgreSQL 16\+). | Añada una clave principal o un índice único. El uso de REPLICA IDENTITY FULL con una selección de índice implícita no es fiable y debería ser el último recurso. | 
| La tabla no tiene ninguna clave adecuada (versiones anteriores a PostgreSQL 16\+). | Añada una clave principal o un índice único; evite REPLICA IDENTITY FULL si es posible. | 
| Una carga de trabajo que requiere mucha escritura con columnas grandes o almacenadas mediante TOAST. | Evite REPLICA IDENTITY FULL debido a la amplificación de volumen de WAL. | 