Skip to content

Persistence

rebaserHEAD edited this page Oct 18, 2026 · 3 revisions

explanation, committed verified 2026-10-18 bootstrap c3409ba run 2026-10-18

How persistence works as built, and why: one database with a schema per owner, and a queue that carries every owner's work to it so no owner ever waits. The facts to look up are on Persistence schemas and Persistence write-behind; what's still to build is on Persistence design.

How it fits together

The shape

flowchart TD
    O["An owner"] -->|"work naming its keys"| Q["Write-behind queue"]
    Q -->|"heads every key's line"| W["A save worker"]
    W -->|"one transaction"| DB[("PostgreSQL 18")]
    W -->|"committed, refused or retried"| O
Loading

One database, a schema per owner. The account owner's data lives in account, the realm owner's in realm, and the command layer's audit in ops, each with its own context and its own migrations (Persistence schemas).

No owner touches the database. An owner hands the write-behind queue a piece of work naming the keys it touches, and hears back by message on its own thread. The queue keeps one line per key, so work on one account or character runs in order and work on different ones runs side by side (Persistence write-behind).

An answer waits for its commit. An owner tells the client a change happened only once the change is committed, and a write a unique rule refuses comes back as an answer, not a failure.

Sign-on is the one direct read. The gateway reads an account's credentials itself, through the account directory, because it holds no game state an owner would have to wait for.

Why it's built this way

An owner that waits on the database stalls everything it holds for as long as the database takes. Owners ask, workers do, and the order of saves and loads per key is kept in one place, so a load can't overtake the save it depends on.

PostgreSQL 18 is the minimum because names and emails use a case-insensitive ICU collation, and LIKE works on such collations from 18.

Idea What killed it
citext for names and emails An ICU collation does the same job, and PostgreSQL 18 lets LIKE work on it

Knobs

Setting Default Sets
Persistence:ConnectionString none, required The database
Persistence:SaveWorkers 4 Workers applying work, at least one
Persistence:FirstRetryDelay 100 ms The first wait before a retry
Persistence:MaxRetryDelay 30 s The longest wait between retries
Persistence:FirstIdBlockTimeout 30 s How long start-up waits for the first block of character ids

Vocabulary

Term Means here
Work One piece of database work an owner hands the queue, with the keys it touches
Key What orders work: an account, a character, an email or a named line
Refused Completed without writing, because a unique rule said no

Known edges

Where the code lives

Piece Path
Contexts, conventions and migrations Source/NexusTomorrow.Persistence/
Account work and the account directory Source/NexusTomorrow.Persistence/Account/
Character work and id blocks Source/NexusTomorrow.Persistence/Realm/
The audit Source/NexusTomorrow.Persistence/Ops/
The write-behind queue Source/NexusTomorrow.Persistence/WriteBehind/
Tests Tests/NexusTomorrow.Persistence.Tests/

Clone this wiki locally