Caching Strategies for Amazon Ads Dashboards
Advertising dashboards are read-heavy, bursty, and expensive to compute. A single page can ask for spend, sales, ACOS, ROAS, campaign status, budget pacing, placement breakdowns, and search-term trends. Without caching, the database becomes the place where every product decision is paid for repeatedly.
The hard part is not adding Redis. The hard part is deciding what can be cached, for how long, and how to invalidate it when advertisers expect fresh numbers.
Cache Data Products, Not SQL Rows
A common mistake is caching low-level query results. That leaks implementation details into the cache and makes invalidation painful. I prefer caching data products: the exact response shape used by the dashboard card or API endpoint.
type DashboardCacheKey struct {
CompanyID int64
ProfileID int64
DateRange string
Marketplace string
Card string
Version int
}
The `Version` field is important. When the calculation changes, bump the version and old entries become irrelevant without a risky delete operation.
Freshness by Metric Type
Not all metrics need the same freshness. Campaign status should be close to real time. Yesterday's sales aggregation can tolerate a longer TTL. Historical monthly charts can be cached aggressively.
- Live operational cards: 30 to 90 seconds.
- Today performance cards: 2 to 5 minutes.
- Historical charts: 30 minutes to several hours.
- Export files: cache by request hash and expire after a day.
Avoid Cache Stampedes
The worst cache bug is a stampede. A popular dashboard expires, one hundred users refresh, and all requests rebuild the same expensive result at once. Use singleflight inside the service and a distributed lock for the most expensive cross-instance computations.
var group singleflight.Group
func (s *Service) GetCard(ctx context.Context, key DashboardCacheKey) (Card, error) {
if cached, ok := s.cache.Get(ctx, key); ok {
return cached, nil
}
value, err, _ := group.Do(key.String(), func() (any, error) {
return s.computeCard(ctx, key)
})
if err != nil {
return Card{}, err
}
return value.(Card), nil
}
Stale Is Better Than Down
For dashboards, serving slightly stale data is often better than returning an error. If the database is slow or an upstream API is unavailable, the cache can return the last known value with a clear `lastUpdatedAt` timestamp. Users can reason about stale data. They cannot reason about a blank page.
A good cache strategy makes the product feel fast while keeping honesty about freshness. That honesty matters in advertising because money decisions are being made from the numbers on the screen.
Comments
Post a Comment