Skip to content

Commit de6e6af

Browse files
feat: add caching to reduce db calls
1 parent fb857e0 commit de6e6af

File tree

3 files changed

+147
-4
lines changed

3 files changed

+147
-4
lines changed

coderd/autobuild/cache.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package autobuild
2+
3+
import (
4+
"errors"
5+
"sync"
6+
)
7+
8+
var errCacheLoaderNil = errors.New("loader is nil")
9+
10+
type cacheOf[K comparable, V any] struct {
11+
mu sync.Mutex
12+
m map[K]V
13+
}
14+
15+
func newCacheOf[K comparable, V any]() *cacheOf[K, V] {
16+
return &cacheOf[K, V]{
17+
m: make(map[K]V),
18+
}
19+
}
20+
21+
func (c *cacheOf[K, V]) LoadOrStore(key K, loader func() (V, error)) (V, error) {
22+
c.mu.Lock()
23+
defer c.mu.Unlock()
24+
25+
value, found := c.m[key]
26+
if !found {
27+
if loader == nil {
28+
return *new(V), errCacheLoaderNil
29+
}
30+
31+
loaded, err := loader()
32+
if err != nil {
33+
return *new(V), err
34+
}
35+
36+
c.m[key] = loaded
37+
return loaded, nil
38+
}
39+
40+
return value, nil
41+
}

coderd/autobuild/cache_test.go

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
package autobuild
2+
3+
import (
4+
"errors"
5+
"testing"
6+
7+
"github.com/google/uuid"
8+
"github.com/stretchr/testify/require"
9+
)
10+
11+
func TestCache(t *testing.T) {
12+
t.Parallel()
13+
14+
t.Run("CallsLoaderOnce", func(t *testing.T) {
15+
callCount := 0
16+
cache := newCacheOf[uuid.UUID, int]()
17+
key := uuid.New()
18+
19+
// Call LoadOrStore for key `key` for the first time.
20+
// We expect this to call our loader function.
21+
value, err := cache.LoadOrStore(key, func() (int, error) {
22+
callCount += 1
23+
return 1, nil
24+
})
25+
require.NoError(t, err)
26+
require.Equal(t, 1, value)
27+
require.Equal(t, 1, callCount)
28+
29+
// Call LoadOrStore for key `key` for the second time.
30+
// We expect this to return data from the previous load.
31+
value, err = cache.LoadOrStore(key, func() (int, error) {
32+
callCount += 1
33+
34+
// We return a different value to further check
35+
// that this function isn't called.
36+
return 2, nil
37+
})
38+
require.NoError(t, err)
39+
require.Equal(t, 1, value)
40+
require.Equal(t, 1, callCount)
41+
})
42+
43+
t.Run("ReturnsErrOnLoaderErr", func(t *testing.T) {
44+
exampleErr := errors.New("example error")
45+
cache := newCacheOf[uuid.UUID, int]()
46+
key := uuid.New()
47+
48+
_, err := cache.LoadOrStore(key, func() (int, error) {
49+
return 0, exampleErr
50+
})
51+
require.Error(t, err)
52+
require.Equal(t, exampleErr, err)
53+
})
54+
55+
t.Run("CanCacheWithMultipleKeys", func(t *testing.T) {
56+
cache := newCacheOf[uuid.UUID, int]()
57+
keyA := uuid.New()
58+
keyB := uuid.New()
59+
60+
// We first insert data with our first key
61+
value, err := cache.LoadOrStore(keyA, func() (int, error) {
62+
return 10, nil
63+
})
64+
require.NoError(t, err)
65+
require.Equal(t, 10, value)
66+
67+
// Next we insert data with a different key
68+
value, err = cache.LoadOrStore(keyB, func() (int, error) {
69+
return 20, nil
70+
})
71+
require.NoError(t, err)
72+
require.Equal(t, 20, value)
73+
74+
// Now we check the data is still available for the first key
75+
value, err = cache.LoadOrStore(keyA, nil)
76+
require.NoError(t, err)
77+
require.Equal(t, 10, value)
78+
79+
// And that the data is also still available for the second key
80+
value, err = cache.LoadOrStore(keyB, nil)
81+
require.NoError(t, err)
82+
require.Equal(t, 20, value)
83+
})
84+
}

coderd/autobuild/lifecycle_executor.go

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,16 @@ func (e *Executor) runOnce(t time.Time) Stats {
154154
// Limit the concurrency to avoid overloading the database.
155155
eg.SetLimit(10)
156156

157+
// We cache these values to help reduce load on the database.
158+
// These could be outdated during our execution, but this is
159+
// unlikely to be noticed or cause any unwanted behaviour.
160+
var (
161+
userCache = newCacheOf[uuid.UUID, database.User]()
162+
templateCache = newCacheOf[uuid.UUID, database.Template]()
163+
templateVersionCache = newCacheOf[uuid.UUID, database.TemplateVersion]()
164+
templateScheduleCache = newCacheOf[uuid.UUID, schedule.TemplateScheduleOptions]()
165+
)
166+
157167
for _, ws := range workspaces {
158168
wsID := ws.ID
159169
wsName := ws.Name
@@ -184,7 +194,9 @@ func (e *Executor) runOnce(t time.Time) Stats {
184194
return xerrors.Errorf("get workspace by id: %w", err)
185195
}
186196

187-
user, err := tx.GetUserByID(e.ctx, ws.OwnerID)
197+
user, err := userCache.LoadOrStore(ws.OwnerID, func() (database.User, error) {
198+
return tx.GetUserByID(e.ctx, ws.OwnerID)
199+
})
188200
if err != nil {
189201
return xerrors.Errorf("get user by id: %w", err)
190202
}
@@ -200,17 +212,23 @@ func (e *Executor) runOnce(t time.Time) Stats {
200212
return xerrors.Errorf("get latest provisioner job: %w", err)
201213
}
202214

203-
templateSchedule, err := (*(e.templateScheduleStore.Load())).Get(e.ctx, tx, ws.TemplateID)
215+
templateSchedule, err := templateScheduleCache.LoadOrStore(ws.TemplateID, func() (schedule.TemplateScheduleOptions, error) {
216+
return (*(e.templateScheduleStore.Load())).Get(e.ctx, tx, ws.TemplateID)
217+
})
204218
if err != nil {
205219
return xerrors.Errorf("get template scheduling options: %w", err)
206220
}
207221

208-
tmpl, err = tx.GetTemplateByID(e.ctx, ws.TemplateID)
222+
tmpl, err := templateCache.LoadOrStore(ws.TemplateID, func() (database.Template, error) {
223+
return tx.GetTemplateByID(e.ctx, ws.TemplateID)
224+
})
209225
if err != nil {
210226
return xerrors.Errorf("get template by ID: %w", err)
211227
}
212228

213-
activeTemplateVersion, err = tx.GetTemplateVersionByID(e.ctx, tmpl.ActiveVersionID)
229+
activeTemplateVersion, err = templateVersionCache.LoadOrStore(tmpl.ActiveVersionID, func() (database.TemplateVersion, error) {
230+
return tx.GetTemplateVersionByID(e.ctx, tmpl.ActiveVersionID)
231+
})
214232
if err != nil {
215233
return xerrors.Errorf("get active template version by ID: %w", err)
216234
}

0 commit comments

Comments
 (0)