-
Notifications
You must be signed in to change notification settings - Fork 0
Distributed Systems Model
Audience: Engine Developer Status: ✅ Ready
TelosMUD scales a persistent, stateful text world horizontally by sharding it into zones and giving each zone exactly one owner. The invariants that make that safe — single-writer ownership, time-fenced leases, monotonic placement, apply-once event delivery — are collected here. This is the "why it's correct" companion to the mechanisms documented on Zone Runtime & Actor Model, Cross-Shard Handoff, Scoped Event Bus, and Persistence & Durability. For the operator's view see Running at Scale.
| Service | Scaling model | Coordination |
|---|---|---|
telos-gate |
horizontal, stateless edge | routes a session to its shard via the Redis directory; any gate can serve any player |
telos-world |
horizontal by zone, single-writer per zone | claims zones from a pool via Redis CAS leases |
telos-director |
singleton per scope, leader-elected | Redis lease; warm standbys (Orchestration & Directors) |
telos-account |
horizontal, stateless behind Postgres | off the hot path — the world trusts a signed assertion |
The gate holds no authoritative game state, so you add instances freely. Session→shard routing is Redis-directory-driven (per-character placement → home-zone owner → configured fallback); a cross-shard move carries the destination address in the world's Redirect frame, so there is no gate affinity.
Each zone is one goroutine that owns all its entities; every mutation funnels through its inbox. No in-zone locks, deterministic ordering, and a clean unit of placement. Two-writer prevention is enforced at the directory, not in-process:
-
Time-fenced lease CAS:
claimZonegrants ownership only if the zone is unowned, expired, or already yours, using the single RedisTIMEclock so a skewed shard can't steal a live lease. Default lease 15 s, renewed at TTL/3. - Duplicate shard-id refused: registering a shard id that a different live endpoint holds is rejected — the guard that stops two same-id owners both reading as renewals.
- Monotonic placement epoch: a per-player CAS makes placement monotonic, so a stale or duplicated handoff can't route a player back to a shard they've left.
Cross-boundary interaction is message-passing only (the gRPC Play stream and the Handoff RPC); no shared mutation ever crosses a shard.
World servers claim zones from a shared pool rather than statically declaring them. On boot a shard registers its id→endpoint, then walks the configured pool and wins each free zone via the directory CAS; a zone already owned by a live peer is skipped. Liveness is decentralized and works even with no director running — claim-from-pool plus lease expiry is the failover mechanism (a crashed shard's zones become unclaimed when its 15 s leases lapse). A server that wins nothing runs as a warm standby. The director's placement role is an optimizer, not a dependency.
The bootstrap core zone is hosted locally + unleased on every shard, so a fresh/empty fleet still serves a lobby.
State is checkpointed to Redis (~10 s) and Postgres (~60 s), with a state_version optimistic-concurrency guard that fences stale writes: the CAS WHERE state_version = $expected means a mis-fired handoff or a zombie owner loses the race rather than clobbering the durable record. On load the fresher of {row, checkpoint} wins by state_version. See Persistence & Durability.
The scoped event bus splits transient down-broadcasts (at-most-once, NATS core) from durable signal-up (at-least-once, JetStream). Correctness comes from apply-once over at-least-once: the director consumes the durable stream only while leader (stable consumer id → resume from last ack) and dedups by a per-source monotonic watermark. A capstone test proves exactly-once effect — precisely three boss kills counted across a mid-sequence director restart.
The world degrades rather than crashing:
- Redis down → single-shard mode; cross-shard exits sealed.
- Postgres down → ephemeral characters; hot-reload disabled.
- NATS down → comms/tells/hot-reload disabled.
- A dead gate → gRPC server keepalive pings reclaim leaked world streams.
Security-sensitive gaps fail closed on boot rather than running open: a gate with no account target, a shard with no handoff verify key, an account service with no caller token, or a pack-set divergence each refuse to start unless TELOS_ALLOW_INSECURE is explicitly set (Deployment).
The fundamental limit is one zone = one core: a zone is a single goroutine with a 250 ms heartbeat budget, and all combat rounds and affect ticks run inline on it. telos.zone.tick_lag_ms (how far past 250 ms a heartbeat fired) is the headline scale signal. A single hot zone cannot exceed one core — it does not shard within itself, and instancing (multiple copies of a zone) is deferred/not implemented. There is no hard players-per-box constant in code; capacity is measured with the bot-swarm load tester plus OpenTelemetry (Running at Scale).
Placement is a maturing subsystem — the load-aware rebalancer is shipped, but a few edges remain:
- Rebalancing is automatic and load-aware. Each shard heartbeats per-zone occupancy; the leader director plans a player-weighted spread and issues rebalance-drain directives that the owning shard's executor drains via the handoff, with a 5-minute per-zone cooldown and locality-aware colocation. It stays an optimizer — if the director is down, zones are still claimed and served, just possibly unbalanced.
- Drain-target selection is director-owned and serialized — a peer is chosen against a soft occupancy ceiling and its headroom is atomically reserved in the directory, so concurrent drains don't pile onto one peer. If every peer is reservation-full it admits over the soft ceiling rather than stall (a dropped connection is worse than transient overload the rebalancer then corrects); the reservation is an admission hint (~15 s TTL), not a durable lock.
- Orphan-zone failover is not instantaneous — it relies on lease expiry (~15 s) plus a re-claiming shard on boot; there is no running-standby auto-adopt loop, so recovery is ~lease-TTL + reclaim.
TelosMUD — Wiki under construction.
- Builder Reference
- Builder Commands
- Trust Tier Model
- Pack Authoring
- Pack MUD Settings
- Pack Lua Scripting
- Pack Lua Hooks
- Pack Entity Reference
- Building Instanced Zones
- Engine Developer Reference
- Architecture Overview
- Entity Component Model
- Zone Runtime & Actor Model
- Instanced Zones
- Command Parser & Targeting
- Edge & Protocol
- GMCP Reference
- Persistence & Durability
- Content Loading & Hot Reload
- Abilities & Effects
- Combat System
- Loot, Spawns & Crafting
- Accounts & Auth Internals
- Orchestration & Directors
- Scoped Event Bus
- Cross-Shard Handoff
- Lua Sandbox Internals
- Distributed Systems Model
- RPC & Protobuf