Debuggear la ingesta: cuando el dato no aterrizó como esperabas
Un Data Stream aterrizó mal y nada tiró error. El diagnóstico es ordenado — confirmá primero que la corrida siquiera pasó, después leé qué le hizo a los row counts, porque casi todo bug de ingesta es un refresh que no corrió, una confusión de full-vs-upsert, un problema de primary key, o el comportamiento del upsert-que-no-borra. Chequeá el schedule y la auth del connector, contá el DLO antes y después, y verificá que el refresh realmente aterrizó. El playbook de debugging de la ingesta.
Un Data Stream está alimentando datos equivocados a Data 360 (antes Data Cloud) y nada te avisó. Capaz un count que debería haber crecido está plano. Capaz se duplicó de un día para el otro. Capaz un cliente que se desuscribió la semana pasada sigue en una activación, o un registro que sabés que existe nunca apareció. El stream se ve sano — la última corrida está en verde, el connector está conectado, el schedule dice diario — y la ingesta casi nunca tira error por aterrizar datos equivocados, solo por no correr en absoluto. Así que el diagnóstico tiene siempre la misma forma: confirmá que la corrida pasó antes de preguntar qué hizo, después leé los row counts del DLO antes de culpar a nada de abajo. El bug casi siempre está en uno de cinco lugares, y fallan en un orden fijo.
Lo que hay que internalizar antes de arrancar: todo lo de acá se lee en el DLO (__dll) — el objeto crudo de aterrizaje que produce el stream. Esta página debuggea si aterrizaron las filas correctas, no si fueron mapeadas al significado correcto. Un valor que aterrizó bien pero se lee mal en un segmento es un bug de mapping una capa más abajo (mapping, debuggear fallas de mapping); un número que está mal solo en una query es un bug de query (debuggear resultados de query). La ingesta es dueña de una sola pregunta: ¿las filas que el origen tiene terminaron en el DLO, en la cantidad correcta, frescas? Confirmá eso primero, porque cada capa de arriba lo da por sentado.
Los pasos
[ PASO 1 — ¿El refresh siquiera corrió? (schedule + auth del connector) ]
↓
[ PASO 2 — ¿Los row counts son los que esperabas? (full vs upsert) ]
↓
[ PASO 3 — ¿Registros faltantes o duplicados? (primary key / dedup en upsert) ]
↓
[ PASO 4 — ¿Registros que deberían haberse ido siguen ahí? (upsert-que-no-borra) ]
↓
[ PASO 5 — ¿Fallas de mapping/validación en el DLO? ]
↓
[ PASO 6 — Verificá que un refresh realmente aterrizó (last-run + counts antes/después) ]Bajá en orden. Un count equivocado en el Paso 2 no significa nada si el refresh nunca corrió (Paso 1), y un "registro faltante" en el Paso 3 es un bug distinto de un "registro obsoleto que no se va" en el Paso 4 — arreglos opuestos, así que nombrar cuál tenés viene antes de tocar el stream. Los Pasos 1 al 5 son dueños cada uno de una falla distinta; el Paso 6 es la verificación que corrés después de cualquier arreglo, porque en la ingesta un status verde no es prueba de que el dato esté bien.
Paso 1 — ¿El refresh siquiera corrió?
Antes de nada sobre counts, probá que la corrida pasó. El "bug de ingesta" más común no es dato malo — es dato nuevo que no hay, un stream que calladamente dejó de refrescar mientras cada consumidor de abajo siguió leyendo la última carga buena como si fuera actual. Un DLO obsoleto se ve idéntico a uno fresco; nada en un segmento dice "esto tiene tres días".
- El chequeo — abrí el stream y leé su status y timestamp de última corrida. ¿Cuándo completó por última vez, y completó o falló? Una corrida que viene fallando en el schedule, o un timestamp de última corrida mucho más viejo que lo que la cadencia promete, es el bug entero — no hay dato nuevo porque nada aterrizó. Después chequeá las dos cosas que frenan una corrida: el schedule (¿está realmente habilitado y en la cadencia que creés, o pausado?) y la autenticación del connector (¿venció la credencial, el token o la key?). Una credencial de connector vencida es el freno silencioso clásico: el stream funcionaba, el token caducó, las corridas empezaron a fallar, y abajo se presenta idéntico a "el origen no tenía registros nuevos".
- El síntoma — un count plano cuando esperabas crecimiento, datos correctos pero obsoletos (bien para un momento del pasado), o un timestamp de última corrida que no avanzó. Para un origen de file storage, "no hay dato nuevo" también puede significar que el export de arriba dejó de dejar archivos en el bucket — el stream corrió bien y no encontró nada, que no es la misma falla pero se presenta igual (ver connectors).
- El arreglo — re-autenticá el connector si la credencial caducó, re-habilitá o corregí el schedule si estaba pausado o mal seteado, y para un origen de archivos confirmá que el export de arriba sigue dejando archivos. Después forzá un refresh y andá al Paso 6 para confirmar que aterrizó. No debuggees counts hasta que confirmaste que una corrida realmente completó contra data de origen actual — un número equivocado de una corrida que nunca pasó no es el bug que pensás.
Si la corrida completó contra data de origen actual, el dato está fresco y el bug está en qué aterrizó. Seguí.
Paso 2 — ¿Los row counts son los que esperabas?
La corrida completó, pero el count está mal — demasiado alto, demasiado bajo, o se movió por una cantidad que no tiene sentido. Antes de perseguir registros individuales, leé el row count del DLO y compará con lo que el origen realmente tiene, porque el bug de count más común es una confusión de full-refresh-versus-upsert: un desajuste entre lo que creés que la corrida le hizo al dataset y lo que el modo realmente hace. Un full refresh reemplaza el conjunto; un upsert mergea dentro de él (modos de refresh). Confundí los dos y el count te sorprende.
- El chequeo — primero, contá lo que hay en el DLO ahora. Después preguntá en qué modo está el stream y si el count coincide con el comportamiento de ese modo. Un full refresh debería dejar el DLO con exactamente lo que el origen tiene en esta corrida — ni más, ni menos. Un upsert debería dejarlo con todo lo que se mandó alguna vez y no fue reemplazado — que crece con el tiempo y puede ser más grande que el origen actual si el origen desde entonces se achicó. El count más simple sobre el objeto de aterrizaje:
-- ¿Cuántas filas hay en el DLO ahora mismo? Corré después de un refresh y compará
-- con lo que el origen realmente tiene en esta corrida. Para un FULL REFRESH estas
-- deberían coincidir con el origen; para un UPSERT el DLO puede superarlo (acumula, ver Paso 4).
SELECT COUNT(*) AS dlo_row_count
FROM YourSource__dll;- El síntoma — dos formas, causas opuestas. Un count más bajo que el origen después de lo que asumiste como una carga aditiva normalmente significa que el stream está en full refresh y reemplazó el conjunto, cuando esperabas que sumara — las filas previas no se conservaron, se sobrescribieron. Un count más alto que el origen actual normalmente significa que el stream está en upsert y viene acumulando filas que el origen ya no tiene (el problema de bajas del Paso 4), o que el mismo registro lógico aterriza bajo keys distintas y se duplica (Paso 3). De cualquier modo, el count está "mal" solo relativo al modo que asumiste; contra el modo en que el stream realmente está, se comporta exactamente como fue diseñado.
- El arreglo — reconciliá tu expectativa con el modo configurado antes de cambiar nada. Si esperabas acumulación incremental y obtuviste reemplazo, el stream está en full refresh — decidí si eso está realmente mal (el full refresh suele ser el modo más seguro; ver Paso 4 y modos de refresh) o si necesitás upsert con una key real. Si esperabas que el DLO espejara al origen y es más grande, el stream está en upsert y o retiene registros borrados (Paso 4) o duplica sobre una key mala (Paso 3) — andá a nombrar cuál. El count es un cartel hacia el próximo paso, no el bug en sí.
Una vez que la dirección del count te dice si estás ante un problema de registros faltantes o de demasiados registros, andá al paso que corresponde. Seguí.
Paso 3 — ¿Registros faltantes o duplicados?
El count apuntó a registros individuales: filas específicas que deberían estar en el DLO no están, o el mismo registro lógico aparece más de una vez. En un stream de upsert los dos síntomas rastrean a la misma raíz — la primary key. La key es lo que le dice a un upsert si un registro entrante es el mismo que ya tiene (actualizalo) o uno nuevo (insertalo). Una key que no es genuinamente única, o que no es genuinamente estable, rompe esa decisión en silencio.
- El chequeo — averiguá si la key está haciendo su trabajo contando filas por valor de key sobre el DLO. Una primary key debería ser única: exactamente una fila por key. Más de una significa que la key no es única — dos registros reales comparten un valor, o el mismo registro aterrizó dos veces bajo una key que el stream trató como nueva cada vez.
-- ¿La key del upsert es realmente única en el DLO? Agrupá por la key y encontrá
-- cualquier valor que aparezca más de una vez. Cero filas devueltas = la key es única (sana).
-- Cualquier fila = duplicación: una key no única, o el mismo registro re-insertado como "nuevo".
SELECT
YourKeyField__c AS key_value,
COUNT(*) AS rows_for_this_key
FROM YourSource__dll
GROUP BY YourKeyField__c
HAVING COUNT(*) > 1
ORDER BY rows_for_this_key DESC;- El síntoma — duplicados: la misma persona o registro aparece varias veces en el DLO, cada una como una fila separada, porque una key que no es estable hizo que cada llegada pareciera nueva (elegiste como key un campo que cambia, así que el upsert insertó en vez de actualizar). O pérdida silenciosa: un registro que sabés que se mandó falta, porque una key no única dejó que un registro entrante sobrescribiera a otro registro real que casualmente compartía el valor de key — un upsert reemplazó al otro en silencio y ningún error disparó (modos de refresh). La duplicación infla el count; la pérdida por sobrescritura puede dejar el count con buen aspecto mientras un registro específico simplemente desapareció.
- El arreglo — la reparación durable está en la key, no en la regla que la lee. Si la key no es única, elegiste el campo equivocado — encontrá un campo (o un compuesto) que sea genuinamente uno-por-registro y re-keyeá el stream (relationships & keys). Si la key no es estable — cambia entre corridas para el mismo registro — aplica el mismo arreglo: elegí un campo que sea constante durante la vida del registro. Parchar el síntoma (deduplicar abajo, ignorar las filas de más) solo mueve una ingesta sabidamente-mala a cada consumidor. La key es el contrato; si está roto, arreglá el contrato.
Si las keys son únicas y estables y los registros siguen estando mal, el problema no es duplicación ni pérdida en el insert — es que registros que deberían haberse ido persisten. Esa es una falla distinta. Seguí.
Paso 4 — ¿Registros que deberían haberse ido siguen ahí?
Un tipo específico de "count mal, demasiado alto" merece su propio paso porque es la trampa central de correctitud de la ingesta y confunde a todos la primera vez: registros borrados del origen siguen en el DLO, con aspecto tan válido como los vivos. Si el stream está en upsert, esto no es un bug en tu data — es el comportamiento documentado, y el arreglo es una estrategia, no un parche.
- El chequeo — confirmá que el síntoma es retención de bajas, no duplicación. Elegí un registro que sabés que se removió del origen — una cuenta que se cerró, un contacto que se borró — y buscalo en el DLO. Si sigue ahí, y el stream está en upsert, reprodujiste el comportamiento. La forma general: el DLO tiene más registros distintos que los que el origen tiene actualmente, y el excedente son registros que el origen ya no tiene.
-- ¿Un registro de origen que sabés borrado sigue viviendo en el DLO? Reemplazá el
-- literal con un registro que SABÉS que se removió en el origen. En un stream de UPSERT
-- va a seguir acá — el upsert nunca remueve un registro solo porque el origen dejó de mandarlo.
SELECT *
FROM YourSource__dll
WHERE YourKeyField__c = 'KEY_OF_A_RECORD_DELETED_AT_SOURCE';- El síntoma — un cliente que se desuscribió o se borró sigue segmentado y sigue siendo contactado; counts "activos" que nunca bajan aunque el origen pierda registros; un DLO que solo crece. Como todo lo de abajo lee el DLO, el registro obsoleto fluye hacia la identity resolution, los segmentos y las activaciones, y alguien que debería haber sido removido es alcanzado — sin ningún error en ningún lado, porque la corrida que debería haberlo removido simplemente nunca lo tocó. Un upsert toca un registro solo cuando está en la corrida, y un registro borrado es, por definición, el único caso que deja de llegar.
- El arreglo — esta es la mitad de la decisión del modo de refresh que muerde más tarde: el upsert no elimina los registros borrados en el origen salvo que mandes las bajas explícitamente (modos de refresh). Si el origen puede borrar registros, necesitás una estrategia de bajas deliberada — una forma de que la baja llegue a Data 360 como una señal de baja explícita — o pasás el stream a full refresh, que captura las bajas por ausencia (un registro que se fue del origen en esta corrida simplemente no está en la carga nueva, así que tampoco está en el DLO). Elegí: mandá bajas, o full refresh. No hay una tercera opción donde el upsert calladamente limpia detrás de sí mismo.
Si los registros aterrizan, no se duplican, y se van cuando deberían, las filas están correctas — pero una fila puede aterrizar y aun así ser parcialmente rechazada si sus valores no coinciden con la forma esperada del DLO. Seguí.
Paso 5 — ¿Fallas de mapping/validación en el DLO?
Las filas están bien en cantidad e identidad, pero un campo en ellas está vacío o mal para cada registro — y estaba vacío al aterrizar, antes de cualquier modelado. Esta es la falla de validación del lado de la ingesta: el dato entrante no se ajustó a las definiciones de campo del DLO, así que un campo fue descartado, vaciado, o forzado. Es distinta del mapping DLO→DMO (esa es la capa siguiente, trabajo de Data Architecture); acá la pregunta es más acotada — ¿el valor sobrevivió al aterrizaje en el DLO en absoluto?
- El chequeo — leé el campo sospechoso directo del DLO, en reposo, antes de que nada de abajo lo toque. ¿Está nulo o mal para cada fila, o solo para algunas? Un campo en blanco en todas las filas apunta a que el campo nunca llegó (un desajuste de nombre entre el payload y el schema registrado) o a un tipo que el DLO rechazó; en blanco para algunas filas apunta a data de origen que falla la validación solo para esos registros (una fecha malformada, un número donde se esperaba texto). Leelo como aterrizó:
-- ¿El campo está poblado en el DLO, en reposo? Contá cuántas filas lo tienen nulo.
-- Un count alto de nulos para un campo que el origen siempre manda apunta a una falla
-- de aterrizaje/validación (desajuste de schema/nombre o un tipo rechazado), no a un bug de mapping de abajo.
SELECT
COUNT(*) AS total_rows,
SUM(CASE WHEN YourField__c IS NULL THEN 1 ELSE 0 END) AS null_rows
FROM YourSource__dll;- El síntoma — para un origen de connector, un campo consistentemente vacío en el DLO normalmente significa un desajuste de schema/tipo que el connector no pudo reconciliar; para un origen de Ingestion API, normalmente significa que el payload no coincidió con el schema registrado — un campo nombrado distinto de lo que el schema declara, o tipado distinto de lo registrado, es rechazado o descartado en silencio en vez de forzado (la Ingestion API). Un stream de Engagement al que le falta su campo de event-time es un caso específico que vale nombrar: sin el timestamp los registros no son una serie temporal usable, y la falla aparece abajo como lógica de ventana temporal sin nada sobre lo que pararse (data streams).
- El arreglo — arreglalo en el borde que el dato cruza. Para un origen de Ingestion API, reconciliá el payload con el schema registrado (o re-registrá el schema si la forma del origen cambió de verdad) para que los nombres y tipos de campo coincidan exactamente — el schema es el contrato y el dato debe ajustarse a él, no al revés. Para un connector, corregí el mapping de campo/tipo que el connector ofrece en la ingesta. Lo que no hacés es tapar una falla de aterrizaje en el mapping del DMO de abajo — un campo que nunca aterrizó no se puede mapear, y reformarlo después solo esconde dónde realmente se rompió.
Una vez que las filas correctas aterrizan con sus campos intactos, el dato está correcto en el DLO. El último paso no es una falla nueva — es cómo probás que cualquier arreglo de arriba realmente prendió. Seguí.
Paso 6 — Verificá que un refresh realmente aterrizó
Cambiaste algo — re-autenticaste un connector, re-keyeaste un stream, pasaste a full refresh, arreglaste un schema — y forzaste una corrida. Ahora confirmá que el arreglo prendió, del mismo modo que confirmarías cualquier afirmación de ingesta: no leyendo el badge de status, sino leyendo los row counts del DLO antes y después, contra el timestamp de última-completada de la corrida. Un "refresh completo" verde te dice que el motor corrió; no dice nada sobre si las filas están ahora bien.
- El chequeo — capturá el count del DLO antes de la corrida, forzá el refresh, esperá a que la corrida reporte completa, después volvé a contar y compará. Predecí la dirección primero: un cambio a full refresh que descarta bajas varadas debería bajar el count para coincidir con el origen; un re-key que frena la duplicación debería bajarlo hacia una fila por registro; un connector re-autenticado que reanuda un stream frenado debería subirlo para reflejar data que no estaba aterrizando. Después leé el timestamp de última corrida para confirmar que el count que estás leyendo es de la corrida que recién forzaste, no de la vieja.
-- ANTES y DESPUÉS de un arreglo: el row count del DLO para el stream que tocaste.
-- Capturá esto antes de forzar el refresh, después volvé a correr cuando la corrida
-- reporte completa. El count moviéndose en la dirección que predijiste es la verificación — no el status verde.
SELECT COUNT(*) AS dlo_row_count
FROM YourSource__dll;- El síntoma de que estás leyendo demasiado temprano — volvés a contar y el número no se movió, o se movió a medias. Un refresh no es instantáneo; el DLO que consultás justo después de forzar una corrida puede seguir aterrizando, así que un count "sin cambios" puede simplemente estar en vuelo. Igualmente, un status que lee completo mientras el count no se movió en absoluto significa que la corrida no hizo lo que cambiaste que hiciera — el schedule disparó pero la credencial sigue mala, o re-keyeaste el campo equivocado — y volvés al paso que es dueño de ese arreglo.
- El arreglo — tratá el count del DLO y el timestamp de última corrida juntos como la fuente de verdad, y reconfirmá sobre los registros específicos del paso anterior, no solo el total. Volvé a correr el count por key del Paso 3: ¿se fueron los duplicados? Volvé a correr el lookup del Paso 4: ¿finalmente se fue el registro que sabías borrado (o se fue porque el full refresh lo descartó por ausencia)? Que el total se mueva en la dirección predicha es necesario; que los registros específicos estén bien es suficiente. Y recordá que el count mueve cada número de abajo que lee este DLO — un refresh que por fin aterriza bajas va a desplazar tamaños de segmento y cualquier reporte que se apoye en él, así que avisale a quien lee esos números antes de que se muevan bajo sus pies (principio 11).
Un diagnóstico que podés correr
Cuando el reporte es "la ingesta se ve mal" y todavía no sabés qué falla tenés, el triage más rápido es dos lecturas, a ojo — sin arreglo, sin adivinar el modo. Contestan las dos preguntas que parten cada bug de ingesta en su rama.
- ¿Cuándo corrió bien este stream por última vez, y contra qué? Leé el status y timestamp de última corrida en el stream. Una corrida que falla, o un timestamp más viejo que lo que la cadencia promete, es el bug — es el Paso 1, un refresh que paró (schedule pausado, auth del connector vencida, un export de arriba que dejó de dejar archivos). No tener dato nuevo se ve idéntico a tener dato fresco, así que esta es siempre la primera lectura.
- ¿El row count del DLO coincide con el modo en que está el stream? Contá el DLO y compará con lo que el origen tiene. Más bajo que el origen tras una carga asumida-aditiva es reemplazo de full refresh (Paso 2); más alto que el origen es acumulación de upsert — o bajas retenidas (Paso 4) o duplicación sobre una key mala (Paso 3). La dirección de la brecha te rutea al paso exacto.
La dirección que encontrás dicta todo lo que viene después: un stream que no corrió es el Paso 1, un count demasiado bajo es reemplazo que no esperabas, un count demasiado alto son bajas que nunca se fueron o una key que duplica. Un DLO que tiene a un cliente que se desuscribió la semana pasada, sobre un stream que confirmás que está en upsert, es la trampa de bajas localizada — sin corrida fallida que buscar, solo una estrategia de bajas que todavía no tenés.
Síntomas comunes mapeados a pasos
| Síntoma | Causa probable | Dónde mirar | |---|---|---| | Count plano cuando esperabas crecimiento | Refresh frenado — schedule pausado o auth vencida | Status y timestamp de última corrida (Paso 1) | | Datos correctos pero obsoletos (de ayer) | El stream dejó de refrescar; sirve la última carga | Timestamp de última corrida vs. cadencia (Paso 1) | | Count más bajo que el origen | El full refresh reemplazó el conjunto que esperabas crecer | Modo configurado vs. tu expectativa (Paso 2) | | Count más alto que el origen actual | Upsert acumulando — bajas retenidas o duplicados | Count por key (Paso 3), lookup de borrado conocido (Paso 4) | | El mismo registro aparece varias veces | Key no estable — el upsert inserta en vez de actualizar | Filas por valor de key en el DLO (Paso 3) | | Un registro que mandaste simplemente falta | Key no única — un upsert sobrescribió a otro | Filas por valor de key en el DLO (Paso 3) | | Registro desuscripto/borrado todavía presente | El upsert no borra sin una señal explícita | Lookup del registro borrado conocido (Paso 4) | | Un campo en blanco para cada fila, al aterrizar | Desajuste de schema/tipo o payload fuera del schema | El campo, contado en nulos en el DLO (Paso 5) | | Lo arreglé, pero el count no se movió | Leyendo demasiado temprano, o la corrida no aplicó el arreglo | Volver a contar vs. timestamp de última corrida (Paso 6) |
Relacionado
- Data streams — la unidad de ingesta que lee cada sonda de esta página: origen → DLO, la categoría, y el schedule cuyo status chequeás primero
- Modos de refresh — full refresh vs upsert y el comportamiento de bajas detrás de los Pasos 2, 3 y 4
- Gotchas de ingestion — las fallas silenciosas de ingesta que esta página diagnostica, en forma de producción
- Debuggear resultados de query — cuando el número equivocado es un bug de query, no de ingesta (la capa de arriba)
Referencia: