carlos / platform Public

Operator-set CPU/memory per tenant instance (today only the two platform tiers, chosen by the manifest's children bit) Feature Request open

Claimed by claude~paul@keymail.dev 3 days ago

Goal (Tito): give individual tenants more or less CPU and memory. A large event's account gets more headroom; a dormant one gets less.

Today (v0.28.4): an isolated instance's limits come from one of two platform-chosen tiers (internal/cgroup/cgroup.go): TierDefault (512M, CPUQuota=100%) and TierChildren (1G, 200%). The tier is picked by the manifest's children bit, and "no number comes from a manifest". That's right for app authors, but it leaves an operator no way to size one tenant differently from another of the same app.

Request: an operator-set size per instance, and perhaps a default per app.

  • For example carlos instances set <host> --size large, or explicit --memory 1G --cpu 200%.
  • Ops-owner or app-owner only, never from a manifest, and recorded and shown in carlos instances list.
  • The console or box refuses a size the box's capacity can't honour, and says how much is left.
  • It applies at the next start, or live through systemctl set-property --runtime, as setTier already does.
  • A small fixed set of named sizes would suit us better than free numbers, and matches how tiers work today.

We'd use it once Tito's tenants are isolated. That's being prepared now, and is also blocked on egress to a private service (filed alongside).

Any ruling on this? It's the reason we're isolating tenants at all. We'd like to give a large event's account more memory and CPU during its sale, and a dormant one less. Named sizes (small, standard, large) set per instance by an operator would cover it.

New evidence from Tito today (2026-10-23), measured on production.

The default tier caps a busy tenant below Tito's target. Tito wants 160–320 req/s per instance on an uncached event page, behind a per-client rate limit at the edge (#358). After this morning's fix, one page costs about 9 ms of CPU on the ie box (t4g.xlarge, Graviton2). TierDefault's CPUQuota=100% therefore puts a hard ceiling of roughly 110 req/s on any platform instance, whatever the app does. The box itself had at least two cores free during the test: 33–52% of 4 vCPUs, credit balance at the 2304 maximum, unlimited mode.

What we measured. go.tito.io/paulca/awesomeconf, root domain (never edge-cached), open loop capped at 16 in flight:

  • Before the fix: 45 req/s delivered at 80 attempted.
  • After: 78 req/s delivered at 160 attempted.

That account is still a legacy titogo@ systemd unit with cpu.max = max and zero throttling, so this tier hasn't touched it yet. It will once it is adopted (#259/#261).

What this adds to the ask. Per-instance sizing set by an operator, as above, is still the request. Two specifics from today:

  1. A size above one CPU for a single-process instance, without also claiming the children tier (whose extra memory we don't need). CPUQuota=200% or 300% for a "large" size would let an on-sale account use the cores the box has idle.
  2. If sizing is far off, an interim option: keep CPUWeight as the fairness mechanism under contention, and raise or drop the hard CPUQuota for the default tier. Weight already makes tenants take turns when the box is busy. The quota only bites when the box has headroom, which is exactly when we want a busy tenant to use it.

Ruling: operator-set named sizes, an app default plus a per-instance override. The design is approved (same spec as #328, rev 9); the build follows egress.

| size | memory cap | CPU quota | CPU weight | |---|---|---|---| | small | 256 MiB | 50% | 50 | | standard | 512 MiB | 100% | 100 (today's default) | | large | 1 GiB | 200% | 100 (today's children tier) | | xlarge | 2 GiB | 400% | 200 |

  • Set by a system operator: carlos apps set <app> --size large for the app, carlos instances set <host> --size xlarge for one instance, --size default to clear.
  • Growing applies to a running instance at once. A smaller memory size waits for the instance's next cold start, so a resize never OOM-kills anything; CPU and IO changes apply live.
  • carlos status --layout shows the size in force, read back from systemd.

On today's evidence: large is the answer to point 1. It gives a single-process instance two CPUs without needing the children bit, and its 1 GiB is a ceiling, not a reservation. On point 2: no interim change to the default tier's quota. It would apply to every isolated instance on every fleet, including shared boxes where burstable CPU credits cost money and neighbours would feel it. It also doesn't bind awesomeconf yet: that account runs unthrottled until it is adopted (#259/#261), and the sizes should land before then.