Проблема N+1
Вопрос
Объясни проблему N+1: как она возникает, как её заметить и как исправить.
Попробуйте сами
Ответьте вслух за 2 минуты, как на собесе. Потом сверьтесь с тезисами: что прозвучало, а что нет.
Показать ответ и разбор
Коротко
Что должно прозвучать в ответе
Один запрос за списком и по запросу на каждый элемент за связанными данными
Типично возникает из-за ленивой загрузки в ORM в цикле
Заметить: число запросов растёт с размером списка, логи SQL, APM, счётчик запросов в тестах
Исправить: JOIN, запрос по списку id (IN или ANY), eager loading, DataLoader в GraphQL
Следить, чтобы JOIN не размножил строки и не тянул лишнее
Что могут спросить следом
- Как DataLoader решает N+1 в GraphQL?
- Когда JOIN хуже двух отдельных запросов?
Разбор
N+1 это когда мы получаем список одним запросом, а потом в цикле для каждого элемента делаем ещё по запросу: 50 статей и 50 запросов за авторами. Каждый запрос быстрый, но вместе это десятки походов по сети, и время растёт вместе с размером страницы. Чаще всего так происходит из-за ленивой загрузки в ORM: обращаемся к article.author в цикле, а ORM тихо ходит в базу. Замечаю по логам SQL или APM, где видно много одинаковых запросов с разными id; в тестах можно считать число запросов на эндпоинт. Исправляю загрузкой пачкой: JOIN, один запрос WHERE id = ANY($1) по собранным id, eager loading в ORM (include в Prisma, relations в TypeORM), в GraphQL DataLoader. С JOIN слежу, чтобы связь один ко многим не размножила строки; иногда два запроса, список и связанные данные по id, проще и дешевле.
Ответьте сами, а не прочитайте
В тренажёре вопрос попадёт в повторения и вернётся, когда начнёт забываться. Плюс билеты с зачётом: 20 вопросов, не больше 2 ошибок.