Skip to content

Data Model

Tables, entities, channels

A table is a set of channels (columns) observed per entity (device) over time:

  • entity — string device/row key, implicit on every table
  • ts — Unix milliseconds, implicit on every table
  • every other column is a channel with a declared type and fill policy

The table key is (entity, ts): it gives ordering, fragment coalescing and scan locality in one stroke.

The frozen type system

Channels are f64 or string. Nothing else, ever. Booleans are 0/1, enums are strings (the engine dictionary-compresses them), binary blobs live in external object storage as reference strings. This is a load-bearing constraint: the core stays small because the answer to every new-type request is code, not engine.

Sparse by construction

Devices send fragments. Whatever channels a fragment carries get appended to those channels' streams; the others receive nothing. There is no "row waiting to be completed" — a row is something reads reconstruct, not something storage keeps. See Forward-Fill.

Mutability classes

Kind Declared with Behavior
IoT data (default) immutable, append-only; duplicates allowed; retention applies
Metadata WITH (MUTABLE) last-write-wins per entity; DELETE supported

Mutable tables never mutate in place: an update is a versioned append (same entity, newer ts), a delete is a tombstone. The merge worker resolves both — the newest version survives, tombstoned entities vanish. The API feels like a document store; the files behave like an LSM tree.

CREATE TABLE devices (name TEXT, fw DOUBLE) WITH (MUTABLE);
INSERT INTO devices (entity, ts, name) VALUES ('d1', 1, 'first');
INSERT INTO devices (entity, ts, name) VALUES ('d1', 2, 'renamed');  -- update
DELETE FROM devices WHERE entity = 'd1';                             -- tombstone