-
Notifications
You must be signed in to change notification settings - Fork 1.1k
perf: reduce DB calls to GetWorkspaceByAgentID via caching workspace info
#20662
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
26 commits
Select commit
Hold shift + click to select a range
77f9c6a
remove unnecessary calls to GetWorkspaceAgentByID for stats api calls
cstyan 0bf08af
modify workspace caching, significantly reduce calls to
cstyan f447927
Add an IsEmpty check for rbac object.
cstyan 9561447
don't need to wait on ctx after tickerfunc, plus log when we've exited
cstyan c5f0c60
only wrap context for the one database call
cstyan f77df04
formatting
cstyan c39830b
guard against the rbac object being invalid when adding it to the
cstyan 8ae5993
lint
cstyan e99a8c5
fix tests never finishing; we need to wait on the tickerFunc finishing
cstyan b8d3604
don't cache prebuilds
cstyan 31432ed
use the opts workspaceID in refreshCachedWorkspace now that we don't
cstyan 40e423a
these shouldn't have been removed
cstyan dac1117
lint and fmt + move workspace rbac context stuff to a separate file
cstyan bdf076a
store pointer to cached workspace instead of functions
cstyan a77b310
we need to cache the AutostartSchedule for the stats api
cstyan 02d84bd
move cached workspace struct to its own file, include mutex on the
cstyan afe37c9
make fmt and linter happy
cstyan 15081e8
make cached workspace a pointer on the API struct
cstyan 089a5ef
clear the cached workspace on DB error
cstyan a2ee19d
address more review feedback; attempt to simplify cached workspace is
cstyan 1d48500
remove IsEmpty from rbac object, we'd need to handle too many rbac
cstyan 96eb4c0
typo in function name
cstyan 6811090
refactor so that functions which will now make use of the cached
cstyan 81bc27a
fmt and lint again
cstyan 8304864
cleanup/refactor tests, we no longer cache prebuilds but still want to
cstyan 740348c
reduce logging for expected paths
cstyan File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
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
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,52 @@ | ||
| package agentapi | ||
|
|
||
| import ( | ||
| "sync" | ||
|
|
||
| "github.com/coder/coder/v2/coderd/database" | ||
| ) | ||
|
|
||
| // CachedWorkspaceFields contains workspace data that is safe to cache for the | ||
| // duration of an agent connection. These fields are used to reduce database calls | ||
| // in high-frequency operations like stats reporting and metadata updates. | ||
| // Prebuild workspaces should not be cached using this struct within the API struct, | ||
| // however some of these fields for a workspace can be updated live so there is a | ||
| // routine in the API for refreshing the workspace on a timed interval. | ||
| // | ||
| // IMPORTANT: ACL fields (GroupACL, UserACL) are NOT cached because they can be | ||
| // modified in the database and we must use fresh data for authorization checks. | ||
| type CachedWorkspaceFields struct { | ||
| lock sync.RWMutex | ||
|
|
||
| identity database.WorkspaceIdentity | ||
| } | ||
|
|
||
| func (cws *CachedWorkspaceFields) Clear() { | ||
| cws.lock.Lock() | ||
| defer cws.lock.Unlock() | ||
| cws.identity = database.WorkspaceIdentity{} | ||
| } | ||
|
|
||
| func (cws *CachedWorkspaceFields) UpdateValues(ws database.Workspace) { | ||
| cws.lock.Lock() | ||
| defer cws.lock.Unlock() | ||
| cws.identity.ID = ws.ID | ||
| cws.identity.OwnerID = ws.OwnerID | ||
| cws.identity.OrganizationID = ws.OrganizationID | ||
| cws.identity.TemplateID = ws.TemplateID | ||
| cws.identity.Name = ws.Name | ||
| cws.identity.OwnerUsername = ws.OwnerUsername | ||
| cws.identity.TemplateName = ws.TemplateName | ||
| cws.identity.AutostartSchedule = ws.AutostartSchedule | ||
| } | ||
|
|
||
| // Returns the Workspace, true, unless the workspace has not been cached (nuked or was a prebuild). | ||
| func (cws *CachedWorkspaceFields) AsWorkspaceIdentity() (database.WorkspaceIdentity, bool) { | ||
| cws.lock.RLock() | ||
| defer cws.lock.RUnlock() | ||
| // Should we be more explicit about all fields being set to be valid? | ||
| if cws.identity.Equal(database.WorkspaceIdentity{}) { | ||
| return database.WorkspaceIdentity{}, false | ||
| } | ||
| return cws.identity, true | ||
| } |
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,97 @@ | ||
| package agentapi_test | ||
|
|
||
| import ( | ||
| "testing" | ||
|
|
||
| "github.com/google/uuid" | ||
| "github.com/stretchr/testify/require" | ||
|
|
||
| "github.com/coder/coder/v2/coderd/agentapi" | ||
| "github.com/coder/coder/v2/coderd/database" | ||
| ) | ||
|
|
||
| func TestCacheClear(t *testing.T) { | ||
| t.Parallel() | ||
|
|
||
| var ( | ||
| user = database.User{ | ||
| ID: uuid.New(), | ||
| Username: "bill", | ||
| } | ||
| template = database.Template{ | ||
| ID: uuid.New(), | ||
| Name: "tpl", | ||
| } | ||
| workspace = database.Workspace{ | ||
| ID: uuid.New(), | ||
| OwnerID: user.ID, | ||
| OwnerUsername: user.Username, | ||
| TemplateID: template.ID, | ||
| Name: "xyz", | ||
| TemplateName: template.Name, | ||
| } | ||
| workspaceAsCacheFields = agentapi.CachedWorkspaceFields{} | ||
| ) | ||
|
|
||
| workspaceAsCacheFields.UpdateValues(database.Workspace{ | ||
| ID: workspace.ID, | ||
| OwnerID: workspace.OwnerID, | ||
| OwnerUsername: workspace.OwnerUsername, | ||
| TemplateID: workspace.TemplateID, | ||
| Name: workspace.Name, | ||
| TemplateName: workspace.TemplateName, | ||
| AutostartSchedule: workspace.AutostartSchedule, | ||
| }, | ||
| ) | ||
|
|
||
| emptyCws := agentapi.CachedWorkspaceFields{} | ||
| workspaceAsCacheFields.Clear() | ||
| wsi, ok := workspaceAsCacheFields.AsWorkspaceIdentity() | ||
| require.False(t, ok) | ||
| ecwsi, ok := emptyCws.AsWorkspaceIdentity() | ||
| require.False(t, ok) | ||
| require.True(t, ecwsi.Equal(wsi)) | ||
| } | ||
|
|
||
| func TestCacheUpdate(t *testing.T) { | ||
| t.Parallel() | ||
|
|
||
| var ( | ||
| user = database.User{ | ||
| ID: uuid.New(), | ||
| Username: "bill", | ||
| } | ||
| template = database.Template{ | ||
| ID: uuid.New(), | ||
| Name: "tpl", | ||
| } | ||
| workspace = database.Workspace{ | ||
| ID: uuid.New(), | ||
| OwnerID: user.ID, | ||
| OwnerUsername: user.Username, | ||
| TemplateID: template.ID, | ||
| Name: "xyz", | ||
| TemplateName: template.Name, | ||
| } | ||
| workspaceAsCacheFields = agentapi.CachedWorkspaceFields{} | ||
| ) | ||
|
|
||
| workspaceAsCacheFields.UpdateValues(database.Workspace{ | ||
| ID: workspace.ID, | ||
| OwnerID: workspace.OwnerID, | ||
| OwnerUsername: workspace.OwnerUsername, | ||
| TemplateID: workspace.TemplateID, | ||
| Name: workspace.Name, | ||
| TemplateName: workspace.TemplateName, | ||
| AutostartSchedule: workspace.AutostartSchedule, | ||
| }, | ||
| ) | ||
|
|
||
| cws := agentapi.CachedWorkspaceFields{} | ||
| cws.UpdateValues(workspace) | ||
| wsi, ok := workspaceAsCacheFields.AsWorkspaceIdentity() | ||
| require.True(t, ok) | ||
| cwsi, ok := cws.AsWorkspaceIdentity() | ||
| require.True(t, ok) | ||
| require.True(t, wsi.Equal(cwsi)) | ||
| } |
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
Oops, something went wrong.
Oops, something went wrong.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we only log if the update yields a different workspace then what we had?
Not a huge deal as the logs are infrequent. Just trying to keep the logs down.