mirror of
https://github.com/nolen777/eagle0.git
synced 2026-07-28 21:35:42 +00:00
Document Postgres history migration plan (#6964)
This commit is contained in:
@@ -0,0 +1,149 @@
|
||||
# Postgres History Migration Plan
|
||||
|
||||
## Goal
|
||||
|
||||
Evaluate and, if the measurements support it, migrate production game history persistence from local SQLite plus S3 base
|
||||
and delta files to a managed Postgres database.
|
||||
|
||||
This is not just a mechanical port. The important product requirement is that battle commands, especially Shardok map
|
||||
clicks, show results to the player as quickly as possible while still keeping the chance of lost results acceptably low.
|
||||
|
||||
## Proposed Direction
|
||||
|
||||
Use Postgres as the authoritative production store. Local SQLite should remain useful for development, tests, and rollback
|
||||
during rollout, but production should not have two long-lived authoritative stores for the same game.
|
||||
|
||||
Start with a small DigitalOcean Managed Postgres instance in the same region and VPC as Eagle. The $15 single-node plan is
|
||||
reasonable for early measurement and staging. If the design works, production can move to a more reliable plan when the
|
||||
availability requirements justify it.
|
||||
|
||||
## Phase 1: Measure First
|
||||
|
||||
Before porting the full history implementation, add a benchmark or admin utility that runs from the Eagle deployment
|
||||
environment against the candidate Postgres database.
|
||||
|
||||
Measure at least:
|
||||
|
||||
- single action-result append transaction latency
|
||||
- batch append latency for 25 and 100 action results
|
||||
- latest snapshot lookup
|
||||
- replay from a snapshot to a target index
|
||||
- `since` and stream-update query latency
|
||||
- Shardok battle result append latency
|
||||
- p50, p95, p99, and max latency
|
||||
- async queue lag, if an async persistence prototype is included
|
||||
|
||||
Useful initial exit criteria:
|
||||
|
||||
- Strategic action-result writes should be comfortably below 50 ms p95.
|
||||
- Shardok synchronous persistence should be below roughly 20-30 ms p95 to stay on the click-to-animation path.
|
||||
- If Shardok sync writes are above that, use an async Shardok persistence design rather than blocking player feedback.
|
||||
|
||||
## Phase 2: Schema Prototype
|
||||
|
||||
Add Postgres configuration and a prototype schema that maps the current SQLite history model.
|
||||
|
||||
Expected tables:
|
||||
|
||||
- `games`, for game-level metadata and backend bookkeeping
|
||||
- `action_results`, for ordered strategic action results
|
||||
- per-entity decomposition tables currently written by the SQLite dispatchers
|
||||
- `state_snapshots`, for replay anchors
|
||||
- Shardok battle result tables, including battle id, sequence number, command/result payloads, and player metadata
|
||||
- `schema_migrations`, unless the migration mechanism is handled outside the app
|
||||
|
||||
Prefer batched inserts and batched reads. The current SQLite implementation can afford some local-file patterns that will
|
||||
be too chatty over a network.
|
||||
|
||||
## Phase 3: Implement Postgres History
|
||||
|
||||
Implement a `FullGameHistory` backend backed by Postgres. Keep the existing SQLite backend available behind a config flag
|
||||
during rollout.
|
||||
|
||||
Core methods to support:
|
||||
|
||||
- `count`
|
||||
- `last`
|
||||
- `withNewResults`
|
||||
- `stateAfter`
|
||||
- `since`
|
||||
- `sinceDate`
|
||||
- round-local recent-result queries
|
||||
- Shardok result writes and reads
|
||||
|
||||
The implementation should preserve action ordering and idempotency. If retries are possible, writes should include stable
|
||||
keys or sequence ranges so replaying a write request cannot duplicate results.
|
||||
|
||||
## Phase 4: Decide Shardok Durability Mode
|
||||
|
||||
Shardok latency is the place where this migration can most visibly hurt the player. There are two viable modes.
|
||||
|
||||
### Option A: Synchronous Shardok Writes
|
||||
|
||||
Use this if measured Postgres latency is low enough. The command path writes results to Postgres before telling the client
|
||||
about them.
|
||||
|
||||
This is the simplest durability model, but only acceptable if it keeps click-to-animation latency low.
|
||||
|
||||
### Option B: Async Shardok Writes
|
||||
|
||||
Use this if synchronous Postgres writes add noticeable latency.
|
||||
|
||||
The model:
|
||||
|
||||
- apply battle results to authoritative in-memory game state immediately
|
||||
- send client updates immediately after the ordered in-memory apply
|
||||
- enqueue ordered persistence batches in the same order they were applied
|
||||
- persist batches with `battle_id`, sequence range, payload hash, and payload
|
||||
- track `lastAppliedSeq` and `lastPersistedSeq`
|
||||
- retry failed writes until they succeed
|
||||
- alert when persistence lag exceeds a small threshold
|
||||
- block or degrade new commands if the queue grows too large
|
||||
- force a synchronous checkpoint before battle end reconciliation or strategic-layer handoff
|
||||
- drain the queue during graceful shutdown
|
||||
|
||||
This creates a small crash-loss window, but only for rare crashes that happen after the player sees a result and before
|
||||
the async write completes. That tradeoff may be better than adding network persistence latency to every tactical command.
|
||||
|
||||
## Phase 5: Rollout
|
||||
|
||||
Add a backend flag such as:
|
||||
|
||||
```text
|
||||
EAGLE_HISTORY_BACKEND=sqlite|postgres
|
||||
```
|
||||
|
||||
Suggested rollout:
|
||||
|
||||
1. Run benchmarks from the deployed environment.
|
||||
2. Enable Postgres in staging for new games.
|
||||
3. Run warmup games and Shardok battle smoke tests.
|
||||
4. Enable Postgres for new production games.
|
||||
5. Migrate old games either lazily on first open or through an admin-triggered migration job.
|
||||
6. Keep SQLite/S3 rollback available until enough new-game production time has passed.
|
||||
|
||||
If Postgres is unavailable, commands should not be acknowledged as durable. Either hold/retry them before applying, or
|
||||
reject them clearly. Do not show a result to the client while pretending it has already been durably stored unless that is
|
||||
an explicit async-durability path with lag monitoring.
|
||||
|
||||
## Phase 6: Cleanup
|
||||
|
||||
After production confidence:
|
||||
|
||||
- remove production dependence on S3 base/delta history files
|
||||
- keep object storage for backups, exports, or emergency archives
|
||||
- keep SQLite for local development and tests if it remains useful
|
||||
- document restore, migration, and rollback procedures
|
||||
|
||||
## Open Questions
|
||||
|
||||
- What exact durability guarantee do we want for "the client already saw this result"?
|
||||
- What Shardok persistence latency is acceptable in real play?
|
||||
- Should old games migrate lazily or through a bulk/admin process?
|
||||
- When should the production database move from single-node to HA?
|
||||
- What should the player experience be if Postgres is reachable but slow?
|
||||
|
||||
## Recommended Next PR
|
||||
|
||||
The next implementation PR should add a Postgres benchmark/admin utility and configuration plumbing, not the full port.
|
||||
That gives us real latency numbers from the Eagle deployment environment before committing to the larger migration.
|
||||
Reference in New Issue
Block a user