Repository navigation
Conversation
Since the statement caches keep every statement of a document, an editor with 20 files of 5,000 statements open kept 2.7 GB of parse trees. The open documents now share a budget of 20,000 cached parse results and trees: when they keep more, the caches of the least recently used documents are dropped and recomputed on their next use. Closing a document also drops the hover snapshot, which may have been built for it.
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on #929.
#913 made each document keep the parse result and tree of every one of its statements, so that walking a big file doesn't parse it again. Nothing bounded that across documents. A statement's parse tree takes about 10 KB, so an editor with 20 migration files of 5,000 statements each open kept 2.7 GB, until the files were closed.
What the caches are
PgQueryStore::ast_db(#913)PgQueryStore::plpgsql_db(#913)CREATE FUNCTION ... plpgsqlTreeSitterStore(#913)AnnotationStore(#913);Document::analysis(#929)snapshot_before(#913)catalog_base(main)Measured with the new test (release build), the statement caches are almost all of it: about 21 KB per statement with its parse result and tree, against 0.5 MB for the whole analysis of a 5,000-statement file.
Bounds
DOCUMENT_CACHE_ENTRIES_MAX = 20_000cached parse results and trees, about 200 MB at the measured 10 KB per entry. That is enough to keep a few files of several thousand statements fully cached, so switching between them doesn't parse them again. When the open documents keep more, the caches of the least recently used documents are dropped. They are recomputed on the next request for that document, so results stay the same. Dropping is logged at debug level. The document of the current request is never evicted, so a single file larger than the budget stays fully cached while it is in use.check,format) opens each file through aFileGuard, which closes it once the file is processed. At any time it holds at most one file per rayon worker, and nothing is retained after a file. The test checks this path.The mechanism, for the other caches (lane G's walk cache)
server/cache_budget.rshas a small genericCacheBudget<K>, kept separate from the documents:recordupdateskey's usage and last use. It returns the other keys to evict, oldest first, until the total is withinmax, and stops tracking them.Document::clear_caches(), under the documents read lock that the request already holds. The stores use interior mutability, and a request that is iterating an evicted document keeps itsArcs and simply recomputes on a miss.charge_document_caches) at the end of diagnostics, hover, inlay hints, code actions, completions and formatting.A cache with checkpoints (Catalog/Session) can count checkpoints or approximate bytes as its usage and use its own
CacheBudgetwith its own*_MAX.Measurements
tests/memory_bounds.rscounts every allocation with a global allocator. It opens N documents (migration files: a table, an index, a SQL function and a query per group), and on each runs diagnostics, hover and code actions at the end of the file and inlay hints over the last 4 KB. Then it closes them all, and runs the CLI path (open, diagnostics, close) on the same files. Release build, 20 documents × 5,000 statements (9.6 MiB of SQL):The test runs in CI with 20 × 1,000 statements (debug build, 35 s), so the budget evicts there too. It asserts:
DOCUMENT_CACHE_ENTRIES_MAX, plus one, plus the text of the others);MEMORY_DOCUMENTSandMEMORY_GROUPSchange the workload.The CLI's peak is what analysing one 5,000-statement file takes (its parse trees plus the analyser's copies of them). It is unchanged here.
checkruns one file per rayon worker, so its peak is that times the number of workers.Tests
cache_budget::tests::evicts_the_least_recently_used_keys_first: LRU order, the key in use is never evicted,remove.document_caches_stay_within_the_budget: with a small budget, using a second document drops the caches (statements and analysis) of the first. Using the first again recomputes them with the same diagnostics, and closing it removes it from the budget.open_documents_stay_within_the_budget: the measurement above.Not covered