Skip to content

Commit ccdfba0

Browse files
committed
report-listener: restore by-brand total_count for n=1 with fast cache
1 parent c55b853 commit ccdfba0

File tree

2 files changed

+64
-6
lines changed

2 files changed

+64
-6
lines changed

report-listener/.version

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
BUILD_VERSION=1.0.115
1+
BUILD_VERSION=1.0.116

report-listener/handlers/handlers.go

Lines changed: 63 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"net/http"
1111
"strconv"
1212
"strings"
13+
"sync"
1314
"time"
1415

1516
"report-listener/database"
@@ -25,8 +26,17 @@ import (
2526
const (
2627
// MaxReportsLimit is the maximum number of reports that can be requested in a single query
2728
MaxReportsLimit = 100000
29+
// brandCountsCacheTTL controls how long by-brand aggregate counts are cached in memory.
30+
brandCountsCacheTTL = 10 * time.Minute
2831
)
2932

33+
type brandCountsCacheEntry struct {
34+
Total int
35+
High int
36+
Medium int
37+
ExpiresAt time.Time
38+
}
39+
3040
// WebSocket upgrader with default configuration
3141
var upgrader = gorilla.Upgrader{
3242
CheckOrigin: func(r *http.Request) bool {
@@ -40,6 +50,9 @@ type Handlers struct {
4050
db *database.Database
4151
rabbitmqPublisher *rabbitmq.Publisher
4252
rabbitmqReplier *rabbitmq.Publisher
53+
54+
brandCountsMu sync.RWMutex
55+
brandCountsCache map[string]brandCountsCacheEntry
4356
}
4457

4558
// NewHandlers creates a new handlers instance
@@ -49,7 +62,36 @@ func NewHandlers(hub *ws.Hub, db *database.Database, pub *rabbitmq.Publisher, re
4962
db: db,
5063
rabbitmqPublisher: pub,
5164
rabbitmqReplier: replyPub,
65+
brandCountsCache: make(map[string]brandCountsCacheEntry),
66+
}
67+
}
68+
69+
func brandCountsCacheKey(brandName string) string {
70+
return strings.ToLower(strings.TrimSpace(brandName))
71+
}
72+
73+
func (h *Handlers) getBrandCountsCached(brandName string) (brandCountsCacheEntry, bool, bool) {
74+
key := brandCountsCacheKey(brandName)
75+
h.brandCountsMu.RLock()
76+
entry, ok := h.brandCountsCache[key]
77+
h.brandCountsMu.RUnlock()
78+
if !ok {
79+
return brandCountsCacheEntry{}, false, false
5280
}
81+
fresh := time.Now().Before(entry.ExpiresAt)
82+
return entry, true, fresh
83+
}
84+
85+
func (h *Handlers) setBrandCountsCached(brandName string, total, high, medium int) {
86+
key := brandCountsCacheKey(brandName)
87+
h.brandCountsMu.Lock()
88+
h.brandCountsCache[key] = brandCountsCacheEntry{
89+
Total: total,
90+
High: high,
91+
Medium: medium,
92+
ExpiresAt: time.Now().Add(brandCountsCacheTTL),
93+
}
94+
h.brandCountsMu.Unlock()
5395
}
5496

5597
// DB exposes the underlying database handle for wiring
@@ -981,20 +1023,36 @@ func (h *Handlers) GetReportsByBrand(c *gin.Context) {
9811023
}
9821024

9831025
// Get counts with a bounded timeout to avoid slow/full-table scans blocking UI.
984-
// For n=1 (digital search flow), skip aggregate counts entirely.
9851026
totalCount := len(reports)
9861027
highPriorityCount := 0
9871028
mediumPriorityCount := 0
988-
if limit > 1 {
989-
countCtx, cancelCounts := context.WithTimeout(c.Request.Context(), 1200*time.Millisecond)
990-
defer cancelCounts()
1029+
1030+
if cached, ok, fresh := h.getBrandCountsCached(brandName); ok && fresh {
1031+
totalCount = cached.Total
1032+
highPriorityCount = cached.High
1033+
mediumPriorityCount = cached.Medium
1034+
} else {
1035+
countTimeout := 1200 * time.Millisecond
1036+
if limit <= 1 {
1037+
// Keep search dropdown (n=1) snappy while still attempting accurate totals.
1038+
countTimeout = 250 * time.Millisecond
1039+
}
1040+
1041+
countCtx, cancelCounts := context.WithTimeout(c.Request.Context(), countTimeout)
9911042
t, hpc, mpc, err := h.db.GetBrandPriorityCountsByBrandName(countCtx, brandName)
1043+
cancelCounts()
9921044
if err != nil {
993-
log.Printf("Failed to get aggregated counts for brand '%s' (fallbacking to partial): %v", brandName, err)
1045+
log.Printf("Failed to get aggregated counts for brand '%s' (fallbacking to cache/partial): %v", brandName, err)
1046+
if cached, ok, _ := h.getBrandCountsCached(brandName); ok {
1047+
totalCount = cached.Total
1048+
highPriorityCount = cached.High
1049+
mediumPriorityCount = cached.Medium
1050+
}
9941051
} else {
9951052
totalCount = t
9961053
highPriorityCount = hpc
9971054
mediumPriorityCount = mpc
1055+
h.setBrandCountsCached(brandName, t, hpc, mpc)
9981056
}
9991057
}
10001058

0 commit comments

Comments
 (0)