version, deletes as tombstones. It is the ClickHouse sibling of state — same idea, different scale and guarantees.
Use it for edge relational mirrors (work orders, inventory, child rows) and cloud catalogs you upsert and scan at analytics volume.
The API modality is mutable.
→ All modalities
When to use it
Use mutable when- Each source holds many current rows, keyed by a business primary key.
- You upsert and delete as a bulk replica, not as one document per device.
- Columnar scans and filters matter more than GET-after-PUT.
- Last-write-wins on a monotonic
versionis enough. A missed change event on crash is acceptable.
UPDATE … WHERE status = 'running'). ReplacingMergeTree has no row locks. Job/claim state belongs in ordinary platform tables.
Backing store
Mutable lives in ClickHouse asReplacingMergeTree(version, is_deleted) (replicated variant in clustered deployments).
How reads collapse versions
A rawSELECT on the hot table is not last-value-wins while extra versions still sit unmerged. The read view collapses for you:
- Group by
(source_id, natural_key) - Take
argMax(column, version)for each projected field - Hide keys whose latest version is a tombstone (
HAVING argMax(is_deleted, version) = 0)
read_view). Do not scan the physical table and assume one row per key.
Identity and natural_key
Mutable requires a natural key at create. A keyed table with no identity is rejected.
source_id— which producer (device id on the edge path).natural_key— the row’s business identity. You declare which source fields compose it (natural_key: ["sku"]or a composite, in order). The table stores a singlenatural_keycolumn plussource_id; the catalog remembers the field list.
natural_key and set source_id to the device. Cloud producers should send a stable encoding of the same fields on every write and retry.
version must be stable across redeliveries — the producer’s logical version (commit_seq, source_commit_seq), never wall clock. A clock default would turn last-write-wins into last-arrival-wins and emit duplicate events.
You can rekey only if the dataspace currently has no key. Mutable always has a key, so identity is fixed at create.
Meta columns
In addition to the ClickHouse meta set (source_type, source_id, event_ts, ingest_ts):
event_ts is present but not the sort or partition key. Mutable rows are not a time series.
Schema and layouts
Default
hybrid so a mirrored SQLite table can ingest before every column is approved. Sync-tagged dataspaces force review before promotion.
Events and edge
Mutable does emit row-changed events (full row) so cloud-authoritative tables can downsync. The ClickHouse insert and the Postgres outbox row are not one transaction. A crash between them can miss an event or require the poll sweep. Treat delivery as at-least-once and best-effort relative to state. Consumers must be idempotent on identity +version.
Edge:
- Policy strategies
rows,mutable,relational,row_batchprovision a mutable dataspace. - Upsync always. Downsync only if the table’s authority is
cloud. - Device-authoritative mirrors still upsync; the cloud does not push those rows down.
- Tombstones replicate: a source
DELETEis anis_deletedinsert, so a stale replay stays a read-time no-op.
Mutable vs state
If you are unsure and the table has a primary key and
UPDATE/DELETE in SQLite, it is almost always mutable. If it is “the device’s current JSON blob”, it is state.
Create
$API is the platform base URL.
Related
- Dataspaces overview
- Schema and layouts
- Storage
- State — ACID current documents
- Edge capture strategies
- API introduction