Skip to content

Qirava Cloud docs

Vertical scaling: live capacity growth

Vertical scaling grows or shrinks a single tenant's per-instance capacity — its CPU threads, memory, and storage — without recreating the tenant or taking it offline. In Qirava Cloud this is the cloud.scale_vertical orchestration function: it writes the new cap onto the tenant's _cp_tenants row, adjusts the host node's allocation counters by the delta, and returns the intended live effect. The control plane persists the new state durably (real); the infra step of signalling the running tenant DMS to re-read its cap is SIMULATED today and surfaced to the operator as a PLANNED note, never faked as done.

What vertical scaling changes#

A tenant has a per-instance resource cap: how many CPU threads it may use, how much memory, and how much storage. Vertical scaling sets a NEW absolute cap (not a delta) for that single instance. It is the right tool when one tenant simply needs a bigger or smaller box — more concurrency, more working memory, or more disk — and you are not changing how many copies of the tenant run. (Changing the number of copies is horizontal scaling, and only applies in cluster mode.)

threads
CPU thread budget for the instance. The executor (qexec) re-reads this to expand or shrink its concurrency budget live.
mem_gb
Memory cap in whole gigabytes.
storage_gb
Storage cap in whole gigabytes.
per-instance
The cap is per running copy. For a cluster tenant the node allocation is charged once per replica (cap x replicas), but the cap value itself is the size of ONE instance.

Who may call it#

cloud.scale_vertical requires cloud role admin or higher. Cloud governance is its own authority domain (cloud custodian > admin > user), entirely separate from any tenant's own governance. The check is deny-by-default: a missing or unknown role ranks 0 and is denied with AccessDenied. The role is read from ctx.role, which the Cloud Console session injects; there is no top-level role field a caller could spoof.

RoleRankMay scale vertically?
custodian3Yes
admin2Yes
user1No (AccessDenied)
(none / unknown)0No (AccessDenied)

Inputs and clamps#

The function takes the tenant id plus the new absolute cap. Any of the three cap fields may be omitted; an omitted field keeps the tenant's current value as its default. Each numeric field is parsed (integers, or numeric strings from a form POST) and CLAMPED to a safe range so junk input can never become a wild allocation.

FieldRequiredDefault if omittedClamp (min..max)
idyesmust name an existing, non-terminated tenant
threadsnocurrent threads1 .. 256
mem_gbnocurrent mem_gb1 .. 1024
storage_gbnocurrent storage_gb1 .. 100000

Validation rejects two cases before any write: the tenant id must exist (InvalidInput: no tenant '<id>'), and the tenant must not be terminated (InvalidInput: tenant is terminated). A terminated tenant has had its allocation released and cannot be resized.

What it does, step by step#

  1. Decode the request and re-check ctx.role >= admin (deny-by-default).
  2. Load the tenant's _cp_tenants row; reject if absent or terminated.
  3. Read the OLD cap (threads, mem_gb, storage_gb) and the replica count (at least 1).
  4. Compute the NEW cap from the input fields, applying defaults + clamps.
  5. UPDATE the tenant row to the new cap via the write funnel.
  6. Apply the node allocation delta: (new - old) x replicas for each of threads, mem, storage, against the tenant's placed node. Node alloc counters are clamped at 0.
  7. Write a _cp_audit row (operator, action=scale_vertical, tenant, node, result=ok).
  8. Return the new tenant state plus the PLANNED note describing the live re-read.
console form (admin)
   POST /cloud/tenants  (id, threads, mem_gb, storage_gb, _csrf)
        │  session -> ctx.{user,role};  CSRF verify
        ▼
 cloud.scale_vertical  (re-checks ctx.role >= admin)
        │  REAL:  UPDATE _cp_tenants SET cap...
        │  REAL:  node alloc_* += (new - old) x replicas   (clamped >= 0)
        │  REAL:  INSERT _cp_audit { action: scale_vertical }
        ▼
 SIMULATED (PLANNED): signal the running DMS to re-read its cap LIVE
                       (expand qexec budget / storage; no rebuild, no downtime)
Vertical scale flow (cap re-read is simulated)

Worked example#

A standalone tenant acme was provisioned at 2 threads / 4 GB / 20 GB and placed on a node. We grow it to 8 threads / 16 GB / 50 GB. The call below is exactly how the Cloud Console drives it: a cloud.* function invoked through the runtime with the operator's role in ctx.

Request

// request record passed to cloud.scale_vertical
{
  ctx: { user: "op", role: "admin" },
  id: "acme",
  threads: 8,
  mem_gb: 16,
  storage_gb: 50
}

Response

// FunctionResponse: code = Ok, data = encoded record
{
  tenant_id: "acme",
  simulated: "PLANNED: signal the running DMS to re-read its cap LIVE (2->8 thr, 4->16 GB mem, 20->50 GB storage) — expand qexec budget / storage, no rebuild or downtime",
  status: "active",
  mode: "standalone",
  threads: 8,
  mem_gb: 16,
  storage_gb: 50,
  replicas: 1,
  node_id: "node-eu-1"
}

Output

After the call the persisted rows reflect the new cap:
  _cp_tenants(acme): threads=8, mem_gb=16, storage_gb=50
  _cp_nodes(node-eu-1): alloc_threads bumped by (8-2)x1 = +6  (now 8 for this tenant)
The Console renders the `simulated` string as a PLANNED badge in the success flash.

Console usage#

In the Cloud Console, vertical scaling is a form on the Tenants screen (/cloud/tenants, admin+). The form POSTs id + the three cap fields (as strings) to the cloud.action.scale_vertical handler. That handler resolves the session, verifies the CSRF token, does a cheap client-side rank check, then calls cloud.scale_vertical WITH the operator's role in ctx — the function re-checks the role server-side regardless. On success it redirects back with a ?flash= carrying the success message and the PLANNED note; on failure, a ?error= with the function's message.

Vertical vs migration#

Vertical scaling here means resizing the cap on the SAME node — the node already has headroom and the live instance simply re-reads a larger budget. When a tenant needs to move to a bigger server entirely (the node lacks headroom), that is a migration: a new node is brought up, synced live, and cut over via the FIFO-hold cutover. Both are governed by the one replication mechanism described in Scaling, migration & upgrades (the mechanism page). The control-plane function modeled here covers the same-node cap change; migration placement is the placer's job when headroom runs out.