Skip to content

feat: store session usage per app and family in template usage stats - #29109

Merged
EhabY merged 4 commits into
mainfrom
feat/session-family-usage-rollup
Sep 18, 2026
Merged

EhabY merged 4 commits into
mainfrom
feat/session-family-usage-rollup

Conversation

@EhabY

@EhabY EhabY commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

Store per-app session minutes in template_usage_stats_session_apps, replacing the fixed family-minute columns. A new app no longer requires a rollup schema change, and the API, Prometheus, and telemetry outputs are unchanged.

Reads group app names into families through the registry in codersdk, so the registry never reaches SQL and attribution applies to all history rather than only the buckets the rollup still revisits. GetTemplateInsightsByTemplate, which feeds Prometheus, groups by app for the same reason, so both surfaces report a family identically. A bucket the rollup recomputes rewrites no child row whose minutes are unchanged, which the insert's conflict guard handles on its own.

Semantic change: a family total is the sum of its apps, not the distinct minutes any of them was active, so a minute two apps of one family share counts in both. Reported numbers are unchanged today, because every agent reports one canonical app name per family. The difference appears once #28338 lands the producers and clients send names such as cursor or zed. The apps report already overlaps across rows, and the dashboard builds its percentage denominator from the sum of the rows, so a family row stays consistent with the per-app breakdown it will sit above.

Migration 000596: backfill the family totals the fixed columns recorded, sftp included, under the family name as the app name, which the registry maps back to itself. Workspace web-app usage stays separate. The migration alters template_usage_stats; the exclusive locks the ALTERs acquire last until the migration transaction commits. Downgrading folds app names back into the five fixed columns, counting a name its copy of the registry does not know as ssh, and discards the per-app detail. Already-merged migration 000590 is unchanged.

The commits after the first are the response to review and read on their own; the first is the rest of the work squashed.

Follows merged #28337 and #29134; #28338 enables the producers. Covers the storage portion of CLIENT-642 (#27413). Dynamic external projections (#27411 and the remaining #27413 work) and the connection-log migration (#27412) remain separate.

Design decisions and performance context
  • A child table rather than a family table: the family grouping is a read-time concern, and the per-app rows are what the dashboard breakdown needs anyway. Folding in Go costs nothing on reads, because the query aggregates straight to its output rows instead of adding an intermediate per-bucket aggregate that spills at work_mem=8MB.
  • A child table rather than a jsonb column on template_usage_stats: jsonb is cheaper to write and store but roughly 2.3x the read, and it widens the main row by 22% for every query that scans it.
  • Recomputed buckets and old buckets are attributed the same way, because attribution happens at read time.
  • GetTemplateInsights splits the half-hour cap into the buckets that can reach it and the rest, because the cap applies per user across templates. One grouping sets pass that caps every bucket gives identical output and takes 823 ms against 460 ms.
  • The rollup counts an app's minutes with COUNT(DISTINCT) rather than a minute bitmask, which costs 1.00 s against 0.73 s over 422k agent stats. That is the cold path, reached only when template_usage_stats is empty; a steady-state run covers about two hours of stats, where the difference is around 15 ms.
  • Synthetic fixtures on PostgreSQL 17, ~310k buckets, ~540k app rows over a 30 day window, and 422k agent stats, median of 9 to 11 runs. Comparing the designs: GetTemplateInsights 429 ms with the family table against 444 ms folding in Go, 777 ms folding in SQL, and 1034 ms from a jsonb column; cold rollup 3386 ms against 2687 ms; storage 221 MB against 151 MB. These are fixture measurements, not production guarantees.

Description updated by Coder Agents for @EhabY.

🤖 Generated with Claude Code

@EhabY
EhabY force-pushed the feat/session-family-usage-rollup branch from 83aa8d3 to cd9543f Compare September 9, 2026 14:33
@EhabY
EhabY removed this pull request from stack #29110 September 9, 2026 14:34
@EhabY
EhabY changed the base branch from feat/session-count-family-attribution to feat/session-count-generic-queries September 9, 2026 14:34
@EhabY
EhabY added this pull request to stack #29135 September 9, 2026 14:35
@EhabY
EhabY force-pushed the feat/session-family-usage-rollup branch from cd9543f to bdac30a Compare September 10, 2026 11:27
@EhabY
EhabY force-pushed the feat/session-family-usage-rollup branch from bdac30a to d34d4b6 Compare September 10, 2026 11:54
Base automatically changed from feat/session-count-generic-queries to main September 10, 2026 12:07
@EhabY
EhabY force-pushed the feat/session-family-usage-rollup branch from d34d4b6 to 32b28f7 Compare September 10, 2026 12:07
@EhabY
EhabY marked this pull request as ready for review September 10, 2026 12:12
@EhabY
EhabY requested a review from code-asher September 10, 2026 16:31
@EhabY
EhabY force-pushed the feat/session-family-usage-rollup branch from a16831d to d17e5b7 Compare September 14, 2026 18:10
Comment thread coderd/database/migrations/000595_template_usage_stats_session_usage.up.sql Outdated
@EhabY
EhabY force-pushed the feat/session-family-usage-rollup branch 3 times, most recently from 44025d6 to cea8c2f Compare September 16, 2026 12:42
@EhabY
EhabY requested a review from code-asher September 16, 2026 12:44

@code-asher code-asher left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is mostly me trying to figure out the sql lol

maybe we wanna get one more pair of eyes with more sql experience than me 😆

Comment thread coderd/database/migrations/000596_template_usage_stats_session_usage.down.sql Outdated
Comment thread coderd/database/migrations/000596_template_usage_stats_session_usage.up.sql Outdated
Comment thread coderd/database/queries/insights.sql Outdated
Comment thread coderd/database/queries/insights.sql
Comment thread coderd/database/queries/insights.sql Outdated
Comment thread coderd/database/queries/insights.sql Outdated
Comment thread coderd/database/queries/insights.sql Outdated
Comment thread coderd/database/queries/insights.sql Outdated
Comment thread coderd/database/queries/insights.sql
@EhabY
EhabY force-pushed the feat/session-family-usage-rollup branch from eda10b7 to bd6bc7c Compare September 17, 2026 08:55
@EhabY
EhabY requested a review from code-asher September 17, 2026 13:58
@EhabY
EhabY force-pushed the feat/session-family-usage-rollup branch from 6100fae to 532f05d Compare September 17, 2026 14:16

@code-asher code-asher left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sweet! Thanks for bearing with me on the sql

Replace the fixed family-minute columns with per-app and per-family
child tables, and skip the child writes for buckets whose session usage
digest is unchanged.
Drop template_usage_stats_session_families and fold app names into
families in Go, so the registry no longer reaches SQL and attribution
applies to every bucket rather than only the ones the rollup revisits.
The child writes cover every bucket the rollup recomputed instead of only
the ones the main upsert returned, so nothing needs the digest to notice a
session-only change, and the conflict guards already leave an unchanged row
alone. Also counts an app's minutes with COUNT(DISTINCT), folds an
unregistered app name into ssh on downgrade, trims the insights queries, and
corrects the NOTE about missing connection counts.
Keep the child table window sargable so a dashboard-sized range uses an
index scan instead of scanning all history, and let COUNT(DISTINCT) dedupe
minutes in the grouping rather than sorting the expanded rows, which spilled
to disk past a few thousand agents.

Also type session_app_usage_seconds via a sqlc column override, which drops
the decode and its error path from the collector.
@EhabY
EhabY force-pushed the feat/session-family-usage-rollup branch from 532f05d to 48e7514 Compare September 18, 2026 12:56
@EhabY
EhabY merged commit 817fc77 into main Sep 18, 2026
48 of 51 checks passed
@EhabY
EhabY deleted the feat/session-family-usage-rollup branch September 18, 2026 13:26
@github-actions github-actions Bot locked and limited conversation to collaborators Sep 18, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants