Almacenamiento de series temporales
1) Por qué una arquitectura separada para series de tiempo
Las series de tiempo (time series) son secuencias de pares (timestamp, value) con etiquetas (labels) que se caracterizan por:- Alta velocidad de grabación (ingest) y periodicidad.
- Lecturas por intervalos de tiempo (scan + agregados/funciones de ventana).
- Cardinalidad explosiva debido a combinaciones de etiquetas.
- Necesidad de retoque (límite de tiempo de retención) y downsampling (compresión por tiempo).
- De ahí el modelo especial de almacenamiento, los formatos de compresión y los protocolos de consulta.
2) Modelo de datos y contrato de métricas
2. 1 Nombres y etiquetas
metric_name: verbo/sustantivo en singular ('http _ requests _ total', 'cpu _ usage _ seconds _ total').
labels: claves-atributos ('job', 'instance', 'dc', 'pod', 'status', 'method').
Invariantes: no cambiar la semántica del nombre, añadir versiones ('metric _ v2') en cambios incompatibles.
2. 2 Tipos de filas
Gauge (instantánea), Counter (acumulativo), Histograma/Resumen (distribuciones/cuantili), Event/Span (trace point).
Para finanzas/densidades: fije las unidades y agregabilidad (sumadas/promediadas).
2. 3 Políticas de retiro y rollo
Detalles calientes (segundos/1-10 min) → unidades calientes (5m/1h) → frías (1d/1w).
Para counter, almacenar agregados rate/deriv.
3) Ruta de grabación: recepción, amortiguación, compacto
3. 1 Ingest-pipeline
Scrape (pull, Prometheus) o push (OTLP/StatsD/Graphite), a menudo a través de gateway/agent.
Bufering en WAL (write-ahead log), luego compacto en segmentos/bloques (arquitectura similar a LSM).
Batching y clasificación por tiempo aumentan la compresión y la velocidad.
3. 2 Tratamiento fuera de orden y tomas
Ventana de tolerancia (ventana lateness, por ejemplo, 5-15 min) + política: 'drop | upsert | keep-last'.
Deduplicación por '(series_id, timestamp)' con versionalidad o «el último registro gana».
3. 3 Compresión
Delta-of-delta para marcas de tiempo, Gorilla/XOR para float, RLE y varint para enteros, dictionary para etiquetas.
El tamaño óptimo del bloque («chanka») de 1-8K puntos es un compromiso entre IOPS y CPU.
4) Esquemas de almacenamiento: TSDB vs SQL/columnistas
4. 1 TSDB especializados
Prometheus (localmente, retoque corto, PromQL, remote_write).
VictoriaMetrics/M3/InfluxDB - escala horizontal, retén largo, lectura remota.
Los formatos de bloque están optimizados para range scan + agregaciones de licitación.
4. 2 Motores relacionales/de columna
TimescaleDB (PostgreSQL): hipertables, chancas en tiempo/espacio, aggregates continuos.
ClickHouse: MergeTree/TTL/representaciones materializadas, excelente compresión y agregación en el tiempo.
Selección: por ecosistema de consultas (SQL vs PromQL), requerimientos de join/BI y habilidades operativas del equipo.
5) Esquema y ejemplos
5. 1 TimescaleDB: hipertable + aggregate continuo
sql
CREATE TABLE metrics_cpu(
ts timestamptz NOT NULL,
host text NOT NULL,
dc text NOT NULL,
usage double precision NOT NULL,
PRIMARY KEY (ts, host, dc)
);
SELECT create_hypertable('metrics_cpu', by_range('ts'), chunk_time_interval => interval '1 day');
-- Continuous unit (5 minutes)
CREATE MATERIALIZED VIEW cpu_5m
WITH (timescaledb. continuous) AS
SELECT time_bucket('5 minutes', ts) AS ts5m, host, dc, avg(usage) AS avg_usage
FROM metrics_cpu GROUP BY 1,2,3;
-- Politicians
SELECT add_retention_policy('metrics_cpu', INTERVAL '14 days');
SELECT add_retention_policy('cpu_5m', INTERVAL '180 days');
5. 2 ClickHouse: almacenamiento de agregación
sql
CREATE TABLE metrics_cpu (
ts DateTime,
host LowCardinality(String),
dc LowCardinality(String),
usage Float32
) ENGINE = MergeTree()
PARTITION BY toYYYYMM(ts)
ORDER BY (host, dc, ts)
TTL ts + INTERVAL 14 DAY
SETTINGS index_granularity = 8192;
-- Rollup in hourly detail
CREATE MATERIALIZED VIEW cpu_1h
ENGINE = SummingMergeTree()
PARTITION BY toYYYYMM(ts)
ORDER BY (host, dc, ts)
POPULATE AS
SELECT toStartOfHour(ts) AS ts, host, dc, avg(usage) AS usage
FROM metrics_cpu GROUP BY ts, host, dc;
5. 3 Prometheus/VictoriaMetrics: remote_write
yaml global:
scrape_interval: 15s remote_write:
- url: http://vminsert:8480/insert/0/prometheus/api/v1/write
6) Cardinalidad: cómo no «explotar» la bóveda
6. 1 Reglas
Limite la calidad de la etiqueta (número de valores únicos). No incluya 'user _ id', 'request _ id', 'trace _ id'.
Normalice las etiquetas «multivalor» (categorías → códigos).
Utilice LowCardinality tipos (en CH), diccionarios/árboles de etiquetas (en TSDB).
6. 2 Controles y alertas
Métricas: 'series _ count', 'label _ values {label}', top-N de series 'caras'.
Directivas de error de escritura cuando se supera el límite de cardinalidad per tenant/job.
6. 3 Historias/histogramas
Para la alta cardinalidad, es mejor almacenar los agregados (histograma buckets) y pre-rollup; cuantili calcular en línea en las unidades.
7) Retiro, descarga y tiered-storage
7. 1 Políticas
Hot: 3-30 días de segundo/minuto de detalle.
Warm: 90-365 días 5m/1h unidades.
Cold: años de unidades diurnas, un archivo en el repositorio de objetos (S3/Glacier) con Parquet.
7. 2 Técnicas
Aggregates continuos (Timescale), representaciones materializadas (CH), retention + rollup tasks (Victoria/M3/Influx).
Almacenamiento tirado: «bloques calientes» localmente, «fríos» en un objeto con caché local.
8) Consultas e idiomas
8. 1 PromQL (ejemplo)
promql rate(http_requests_total{job="api",status=~"5.."}[5m])
Estamos buscando un ritmo de errores 5xx en la API.
8. 2 unidades SQL por ventana
sql
SELECT time_bucket('1h', ts) AS hour,
dc, avg(usage) AS avg, max(usage) AS pmax
FROM metrics_cpu
WHERE ts >= now() - interval '24 hours'
GROUP BY 1,2 ORDER BY 1;
8. 3 Anomalías (esbozo)
Z-score/ESD sobre estadísticas de ventanas, descomposición STL de estacionalidad; almacenar los resultados en una fila separada 'anomaly = 1/0'.
9) Integraciones y protocolos
OTLP (OpenTelemetry): métricas/tracks/logs, exportadores en agentes (otel-collector) → TSDB/clickhouse/objeto.
StatsD/Graphite: contadores/temporizadores simples; proxy en edge, a continuación, la conversión a un solo formato.
Kafka/NATS: búfer para ráfagas de ingest, replayer para backfill; Los consumidores escriben batches.
text kafka(topic=metrics) -> stream processor (normalize/tags) -> CH INSERT INTO metrics_cpu FORMAT RowBinary
10) Disponibilidad, HA y federación
Replica/HA pares TSDB o federación Prometheus (nivel región → global).
Remote read/write para almacenamiento a largo plazo y dashboards centralizados.
Shard-by-label/time: distribución uniforme de ingest, locality por 'dc/tenant'.
11) Observabilidad de la propia bóveda
11. 1 Métricas
Ingest: `samples/sec`, `append_latency`, `wal_fsync_ms`.
Хранение: `blocks_count`, `compaction_queue_len`, `chunk_compression_ratio`.
Запросы: `query_qps`, `scan_bytes`, `p95/p99_latency`, `alloc_bytes`.
Cardinalidad: 'series _ count', top-labels.
11. 2 SLO
«p99 latency para el rango de 1h ≤ 200 ms al QPS≤500».
«Ingest-drop ≤ 0. 01% con burst a X samples/sec".
«Compaction backlog < 10 min».
11. 3 Alertas
Crecimiento de 'series _ count'>% por hora.
Cola de acuerdo/flush> umbral.
Доля out-of-order > N%, dedup/late-drops.
12) Seguridad y multi-tenencia
Aislamiento por 'tenant' (etiqueta en llaves, tablas/bases individuales, cuotas).
Saneamiento de etiquetas (prohibición PII), control de dimensiones/valores.
Cifrado «en reposo» y en transporte, auditoría del acceso a métricas «sensibles».
13) Prácticas de explotación
Calentamiento y inicio frío: pin «caliente» bloques en caché, prefetch de las últimas horas de N.
Backfill: pipelines individuales de baja prioridad, no mezclar con en línea.
Versionar diagrama: migraciones con escritura paralela (dual-write) y posterior sweet.
Presupuesto de almacenamiento: control de 'cost _ per _ TB _ month' + el crecimiento de la cardinalidad.
14) Anti-patrones
Las etiquetas de alta cardinalidad (user_id, uuid) → una explosión de filas.
Las filas «eternas» sin retoque → un crecimiento incontrolable.
Registro sin batcheo/clasificación → mala compresión y tormenta IOPS.
Mezcla OLTP y escaneos largos en un solo grupo de discos.
La ausencia de políticas fuera de orden → duplicadas e infladas.
Histogramas con cientos de baquetas → costo × 10 sin uso.
15) Lista de verificación de implementación
- Definir las métricas, sus tipos y unidades; fije el contrato de nombres/etiquetas.
- Seleccione el motor (TSDB vs SQL/columnista) y el lenguaje de consulta (PromQL/SQL).
- Diseñe retén/rollo (hot/warm/cold) e ILM-tasky.
- Configure ingest: WAL/batch/ordenar, ventanas out-of-order.
- Habilite la compresión (delta-of-delta/XOR/RLE), las chancas óptimas.
- Controlar la cardinalidad: cuotas, alertas, políticas de rechazo.
- Configure EN/federación y remote-write/read.
- SLO Dashboards y métricas de almacenamiento (ingest/query/storage).
- Políticas de seguridad/aislamiento tenante y ausencia de PII en las etiquetas.
- «game day» regular: backfill, pérdida de nodo, estallido de ingest.
16) FAQ
P: ¿Qué elegir para monitorear la infraestructura: Prometheus o ClickHouse/Timescale?
R: Para monitoreo nativo y PromQL - Prometheus + almacenamiento de larga duración (Victoria/M3). Para scripts BI/stock y SQL - Timescale/ClickHouse.
P: ¿Cómo almacenar los cuantiles sin summary pesados?
R: Utilice un histograma con backets ordenados y calcule los cuantiles cuando se le solicite; o t-digest/CKMS en unidades.
P: ¿Cómo hacer con la orden de salida?
R: Introduzca la ventana de tolerancia (5-15 min) y la política de dedup determinista; para telemetría de móvil/edge - ventana más ancha.
P: ¿Cuándo necesitas un rollup?
R: Siempre con retoque> 30-90 días: las unidades reducen el tamaño × 10-100 y aceleran la analítica.
P: ¿Es posible mezclar registros y métricas?
R: Mantenga separado (los formatos/solicitudes son diferentes). Para la correlación, use Exemplar/TraceID y dashboards, pero no agregue todo a la misma tabla.
17) Resultados
Un repositorio eficiente de series de tiempo es un contrato de métricas + disciplina de etiquetas, legible ingest (WAL/Compactación), compresión y políticas de retenche/rollups. Agregue el control de cardinalidad, NA/federación y la observabilidad del propio stor - y obtendrá p95 predecibles, un costo razonable para los meses-años y resistencia a las ráfagas.