Repository navigation
perf(session): write on lookup only when sliding expiration renews - #279
Merged
Merged
Conversation
get_session() saved the session on every lookup to record last_accessed, so each authenticated request cost a backend write, and one that also modified the session cost two. It now saves only when sliding expiration renewed the session, which is the only lookup that changes the expiry or backend TTL. The keyword-only touch=True keeps the old behaviour for callers that need the stored last_accessed to be exact. Session entries are never compared by fingerprint, so they store the constant "session" instead of a SHA-256 of the payload. Closes #115
This was referenced Sep 27, 2026
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.
Closes #115.
What
SessionManager.get_session()saved the session on every lookup just to recordlast_accessed. Every authenticated request therefore cost a backend write, and a request that also modified the session cost two (the lookup, then the middleware's save).get_session()now saves only when sliding expiration renewed the session. That is the only kind of lookup that changesexpires_at, and so the backend TTL.touch=Truesaves on every lookup, for callers who need the storedlast_accessedto be exact."session"instead of a SHA-256 of the payload. Nothing reads session fingerprints: the monitoring routes skip non-HTTP keys, and sessions don't usedelete_if_equals.These are all unchanged:
EXPIREDand raises);absolute_timeout;regenerate_session_id;clear()/logout logic, rotation detection and saving of modified data.Behaviour change
The
last_accessedon the returned session is still the current time. The storedlast_accessedis now the time of the last write (create, modify, renew, regenerate ortouch=True), not the time of the last lookup. Nothing in the library reads it.A side benefit: a read-only request no longer rewrites the session it loaded at the start. So it can no longer bring back a session that a concurrent request deleted, or overwrite that request's changes with stale data. The same window still exists for requests that modify the session: the middleware saves the copy it loaded. That is out of scope here.
Tests
tests/session/test_lookup_writes.pywrapsMemoryBackendin a spy that records everyset:expires_at;touch=Truemakes 1 write and storeslast_accessed;FastAPICacheXSessionMiddleware, a read-only request makes 0 writes and a modifying request makes exactly 1;SessionMiddlewarelookup makes 0 writes;"session".Mutation check: each change below was applied to
manager.pyand the full suite was run.test_no_renewal_once_expiry_reaches_absolute_timeouttouchuv run pytest -q: 821 passed, 191 skipped. The live Redis/Memcached tests were skipped and are still to be run.pre-commit run --all-filesis clean (theuv-lockhook was skipped because it only bumps unrelated upstream pins;uv lock --checkpasses). Both strict docs builds (EN and zh-TW) pass.CHANGELOG
Changed
SessionManager.get_session()used to save the session on every call to recordlast_accessed, so each authenticated request cost a write (two if it also modified the session). The storedlast_accessedis now updated only when the session is written (created, modified, renewed or regenerated); passget_session(..., touch=True)to save it on every lookup. Session entries also store a constant fingerprint instead of hashing the payload. (Session lookups write to the backend on every request #115)