Saltar a contenido

Guía 02 — Lakehouse con Apache Iceberg

¿Por qué Iceberg?

Iceberg es el ganador estructural de la "open table format war" tras: - Adquisición de Tabular por Databricks (~$2B, jun 2024). - Liberación de Polaris por Snowflake (jul 2024, ahora en ASF). - Soporte nativo en AWS S3 Tables, Google BigLake, Azure, Cloudera, Confluent. - Iceberg v3 (primavera 2025): Deletion Vectors, Row Lineage, VARIANT type. - Hudi añadió soporte nativo de lectura Iceberg. - Delta UniForm exporta como Iceberg sin duplicar.

Conceptos fundamentales

Metadata layout

s3://lakehouse/db/table/
├── data/                          ← Parquet files
│   ├── 00000-0-abc.parquet
│   └── 00001-0-def.parquet
├── metadata/
│   ├── v1.metadata.json           ← table metadata (versionado)
│   ├── v2.metadata.json
│   ├── snap-12345-1-abc.avro      ← manifest list (snapshot)
│   └── abc-m0.avro                ← manifest file (lista archivos)
└── (no _SUCCESS file, no Hive partitions on disk)

Snapshots y time travel

Cada commit genera un snapshot. Permite: - Time travel: SELECT * FROM tbl FOR VERSION AS OF 12345 o FOR TIMESTAMP AS OF '2026-05-01'. - Rollback: ALTER TABLE tbl SET CURRENT SNAPSHOT 12345. - Branching y tagging para releases.

Hidden partitioning

A diferencia de Hive (year=/month=/day=), Iceberg almacena el spec en metadata. Cambias el partitioning sin reescribir datos:

ALTER TABLE orders ADD PARTITION FIELD day(created_at);

Las queries siguen funcionando sin cambios.

Schema evolution

  • Add column ✓ (sin cost)
  • Drop column ✓
  • Rename column ✓
  • Promote type (int → long, float → double) ✓
  • Reorder columns ✓
  • Cambio incompatible: bloqueado por defecto.

DDL recomendado

CREATE TABLE catalog.silver_sales.orders (
  order_id      BIGINT,
  customer_id   BIGINT,
  product_id    BIGINT,
  amount        DECIMAL(18,2),
  currency      STRING,
  status        STRING,
  created_at    TIMESTAMP,
  updated_at    TIMESTAMP,
  _ingested_at  TIMESTAMP,
  _source       STRING
)
USING iceberg
PARTITIONED BY (day(created_at), bucket(16, customer_id))
TBLPROPERTIES (
  'format-version'='3',
  'write.format.default'='parquet',
  'write.parquet.compression-codec'='zstd',
  'write.parquet.compression-level'='3',
  'write.target-file-size-bytes'='268435456',
  'write.delete.mode'='merge-on-read',
  'write.update.mode'='merge-on-read',
  'write.merge.mode'='merge-on-read',
  'write.metadata.delete-after-commit.enabled'='true',
  'write.metadata.previous-versions-max'='100',
  'history.expire.max-snapshot-age-ms'='604800000',  -- 7 días
  'history.expire.min-snapshots-to-keep'='10'
);

Operaciones de mantenimiento (críticas)

Compactación (semanal mínimo)

CALL system.rewrite_data_files(
  table => 'silver_sales.orders',
  options => map(
    'target-file-size-bytes', '268435456',
    'min-input-files', '5'
  )
);

Expirar snapshots viejos

CALL system.expire_snapshots(
  table => 'silver_sales.orders',
  older_than => TIMESTAMP '2026-05-01 00:00:00',
  retain_last => 10
);

Reescribir manifests (cuando >100 escritores concurrentes)

CALL system.rewrite_manifests('silver_sales.orders');

Eliminar archivos huérfanos

CALL system.remove_orphan_files(
  table => 'silver_sales.orders',
  older_than => TIMESTAMP '2026-05-01 00:00:00'
);

Branching para CI/CD

-- En PR: crear branch
ALTER TABLE silver_sales.orders CREATE BRANCH pr_1234;

-- Spark/Trino escribe en branch
INSERT INTO silver_sales.orders.`branch_pr_1234` SELECT ...;

-- Tests: leer del branch
SELECT * FROM silver_sales.orders VERSION AS OF 'pr_1234';

-- Si pasa: fast-forward main
ALTER TABLE silver_sales.orders REPLACE BRANCH main FROM pr_1234;

-- Si no: drop branch
ALTER TABLE silver_sales.orders DROP BRANCH pr_1234;

Tags para releases

ALTER TABLE silver_sales.orders CREATE TAG release_2026_05 RETAIN 90 DAYS;

Catálogos REST

Catálogo Vendor Notas
Polaris Snowflake (ASF) OSS, multi-engine, RBAC, federación
Unity Catalog Databricks OSS, integración profunda Databricks, federation
Nessie Dremio Git-like branching nativo
AWS Glue REST endpoint AWS Sin ops, integración AWS
Tabular (acquired) Integrado en Databricks
HMS Apache Hive Legacy, evitar en nuevos proyectos

Performance tips

  1. File size: target 256MB (write.target-file-size-bytes).
  2. Z-ordering / sort order: ORDER BY al escribir reduce skipping.
  3. Bloom filters: habilitar para columnas con high cardinality.
  4. Page indexes: Parquet 2.10+ permite skip a nivel página.
  5. Column projection: SELECT solo las columnas necesarias.
  6. Predicate pushdown: filtros con columnas particionadas siempre que sea posible.
  7. Manifest caching: habilitar en motores que lo soporten.

Migración desde Hive / Delta

De Hive a Iceberg

CALL system.migrate('hive_db.legacy_table');

De Delta a Iceberg (Databricks UniForm)

ALTER TABLE delta_table SET TBLPROPERTIES (
  'delta.universalFormat.enabledFormats' = 'iceberg'
);

Benchmarks empíricos (ResearchGate 2025, escala TB)

Métrica Iceberg Delta Hudi
Write throughput inicial medio alto bajo
Query planning rápido rápido lento
Partition pruning excelente bueno bueno
Upsert streaming bueno bueno excelente
Degradación >100 escritores alta (contención manifest list) media baja

Conclusión: Iceberg para BI/analytics general, Delta si stack Databricks-first, Hudi para CDC streaming masivo con upserts continuos.