Repository navigation
Make Cache.set faster by running fewer statements and less Python per write - #37
Merged
Merged
Conversation
…et and skip the expired search when nothing has expired.
…lass, which costs less per write.
…2% write gain does not justify replacing it.
Merged
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.
Two changes that cut the SQL run on every write. Each one stands on its own and has its own commit.
Cache.volume()in one statement. Each set callsvolume()to check the size limit, and it ran two statements:PRAGMA page_countand a read of the stored size. It now reads both in one statement throughpragma_page_count(). It falls back to the old two statements on SQLite older than 3.16, and on a read-only snapshot without asizerow.volume()still updatescache.size, whichcheck()reads.Cache.set. The query that finds the existing row for the key now also returns the earliest expire time in the cache._cullskips its search for expired items when nothing can have expired yet, which includes a row written already expired. When something has expired, culling works exactly as before. Eviction timing doesn't change, and the check stays correct across processes because it reads inside the same transaction.A third change, replacing the
contextlibgenerator behindCache._transactwith a class, was measured at about 2% per write and then reverted: the gain doesn't justify replacing that code. There's aCHANGELOG.mdentry underUnreleased.Measured with interleaved runs: each build in its own venv on the Python 3.14.6 GIL build, SQLite 3.53.1, Apple M5 Pro, 20 rounds in random order. Medians are against PyPI diskcache 5.6.3; master was run twice to show the noise.
expire=300The combined row was measured with the transaction class, which saved about 2 points of it, so this branch should land about 2 points short of that row; it wasn't re-measured after the revert.
Cache.getis unchanged.Limits of these numbers. The lookup change saves the expired-items search only on sets where nothing in the cache has expired. In these benchmarks nothing expired.
getmisses on an expired key and the same key issetagain.volume()change. The Django figures above are the best case.Possible follow-ups, not in this PR:
DELETE … RETURNINGto remove expired items in one statementChecked:
volume()fallbacksbluecheck,flake8,isortcheck,mypyandpylintpass.