Skip to content
SP StackPractices
intermediate Por StackPractices

Arquitectura Lakehouse — Lo Mejor de Ambos Mundos

Guía práctica de arquitectura Lakehouse: combinar flexibilidad de almacenamiento de data lake con confiabilidad de data warehouse usando formatos de tabla abiertos.

Nota para desarrolladores hispanohablantes: Esta guía incluye ejemplos y convenciones de nomenclatura adaptadas a equipos que trabajan en español. Cuando existen diferencias significativas en terminología técnica entre el inglés y el español, se indican explícitamente para facilitar la comunicación en equipos multiculturales.

Overview

La arquitectura Lakehouse, pionera de Databricks, unifica lo mejor de Data Lakes y Data Warehouses. Almacena datos en formatos abiertos (Parquet) en almacenamiento de objetos de bajo costo mientras añade garantías transaccionales, enforce de schema y time travel. Los formatos de tabla abiertos como Delta Lake, Apache Iceberg y Hudi hacen esto posible manteniendo capas de metadata que rastrean cambios, particiones y estadísticas.

Cuándo Usar

  • Necesitas confiabilidad de warehouse (ACID, schema) con economía de lake
  • El vendor lock-in en warehouses propietarios es una preocupación
  • Quieres una capa de almacenamiento para cargas BI y ML
  • Time travel y auditabilidad son requeridos
  • Los datos se consumen por múltiples motores (Spark, Presto, DuckDB, Snowflake)

Comparativa

CaracterísticaData LakeData WarehouseLakehouse
Costo de almacenamientoBajoAltoBajo
Transacciones ACIDNo
Enforce de schemaNo
Time travelNoLimitado
Formatos abiertosNo
Soporte ML/AIExcelenteLimitadoExcelente
Rendimiento BILentoRápidoRápido (con indexado)

Formatos de Tabla Abiertos

FormatoCaracterística PrincipalMejor Para
Delta LakeACID, time travel, schema enforcementEcosistemas Spark, streaming
Apache IcebergParticionado oculto, evolución de particionesCloud-native, multi-motor
Apache HudiProcesamiento incremental, updates a nivel de registroCDC, ingesta near-real-time

Delta Lake

from pyspark.sql import SparkSession

spark = SparkSession.builder \
    .appName("LakehouseDelta") \
    .config("spark.sql.extensions", "io.delta.sql.DeltaSparkSessionExtension") \
    .config("spark.sql.catalog.spark_catalog", "org.apache.spark.sql.delta.catalog.DeltaCatalog") \
    .getOrCreate()

# Crear tabla Delta con enforce de schema
spark.sql("""
    CREATE TABLE IF NOT EXISTS bronze.orders (
        order_id STRING,
        customer_id STRING,
        total DECIMAL(10,2),
        status STRING,
        created_at TIMESTAMP
    ) USING DELTA
    LOCATION 's3://lakehouse/bronze/orders'
""")

# Insert con garantías ACID
spark.sql("""
    INSERT INTO bronze.orders
    VALUES ('ord-001', 'cust-123', 99.99, 'placed', current_timestamp())
""")

# Update in-place
spark.sql("""
    UPDATE bronze.orders
    SET status = 'shipped'
    WHERE order_id = 'ord-001'
""")

# Time travel
spark.read.format("delta") \
    .option("versionAsOf", 0) \
    .load("s3://lakehouse/bronze/orders") \
    .show()

Apache Iceberg

from pyiceberg.catalog import load_catalog

catalog = load_catalog("rest", **{
    "uri": "https://catalog.example.com",
    "warehouse": "s3://lakehouse/"
})

# Crear tabla con particionado oculto
table = catalog.create_table(
    identifier="silver.events",
    schema=Schema(
        NestedField(1, "event_id", StringType()),
        NestedField(2, "event_type", StringType()),
        NestedField(3, "timestamp", TimestampType()),
        NestedField(4, "payload", StringType())
    ),
    partition_spec=PartitionSpec(
        PartitionField(source_id=3, field_id=1000, transform=Day(), name="day")
    )
)

# Evolución de particiones sin reescribir datos
with table.update_spec() as update:
    update.add_field("hour", Hour(source_column="timestamp"))

Apache Hudi

from pyspark.sql import SparkSession

spark = SparkSession.builder.appName("HudiExample").getOrCreate()

# Upserts a nivel de registro
df.write.format("hudi") \
    .option("hoodie.table.name", "customer_orders") \
    .option("hoodie.datasource.write.recordkey.field", "order_id") \
    .option("hoodie.datasource.write.precombine.field", "updated_at") \
    .option("hoodie.datasource.write.operation", "upsert") \
    .mode("append") \
    .save("s3://lakehouse/hudi/customer_orders")

# Lectura incremental
spark.read.format("hudi") \
    .option("hoodie.datasource.query.type", "incremental") \
    .option("hoodie.datasource.read.begin.instanttime", "20240101000000") \
    .load("s3://lakehouse/hudi/customer_orders")

Arquitectura Medallion con Lakehouse

Bronze (Raw)
    ├── Tablas Delta Lake / Iceberg / Hudi
    ├── Mínima transformación
    └── Historia completa retenida

Silver (Limpio)
    ├── Deduplicado, tipado, validado
    ├── Joined con datos de referencia
    └── Chequeos de calidad forzados

Gold (Curado)
    ├── Métricas de negocio agregadas
    ├── Optimizado para consumo BI
    └── Gestionado como productos de datos

Errores Comunes

  • Tratar lakehouse como solo almacenamiento — la capa de formato de tabla es crítica; sin ella, solo tienes un lake
  • Sobre-optimizar prematuramente — empieza con un formato (Delta es el más maduro), añade otros solo si es necesario
  • Sin estrategia de compactación — archivos pequeños matan el rendimiento; programa jobs de compactación regular
  • Ignorar metastore — Glue, Hive o Unity Catalog son esenciales para descubrimiento y gobernanza de tablas
  • Mezclar formatos de tabla en un pipeline — cada formato tiene diferentes semánticas; mezclarlos añade complejidad

FAQ

Qué formato de tabla debo elegir?

  • Delta Lake: Mejor para pipelines centrados en Spark y cargas de trabajo de streaming
  • Iceberg: Mejor para entornos multi-motor y evolución de particiones
  • Hudi: Mejor para CDC y procesamiento de datos incremental

Puedo consultar tablas lakehouse desde Snowflake o BigQuery? Sí. Snowflake soporta tablas Iceberg nativamente. BigQuery soporta BigLake tables sobre Iceberg y Delta via conectores.

Es Lakehouse más barato que un warehouse tradicional? El almacenamiento es significativamente más barato (S3/GCS vs propietario). Los costos de cómputo dependen del motor elegido. El TCO total suele ser menor, especialmente para datasets grandes.