¿Qué hace lakehouse?
Soporte de decisión de nivel de referencia para almacenamiento en formato de tabla abierta y código Spark. Las versiones y fechas se encuentran en references/landscape-2026-11.md; verifícalas antes de cualquier afirmación específica de versión. Para detalles sobre Databricks, Snowflake o servicios de AWS, usa el skill de-vendors y los skills oficiales de proveedores a los que apunta.
1. ¿Se justifica un lakehouse?
Empieza por la opción más barata que encaje y sube solo cuando un desencadenante listado sea real y esté medido.
- Postgres (o DuckDB sobre un conjunto de archivos) cuando los datos son menores de aproximadamente 1 TB, un solo motor los lee y los usuarios analíticos concurrentes son pocos. Razón: no hay catálogo, ni compactación, ni clúster que gestionar.
- Warehouse en la nube cuando muchos usuarios SQL necesitan concurrencia elástica y nadie necesita un segundo motor sobre los mismos bytes. Razón: el almacenamiento, mantenimiento y gobernanza gestionados están incluidos en el precio.
- Lakehouse cuando al menos una de estas condiciones se cumple: decenas de TB o más; varios motores (Spark, Trino, DuckDB, Python) deben leer una misma copia; las cargas de trabajo de ML o basadas en archivos comparten tablas con SQL; el costo de almacenamiento en almacenamiento de objetos supera claramente al almacenamiento del warehouse en tu volumen; el formato abierto es un requisito estricto (salida, auditoría, neutralidad de proveedor).
- No elijas un lakehouse por una "escala futura" que no tenga un número asociado. Pide recuentos de filas, crecimiento diario, lectores concurrentes y motores antes de recomendar.
- Datos pequeños y medianos que aún quieran formato abierto: DuckLake o parquet simple más DuckDB supera a ejecutar Spark.
2. Disposición de Parquet
- Apunta a 128 MB a 1 GB por archivo; busca 256 MB a 512 MB cuando no estés seguro. Muchos archivos por debajo de 32 MB significan que los costos de listado y apertura dominan la consulta.
- Grupos de filas de aproximadamente 128 MB sin comprimir (1M+ filas) por archivo; grupos más pequeños debilitan el podado min/max y la compresión.
- Particiona solo por columnas de baja cardinalidad que las consultas filtren (fecha o mes, región). Una buena partición contiene al menos un archivo de tamaño objetivo completo por lote de escritor; si las particiones promedian menos de 1 GB, usa un grano más grueso o clustering en su lugar.
- Nunca particiones por un id de alta cardinalidad. Usa clustering (clustering líquido de Delta, orden de clasificación / Z-order de Iceberg) para esas columnas.
- Ordena o agrupa dentro de los archivos en la columna más filtrada para que las estadísticas min/max puedan podar grupos de filas.
- Las escrituras en streaming o micro-batches frecuentes crean archivos pequeños; programa compactación, no confíes en que los escritores dimensionen los archivos.
3. Elegir un formato de tabla
Decide según el motor y catálogo que ya tienes, no por listas de características.
- Delta Lake: elígelo cuando Databricks o Spark son el motor principal, o Unity Catalog ya es la capa de gobernanza. La mejor integración con Spark; otros motores lo leen, pero el soporte de escritura varía.
- Apache Iceberg: elígelo cuando múltiples motores deben escribir, o quieres catálogos neutrales a proveedores (REST). v3 añade vectores de eliminación, linaje de filas y un tipo variant; confirma que cada motor y catálogo en la ruta soporta las características v3 que usas antes de actualizar una tabla, porque un lector antiguo fallará en una versión de formato más nueva.
- DuckLake: elígelo para datos pequeños y medianos, un solo equipo, y una base de datos SQL que ya operas como catálogo (los metadatos viven en tablas simples). Barato de ejecutar, ecosistema joven; no lo elijas cuando Spark, Trino o Snowflake deban escribir las mismas tablas.
- Las capas de interoperabilidad (Delta UniForm, metadatos Iceberg para tablas Delta) son rutas de lectura. Elige un formato como escritor de registro por tabla.
- Cambiar de formato más tarde implica una reescritura o conversión de metadatos; elige el escritor de registro deliberadamente.
4. Catálogos
- El catálogo es el coordinador de confirmaciones y el punto de control de acceso; elígelo antes de que los detalles del formato importen.
- Unity Catalog: el predeterminado centrado en Databricks; gobernanza para Delta e Iceberg.
- AWS Glue y S3 Tables: elígelos cuando la pila es nativa de AWS; S3 Tables gestiona la compactación por ti pero te vincula a AWS.
- Catálogo REST de Iceberg (por ejemplo, Polaris): elígelo para neutralidad multi-motor y multi-nube.
- DuckLake: el catálogo es una base de datos SQL; respáldalo con Postgres para uso multi-usuario, SQLite o DuckDB solo para un escritor.
- Nunca dejes que dos catálogos registren la misma ruta de tabla; las confirmaciones en uno corromperán la vista del otro.
5. Mantenimiento (programalo, o la tabla se degrada)
- Compacta archivos pequeños:
OPTIMIZEen Delta,rewrite_data_filesen Iceberg, fusión de archivos adyacentes en DuckLake. Ejecuta después de una ingesta intensa, no por escritura. - Elimina datos antiguos:
VACUUMen Delta,expire_snapshotsmás eliminación de archivos huérfanos en Iceberg, expiración de instantáneas y luego limpieza en DuckLake. La retención debe exceder la ventana del lector más longevo o de time travel, porque eliminar archivos bajo un lector en ejecución lo rompe. - Clustering o Z-order solo para columnas que aparecen en filtros; re-agrupar reescribe datos y cuesta cómputo.
- Comprueba el recuento de archivos y el tamaño promedio antes y después; un trabajo de mantenimiento que no cambia ninguno no hizo nada.
- Ejecuta primero un ensayo de VACUUM o eliminación de huérfanos donde el motor lo ofrezca.
6. MERGE, evolución de esquema, time travel
- MERGE: desduplica la fuente en la clave de coincidencia primero (las coincidencias de origen duplicadas fallan o se aplican dos veces). Incluye un predicado de partición o clustering en la cláusula ON para que el motor pode archivos en lugar de escanear toda la tabla.
- Prefiere append más MERGE periódico o una clave idempotente sobre repetir MERGE de tabla completa en tablas grandes.
- Evolución de esquema: las columnas aditivas son seguras; los renombres y la ampliación de tipos dependen del formato y la versión (mapeo de columnas en Delta, IDs de campo en Iceberg). Verifica el soporte en los lectores que importan antes de renombrar. Nunca habilites la fusión automática de esquema en una entrada de pipeline de producción que no controlas.
- Time travel es una herramienta de depuración, auditoría y reversión, no una copia de seguridad. Cuesta los archivos retenidos; cuanto más larga la retención, más almacenamiento y metadatos más lentos. Establece la retención deliberadamente y mantén copias de seguridad reales de las tablas críticas.
7. Esenciales de PySpark 4.x
- Las transformaciones son perezosas; nada se ejecuta hasta una acción. Construye un plan, dispara una escritura;
count()oshow()repetidos re-ejecutan todo el plan a menos que uses caché deliberadamente. - Evita
collect(),toPandas()y.rdden datos grandes; traen todo al driver. Limita o agrega primero. - Usa funciones integradas o expresiones SQL en lugar de UDFs de Python. Los UDFs bloquean la optimización del optimizador y serializan filas. Si es inevitable, usa un UDF vectorizado (Arrow).
- Las operaciones amplias (join, groupBy, window, distinct) hacen shuffle. Filtra y proyecta antes de ellas; revisa el plan con
explain(). - Broadcasta un lado pequeño de un join (
broadcast(df)) cuando quepa en la memoria del executor; no broadcastes una tabla sin límite. - Adaptive Query Execution está activado por defecto en Spark 4.x y maneja coalescencia de particiones, joins asimétricos y cambio de estrategia de join; ajusta después de leer el plan, no antes.
- La asimetría se muestra como una tarea lenta; añade sal o aísla la clave caliente solo después de que AQE demuestre ser insuficiente.
- Spark Connect es el modo cliente-servidor: el código se ejecuta como un cliente ligero sobre una sesión remota. No confíes en internos JVM del driver (
sparkContext, APIs RDD) en código Connect. - Escribe con un tamaño de archivo objetivo explícito mediante compactación, no reparticionando a un número adivinado.
8. Ruta local primero
Desarrolla y prueba en DuckDB o polars antes de pagar por un clúster.
- DuckDB lee Delta e Iceberg a través de extensiones y adjunta catálogos DuckLake; polars lee Delta e Iceberg de manera perezosa con pushdown de predicados.
- Usa una muestra o una partición contra el diseño real de la tabla para que el comportamiento de podado de archivos coincida.
- Confirma las semánticas de escritura (MERGE, vectores de eliminación, características v3) en el motor real; los lectores locales pueden ir rezagados en las características del formato. Trata una lectura local exitosa como necesaria, no suficiente.
Procedimiento
- Obtén los números: tamaño, crecimiento, escritores, lectores, motores. 2. Recorre la sección 1; detente en la opción más barata que encaje. 3. Si es lakehouse: elige catálogo, luego formato (secciones 4, 3). 4. Establece la disposición y un programa de mantenimiento antes de la primera carga. 5. Prototipa localmente (sección 8), luego ejecuta en el clúster con la salida de
explain()revisada. 6. Verifica con recuentos de archivos y planes de consulta, no con suposiciones.
Errores conocidos y soluciones: consulta references/gotchas.md.