Cut database egress 189x by deferring the prompt snapshot
The free-tier 5 GB/month network transfer allowance ran out, which blocks connections outright. The database is only ~55 MB, so 5 GB meant the whole thing was being pulled roughly 90 times over. Cause: actions is 39 MB of that 55 MB -- 541 rows at ~74 KB each, almost entirely context_snapshot, which stores the whole assembled prompt for a turn. Every adventure load and every turn fetched all of it in order to read two small things out of it: the world-change chips under an AI message (Action.world_changes) and the emit block re-attached when replaying history to the model (_history_text). The Insights viewer is the only consumer that wants the whole snapshot, and it asks for one action at a time. Lifts that slice into its own small actions.world_delta column (migration 36) and marks context_snapshot, state_before and world_state_before deferred, so they load only when something touches the attribute -- Insights, undo and retry, all single-action paths. The backfill runs server-side, dialect-specific (json_extract on SQLite, #> on Postgres), because pulling 39 MB of snapshots into Python to rewrite a slice of each would defeat the purpose. Measured at production shape (541 actions, 72 KB snapshots), one adventure load goes from 38.46 MB to 0.20 MB. The traffic that consumed 5 GB would now be about 27 MB. Deliberately not included: limiting the history query to recent actions, and removing the redundant db.refresh(adventure) calls. Both were sized against the old numbers; against a 0.20 MB load they would take ~27 MB a month down to ~10 MB, which is not worth the complexity. tests/test_egress.py hooks before_cursor_execute and asserts the emitted SQL never names the deferred columns during a bulk load, so this cannot regress silently. 123 tests pass. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UeQVy5bEjLhfgWNc27Efet
This commit is contained in:
co-authored by
Claude Opus 5
parent
7c538c8235
commit
f1bd099ec8
@@ -62,15 +62,17 @@ def _history_text(action: models.Action) -> str:
|
||||
delta). The block is stripped before storage/UI, so without this every past
|
||||
AI turn would look like one that emitted nothing — biasing the model, by
|
||||
imitation, to stop emitting too. Player turns and blockless turns are
|
||||
returned unchanged."""
|
||||
returned unchanged.
|
||||
|
||||
Reads `world_delta`, not `context_snapshot`: this runs for every action in
|
||||
the replayed history, and the snapshot is deferred precisely so a turn
|
||||
never drags the prompt archive out of the database."""
|
||||
text = action.text
|
||||
snap = action.context_snapshot if isinstance(action.context_snapshot, dict) else None
|
||||
if snap:
|
||||
ws = snap.get("world_state")
|
||||
if isinstance(ws, dict):
|
||||
block = worldstate.render_delta_block(ws.get("delta") or {})
|
||||
if block:
|
||||
text = f"{text}\n{block}"
|
||||
wd = action.world_delta if isinstance(action.world_delta, dict) else None
|
||||
if wd:
|
||||
block = worldstate.render_delta_block(wd.get("delta") or {})
|
||||
if block:
|
||||
text = f"{text}\n{block}"
|
||||
return text
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user