Skip to content

Commit 7923b7c

Browse files
committed
Add possible DO cohort support section to cohort-based deployments docs
1 parent fdcd03c commit 7923b7c

File tree

1 file changed

+153
-1
lines changed

1 file changed

+153
-1
lines changed

src/content/docs/workers/configuration/versions-and-deployments/cohort-based-deployments.mdx

Lines changed: 153 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -271,10 +271,162 @@ If Worker A does not pass a cohort, Worker B falls back to its default version c
271271

272272
## Durable Objects
273273

274+
### Current behavior
275+
274276
:::warning
275-
Cohort-based deployments do not affect [Durable Object](/durable-objects/) version routing. Durable Objects ignore the `Cloudflare-Workers-Cohort` header and follow the standard [gradual deployments behavior for Durable Objects](/workers/configuration/versions-and-deployments/gradual-deployments/#gradual-deployments-for-durable-objects).
277+
Cohort-based deployments do not currently affect [Durable Object](/durable-objects/) version routing. Durable Objects ignore the `Cloudflare-Workers-Cohort` header and follow the standard [gradual deployments behavior for Durable Objects](/workers/configuration/versions-and-deployments/gradual-deployments/#gradual-deployments-for-durable-objects).
278+
:::
279+
280+
With [gradual deployments](/workers/configuration/versions-and-deployments/gradual-deployments/#gradual-deployments-for-durable-objects), each Durable Object instance is assigned a version based on a hash of its unique ID and the deployment percentages. The assignment is deterministic and sticky -- the same instance stays on the same version until a new deployment is created. However, you have no control over which instances get which version.
281+
282+
```txt
283+
Deployment: Version A @ 60%, Version B @ 40%
284+
285+
DO Namespace: USER_DO
286+
┌──────────────────────────────────┬──────────────────────┐
287+
│ Version A (60%) │ Version B (40%) │
288+
│ ┌──────┐ ┌──────┐ ┌──────┐ │ ┌──────┐ ┌──────┐ │
289+
│ │alice │ │carol │ │eve │ │ │ bob │ │dave │ │
290+
│ └──────┘ └──────┘ └──────┘ │ └──────┘ └──────┘ │
291+
│ │ │
292+
│ Assigned by: hash(DO ID) │ │
293+
│ You cannot choose. │ │
294+
└──────────────────────────────────┴──────────────────────┘
295+
```
296+
297+
This means you cannot say "I want all paid users' Durable Objects on the new version." The hash decides, and a high-value customer's Durable Object might end up on the untested version.
298+
299+
### Planned: Cohort-based version routing for Durable Objects
300+
301+
:::note
302+
The following describes planned behavior that is not yet available. The API and behavior may change before release.
276303
:::
277304

305+
Cohort-based deployments for Durable Objects will let you control which instances run which version based on your business logic. Instead of a random hash deciding, your Worker code explicitly assigns a cohort when obtaining a stub to a Durable Object instance.
306+
307+
#### How it will work
308+
309+
Your deployment configuration maps cohorts to versions, the same as for stateless Workers:
310+
311+
```txt
312+
Deployment config:
313+
cohort "free" → Version A (stable)
314+
cohort "paid" → Version B (new features)
315+
default (null) → Version A
316+
```
317+
318+
Your Worker passes the cohort at `.get()` time -- when obtaining a [stub](/durable-objects/best-practices/access-durable-objects-from-a-worker/#2-create-durable-object-stubs) to a Durable Object, not when making an RPC call on it:
319+
320+
```ts
321+
export default {
322+
async fetch(request: Request, env: Env) {
323+
const userId = getUserId(request);
324+
const plan = getUserPlan(request); // "free" or "paid"
325+
326+
const id = env.USER_DO.idFromName(userId);
327+
328+
// Pass the cohort when getting the stub
329+
const stub = env.USER_DO.get(id, {
330+
version: { cohort: plan }
331+
});
332+
333+
// Make the RPC call -- no cohort here, the version is already decided
334+
const result = await stub.getProfile();
335+
return Response.json(result);
336+
},
337+
};
338+
```
339+
340+
The Durable Object sees its cohort at runtime:
341+
342+
```ts
343+
export class UserDO extends DurableObject {
344+
async getProfile() {
345+
const myCohort = this.ctx.version.cohort; // "free" or "paid"
346+
const myVersion = this.env.CF_VERSION_METADATA.id; // version UUID
347+
348+
return {
349+
cohort: myCohort,
350+
version: myVersion,
351+
};
352+
}
353+
}
354+
```
355+
356+
#### Version locking
357+
358+
A Durable Object instance locks to the version of the cohort it was instantiated with. If a subsequent request passes a different cohort, the instance stays on its original version. This prevents the Durable Object from flipping between code versions mid-lifetime, which would risk data corruption.
359+
360+
```txt
361+
Request 1: get(bobId, { version: { cohort: "paid" } })
362+
→ bob starts on Version B, cohort "paid" is saved
363+
364+
Request 2: get(bobId, { version: { cohort: "free" } })
365+
→ bob is ALREADY RUNNING on "paid" / Version B
366+
→ the "free" hint is ignored
367+
→ bob stays on Version B (no reset, no flapping)
368+
```
369+
370+
#### Sticky across evictions
371+
372+
The cohort will be persisted so that when a Durable Object is [evicted](/durable-objects/concepts/durable-object-lifecycle/) from memory (due to inactivity or server maintenance) and later reinstantiated, it returns to the same cohort and version.
373+
374+
```txt
375+
1. bob starts with cohort "paid" → Version B
376+
Cohort "paid" is persisted.
377+
378+
2. bob is evicted (idle overnight).
379+
Stored data and cohort persist.
380+
381+
3. New request arrives for bob.
382+
System reads persisted cohort → "paid"
383+
→ bob reinstantiates on Version B ✅
384+
385+
4. bob has a scheduled alarm that fires.
386+
No incoming request, no caller cohort.
387+
System reads persisted cohort → "paid"
388+
→ bob runs on Version B ✅
389+
```
390+
391+
This means not every caller needs to know the correct cohort. Once a Durable Object's cohort is set, it sticks until you change it or create a new deployment.
392+
393+
#### Cohort is optional
394+
395+
If you do not pass a cohort, the Durable Object uses the default version selection -- the same behavior as standard gradual deployments. Existing code that does not pass a cohort will continue to work without changes.
396+
397+
```ts
398+
// No cohort -- uses default version selection (hash-based)
399+
const stub = env.USER_DO.get(id);
400+
401+
// With cohort -- uses cohort-based version selection
402+
const stub = env.USER_DO.get(id, { version: { cohort: "paid" } });
403+
```
404+
405+
#### Why this matters
406+
407+
The key difference from gradual deployments is the type of control you get over version assignment:
408+
409+
```txt
410+
Gradual deployments (today):
411+
"Roughly 40% of my DO instances will run the new code."
412+
"I do not know which ones. The hash decides."
413+
414+
Cohort-based deployments (planned):
415+
"All paid users' DOs run the new code."
416+
"All free users' DOs stay on the stable code."
417+
"I decide, based on my business logic, and it sticks."
418+
```
419+
420+
With gradual deployments, you have **statistical** control -- "approximately this percentage." With cohort-based deployments, you have **semantic** control -- "these specific user segments." You can start with your lowest-risk instances, verify everything works, then move higher-value instances over deliberately.
421+
422+
| | Gradual deployments | Cohort-based deployments |
423+
|---|---|---|
424+
| Who decides the version | hash of DO ID | Your Worker code via `.get()` |
425+
| You choose which instances get new code | No | Yes |
426+
| Sticky across evictions | Yes (hash is deterministic) | Yes (cohort is persisted) |
427+
| Survives alarms | Yes | Yes |
428+
| Use case | "Roll out to 10% of instances" | "Free users get stable, paid users get new" |
429+
278430
## Observability
279431

280432
To monitor cohort-based deployments, use the following tools:

0 commit comments

Comments
 (0)