# El buffer caliente y allocate

Arrancar un servidor de juego tarda decenas de segundos. Un **buffer caliente** es un número de unidades que la plataforma mantiene iniciadas e inactivas, de modo que cuando una partida está lista, una sola llamada le entrega un servidor que ya está en marcha.

## Pasar la política a modo `code`

La política de escalado vive en `.../units/policy`. Cambiarla requiere el token de una persona: un principal de servicio puede controlar unidades pero nunca la política.

**curl**

```bash
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**

```ts
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**

```python
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"])
```


| Campo | Significado | Por defecto y rango |
|---|---|---|
| `mode` | `fixed`, `autoscaled` o `code` | `fixed` con una unidad |
| `max_units` | el máximo de unidades **con pod** a la vez: calientes, asignadas, creadas por clave y en marcha | hasta el máximo del producto y el límite de tu organización |
| `warm_units` | unidades listas y sin asignar que se mantienen además de todo lo demás | de 0 a `max_units` |
| `default_on_exit` | `restart`, `stop` o `destroy` | `restart` |
| `idle_action`, `idle_after_seconds` | detiene o destruye una unidad que lleva este tiempo señalando `idle`. Nunca se aplica a unidades calientes sin asignar | ninguna; de 60 a 86.400 |
| `destroy_stopped_after_days` | destruye las unidades detenidas tras este tiempo | 90; de 1 a 365 |
| `ready_timeout_seconds` | una unidad que no está Ready tras este tiempo desde que arrancó su pod pasa a `Failed` | 300; de 10 a 3.600 |
| `readiness` | `auto` o `signal` (Ready solo cuando la unidad señala `ready`) | `auto` |
| `rollout` | `rolling`, `replace_idle` o `next_start` | `replace_idle` para `code` |
| `spend_cap_cents` | lo máximo que pueden costar las unidades extra en un mes antes de rechazar el crecimiento | el peor caso |

Calcula el coste antes de confirmar, con el mismo cuerpo, en `POST .../units/policy/quote`:

**curl**

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

**TypeScript**

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

**Python**

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


Una unidad caliente se factura como cualquier unidad extra en marcha: ocupa CPU y memoria. Cada arranque factura al menos 5 minutos. Las unidades detenidas son gratis.

## Allocate

`POST .../units/allocate` toma **una unidad caliente lista, que acepta tráfico, sin asignar y sin drenar**, prefiriendo la plantilla actual y después la que lleva más tiempo lista. Fija `labels` y `user_data` de la unidad si los envías y el buffer se rellena en aproximadamente un segundo.

**curl**

```bash
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**

```ts
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**

```python
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"])
```


- **Atómico.** Dos llamadas nunca reciben la misma unidad.
- **`Idempotency-Key`** (opcional, hasta 64 caracteres imprimibles): reintentar con la misma clave devuelve la misma unidad mientras viva (`200`, `replayed: true`). Sin ella, cada llamada asigna una unidad nueva. Envíala siempre desde un matchmaker.
- **`?wait=delivered`** retiene la respuesta hasta que el agente de la unidad ha escrito los archivos de la asignación (`/zaroz/allocation.json`, `/zaroz/user-data.json`), hasta 10 segundos. Sin él, `unit.allocation_delivered` llega como [evento](/es/guides/units-api/events).
- **`if_none`**: `fail` (por defecto) responde `409 no-warm-unit` con `warm_ready`, `warm_starting` y `retry_after_seconds`. `create` arranca en frío una unidad nueva ya asignada a ti y responde `202` con la unidad aún arrancando.
- **Una asignación nunca se deshace.** La unidad termina por su propia salida, una parada o una destrucción.
- Si la región no es alcanzable, la asignación se revierte y recibes `503 region-unreachable`. Las unidades en marcha siguen sirviendo.

La unidad de la respuesta trae el `private_name` (`<clave>.<slug-del-workload>`) y los `public_endpoints` a los que enviar a los jugadores.

## Despliegues al publicar una imagen nueva

`replace_idle` reemplaza las unidades obsoletas que no están asignadas ni ocupadas, como mucho una cuarta parte a la vez y creando primero la nueva. Las unidades asignadas u ocupadas se reemplazan cuando terminan. `next_start` no toca las unidades en marcha y aplica el cambio la próxima vez que una unidad arranque. Mira las [reglas de despliegue](/es/units-code/rollouts).
