o11ykit

browser-native observability databases

OTLP in.
Pixels out.

Every observability SPA today is a dumb terminal—every pan, zoom, and filter round-trips to a backend. o11ykit puts a real columnar database in the browser: sub-4 bytes per point, instant cross-signal correlation, zero GC pressure. No Prometheus. No Elasticsearch. No backend.

01
OTLP
Protobuf or JSON ingest via OtlpKit
02
Encoding
XOR-delta + ALP columnar compression
03
o11y*db
Signal-specific columnar store
04
Pixels
uPlot, ECharts, Chart.js output
<4 B per data point
10-15x less memory than JS objects
Local cross-signal correlation
<5 KB core bundle size

primitives

A toolkit, not a platform.

Compose your own observability stack from focused, single-purpose packages. Each does one thing. Each earns its bytes.

tsdb metrics

ALP + XOR-delta + dictionary encoding. Sub-4 bytes per sample. Columnar compression purpose-built for time-series.

demo
tracesdb traces

Dictionary encoding collapses 90%+ of span strings. Typed-array parent-child indices for SIMD-friendly critical-path computation.

demo
logsdb logs

Drain3-style template extraction collapses string columns 5-10x. Turns unique messages into template_id + params.

source
otlpkit ingest

OTLP protobuf and JSON decoder. Zero-copy where possible. Streams directly into o11y*db columnar format.

docs
benchkit verify

Statistical benchmarking harness. If a stat appears in copy, the demo proves it live. Regression detection built in.

benchmarks

correlation

Three signals. One window. Zero network.

All three stores co-resident in memory. Brush a metric spike, get matching traces and logs in the same frame. No server round-trips. No credentials. No loading spinners.

// 1. Brush a time range in tsdb
const window = tsdb.query({
  start: brushStart,
  end:   brushEnd,
  metric: "http.duration"
});

// 2. Correlate: pass to traces + logs
const spans  = tracesdb.query({
  start: window.start,
  end:   window.end
});
const logs   = logsdb.query({
  traceIds: spans.traceIds
});

// 3. Render: zero network calls
chart.update(window.points);
waterfall.update(spans);
logTable.update(logs);

How it works

step 1

Brush a spike in your metric chart. tsdb returns the matching time window as typed arrays.


step 2

Pass the window to tracesdb and logsdb in local memory. Dictionary-encoded lookups, zero serialization.


step 3

Render all three panels in the same animation frame. Total latency: 0 ms network, sub-ms compute.

boundaries

Six things this isn't.

Clear boundaries prevent scope creep. o11ykit sits after your backend, inside the UI. Limits are a feature.

Not Prometheus

We don't scrape, store long-term, or serve remote queries. We consume what Prometheus produces.

Not Elasticsearch

We don't do distributed full-text search. We do template-aware log compression in a single tab.

Not Grafana

We're a library, not a dashboard. We output typed arrays—what you render is your business.

Not OTel SDK

We don't instrument your code. We store and query what your instrumentation produces.

Not DuckDB

DuckDB-WASM is 18 MB with no metric semantics. We're <5 KB with deep signal awareness.

Not a backend

Single-user, single-tab, in-process. Complements your server-side store, never replaces it.

manifesto

The browser is no longer a thin client. It has 8 GB, six cores, and SIMD. We should stop treating it like a 1996 terminal and put a real database in there.

open source

Star the repo. Then break it.

GitHub TSDB Demo TracesDB Demo Benchmarks