Bronze debería ser aburrido

Diseñando una capa de ingesta dirigida por metadatos que desaparece detrás de un commit de YAML.


Abrís una llave y sale agua. Limpia, segura, potable. Nadie lo celebra. Nadie piensa en ello. Pero detrás de ese momento sin gracia hay plantas de tratamiento, redes de distribución, sistemas de monitoreo de presión, pruebas de calidad y siglos de ingeniería. La llave es aburrida porque la infraestructura es excelente.

Bronze debería sentirse igual.

Si agregar una nueva fuente de datos a tu Lakehouse requiere un nuevo pipeline, una ronda de depuración, un despliegue y una pequeña plegaria, no tenés ingeniería. Tenés artesanía. Artesanía hecha a mano, cableada una por una, un pipeline por tabla, que funciona bien con diez entidades y se vuelve un lastre con cincuenta.

Este post explora cómo hacer que Bronze sea aburrido, y por qué eso requiere más ingeniería de la que uno esperaría. Es el primero de una serie en la que recorremos la arquitectura dirigida por metadatos que hace posible esa simplicidad. Empezamos con el cimiento: la capa Bronze y por qué YAML es el contrato que la sostiene.


El problema: la proliferación de pipelines

Esta historia seguro la viviste. Arrancás un proyecto de Lakehouse con diez entidades y dos fuentes. Construís un pipeline por entidad y un pipeline orquestador por encima. Funciona. Es «ágil».

Un año después tenés cinco fuentes, tres países y 350 entidades. También tenés 350 pipelines. Cada uno con su propia lógica de conexión, su propio manejo de errores, sus propias columnas técnicas (o no), y su propia nomenclatura inventada por quien lo construyó ese día. Algunos tienen lógica de reintentos. Otros no. Algunos registran en una tabla de control. Otros no registran nada.

El problema no es el volumen de datos. Es el volumen de pipelines. Cada uno es código que hay que mantener, probar, documentar y depurar. Cada uno es una superficie de ataque para bugs. Cada uno es un despliegue. Y cada nueva entidad se convierte en un ticket de desarrollo de 2-3 días cuando debería ser un commit de 15 minutos —porque alguien tiene que clonar un pipeline existente, cambiar quince cosas, olvidarse de cambiar dos, y depurar en producción hasta que funciona. Hasta que deja de funcionar, y nadie recuerda por qué se construyó así.

Hay otra forma. Y empieza repensando qué es realmente Bronze.


Bronze redefinido: no es solo «copia cruda»

La definición clásica del Medallón es limpia y ordenada. Bronze es copia cruda —datos como llegan, sin transformaciones. Silver limpia. Gold agrega (SIC). Es un modelo mental útil con un problema práctico: si Bronze es solo un vertedero, los datos entran sin identidad.

No sabés de dónde vinieron. No sabés quién los envió. No sabés si están completos. No sabés cuándo llegaron. Y más importante: no sabés quién es el dueño. Si esperás hasta Silver para responder esas preguntas, ya dejaste entrar a alguien a tu edificio sin pedirle identificación, y ahora estás tratando de averiguar quién es después del hecho.

Nuestra redefinición: Bronze es una copia con fidelidad total a la fuente, enriquecida con metadatos y gobierno desde el punto de entrada.

Bronze clásico («copia cruda»)Bronze enriquecido
Datos tal cualDatos + columnas técnicas (RunID, LoadDateUTC, BatchID, SnapshotDate)
Esquema genérico (staging, raw)Esquema que refleja fuente y dominio en el nombre
Sin clasificaciónTags de seguridad y gobierno desde la ingesta
Pasa o fallaLas anomalías van a cuarentena, no rompen la carga
Esquema desconocidoCaptura de esquema y detección de drift desde el día uno

«Pero eso ya no es Bronze —eso es Silver.» No. Los datos originales no se modifican ni se limpian. Lo que se agrega son metadatos: columnas técnicas, clasificación, nomenclatura del esquema. Los datos mantienen su fidelidad a la fuente. Es dato crudo más metadato rico.

OK, Bronze enriquecido suena bien. ¿Pero cómo lo implementás sin terminar con 200 pipelines artesanales? Ahí entra YAML.


La solución: un motor fijo más N YAMLs

La arquitectura en una frase:

Un motor pequeño y fijo de componentes especializados + N archivos YAML = N fuentes ingeridas sin escribir código nuevo.

La idea clave: no es un único notebook mágico que hace todo. Es un conjunto finito de componentes genéricos, cada uno especializado en una única responsabilidad:

  • Ingesta por tipo de fuente — Distintos componentes para distintos orígenes. Archivos (CSV, Parquet, Excel): un notebook que lee, aplica esquema, escribe Delta. Bases de datos relacionales: un pipeline con ForEach y Copy Activity parametrizado. APIs: un notebook con patrón request-paginar-aterrizar.
  • Captura de esquema — Un notebook que registra el diccionario de datos (estructura de la tabla) con cada ingesta. Cuando la estructura se desvía de la última versión conocida —nueva columna, cambio de tipo, campo eliminado— el drift se registra para revisarse antes de que sorprenda a un consumidor aguas abajo (downstream).
  • Validación de volumen — Un notebook que compara filas recibidas vs. esperadas y marca anomalías. Las filas que fallan reglas de integridad no rompen la carga; se redirigen a una tabla de cuarentena etiquetadas con la razón, mientras el resto sigue. La cuarentena es un canal lateral, no un fallo.
  • Orquestación — Un pipeline maestro que lee los YAMLs y dispara los componentes apropiados. Bronze no tiene dependencias entre tablas por diseño: cada entidad aterriza de forma independiente. La resolución de dependencias es un problema de Silver, no de Bronze.

La cantidad de componentes del motor es pequeña y acotada —cinco, siete, quizá diez en entornos complejos. No crece con el volumen de datos ni con la cantidad de fuentes. Lo que escala es la configuración: agregar la fuente número 201 no requiere un componente nuevo, solo un YAML nuevo que los componentes existentes ya saben leer. Nuevas preocupaciones como detección de PII, verificaciones de frescura o vista previa de muestras se enchufan como notebooks nuevos conectados al mismo contrato —no como pipelines nuevos por fuente.

Por qué YAML (y no JSON, una UI o tablas de metadatos en SQL)

Este no es un artículo sobre la sintaxis de YAML. Es sobre las propiedades que necesitás en tu formato de configuración. Tres ejes:

1. Gobernabilidad — La configuración vive en Git, no en una base de datos

YAML en Git hereda toda la maquinaria de code review que ya usás: PRs (Pull Requests), diffs, blame, rollback, puertas de aprobación tan estrictas como lo exija el gobierno. «¿Quién editó la fila 47 de dbo.PipelineConfig el martes a las 3am?» es una pregunta difícil. «¿Quién editó Sales_Salesforce.yml?» es git blame.

Este no es un patrón nuevo. Es Infrastructure as Code —configuración declarativa, control de versiones, despliegue automatizado, repetibilidad— aplicado a la ingesta en lugar de a la infraestructura. Los mismos fundamentos que hicieron predecible el aprovisionamiento de servidores con Terraform hacen predecible el aprovisionamiento de fuentes de datos aquí. Cómo encaja con la práctica DevOps (validación pre-despliegue, pipelines de aprobación, rollback) es el tema del próximo post.

2. Extensibilidad — El contrato evoluciona sin romperse

Agregar una sección nueva a un YAML no rompe los YAMLs existentes que no la usan. template_version: "1.0" evoluciona a "2.0", y el motor sabe qué versión procesar. Agregar una columna a una tabla de configuración, en cambio, implica ALTER TABLE, un despliegue, y coordinar con todo lo que la lee. Uno es un commit; el otro es un proyecto.

3. Usabilidad — Legible por humanos, por máquinas y por IAs

Un ingeniero de datos nuevo abre el YAML y entiende qué hace sin documentación —sin conocimiento de SQL, sin acceso a la base de datos, sin UI que aprender. Copiás un YAML existente, cambiás cinco líneas, mandás un PR. Y un argumento que nadie planteaba hace dos años: YAML es texto plano estructurado, el formato ideal para que un LLM lea, genere y modifique. Pedile a un modelo que produzca un INSERT INTO con 15 columnas nullable versus un YAML siguiendo una plantilla —la diferencia de confiabilidad es dramática.

Las alternativas, en breve:

  • JSON: Las herramientas modernas (JSON5, editores con soporte de schema) achican la brecha, pero las configuraciones largas siguen ahogándose en comillas y corchetes, y comparar JSON anidado es un examen de la vista.
  • UI (portales web): No versionable, no diffable, no scriptable. La configuración queda atrapada en la plataforma.
  • Tablas de metadatos en SQL: Excelentes para metadatos operacionales (logs, estado, métricas) —pero para configuración declarativa mezclan responsabilidades: el mismo lugar donde escribís la configuración es donde el motor ejecuta.

Los principios de diseño que más importan

No es una lista exhaustiva; los posts siguientes profundizan más. Solo los fundacionales:

  1. GitOps como fuente de verdad — El YAML se edita solo en Git. Cada cambio vía PR. El Lakehouse contiene artefactos desplegados, no editables.
  2. Filename-as-metadata — El nombre del archivo es metadato. Sales_Salesforce.yml significa domain=Sales, source=Salesforce, y el esquema en el Lakehouse se deriva del nombre. Esta es la apuesta más arriesgada del diseño —y la que más paga; ver el análisis detallado abajo.
  3. Un YAML por fuente-dominio — Granularidad clara. Ni un mega-YAML con todo, ni un YAML por tabla individual.
  4. Declarativa pura — El YAML nunca contiene lógica. Solo parámetros. «El YAML describe qué se ingiere; el motor decide cómo

Cómo se ve un YAML

Así se ve un YAML de ingesta real, recortado a lo esencial:

# Archivo: Sales_Salesforce.yml
# Metadato implícito del nombre: domain=Sales, source=Salesforce
# Esquema destino automático: Sales_Salesforce

config:
  template_version: "1.0"
  source_type: relational
  source_system:
    owner: "Revenue-Ops"
  tags:
    security.sensitivity: "confidential"
    governance.data_owner: "Sales"

entities:
  - name: Opportunities
    table: Salesforce.Opportunity
    tags:
      security.sensitivity: "restricted"       # sobrescribe el tag global
      security.classification: "PII"   
    post_ingestion_tasks:
      volume_check:
        enabled: true
        min_rows: 500
      schema_capture:
        enabled: true

  - name: Products
    query: "SELECT ProductCode, Name, Family FROM Salesforce.Product"
    tags:
      security.sensitivity: "internal"
    post_ingestion_tasks:
      volume_check:
        enabled: false
      schema_capture:
        enabled: true

Todo lo que necesitás saber sobre estas dos entidades entra en una pantalla. El dominio, la fuente, el dueño del gobierno, la clasificación de seguridad, los umbrales de volumen, el flag de captura de esquema —todo declarado, sin código. Agregar una tercera entidad son cinco líneas más y un pull request.

Este YAML se valida contra un schema antes de correr. Ese es el tema del próximo post.

Filename-as-metadata: la apuesta que paga

Sales_Salesforce.yml parece una convención de nombres. Es un contrato de gobierno.

El instinto natural al registrar una nueva fuente es nombrarla por origen: Salesforce.yml, SAP.yml, Oracle.yml. La fuente es inequívoca. Asignar un dominio no lo es. Alguien tiene que decidir si los datos de Salesforce pertenecen a Sales, Revenue o Customer Success, y esos son equipos distintos con intereses distintos. Por eso exactamente lo exigimos.

El nombre del archivo fuerza una pregunta política de entrada: ¿quién es dueño de este dato? No después, no en una planilla, no «lo vemos en Silver». Antes de que el YAML pueda integrarse, alguien ya respondió. La alternativa —dejar que los datos aterricen anónimos en Bronze y discutir la propiedad seis meses después, cuando un tablero (dashboard) está mal y nadie sabe a quién escalar— es lo habitual en la mayoría de las organizaciones, y es también lo que erosiona la confianza en el Lakehouse más rápido que cualquier fallo técnico.

Exigir un dominio te da tres cosas que la nomenclatura solo-por-fuente nunca podría:

  • Un owner y un steward, obligatorios y nombrados. Cada dominio mapea a un Product Owner y un Steward —personas reales, no «TI». Cuando una ingesta incumple su SLA, hay alguien a quien notificar. Cuando la definición de una columna se disputa, hay alguien que arbitra. La responsabilidad está codificada en la arquitectura, no relegada a una página de wiki que nadie mantiene.
  • Seguridad a nivel de esquema. El dominio se convierte en el esquema del Lakehouse: Sales_Salesforce, Finance_SAP, HR_Workday. Eso no es cosmético. El acceso se puede otorgar al esquema Sales como unidad, y todo el dominio queda gobernado por la misma ACL sin importar cuántas fuentes lo alimenten. Probá hacer eso cuando tu esquema se llama raw o staging.
  • Agrupamiento natural de entidades que van juntas. Las entidades de Sales —Opportunities, Products, Accounts— comparten calendarios de lanzamiento (release), definiciones de negocio y patrones de consumo. Agruparlas por dominio refleja la realidad de que tienen sentido juntas, y que ingerirlas desincronizadas crea inconsistencias que el negocio va a sentir aguas abajo.

El riesgo es real. Un error tipográfico en el nombre del archivo, un dominio renombrado a mitad del proyecto, un nombre que no existe en el glosario de negocio: cualquiera rompe el contrato. La mitigación es aburrida: una verificación de CI valida cada nombre nuevo contra una lista de permitidos de dominios y fuentes aprobados antes de que el PR pueda integrarse. Un linter, unas pocas líneas de código. La convención deja de ser una convención y se convierte en un contrato exigible.

Bronze deja de ser el problema de TI. Esa es la apuesta que paga.


El resultado: el grifo funciona

Números de implementaciones representativas —órdenes de magnitud, no SLAs:

  • Agregar una entidad nueva: típicamente menos de 15 minutos para una tabla relacional estándar; más para una API con rarezas de paginación o un archivo con un esquema desprolijo. Solo YAML, sin código.
  • Rollback de configuración: menos de 10 minutos. Revertís el PR, redesplegás el YAML.
  • 100% de la configuración versionada en Git. Este no es aproximado: cada cambio tiene un autor, un revisor y una marca de tiempo, porque no hay otra forma de cambiar la configuración.

Dos objetivos para los que la arquitectura está diseñada, y que tiene que seguir mereciéndose:

Ergonomía para junior. Alguien nuevo en el proyecto debería poder agregar una fuente copiando un YAML existente, cambiando lo que sea distinto, y abriendo un PR —sin leer código de notebooks ni tocar PySpark. Cuando eso deja de ser cierto, el motor está filtrando complejidad de vuelta hacia la configuración.

Legibilidad a las 3am. Cuando una carga falla a las 3am, el ingeniero de guardia debería abrir el YAML de la entidad que falló y entender qué se suponía que tenía que pasar, sin bucear en código de pipelines. La configuración es la documentación.

Cuando alguien pregunta qué hace tu capa Bronze, la mejor respuesta es: «nada interesante». Bronze aburrido no es el objetivo; es la consecuencia de una ingeniería que efectivamente se hizo. La emoción debería vivir en lo que construís por encima: Silver, Gold, modelos semánticos, tableros.

Bronze es la infraestructura invisible que hace posible todo eso.


Tradeoffs honestos

Secretos y credenciales — fuera de alcance por diseño. El YAML nunca lleva secretos. Ni cadenas de conexión fijas en el código, ni tokens, ni claves de API. Si una fuente necesita autenticación, el YAML apunta a una entrada de Key Vault; el motor la resuelve en tiempo de ejecución vía un service principal. En el momento que un secreto aterriza en Git, tu registro de auditoría (audit trail) se convierte en un pasivo en lugar de un activo. Configuración de ingesta en Git, secretos en Key Vault —dos sistemas, cero solapamiento.

El motor es un único punto de fallo. Doscientos pipelines artesanales fallan de forma independiente; un motor compartido con un bug afecta a las doscientas fuentes a la vez. La superficie de mitigación también es compartida (una corrección sana a las doscientas), pero el radio de impacto no es simétrico —invertí en la cobertura de tests del motor en consecuencia. Un efecto secundario: los logs por pipeline de Fabric se vuelven menos útiles cuando cada entidad corre bajo el mismo objeto orquestador. Necesitás tus propias tablas de control. Ese es el Post 4.


Qué viene

Pero hacer algo aburrido requiere resolver problemas que no son nada aburridos.

Próximo post — Metadatos hasta el fondo: ¿Quién valida el YAML? ¿Quién valida al validador? En un sistema dirigido por metadatos, el metadato mismo necesita gobierno. Son metadatos hasta el fondo.

Después — Battle Scars: El YAML se veía perfecto en el PR. Después Spark 3.x rechazó fechas de 1753. Lecciones operacionales y cicatrices de producción.

Y finalmente — Living Metadata: Capturamos metadatos con cada ingesta. Pero metadato que nadie lee es metadato muerto. Cómo convertirlo en decisiones.


Este es el primer post de la serie «Ingesta dirigida por metadatos en YAML». Los patrones descritos evolucionaron a través de varias implementaciones de Lakehouse empresariales y son agnósticos a la plataforma, aunque nuestra plataforma de referencia es Microsoft Fabric.

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *