Увійти Реєстрація
Блог Серії
Кар'єра
Вакансії Компанії
Навчання
Документація Співбесіди Тестування Відео
Екосистема
Пакети Ресурси Проєкти Інструменти Події
Інше
Про нас Реклама

Звідки в GraphQL проблема N+1 і як її розв'язує DataLoader?

GraphQL викликає резолвер для кожного поля кожного об'єкта. Запит

{
  posts(first: 50) {
    title
    author { name }
  }
}

виконає один запит за постами, а потім резолвер author - 50 разів, по одному на пост. Класичне N+1, тільки його не видно в коді: кожен резолвер окремо виглядає невинно.

DataLoader - патерн (і бібліотека з такою назвою від авторів GraphQL), що збирає звернення в пакет:

  1. резолвери не запитують базу одразу, а кажуть завантажувачу «мені потрібен автор 7», «мені потрібен автор 12»;
  2. завантажувач накопичує ключі протягом поточного «такту» виконання;
  3. потім робить один запит за всіма ключами (WHERE id IN (7, 12, ...)) і роздає результати резолверам;
  4. в межах запиту кешує вже завантажені значення - той самий автор не завантажиться двічі.

Головна вимога до функції пакетного завантаження: повернути результати в тому самому порядку, що й ключі, і з відповідною кількістю елементів (null для відсутніх).

У Laravel з Lighthouse це зроблено за вас для зв'язків:

type Post {
  title: String!
  author: User! @belongsTo
  comments: [Comment!]! @hasMany
}

Директиви зв'язків Lighthouse пакетують запити до бази - зв'язок для 50 постів завантажиться одним запитом. Для даних, що не є зв'язками Eloquent (зовнішній сервіс, обчислення), Lighthouse дає змогу писати власні пакетні завантажувачі.

Типові пастки:

  • власний резолвер з ->find() усередині поля обходить пакетування й повертає N+1;
  • вкладені списки з пагінацією в кожному елементі (posts { comments(first: 5) }) складні для пакетування - «перші 5 коментарів кожного поста» не виражається простим IN, потрібні віконні функції чи окремі стратегії;
  • кеш завантажувача - на запит, не глобальний: інакше користувачі побачать чужі дані.

Як помітити: лічильник SQL-запитів на один GraphQL-запит (Telescope, Debugbar, Pulse) - тест, що перевіряє: кількість запитів не росте разом із кількістю елементів у списку.

Докладніше в документації: DataLoader

Схожі питання