One Durable Object per lot holds every WebSocket and processes bids one at a time. When the lot closes, it reconciles into AWS and deletes its data. $15–30 a month vs $130–410 on AWS designs.
The one idea
idFromName("lot-123") reaches the same single instance anywhere in the world. Everyone watching lot 123 connects to that one object, which handles one event at a time. Two bids in the same millisecond are processed in turn; the second sees the first's result.
The protocols
| Protocol | Role in the auction |
|---|---|
| WebSockets (hibernating) | Every watcher's live connection. Idle objects sleep and aren't billed; outgoing pushes are free. |
| HTTP | Page load: current price, leader, time left. |
| RPC from a Worker | stub.placeBid(user, amount): the REST bid endpoint and admin actions. |
| Alarms | One timer per object at the close time. Anti-sniping just moves it. |
| SQLite | lot and bids tables inside the object. |
| Outbound fetch | Sends the result to AWS. |
The flow
Browsers (500–10,000 per lot)
│ wss://auctions.example.com/lot/123
▼
Worker ── verify JWT, rate-limit, route by lot ID
│ idFromName("lot-123")
▼
LotDO "lot-123" (one instance worldwide)
SQLite: lot + bids · holds every socket · alarm at close time
│ │
│ broadcast each new price │ alarm fires → lot SOLD
▼ (in order, free) ▼
every watcher POST result + full bid log to AWS
(retries by alarm until AWS acknowledges)
▼
Function URL → SQS → Lambda → Postgres (invoice, Stripe, reports)
└→ S3 Parquet (data lake)
▼
object deletes its own data
The Worker is the door, the object is the room, and AWS gets the minutes when the meeting ends.
The gotcha: await
Single-threaded doesn't mean transactional across an await. If the object awaits an outside call (asking AWS whether the bidder is verified) between reading the price and writing the bid, other bids run in the gap. Do outside checks first; keep read → check → write → broadcast synchronous.
Also: every deploy restarts all objects and drops every socket. Clients reconnect automatically; don't deploy during a live auction.
Cost: 2,000 lots a month, 500 watchers each, 300M pushed updates
| Design | Monthly |
|---|---|
| Durable Objects + Worker | $15–30 |
| Self-run Fargate + load balancer + Valkey | ~$130–165, plus deploys, scaling and failover |
| AppSync Events + Lambda | ~$310 |
| API Gateway WebSocket + Lambda + DynamoDB | ~$407 ($390 of it is pushes) |
Postgres takes the bids fine with a conditional UPDATE. What's hard on AWS is holding thousands of sockets and pushing every update in order: one hot lot with 10,000 watchers needs 40,000 pushes a second, four times API Gateway's default account limit.
Durable Objects beat everything else when all four are true
- One thing with shared live state (a lot, room, document, game, device).
- Many clients connected to it at once, receiving pushes.
- A single referee: first wins, no double-booking.
- A lifespan or independence: it lives for an event, and you rarely query across many things.