Промах кэша вызывает лавину одинаковых запросов в базу
Кэш каталога в сервисе под 20K RPS устроен так:
func (c *Cache) Get(ctx context.Context, key string) (Item, error) {
c.mu.RLock()
v, ok := c.m[key]
c.mu.RUnlock()
if ok {
return v, nil
}
v, err := c.loadFromDB(ctx, key) // тяжёлый запрос, ~300 мс
if err != nil {
return Item{}, err
}
c.mu.Lock()
c.m[key] = v
c.mu.Unlock()
return v, nil
}
В момент истечения записи по популярному ключу в базу одновременно летят сотни одинаковых запросов, p99 подскакивает. Какое решение устраняет причину?
- Заменить `RWMutex` на обычный `Mutex` — блокировка на чтение перестанет пропускать параллельные промахи
- Выполнять `loadFromDB` под удержанным `RLock` — тогда параллельные читатели дождутся результата
- Схлопнуть одинаковые промахи в один вызов на ключ (singleflight): первая горутина идёт в БД, остальные ждут её результат
- Увеличить TTL записей, чтобы промахи по популярным ключам случались реже и всплески в базу возникали не так часто
