refactor(coderd): add related-data selection to workspace queries - #28302
Merged
Conversation
cstyan
approved these changes
Aug 19, 2026
cstyan
left a comment
Contributor
There was a problem hiding this comment.
👍 nice and clean, easy to follow the code (especially with the detailed PR description)
spikecurtis
force-pushed
the
spikecurtis/workspace-related-data
branch
from
August 20, 2026 08:17
a15ed03 to
01ea8a1
Compare
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 subscribe to this conversation on GitHub.
Already have an account?
Sign in.
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.
This is the first of three PRs implementing lite
codersdk.Workspaceresponses (GRU-82, RFC). A fully populated workspace fans out into a large tree of DB queries (template, latest build, provisioner job + queue position, resources, metadata, agents, apps, statuses, scripts, log sources, template version). Most callers do not need most of that data, and one of these queries,GetProvisionerJobsByIDsWithQueuePosition, is consistently the top resource consumer in scale tests.This PR adds the internal plumbing to load only a selected subset of that tree. It introduces a
workspaceRelatedselection type and threads it throughworkspaceDataandworkspaceBuildsDatadown to the individual query call sites, skipping any query whose data was not selected.The selection is a pointer tree that mirrors the parent/child relationships between the objects: branch nodes are pointers (non-nil when selected) and leaf nodes are bools. Modeling it this way makes selecting a child without its parent unrepresentable, which matches how loading works (a parent must be queried to learn its children's identifiers), so there is no normalization step to forget.
No behavior change: every existing caller passes a fully populated selection (
allWorkspaceRelated/allLatestBuildRelated), so responses are identical. Wiring the HTTPinclude_relatedquery parameter (Phase 2) and narrowing individual callers (Phase 3) come in follow-up PRs.Two notes on the current mapping:
queue_positionis modeled as its own node, but only one job query exists and it always computes the ranking, so selectingjobruns the full query today. Splitting off a cheaper queue-position-free job query is deferred (it needs a new SQL query +make gen).LatestAppStatusis gated on thelatest_build.resources.agents.apps.statusesnode. It is queried directly by workspace ID, but a caller selecting it also pulls in the build chain as ancestors. Happy to decouple this if preferred.Implementation plan
Lite Workspace API — Phase 1 Plan
Add an internal configuration structure to the Coderd method that fetches a
workspace with its related data (
workspaceData/workspaceBuildsData), threadit down to the SQL query call sites, and skip ("squash") queries that are not
requested. Server-internal only. No HTTP query-string parsing (Phase 2) and no
wsrelatedstruct tags or field nil-ing (Phase 3).Scope
workspaceDataandworkspaceBuildsData; gating every related-data query;comprehensive unit tests with a mocked
database.Store(gomock/dbmock).include_relatedparsing, codersdk changes,wsrelatedtags, docsautogen, and changing
convertWorkspacesskip-on-missing-data behavior. Allexisting callers pass an "include all" config, so Phase 1 is a behavior-
preserving refactor.
Design
New file
coderd/wsrelateddata.gowith a pointer tree that mirrors the RFChierarchy. Branch nodes are pointers (present when non-nil); leaf nodes are
bools. Because a child cannot be expressed without its parent, the RFC
"includes all ancestors" rule is a structural invariant of the type: an illegal
selection is unrepresentable, so no normalization pass exists or is needed.
Helpers:
allWorkspaceRelated()/allLatestBuildRelated()construct a fullypopulated tree. Passed by all current callers so behavior is unchanged.
(*latestBuildRelated).appStatuses()is a nil-safe accessor for the deepapps.statusesleaf so call sites descend the tree without a guard chain.Rationale for the pointer tree over a flat struct plus
withAncestors(): theinvariant holds by construction (illegal state is unrepresentable) instead of
relying on a normalization step a caller must remember.
workspaceBuildsDatatakes the
*latestBuildRelatedsubtree directly.Query gating
workspaceData(ctx, workspaces, cfg):GetTemplatesWithFiltergated onTemplate.GetLatestWorkspaceBuildsByWorkspaceIDsgated onLatestBuild; when off, skipworkspaceBuildsDataentirely (empty builds).GetLatestWorkspaceAppStatusesByWorkspaceIDs(workspace-level LatestAppStatus)gated on
AppStatuses.workspaceBuildsData(ctx, builds, cfg)gates each stage:GetProvisionerJobsByIDsWithQueuePosition) + eligible provisionerdaemons gated on
Job. Only one job query exists and it always computes thequeue position, so
QueuePositiondoes not gate a separate query yet; it ismodeled for hierarchy/Phase 2 completeness. A cheaper job query without the
window-function ranking is a worthwhile follow-up but is out of scope for a
simple Phase 1 (needs new SQL + sqlc gen).
Resources; metadata onMetadata; agents onAgents;apps on
Apps; app statuses onAppStatuses; scripts onScripts; logsources on
LogSources; template versions onTemplateVersion.Downstream
convertWorkspaceBuildsalready tolerates empty slices, so squashingqueries yields zero/nil fields without extra work.
Testing (comprehensive, mocked database.Store)
Internal tests (
coderd/wsrelateddata_internal_test.go) build&API{Options: &Options{Database: dbmock.NewMockStore(ctrl)}}and callworkspaceDatadirectly. gomock is strict, so setting EXPECT only on permittedqueries proves the rest are squashed.
Cases: include-all (every query runs); zero value (no related queries run);
each single node (template only, latest_build only, job, resources, metadata,
agents, apps, statuses, scripts, log_sources, template_version, workspace-level
app statuses); the empty-resources short-circuit; and the constructors plus the
nil-safe
appStatuses()accessor at every depth.Steps
coderd/wsrelateddata.go(selection tree, constructors,appStatuses).workspaceDataandworkspaceBuildsData; gatequeries by descending the tree.
allWorkspaceRelated()/allLatestBuildRelated().make fmt,make lint, targetedgo test.Resolved decisions
structural invariant.
Generated by Coder Agents on behalf of @spikecurtis.