Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit0bf08af

Browse files
committed
modify workspace caching, significantly reduce calls to
GetWorkspaceByAgentID for agent metadata updatesSigned-off-by: Callum Styan <callumstyan@gmail.com>
1 parent77f9c6a commit0bf08af

File tree

6 files changed

+767
-29
lines changed

6 files changed

+767
-29
lines changed

‎coderd/agentapi/api.go‎

Lines changed: 121 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package agentapi
22

33
import (
44
"context"
5+
"database/sql"
56
"io"
67
"net"
78
"net/url"
@@ -21,6 +22,7 @@ import (
2122
"github.com/coder/coder/v2/coderd/appearance"
2223
"github.com/coder/coder/v2/coderd/connectionlog"
2324
"github.com/coder/coder/v2/coderd/database"
25+
"github.com/coder/coder/v2/coderd/database/dbauthz"
2426
"github.com/coder/coder/v2/coderd/database/pubsub"
2527
"github.com/coder/coder/v2/coderd/externalauth"
2628
"github.com/coder/coder/v2/coderd/notifications"
@@ -36,6 +38,35 @@ import (
3638
"github.com/coder/quartz"
3739
)
3840

41+
constworkspaceCacheRefreshInterval=5*time.Minute
42+
43+
// CachedWorkspaceFields contains workspace data that is safe to cache for the
44+
// duration of an agent connection. These fields are used to reduce database calls
45+
// in high-frequency operations like stats reporting and metadata updates.
46+
//
47+
// IMPORTANT: ACL fields (GroupACL, UserACL) are NOT cached because they can be
48+
// modified in the database and we must use fresh data for authorization checks.
49+
//
50+
// Prebuild Safety: When a prebuild is claimed, the owner_id changes in the database
51+
// but the agent connection persists. Currently we handle this by periodically refreshing
52+
// the cached fields (every 5 minutes) to pick up changes like prebuild claims.
53+
typeCachedWorkspaceFieldsstruct {
54+
// Identity fields
55+
ID uuid.UUID
56+
OwnerID uuid.UUID
57+
OrganizationID uuid.UUID
58+
TemplateID uuid.UUID
59+
60+
// Display fields for logging/metrics
61+
Namestring
62+
OwnerUsernamestring
63+
TemplateNamestring
64+
65+
// Lifecycle fields needed for stats reporting
66+
AutostartSchedule sql.NullString
67+
DormantAt sql.NullTime
68+
}
69+
3970
// API implements the DRPC agent API interface from agent/proto. This struct is
4071
// instantiated once per agent connection and kept alive for the duration of the
4172
// session.
@@ -54,7 +85,7 @@ type API struct {
5485
*SubAgentAPI
5586
*tailnet.DRPCService
5687

57-
cachedWorkspace database.Workspace
88+
cachedWorkspaceFieldsCachedWorkspaceFields
5889

5990
mu sync.Mutex
6091
}
@@ -100,9 +131,25 @@ func New(opts Options, workspace database.Workspace) *API {
100131
}
101132

102133
api:=&API{
103-
opts:opts,
104-
cachedWorkspace:workspace,
105-
mu: sync.Mutex{},
134+
opts:opts,
135+
cachedWorkspaceFields:CachedWorkspaceFields{
136+
ID:workspace.ID,
137+
OwnerID:workspace.OwnerID,
138+
OrganizationID:workspace.OrganizationID,
139+
TemplateID:workspace.TemplateID,
140+
Name:workspace.Name,
141+
OwnerUsername:workspace.OwnerUsername,
142+
TemplateName:workspace.TemplateName,
143+
AutostartSchedule: sql.NullString{
144+
String:workspace.AutostartSchedule.String,
145+
Valid:workspace.AutostartSchedule.Valid,
146+
},
147+
DormantAt: sql.NullTime{
148+
Time:workspace.DormantAt.Time,
149+
Valid:workspace.DormantAt.Valid,
150+
},
151+
},
152+
mu: sync.Mutex{},
106153
}
107154

108155
api.ManifestAPI=&ManifestAPI{
@@ -166,10 +213,11 @@ func New(opts Options, workspace database.Workspace) *API {
166213
}
167214

168215
api.MetadataAPI=&MetadataAPI{
169-
AgentFn:api.agent,
170-
Database:opts.Database,
171-
Pubsub:opts.Pubsub,
172-
Log:opts.Log,
216+
AgentFn:api.agent,
217+
RBACContextFn:api.rbacContext,
218+
Database:opts.Database,
219+
Pubsub:opts.Pubsub,
220+
Log:opts.Log,
173221
}
174222

175223
api.LogsAPI=&LogsAPI{
@@ -209,6 +257,10 @@ func New(opts Options, workspace database.Workspace) *API {
209257
Database:opts.Database,
210258
}
211259

260+
// Start background cache refresh loop to handle workspace changes
261+
// like prebuild claims where owner_id and other fields may be modified in the DB.
262+
goapi.startCacheRefreshLoop(opts.Ctx)
263+
212264
returnapi
213265
}
214266

@@ -258,8 +310,67 @@ func (a *API) agent(ctx context.Context) (database.WorkspaceAgent, error) {
258310
returnagent,nil
259311
}
260312

261-
func (a*API)workspace() (database.Workspace,error) {
262-
returna.cachedWorkspace,nil
313+
func (a*API)workspace() (database.Workspace) {
314+
a.mu.Lock()
315+
defera.mu.Unlock()
316+
317+
return database.Workspace{
318+
ID:a.cachedWorkspaceFields.ID,
319+
OwnerID:a.cachedWorkspaceFields.OwnerID,
320+
OrganizationID:a.cachedWorkspaceFields.OrganizationID,
321+
TemplateID:a.cachedWorkspaceFields.TemplateID,
322+
Name:a.cachedWorkspaceFields.Name,
323+
OwnerUsername:a.cachedWorkspaceFields.OwnerUsername,
324+
TemplateName:a.cachedWorkspaceFields.TemplateName,
325+
AutostartSchedule:a.cachedWorkspaceFields.AutostartSchedule,
326+
DormantAt:a.cachedWorkspaceFields.DormantAt,
327+
}
328+
}
329+
330+
func (a*API)rbacContext(ctx context.Context) context.Context {
331+
workspace:=a.workspace()
332+
returndbauthz.WithWorkspaceRBAC(ctx,workspace.RBACObject())
333+
}
334+
335+
// refreshCachedWorkspace periodically updates the cached workspace fields.
336+
// This ensures that changes like prebuild claims (which modify owner_id, name, etc.)
337+
// are eventually reflected in the cache without requiring agent reconnection.
338+
func (a*API)refreshCachedWorkspace(ctx context.Context) {
339+
a.mu.Lock()
340+
defera.mu.Unlock()
341+
342+
ws,err:=a.opts.Database.GetWorkspaceByID(ctx,a.cachedWorkspaceFields.ID)
343+
iferr!=nil {
344+
a.opts.Log.Warn(ctx,"failed to refresh cached workspace fields",slog.Error(err))
345+
return
346+
}
347+
348+
// Update fields that can change during workspace lifecycle (e.g., prebuild claim)
349+
a.cachedWorkspaceFields.OwnerID=ws.OwnerID
350+
a.cachedWorkspaceFields.Name=ws.Name
351+
a.cachedWorkspaceFields.OwnerUsername=ws.OwnerUsername
352+
a.cachedWorkspaceFields.AutostartSchedule=ws.AutostartSchedule
353+
a.cachedWorkspaceFields.DormantAt=ws.DormantAt
354+
355+
a.opts.Log.Debug(ctx,"refreshed cached workspace fields",
356+
slog.F("workspace_id",ws.ID),
357+
slog.F("owner_id",ws.OwnerID),
358+
slog.F("name",ws.Name))
359+
}
360+
361+
// startCacheRefreshLoop runs a background goroutine that periodically refreshes
362+
// the cached workspace fields. This is primarily needed to handle prebuild claims
363+
// where the owner_id and other fields change while the agent connection persists.
364+
func (a*API)startCacheRefreshLoop(ctx context.Context) {
365+
// Refresh every 5 minutes. This provides a reasonable balance between:
366+
// - Keeping cache fresh for prebuild claims and other workspace updates
367+
// - Minimizing unnecessary database queries
368+
_=a.opts.Clock.TickerFunc(ctx,workspaceCacheRefreshInterval,func()error {
369+
a.refreshCachedWorkspace(ctx)
370+
returnnil
371+
},"cache_refresh")
372+
373+
<-ctx.Done()
263374
}
264375

265376
func (a*API)publishWorkspaceUpdate(ctx context.Context,agent*database.WorkspaceAgent,kind wspubsub.WorkspaceEventKind)error {

‎coderd/agentapi/metadata.go‎

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,11 @@ import (
1717
)
1818

1919
typeMetadataAPIstruct {
20-
AgentFnfunc(context.Context) (database.WorkspaceAgent,error)
21-
Database database.Store
22-
Pubsub pubsub.Pubsub
23-
Log slog.Logger
20+
AgentFnfunc(context.Context) (database.WorkspaceAgent,error)
21+
RBACContextFnfunc(context.Context) context.Context
22+
Database database.Store
23+
Pubsub pubsub.Pubsub
24+
Log slog.Logger
2425

2526
TimeNowFnfunc() time.Time// defaults to dbtime.Now()
2627
}
@@ -107,6 +108,9 @@ func (a *MetadataAPI) BatchUpdateMetadata(ctx context.Context, req *agentproto.B
107108
)
108109
}
109110

111+
// Inject RBAC object into context for dbauthz fast path, avoid having to
112+
// call GetWorkspaceByAgentID on every metadata update.
113+
ctx=a.RBACContextFn(ctx)
110114
err=a.Database.UpdateWorkspaceAgentMetadata(ctx,dbUpdate)
111115
iferr!=nil {
112116
returnnil,xerrors.Errorf("update workspace agent metadata in database: %w",err)

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp