Update dashboard statistics caching and retrieval logic
continuous-integration/drone/push Build is passing

- Increased the cache duration for dashboard statistics from 60 seconds to 5 minutes, improving data freshness and reducing load on the backend.
- Introduced a stale cache mechanism that allows retrieval of stale statistics while refreshing them in the background, enhancing user experience by providing quicker access to data.
- Updated the statistics repository to handle the new caching logic, ensuring accurate and timely statistics reporting.
- Added tests to validate the new caching behavior and ensure the integrity of statistics retrieval.

This update optimizes the dashboard's performance and responsiveness by improving the caching strategy for statistics.
This commit is contained in:
Mazyar
2026-06-30 18:50:47 +03:30
parent 7aacb7dfc9
commit 3002935b76
4 changed files with 154 additions and 41 deletions
+40 -7
View File
@@ -20,7 +20,8 @@ const (
defaultListLimit = 5
maxListLimit = 20
summaryCacheTTL = 60 * time.Second
statisticsCacheTTL = 60 * time.Second
statisticsCacheTTL = 5 * time.Minute
statisticsStaleGraceTTL = 15 * time.Minute
)
// Service defines dashboard business operations.
@@ -35,8 +36,9 @@ type Service interface {
}
type cacheEntry[T any] struct {
expiresAt time.Time
value T
expiresAt time.Time
staleUntil time.Time
value T
}
type service struct {
@@ -203,6 +205,19 @@ func (s *service) Statistics(ctx context.Context, query StatisticsQuery) (*Stati
return cached, nil
}
if stale, ok := s.staleStatistics(days); ok {
go s.refreshStatistics(days)
return stale, nil
}
return s.loadStatistics(ctx, days)
}
func (s *service) refreshStatistics(days int) {
_, _ = s.loadStatistics(context.Background(), days)
}
func (s *service) loadStatistics(ctx context.Context, days int) (*StatisticsReportResponse, error) {
cacheKey := strconv.Itoa(days)
out, err, _ := s.statisticsGroup.Do(cacheKey, func() (interface{}, error) {
if cached, ok := s.cachedStatistics(days); ok {
@@ -255,8 +270,9 @@ func (s *service) storeSummary(windowSec int64, value *SummaryResponse) {
defer s.summaryMu.Unlock()
s.summary[windowSec] = cacheEntry[*SummaryResponse]{
expiresAt: time.Now().Add(summaryCacheTTL),
value: value,
expiresAt: time.Now().Add(summaryCacheTTL),
staleUntil: time.Now().Add(summaryCacheTTL),
value: value,
}
}
@@ -268,6 +284,20 @@ func (s *service) cachedStatistics(days int) (*StatisticsReportResponse, bool) {
entry, ok := s.statistics[days]
if !ok || now.After(entry.expiresAt) {
return nil, false
}
return entry.value, true
}
func (s *service) staleStatistics(days int) (*StatisticsReportResponse, bool) {
now := time.Now()
s.statisticsMu.Lock()
defer s.statisticsMu.Unlock()
entry, ok := s.statistics[days]
if !ok || now.After(entry.staleUntil) {
if ok {
delete(s.statistics, days)
}
@@ -278,12 +308,15 @@ func (s *service) cachedStatistics(days int) (*StatisticsReportResponse, bool) {
}
func (s *service) storeStatistics(days int, value *StatisticsReportResponse) {
now := time.Now()
s.statisticsMu.Lock()
defer s.statisticsMu.Unlock()
s.statistics[days] = cacheEntry[*StatisticsReportResponse]{
expiresAt: time.Now().Add(statisticsCacheTTL),
value: value,
expiresAt: now.Add(statisticsCacheTTL),
staleUntil: now.Add(statisticsCacheTTL + statisticsStaleGraceTTL),
value: value,
}
}