Teu progresso
0 / 83 módulos0%
Estágio 03 · 03-13
BloqueadoOLTP e OLAP são problemas diferentes. Postgres (02-09) é otimizado pra row-wise transactional: leitura/escrita de rows individuais, joins em PKs, alta concorrência. Analytics, observability e time-series têm shape oposto: bilhões de rows, queries sobre muitas linhas com poucas colunas, agregações (SUM, AVG, percentiles), filtros temporais, retention policies.
Tentar fazer dashboards Grafana ou agregações de billing em Postgres puro vai funcionar até dezenas de milhões, depois você sente. Columnar storage muda regras: ler só as colunas usadas, comprimir agressivamente (delta-of-delta, dictionary, RLE), agregação vetorizada. ClickHouse, DuckDB, BigQuery, Snowflake, Druid, Pinot, Apache Doris vivem nesse espaço. TimescaleDB atravessa: extensão Postgres com hypertables + columnar compressão pra dados antigos.
Este módulo é OLAP por dentro: row-vs-column store, vectorized execution, columnar compression, time-series specifics (downsampling, retention, continuous aggregates), OLAP cubes vs columnar, particionamento, e quando manter dados em OLTP vs separar.
Dimensões: row vs column, normalized vs star schema, indexed access vs full scan, point queries vs aggregations.
Tentar OLAP em OLTP DB falha em scale. Tentar OLTP em OLAP falha em latency e concorrência.
Row-store (Postgres): linhas armazenadas contiguamente. SELECT * WHERE id=... é fast (lê 1 page).
Column-store: cada coluna armazenada separadamente. Queries que tocam poucas colunas leem pouco disco. SELECT SUM(amount) WHERE date>... lê apenas amount e date, não toda row.
Trade-offs:
CPU moderna usa SIMD (AVX2, AVX-512). Vectorized engines processam batches de valores por operação. Em vez de "loop por row, pra cada row faça op", "loop por column chunk, aplique op em 1024 valores".
Cache friendly + SIMD friendly. Aceleração 10-100x em scans/aggregations. ClickHouse e DuckDB são exemplos canônicos.
Por colunas tipadas, técnicas baratas funcionam:
(value, count).Compressão típica em time-series: 5-10x. Dados de monitoring chegam a 20x.
ClickHouse (Yandex) é column-store distribuído.
Queries: SQL-like. Funções analíticas ricas (uniqHLL12 HLL approx, percentiles via t-digest, arrays nativas).
Forças: ingestão maciça, queries de agregação rapidíssimas. Fraquezas: updates/deletes caros (ALTER TABLE UPDATE é mutation pesada), point-lookups menos rápidos que OLTP.
Extensão Postgres. Hypertable: tabela "lógica" segmentada em chunks por tempo (ex: 1 chunk por dia). Cada chunk é tabela Postgres regular. Queries com filtro de tempo só tocam chunks relevantes (chunk pruning).
Recursos:
time_bucket, histogram, percentile_cont, gap-fill.Trade-off: integra bem com app Postgres existente; menos performance bruta que ClickHouse em grande scale (10B+ rows). Bom até dezenas de bilhões.
In-process columnar (como SQLite, mas analytical). Lê Parquet, CSV, Arrow. Sem servidor, embed em app.
Use cases: ETL local, analytics dentro de notebook, query Parquet em 04-03 sem subir cluster, Logística analytics offline.
OLAP clássico: star schema (fact table central + dimensions). Joins resolvidos em query.
OLAP moderno: wide denormalized table. Joins evitados; storage é barato; compressão lida com redundância. ClickHouse e BigQuery favorecem.
Snowflake (data warehouse) escolhe estilo conforme team.
Particionar tabelas por chave (tempo, tenant, hash). Postgres declarative partitioning, TimescaleDB chunks, ClickHouse PARTITION BY.
Benefícios: pruning (só toca partições relevantes), retention barata (drop partition), vacuum local.
Anti-pattern: partition key sem cardinalidade (1 partição) ou explosão (10k partições pequenas). Tipicamente partition por dia/semana.
Padrões:
Parquet em 04-03 + catalog (Iceberg, Delta Lake, Hudi) = lakehouse. Engines (Trino, Spark, DuckDB, ClickHouse external) consultam.
Vantagens: cheap storage, separation of compute/storage, multi-engine acesso.
Iceberg: tabelas ACID em data lake (snapshots, time travel). Adoção crescendo (Databricks, Snowflake adicionaram). Padrão emergente.
Em analytics, queries COUNT(DISTINCT x) em bilhões de rows são caros. Approximate:
ClickHouse tem uniqHLL12, quantilesTDigest. Postgres extension hll. Indispensável em high-cardinality.
Você raramente otimiza no nível baixo; conhecer ajuda diagnosticar quando query é lenta.
OLAP ingere via batch (preferido) ou stream:
clickhouse-client --query=..., COPY em Postgres/Timescale.Anti-pattern: 1 row por insert no ClickHouse. Mata performance.
Regra: se queries são "point lookup ou small range" → OLTP. Se são "scan e agregue muito" → OLAP. Misto: replica OLTP → OLAP via CDC.
Cenário: Logística v2 precisa analytics — daily revenue por lojista, courier utilization, time-to-delivery por região, cohort de clientes. Volume cresce de 1M pings/dia em v1 pra 100M pings/dia em v3. Decision tree por estágio:
Cenário 1: < 1M rows agregadas/dia, < 1k MAU dashboard
created_at, delivered_at) consomem ~0.01% do espaço de B-Tree e queries de agregação por range são 10-100x mais rápidas.pg_cron ou GitHub Actions cron HTTP).Cenário 2: 1M-100M rows/dia, < 100 dashboards concurrent
Cenário 3: 100M+ rows/dia, dashboards complexos com joins, multi-tenant SaaS
Cenário 4: ad-hoc analytics interno, dataset cabe em laptop ou single-node
analyst.duckdb carrega dump diário do Postgres + S3 logs, gera relatórios em 30s.pandas.read_sql_query(... duckdb ...)).Cenário 5: SaaS analítico managed, time prefere zero-ops
Cenário 6: real-time interactive dashboards (sub-segundo)
Cenário 7: lakehouse (data lake + warehouse semantics)
| Estágio Logística | Volume | Recomendação |
|---|---|---|
| v1 (CAPSTONE-plataforma) | < 100k pedidos | Postgres + BRIN + matviews |
| v2 (CAPSTONE-producao) | 1-10M pedidos/mês | Postgres OLTP + TimescaleDB pra tracking pings + DuckDB pra ad-hoc |
| v3 (CAPSTONE-sistemas) | 100M+ pings/dia | OLTP Postgres + ClickHouse via CDC pra analytics + Iceberg pra histórico |
| v4 (CAPSTONE-amplitude) | + ML pipeline + multi-region | Adiciona Druid/Pinot pra real-time supply chain dashboard |
Cruza com 02-09 §2.13.1 (CDC alimenta o pipeline OLTP → OLAP), 04-13 (streaming engines fazem transformação no caminho), 04-09 §2.14 (custo cloud quando volume cresce).
Real-time OLAP otimizado pra dashboards interativos com latency baixa. Apache Druid (Imply), Apache Pinot (LinkedIn). Dimensão de tempo central; segmentos em hot tier; queries via SQL/JSON.
Use case: dashboards user-facing com SLA sub-segundo em bilhões de rows. Mais complexo de operar que ClickHouse.
Logística analytics:
Esses queries não cabem bem em OLTP. Pipeline: Postgres OLTP → CDC → ClickHouse/Timescale → dashboards. Mantém OLTP performante; analytics tem palco próprio.
Decisão "ClickHouse" só vence se schema + queries são otimizados. Default de ORDER BY (timestamp) em fact table de 10B rows com WHERE em outra coluna = full scan, mesma latência que Postgres pesado. ClickHouse vence quando você usa primary key (sorting key) certo + skip indexes + projections + materialized views — patterns operacionais com código.
Foundation: sorting key (PRIMARY KEY) escolhido como filtro frequente.
-- Bad: sort por timestamp; queries comuns filtram por tenant
CREATE TABLE events (
event_at DateTime,
tenant_id UUID,
event_type LowCardinality(String),
payload String
) ENGINE = MergeTree
ORDER BY (event_at);
-- Good: tenant-first, queries WHERE tenant_id = X filtram blocos cedo
CREATE TABLE events (
event_at DateTime,
tenant_id UUID,
event_type LowCardinality(String),
payload String
) ENGINE = MergeTree
ORDER BY (tenant_id, event_type, event_at)
PARTITION BY toYYYYMM(event_at)
SETTINGS index_granularity = 8192;
index_granularity = 8192 (default): 1 mark por 8192 rows. Mais granular = mark file maior + lookup mais rápido. Tune só se medir.Skip indexes — segundo filtro embutido.
-- Skip index pra coluna NÃO no sorting key
ALTER TABLE events ADD INDEX idx_event_type event_type TYPE set(100) GRANULARITY 4;
ALTER TABLE events ADD INDEX idx_user_id user_id TYPE bloom_filter(0.01) GRANULARITY 1;
ALTER TABLE events ADD INDEX idx_payload_kw payload TYPE tokenbf_v1(8192, 3, 0) GRANULARITY 1;
Tipos:
minmax: armazena min/max por granule. Vence em range queries.set(N): armazena set de até N values por granule. Vence em equality em coluna low-cardinality.bloom_filter(p): probabilistic, p = false positive rate. Vence em equality em high-cardinality.tokenbf_v1(size, k, seed): bloom filter sobre tokens. Vence em LIKE/IN textual.ngrambf_v1(n, size, k, seed): bloom de n-grams. Vence em LIKE substring.Pegadinha: skip index NÃO acelera nada se query JÁ filtra granules via sorting key bem. Skip index brilha em queries que NÃO casam com sorting key.
Validate via:
EXPLAIN indexes = 1
SELECT count() FROM events WHERE user_id = 'abc';
-- Procure: "Skip" sections; "Granules: 12/8000" = pulou 99.85%.
Projections — segunda cópia ordenada do dado, transparente.
-- Tabela base ordenada por (tenant_id, event_at)
-- Mas tem dashboard que agrupa por user_id em jornal diário
ALTER TABLE events ADD PROJECTION p_user_daily (
SELECT
toDate(event_at) as day,
user_id,
count() as events_count,
countIf(event_type = 'click') as clicks
GROUP BY day, user_id
);
ALTER TABLE events MATERIALIZE PROJECTION p_user_daily;
SELECT day, user_id, sum(events_count) automaticamente roteia pra projection (transparente).Materialized views — pipeline de transformação on-insert.
-- MV agrega events em métricas hourly em tabela separada
CREATE TABLE metrics_hourly (
hour DateTime,
tenant_id UUID,
event_type LowCardinality(String),
count UInt64,
unique_users AggregateFunction(uniq, UUID)
) ENGINE = AggregatingMergeTree
ORDER BY (tenant_id, event_type, hour);
CREATE MATERIALIZED VIEW mv_metrics_hourly TO metrics_hourly AS
SELECT
toStartOfHour(event_at) as hour,
tenant_id,
event_type,
count() as count,
uniqState(user_id) as unique_users
FROM events
GROUP BY hour, tenant_id, event_type;
AggregateFunction(uniq, UUID) + uniqState/uniqMerge permite incremental aggregation correta (HyperLogLog state).SELECT hour, sum(count), uniqMerge(unique_users) FROM metrics_hourly WHERE tenant_id = X GROUP BY hour.INSERT INTO metrics_hourly SELECT toStartOfHour(event_at), ..., FROM events WHERE event_at < now().Logística caso real — fact_tracking_pings.
CREATE TABLE fact_tracking_pings (
ping_at DateTime64(3),
tenant_id UUID,
courier_id UUID,
order_id UUID,
lat Float64,
lng Float64,
speed_kmh Float32,
accuracy_m Float32
) ENGINE = MergeTree
ORDER BY (tenant_id, courier_id, ping_at)
PARTITION BY toYYYYMM(ping_at)
TTL ping_at + INTERVAL 90 DAY DELETE,
ping_at + INTERVAL 7 DAY TO VOLUME 'cold'
SETTINGS storage_policy = 'hot_cold';
-- Skip pra queries ad-hoc por order
ALTER TABLE fact_tracking_pings ADD INDEX idx_order order_id TYPE bloom_filter(0.01) GRANULARITY 4;
-- Projection pra trajetória de ordem (queried pelo customer support)
ALTER TABLE fact_tracking_pings ADD PROJECTION p_by_order (
SELECT * ORDER BY (order_id, ping_at)
);
p_by_order: replica ordenada por order_id; query de trajetória usa-a transparente.Query rewrite patterns que ganham ordens de magnitude.
PREWHERE — filtro avaliado ANTES de ler colunas não-filtradas:
-- ClickHouse já faz auto, mas pode hint
SELECT lat, lng FROM fact_tracking_pings
PREWHERE tenant_id = '...' AND ping_at > now() - INTERVAL 1 HOUR
WHERE speed_kmh > 80;
PREWHERE: filtro cheap (sorted columns), evita ler colunas pesadas.
SAMPLE — query estatística aproximada usando subset:
SELECT avg(speed_kmh) FROM fact_tracking_pings
SAMPLE 0.1
WHERE tenant_id = '...';
10x menos data, ~3% erro estatístico. Use em dashboards exploratórios. Requer SAMPLE BY definido em CREATE TABLE.
LIMIT N BY — top-N por grupo sem subquery:
-- Top 5 ordens com mais pings por courier
SELECT courier_id, order_id, count() as pings
FROM fact_tracking_pings
GROUP BY courier_id, order_id
ORDER BY courier_id, pings DESC
LIMIT 5 BY courier_id;
Anti-patterns observados.
SELECT * em wide table: lê todas colunas mesmo precisando 2. ClickHouse columnar: lista colunas explícitas.joinGet, dictionary, ou denormalize.EXPLAIN indexes=1 validation: cria índice "porque achei que ajudaria" mas nunca é usado. Sempre validate.ALTER TABLE ... UPDATE é assíncrono via mutation; lento + perigoso. Use ReplacingMergeTree + FINAL ou ReplaceableMergeTree.ORDER BY sem LIMIT: streams full result; pode quebrar memory limit. Set max_bytes_before_external_sort se preciso ordenar muito.Diagnostic toolbox.
system.query_log: query history com type, duration, memory, read_rows.system.parts: partições por tabela; busque parts grandes não-merged (active = 1, level baixo).system.mutations: mutations em-flight; órfãs travam space.clickhouse-benchmark pra repro testing.EXPLAIN PIPELINE pra ver vectorized execution.Cruza com 03-13 §2.13 (query optimization geral), 03-13 §2.15 (decision tree onde ClickHouse cabe), 04-13 §2.9.1 (dbt incremental + ClickHouse), 04-13 §2.11 (Iceberg como fonte external table).
Time-series é guarda-chuva pra workloads bem distintos. Tratar tudo com a mesma ferramenta = 10-100x cost overhead. Categorize antes de escolher.
Workload categorization.
Decision matrix 2026.
| Tool | Best fit | Cost @ 1M series, 1mo retention | Pain points |
|---|---|---|---|
| Prometheus (vanilla) | Low cardinality (<500k series), single instance | Free OSS; ~16GB RAM/$50/mo node | Federation pain; long retention costly |
| Thanos | Multi-cluster Prometheus + cheap S3 long-retention | OSS; +25% Prom cost | Query latency over old data |
| Cortex | Multi-tenant Prom-as-a-service (older) | OSS; ops complex | Mostly replaced by Mimir |
| Mimir (Grafana, 2.13+) | High-scale (1M-1B series), multi-tenant Prom | OSS or Grafana Cloud $0.16/MM samples | Operationally complex self-host |
| VictoriaMetrics (1.95+) | Drop-in Prom replacement, 5-10x storage efficiency | OSS; ~30% Prom cost @ same scale | Smaller community |
| InfluxDB 3.0 (Edge) | Lakehouse pattern, Iceberg + Apache Arrow | Cloud $0.25/GB; OSS Edge MIT | Migration from 1.x/2.x painful |
| TimescaleDB (2.16+) | Postgres extension, joins relational + time-series | OSS Postgres + extension | Series cardinality scales worse than columnar |
| ClickHouse (24.x) | Application analytics, high cardinality, ad-hoc | OSS; ~$0.05/GB/mo S3-backed; ~$200/mo modest | Operational overhead self-host |
| DataDog Metrics | Managed Prom-compat | $5/host/mo + ~$0.20/MM custom metrics | Vendor lock-in, $$$ at scale |
Cardinality é o #1 cost driver. Series = unique combination de metric + label values. http_requests_total{path="/orders/123"} com path concreto por orderId = explosão. Numbers reais 2026: 1M series = ~16GB RAM Prometheus; 100M series = ~16TB; 1B series = self-host impractical, Mimir/VM cluster obrigatório. Mitigação: template path (/orders/:id não /orders/123); aggregate por tenant_id NÃO user_id; histograms pré-agregados NÃO raw observations.
VictoriaMetrics deep — drop-in Prometheus.
# docker-compose.yml — single binary, 1M+ active series
services:
victoriametrics:
image: victoriametrics/victoria-metrics:v1.95.1
ports: ["8428:8428"]
volumes: ["./vm-data:/storage"]
command:
- "-storageDataPath=/storage"
- "-retentionPeriod=12" # months
- "-memory.allowedPercent=80"
# prometheus.yml — keep Prom como scraper, remote_write pra VM
remote_write:
- url: http://victoriametrics:8428/api/v1/write
queue_config: { max_samples_per_send: 10000, capacity: 100000 }
5-10x storage efficiency vs Prometheus mesma retention (compressão melhor). PromQL + MetricsQL extensions (rollup_increase, label_replace). Cluster mode (vmselect + vminsert + vmstorage) só acima de 50M series — single binary resolve antes.
Mimir deep — high-scale multi-tenant.
Architecture: distributor → ingester → querier → store-gateway. S3 backend pra blocks. Multi-tenant nativo via header HTTP X-Scope-OrgID: tenant-X. Compactor merges blocks e downsampleia: 5min-aggregated após 1 dia; 1h-aggregated após 1 semana. 5-15 services pra rodar; vale a pena APENAS em > 10M series ou multi-tenant managed prod.
# Push amostras pra tenant específico
curl -H "X-Scope-OrgID: logistica-prod" \
--data-binary @samples.pb \
http://mimir:9009/api/v1/push
ClickHouse pra application analytics (ver §2.18 query opt; aqui storage decision). Não é time-series puro mas dominant em high-cardinality. Partition by date + sorting key por series tag.
-- Application events em ClickHouse — courier pings em escala Logística
CREATE TABLE courier_events (
ts DateTime64(3),
tenant_id UUID,
courier_id UUID,
event_type LowCardinality(String),
payload String CODEC(ZSTD(3))
) ENGINE = MergeTree
PARTITION BY toYYYYMM(ts)
ORDER BY (tenant_id, courier_id, ts)
TTL ts + INTERVAL 365 DAY;
Stack 2026: ClickHouse + Vector pra ingest + Grafana pra dashboards. Cost ~$0.05/GB/mo S3-backed. Logística use case: ~10M events/dia × 365 dias = 3.6B rows; ~50GB compressed.
TimescaleDB — quando faz sentido (2.16+).
Vence: workloads onde JOIN com data relational (metric + dimension table) + time-series. Perde: > 100M series active (Postgres index pressure). Logística use case: courier earnings dashboard que joins delivery events + courier profile + tenant subscription = TimescaleDB elegante (hypertable + continuous aggregate + JOIN normal).
InfluxDB 3.0 (Edge / Cloud) 2026. Rewrite em Rust + Apache Arrow + Iceberg. Tagless cardinality (substitui o problema de "series cardinality" do 1.x/2.x). SQL primary; InfluxQL legacy supported. Cloud Serverless ~$0.25/GB query + storage S3 cheap. Migration 1.x/2.x → 3.0 NÃO é trivial; tagless model = re-think schema.
Long-retention strategy.
-- TimescaleDB: continuous aggregate + downsampling policy
CREATE MATERIALIZED VIEW pings_5min
WITH (timescaledb.continuous) AS
SELECT time_bucket('5 minutes', ts) AS bucket,
courier_id,
avg(speed_kmh) AS avg_speed,
count(*) AS pings
FROM location_pings
GROUP BY bucket, courier_id;
SELECT add_continuous_aggregate_policy('pings_5min',
start_offset => INTERVAL '1 day',
end_offset => INTERVAL '1 hour',
schedule_interval => INTERVAL '5 minutes');
SELECT add_retention_policy('location_pings', INTERVAL '30 days');
Logística applied stack.
Anti-patterns observados.
user_id, request_id) — RAM blowup imediato.Cruza com 03-07 (observability, full stack), 03-05 (AWS, S3 backend cost), 04-09 (scaling, observability cost @ scale), 02-09 (Postgres, TimescaleDB extension), 02-08 (frameworks, scrape endpoints).
ClickHouse e DuckDB venceram 2024-2026 nos seus nichos. ClickHouse dominou OLAP at scale (>10TB) com ClickHouse Cloud GA (Dec 2022) consolidando o managed serverless tier sobre object storage. DuckDB virou padrão de embedded analytics: 1.0 GA Q2 2024, 1.1 (Q3), 1.2 (Q4 — encryption + extensions). Polars 1.x (Q3 2024) substituiu Pandas em pipelines Python sérios. Lance/LanceDB (v0.18 Q4 2024) emergiu como columnar format ML-native. Decisão 2026 não é mais "ClickHouse vs Snowflake" mas "qual nicho do stack analytics" — managed warehouse, embedded ad-hoc, ML embeddings, batch ETL — cada um tem ferramenta dedicada.
ClickHouse 24.x deep (24.3 LTS Q1 2024, 24.10 Q4 2024 trouxe refreshable MVs e JSON object type GA):
ReplicatedMergeTree é default em prod self-hosted desde sempre. Em ClickHouse Cloud, SharedMergeTree (GA 2023) substitui Replicated: storage compartilhado em S3, replicas são compute stateless, scaling horizontal trivial. Self-hosted continua Replicated com ZooKeeper/Keeper.
-- Self-hosted: ReplicatedMergeTree clássico
CREATE TABLE orders_local ON CLUSTER prod_cluster (
order_id UUID,
user_id UInt64,
courier_id UInt64,
created_at DateTime64(3) CODEC(Delta, ZSTD(3)),
amount_cents UInt32 CODEC(T64, ZSTD(3)),
status LowCardinality(String),
payload JSON
)
ENGINE = ReplicatedMergeTree('/clickhouse/tables/{shard}/orders', '{replica}')
PARTITION BY toYYYYMM(created_at)
ORDER BY (user_id, created_at, order_id)
TTL created_at + INTERVAL 365 DAY DELETE
SETTINGS index_granularity = 8192,
allow_experimental_json_type = 1;
-- Cloud: SharedMergeTree (object storage backed, scale-to-zero)
CREATE TABLE orders_cloud (
order_id UUID,
user_id UInt64,
created_at DateTime64(3) CODEC(Delta, ZSTD),
amount_cents UInt32 CODEC(T64, ZSTD),
payload JSON
)
ENGINE = SharedMergeTree
PARTITION BY toYYYYMM(created_at)
ORDER BY (user_id, created_at);
Codecs por column type: Delta + ZSTD para sequential numerics (timestamps, IDs incrementais), T64 + ZSTD para integers com magnitudes variadas, LowCardinality(String) para enums/status (dictionary encoding automático), ZSTD(level) default geral, LZ4 quando latência > compressão.
Refreshable Materialized Views (24.4+) substituem cron externo:
CREATE MATERIALIZED VIEW orders_daily_agg
REFRESH EVERY 1 HOUR
ENGINE = ReplacingMergeTree
ORDER BY (day, courier_id)
AS SELECT
toDate(created_at) AS day,
courier_id,
count() AS orders_count,
sum(amount_cents) AS revenue_cents,
quantileTDigest(0.95)(delivery_seconds) AS p95_delivery
FROM orders
WHERE created_at >= now() - INTERVAL 7 DAY
GROUP BY day, courier_id;
MV incremental clássica continua via AggregatingMergeTree para pre-agg em insert, ReplacingMergeTree para latest-version dedup. Refreshable é batch-style, ideal para BI compatibility (Metabase/Superset esperam tabelas materializadas estáveis).
JSON Object type GA (24.8+): typing dinâmico sem schema upfront. Use SETTINGS schema_inference_make_columns_nullable = 1 se ingerir JSON com fields opcionais — sem isso, NULL silencioso vira default value.
ClickHouse Cloud (managed serverless):
DuckDB 1.x deep — embedded analytics, no server:
Process-embedded (single binary, in-process). Single-writer (multi-process write corrompe). Reads concurrent OK. Throughput: ~500M rows/sec single-thread Parquet scan em hardware moderno.
-- Install + load extensions (one-time per session)
INSTALL httpfs;
LOAD httpfs;
INSTALL iceberg;
LOAD iceberg;
-- S3 credentials via SECRET (DuckDB 1.x pattern)
CREATE SECRET s3_logistics (
TYPE S3,
KEY_ID 'AKIA...',
SECRET 'xxx',
REGION 'us-east-1'
);
-- Federated query: S3 Parquet JOIN local table
ATTACH 'logistics_local.duckdb' AS local;
SELECT
o.user_id,
sum(o.amount_cents) AS revenue,
u.tier
FROM read_parquet('s3://logistics-lake/orders/year=2026/*.parquet') o
JOIN local.users u ON u.id = o.user_id
WHERE o.created_at >= '2026-04-01'
GROUP BY o.user_id, u.tier
ORDER BY revenue DESC
LIMIT 100;
-- Iceberg: read latest snapshot
SELECT count(*)
FROM iceberg_scan('s3://lake/warehouse/orders', allow_moved_paths = true);
Extensions GA: httpfs (S3/HTTP/Azure direct read), iceberg (Iceberg table format), delta (Delta Lake), parquet (built-in), postgres_scanner (federated to Postgres). MotherDuck é DuckDB Cloud variant — hybrid local+cloud query, mesma sintaxe.
Polars + Lance ecosystem:
# Polars LazyFrame — Rust-backed, Arrow native, 5-10x mais rápido que Pandas
import polars as pl
df = (
pl.scan_parquet("s3://logistics-lake/orders/year=2026/*.parquet")
.filter(pl.col("created_at") >= pl.datetime(2026, 4, 1))
.group_by("courier_id")
.agg([
pl.col("amount_cents").sum().alias("revenue"),
pl.col("delivery_seconds").quantile(0.95).alias("p95"),
])
.sort("revenue", descending=True)
.collect(streaming=True) # streaming = larger-than-memory
)
Lance é columnar format optimizado para ML: pyarrow-compatible, vector indexes (IVF_PQ, HNSW) built-in, versioned (time-travel via snapshots). LanceDB é o KV+vector store sobre Lance. Diferencial vs Parquet: random access barato (importante pra ML training shuffles), vector search nativo sem index externo (Pinecone/Weaviate alternative).
Decision matrix 2026:
| Caso de uso | Ferramenta |
|---|---|
| Dashboards low-latency (p95 < 100ms), >10TB | ClickHouse self-hosted |
| Mesmo, sem ops | ClickHouse Cloud |
| Embedded SQL em CLI tool / IDE / app | DuckDB |
| Ad-hoc analytics em S3 lakehouse, sem warehouse cost | DuckDB + httpfs/iceberg |
| ETL Python, DataFrame ergonomics | Polars |
| ML embeddings + vector search dominante | Lance + LanceDB |
| Time-series + Postgres compatibility | TimescaleDB (§2.6) |
| Lakehouse curated, Spark/Flink heavy | Iceberg + Trino (04-13) |
Stack Logística aplicado: ClickHouse Cloud para orders + courier_locations (365d retention TTL) + dashboards executivos p95 < 100ms via refreshable MVs hourly. DuckDB embedded em CLI tool de SRE — logi-cli query "SELECT ... FROM read_parquet('s3://logs/...')" substitui Athena para investigação ad-hoc (zero infra, query starts em <1s vs Athena ~5-10s). Polars em ETL Python jobs (reconciliação financeira, batch fraud features). Lance como vector store de order embeddings (similarity search "pedidos similares" sem rodar Pinecone/pgvector dedicado).
10 anti-patterns:
ALTER TABLE ... UPDATE em hot path — mutations são async + rewrite parts inteiros, lock IO. Use ReplacingMergeTree com versioning column ou CollapsingMergeTree.ORDER BY diferente da query consumidora — sem index pruning, full scan da MV..collect() eager em dataset >RAM — use streaming=True ou continue lazy.schema_inference_make_columns_nullable = 1 — campos ausentes viram default silently, agregações erradas.ORDER BY começando com created_at (high cardinality timestamp) ao invés de coluna de filtro principal — sparse index inútil. Ordem certa: filter column → sort column → tiebreaker.ReplicatedMergeTree + Keeper.Cruza com: §2.5 (ClickHouse arquitetura base), §2.6 (TimescaleDB compare), §2.7 (DuckDB intro), §2.11 (lakehouse Iceberg/Delta), §2.18 (ClickHouse query optimization deep), §2.19 (time-series storage 2026), 02-09 §2.23 (TimescaleDB deep), 04-13 §2.20 (Iceberg + REST catalogs), 02-15 §2.20 (vector search — Lance native alternative), 03-05 (S3 backend para ClickHouse Cloud + DuckDB).
Você precisa, sem consultar:
Adicionar camada analítica à Logística com TimescaleDB + ClickHouse para diferentes domínios.
delivery_metrics(tenant_id, courier_id, started_at, finished_at, distance_km, fuel_cost, status).location_pings(courier_id, ts, lat, lng, speed).daily_metrics_per_tenant, hourly_courier_throughput.events_log ingerindo todos eventos (order_created, assigned, picked_up, ...).revenue_daily_mv agregando GMV/take rate por tenant por dia.bench.md.SELECT *; sempre projection.bench.md mostra ClickHouse > Timescale > Postgres pra agregação grande, com números.Threshold de Maestria
Acerte todas as 5 pra marcar o módulo como concluído. Sem pressa, sem timer. Tudo fica salvo no teu navegador.
Q1Por que column-store comprime mais que row-store?
Q2Qual é a função correta de uma sorting key (PRIMARY KEY) bem escolhida em ClickHouse?
Q3Quando faz sentido usar TimescaleDB ao invés de ClickHouse?
Q4Por que batch insert (10k-1M rows) é crítico em ClickHouse?
Q5Qual é a função de HyperLogLog (HLL) em queries OLAP?
Destrava
03-13 é prereq dos seguintes módulos: