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

API

Federation и lookahead: батчинг за пределами DataLoader

Суть Apollo Federation даёт кросс-subgraph батчинг через _entities бесплатно; внутри subgraph ты всё равно обязан DataLoader. Lookahead-джойны сворачивают многоуровневую вложенность ценой изоляции резолверов.
Высота — путь к senior
НольJuniorMiddleSenior
Ты на middle-высоте — в небе
◷ 11 min

Федеративный граф маршрутизирует запрос через subgraph users и subgraph posts. Supergraph-роутер автоматически собирает 50 user-ссылок в один _entities-вызов. Внутри users-subgraph реализация __resolveReference проходит цикл по 50 representations и 50 раз ходит в базу. Federation отдал тебе сетевой батч бесплатно; батч на базу — всё ещё твоя ответственность.

Как Apollo Federation батчит между subgraph’ами

В федеративной схеме каждый subgraph, владеющий entity-типом, экспонирует резолвер __resolveReference(representation). Когда запрос пересекает границы subgraph — скажем, posts-subgraph возвращает Post.author: User, а users-subgraph владеет User — supergraph-роутер:

  1. Собирает все User-ссылки, возникшие при разрешении запроса (например, 50 постов, каждый ссылается на автора).
  2. Отправляет один _entities(representations: [_Any!]!) в users-subgraph со всеми 50 representations.
  3. __resolveReference в users-subgraph вызывается для каждого representation.

Кросс-subgraph батч автоматический и бесплатный. Роутер делает всё сам.

Что ты всё ещё обязан сделать внутри __resolveReference

Наивная реализация:

// users subgraph — наивно, N+1 внутри _entities
User: {
  __resolveReference: async (rep) => {
    return db.users.findById(rep.id); // Один запрос на representation
  }
}

С 50 representations это делает 50 SQL-запросов внутри единственного _entities-вызова. Нужен DataLoader:

// users subgraph — правильно
User: {
  __resolveReference: (rep, ctx) => ctx.loaders.user.load(rep.id),
}

Загрузчик батчит все 50 .load(rep.id) вызовов из 50 __resolveReference-вызовов в один WHERE id IN (...).

Supergraph-роутер:
  posts subgraph → 50 постов, у каждого authorId
  → один _entities([{__typename:"User", id:7}, ...×50]) в users subgraph

users subgraph (наивно):
  __resolveReference({id:7})  → SELECT * FROM users WHERE id=7   (1 SQL)
  __resolveReference({id:9})  → SELECT * FROM users WHERE id=9   (1 SQL)
  ... × 50                                                       (50 SQL)

users subgraph (с DataLoader):
  __resolveReference({id:7})  → loader.load(7)   (в очереди)
  __resolveReference({id:9})  → loader.load(9)   (в очереди)
  ... × 50                    → batchFn([7,9,...]) → 1 SQL

Lookahead: сворачивание многоуровневой вложенности

DataLoader чинит форму «N запросов за одной колонкой». Если клиент просит posts { author { profile { bio } } }, у тебя по-прежнему три уровня разрешения — DataLoader накапливает каждый уровень отдельно, три поездки вместо одной.

Resolver lookahead читает AST из аргумента info внутри top-level резолвера, видит, какие дочерние поля запросил клиент, и джойнит их одним SQL на верхнем уровне.

Query: {
  posts: (_, __, ctx, info) => {
    const wantsAuthor = selectionSetContains(info, 'author');
    const query = wantsAuthor
      ? 'SELECT posts.*, users.name AS author_name FROM posts JOIN users...'
      : 'SELECT * FROM posts';
    return db.query(query);
  }
}

Библиотеки Pothos, TypeGraphQL с SelectQueryBuilder и Hasura реализуют это, маппя AST в SQL-планировщик.

Почему это работает

Tradeoff: lookahead-резолверы знают про целое поддерево. Top-level резолвер больше не изолирован — он меняет поведение в зависимости от того, какие дочерние поля выбрал клиент. Это ломает ключевое свойство GraphQL (per-field изоляция). Используй lookahead только когда DataLoader на каждом уровне оставляет наблюдаемую латентность после измерений.

Викторина

Федеративный supergraph получает запрос, ссылающийся на 50 пользователей через два subgraph. Что делает роутер и что subgraph всё ещё нужен?

Викторина

Каков главный tradeoff resolver lookahead по сравнению с DataLoader?

Вспомните перед уходом
  1. 01
    Почему _entities-батчинг Apollo Federation не устраняет потребность в DataLoader внутри subgraph?
  2. 02
    Когда выбрать lookahead вместо DataLoader?
Итог

Apollo Federation обрабатывает кросс-subgraph батчинг на роутере: все user-ссылки в запросе становятся одним _entities-вызовом в users-subgraph. Внутри subgraph __resolveReference всё равно вызывается по разу на representation, и DataLoader всё ещё нужен для батчинга их в один SQL-запрос. Для запросов, пересекающих несколько уровней вложенности, lookahead читает клиентский AST selection set на верхнем уровне и делает один JOIN — сворачивая три DataLoader-поездки в одну ценой per-field изоляции.

Связанные уроки
встречается в202
Продолжить восхождение ↑Защита сложности запросов: depth, cost, persisted queries
хоткеи развернуть
поиск
K
пред. пьеса
k
след. пьеса
j
тиры
t
это меню
?
sources2
expand
  1. 01
  2. 02

Trademarks belong to their respective owners. Editorial reference only.