Repository navigation
perf(dashboard): Read assistant usage fields once per event row - #2650
Merged
Merged
Conversation
Co-Authored-By: BYK <126780+BYK@users.noreply.github.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Contributor
|
4 screenshot changes — 4 changed · 0 added · 0 removed Review screenshots in Frameshift
|
thomasjiangcy
approved these changes
Nov 4, 2026
This was referenced Nov 4, 2026
thomasjiangcy
pushed a commit
that referenced
this pull request
Nov 4, 2026
…ls (#2655) Open detail pages poll `GET /api/conversations/:id`. The old ETag (#1954) hashed the response, so every poll still built the whole report (p50 814ms, p95 1.5s in prod) only to return 304. This compares a cheap version token first and skips the build when nothing changed. Follow-up to #2650. ## Design - New `junior_conversations.detail_version` column (migration 0075). Writes to a table the report reads advance it: - `juniorConversations` writes set `detailVersion: nextDetailVersion` inline. - Event, attachment, annotation, Brief, and archive writes use `db.with(conversationDetailTouch(db, ids))`, a data-modifying CTE that bumps in the same statement. - Purge and attachment GC call `touchConversationDetail(db, ids)` after the write, because their ids come from the write itself. - Participant upserts do not bump. Each caller already bumps the Conversation in the same transaction, and the root sums its subtree. - Pin writes do not bump. A pin is a viewer mark, which the ETag reads on each request. - `readConversationDetailEtag` hashes the subtree `sum`/`count` of `detail_version` with the state that is per viewer or owned by other Conversations: access, viewer marks (archive and pin), read position, forks, agent tree, `limit`, and `JUNIOR_VERSION`. Child agents bump only their own row. A new child changes the count. - The route returns 304 before `readConversationDetail` when `If-None-Match` matches, else 200 with the ETag. - The dashboard content-hash middleware now runs only for mock reporting, which has no stored version. ## Lint rule `junior/touch-conversation-detail` (oxlint JS plugin in `tools/oxlint/junior`, on for `packages/junior/src`) reports: - `insert`/`update`/`delete` on a detail table, or a raw `sql` write to one, that is not built on `.with(conversationDetailTouch(...))` and has no later `touchConversationDetail` in the same function. - A `juniorConversations` update or upsert that does not set `detailVersion`. Plain inserts pass, because the row count covers them. RuleTester cases run in `pnpm oxlint-rules:check`, which is now part of `pnpm lint`. ## Not covered by the version Identity names/avatars, destination names, Automation titles/source task, Slack team domains, and plugin sidebar hook output. These update with the next detail change or a reload. ## Cost The bump rides in the write statement, so it adds no round trip. It costs one primary-key update of the Conversation row in Postgres. `detail_version` has no index, so Postgres can usually update the row in place without touching indexes. Event appends already hold the event lock. <!-- junior-session-footer:start --> <!-- junior-conversation-id:local%3Aweb%3A17b6d7f555623680001f2a90 --> -- [View Session in Sentry](https://sentry.sentry.io/explore/conversations/local%3Aweb%3A17b6d7f555623680001f2a90/?project=4510944073809921) <!-- junior-session-footer:end --> --------- Co-authored-by: sentry-junior[bot] <264270552+sentry-junior[bot]@users.noreply.github.com> Co-authored-by: BYK <126780+BYK@users.noreply.github.com> Co-authored-by: BYK <burak.kaya@sentry.io>
This branch was successfully 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.
Long conversations are slow to open in the dashboard. Each detail poll (every 2–10s) runs the model usage query, and in production it takes up most of the request: for one long web conversation, detail p50 was 814ms and p95 1.5s, and in the slowest trace the usage query ran for 2.8s of a 3.3s request.
The query reads
payload->...about 100 times perassistant_messagerow. Postgres detoasts and decompresses the whole payload on each read, and that payload includes the full reply content. So the cost grows with the size of the conversation, not with the number of usage fields.This change reads
provider,model, andusageonce per row with a lateraljsonb_to_record, and computes all aggregates from the smallusagevalue. The output does not change.Local benchmark on Postgres 17 with 1,500 assistant messages of about 27KB each: 415ms → 37ms. The same rows with tiny payloads ran in 27ms, so the remaining time is mostly the row scan.
via @BYK.
--
View Session in Sentry