DuckDB
Embedded DuckDB AnalyticsSink — real column-store OLAP in-process, no external service to run.
@voltro/plugin-duckdb is the embedded AnalyticsSink — it runs DuckDB
in-process (via @duckdb/node-api) so you get real column-store +
vectorized OLAP performance without deploying an external service. Events land
in a voltro_events column-store table inside the DuckDB instance; the sink
implements all four contract methods against it. It's the sweet spot between
postgres-lite (no infra, but row-store) and
ClickHouse (fastest, but an external cluster). The
catch: it's single-process. For the shared AnalyticsSink API and the
useAnalytics() read methods, see Analytics & warehouse sinks.
Install
pnpm add @voltro/plugin-duckdb@duckdb/node-api is an optional dependency — installed alongside the plugin.
Wiring
// app.config.ts
import { duckdbAnalytics } from '@voltro/plugin-duckdb'
export default {
type: 'api' as const,
name: 'myApi',
store: 'postgres' as const,
analytics: duckdbAnalytics({ path: '.voltro/analytics.duckdb' }),
}At boot the plugin opens the DuckDB connection and creates the events table; the connection + instance are closed on graceful shutdown.
Options
duckdbAnalytics(options?) — all optional:
| Option | Type | Default | Notes |
|---|---|---|---|
path |
string |
in-memory | A file path (.voltro/analytics.duckdb) → durable across restarts, single-process. Omit or pass :memory: → events live in process memory, lost on restart (ephemeral dev / tests). |
mirrorTables |
ReadonlyArray<string> |
[] (events-only) |
Reactive tables to CDC-mirror into voltro_mirror_<table> inside DuckDB so analytical queries JOIN events against live user data. |
mirrorPrimaryKey |
string |
'id' |
Primary-key column on the mirrored source rows. |
analytics: duckdbAnalytics({
path: '.voltro/analytics.duckdb',
mirrorTables: ['users', 'teams'],
})Tenant isolation
Parity with postgres-lite: track stamps the event's tenantId into a tenant_id column, and aggregate/timeseries/topN filter by the query's tenantId (injected by useAnalytics() from the caller's subject), so a tenant-scoped read never spans tenants. A query with no tenantId is an unscoped system read. Swapping postgresAnalytics() → duckdbAnalytics() preserves the same tenant semantics.
Single-process constraint
DuckDB can't open the same file from two workers. For multi-instance
deployments, either pin all analytics traffic to one replica
(sticky-session style) or use @voltro/plugin-clickhouse
instead. There is no provider escape-hatch Tag — the contract methods are the
whole surface.
See also
- Analytics & warehouse sinks — the shared
AnalyticsSinkcontract, theuseAnalytics()read API,composeAnalytics, and the CDC-mirror details (incl. thevoltro_mirror_<table>JOIN pattern).