Enhance dashboard statistics loading with cold-cache handling
continuous-integration/drone/push Build is passing

- Introduced a new constant, statisticsColdLoadWait, to define the wait time for cold-cache requests before serving a placeholder response.
- Updated the Statistics method to initiate a background load for statistics while allowing for a brief wait for real data, improving responsiveness during cache warming.
- Implemented a channel to handle the result of the background loading process, ensuring that users receive timely feedback while the cache is being populated.

This update optimizes the dashboard's performance by ensuring that users are served quickly, even when the statistics are being freshly loaded from the backend.
This commit is contained in:
Mazyar
2026-06-30 22:24:04 +03:30
parent 12d1cabf7e
commit addd616d59
+29 -4
View File
@@ -25,6 +25,11 @@ const (
summaryCacheTTL = 60 * time.Second summaryCacheTTL = 60 * time.Second
statisticsCacheTTL = 5 * time.Minute statisticsCacheTTL = 5 * time.Minute
statisticsStaleGraceTTL = 15 * time.Minute statisticsStaleGraceTTL = 15 * time.Minute
// statisticsColdLoadWait bounds how long a cold-cache request waits for a fresh
// load before falling back to a placeholder. Mongo-only loads (MinIO scope
// already cached) comfortably finish within this window; a first-ever MinIO
// bucket scan does not, so we don't block the request for that.
statisticsColdLoadWait = 4 * time.Second
) )
// Service defines dashboard business operations. // Service defines dashboard business operations.
@@ -224,13 +229,33 @@ func (s *service) Statistics(ctx context.Context, query StatisticsQuery) (*Stati
} }
// Cold cache: the underlying load performs a full MinIO bucket scan plus Mongo // Cold cache: the underlying load performs a full MinIO bucket scan plus Mongo
// aggregations that can take minutes. Never block the request on it. Kick off a // aggregations that can take minutes on a first run. Start the load in the
// background refresh (deduped via singleflight) and serve an empty report so the // background (deduped via singleflight, and detached from this request's
// endpoint stays fast; subsequent requests get full data once the cache warms. // context so it keeps running even if we give up waiting) and wait briefly for
go s.refreshStatistics(days) // it. If it finishes in time we return real data; otherwise we serve a
// placeholder so the endpoint stays fast, and the load keeps warming the cache
// for the next request.
resultCh := make(chan *StatisticsReportResponse, 1)
go func() {
result, err := s.loadStatistics(context.Background(), days)
if err != nil {
resultCh <- nil
return
}
resultCh <- result
}()
select {
case result := <-resultCh:
if result != nil {
return result, nil
}
case <-time.After(statisticsColdLoadWait):
s.logger.Info("Serving placeholder dashboard statistics while cache warms", map[string]interface{}{ s.logger.Info("Serving placeholder dashboard statistics while cache warms", map[string]interface{}{
"days": days, "days": days,
}) })
}
return emptyStatisticsReport(days), nil return emptyStatisticsReport(days), nil
} }