aboutsummaryrefslogtreecommitdiffstats
path: root/dashboard/app/cache.go
diff options
context:
space:
mode:
authorAleksandr Nogikh <nogikh@google.com>2023-10-17 14:52:17 +0200
committerAleksandr Nogikh <nogikh@google.com>2023-10-23 12:07:54 +0000
commitaf8d2e46418eefb127e9fa9309a63fa60ef7fc66 (patch)
tree142d18038b84a3507742c94d84488d45c29d20e2 /dashboard/app/cache.go
parent6346f51eb10624fc17701d3a24d9f35c902b29a1 (diff)
dashboard: optionally cache displayed bug groups
For upstream Linux namespace, it sometimes takes up to 5-10 seconds to load the main page. That is too much and the reason for this is datastore not being intended for frequent querying of thousands of entities from the database. Let's take a step forward and at least cache the bugs we display on the main page. Once in a minute, query them for all access levels, compress and save to the memcached. Only do it for non-filtered bugs, because otherwise it works fast enough. As the next step we could also take care of terminal pages.
Diffstat (limited to 'dashboard/app/cache.go')
-rw-r--r--dashboard/app/cache.go75
1 files changed, 74 insertions, 1 deletions
diff --git a/dashboard/app/cache.go b/dashboard/app/cache.go
index b9ed0d54f..4e77d55cf 100644
--- a/dashboard/app/cache.go
+++ b/dashboard/app/cache.go
@@ -4,10 +4,12 @@
package main
import (
+ "encoding/json"
"fmt"
"net/http"
"time"
+ "github.com/google/syzkaller/pkg/image"
"golang.org/x/net/context"
"google.golang.org/appengine/v2"
"google.golang.org/appengine/v2/log"
@@ -48,6 +50,8 @@ func CacheGet(c context.Context, r *http.Request, ns string) (*Cached, error) {
return buildAndStoreCached(c, bugs, backports, ns, accessLevel)
}
+var cacheAccessLevels = []AccessLevel{AccessPublic, AccessUser, AccessAdmin}
+
// cacheUpdate updates memcache every hour (called by cron.yaml).
// Cache update is slow and we don't want to slow down user requests.
func cacheUpdate(w http.ResponseWriter, r *http.Request) {
@@ -63,7 +67,7 @@ func cacheUpdate(w http.ResponseWriter, r *http.Request) {
log.Errorf(c, "failed load ns=%v bugs: %v", ns, err)
continue
}
- for _, accessLevel := range []AccessLevel{AccessPublic, AccessUser, AccessAdmin} {
+ for _, accessLevel := range cacheAccessLevels {
_, err := buildAndStoreCached(c, bugs, backports, ns, accessLevel)
if err != nil {
log.Errorf(c, "failed to build cached for ns=%v access=%v: %v", ns, accessLevel, err)
@@ -104,6 +108,7 @@ func buildAndStoreCached(c context.Context, bugs []*Bug, backports []*rawBackpor
}
}
}
+
item := &memcache.Item{
Key: cacheKey(ns, accessLevel),
Object: v,
@@ -133,3 +138,71 @@ func (c *CachedBugStats) Record(bug *Bug) {
func cacheKey(ns string, accessLevel AccessLevel) string {
return fmt.Sprintf("%v-%v", ns, accessLevel)
}
+
+func CachedBugGroups(c context.Context, ns string, accessLevel AccessLevel) ([]*uiBugGroup, error) {
+ item, err := memcache.Get(c, cachedBugGroupsKey(ns, accessLevel))
+ if err == memcache.ErrCacheMiss {
+ return nil, nil
+ }
+ if err != nil {
+ return nil, err
+ }
+
+ jsonData, destructor := image.MustDecompress(item.Value)
+ defer destructor()
+
+ var ret []*uiBugGroup
+ err = json.Unmarshal(jsonData, &ret)
+ return ret, err
+}
+
+func cachedBugGroupsKey(ns string, accessLevel AccessLevel) string {
+ return fmt.Sprintf("%v-%v-bug-groups", ns, accessLevel)
+}
+
+// minuteCacheUpdate updates memcache every minute (called by cron.yaml).
+func handleMinuteCacheUpdate(w http.ResponseWriter, r *http.Request) {
+ c := appengine.NewContext(r)
+ for ns, nsConfig := range getConfig(c).Namespaces {
+ if !nsConfig.CacheUIPages {
+ continue
+ }
+ err := minuteCacheNsUpdate(c, ns)
+ if err != nil {
+ http.Error(w, fmt.Sprintf("bug groups cache update for %s failed: %v", ns, err),
+ http.StatusInternalServerError)
+ return
+ }
+ }
+}
+
+func minuteCacheNsUpdate(c context.Context, ns string) error {
+ bugs, err := loadVisibleBugs(c, ns, nil)
+ if err != nil {
+ return err
+ }
+ managers, err := managerList(c, ns)
+ if err != nil {
+ return err
+ }
+ for _, accessLevel := range cacheAccessLevels {
+ groups, err := prepareBugGroups(c, bugs, managers, accessLevel, ns)
+ if err != nil {
+ return fmt.Errorf("failed to fetch groups: %w", err)
+ }
+ encoded, err := json.Marshal(groups)
+ if err != nil {
+ return fmt.Errorf("failed to marshal: %w", err)
+ }
+ item := &memcache.Item{
+ Key: cachedBugGroupsKey(ns, accessLevel),
+ // The resulting blob can be quite big, so let's compress.
+ Value: image.Compress(encoded),
+ Expiration: 2 * time.Minute, // supposed to be updated by cron every minute
+ }
+ if err := memcache.Set(c, item); err != nil {
+ return err
+ }
+ }
+ return nil
+}