TL;DR — Resumen Rápido

Guía completa de ClickHouse para analítica en tiempo real: almacenamiento columnar, motores MergeTree, replicación, vistas materializadas e ingesta de datos.

ClickHouse es un DBMS orientado a columnas de código abierto, construido para consultas OLAP en tiempo real a escala. Desarrollado por Yandex en 2016, procesa miles de millones de filas por segundo en hardware convencional combinando almacenamiento columnar, ejecución vectorizada de consultas y compresión agresiva de datos.

Requisitos Previos

  • Host Linux o Docker con al menos 4 GB de RAM (16 GB+ recomendado para producción).
  • Conocimientos básicos de SQL.
  • Para replicación: ZooKeeper 3.6+ o ClickHouse Keeper (incluido desde v22.4).

Arquitectura de ClickHouse

Almacenamiento Columnar

Las bases de datos relacionales tradicionales almacenan todas las columnas de una fila juntas en disco. ClickHouse almacena cada columna en un archivo separado. Cuando una consulta toca solo 3 de 100 columnas, ClickHouse lee aproximadamente el 3% de los datos. Esta afinidad columnar también permite mejores tasas de compresión porque los valores similares repetidos se empaquetan de forma eficiente.

Ejecución Vectorizada de Consultas

ClickHouse procesa datos en lotes de 8,192–65,536 filas a la vez (el chunk o bloque). Las instrucciones SIMD de la CPU operan simultáneamente sobre vectores enteros de valores. Combinado con la compilación de consultas vía LLVM, las agregaciones que tardarían minutos en una base de datos orientada a filas se completan en segundos.

Familia de Motores MergeTree

MotorMejor Para
MergeTreeAnalítica de solo escritura, motor base
ReplacingMergeTreeDeduplicación last-write-wins
SummingMergeTreeContadores y sumas preagregas
AggregatingMergeTreeEstados de agregación (con vistas materializadas)
CollapsingMergeTreeFilas mutables via columna de signo
ReplicatedMergeTreeReplicación HA via Keeper/ZooKeeper

Compresión de Datos

ClickHouse usa LZ4 por defecto y soporta ZSTD. Los codecs por columna se apilan: CODEC(Delta, LZ4) reduce columnas de marcas de tiempo en más del 90%. Las tasas de compresión típicas son de 5–15× sobre los datos sin procesar.


Instalación

apt (Ubuntu/Debian)

apt-get install -y apt-transport-https ca-certificates curl gnupg
curl -fsSL 'https://packages.clickhouse.com/rpm/lts/repodata/repomd.xml.key' \
  | gpg --dearmor -o /usr/share/keyrings/clickhouse-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/clickhouse-keyring.gpg] \
  https://packages.clickhouse.com/deb stable main" \
  > /etc/apt/sources.list.d/clickhouse.list
apt-get update && apt-get install -y clickhouse-server clickhouse-client
systemctl enable --now clickhouse-server

Docker

docker run -d \
  --name clickhouse \
  --ulimit nofile=262144:262144 \
  -p 8123:8123 -p 9000:9000 \
  -v clickhouse-data:/var/lib/clickhouse \
  clickhouse/clickhouse-server:latest

Configuración Inicial

config.xml

<listen_host>0.0.0.0</listen_host>
<path>/var/lib/clickhouse/</path>

users.xml — Perfiles y Cuotas

<profiles>
  <default>
    <max_memory_usage>10000000000</max_memory_usage>
    <max_threads>8</max_threads>
  </default>
</profiles>

Diseño de Tablas

CREATE TABLE eventos
(
    fecha_evento   Date,
    hora_evento    DateTime64(3),
    user_id        UInt64,
    accion         LowCardinality(String),
    ingresos       Decimal(18, 4)
)
ENGINE = MergeTree()
PARTITION BY toYYYYMM(fecha_evento)
ORDER BY (accion, user_id, hora_evento);

Use LowCardinality(String) para columnas con menos de 10,000 valores distintos (códigos de país, métodos HTTP). Evite Nullable siempre que sea posible — use valores centinela como '' o 0.


Ingesta de Datos

# Cliente interactivo
clickhouse-client --host=localhost --user=default --password=secreto

# Inserción por lotes desde CSV
clickhouse-client --query="INSERT INTO eventos FORMAT CSVWithNames" < eventos.csv

Motor Kafka

CREATE TABLE eventos_kafka ( ... )
ENGINE = Kafka
SETTINGS kafka_broker_list = 'broker:9092',
         kafka_topic_list = 'eventos',
         kafka_group_name = 'clickhouse-consumer',
         kafka_format = 'JSONEachRow';

CREATE MATERIALIZED VIEW eventos_mv TO eventos AS
SELECT * FROM eventos_kafka;

Vistas Materializadas

CREATE TABLE eventos_por_hora
(
    accion    LowCardinality(String),
    hora      DateTime,
    cnt       AggregateFunction(count),
    ingresos  AggregateFunction(sum, Decimal(18,4))
)
ENGINE = AggregatingMergeTree()
ORDER BY (accion, hora);

CREATE MATERIALIZED VIEW eventos_por_hora_mv
TO eventos_por_hora AS
SELECT accion,
       toStartOfHour(hora_evento) AS hora,
       countState()               AS cnt,
       sumState(ingresos)         AS ingresos
FROM eventos
GROUP BY accion, hora;

Replicación y Fragmentación

CREATE TABLE eventos ON CLUSTER mi_cluster
( ... )
ENGINE = ReplicatedMergeTree(
    '/clickhouse/tables/{shard}/eventos', '{replica}'
)
PARTITION BY toYYYYMM(fecha_evento)
ORDER BY (accion, user_id, hora_evento);

CREATE TABLE eventos_dist ON CLUSTER mi_cluster
AS eventos
ENGINE = Distributed(mi_cluster, default, eventos, rand());

ClickHouse vs Alternativas

ClickHouseTimescaleDBApache DruidDuckDBBigQueryElasticsearch
AlmacenamientoColumnarFila + compresiónSegmentos columnaresColumnarColumnarÍndice invertido
VelocidadMuy altaAltaAltaMuy altaAltaMedia
IngestaMuy altaAltaAltaBajaAltaAlta
StreamingMotor KafkaAggs continuasNativoNoPub/SubLogstash
Auto-alojadoNo
Mejor paraOLAP/logs/eventosSeries temporalesEventosArchivos localesDW serverlessBúsqueda texto

Ejemplo Práctico: Analítica de Logs

CREATE TABLE nginx_logs
(
    fecha_log    Date,
    hora_log     DateTime64(3),
    ip_cliente   IPv4,
    metodo       LowCardinality(String),
    uri          String,
    status       UInt16,
    bytes        UInt64,
    tiempo_req   Float32
)
ENGINE = MergeTree()
PARTITION BY fecha_log
ORDER BY (status, hora_log)
TTL fecha_log + INTERVAL 90 DAY DELETE;

Errores Comunes

  • FINAL es costoso — Evite SELECT ... FINAL en tablas grandes; programe OPTIMIZE TABLE ... FINAL fuera de horas pico.
  • Inserts pequeños destruyen el rendimiento — Agrupe siempre al menos 10,000 filas por INSERT o active async_insert = 1.
  • Orden de JOIN — El lado derecho del JOIN se carga en memoria. Coloque siempre la tabla más pequeña a la derecha.
  • Particionado excesivo — Particionar por día en tablas de alto volumen crea demasiados fragmentos pequeños. Prefiera particiones mensuales.

Resumen

  • El almacenamiento columnar + vectorizado ofrece agregaciones 100-1000× más rápidas para cargas analíticas.
  • La familia MergeTree cubre deduplicación, preagregar y mutabilidad.
  • El diseño de esquema es la palanca de ajuste más importante: ORDER BY para patrones de consulta, PARTITION BY para retención.
  • Las vistas materializadas + AggregatingMergeTree crean pipelines de agregación con latencia cero.
  • Los motores Kafka y S3 eliminan el ETL externo para ingesta en streaming y por lotes.

Artículos Relacionados