Почему счётчик лайков в Redis расходился с БД

Схема была обычная: лайк пишется в Postgres и дублируется инкрементом в Redis — Redis отдаёт счётчик на фронт без похода в БД на каждый рендер. Через пару недель счётчики в Redis стали заметно выше, чем реальное количество строк в таблице лайков.

Причина — retry на уровне HTTP-клиента фронта. Пользователь кликал лайк, запрос подвисал, клиент ретраил через таймаут. Первый запрос в итоге доходил до сервера и успевал отработать оба инкремента, второй — тоже. INSERT в Postgres защищён уникальным индексом по паре (user_id, post_id), поэтому там дубль просто падал с конфликтом и откатывался. А инкремент в Redis ничего не знает об уникальности — он слепо прибавляет единицу при каждом вызове.

try { likeRepository.save(like); redisTemplate.opsForValue() .increment(key); } catch (DataIntegrityViolationException e) { // дубль в БД поймали, // а инкремент в Redis уже ушёл }

Чинили не ретраем-идемпотентностью на фронте — это лечит симптом, а не причину. Инкремент Redis вынесли после успешного коммита в БД, а не параллельно с ним: если INSERT упал на уникальном индексе, инкремент просто не выполняется.

Кэш, который не знает о бизнес-ограничениях основного хранилища, рано или поздно начинает жить своей жизнью.

Тренажёр: 600 вопросов, мок с таймером, план повторов

senior·base — что спрашивают на самом деле


В этом посте были ссылки, но мы их удалили по правилам Сетки