Nota:
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
Cuando varios cuadernos de Fabric, canalizaciones o trabajos de Spark escriben en la misma tabla Delta al mismo tiempo, Delta Lake usa el control de concurrencia optimista (OCC) para mantener la tabla coherente. Cada transacción lee una instantánea, escribe nuevos archivos y, a continuación, valida que no se produjo ninguna confirmación en conflicto entre sí. Si se detecta un conflicto, la transacción falla y genera una excepción en lugar de corromper los datos.
Este artículo aborda patrones prácticos para gestionar escrituras simultáneas en Fabric. Para obtener una especificación completa del protocolo OCC, consulte Control de simultaneidad de Delta Lake (documentación de código abierto).
Niveles de aislamiento
Todas las tablas Delta usan el nivel de aislamiento Serializable. Serializable es el nivel más estricto y el único admitido. Garantiza que el resultado de las transacciones simultáneas sea idéntico a algún orden de ejecución secuencial.
Delta Lake también usa un nivel de SnapshotIsolation interno para las operaciones que no cambian los datos lógicos (como OPTIMIZE). SnapshotIsolation omite la comprobación de adición concurrente, lo que permite que la compactación se lleve a cabo sin entrar en conflicto con inserciones concurrentes. No se configura SnapshotIsolation directamente: Delta Lake la aplica automáticamente cuando corresponda.
Con el aislamiento Serializable, un anexado ciego simultáneo (INSERT INTO) puede entrar en conflicto con un MERGE o UPDATE que lee la misma partición.
Qué operaciones entran en conflicto
No todas las escrituras simultáneas entran en conflicto. El factor clave es si dos operaciones tocan los mismos archivos subyacentes.
| Par simultáneo | ¿Conflicto? | Por qué |
|---|---|---|
Dos INSERT (anexar) operaciones |
No | Cada uno añade nuevos archivos sin leer los ya existentes (anexado a ciegas). |
INSERT + OPTIMIZE |
No |
OPTIMIZE se confirma en SnapshotIsolation porque no cambia los datos lógicos, por lo que omite por completo la comprobación de anexión concurrente. Las adiciones añaden nuevos archivos que no se superponen con los archivos que se están compactando. |
Dos operaciones UPDATE, DELETE o MERGE |
Sí, si leen o modifican archivos superpuestos | Cada uno reescribe los archivos, por lo que la instantánea del segundo escritor está obsoleta. |
OPTIMIZE + UPDATE/DELETE/MERGE |
Sí, si tocan los mismos archivos |
OPTIMIZE quita y lee los archivos (con dataChange=false). Si una operación de modificación de datos también lee esos mismos archivos, se genera un ConcurrentDeleteReadException . |
Dos OPTIMIZE ejecuciones |
Sí, si seleccionan los mismos archivos | Ambos intentan quitar y reescribir el mismo conjunto de archivos, lo que desencadena un ConcurrentDeleteDeleteException. |
INSERT + MERGE/UPDATE/DELETE |
Sí, si la operación de modificación de datos lee la misma partición. | En Serializable, un anexo ciego puede entrar en conflicto con modificaciones simultáneas de datos si la operación lee una partición a la que escribió el anexado. |
Tip
Las canalizaciones de solo anexión (INSERT INTO, df.write.mode("append")) son la manera más sencilla de evitar conflictos completamente. Si su carga de trabajo permite añadir primero y reconciliar después, se eliminan los conflictos entre escrituras.
Aislamiento de escritores con particiones
La manera más común de ejecutar DML simultáneo en la misma tabla sin conflictos es particionar la tabla por la columna que separa los escritores y, a continuación, incluir esa columna en cada condición de operación. Cuando cada escritor tiene como destino una partición diferente, las operaciones tocan conjuntos de archivos separados y no entran en conflicto.
Un escenario típico: varias canalizaciones, cada una de las cuales procesa datos de una unidad de negocio o inquilino diferente. Divida por esa dimensión y ancle el MERGE de cada canal a su partición.
-- Each pipeline targets its own partition, so concurrent runs don't conflict
MERGE INTO events AS target
USING staged AS source
ON target.event_id = source.event_id
AND target.business_unit = 'EMEA'
WHEN MATCHED THEN UPDATE SET *
WHEN NOT MATCHED THEN INSERT *
Importante
La columna de partición debe aparecer en la propia condición de combinación, no solo en los datos de origen. Sin él, Delta Lake no puede determinar en tiempo de validación que las dos operaciones tocaron conjuntos de archivos separados y el comprobador de conflictos trata la operación como lectura de tabla completa.
Para obtener más información sobre las estrategias de creación de particiones, consulte Creación de particiones para tablas Delta.
Reintento de confirmación integrado
Delta Lake reintenta automáticamente una confirmación cuando detecta que otra transacción se ha confirmado primero. En cada reintento, lee la confirmación ganadora, ejecuta el comprobador de conflictos y, si no existe ningún conflicto lógico, vuelve a intentar la confirmación en la siguiente versión disponible. Este proceso se repite de forma transparente sin ninguna acción del código.
Un conflicto lógico (por ejemplo, dos operaciones que vuelven a escribir el mismo archivo) no se pueden resolver automáticamente. El reintento genera una de las excepciones enumeradas en Excepciones de conflictos comunes. Sin embargo, muchas colisiones de versiones transitorias (como dos anexiones ciegas para la misma ranura de versión) se resuelven automáticamente y nunca se exponen a la aplicación.
Excepciones de conflictos comunes
Cuando se detecta un conflicto, Delta Lake genera una excepción específica. Comprender qué excepción ve ayuda a identificar la causa principal.
| Exception | ¿Qué pasó |
|---|---|
ConcurrentAppendException |
Otro escritor anexaba archivos en una partición (o conjunto de archivos) que la operación estaba leyendo. Común cuando un MERGE se ejecuta sobre una partición que también recibe inserciones de otro proceso. En Serializable aislamiento, incluso los anexos ciegos (operaciones sin formato INSERT ) pueden desencadenar esta excepción. |
ConcurrentDeleteReadException |
Otro escritor eliminó o reescribió un archivo que leyó la operación. Típico cuando OPTIMIZE compacta los archivos que un elemento simultáneo UPDATE o MERGE también estaba leyendo, o cuando dos operaciones de modificación de datos se superponen en las mismas filas. |
ConcurrentDeleteDeleteException |
Ambas operaciones intentaron eliminar o volver a escribir el mismo archivo. Suele deberse a OPTIMIZE ejecuciones superpuestas o a dos canalizaciones que reescriben la misma partición simultáneamente. |
ConcurrentWriteException |
Un conflicto genérico que se produce cuando otra transacción se haya confirmado en la misma versión de la tabla antes de que pueda ejecutarse la resolución de conflictos, por ejemplo, durante una actualización del sistema de archivos a las confirmaciones gestionadas. |
MetadataChangedException |
El esquema de la tabla o sus propiedades cambiaron durante la transacción, por ejemplo, debido a una escritura simultánea ALTER TABLE o a una escritura de evolución del esquema. |
ConcurrentTransactionException |
Dos consultas de Structured Streaming con la misma ubicación del punto de control escribieron en la tabla al mismo tiempo. Elimine los duplicados de sus trabajos de streaming o utilice rutas de punto de control distintas. |
ProtocolChangedException |
Una transacción concurrente actualizó o degradó el protocolo de la tabla mientras la transacción actual también intentaba cambiar el protocolo. También se puede producir cuando se elimina simultáneamente una función de tabla. |
Estrategias comunes para evitar conflictos de escritura
Habilitación de la compactación automática
La compactación automática se ejecuta sincrónicamente como parte de las operaciones de escritura. La compactación sincrónica impide que los trabajos de compactación programados de forma independiente se superpongan con las operaciones de modificación de datos y, por lo tanto, provoquen excepciones de escritor concurrente.
Programar el mantenimiento fuera de las ventanas de escritura
OPTIMIZE y VACUUM pueden entrar en conflicto con las operaciones simultáneas de modificación de datos. En Fabric, programe trabajos de notebook o actividades de canalización para la compactación de tablas y VACUUM en periodos de baja actividad; por ejemplo, una vez finalizada la ingesta nocturna, en lugar de hacerlo durante esta.
Uso de patrones de anexión y combinación
Para la ingesta de alta concurrencia, cargue los datos sin procesar con escrituras de solo anexado en una tabla de preparación (sin posibilidad de conflictos) y, a continuación, ejecute una única tarea MERGE para consolidarlos en la tabla de destino. El patrón serializa la operación propensa a conflictos mientras mantiene el proceso de ingestión completamente en paralelo.
Adición de lógica de reintento para conflictos lógicos
El reintento de confirmación integrado controla las colisiones de versiones transitorias automáticamente, pero los conflictos lógicos, donde dos operaciones se superponen realmente, generan una excepción. Dado que Delta Lake nunca genera escrituras parciales, una transacción fallida puede reintentarse de forma segura a nivel de aplicación. Para las canalizaciones en las que se esperan conflictos lógicos ocasionales, envuelve la operación de escritura en una lógica de reintento:
from delta.exceptions import ConcurrentAppendException
import time
# Retry with backoff on transient concurrent write conflicts
max_retries = 3
for attempt in range(max_retries):
try:
spark.sql("MERGE INTO target USING source ON ...")
break
except ConcurrentAppendException:
if attempt < max_retries - 1:
time.sleep(2 ** attempt)
else:
raise
Elección de la estrategia de diseño correcta
La agrupación en clústeres líquidos y la creación de particiones resuelven diferentes problemas. La agrupación en clústeres líquidos optimiza el diseño de archivos para el rendimiento de lectura. La partición crea límites físicos que evitan conflictos entre escritores concurrentes. Si la carga de trabajo necesita ambos, particiónela por la columna de aislamiento del escritor y use Z-Order dentro de cada partición para mejorar el rendimiento de lectura.