Skip to content
SkyKeephelp

Sizing the datastores

You do not need a database administrator to read this page, and that is the point. Both Postgres instances ship with sizes chosen for the machine this vault actually runs on rather than the ones a container image assumes. You can leave every one of them alone: unset means the built-in default, and the built-in default is the sized configuration, not the stock one.

What is being sized, and why it matters here. On the measured demonstration vault — 264 documents, 6,822 chunks, 109,819,571 bytes of database — the vector index chunks_embedding_768_hnsw is 27,115,520 bytes, which is 24.7% of everything, and the 768-dimension vectors beside it are another 19.1%. Half of this product's bytes are what a search reads on every single question. A stock container gives that 128 MB of cache and 4 MB of sort memory.

A typo costs you the default, never the start-up. Unset, blank, unit-less, unparseable, zero, or larger than this machine all fall back to the built-in default, because a datastore that refuses to boot is the one holding the evidence you were about to look at. The value that was ignored is not swallowed: the administrator's performance panel names it and says why. Always write a unit. shared_buffers=512 is valid Postgres and means 512 blocks — 4 MB, thirty-two times smaller than the 128 MB this exists to raise — so a bare number is refused rather than guessed at.

One exception, and it is the only one. shm_size is a Docker field, not a Postgres parameter: the container runtime reads it before any process of ours exists, so nothing can catch a malformed value and fall back. docker compose refuses the stack and names the variable. Loud, but a refusal.

What ships, and why each number

ServiceParameterSettingDefaultWhy this number
vecstoreshared_buffersSKYKEEP_PG_SHARED_BUFFERS512MBHolds the whole measured database (104.7 MiB) five times over and the vector hot set (48 MiB of index plus vectors) ten times over; at the measured 186 KiB of hot vector bytes per document that is the working set of roughly 2,800 documents. 12.5% of an 8 GiB box rather than the textbook 25%, because the model runtime is the other tenant.
vecstorework_memSKYKEEP_PG_WORK_MEM16MBPostgres' 4 MB is per sort or hash NODE, per statement, times parallel workers. Hybrid retrieval orders a candidate set by vector distance and ranks a tsvector query in one statement, and at 4 MB both spill to disk. The smallest raise of the four on purpose: it is the only dial here that multiplies.
vecstoremaintenance_work_memSKYKEEP_PG_MAINTENANCE_WORK_MEM256MBThe HNSW dial. pgvector builds the graph in this budget and spills to a much slower two-pass build when it does not fit; the measured index is 25.9 MiB for 264 documents, so 256 MB is the in-memory build envelope for roughly a 2,600-document vault — which is what a restore or an embedder change rebuilds.
vecstoreeffective_cache_sizeSKYKEEP_PG_EFFECTIVE_CACHE_SIZE2GBReserves nothing; it is what the planner believes the machine can keep cached between shared buffers and the OS. Understated it prefers sequential scans over exactly the index-shaped work this vault does. 2 GiB assumes about half of an 8 GiB box is generally available for file caching.
vecstoreshm_sizeSKYKEEP_PG_SHM_SIZE256mbWhere parallel workers put dynamic shared memory. At Docker's 64 MiB default a parallel index scan or a parallel HNSW build can die with “could not resize shared memory segment”, which reads as a product defect and is a container default.
auditstoreshared_buffersSKYKEEP_PG_AUDIT_SHARED_BUFFERS256MBNo vectors, no HNSW, no TOASTed content: a hash-chained append-only ledger of small JSON rows. What its readers want cached is the RECENT TAIL — the audit view's windowed pages and the per-agent usage aggregation — and 256 MB buys that without taking a second 512 MB bite out of the same box.
auditstorework_memSKYKEEP_PG_AUDIT_WORK_MEM8MBTwice Postgres' default, half the vecstore's. The heaviest sort on this instance is an ORDER BY over a bounded window of the newest entries; there is no vector distance ordering here to spill.
auditstoremaintenance_work_memSKYKEEP_PG_AUDIT_MAINTENANCE_WORK_MEM64MBPostgres' own default, kept deliberately. There is no index build on this instance worth more, and the value is here so that an operator whose ledger has grown can raise it — not because the shipped number was wrong.
auditstoreeffective_cache_sizeSKYKEEP_PG_AUDIT_EFFECTIVE_CACHE_SIZE1GBHalf the vecstore's hint, for the same reason its buffers are half: two instances share one page cache, and telling both that they have all of it would make both plan as if the other were not there.
auditstoreshm_sizeSKYKEEP_PG_AUDIT_SHM_SIZE64mbDocker's own default, kept deliberately: nothing on this instance runs parallel workers over a large relation, so raising it would reserve memory for a plan that never runs.

What to raise, on what evidence, and what it costs

A starting point for a bigger box

Not a recommendation — a starting point. The shipped column assumes the small machine, because the small machine is the one that breaks, and because the model runtime, not Postgres, is the hungry tenant on this box. Measure before you move off it.

Machineshared_bufferswork_memmaintenance_work_mem / effective_cache_size
8 GiB (the shipped default)512MB16MB256MB / 2GB
16 GiB1GB24MB512MB / 4GB
32 GiB or more2GB32MB1GB / 8GB

Measuring, rather than guessing

Every sentence above says measure, so there is a thing to measure with. Against a disposable verification stack — never this deployment, and never one holding anything you would miss — scripts/loadlane.py seeds a fresh compartment with N synthetic documents through the ordinary upload route, waits for every one of them to leave the processing queue, then runs M agents issuing searches and questions for a fixed duration, and prints one table: documents admitted per hour, search p50/p95/p99, and how far the audit chain's verified position trails the outbox. It takes the stack it points at entirely from the environment, it deletes nothing, and it refuses to start unless that environment carries the disposable-lane acknowledgment and the repository's lane posture guard agrees the target is not a demonstration. Run it once before you change a dial and once after; the difference is the evidence the sections above ask for, and --out writes the same table to a file so the comparison survives the terminal.

python3 scripts/loadlane.py --documents 200 --uploaders 4 --agents 8 --duration 300 --out before.md

Making a change take effect

A Postgres memory parameter is read once, by the server process, when it starts. Editing the environment file changes nothing until the container is recreated — a restart of the vault's own service is not enough, because the value is resolved from the environment the container was created with.

scripts/skykeep.sh stop
scripts/skykeep.sh start

Then confirm the server agrees with you, which is the only check worth making:

docker compose exec vecstore psql -U <user> -d <database> -c 'SHOW shared_buffers; SHOW work_mem;'