Факапы разработчика, день 6
💥 Ошибка, которая научила меня не злоупотреблять кэшем
Программирование — это как игра в шахматы: один ход может казаться гениальным, пока не поймёшь, что он сломал всю партию. После прошлых факапов я решил, что теперь буду умнее. Но кэширование данных показало мне, что я всё ещё могу облажаться.
📌 Ситуация
Я работал над небольшим REST API на FastAPI для внутренней системы. API возвращало данные о пользователях из базы данных PostgreSQL. Чтобы ускорить запросы, я решил добавить кэширование с помощью библиотеки cachetools. Идея простая: если запрос приходит с тем же параметром, возвращаем результат из кэша, а не лезем в базу. Написал код, добавил кэш:
from cachetools import TTLCache
cache = TTLCache(maxsize=100, ttl=300)
@app.get("/user/{user_id}")
def get_user(user_id: int):
--if user_id in cache:
----return cache[user_id]
--user = db.query(User).get(user_id)
--cache[user_id] = user
--return user
Протестировал — всё летает! Запросы стали быстрее, база нагружается меньше. Я был доволен собой, пока не начали поступать жалобы от коллег.
🔎 День разборок
Пользователи заметили, что данные в API иногда устаревшие. Например, если кто-то обновлял профиль (имя или email), API продолжал возвращать старые данные. Я проверил базу — там всё обновляется, а вот API врёт. Сначала подумал, что проблема в базе, но запросы напрямую показывали актуальные данные.
Тогда я полез в код. Проверил, как работает cachetools — вроде всё ок, кэш должен обновляться через 5 минут (ttl=300). Я даже увеличил ttl до 10 минут, думая, что данные просто не успевают обновляться. Но это не помогло. Коллеги уже начали шутить, что мой API живёт в прошлом.
🤯 В чём была проблема
После дня копания я понял свою ошибку. Я не учёл, что кэш хранит данные по user_id, но не обновляется, если данные в базе меняются. Когда пользователь редактировал профиль, запись в базе обновлялась, но в кэше оставалась старая версия до истечения ttl. Нужно было либо инвалидировать кэш при обновлении, либо вообще не кэшировать изменяемые данные.
Я добавил инвалидацию кэша при обновлении профиля:
@app.put("/user/{user_id}")
def update_user(user_id: int, user_data):
--db.query(User).filter(User.id == user_id).update(user_data)
--db.commit()
--if user_id in cache:
----del cache[user_id]
--return {"status": "updated"}
Теперь API возвращало актуальные данные, и проблема исчезла.
🎯 Вывод
Эта ошибка стоила мне дня и пары шуток от коллег. Что я вынес:
Кэш — не панацея. Он ускоряет работу, но может вернуть устаревшие данные, если не продумать обновление.
Инвалидируй кэш при изменениях. Если данные могут меняться, нужно либо очищать кэш, либо не использовать его.
Тестируй с реальными сценариями. Я тестировал только получение данных, а не обновление.
🚀 Итог
Кэширование — мощный инструмент, но требует осторожности. Теперь я всегда проверяю, как данные обновляются, и добавляю инвалидацию кэша. API заработало как надо, а я научился не бросаться в оптимизации без полного понимания. И да, коллеги перестали подшучивать. 😅
2 минуты
27 августа 2025