{"schemaVersion":1,"id":"https://lucioduran.com/es/blog/clickhouse-olap-real-time-analytics-architecture","url":"https://lucioduran.com/es/blog/clickhouse-olap-real-time-analytics-architecture","title":"ClickHouse: OLAP Columnar para Analytics en Tiempo Real sobre 10 Mil Millones de Filas","description":"Guía de arquitectura de ClickHouse para analytics en tiempo real: internals del motor MergeTree, vistas materializadas para pre-agregación, optimización de queries en tablas de 10B+ filas, y análisis comparativo con DuckDB y Druid.","language":"es","datePublished":"2026-03-01","dateModified":null,"contentFormat":"text/markdown","content":"Databases\n\n# ClickHouse: OLAP Columnar para Analytics en Tiempo Real sobre 10 Mil Millones de Filas\n\n2026-03-016 min lectura\n\n[Lucio Durán](<https://lucioduran.com/es>)\nEngineering Manager & AI Solutions Architect\n\nTambién disponible en: [English](<https://lucioduran.com/blog/clickhouse-olap-real-time-analytics-architecture>)\n\n## Almacenamiento Columnar y Rendimiento de Queries Analíticas\n\nEn una base de datos orientada a filas como [PostgreSQL](<https://www.postgresql.org/>), los datos se almacenan fila por fila. Cuando corrés ` SELECT country, COUNT(*) FROM events WHERE event = 'click' GROUP BY country `, PostgreSQL lee cada fila del disco, incluyendo columnas que no se necesita. En una tabla con 50 columnas, se está leyendo 50x más datos de lo necesario.\n\nClickHouse almacena datos columna por columna. Esa misma query solo lee las columnas ` event ` y ` country `. En una tabla con 10 mil millones de filas y 50 columnas, eso es ~98% menos I/O. Pero mejora, cada columna se comprime independientemente. La columna ` event ` puede tener solo 20 valores distintos en 10 mil millones de filas, que se comprime de 80GB (strings crudos) a ~500MB con LZ4 + dictionary encoding.\n\n## MergeTree: El Motor Que Lo Hace Funcionar\n\nCada tabla de ClickHouse está respaldada por un motor de tabla. MergeTree es el motor principal para workloads analíticos:\n\n```sql\nCREATE TABLE events (\n tenant_id UInt32,\n event_date Date,\n event_time DateTime64(3),\n event_type LowCardinality(String),\n user_id UInt64,\n session_id String,\n page_url String,\n country LowCardinality(FixedString(2)),\n device_type LowCardinality(String),\n duration_ms UInt32,\n properties Map(String, String)\n)\nENGINE = MergeTree()\nPARTITION BY toYYYYMM(event_date)\nORDER BY (tenant_id, event_date, event_type, user_id)\nTTL event_date + INTERVAL 13 MONTH\nSETTINGS index_granularity = 8192;\n```\n\nDecisiones clave de diseño:\n\n**PARTITION BY**: Particiones mensuales por fecha de evento. Las particiones son la unidad de gestión de datos, es posible eliminar, mover o desprender meses enteros eficientemente.\n\n**ORDER BY**: Esta es la clave primaria y la decisión más importante. ClickHouse almacena datos ordenados por esta clave y construye un índice sparse (una entrada por cada ` index_granularity ` filas) para data skipping. Las queries que filtran por las columnas líderes de la clave ORDER BY saltean bloques enteros de datos sin leerlos.\n\n` tenant_id ` va primero porque cada query en un sistema multi-tenant filtra por tenant. Después ` event_date ` para queries de rango temporal, ` event_type ` para filtrado por categoría, y ` user_id ` para drilldowns a nivel de usuario.\n\n## Vistas Materializadas: Pre-Agregación en Tiempo de Ingesta\n\nLas vistas materializadas en ClickHouse no son lo que podrías esperar de PostgreSQL, se actualizan incrementalmente al momento del insert, no se refrescan periódicamente:\n\n```sql\nCREATE MATERIALIZED VIEW events_daily_mv\nENGINE = SummingMergeTree()\nPARTITION BY toYYYYMM(event_date)\nORDER BY (tenant_id, event_date, event_type, country)\nAS SELECT\n tenant_id,\n event_date,\n event_type,\n country,\n count() AS event_count,\n uniqState(user_id) AS unique_users,\n sumState(duration_ms) AS total_duration_ms\nFROM events\nGROUP BY tenant_id, event_date, event_type, country;\n```\n\nCuando hacés ` INSERT INTO events `, ClickHouse automáticamente transforma las nuevas filas a través del SELECT de la vista materializada e inserta los resultados agregados en la tabla ` events_daily_mv `.\n\n```sql\nSELECT\n event_date,\n event_type,\n sum(event_count) AS events,\n uniqMerge(unique_users) AS users,\n sumMerge(total_duration_ms) / sum(event_count) AS avg_duration_ms\nFROM events_daily_mv\nWHERE tenant_id = 42\n AND event_date BETWEEN '2026-01-01' AND '2026-01-31'\nGROUP BY event_date, event_type\nORDER BY event_date;\n```\n\nEsta query escanea la tabla pre-agregada (~10.000 filas para un mes) en vez de la tabla de eventos crudos (~500 millones de filas para el mismo tenant y período). Tiempo de respuesta: 15ms vs 1.800ms.\n\nUn setup típico incluye cuatro vistas materializadas sobre la tabla de eventos. Overhead total de almacenamiento para las cuatro tablas MV: ~3% de la tabla de eventos crudos. La mejora de rendimiento de queries es 50-200x para queries de dashboard.\n\n## Optimización de Queries en 10B Filas\n\nLas técnicas de optimización reales que importan a escala:\n\n### 1. Filtrar por columnas del ORDER BY\n\n```sql\n-- Rápido: tenant_id y event_date son las primeras dos columnas del ORDER BY\nSELECT event_type, count()\nFROM events\nWHERE tenant_id = 42 AND event_date = '2026-03-15'\nGROUP BY event_type;\n-- Escanea: ~2M filas de 10B (0.02%)\n\n-- Lento: country no está en la clave ORDER BY\nSELECT country, count()\nFROM events\nWHERE country = 'US'\nGROUP BY country;\n-- Escanea: las 10B filas todas\n```\n\n### 2. Funciones aproximadas para analytics a gran escala\n\n```sql\n-- Conteo de únicos exacto: 4.2 segundos en 10B filas\nSELECT uniqExact(user_id) FROM events WHERE tenant_id = 42;\n\n-- Conteo de únicos aproximado (2% error): 0.3 segundos\nSELECT uniq(user_id) FROM events WHERE tenant_id = 42;\n```\n\nPara dashboards donde +-2% de precisión está bien, ` uniq() ` (HyperLogLog) es 14x más rápido que ` uniqExact() `. Los stakeholders de negocio nunca notaron la diferencia.\n\n## ClickHouse vs DuckDB vs Druid: Análisis Comparativo\n\nEvaluando workloads de producción en los tres, cada uno brilla en diferentes áreas:\n\n| Criterio | ClickHouse | DuckDB | Druid |\n| --- | --- | --- | --- |\n| Escala de datos | 100GB - PB | 1GB - 100GB | 100GB - PB |\n| Usuarios concurrentes | Docenas a cientos | Un solo usuario | Cientos a miles |\n| Ingesta | Batch (alto throughput) | Batch/archivo | Streaming tiempo real |\n| Latencia de query (10B filas) | 200ms - 5s | N/A (solo local) | 100ms - 2s |\n| Complejidad operacional | Media | Ninguna (embebido) | Alta |\n| Soporte SQL | SQL analítico completo | SQL completo + extensiones | SQL limitado |\n| Mejor para | API backend de analytics | Data science, análisis local | Dashboards tiempo real |\n\n**DuckDB** es notable por lo que hace, queries analíticos en archivos locales con cero infraestructura. Es excelente para prototipar queries antes de deployar a ClickHouse y para análisis ad-hoc en exports CSV/Parquet. Pero no es un servidor. No es posible apuntar 50 usuarios de dashboard a una instancia de DuckDB.\n\n**Druid** está construido a propósito para analytics en tiempo real con queries sub-segundo en dimensiones de muy alta cardinalidad. Maneja queries concurrentes mejor que ClickHouse a escala extrema. Pero la complejidad operacional de Druid es significativamente mayor, tiene seis tipos de nodos, cada uno necesitando capacity planning separado. ClickHouse tiene un tipo de nodo.\n\n## La Arquitectura de Producción\n\n```\nKafka ──► Buffer Table ──► Events Table ──► Vistas Materializadas\n │\n ┌───────┼───────┐\n │ │ │\n Queries Queries Queries\n Dashboard API Ad-hoc\n```\n\nCluster ClickHouse: 3 nodos, cada uno con 32 cores, 128GB RAM, 4TB NVMe SSD. ReplicatedMergeTree con 2 réplicas para tolerancia a fallos.\n\n```sql\nCREATE TABLE events_buffer AS events\nENGINE = Buffer(currentDatabase(), events, 16, 10, 100, 10000, 100000, 1000000, 10000000);\n```\n\nLa Buffer table acumula inserts en memoria y flushea a la tabla events subyacente en batches. Esto convierte los inserts por mensaje del consumidor de Kafka en batch inserts eficientes de 10.000-100.000 filas.\n\nTasa de ingesta: ~120.000 eventos/segundo sostenidos. Carga de queries: ~200 queries/segundo entre todos los usuarios de dashboard. Latencia P99 para queries de dashboard (pegando a vistas materializadas): 180ms. P99 para queries ad-hoc en eventos crudos (un mes completo, un solo tenant): 3,2 segundos.\n\nTodo corre en tres servidores que cuestan alrededor de $2.400/mes. El setup equivalente en PostgreSQL (que ni siquiera podía servir las queries a tiempo) estaba corriendo en un r6g.8xlarge ($2.800/mes) y seguía tirando timeout.\n\nClickHouse no es magia. Son trade-offs de ingeniería hechos explícitos: entregás transacciones, updates a nivel de fila, y garantías ACID. A cambio, obteners rendimiento de queries analíticos que es genuinamente 100-1000x más rápido que bases de datos de propósito general. Para workloads de analytics, ese trade-off es obvio una vez que viste los números.\n\n## Preguntas frecuentes\n\n### Cuándo debería usar ClickHouse en vez de PostgreSQL para analytics?\n\nCuando tus queries de analytics escanean millones de filas con agregaciones (COUNT, SUM, AVG, percentiles) y tu volumen de datos excede lo que PostgreSQL maneja cómodamente (aproximadamente 100M+ filas). ClickHouse está construido a propósito para estos workloads y puede ser 100-1000x más rápido que PostgreSQL en queries de agregación. Sin embargo, ClickHouse no es un reemplazo de PostgreSQL, no soporta transacciones, foreign keys, ni lookups eficientes por clave primaria.\n\n### Cómo se compara ClickHouse con DuckDB?\n\nDuckDB es una base de datos analítica embebida, corre dentro del proceso de tu aplicación sin servidor. Es excelente para analytics de un solo usuario, notebooks de data science, y procesamiento de datasets hasta ~100GB. ClickHouse es un servidor distribuido diseñado para acceso concurrente multi-usuario, ingesta en tiempo real, y datasets desde 100GB hasta petabytes. Utilizar DuckDB para análisis local, ClickHouse para servicios de analytics en producción.\n\n### ClickHouse puede manejar ingesta de datos en tiempo real?\n\nSí, pero con caveats. ClickHouse puede ingestar millones de filas por segundo, pero está optimizado para inserts en batch (1000+ filas por INSERT). Los inserts de una sola fila crean partes de datos diminutas que aumentan la presión de merge y degradan el rendimiento de queries. Utilizar una buffer table o inserts en batch desde Kafka/colas de mensajes para patrones de ingesta en tiempo real.\n\n## Tips\n\n1. **Tip:** Se recomienda definir siempre la clave ` ORDER BY ` en tablas MergeTree para que coincida con los patrones más comunes de ` WHERE ` y ` GROUP BY `. ClickHouse usa la clave primaria para data skipping, si tus queries filtran por ` tenant_id ` y ` event_date `, colocarlos primero en el ` ORDER BY `. Las queries pueden pasar de 12 segundos a 200ms solo reordenando la clave primaria para coincidir con los patrones de acceso.\n2. **Tip:** Se recomienda utilizar ` LowCardinality(String) ` en vez de ` String ` plano para cualquier columna con menos de ~10.000 valores distintos (códigos de país, tipos de evento, campos de estado). Hace dictionary-encoding de los valores y puede reducir el almacenamiento un 80% mientras hace el filtrado 3-5x más rápido. Convertir una tabla de eventos de 2TB a LowCardinality en tres columnas puede encogerla a 400GB.\n3. **Tip:** Nunca se debe utilizar ` SELECT * ` en ClickHouse. El almacenamiento columnar significa que cada columna es un archivo separado, ` SELECT * ` lee cada columna del disco aunque solo necesites tres. En una tabla con 50 columnas, ` SELECT col1, col2, col3 ` corre 18x más rápido que ` SELECT * ` para el mismo filtro. Es necesario ser explícito sobre las columnas requeridas.\n4. **Tip:** Se recomienda establecer ` max_threads ` por query basándote en tu workload. El default usa todos los cores disponibles, que está bien para analytics de un solo usuario pero va a matar de hambre a queries concurrentes. Para una API de analytics multi-tenant sirviendo 50 usuarios concurrentes, establecer ` max_threads = 4 ` por query produce latencias p99 mucho más consistentes, 800ms en vez de swings salvajes entre 200ms y 5s.\n\n- clickhouse\n- olap\n- analytics\n- columnar-database\n- mergetree\n- materialized-views\n- real-time-analytics\n- duckdb\n- druid\n\n[Volver al Blog](<https://lucioduran.com/es/blog>)","headings":[{"level":1,"text":"ClickHouse: OLAP Columnar para Analytics en Tiempo Real sobre 10 Mil Millones de Filas","url":"https://lucioduran.com/es/blog/clickhouse-olap-real-time-analytics-architecture#title"},{"level":2,"text":"Almacenamiento Columnar y Rendimiento de Queries Analíticas","url":"https://lucioduran.com/es/blog/clickhouse-olap-real-time-analytics-architecture#almacenamiento-columnar-y-rendimiento-de-queries-analticas"},{"level":2,"text":"MergeTree: El Motor Que Lo Hace Funcionar","url":"https://lucioduran.com/es/blog/clickhouse-olap-real-time-analytics-architecture#mergetree-el-motor-que-lo-hace-funcionar"},{"level":2,"text":"Vistas Materializadas: Pre-Agregación en Tiempo de Ingesta","url":"https://lucioduran.com/es/blog/clickhouse-olap-real-time-analytics-architecture#vistas-materializadas-pre-agregacin-en-tiempo-de-ingesta"},{"level":2,"text":"Optimización de Queries en 10B Filas","url":"https://lucioduran.com/es/blog/clickhouse-olap-real-time-analytics-architecture#optimizacin-de-queries-en-10b-filas"},{"level":3,"text":"1. Filtrar por columnas del ORDER BY","url":"https://lucioduran.com/es/blog/clickhouse-olap-real-time-analytics-architecture#1-filtrar-por-columnas-del-order-by"},{"level":3,"text":"2. Funciones aproximadas para analytics a gran escala","url":"https://lucioduran.com/es/blog/clickhouse-olap-real-time-analytics-architecture#2-funciones-aproximadas-para-analytics-a-gran-escala"},{"level":2,"text":"ClickHouse vs DuckDB vs Druid: Análisis Comparativo","url":"https://lucioduran.com/es/blog/clickhouse-olap-real-time-analytics-architecture#clickhouse-vs-duckdb-vs-druid-anlisis-comparativo"},{"level":2,"text":"La Arquitectura de Producción","url":"https://lucioduran.com/es/blog/clickhouse-olap-real-time-analytics-architecture#la-arquitectura-de-produccin"},{"level":2,"text":"Preguntas frecuentes","url":"https://lucioduran.com/es/blog/clickhouse-olap-real-time-analytics-architecture#questions"},{"level":3,"text":"Cuándo debería usar ClickHouse en vez de PostgreSQL para analytics?","url":"https://lucioduran.com/es/blog/clickhouse-olap-real-time-analytics-architecture#question-1"},{"level":3,"text":"Cómo se compara ClickHouse con DuckDB?","url":"https://lucioduran.com/es/blog/clickhouse-olap-real-time-analytics-architecture#question-2"},{"level":3,"text":"ClickHouse puede manejar ingesta de datos en tiempo real?","url":"https://lucioduran.com/es/blog/clickhouse-olap-real-time-analytics-architecture#question-3"},{"level":2,"text":"Tips","url":"https://lucioduran.com/es/blog/clickhouse-olap-real-time-analytics-architecture#article-tips"}],"sections":[{"heading":"ClickHouse: OLAP Columnar para Analytics en Tiempo Real sobre 10 Mil Millones de Filas","level":1,"url":"https://lucioduran.com/es/blog/clickhouse-olap-real-time-analytics-architecture","content":"Databases"},{"heading":"ClickHouse: OLAP Columnar para Analytics en Tiempo Real sobre 10 Mil Millones de Filas","level":1,"url":"https://lucioduran.com/es/blog/clickhouse-olap-real-time-analytics-architecture#title","content":"2026-03-016 min lectura\n\n[Lucio Durán](<https://lucioduran.com/es>)\nEngineering Manager & AI Solutions Architect\n\nTambién disponible en: [English](<https://lucioduran.com/blog/clickhouse-olap-real-time-analytics-architecture>)"},{"heading":"Almacenamiento Columnar y Rendimiento de Queries Analíticas","level":2,"url":"https://lucioduran.com/es/blog/clickhouse-olap-real-time-analytics-architecture#almacenamiento-columnar-y-rendimiento-de-queries-analticas","content":"En una base de datos orientada a filas como [PostgreSQL](<https://www.postgresql.org/>), los datos se almacenan fila por fila. Cuando corrés ` SELECT country, COUNT(*) FROM events WHERE event = 'click' GROUP BY country `, PostgreSQL lee cada fila del disco, incluyendo columnas que no se necesita. En una tabla con 50 columnas, se está leyendo 50x más datos de lo necesario.\n\nClickHouse almacena datos columna por columna. Esa misma query solo lee las columnas ` event ` y ` country `. En una tabla con 10 mil millones de filas y 50 columnas, eso es ~98% menos I/O. Pero mejora, cada columna se comprime independientemente. La columna ` event ` puede tener solo 20 valores distintos en 10 mil millones de filas, que se comprime de 80GB (strings crudos) a ~500MB con LZ4 + dictionary encoding."},{"heading":"MergeTree: El Motor Que Lo Hace Funcionar","level":2,"url":"https://lucioduran.com/es/blog/clickhouse-olap-real-time-analytics-architecture#mergetree-el-motor-que-lo-hace-funcionar","content":"Cada tabla de ClickHouse está respaldada por un motor de tabla. MergeTree es el motor principal para workloads analíticos:\n\n```sql\nCREATE TABLE events (\n tenant_id UInt32,\n event_date Date,\n event_time DateTime64(3),\n event_type LowCardinality(String),\n user_id UInt64,\n session_id String,\n page_url String,\n country LowCardinality(FixedString(2)),\n device_type LowCardinality(String),\n duration_ms UInt32,\n properties Map(String, String)\n)\nENGINE = MergeTree()\nPARTITION BY toYYYYMM(event_date)\nORDER BY (tenant_id, event_date, event_type, user_id)\nTTL event_date + INTERVAL 13 MONTH\nSETTINGS index_granularity = 8192;\n```\n\nDecisiones clave de diseño:\n\n**PARTITION BY**: Particiones mensuales por fecha de evento. Las particiones son la unidad de gestión de datos, es posible eliminar, mover o desprender meses enteros eficientemente.\n\n**ORDER BY**: Esta es la clave primaria y la decisión más importante. ClickHouse almacena datos ordenados por esta clave y construye un índice sparse (una entrada por cada ` index_granularity ` filas) para data skipping. Las queries que filtran por las columnas líderes de la clave ORDER BY saltean bloques enteros de datos sin leerlos.\n\n` tenant_id ` va primero porque cada query en un sistema multi-tenant filtra por tenant. Después ` event_date ` para queries de rango temporal, ` event_type ` para filtrado por categoría, y ` user_id ` para drilldowns a nivel de usuario."},{"heading":"Vistas Materializadas: Pre-Agregación en Tiempo de Ingesta","level":2,"url":"https://lucioduran.com/es/blog/clickhouse-olap-real-time-analytics-architecture#vistas-materializadas-pre-agregacin-en-tiempo-de-ingesta","content":"Las vistas materializadas en ClickHouse no son lo que podrías esperar de PostgreSQL, se actualizan incrementalmente al momento del insert, no se refrescan periódicamente:\n\n```sql\nCREATE MATERIALIZED VIEW events_daily_mv\nENGINE = SummingMergeTree()\nPARTITION BY toYYYYMM(event_date)\nORDER BY (tenant_id, event_date, event_type, country)\nAS SELECT\n tenant_id,\n event_date,\n event_type,\n country,\n count() AS event_count,\n uniqState(user_id) AS unique_users,\n sumState(duration_ms) AS total_duration_ms\nFROM events\nGROUP BY tenant_id, event_date, event_type, country;\n```\n\nCuando hacés ` INSERT INTO events `, ClickHouse automáticamente transforma las nuevas filas a través del SELECT de la vista materializada e inserta los resultados agregados en la tabla ` events_daily_mv `.\n\n```sql\nSELECT\n event_date,\n event_type,\n sum(event_count) AS events,\n uniqMerge(unique_users) AS users,\n sumMerge(total_duration_ms) / sum(event_count) AS avg_duration_ms\nFROM events_daily_mv\nWHERE tenant_id = 42\n AND event_date BETWEEN '2026-01-01' AND '2026-01-31'\nGROUP BY event_date, event_type\nORDER BY event_date;\n```\n\nEsta query escanea la tabla pre-agregada (~10.000 filas para un mes) en vez de la tabla de eventos crudos (~500 millones de filas para el mismo tenant y período). Tiempo de respuesta: 15ms vs 1.800ms.\n\nUn setup típico incluye cuatro vistas materializadas sobre la tabla de eventos. Overhead total de almacenamiento para las cuatro tablas MV: ~3% de la tabla de eventos crudos. La mejora de rendimiento de queries es 50-200x para queries de dashboard."},{"heading":"Optimización de Queries en 10B Filas","level":2,"url":"https://lucioduran.com/es/blog/clickhouse-olap-real-time-analytics-architecture#optimizacin-de-queries-en-10b-filas","content":"Las técnicas de optimización reales que importan a escala:"},{"heading":"1. Filtrar por columnas del ORDER BY","level":3,"url":"https://lucioduran.com/es/blog/clickhouse-olap-real-time-analytics-architecture#1-filtrar-por-columnas-del-order-by","content":"```sql\n-- Rápido: tenant_id y event_date son las primeras dos columnas del ORDER BY\nSELECT event_type, count()\nFROM events\nWHERE tenant_id = 42 AND event_date = '2026-03-15'\nGROUP BY event_type;\n-- Escanea: ~2M filas de 10B (0.02%)\n\n-- Lento: country no está en la clave ORDER BY\nSELECT country, count()\nFROM events\nWHERE country = 'US'\nGROUP BY country;\n-- Escanea: las 10B filas todas\n```"},{"heading":"2. Funciones aproximadas para analytics a gran escala","level":3,"url":"https://lucioduran.com/es/blog/clickhouse-olap-real-time-analytics-architecture#2-funciones-aproximadas-para-analytics-a-gran-escala","content":"```sql\n-- Conteo de únicos exacto: 4.2 segundos en 10B filas\nSELECT uniqExact(user_id) FROM events WHERE tenant_id = 42;\n\n-- Conteo de únicos aproximado (2% error): 0.3 segundos\nSELECT uniq(user_id) FROM events WHERE tenant_id = 42;\n```\n\nPara dashboards donde +-2% de precisión está bien, ` uniq() ` (HyperLogLog) es 14x más rápido que ` uniqExact() `. Los stakeholders de negocio nunca notaron la diferencia."},{"heading":"ClickHouse vs DuckDB vs Druid: Análisis Comparativo","level":2,"url":"https://lucioduran.com/es/blog/clickhouse-olap-real-time-analytics-architecture#clickhouse-vs-duckdb-vs-druid-anlisis-comparativo","content":"Evaluando workloads de producción en los tres, cada uno brilla en diferentes áreas:\n\n| Criterio | ClickHouse | DuckDB | Druid |\n| --- | --- | --- | --- |\n| Escala de datos | 100GB - PB | 1GB - 100GB | 100GB - PB |\n| Usuarios concurrentes | Docenas a cientos | Un solo usuario | Cientos a miles |\n| Ingesta | Batch (alto throughput) | Batch/archivo | Streaming tiempo real |\n| Latencia de query (10B filas) | 200ms - 5s | N/A (solo local) | 100ms - 2s |\n| Complejidad operacional | Media | Ninguna (embebido) | Alta |\n| Soporte SQL | SQL analítico completo | SQL completo + extensiones | SQL limitado |\n| Mejor para | API backend de analytics | Data science, análisis local | Dashboards tiempo real |\n\n**DuckDB** es notable por lo que hace, queries analíticos en archivos locales con cero infraestructura. Es excelente para prototipar queries antes de deployar a ClickHouse y para análisis ad-hoc en exports CSV/Parquet. Pero no es un servidor. No es posible apuntar 50 usuarios de dashboard a una instancia de DuckDB.\n\n**Druid** está construido a propósito para analytics en tiempo real con queries sub-segundo en dimensiones de muy alta cardinalidad. Maneja queries concurrentes mejor que ClickHouse a escala extrema. Pero la complejidad operacional de Druid es significativamente mayor, tiene seis tipos de nodos, cada uno necesitando capacity planning separado. ClickHouse tiene un tipo de nodo."},{"heading":"La Arquitectura de Producción","level":2,"url":"https://lucioduran.com/es/blog/clickhouse-olap-real-time-analytics-architecture#la-arquitectura-de-produccin","content":"```\nKafka ──► Buffer Table ──► Events Table ──► Vistas Materializadas\n │\n ┌───────┼───────┐\n │ │ │\n Queries Queries Queries\n Dashboard API Ad-hoc\n```\n\nCluster ClickHouse: 3 nodos, cada uno con 32 cores, 128GB RAM, 4TB NVMe SSD. ReplicatedMergeTree con 2 réplicas para tolerancia a fallos.\n\n```sql\nCREATE TABLE events_buffer AS events\nENGINE = Buffer(currentDatabase(), events, 16, 10, 100, 10000, 100000, 1000000, 10000000);\n```\n\nLa Buffer table acumula inserts en memoria y flushea a la tabla events subyacente en batches. Esto convierte los inserts por mensaje del consumidor de Kafka en batch inserts eficientes de 10.000-100.000 filas.\n\nTasa de ingesta: ~120.000 eventos/segundo sostenidos. Carga de queries: ~200 queries/segundo entre todos los usuarios de dashboard. Latencia P99 para queries de dashboard (pegando a vistas materializadas): 180ms. P99 para queries ad-hoc en eventos crudos (un mes completo, un solo tenant): 3,2 segundos.\n\nTodo corre en tres servidores que cuestan alrededor de $2.400/mes. El setup equivalente en PostgreSQL (que ni siquiera podía servir las queries a tiempo) estaba corriendo en un r6g.8xlarge ($2.800/mes) y seguía tirando timeout.\n\nClickHouse no es magia. Son trade-offs de ingeniería hechos explícitos: entregás transacciones, updates a nivel de fila, y garantías ACID. A cambio, obteners rendimiento de queries analíticos que es genuinamente 100-1000x más rápido que bases de datos de propósito general. Para workloads de analytics, ese trade-off es obvio una vez que viste los números."},{"heading":"Preguntas frecuentes","level":2,"url":"https://lucioduran.com/es/blog/clickhouse-olap-real-time-analytics-architecture#questions","content":""},{"heading":"Cuándo debería usar ClickHouse en vez de PostgreSQL para analytics?","level":3,"url":"https://lucioduran.com/es/blog/clickhouse-olap-real-time-analytics-architecture#question-1","content":"Cuando tus queries de analytics escanean millones de filas con agregaciones (COUNT, SUM, AVG, percentiles) y tu volumen de datos excede lo que PostgreSQL maneja cómodamente (aproximadamente 100M+ filas). ClickHouse está construido a propósito para estos workloads y puede ser 100-1000x más rápido que PostgreSQL en queries de agregación. Sin embargo, ClickHouse no es un reemplazo de PostgreSQL, no soporta transacciones, foreign keys, ni lookups eficientes por clave primaria."},{"heading":"Cómo se compara ClickHouse con DuckDB?","level":3,"url":"https://lucioduran.com/es/blog/clickhouse-olap-real-time-analytics-architecture#question-2","content":"DuckDB es una base de datos analítica embebida, corre dentro del proceso de tu aplicación sin servidor. Es excelente para analytics de un solo usuario, notebooks de data science, y procesamiento de datasets hasta ~100GB. ClickHouse es un servidor distribuido diseñado para acceso concurrente multi-usuario, ingesta en tiempo real, y datasets desde 100GB hasta petabytes. Utilizar DuckDB para análisis local, ClickHouse para servicios de analytics en producción."},{"heading":"ClickHouse puede manejar ingesta de datos en tiempo real?","level":3,"url":"https://lucioduran.com/es/blog/clickhouse-olap-real-time-analytics-architecture#question-3","content":"Sí, pero con caveats. ClickHouse puede ingestar millones de filas por segundo, pero está optimizado para inserts en batch (1000+ filas por INSERT). Los inserts de una sola fila crean partes de datos diminutas que aumentan la presión de merge y degradan el rendimiento de queries. Utilizar una buffer table o inserts en batch desde Kafka/colas de mensajes para patrones de ingesta en tiempo real."},{"heading":"Tips","level":2,"url":"https://lucioduran.com/es/blog/clickhouse-olap-real-time-analytics-architecture#article-tips","content":"1. **Tip:** Se recomienda definir siempre la clave ` ORDER BY ` en tablas MergeTree para que coincida con los patrones más comunes de ` WHERE ` y ` GROUP BY `. ClickHouse usa la clave primaria para data skipping, si tus queries filtran por ` tenant_id ` y ` event_date `, colocarlos primero en el ` ORDER BY `. Las queries pueden pasar de 12 segundos a 200ms solo reordenando la clave primaria para coincidir con los patrones de acceso.\n2. **Tip:** Se recomienda utilizar ` LowCardinality(String) ` en vez de ` String ` plano para cualquier columna con menos de ~10.000 valores distintos (códigos de país, tipos de evento, campos de estado). Hace dictionary-encoding de los valores y puede reducir el almacenamiento un 80% mientras hace el filtrado 3-5x más rápido. Convertir una tabla de eventos de 2TB a LowCardinality en tres columnas puede encogerla a 400GB.\n3. **Tip:** Nunca se debe utilizar ` SELECT * ` en ClickHouse. El almacenamiento columnar significa que cada columna es un archivo separado, ` SELECT * ` lee cada columna del disco aunque solo necesites tres. En una tabla con 50 columnas, ` SELECT col1, col2, col3 ` corre 18x más rápido que ` SELECT * ` para el mismo filtro. Es necesario ser explícito sobre las columnas requeridas.\n4. **Tip:** Se recomienda establecer ` max_threads ` por query basándote en tu workload. El default usa todos los cores disponibles, que está bien para analytics de un solo usuario pero va a matar de hambre a queries concurrentes. Para una API de analytics multi-tenant sirviendo 50 usuarios concurrentes, establecer ` max_threads = 4 ` por query produce latencias p99 mucho más consistentes, 800ms en vez de swings salvajes entre 200ms y 5s.\n\n- clickhouse\n- olap\n- analytics\n- columnar-database\n- mergetree\n- materialized-views\n- real-time-analytics\n- duckdb\n- druid\n\n[Volver al Blog](<https://lucioduran.com/es/blog>)"}],"links":[{"text":"Lucio Durán","url":"https://lucioduran.com/es"},{"text":"English","url":"https://lucioduran.com/blog/clickhouse-olap-real-time-analytics-architecture"},{"text":"PostgreSQL","url":"https://www.postgresql.org/"},{"text":"Volver al Blog","url":"https://lucioduran.com/es/blog"}],"structuredData":[{"@context":"https://schema.org","@graph":[{"@type":"Person","@id":"https://lucioduran.com/#person","name":"Lucio Durán","url":"https://lucioduran.com/"},{"@type":"WebSite","@id":"https://lucioduran.com/#website","url":"https://lucioduran.com/","name":"Lucio Durán","inLanguage":["en","es"],"publisher":{"@id":"https://lucioduran.com/#person"}},{"@type":"WebPage","@id":"https://lucioduran.com/es/blog/clickhouse-olap-real-time-analytics-architecture#page","url":"https://lucioduran.com/es/blog/clickhouse-olap-real-time-analytics-architecture","name":"ClickHouse: OLAP Columnar para Analytics en Tiempo Real sobre 10 Mil Millones de Filas | Lucio Durán","description":"Guía de arquitectura de ClickHouse para analytics en tiempo real: internals del motor MergeTree, vistas materializadas para pre-agregación, optimización de queries en tablas de 10B+ filas, y análisis comparativo con DuckDB y Druid.","inLanguage":"es","author":{"@id":"https://lucioduran.com/#person"},"publisher":{"@id":"https://lucioduran.com/#person"},"isPartOf":{"@id":"https://lucioduran.com/#website"},"isAccessibleForFree":true,"datePublished":"2026-03-01","mainEntity":{"@id":"https://lucioduran.com/es/blog/clickhouse-olap-real-time-analytics-architecture#article"},"breadcrumb":{"@id":"https://lucioduran.com/es/blog/clickhouse-olap-real-time-analytics-architecture#breadcrumb"}},{"@type":"BlogPosting","@id":"https://lucioduran.com/es/blog/clickhouse-olap-real-time-analytics-architecture#article","mainEntityOfPage":{"@id":"https://lucioduran.com/es/blog/clickhouse-olap-real-time-analytics-architecture#page"},"headline":"ClickHouse: OLAP Columnar para Analytics en Tiempo Real sobre 10 Mil Millones de Filas","description":"Guía de arquitectura de ClickHouse para analytics en tiempo real: internals del motor MergeTree, vistas materializadas para pre-agregación, optimización de queries en tablas de 10B+ filas, y análisis comparativo con DuckDB y Druid.","datePublished":"2026-03-01","author":{"@type":"Person","@id":"https://lucioduran.com/#person","name":"Lucio Durán","url":"https://lucioduran.com"},"publisher":{"@id":"https://lucioduran.com/#person"},"url":"https://lucioduran.com/es/blog/clickhouse-olap-real-time-analytics-architecture","inLanguage":"es","isPartOf":{"@id":"https://lucioduran.com/es/blog#blog"},"articleSection":"Databases","keywords":"clickhouse, olap, analytics, columnar-database, mergetree, materialized-views, real-time-analytics, duckdb, druid","wordCount":1116,"timeRequired":"PT6M","workTranslation":[{"@type":"BlogPosting","@id":"https://lucioduran.com/blog/clickhouse-olap-real-time-analytics-architecture#article","inLanguage":"en","url":"https://lucioduran.com/blog/clickhouse-olap-real-time-analytics-architecture"}],"isAccessibleForFree":true},{"@type":"Blog","@id":"https://lucioduran.com/es/blog#blog","url":"https://lucioduran.com/es/blog","name":"Blog","inLanguage":"es","author":{"@type":"Person","@id":"https://lucioduran.com/#person","name":"Lucio Durán","url":"https://lucioduran.com/"},"publisher":{"@id":"https://lucioduran.com/#person"},"isPartOf":{"@id":"https://lucioduran.com/#website"}},{"@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Inicio","item":"https://lucioduran.com/es"},{"@type":"ListItem","position":2,"name":"Blog","item":"https://lucioduran.com/es/blog"},{"@type":"ListItem","position":3,"name":"ClickHouse: OLAP Columnar para Analytics en Tiempo Real sobre 10 Mil Millones de Filas","item":"https://lucioduran.com/es/blog/clickhouse-olap-real-time-analytics-architecture"}],"@id":"https://lucioduran.com/es/blog/clickhouse-olap-real-time-analytics-architecture#breadcrumb"},{"@type":"FAQPage","mainEntity":[{"@type":"Question","name":"Cuándo debería usar ClickHouse en vez de PostgreSQL para analytics?","acceptedAnswer":{"@type":"Answer","text":"Cuando tus queries de analytics escanean millones de filas con agregaciones (COUNT, SUM, AVG, percentiles) y tu volumen de datos excede lo que PostgreSQL maneja cómodamente (aproximadamente 100M+ filas). ClickHouse está construido a propósito para estos workloads y puede ser 100-1000x más rápido que PostgreSQL en queries de agregación. Sin embargo, ClickHouse no es un reemplazo de PostgreSQL, no soporta transacciones, foreign keys, ni lookups eficientes por clave primaria."}},{"@type":"Question","name":"Cómo se compara ClickHouse con DuckDB?","acceptedAnswer":{"@type":"Answer","text":"DuckDB es una base de datos analítica embebida, corre dentro del proceso de tu aplicación sin servidor. Es excelente para analytics de un solo usuario, notebooks de data science, y procesamiento de datasets hasta ~100GB. ClickHouse es un servidor distribuido diseñado para acceso concurrente multi-usuario, ingesta en tiempo real, y datasets desde 100GB hasta petabytes. Utilizar DuckDB para análisis local, ClickHouse para servicios de analytics en producción."}},{"@type":"Question","name":"ClickHouse puede manejar ingesta de datos en tiempo real?","acceptedAnswer":{"@type":"Answer","text":"Sí, pero con caveats. ClickHouse puede ingestar millones de filas por segundo, pero está optimizado para inserts en batch (1000+ filas por INSERT). Los inserts de una sola fila crean partes de datos diminutas que aumentan la presión de merge y degradan el rendimiento de queries. Utilizar una buffer table o inserts en batch desde Kafka/colas de mensajes para patrones de ingesta en tiempo real."}}],"@id":"https://lucioduran.com/es/blog/clickhouse-olap-real-time-analytics-architecture#questions","url":"https://lucioduran.com/es/blog/clickhouse-olap-real-time-analytics-architecture#questions","inLanguage":"es","isPartOf":{"@id":"https://lucioduran.com/es/blog/clickhouse-olap-real-time-analytics-architecture#page"},"author":{"@id":"https://lucioduran.com/#person"}}]}],"contentHash":"ad9f0a0dc2fcb4c99adf8d7185a8d1f3a4e79ea63e18cb9bcb2c9e1a1b756439","provenance":{"method":"Deterministic serialization of the server-rendered main element","source":"https://lucioduran.com/es/blog/clickhouse-olap-real-time-analytics-architecture","hashAlgorithm":"sha256","hashScope":"JSON document fields before contentHash and provenance","sourceHTMLBytes":68525,"contentBytes":11857}}
