Store context_snapshot compressed
One column is 89% of the database and the free tier allows 512 MB. Reads were already solved -- the column is deferred, so a page load never touches it and one screen fetches one row at a time -- but nothing had costed storage, and storage is the constraint with a cliff: 99.6 MB used, ~94 kB of disk per action, so the ceiling arrives around 5,400 actions and 944 are stored. Postgres already compresses it and only gets 1.7x. pglz is tuned for fast decompression of data a query might filter on, and nothing has ever filtered on an assembled prompt -- it is written once and read whole, rarely, by the Insights viewer. zlib gets 3.5x on the same text for a decompress on a request that already made an LLM call. Done as a TypeDecorator rather than a second column, so every call site still writes a dict and reads a dict back, and deferred/undefer/load_only keep naming the same attribute. Only the storage format moves. Migrations 43-45: add the bytea, convert into it, drop the original, rename. The backfill is the one destructive step in the file -- 44 removes the only other copy -- so it decompresses every row and compares it against what went in, and a row that fails aborts the run. The whole loop is one transaction, so an abort rolls the DROP back and the prompts are still there. Verified on real Postgres, replaying 43-45 from a pre-43 schema on a throwaway Neon database: 720,864 B of JSON became 204,293 B of bytea, 3.53x, the column came out named context_snapshot, every snapshot compared equal and the one NULL stayed NULL. Postgres does not return the disk by itself: DROP COLUMN only marks the column gone and the backfill leaves a dead tuple per row, so the table peaks near twice its size before settling. The deploy needs one VACUUM FULL to collect it; the migration comment says so. The egress fixture's snapshots are prose now rather than "x" * 20_000, and the prose generator moved to tools/fakeprose.py so the harness and the tests share one definition. A repeated character compresses a thousandfold: against the old fixture a compressed column looked free and the byte ceilings would have been guarding nothing. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017Dvvqn9ZDR4ixeFPHNbww7
This commit is contained in:
co-authored by
Claude Opus 5
parent
a6cb49293c
commit
ae6e5af6c7
@@ -108,6 +108,7 @@ from app.providers import PromptParts
|
||||
from app.routers import adventures
|
||||
|
||||
from .dbmeter import Meter, kb
|
||||
from .fakeprose import prose
|
||||
|
||||
EMBEDDING_DIMS = 1536
|
||||
|
||||
@@ -123,37 +124,6 @@ EMBEDDING_DIMS = 1536
|
||||
SNAPSHOT_SYSTEM = None # set by _build_text()
|
||||
SNAPSHOT_STORY = None
|
||||
|
||||
_WORDS = (
|
||||
"corridor narrows shoulders brush wet stone torchlight gutters draught "
|
||||
"smells cold iron somewhere ahead water moving count nine paces passage "
|
||||
"opens chamber ceiling lost dark sound breathing comes back half second "
|
||||
"late Gwen catches sleeve without word points floor line pale grit laid "
|
||||
"across threshold deliberate arc quartermaster bandit camp above ford "
|
||||
"tunnels exchange key lantern rope knife bread rain mud hill road gate "
|
||||
"watchman silver debt promise fever horse cart river bridge mill barley "
|
||||
"smoke rafters bench ale ledger seal wax parchment ink candle shutter "
|
||||
"hinge bolt cellar barrel salt fish nets harbour tide gull mast canvas"
|
||||
).split()
|
||||
|
||||
|
||||
def prose(rng: random.Random, nbytes: int) -> str:
|
||||
"""Filler of about `nbytes`, varied enough to compress like prose.
|
||||
|
||||
Not decoration. A sentence repeated N times compresses roughly a
|
||||
hundredfold and English roughly three- or fourfold, so a fixture built out
|
||||
of repeats would report a compression ratio that says nothing about the
|
||||
real column — and that ratio is the whole question for context_snapshot.
|
||||
"""
|
||||
out: list[str] = []
|
||||
total = 0
|
||||
while total < nbytes:
|
||||
sentence = " ".join(rng.choice(_WORDS) for _ in range(rng.randint(8, 18)))
|
||||
chunk = sentence.capitalize() + ". "
|
||||
out.append(chunk)
|
||||
total += len(chunk)
|
||||
return "".join(out)[:nbytes]
|
||||
|
||||
|
||||
PLAYER_INPUT = "> You crouch and look more closely at the grit on the floor."
|
||||
|
||||
# All three are bound by _build_text() from the fixture arguments.
|
||||
|
||||
Reference in New Issue
Block a user