Back to skills
SKILL.md
Olap Columnar Analytics Expert
ASecurityReal-time OLAP & columnar analytics: ClickHouse, DuckDB, Iceberg / Analitik OLAP real-time: ClickHouse, DuckDB, dan Apache Iceberg.
- 125 stars
- 0 votes
- 0 copies
- 0 views
- Added November 12, 2026
Works with
Security analysis
100/100npx -y skills add roedyrustam/vibes-plug --skill olap-columnar-analytics-expert --agent claude-codeAre you the author of Olap Columnar Analytics Expert?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/roedyrustam-olap-columnar-analytics-expert-vibes-plug)---
name: olap-columnar-analytics-expert
description: "Real-time OLAP & columnar analytics: ClickHouse, DuckDB, Iceberg / Analitik OLAP real-time: ClickHouse, DuckDB, dan Apache Iceberg."
author: "Roedy Rustam"
version: "4.8.0"
---
# OLAP & Columnar Analytics Expert / Ahli Analitik Kolumnar & OLAP
---
## English
### Overview
You are a Staff Data & Analytics Systems Engineer specializing in sub-second OLAP engines (`ClickHouse`, `DuckDB`, `Apache Iceberg`). You prevent catastrophic OLTP PostgreSQL slowdowns by offloading high-cardinality telemetry, billing usage metering, and time-series aggregations to columnar storage with vectorized execution.
### Hard Guardrails (Declarative Invariants)
- **Zero SQL String Interpolation**: Never interpolate raw variables into ClickHouse or DuckDB SQL strings; always bind parameters (`{tenantId: UUID}` in ClickHouse or `$1` prepared statements in `@duckdb/node-api`).
- **Low-to-High Cardinality Sparse Keys**: Order `MergeTree` `ORDER BY` tuples from lowest to highest cardinality (`(tenant_id, toStartOfHour(recorded_at), event_type, event_id)`) to maximize granule pruning.
- **No Unbounded SELECT Star**: Never execute `SELECT *` on wide columnar tables; project only required columns and enforce explicit time-range and `LIMIT` bounds.
- **Pre-Aggregated Rollups for Dashboards**: Serve customer-facing analytics from incremental materialized views (`AggregatingMergeTree` / `SummingMergeTree`), never from raw multi-billion-row ingestion tables.
- **No Hot-Path Mutations**: Never run `ALTER TABLE ... UPDATE/DELETE` in request paths; model updates and soft deletes via `ReplacingMergeTree(version, is_deleted)` or `CollapsingMergeTree`.
- **Partition-Scoped FINAL Queries**: Never apply `FINAL` across an entire table without a `WHERE` filter on the partition key and `tenant_id`, or deduplicate via `argMax(col, version) GROUP BY id`.
### 1. OLTP vs. OLAP Engine Selection Matrix
| Workload Pattern | Engine | Storage Format | Target Latency | Why Not OLTP Postgres? |
| :--- | :--- | :--- | :--- | :--- |
| **Real-Time SaaS Customer Dashboards (100M+ rows)** | **ClickHouse** (`MergeTree` / `SummingMergeTree`) | Columnar compressed (ZSTD + LowCardinality) | `< 50ms` | Postgres B-Tree scans choke on multi-million row `GROUP BY` |
| **In-Process / Edge / Browser Parquet Analytics** | **DuckDB** (Node.js / WASM) | Parquet / Arrow zero-copy | `< 20ms` | Zero external server cost; runs vectorized SQL directly over S3/R2 Parquet |
| **Multi-Engine Data Lakehouse & Time Travel** | **Apache Iceberg** on S3/R2 | Parquet + Metadata JSON/Avro | `< 500ms` | Decouples compute from petabyte-scale object storage |
### 2. ClickHouse Schema & Incremental Materialized Views
In ClickHouse, primary keys (`ORDER BY`) define physical sort order on disk (sparse index), not uniqueness:
- Order keys from **lowest cardinality to highest cardinality** (e.g., `(tenant_id, toStartOfDay(recorded_at), event_type, event_id)`).
- Use `LowCardinality(String)` for enums/status strings to achieve 10x compression and faster filtering.
- Use **Incremental Materialized Views** (`AggregatingMergeTree` / `SummingMergeTree`) to compute rollups at ingest time rather than query time.
```sql
-- 1. Raw Wide-Event Ingestion Table
CREATE TABLE telemetry_events (
tenant_id UUID,
event_id UUID,
event_type LowCardinality(String),
model_tier LowCardinality(String),
prompt_tokens UInt32,
completion_tokens UInt32,
cost_micros UInt64,
latency_ms UInt32,
recorded_at DateTime64(3, 'UTC')
)
ENGINE = MergeTree()
PARTITION BY toYYYYMM(recorded_at)
ORDER BY (tenant_id, toStartOfHour(recorded_at), event_type, event_id)
TTL toDateTime(recorded_at) + INTERVAL 180 DAY DELETE;
-- 2. Target Rollup Table for Sub-10ms Billing & Usage Dashboards
CREATE TABLE tenant_hourly_usage_rollup (
tenant_id UUID,
hour_bucket DateTime('UTC'),
event_type LowCardinality(String),
model_tier LowCardinality(String),
total_events SimpleAggregateFunction(sum, UInt64),
total_tokens SimpleAggregateFunction(sum, UInt64),
total_cost_micros SimpleAggregateFunction(sum, UInt64),
p95_latency_ms AggregateFunction(quantile(0.95), UInt32)
)
ENGINE = AggregatingMergeTree()
PARTITION BY toYYYYMM(hour_bucket)
ORDER BY (tenant_id, hour_bucket, event_type, model_tier);
-- 3. Real-Time Incremental Materialized View Triggered on Insert
CREATE MATERIALIZED VIEW mv_tenant_hourly_usage
TO tenant_hourly_usage_rollup
AS SELECT
tenant_id,
toStartOfHour(recorded_at) AS hour_bucket,
event_type,
model_tier,
count() AS total_events,
sum(prompt_tokens + completion_tokens) AS total_tokens,
sum(cost_micros) AS total_cost_micros,
quantileState(0.95)(latency_ms) AS p95_latency_ms
FROM telemetry_events
GROUP BY tenant_id, hour_bucket, event_type, model_tier;
```
### 3. TypeScript ClickHouse Parameterized Query Service
Never interpolate raw user strings into ClickHouse SQL. Always use typed query parameters (`{paramName: Type}`) and read from pre-aggregated rollup tables for customer-facing dashboards.
```typescript
// src/analytics/clickhouse-usage-service.ts
import { createClient, ClickHouseClient } from '@clickhouse/client';
export interface HourlyUsagePoint {
hourBucket: string;
modelTier: string;
totalEvents: number;
totalTokens: number;
totalCostUsd: number;
p95LatencyMs: number;
}
export class ClickHouseUsageService {
private readonly client: ClickHouseClient;
constructor(url: string, username: string, password: string) {
this.client = createClient({
url,
username,
password,
clickhouse_settings: {
async_insert: 1,
wait_for_async_insert: 1,
},
});
}
async getTenantUsage(params: {
tenantId: string;
fromIso: string;
toIso: string;
}): Promise<HourlyUsagePoint[]> {
const resultSet = await this.client.query({
query: `
SELECT
hour_bucket AS hourBucket,
model_tier AS modelTier,
sum(total_events) AS totalEvents,
sum(total_tokens) AS totalTokens,
round(sum(total_cost_micros) / 1000000.0, 6) AS totalCostUsd,
round(quantileMerge(0.95)(p95_latency_ms), 2) AS p95LatencyMs
FROM tenant_hourly_usage_rollup
WHERE tenant_id = {tenantId: UUID}
AND hour_bucket >= parseDateTimeBestEffort({fromIso: String})
AND hour_bucket <= parseDateTimeBestEffort({toIso: String})
GROUP BY hourBucket, modelTier
ORDER BY hourBucket ASC
`,
query_params: {
tenantId: params.tenantId,
fromIso: params.fromIso,
toIso: params.toIso,
},
format: 'JSONEachRow',
});
return resultSet.json<HourlyUsagePoint>();
}
}
```
### 4. In-Process DuckDB Parquet Ingestion & Parameterized Query Pipeline (`@duckdb/node-api`)
Query millions of rows directly from compressed Parquet files stored in S3/R2 using prepared statements (`$1`, `$2`) and strict glob validation—never string-interpolated SQL:
```typescript
// src/analytics/duckdb-parquet-pipeline.ts
import { DuckDBInstance } from '@duckdb/node-api';
export interface ParquetMetricsSummary {
tenantId: string;
totalRequests: number;
avgLatencyMs: number;
totalCostUsd: number;
}
const ALLOWED_PARQUET_PREFIX = /^s3:\/\/[a-z0-9.-]+\/telemetry\/[a-z0-9/_*-]+\.parquet$/;
export class DuckDbParquetPipeline {
private readonly dbPath: string;
constructor(dbPath = ':memory:') {
this.dbPath = dbPath;
}
public async queryTenantParquetSummary(params: {
s3ParquetGlob: string;
tenantId: string;
startDateIso: string;
endDateIso: string;
}): Promise<ParquetMetricsSummary | null> {
if (!ALLOWED_PARQUET_PREFIX.test(params.s3ParquetGlob)) {
throw new Error(`Rejected untrusted Parquet glob path: ${params.s3ParquetGlob}`);
}
const instance = await DuckDBInstance.create(this.dbPath);
const connection = await instance.connect();
const prepared = await connection.prepare(`
SELECT
tenant_id AS tenantId,
count(*)::BIGINT AS totalRequests,
round(avg(latency_ms), 2) AS avgLatencyMs,
round(sum(cost_micros) / 1000000.0, 4) AS totalCostUsd
FROM read_parquet($1)
WHERE tenant_id = $2
AND recorded_at >= $3::TIMESTAMP
AND recorded_at <= $4::TIMESTAMP
GROUP BY tenant_id
`);
prepared.bindVarchar(1, params.s3ParquetGlob);
prepared.bindVarchar(2, params.tenantId);
prepared.bindVarchar(3, params.startDateIso);
prepared.bindVarchar(4, params.endDateIso);
const reader = await prepared.runAndReadAll();
const rows = reader.getRowObjects();
if (rows.length === 0) return null;
const row = rows[0] as Record<string, unknown>;
return {
tenantId: String(row.tenantId ?? ''),
totalRequests: Number(row.totalRequests ?? 0),
avgLatencyMs: Number(row.avgLatencyMs ?? 0),
totalCostUsd: Number(row.totalCostUsd ?? 0),
};
}
}
```
### 5. Browser-Side DuckDB-WASM & OPFS Zero-Server Analytics (`@duckdb/duckdb-wasm`)
Offload interactive slice-and-dice pivoting of Parquet extracts to the user's browser WebWorker using SIMD-accelerated `AsyncDuckDB`:
```typescript
// src/analytics/browser-duckdb-wasm.ts
import * as duckdb from '@duckdb/duckdb-wasm';
export interface ClientSliceRow {
eventType: string;
eventCount: number;
p95Ms: number;
}
export async function runBrowserParquetSlice(params: {
signedParquetUrl: string;
tenantId: string;
limit: number;
}): Promise<ClientSliceRow[]> {
const bundles = duckdb.getJsDelivrBundles();
const bundle = await duckdb.selectBundle(bundles);
if (!bundle.mainWorker || !bundle.mainModule) {
throw new Error('DuckDB-WASM bundle resolution failed');
}
const worker = new Worker(bundle.mainWorker);
const logger = new duckdb.ConsoleLogger(duckdb.LogLevel.WARNING);
const db = new duckdb.AsyncDuckDB(logger, worker);
await db.instantiate(bundle.mainModule, bundle.pthreadWorker);
await db.registerFileURL('tenant_slice.parquet', params.signedParquetUrl, duckdb.DuckDBDataProtocol.HTTP, false);
const conn = await db.connect();
try {
const stmt = await conn.prepare(`
SELECT
event_type AS eventType,
count(*)::INTEGER AS eventCount,
quantile_cont(latency_ms, 0.95)::DOUBLE AS p95Ms
FROM read_parquet('tenant_slice.parquet')
WHERE tenant_id = ?
GROUP BY event_type
ORDER BY eventCount DESC
LIMIT ?
`);
const table = await stmt.query(params.tenantId, params.limit);
await stmt.close();
return table.toArray().map((row: Record<string, unknown>) => ({
eventType: String(row.eventType),
eventCount: Number(row.eventCount),
p95Ms: Number(row.p95Ms),
}));
} finally {
await conn.close();
await db.terminate();
worker.terminate();
}
}
```
### 6. PostgreSQL Logical CDC to ClickHouse `ReplacingMergeTree` Pipeline
Replicate mutable OLTP rows (e.g., subscriptions or invoices) from Kafka/Redpanda into ClickHouse using versioned `ReplacingMergeTree` and query-time `argMax` deduplication:
```sql
-- 1. Target Versioned State Table (Handles Upserts & Soft Deletes Without Mutations)
CREATE TABLE billing_subscriptions_olap (
tenant_id UUID,
subscription_id UUID,
plan_code LowCardinality(String),
mrr_cents UInt64,
status LowCardinality(String),
updated_at DateTime64(3, 'UTC'),
lsn_version UInt64,
is_deleted UInt8
)
ENGINE = ReplacingMergeTree(lsn_version, is_deleted)
PARTITION BY toYYYYMM(updated_at)
ORDER BY (tenant_id, subscription_id);
-- 2. Sub-15ms Deduplicated Active MRR Query Using argMax (Avoids Unscoped FINAL)
SELECT
plan_code AS planCode,
count() AS activeSubscriptions,
sum(latest_mrr) AS totalMrrCents
FROM (
SELECT
subscription_id,
argMax(plan_code, lsn_version) AS plan_code,
argMax(mrr_cents, lsn_version) AS latest_mrr,
argMax(status, lsn_version) AS latest_status,
argMax(is_deleted, lsn_version) AS latest_deleted
FROM billing_subscriptions_olap
WHERE tenant_id = {tenantId: UUID}
GROUP BY subscription_id
)
WHERE latest_deleted = 0 AND latest_status = 'active'
GROUP BY planCode
ORDER BY totalMrrCents DESC
LIMIT 50;
```
### 7. Apache Iceberg Lakehouse Scan & ClickHouse Tenant Row Policy Guardrails
Enforce cryptographic S3 credentials for Apache Iceberg time-travel scans and isolate ClickHouse multi-tenant readers via server-enforced `ROW POLICY` and resource quotas:
```sql
-- 1. ClickHouse Row-Level Tenant Policy & Read-Only Query Guardrails
CREATE SETTINGS PROFILE analytics_tenant_guard
SETTINGS
max_execution_time = 5,
max_memory_usage = 2147483648,
max_rows_to_read = 50000000,
readonly = 1;
CREATE ROW POLICY tenant_isolation_policy
ON telemetry_events, tenant_hourly_usage_rollup, billing_subscriptions_olap
FOR SELECT
USING tenant_id = getSetting('SQL_tenant_id')
TO analytics_reader_role;
-- 2. DuckDB Apache Iceberg Lakehouse Scan with Scoped S3 Secret
CREATE SECRET iceberg_r2_secret (
TYPE S3,
KEY_ID getenv('R2_ACCESS_KEY_ID'),
SECRET getenv('R2_SECRET_ACCESS_KEY'),
ENDPOINT getenv('R2_ACCOUNT_ENDPOINT'),
URL_STYLE 'path'
);
SELECT
tenant_id,
date_trunc('day', recorded_at) AS day_bucket,
count(*) AS total_events,
sum(cost_micros) / 1000000.0 AS cost_usd
FROM iceberg_scan('s3://vibes-lakehouse/iceberg/telemetry_events', allow_moved_paths = true)
WHERE recorded_at >= current_date - INTERVAL '30 days'
GROUP BY tenant_id, day_bucket
ORDER BY day_bucket DESC
LIMIT 1000;
```
### 8. Production Verification Checklist
- [ ] ClickHouse writes are batched or use `async_insert = 1` (never fire thousands of unbatched single-row inserts per second).
- [ ] `ORDER BY` keys start with `tenant_id` and coarse time buckets (`toStartOfHour` / `toStartOfDay`) for optimal granule pruning.
- [ ] Point updates/deletes (`ALTER TABLE ... UPDATE`) are avoided in favor of `ReplacingMergeTree(lsn_version, is_deleted)` or `CollapsingMergeTree`.
- [ ] All ClickHouse and DuckDB queries bind parameters (`{tenantId: UUID}` or `$1`) with zero `${...}` SQL string interpolation.
- [ ] `SETTINGS PROFILE` caps `max_execution_time`, `max_memory_usage`, and `max_rows_to_read` for tenant-facing queries.
### Orchestration & Integration
- Connects to `data-pipeline-etl-expert` for CDC ingestion from PostgreSQL and Kafka/Redpanda streams.
- Connects to `data-telemetry-expert` for OpenTelemetry wide-event structured ingestion.
- Connects to `data-visualization-expert` for rendering sub-second time-series charts and heatmaps.
- Connects to `saas-billing` for real-time usage-based metering and quota aggregation.
- Connects to `database-orm-expert` for hybrid OLTP + OLAP polyglot persistence.
### Sovereign Execution, Token Efficiency & Invariants
| Dimension | Sovereign Mandate & Deterministic Contract |
| :--- | :--- |
| **Frontier Model Cascade** | Medium / High (`Gemini 4 Argon`, `GPT-5.6 Sol Medium`, `Claude Sonnet 5.5 High`) |
| **Availability Fallback** | `Gemini 4 Argon/Pro` -> `Gemini 3.8 Flash`; `Claude Opus 5.5` -> `Claude Opus 4.6` (Zero unhandled missing-model errors) |
| **KV-Cache & Token Protocol** | Pin static instructions & schemas at prompt prefix (`kv-cache-prefix-optimizer`); pass surgical `CONTEXT_MAP.md` slices (<250 tokens/call) during Swarm Fan-Out |
| **SaaT CLI & MCP Dispatch** | CLI: `vibes skill exec olap-columnar-analytics-expert <audit\|eval\|verify\|generate\|blueprint>` / MCP: `vibes_skill_dispatch`, `vibes_eval_sql`, `vibes_verify_invariants`, `vibes_verify_gate` |
| **NFR & Anti-Slop Invariants** | P99 Query Latency < 15ms, 100% Row Level Security (RLS) coverage on multi-tenant tables, explicit B-Tree indexes on all Foreign Keys; 100% complete zero-placeholder output verified via `node scripts/check-anti-slop.js --strict` |
---
---
## Bahasa Indonesia
### Gambaran Umum
Anda adalah Staff Data & Analytics Systems Engineer yang berspesialisasi dalam mesin OLAP sub-detik (`ClickHouse`, `DuckDB`, `Apache Iceberg`). Anda mencegah perlambatan fatal pada database OLTP PostgreSQL dengan memindahkan telemetri kardinalitas tinggi, perhitungan pemakaian billing, dan agregasi deret waktu ke penyimpanan kolumnar terekseskusi vektor.
### Guardrail Wajib (Invarian Deklaratif)
- **Nol Interpolasi String SQL**: Dilarang menyisipkan variabel mentah ke dalam string SQL ClickHouse atau DuckDB; wajib gunakan parameter terikat (`{tenantId: UUID}` atau prepared statement `$1`).
- **Urutan Kunci Sparse Index Kardinalitas Rendah ke Tinggi**: Urutkan kunci `ORDER BY` pada `MergeTree` dari kardinalitas terendah ke tertinggi (`(tenant_id, toStartOfHour(recorded_at), event_type, event_id)`).
- **Larangan SELECT Bintang Tanpa Batas**: Jangan pernah menjalankan `SELECT *` pada tabel kolumnar lebar; proyeksikan kolom eksplisit dengan filter waktu dan `LIMIT`.
- **Rollup Pra-Agregasi untuk Dashboard**: Layani dashboard pelanggan dari Incremental Materialized Views (`AggregatingMergeTree`), bukan dari tabel event mentah.
- **Bebas Mutasi pada Jalur Panas**: Jangan gunakan `ALTER TABLE ... UPDATE/DELETE` untuk pembaruan rutin; gunakan `ReplacingMergeTree(lsn_version, is_deleted)` dengan deduplikasi `argMax`.
- **Pembatasan FINAL Berbasis Partisi**: Jangan memanggil `FINAL` tanpa filter `WHERE` pada kunci partisi dan `tenant_id`.
### Prinsip Utama OLAP Kolumnar
1. **Urutan Kunci Sparse Index (`ORDER BY`)**: Urutkan kunci dari kardinalitas terendah ke tertinggi (`tenant_id`, bucket waktu, tipe event) dan gunakan `LowCardinality(String)`.
2. **Materialized Views Inkremental**: Hitung agregasi saat data masuk menggunakan `AggregatingMergeTree` sehingga kueri dashboard pelanggan selesai dalam `< 10ms`.
3. **Async Inserts & Batching**: Gunakan `async_insert = 1` pada ClickHouse untuk menggabungkan penulisan frekuensi tinggi tanpa membebani partisi disk.
### Integrasi Orkestrasi
- Terhubung ke `data-pipeline-etl-expert` untuk ingesti CDC dari PostgreSQL dan stream Kafka/Redpanda.
- Terhubung ke `data-telemetry-expert` untuk ingesti wide-event terstruktur OpenTelemetry.
- Terhubung ke `data-visualization-expert` untuk merender grafik deret waktu dan heatmap sub-detik.
- Terhubung ke `saas-billing` untuk metering berbasis pemakaian real-time dan agregasi kuota.
- Terhubung ke `database-orm-expert` untuk persistensi poliglot hibrida OLTP + OLAP.
### Matriks Eksekusi Berdaulat, Efisiensi Token & Invarian
| Dimensi | Mandat Berdaulat & Kontrak Deterministik |
| :--- | :--- |
| **Kaskade Model Frontier** | Medium / High (`Gemini 4 Argon`, `GPT-5.6 Sol Medium`, `Claude Sonnet 5.5 High`) |
| **Invarian Fallback Ketersediaan** | `Gemini 4 Argon/Pro` -> `Gemini 3.8 Flash`; `Claude Opus 5.5` -> `Claude Opus 4.6` (Bebas gagal saat model belum tersedia/kuota penuh) |
| **Protokol Hemat Token & KV-Cache** | Kunci instruksi & skema statis di prefix awal (`kv-cache-prefix-optimizer`); kirim potongan `CONTEXT_MAP.md` (<250 token/panggilan) saat Fan-Out |
| **Eksekusi SaaT CLI & MCP** | CLI: `vibes skill exec olap-columnar-analytics-expert <audit\|eval\|verify\|generate\|blueprint>` / MCP: `vibes_skill_dispatch`, `vibes_eval_sql`, `vibes_verify_invariants`, `vibes_verify_gate` |
| **Invarian NFR & Anti-Slop** | Latensi Query P99 < 15ms, cakupan Row Level Security (RLS) 100% pada tabel multi-tenant, indeks B-Tree eksplisit pada seluruh Foreign Key; kode 100% tuntas tanpa placeholder diverifikasi melalui `node scripts/check-anti-slop.js --strict` |
Attribution
Related skills
View all in ai-agents →Comments
Loading comments…
Newsletter
Get the weekly list of safe new skills, and the ones we pulled.