Módulo 1: From File Format To Table Format
Presentación del módulo: de un archivo Parquet a una tabla Iceberg real
Por qué existe este módulo
Si llegaste hasta acá habiendo recorrido las seis guías anteriores del ecosistema de Data Engineering de NIEVA, ya viste a Kiosko —la cadena de tiendas de conveniencia con app de delivery, S01 Kiosko Centro en Bogotá, S02 Kiosko Norte en Lima, S03 Kiosko Sur en Santiago— resolver el mismo problema de fondo, una y otra vez, con herramientas distintas. data-engineering-foundations-guide escribió su capa gold en Parquet, particionada por fecha, con el patrón overwrite-partition: borra la partición vieja, escribe la nueva. data-modeling-for-analytics-guide historizó dim_product a mano, agregando tres columnas (valid_from, valid_to, is_current) y escribiendo un MERGE INTO propio en DuckDB para mantenerlas. dbt-analytics-engineering-guide automatizó exactamente esa misma técnica de columnas con dbt snapshot, que genera dbt_valid_from, dbt_valid_to y dbt_scd_id sin que nadie tenga que escribir el MERGE a mano. spark-and-distributed-processing-guide escribió fact_orders_at_scale.parquet —diez millones de filas— particionado por carpetas con partitionBy("store_id"), el patrón Hive que cualquier motor de big data reconoce.
Fíjate en algo que las cuatro soluciones tienen en común, aunque se sientan completamente distintas: todas terminan en Parquet. Ni una sola guía anterior cambió el formato de archivo — todas escribieron, en algún punto, un .parquet en disco. Lo que cambió fue lo que cada equipo tuvo que construir alrededor de ese archivo para que se comportara como una tabla real: atomicidad a mano (foundations), columnas de historia a mano (data-modeling), columnas de historia automatizadas mediante una herramienta externa (dbt), una convención de carpetas que hay que conocer de memoria para aprovechar (spark). Cuatro soluciones, cuatro herramientas distintas, y el mismo techo exacto debajo de las cuatro: Parquet es un formato de archivo, no un formato de tabla, y un formato de archivo no sabe nada de sí mismo más allá de sus propias filas — no sabe qué versión es, no sabe qué versión existió antes, no sabe si la escritura que lo produjo terminó completa o a medias.
Esta guía —lakehouse-and-iceberg-guide, la séptima de las 17 del ecosistema— enseña Apache Iceberg, el formato de tabla que mueve esa responsabilidad de "comportarse como una tabla" del ingeniero que lo usa al motor que lo implementa. Este módulo 1 no resuelve todavía ninguno de los cuatro problemas a fondo —eso toma el resto de la guía—; hace algo más elemental y más importante: nombra, una por una, con evidencia y con código citado de cada guía anterior, las cuatro veces exactas en que ese techo ya apareció, y después da el primer paso real hacia la solución — instalar PyIceberg y cargar el mismo fact_orders.parquet de siempre como la primera tabla Iceberg de Kiosko.
El caso que nos acompaña: Kiosko, sin un solo dato nuevo
Kiosko no cambia. Las tres tiendas siguen siendo las mismas (stores: store_id, store_name, city, heredado literal de foundations), los cuatro productos siguen siendo los mismos (P001 Bottled Water 600ml en beverages a 0.40, P002 Energy Bar en snacks a 0.60, P003 Instant Coffee Sachet en beverages a 0.35, P004 Phone Charger Cable en electronics a 2.10), y la semana fija de cuarenta órdenes (2026-08-03 al 2026-08-09) sigue anclando el mismo revenue total que ya verificaron las seis guías anteriores, cada una con su propio motor: 106.15 (S01=38.3, S02=38.8, S03=29.05). Esta guía no inventa un caso nuevo ni un modelo nuevo — toma el fact_orders.parquet que spark-and-distributed-processing-guide (módulo 3 de esa guía) ya dejó escrito en disco, y lo convierte en una tabla Iceberg real.
Lo único genuinamente nuevo en este módulo es el vocabulario de Iceberg que se suma al que ya conoces: el namespace de catálogo se llama kiosko, y la primera tabla que vas a crear se llama kiosko.fact_orders — un identificador de dos partes (namespace.table) que vas a usar durante el resto de esta guía.
Una analogía: la caja de fotos sueltas y el álbum con índice
Imagina dos formas de guardar las fotos de un evento. La primera es una caja de fotos sueltas: cada foto está bien revelada, en papel de buena calidad, perfectamente nítida — pero la caja, como objeto, no sabe nada más allá de contener fotos. No sabe cuántas hay sin que alguien las cuente a mano. No sabe en qué orden se tomaron a menos que alguien las haya numerado por detrás. Si alguien saca diez fotos y mete diez distintas, la caja no tiene forma de decirte "esto cambió" — simplemente contiene lo que contiene en este momento, sin memoria de lo que contenía ayer.
La segunda forma es un álbum con un índice al frente: la primera página dice, con exactitud, cuántas fotos hay, en qué orden están, y en qué página vive cada una. Si alguien agrega fotos nuevas, el álbum no las mezcla con las viejas sin avisar — actualiza su índice, y ahora ese índice apunta a una colección distinta, mientras la colección anterior sigue existiendo, intacta, en caso de que alguien quiera volver a mirarla.
Un archivo .parquet suelto en un directorio es la caja de fotos sueltas: los datos adentro están perfectamente bien escritos —columnar, comprimido, tipado, exactamente lo que ya usaste en las seis guías anteriores—, pero el archivo, como objeto, no sabe nada de sí mismo. No sabe si es la versión de hoy o la de ayer. No sabe si la escritura que lo produjo terminó completa. No hay ningún índice al que preguntarle "¿qué versiones de esta tabla existieron, y en qué orden?". Apache Iceberg es el álbum: el mismo Parquet de siempre, exactamente el mismo formato de archivo por dentro, pero ahora con un índice —el catálogo y la cadena de metadata— que sabe, en todo momento, con exactitud, qué versión de la tabla es la vigente, y cómo llegar a cualquier versión anterior sin haber tenido que diseñar esa capacidad a mano.
Ejemplo trabajado: lo que un Parquet suelto puede y no puede decirte
Antes de instalar nada de Iceberg, vale la pena ver, con código, exactamente qué te da y qué no te da un archivo Parquet por sí solo — el punto de partida exacto de este módulo. Vas a reconstruir el fact_orders.parquet que spark-and-distributed-processing-guide dejó escrito en su módulo 3, usando pyarrow directamente (sin ningún clúster, sin JVM), y vas a leerlo como cualquier archivo:
# inspect_plain_parquet.py
import pyarrow.parquet as pq
# fact_orders.parquet: el mismo archivo que Spark M3 ya dejo escrito,
# reconstruido aqui con pyarrow para que este modulo sea autocontenido
pa_table = pq.read_table("fact_orders.parquet")
print(f"Filas: {pa_table.num_rows}")
print(f"Columnas: {pa_table.num_columns}")
print()
print(pa_table.schema)
Qué esperar (verificado corriendo el script real, sobre las cuarenta filas de Kiosko):
Filas: 40
Columnas: 7
order_id: string not null
store_id: string not null
product_id: string not null
quantity: int32 not null
unit_price: double not null
revenue: double not null
order_ts: timestamp[us] not null
Esto es todo lo que el archivo, por sí solo, puede decirte: cuántas filas tiene ahora mismo, y con qué esquema. Fíjate en todo lo que no puede decirte, sin que nadie construya algo adicional por fuera del propio archivo:
- ¿Es esta la única versión que existió alguna vez, o hay una versión de ayer en algún otro lado? El archivo no lo sabe — necesitarías una convención externa (una carpeta con fecha, un sufijo en el nombre) para siquiera empezar a responder eso.
- ¿La escritura que produjo este archivo terminó completa? Si el proceso que lo generó murió a mitad de camino, no hay ninguna señal dentro del propio Parquet que te avise — simplemente tendrías un archivo truncado, indistinguible de uno completo hasta que alguien intente leerlo con cuidado.
- ¿Puedo pedir "muéstrame cómo se veía esta tabla el 10 de agosto", sin tener yo que haber guardado esa versión a mano? No. El archivo no tiene memoria de versiones anteriores — sobrescribirlo destruye lo anterior, sin dejar rastro.
Estas tres preguntas sin respuesta no son un defecto de pyarrow, ni de Parquet como formato columnar —Parquet sigue siendo, y va a seguir siendo durante toda esta guía, el formato de archivo que efectivamente guarda los datos—. Son, con precisión, la definición exacta de lo que le falta a un archivo para ser una tabla. La lección 2 de este módulo nombra, una por una, las cuatro veces que el ecosistema completo de Kiosko ya chocó con esta misma falta de respuestas, cada vez con una solución distinta y parcial.
Diagrama: qué se agrega encima del mismo Parquet de siempre
flowchart TB
subgraph YA["Ya construido (las seis guias anteriores)"]
A["fact_orders.parquet\n(columnar, tipado, comprimido)"]
B["Un archivo suelto en disco\nsin memoria de versiones anteriores"]
end
subgraph NUEVO["Esta guia: Apache Iceberg"]
C["Catalogo: kiosko\n(apunta al metadata vigente)"]
D["Cadena metadata -> manifest list ->\nmanifest files (modulo 2)"]
E["Snapshots automaticos en cada commit\n(modulo 3, sin columnas de historia)"]
F["Evolucion de esquema segura\n(modulo 4)"]
G["Particion oculta y evolucion de particion\n(modulo 5)"]
H["MERGE INTO / upsert nativos\n(modulo 6)"]
end
A -->|"se lee, no se reescribe"| C
B -.->|"reemplazado por"| D
C --> D --> E --> F --> G --> H
El mapa de los 8 módulos de esta guía
lakehouse-and-iceberg-guide es la séptima guía del ecosistema de Data Engineering de NIEVA. Ocho módulos la componen:
| # | Módulo | De qué se trata |
|---|---|---|
| 1 | De formato de archivo a formato de tabla (estás aquí) | Las cuatro veces que Parquet solo no alcanzó; archivo vs tabla; instalar PyIceberg; primera tabla Iceberg de Kiosko. |
| 2 | Anatomía de una tabla Iceberg | Catálogo → metadata → manifest list → manifest files → archivos de datos; inspeccionar la tabla en disco y con la API. |
| 3 | Snapshots y time travel | Cada escritura es un snapshot nuevo; AS OF un snapshot-id; el cambio de P002 recuperado sin columnas de historia. |
| 4 | Evolución de esquema sin reescribir | Por qué overwrite-partition nunca fue atómico; agregar/renombrar/borrar columnas sin tocar los datos existentes. |
| 5 | Partición oculta y evolución de partición | Carpetas Hive vs partición oculta; transforms de partición; evolucionar el esquema de partición sin reescribir 10 millones de filas. |
| 6 | MERGE INTO y upserts nativos | Las tres formas en que Kiosko ya resolvió el cambio de P002, más la cuarta: MERGE INTO de Iceberg vía Spark y table.upsert() de PyIceberg. |
| 7 | Catálogos, mantenimiento y Delta Lake por contraste | Catálogos de producción nombrados; compactación y expiración de snapshots; Delta Lake citado una sola vez. |
| 8 | Proyecto: el lakehouse de Kiosko | El capstone: todas las tablas de Kiosko sobre Iceberg, verificación final, mapa hacia las guías hermanas. |
Fíjate en la progresión: este módulo 1 te da el porqué y la primera tabla. El módulo 2 te da la anatomía — qué hay, de verdad, dentro del directorio que este módulo va a crear. El módulo 3 te da el superpoder central de todo el formato: viajar en el tiempo sin haber diseñado ninguna columna para eso. Los módulos 4, 5 y 6 te dan las tres garantías operativas que un lakehouse real necesita —esquema, partición, upserts—. Y los módulos 7 y 8 cierran con mantenimiento, contraste con Delta Lake, y el capstone que junta todas las piezas.
El mapa de este módulo
Dentro del módulo 1, ocho lecciones construyen la idea de a poco:
Leccion Pregunta que contesta
──────── ──────────────────────────────────────────────────────────────
L1 (esta) De donde venimos, y a donde va este modulo
L2 ¿Cuales son, con nombre y apellido, las cuatro veces que
Parquet solo no alcanzo en este ecosistema?
L3 ¿Que diferencia exacta hay entre "formato de archivo"
y "formato de tabla"?
L4 Instala PyIceberg y crea un catalogo local, de verdad
L5 Crea el namespace kiosko y la tabla kiosko.fact_orders
L6 Carga las 40 filas de fact_orders.parquet dentro de Iceberg
L7 Verifica que el total sigue siendo 106.15, ahora leido
desde una tabla Iceberg
L8 Proyecto: la primera tabla Iceberg de Kiosko, de punta a punta
La lección 2 retoma, con código citado literal de cada guía anterior, las cuatro veces que ya viste este problema. La lección 3 define con precisión qué separa un formato de archivo de un formato de tabla, antes de instalar una sola herramienta. Las lecciones 4, 5 y 6 instalan PyIceberg, crean el catálogo, el namespace y la tabla, y cargan los datos reales de Kiosko. Y las lecciones 7 y 8 verifican, con el mismo número que ya confirmaron las seis guías anteriores (106.15), que la migración fue exacta.
La frontera: qué NO entra en este módulo (ni en esta guía)
Este módulo instala PyIceberg local, con un catálogo SQL respaldado por SQLite y almacenamiento en el filesystem — sin cuenta de nube, sin JVM, sin Spark. pip install "pyiceberg[sql-sqlite,pyarrow]" es la única instalación de este módulo.
Y a nivel de guía completa, la frontera con las hermanas del ecosistema ya está trazada:
- Cómputo distribuido a fondo (particionado y shuffle como motor de trabajo diario, Catalyst, caché) →
spark-and-distributed-processing-guide. Spark aparece una sola vez, en el módulo 6, como el cliente SQL que correMERGE INTOcontra una tabla Iceberg. - Modelado dimensional conceptual (por qué el grano es el que es, star vs snowflake, SCD) → ya lo enseñó
data-modeling-for-analytics-guide. El star schema de Kiosko llega ya diseñado. - Transformación versionada como código (dbt,
ref(), tests declarativos) →dbt-analytics-engineering-guide. Aquí no hay un proyecto dbt: el adaptadordbt-icebergse nombra en el módulo 7, sin construir un solo modelo. - Orquestación real (DAGs, sensores, reintentos gestionados) →
airflow-and-declarative-orchestration-guide. Todo el código de esta guía se corre a mano, desde la terminal. - Streaming/CDC real →
streaming-with-kafka-and-flink-guide. El cambio deP002sigue llegando como un valor fijo declarado en Python. - Catálogos gestionados en la nube (AWS Glue Catalog, S3 Tables, Unity Catalog) →
aws-core-services-guide. Esta guía usa catálogos 100% locales.
Dentro de este módulo 1 específicamente: vas a cargar los datos exactamente como Spark ya los dejó particionados —sin tocar la partición todavía—; convertir esa partición Hive en partición oculta de Iceberg es, con precisión, el trabajo del módulo 5.
Errores comunes
Pensar que Iceberg reemplaza a Parquet como formato de archivo. Qué pasa: alguien, al escuchar "formato de tabla", asume que Iceberg va a guardar los datos en algún formato binario nuevo y propio, distinto de Parquet. Por qué pasa: el nombre "formato de tabla" suena, a primera escucha, como una alternativa competidora a "formato de archivo". Cómo detectarlo: si después de este módulo esperas encontrar archivos .iceberg en vez de .parquet dentro del directorio de la tabla, revisa el ejemplo trabajado de esta lección — los datos siguen siendo Parquet, exactamente el mismo formato columnar de siempre. Cómo corregirlo: Iceberg se sienta encima de Parquet, nunca lo reemplaza — la lección 3 de este módulo hace esa distinción precisa, y el módulo 2 completo te muestra, en disco, que los archivos de datos siguen siendo .parquet legibles con cualquier herramienta que ya conoces.
Saltarse la lección 2 porque "ya viví esos cuatro problemas, no necesito que me los repitan". Qué pasa: alguien, familiarizado con las seis guías anteriores, decide que puede avanzar directo a instalar PyIceberg sin leer la lección 2. Por qué pasa: cada problema individual ya se sintió resuelto en su momento, así que revisarlos de nuevo se siente redundante. Cómo detectarlo: si no puedes nombrar, sin mirar atrás, cuál de los cuatro problemas resuelve cada módulo de esta guía (2 al 6), te falta la lección 2 — no es una repetición decorativa, es el mapa que conecta cada módulo siguiente con un dolor real y ya vivido. Cómo corregirlo: la lección 2 no vuelve a resolver ningún problema — cita, literal, el código exacto de cada guía anterior, y nombra con precisión qué le faltó a cada solución. Ese mapa es lo que hace que instalar Iceberg se sienta como una continuación, no como una herramienta nueva sin conexión con lo ya aprendido.
Ejercicios
Ejercicio 1 — Nombra la pregunta sin respuesta. Sin mirar el ejemplo trabajado de esta lección, escribe de memoria las tres preguntas que un archivo Parquet suelto no puede responder por sí solo.
Ver solución
- ¿Es esta la única versión que existió, o hay una versión anterior en algún otro lado? 2. ¿La escritura que produjo este archivo terminó completa, o pudo haber quedado a medias? 3. ¿Puedo pedir "muéstrame cómo se veía esta tabla en una fecha pasada", sin haber guardado esa versión a mano yo mismo? Las tres preguntas comparten una raíz común: un archivo Parquet solo sabe describirse a sí mismo, ahora mismo — no tiene memoria de nada anterior, ni forma de garantizar que su propia escritura fue atómica.
Ejercicio 2 — Traza la analogía tú mismo. Usando la analogía de la caja de fotos sueltas y el álbum con índice, explica en 2-3 frases qué representa, en esa analogía, el catálogo de Iceberg —aunque todavía no lo hayas instalado, basándote solo en lo que leíste en esta lección—.
Ver solución
El catálogo es el índice al frente del álbum: no contiene las fotos en sí —esas siguen siendo los archivos Parquet, el contenido real—, pero sabe, en todo momento, cuál es la colección de fotos vigente y dónde encontrar su índice detallado (el archivo de metadata). Cuando alguien agrega fotos nuevas, el catálogo no mezcla nada a ciegas: actualiza su puntero hacia un índice nuevo, mientras el índice anterior —y las fotos a las que apuntaba— sigue existiendo, intacto, para quien quiera consultarlo. La lección 4 de este módulo instala ese catálogo de verdad, como un SqlCatalog local respaldado por SQLite.
Ejercicio 3 — Predicción. Antes de leer la lección 4, escribe tu propia hipótesis: ¿por qué crees que PyIceberg necesita un catálogo separado, en vez de simplemente leer y escribir archivos Parquet directamente, como ya hiciste en spark-and-distributed-processing-guide?
Ver solución
No hay una única respuesta correcta —es un ejercicio de predicción—, pero una hipótesis razonable, basada en el ejemplo trabajado de esta lección, apunta a esto: si Iceberg solo leyera y escribiera archivos Parquet sueltos, tendría exactamente el mismo problema que ya viste —ningún archivo, por más bien escrito que esté, sabe cuál es "la versión vigente" sin que algo externo se lo diga—. El catálogo es, con precisión, ese "algo externo": un único lugar, consultado en cada operación, que le dice a cualquier lector "la versión vigente de kiosko.fact_orders es esta, apunta a este archivo de metadata exacto". Sin un catálogo, cada lector tendría que adivinar cuál Parquet es el vigente — exactamente el problema que Iceberg existe para resolver.
Resumen y siguiente paso
En esta lección viste el punto exacto donde te dejaron las seis guías anteriores del ecosistema —cuatro soluciones distintas, todas terminando en Parquet, todas construyendo algo adicional alrededor de ese archivo para que se comportara como una tabla— y las tres preguntas concretas que un archivo Parquet suelto no puede responder por sí solo. Recorriste el mapa completo de los ocho módulos de esta guía y las ocho lecciones de este módulo, y trazaste la frontera con las guías hermanas del ecosistema.
Antes de avanzar deberías poder: explicar, con tus propias palabras, por qué las cuatro soluciones de las guías anteriores comparten el mismo techo aunque se sientan distintas; y nombrar las tres preguntas que un Parquet suelto no puede responder.
La lección 2 se gana el derecho a instalar cualquier herramienta: antes de eso, retoma, una por una, con código citado literal de cada guía anterior, las cuatro veces exactas en que este ecosistema ya chocó con ese techo.
Recursos
- Apache Iceberg — documentación oficial, "What is Iceberg?", la definición formal de formato de tabla que esta lección presenta con la analogía del álbum. iceberg.apache.org/docs/latest. En inglés.
- PyIceberg — documentación oficial (quickstart), la instalación y el flujo que este módulo va a instalar en la lección 4. py.iceberg.apache.org. En inglés.
- DISEÑO de
data-engineering-foundations-guide— fuente del patrónoverwrite-partitionque la lección 2 retoma primero.src/guides/data-engineering-foundations-guide/DISENO.md. En español. - DISEÑO de
data-modeling-for-analytics-guide— fuente dedim_product_scdconvalid_from/valid_to/is_currenty el cambio canónico deP002.src/guides/data-modeling-for-analytics-guide/DISENO.md. En español. - DISEÑO de
dbt-analytics-engineering-guide— fuente dedbt snapshoty sus columnasdbt_valid_from/dbt_valid_to/dbt_scd_id.src/guides/dbt-analytics-engineering-guide/DISENO.md. En español. - DISEÑO de
spark-and-distributed-processing-guide— fuente defact_orders.parquety del particionado Hive (partitionBy("store_id")) que la lección 2 cierra.src/guides/spark-and-distributed-processing-guide/DISENO.md. En español. - DISEÑO de esta guía — el mapa completo de los ocho módulos, incluida la advertencia de mercado sobre Iceberg.
src/guides/lakehouse-and-iceberg-guide/DISENO.md. En español.