Skip to content

Commit eff38f4

Browse files
author
Arch Dev
committed
add request coalescing and caching to reduce api calls
- add request coalescer to prevent duplicate in-flight requests - coalesce version api calls (FindLatestVersion, ListAllVersions) - add caching to servers api (5 minute ttl) - reduces redundant api calls from frontend
1 parent c0dc8b1 commit eff38f4

File tree

3 files changed

+98
-6
lines changed

3 files changed

+98
-6
lines changed

internal/patch/coalesce.go

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package patch
2+
3+
import (
4+
"sync"
5+
)
6+
7+
// RequestCoalescer prevents duplicate in-flight requests
8+
type RequestCoalescer struct {
9+
mu sync.Mutex
10+
calls map[string]*call
11+
}
12+
13+
type call struct {
14+
wg sync.WaitGroup
15+
val interface{}
16+
err error
17+
}
18+
19+
func NewRequestCoalescer() *RequestCoalescer {
20+
return &RequestCoalescer{
21+
calls: make(map[string]*call),
22+
}
23+
}
24+
25+
// Do executes the given function only once for the given key at a time.
26+
// If multiple goroutines call Do with the same key, only one will execute fn,
27+
// and the others will wait for the result.
28+
func (c *RequestCoalescer) Do(key string, fn func() (interface{}, error)) (interface{}, error) {
29+
c.mu.Lock()
30+
if existing, ok := c.calls[key]; ok {
31+
// Another request is in flight, wait for it
32+
c.mu.Unlock()
33+
existing.wg.Wait()
34+
return existing.val, existing.err
35+
}
36+
37+
// Create new call
38+
newCall := &call{}
39+
newCall.wg.Add(1)
40+
c.calls[key] = newCall
41+
c.mu.Unlock()
42+
43+
// Execute the function
44+
newCall.val, newCall.err = fn()
45+
46+
// Clean up and notify waiters
47+
c.mu.Lock()
48+
delete(c.calls, key)
49+
c.mu.Unlock()
50+
newCall.wg.Done()
51+
52+
return newCall.val, newCall.err
53+
}
54+
55+
// Global coalescer for version API calls
56+
var versionCoalescer = NewRequestCoalescer()

internal/patch/version.go

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -109,10 +109,17 @@ func FindLatestVersion(branch string) (int, error) {
109109
return cached.LatestVersion, cached.Error
110110
}
111111

112-
result := findLatestVersion(branch)
113-
versionCache.setLatest(key, &result)
112+
// Use coalescer to prevent duplicate in-flight requests
113+
result, err := versionCoalescer.Do("latest:"+key, func() (interface{}, error) {
114+
r := findLatestVersion(branch)
115+
versionCache.setLatest(key, &r)
116+
return r.LatestVersion, r.Error
117+
})
114118

115-
return result.LatestVersion, result.Error
119+
if err != nil {
120+
return 0, err
121+
}
122+
return result.(int), nil
116123
}
117124

118125
func ListAllVersions(branch string) ([]int, error) {
@@ -122,10 +129,17 @@ func ListAllVersions(branch string) ([]int, error) {
122129
return cached.Versions, cached.Error
123130
}
124131

125-
result := listAllVersions(branch)
126-
versionCache.setAllVersions(key, &result)
132+
// Use coalescer to prevent duplicate in-flight requests
133+
result, err := versionCoalescer.Do("all:"+key, func() (interface{}, error) {
134+
r := listAllVersions(branch)
135+
versionCache.setAllVersions(key, &r)
136+
return r.Versions, r.Error
137+
})
127138

128-
return result.Versions, result.Error
139+
if err != nil {
140+
return nil, err
141+
}
142+
return result.([]int), nil
129143
}
130144

131145
func ClearVersionCache() {

internal/service/servers_service.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"encoding/json"
55
"fmt"
66
"net/http"
7+
"sync"
78
"time"
89
)
910

@@ -24,15 +25,30 @@ type ServerWithUrls struct {
2425

2526
type ServersService struct {
2627
apiBaseURL string
28+
cache []ServerWithUrls
29+
cacheTime time.Time
30+
cacheMu sync.RWMutex
31+
cacheTTL time.Duration
2732
}
2833

2934
func NewServersService() *ServersService {
3035
return &ServersService{
3136
apiBaseURL: "https://api.hylauncher.fun",
37+
cacheTTL: 5 * time.Minute,
3238
}
3339
}
3440

3541
func (s *ServersService) FetchServers() ([]ServerWithUrls, error) {
42+
// Check cache first
43+
s.cacheMu.RLock()
44+
if s.cache != nil && time.Since(s.cacheTime) < s.cacheTTL {
45+
cached := s.cache
46+
s.cacheMu.RUnlock()
47+
return cached, nil
48+
}
49+
s.cacheMu.RUnlock()
50+
51+
// Fetch from API
3652
client := &http.Client{
3753
Timeout: 10 * time.Second,
3854
}
@@ -61,6 +77,12 @@ func (s *ServersService) FetchServers() ([]ServerWithUrls, error) {
6177
}
6278
}
6379

80+
// Update cache
81+
s.cacheMu.Lock()
82+
s.cache = result
83+
s.cacheTime = time.Now()
84+
s.cacheMu.Unlock()
85+
6486
return result, nil
6587
}
6688

0 commit comments

Comments
 (0)