Zaroz
Units API guide

The warm buffer and allocate

Starting a game server takes tens of seconds. A warm buffer is a number of units the platform keeps started and idle, so that when a match is ready, one call hands it a server that is already running.

Switch the policy to code mode

The scaling policy lives at .../units/policy. Changing it needs a person's token: a service principal may control units but never the policy.

curl

TerminalCode
curl -sS -X PATCH "$ZAROZ_API/projects/$PROJECT/service-instances/$SERVICE/units/policy" \ -H "$AUTH" -H "Content-Type: application/json" -d '{ "mode": "code", "max_units": 12, "warm_units": 3, "default_on_exit": "destroy", "idle_action": "stop", "idle_after_seconds": 600, "rollout": "replace_idle" }'

TypeScript

TypeScriptCode
const policy = await call("PATCH", `${UNITS}/policy`, { mode: "code", max_units: 12, warm_units: 3, default_on_exit: "destroy", idle_action: "stop", idle_after_seconds: 600, rollout: "replace_idle", }); console.log(policy.status, policy.body.mode);

Python

Code
status, policy = call("PATCH", f"{UNITS}/policy", { "mode": "code", "max_units": 12, "warm_units": 3, "default_on_exit": "destroy", "idle_action": "stop", "idle_after_seconds": 600, "rollout": "replace_idle", }) print(status, policy["mode"])
FieldMeaningDefault and range
modefixed, autoscaled or codefixed at one unit
max_unitsthe most units with a pod at once: warm, allocated, created by key and runningup to the product maximum and your organization limit
warm_unitsready, unallocated units to keep on top of everything else0 to max_units
default_on_exitrestart, stop or destroyrestart
idle_action, idle_after_secondsstop or destroy a unit that signalled idle this long. Never applied to unallocated warm unitsnone; 60 to 86,400
destroy_stopped_after_daysdestroy stopped units after this long90; 1 to 365
ready_timeout_secondsa unit not Ready this long after its pod started is Failed300; 10 to 3,600
readinessauto or signal (Ready only after the unit signals ready)auto
rolloutrolling, replace_idle or next_startreplace_idle for code
spend_cap_centsthe most the extra units may cost in a month before growth is refusedthe worst case

Preview the cost before committing, with the same body, on POST .../units/policy/quote:

curl

TerminalCode
curl -sS -X POST "$UNITS/policy/quote" -H "$AUTH" -H "Content-Type: application/json" -d '{"mode": "code", "max_units": 12, "warm_units": 3}'

TypeScript

TypeScriptCode
const quote = await call("POST", `${UNITS}/policy/quote`, { mode: "code", max_units: 12, warm_units: 3 }); console.log(quote.body);

Python

Code
status, quote = call("POST", f"{UNITS}/policy/quote", {"mode": "code", "max_units": 12, "warm_units": 3}) print(quote)

A warm unit is billed like any running extra unit: it holds CPU and memory. Every start bills at least 5 minutes. Stopped units are free.

Allocate

POST .../units/allocate takes one ready, accepting, unallocated, draining-free warm unit, preferring the current template and then the longest ready. It sets the unit's labels and user_data when you give them, and the buffer refills in about a second.

curl

TerminalCode
curl -sS -X POST "$UNITS/allocate?wait=delivered" -H "$AUTH" -H "Content-Type: application/json" \ -H "Idempotency-Key: match-7f3a" -d '{ "labels": {"mode": "2v2"}, "user_data": {"roster": ["ana", "luis", "marta", "pau"], "map": "dunes"}, "if_none": "fail" }' # 200 {"created": false, "replayed": false, "unit": {"key": "w-k3m9q2xa", "allocation": {...}, ...}} # 409 {"code": "no-warm-unit", "warm_ready": 0, "warm_starting": 2, "retry_after_seconds": 5, ...}

TypeScript

TypeScriptCode
const res = await call( "POST", `${UNITS}/allocate?wait=delivered`, { labels: { mode: "2v2" }, user_data: { roster: ["ana", "luis", "marta", "pau"], map: "dunes" }, if_none: "fail", }, { "Idempotency-Key": "match-7f3a" }, ); if (res.status === 409 && res.body.code === "no-warm-unit") { // none ready: wait res.body.retry_after_seconds and retry, or use if_none: "create" } else { console.log(res.body.unit.key, res.body.unit.private_name, res.body.unit.public_endpoints); }

Python

Code
status, res = call( "POST", f"{UNITS}/allocate?wait=delivered", { "labels": {"mode": "2v2"}, "user_data": {"roster": ["ana", "luis", "marta", "pau"], "map": "dunes"}, "if_none": "fail", }, headers={"Idempotency-Key": "match-7f3a"}, ) if status == 409 and res["code"] == "no-warm-unit": pass # none ready: sleep res["retry_after_seconds"] and retry, or use if_none="create" else: print(res["unit"]["key"], res["unit"]["private_name"], res["unit"]["public_endpoints"])
  • Atomic. Two callers never get the same unit.
  • Idempotency-Key (optional, up to 64 printable characters): retrying with the same key returns the same unit while it lives (200, replayed: true). Without it, every call allocates a new unit. Always send one from a matchmaker.
  • ?wait=delivered holds the response until the unit's agent has written the allocation files (/zaroz/allocation.json, /zaroz/user-data.json), up to 10 seconds. Without it, unit.allocation_delivered arrives as an event.
  • if_none: fail (default) answers 409 no-warm-unit with warm_ready, warm_starting and retry_after_seconds. create cold-starts a new unit already allocated to you and answers 202 with the unit still starting.
  • An allocation is never undone. The unit ends by its own exit, a stop or a destroy.
  • If the region cannot be reached, the allocation is reverted and you get 503 region-unreachable. Running units keep serving.

The unit in the response has the private_name (<key>.<workload-slug>) and the public_endpoints to send players to.

Rollouts when you ship a new image

replace_idle replaces stale units that are not allocated and not busy, at most a quarter at a time, new unit first. Allocated or busy units are replaced when they end. next_start leaves running units alone and applies the change the next time a unit starts. See rollout rules.

Last modified on