awesome-everything EN
↑ Обратно к восхождению

API

Почему GraphQL получает N+1

Суть Модель независимых резолверов GraphQL даёт клиенту гибкость, а серверу — случайный шторм запросов: один HTTP-запрос превращается в сотни SQL-вызовов.
Высота — путь к senior
НольJuniorMiddleSenior
Ты на junior-высоте — поверхность
◷ 10 min

Bea просит posts { title, author { name } } — один HTTP-запрос на 50 постов. Sven смотрит в лог базы данных: 51 запрос. Схема верна. Никаких ошибок. Это GraphQL, работающий точно по задумке — и обходящийся в 600 мс вместо 80 мс.

Как GraphQL выполняет запрос

GraphQL разрешает документ, обходя его в глубину и в ширину на каждом уровне. На каждое поле, которое выбрал клиент, сервер вызывает резолвер, зарегистрированный для пары Type.field. Резолверы получают (parent, args, context, info) и возвращают значение или Promise. Они намеренно изолированы: каждый резолвер не видит соседей, не знает, как родитель получил данные, и не может посмотреть, какие ещё резолверы запустятся.

Именно эта изоляция делает GraphQL компонуемым — можно добавить поле к типу, не трогая остальные резолверы. Но она же является прямой причиной N+1.

Почему изоляция порождает N+1

Возьмём запрос posts { title, author { name } }. Движок выполнения:

  1. Вызывает Query.posts → выполняет SELECT * FROM posts LIMIT 50 → возвращает 50 объектов постов.
  2. Для каждого из 50 постов вызывает Post.author(parent=post, ...) → выполняет SELECT * FROM users WHERE id = post.author_id → 50 отдельных запросов.

Итого: 51 поездка в базу данных на один HTTP-запрос.

REST-контроллер для того же эндпоинта написал бы SELECT posts JOIN users ON users.id = posts.author_id — один запрос. Разработчик видит всю форму ответа в одном месте и может оптимизировать SQL. В GraphQL резолвер Post.author видит только один пост за раз. Нет глобальной точки планирования.

Метафора официанта

Ресторан, где каждый стол заказывает блюда по одному. Стол на шестерых отправляет официанта шесть раз — по разу на блюдо. Кухня быстрая, но официант проводит смену в дороге. Скорость кухни не имеет значения, когда ходьба и есть затраты.

DataLoader — менеджер, говорящий: держи талоны, пока не хлопну, потом неси всё сразу. Хлопок — один тик event loop. Этот урок объясняет проблему; следующие два — решение.

Без DataLoaderС DataLoader
Query.posts → 1 SQLQuery.posts → 1 SQL
Post.author(post1) → 1 SQLPost.author × 50 → ставим в очередь 50 ID
Post.author(post2) → 1 SQLDataLoader делает 1 SQL на все 50
… (50 строк)Итого: 2 SQL
Итого: 51 SQL
GraphQL N+1: ключевые цифры
Наивные поездки на 50 постов × 1 автор
51 (1 + 50)
С DataLoader
2 (1 + 1)
Сообщённый ускоритель DataLoader
64–84% (реляционные данные)
Скоуп DataLoader
per-request, не глобальный
Викторина

Что значит «N+1» в контексте GraphQL?

Викторина

Почему GraphQL ловит N+1, а REST часто нет?

Расставь шаги по порядку

Поставь шаги наивного GraphQL-разрешения в порядок:

  1. 1 Клиент отправляет один GraphQL-запрос: посты и их авторы
  2. 2 Сервер разрешает корневое поле posts и получает 50 постов
  3. 3 Для каждого поста резолвер Post.author запускается — 50 независимых вызовов
  4. 4 Каждый резолвер выполняет SELECT * FROM users WHERE id = post.author_id
  5. 5 База данных выполняет 50 отдельных запросов, по одному на пост
  6. 6 GraphQL собирает финальный ответ и отправляет клиенту
Вспомните перед уходом
  1. 01
    Почему GraphQL получает N+1, а REST-контроллер на тех же данных обычно нет?
  2. 02
    В одном предложении: каково решение проблемы GraphQL N+1?
Итог

Выполнение GraphQL — поле за полем, резолвер за резолвером. Когда список из N элементов требует вложенного fetch, сервер делает 1+N запросов к базе по умолчанию — не из-за ошибки, а потому что изоляция резолверов — то, что делает GraphQL компонуемым. Страница с 50 постами и их авторами стоит 51 поездку без вмешательства. DataLoader, рассмотренный в следующем уроке, сворачивает 50 per-author поездок в одну, снижая итог с 51 до 2.

Связанные уроки
встречается в178
Продолжить восхождение ↑Механика DataLoader: батчинг на границе тика
хоткеи развернуть
поиск
K
пред. пьеса
k
след. пьеса
j
тиры
t
это меню
?
sources3
expand
  1. 01
  2. 02
  3. 03

Trademarks belong to their respective owners. Editorial reference only.