Qirava Cloud docs
Control plane data model (_cp_* catalogs)
Qirava Cloud is itself a Qirava DMS — "a DMS that manages other DMSes." Everything it knows about the fleet (customers, machines, plans, money, and an audit trail) lives in seven schemaless catalog tables whose names all start with _cp_. These tables live in the control DMS's OWN store, completely separate from any tenant's _sys_* catalogs: a tenant never sees them, and the control plane never reaches into a tenant's data — it only allocates resources and tracks lifecycle here. Reads go through the planner; writes go through execute; the same L1→L3 funnel the open-source core enforces. This page documents each catalog, its exact row shape, and how the data is created and read.
What the `_cp_*` catalogs are#
There are exactly seven control-plane catalogs. They are created on first boot and are entirely distinct from a tenant's _sys_* system tables. The control plane is the only thing that ever touches them; tenants are isolated and never see them. Because they hold lifecycle and resource bookkeeping (not customer data), the control plane being a separate authority domain from any tenant is preserved at the storage level.
| Constant | Table name | Purpose |
|---|---|---|
| CP_TENANTS | _cp_tenants | One row per managed DMS instance (the customer's box) |
| CP_NODES | _cp_nodes | One row per bare-metal node the placer bin-packs against |
| CP_PLANS | _cp_plans | The per-unit pricing plan (the slider IS the plan) |
| CP_SUBSCRIPTIONS | _cp_subscriptions | Links a tenant to a plan |
| CP_USAGE | _cp_usage | Metered usage samples (the meter's stream) |
| CP_INVOICES | _cp_invoices | Generated invoices |
| CP_AUDIT | _cp_audit | Append-only trail of every control-plane action |
Why schemaless#
Every _cp_* table is created SCHEMALESS. The control plane owns the row shape in code, so it can evolve a row's fields (add a column, change a default) without a DDL migration. The shapes below are the fields the code reads and writes today; a schemaless row may carry exactly these keys and nothing forces a column to be present (missing string fields read as empty, missing integers read as 0).
// From cp.rs — the create is idempotent: a table is only created when absent.
for t in CP_TABLES {
if !existing.contains(*t) {
let _ = db.execute(&format!("CREATE TABLE {t} SCHEMALESS"));
}
}Row shapes (field by field)#
Each catalog's exact row shape, transcribed from the source. (standalone|cluster) and (dynamic|fixed) denote the only accepted values for that field.
- _cp_tenants
- { id, owner, plan, mode(standalone|cluster), storage(dynamic|fixed), threads, mem_gb, storage_gb, replicas, status, node_id, created_us }.
statusis one of active, pending_capacity, suspended, terminated.node_idis the placed node (empty when no headroom).planis always "metered".replicasis 1 for standalone, ≥2 for cluster. - _cp_nodes
- { id, provider, cap_threads, cap_mem_gb, cap_storage_gb, alloc_threads, alloc_mem_gb, alloc_storage_gb, status }.
cap_*is the node's total capacity;alloc_*is how much the placer has reserved;statusis online/offline. - _cp_plans
- { id, label, price_thread_hr, price_mem_gb_hr, price_storage_gb_mo, price_bw_gb }. Prices are stored as strings (one source of truth for billing).
- _cp_subscriptions
- { id, tenant_id, plan_id, status, created_us }.
idissub-<tenant_id>;statusis active or cancelled. - _cp_usage
- { id, tenant_id, ts_us, thread_sec, mem_gb_hours, storage_gb, bw_gb }. A metered sample;
idisuse-<now_us>. - _cp_invoices
- { id, tenant_id, period, total, status, created_us }.
idisinv-<now_us>;totalis a string dollar amount;statusis due (or paid). - _cp_audit
- { id, ts_us, operator, action, tenant_id, node_id, result }.
idisaud-<now_us>. One row per control-plane mutation.
The planner reads, `execute` writes#
The control plane reaches its catalogs exactly the way any client reaches a DMS. Reads are planner queries (read_records / read_records_bound); writes are execute statements (the L1→L3 write funnel). The console is a CLIENT — it never bypasses this. The helpers in cp.rs are thin wrappers around those two paths.
all_rows(db, table)→SELECT * FROM <table>(planner read; surfaces every schemaless field).row_by_id(db, table, id)→SELECT * FROM <table> WHERE id = :id LIMIT 1(bound query).row_absent(db, table, id)→SELECT id FROM <table> WHERE id = :id LIMIT 1returns empty (used for idempotent seeds/inserts).- Every catalog mutation is a
db.execute("INSERT …")ordb.execute("UPDATE …").
Default seed (idempotent)#
On boot, seed_defaults inserts the single per-unit plan and two simulated bare-metal nodes, but only when each row's id is absent — so it is safe to run on every restart. The two nodes are the SIMULATED fleet: a real deployment registers actual OVH/Hetzner nodes; here they model the headroom the placer reasons about.
Request
INSERT INTO _cp_plans { id: "metered", label: "Pay-as-you-go (per unit)", price_thread_hr: "0.012", price_mem_gb_hr: "0.006", price_storage_gb_mo: "0.10", price_bw_gb: "0.02" }
INSERT INTO _cp_nodes { id: "node-eu-1", provider: "hetzner", cap_threads: 64, cap_mem_gb: 256, cap_storage_gb: 8000, alloc_threads: 0, alloc_mem_gb: 0, alloc_storage_gb: 0, status: "online" }
INSERT INTO _cp_nodes { id: "node-eu-2", provider: "ovh", cap_threads: 32, cap_mem_gb: 128, cap_storage_gb: 4000, alloc_threads: 0, alloc_mem_gb: 0, alloc_storage_gb: 0, status: "online" }Response
-- after seeding, reading back the plan:
SELECT * FROM _cp_plans
-- yields exactly one row:
{ id: "metered", label: "Pay-as-you-go (per unit)",
price_thread_hr: "0.012", price_mem_gb_hr: "0.006",
price_storage_gb_mo: "0.10", price_bw_gb: "0.02" }Output
SELECT * FROM _cp_nodes -- two rows: node-eu-1 (hetzner), node-eu-2 (ovh), both online, alloc_* all 0A real provisioned tenant row#
This is the row a cloud.provision call writes, transcribed from the provision flow. After provisioning a standalone tenant acme with 2 threads / 4 GB / 20 GB, the catalog holds:
Request
-- provision (executed internally by cloud.provision):
INSERT INTO _cp_tenants { id: "acme", owner: "acme-corp", plan: "metered", mode: "standalone", storage: "dynamic", threads: 2, mem_gb: 4, storage_gb: 20, replicas: 1, status: "active", node_id: "node-eu-1", created_us: <now> }
INSERT INTO _cp_subscriptions { id: "sub-acme", tenant_id: "acme", plan_id: "metered", status: "active", created_us: <now> }
-- read it back:
SELECT * FROM _cp_tenants WHERE id = :id LIMIT 1 (:id = "acme")Response
{ id: "acme", owner: "acme-corp", plan: "metered",
mode: "standalone", storage: "dynamic",
threads: 2, mem_gb: 4, storage_gb: 20,
replicas: 1, status: "active", node_id: "node-eu-1",
created_us: <microseconds> }Output
-- the placed node's allocation is bumped to match the cap:
SELECT alloc_threads FROM _cp_nodes WHERE id = "node-eu-1" -- 2