Repository navigation
Cut round trips on deck detail, deck update and card upsert - #95
Merged
Merged
Conversation
Deck detail loads cards with joinedload (one query instead of two). Deck update is one UPDATE ... RETURNING instead of advanced-alchemy's update, which read the row twice and refreshed it. Card upsert is one INSERT ... ON CONFLICT (id) DO UPDATE ... RETURNING instead of upsert_many, which selected the rows before and after writing them.
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.
Three endpoints send fewer statements to Postgres.
GET /api/decks/{id}/PUT /api/decks/{id}/UPDATE ... RETURNING, COMMITPUT /api/decks/{id}/cards/INSERT ... ON CONFLICT (id) DO UPDATE ... RETURNING, COMMITadvanced-alchemy's
update(item_id=...)fetches the row in the service, fetches it again in the repository, and refreshes it after the flush.upsert_manyselects the existing rows first and refreshes them afterwards. Both repository methods now build the statement themselves, the wayget_or_createalready did.Numbers
In-process (httpx ASGITransport), sequential requests, best of 9 runs of 300 requests (100 with added latency), Postgres 17 in Docker. "1 ms" adds
tc netem delay 1mson the db container. On this host one round trip then costs about 3.5 to 4 ms, so treat the ratio as what carries over, not the absolute values. Microseconds per request:GET /api/decks/{id}/PUT /api/decks/{id}/PUT /api/decks/{id}/cards/(10 cards)GET /api/cards/{id}/(untouched, control)Concurrent throughput through Granian was too noisy on this machine to report.
Behaviour
PUT /api/decks/{id}/now always writes and movesupdated_at. advanced-alchemy skipped the UPDATE when the values were unchanged.updated_atexplicitly, becauseonupdatedoes not apply toON CONFLICT DO UPDATE. It runs insidewrap_sqlalchemy_exception, so a duplicate front is still aDuplicateKeyError(422). An empty list still returns no cards.Tests
A
statementsfixture records what each request sends on the test connection, without savepoints. Three tests assert one statement for the endpoints above; they failed before the change with 2, 4 and 3. Guard tests, which pass before and after:updated_atmoves on both updates, an empty card list, and a duplicate front. AGENTS.md describes the new write path and drops thefetch_with_cardscaveat.