Decisión: cuándo usar merge incremental y cuándo replace
Fecha: 2026-07-29 · Ampliada: 2026-08-10 · Estado: Confirmada
Contexto
Los pipelines de dlt pueden cargar una tabla de dos formas: merge con cursor incremental sobre Fecha_Ult_Modif, o replace (recarga completa). La primera parece siempre mejor por eficiencia, pero tiene un costo que no es obvio.
El problema con merge
merge nunca borra en destino. Si en el origen se elimina un registro, la fila queda en Postgres para siempre.
Se detectó con reorden: Postgres tenía 2,053 filas contra 2,022 en el origen — 31 huérfanas. Al cambiar a replace quedó en 2,022/2,022 exacto.
Decisión
| Modo | Cuándo |
|---|---|
merge + incremental |
Tabla grande, con PK única verificada y cursor poblado |
replace |
Tablas chicas (hasta cientos de miles de filas), sin cursor usable, o sin PK única |
Para tablas chicas, replace es más simple y más correcto. reorden (2 k filas) recarga completa en ~18 segundos junto con producto y catálogos.
Pre-flight obligatorio antes de elegir merge
Tres comprobaciones que cuestan un minuto y evitan pérdidas silenciosas. Las tres salieron de fallos reales.
1. ¿La PK es realmente única en el origen?
SELECT COUNT(*) FROM (SELECT <cols_pk> FROM <tabla> GROUP BY <cols_pk> HAVING COUNT(*)>1) x;
Debe dar 0. ZTRV_SOLICITUD_MATERIA_DOCUMENTO da 31,583 — no tiene clave natural, solo admite replace.
2. ¿La columna cursor está realmente poblada? Que exista Fecha_Ult_Modif no basta: dlt descarta las filas cuyo cursor es NULL.
SELECT COUNT(*) total, SUM(CASE WHEN Fecha_Ult_Modif IS NULL THEN 1 ELSE 0 END) nulos FROM <tabla>;
ZTRV_Solicitud_Agenda_Logistica tiene la columna pero 96 % de sus filas la traen NULL — el incremental cargaba 6 de 142 filas.
3. ¿.200 escribe esa tabla por su cuenta? Ver Fuente de datos.
Gotchas del cursor
initial_valuedebe serdatetime.datetime(1900, 1, 1), no el string"1900-01-01". Si la columna esdatetimeen SQL Server, dlt comparastr > datetimey falla conIncrementalCursorInvalidCoercion.- Hacer el backfill con
dlt.sources.incrementalya activo, para que el cursor quede persistido. Si el backfill se hace con una query a pelo, el estado queda vacío y la primera corrida incremental intenta re-traer la tabla completa. Le pasó amovimiento(4.4 M filas, >20 min, proceso muerto). - Tablas muy grandes: trocear el backfill por año.
movimientomoría sin traceback cargando 4.4 M filas de una (sin memoria, VM de 5.2 GB con swap al límite).