Repository navigation
Conversation
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.
Every authenticated request checks its token against
revoked_tokensbefore the handler runs. Until now that check ran inside an ordinary transaction, so it sent BEGIN, the SELECT and COMMIT: three round trips to Postgres for a single-row lookup. This PR runs the check withisolation_level="AUTOCOMMIT", which SQLAlchemy's asyncpg adapter turns into no BEGIN and no COMMIT. The check now costs one round trip.Change
app/database/resources.py:create_autocommit_transactionreturns a db-retryTransactionwith AUTOCOMMIT when the session is bound to an engine and has not started a transaction yet. Otherwise it returns a plainTransaction. SQLAlchemy only applies an isolation level before the session holds a connection. Test sessions are bound to a connection that is already inside the outer test transaction, so they keep the savepoint behaviour they have today.app/ioc.py: a newDatabase.autocommit_transactionprovider (bound_type=None, so the type-based wiring ofTransactionelsewhere is unchanged), wired intocheck_token_revoked_use_caseonly.tests/use_cases/test_check_token_revoked.py: runs the check against a real engine with an asyncpg query logger on the pooled connection and asserts no BEGIN, COMMIT or ROLLBACK is sent. It fails on main with['BEGIN;', 'COMMIT;'].postgres_retrystill wraps the check. The existing connection-loss tests for it pass, including the case where the pooled connection died while idle.Why not one checkout per request
The original idea was to let the revocation check share the handler's request container so the request takes one pool checkout instead of two. That doesn't work, for two reasons:
di_containerLitestar dependency, which resolves only when the handler runs. The JWT auth middleware callsrevoked_token_handlerbefore that.Transaction.__aexit__callssession.close(), which returns the connection to the pool. Checkouts follow transactions, so two use cases in one request take two checkouts even with a shared session.One checkout would need the revocation read inside every use case's transaction, or no database read for revocation at all (a cache, which would delay logout taking effect). Removing the transaction round trips keeps the current semantics.
Measurements
The app, Postgres 17 and an httpx load generator ran as containers on one Docker network (Apple M2, Docker Desktop). Pool settings were the defaults (
pool_size=5, pre-ping off). Each run had a 5 s warm-up and a 10 s window. Main and branch alternated in each round, with the API restarted at each switch.Local network, 3 rounds each:
GET /api/auth/me/GET /api/auth/me/GET /api/chats/{id}/messages/GET /api/chats/{id}/messages/On a local network a round trip costs about 20 µs, so the gain is small. The
meresults are within run-to-run noise. The message list improves in every round, by 8% at 1 client and 17% at 20.With
tc qdisc add dev eth0 root netem delay 1msin the db container (measuredSELECT 1round trip: 1.25 ms min, 3.0 ms p50), 2 rounds each:GET /api/auth/me/GET /api/auth/me/GET /api/chats/{id}/messages/GET /api/chats/{id}/messages/At 1 client, p50 drops by 7 to 8 ms, about two round trips at that latency. At 20 clients throughput rises 68 to 75%, because each request holds a connection from the five-connection pool for less time.
Tests
just test: 142 passed, coverage 100%.just lintis clean.