Skip to main content
A dataspace is a project-scoped table for device and application data. Devices, APIs, integrations, and edge sync all write into dataspaces. Dashboards, QueryScript, and the HTTP API read from them. It is described by independent axes, not one enum. You pick modality (required). You may also set layout, fields, storage class, and tiering. The platform derives the engine. You cannot change modality later. Create a new dataspace if the contract is wrong. People often say telemetry for the timeseries modality. The API value is timeseries.
document and vector are the content modalities: files, and the searchable text pulled out of them. Both carry a platform-owned set of columns you cannot change or remove. A document dataspace is hybrid on top of that, so you can attach fields of your own to a file through a metadata object; a vector dataspace is typed, because every search filter has to be a real indexed column. Pulling text out of a file is opt-in per file. See Document and Vector.

Guides

Telemetry

Append-only named fields. ClickHouse MergeTree. API timeseries.

Point

Append-only sparse metric bags. Uniform JSON map. Can cold-store from create.

State

Current document. Postgres, HTTP row CRUD, guaranteed events.

Mutable

Current table. ClickHouse ReplacingMergeTree, tombstones.

Document

One row per file. Presigned upload, content-addressed, list and download. Indexing opt-in.

Vector

One row per chunk plus its embedding. Hybrid semantic and keyword search.

Schema and layouts

jsonb / hybrid / typed / map, field types, promotion, proposals.

Storage

Engine, fast vs cheap, hot vs hot_cold, and the valid combination matrix.

Choose a modality

Ask these in order:
  1. Is it a file, or prose you want to search by meaning? A file → document. The text pulled out of it, searched semantically → vector. Pair them when you want files searchable; a document dataspace on its own is a perfectly good file store.
  2. Will this row ever be updated or deleted? No → telemetry or point.
  3. Is each sample a fixed (or soon-fixed) set of named fields? Yes → telemetry. A sparse bag of metric names that varies per device or firmware → point.
  4. Is there one current document per source, and do you need HTTP row CRUD, downsync, or a change event that cannot be lost? Yes → state.
  5. Is it a many-row current table keyed by a business primary key?mutable.
If you need compare-and-set (UPDATE … WHERE status = 'running'), leases, or a job state machine, that is ordinary platform data — not a dataspace.

Modality × layout × storage

Full rules: Schema and Storage. Compact matrix: cheap is Parquet-only (no hot table). hot_cold is a fast hot table plus an aged-out Parquet tier. They are not interchangeable. hot_cold + cheap is rejected. Document and vector take neither: a document’s bytes already live in object storage, and an embedding has no time axis to age along.

At a glance

Layouts (summary)

Layout is schema shape, not storage class. Details and field types: Schema and layouts. Telemetry climbs jsonbhybridtyped. Point stays map. State does not auto-promote. Mutable and document allow hybrid and typed only. Vector is typed and fixed. On both content modalities the platform owns the column set — the fields you send at create are replaced, not merged — but a hybrid document dataspace still absorbs fields of your own into overflow.
A typed dataspace orphans batches with undeclared columns. Declare fields at create if you start typed.

Identity and meta columns

Every row is associated with a source (usually a device). The platform adds meta columns; you do not declare them as user fields. source_id is which producer wrote the row, not your business key. A SKU, machine id, or OPC-UA node belongs in natural_key (or in a user field). Hashing a business key into source_id makes the row unaddressable by the thing it describes.

Writes

All row writes go through one write plane. Producers encode a batch (MQTT, edge sync, HTTP, workers). A durable per-project buffer is the no-loss commit point. The write plane then normalizes against the catalog and persists to ClickHouse or Postgres.
  • Known fields land in typed columns.
  • Surprise fields land in overflow when the layout has one.
  • Surprise fields on a typed layout go to the per-project orphan table (kept, not deleted) for review.
  • event_ts too far in the future (more than one day ahead) goes to the per-project dropped-rows table.
  • event_ts older than the hot window is dropped as late only when tiering is hot_cold. Hot-only dataspaces accept arbitrarily late history.
Retries of the same batch are safe: append tables use native block deduplication; state and mutable ignore a version that is not newer than what is already stored. Document files are the exception to the shape, not to the plane: you upload bytes straight to object storage through a presigned URL, and the row that describes them is written the same way every other keyed row is. Chunking and embedding a file is opt-in per registration (vectorize: true) and produces ordinary rows in the paired vector dataspace.

Reads

  • QueryScript and the query gateway use the dataspace name. Do not put physical table names in documents — those are rejected.
  • SQL (POST /projects/{project_id}/dataspaces/query) is read-only and runs against the dataspace read view. The catalog field read_view is informational; copy the name from the catalog if you use raw SQL.
  • HTTP row CRUD (…/dataspaces/{id}/rows) covers the keyed Postgres modalities: state, document, and vector. Telemetry and point have no current-row identity. Mutable does not offer GET-after-PUT.
  • Document reads (…/dataspaces/{id}/documents) list a document dataspace, read one file’s record, and mint a short-lived download link. Document only, and the surface to prefer over raw rows. See Document.
  • Semantic search (POST …/dataspaces/{id}/search) is vector only. See Vector.
  • Authorization is per dataspace (can_read_data / can_write_data). A caller without read grant does not see the table.
You can share a dataspace into another project for reads (POST …/dataspaces/{id}/shares).

Change events

Hard-real-time safety (for example an e-stop) must still trip on the device. The platform records and propagates the change; it is not a substitute for a local safety loop.

Edge SQLite sync

Attaching a sync policy provisions an ordinary dataspace — one per device table, shared across devices in the project. Rows are keyed by source_id = device id. point is a cloud create choice. Edge attach does not auto-map to it. Only state and mutable may carry an edge_sqlite sync target. Telemetry and point are upsync-only. Omega’s on-device table_strategies (rows / telemetry / ignore) is how data leaves the device. The table above is how the cloud replica is stored. strategy: rows in Omega becomes a mutable dataspace on attach, not state. Use a state / kv policy strategy when the table is one document per device. See Capture strategies and Edge data sync.

Create a dataspace

$API is the platform base URL (production: https://api.ilyama.golain.io/core/api/v1). Optional body fields: storage_class (fast / cheap), tiering (hot / hot_cold), layout, fields, natural_key, sync_targets. Which combinations are legal is the storage matrix. Vector requires embedding_model, embedding_dims, and one credential field. Document optionally takes chunk_dataspace_id, and needs it only if you intend to register files with vectorize: true. All of them are fixed at create. Interactive create, list, query, schema, shares, and row CRUD live under HTTP API → Dataspaces in the API reference.