Когда какой-нибудь эндпоинт вдруг начинает отвечать по полсекунды, все первым делом лезут оптимизировать код: асинхронщина, воркеры, микрооптимизации.
А причина обычно куда скучнее — ты просто дёргаешь базу сотню раз там, где хватило бы одного запроса.
🐘 Как выглядит N+1 Берём список из 50 заказов и для каждого лезем за пользователем:
orders = Order.all # 1 запрос — забрали заказы
for o in orders:
print(o.user.name) # +50 запросов, по одному на заказ
Один запрос на список и ещё по одному на каждую строку.
Отсюда и название — N+1.
На локалке с десятком записей ты это даже не заметишь.
А на проде, где строк тысячи и база стоит на другом сервере, каждый такой поход — это отдельный сетевой запрос туда-обратно.
Вот они и набегают в те самые полсекунды.
🔧 Как чинить Идея одна: вытащить всех пользователей сразу, одним-двумя запросами, а не по штучке в цикле.
В разных ORM это включается по-разному:
# Django
Order.objects.select_related("user")
# SQLAlchemy
session.query(Order).options(joinedload(Order.user))
# Rails
Order.includes(:user)
Под капотом это либо JOIN, либо второй запрос вида
WHERE user_id IN (...). Было 51 обращение — стало два.
На реальных данных разница огромная.
🔍 Как заметить Беда в том, что глазами N+1 не видно — код выглядит чистым.
Поэтому: — смотри SQL-лог в дев-режиме: если на один запрос к API летит пачка одинаковых SELECT-ов — вот он, красавец; — поставь профайлер (django-silk, bullet, rack-mini-profiler) — они прямо тычут носом; — или тупо считай запросы на эндпоинт.
Больше десятка на простую страницу — уже звоночек.
💾 И про кэш, пока не разогнались
Как только заходит разговор про скорость, все сразу хотят прикрутить Redis.
Но кэш — это не «сделать быстро», это «отложить проблему и получить новую»: теперь надо думать, когда его чистить.
Прежде чем кэшировать, честно ответь себе: — эти данные вообще часто читают и редко меняют?
если нет — кэш ни к чему; — что будет, когда они устареют и я отдам юзеру старьё? — как я вообще пойму, что пора обновлять кэш?
Часто выходит, что убрать N+1 и повесить нормальный индекс дают те же 200 мс выигрыша — только без лишнего слоя, который потом будет отдавать неактуальные данные и портить тебе вечера.
Правило простое: сначала померь и убери явную дичь в запросах, и только потом тащи кэш.
Наоборот — почти всегда дорога к боли.
#backend #performance #database #orm #optimization #dev