open atlas
↑ К треку
API API · 06 · 04

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

Apollo Federation даёт кросс-subgraph батчинг через _entities бесплатно; внутри subgraph ты всё равно обязан DataLoader. Lookahead-джойны сворачивают многоуровневую вложенность ценой изоляции резолверов.

API Middle ◷ 11 min
Уровень
ОсновыJuniorMiddleSenior

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

Federation даёт тебе один уровень батчинга бесплатно — но понять точно, где этот уровень заканчивается, отличает рабочий federated API от 50-SQL-сюрприза внутри единственного _entities-вызова.

Как 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 изоляции. Теперь, ревьюя federated subgraph, сразу проверяй __resolveReference: если он идёт в базу напрямую вместо DataLoader — бесплатный сетевой батч federation скрывает per-representation SQL-шторм под собой.

Практика

Начни сверху. Задачи идут от простого к сложному: вспомнить факт, применить к случаю, затем senior-уровень. Открой, попробуй, потом открой ответ.

вспомнитьприменитьуглубить0 из 5 завершено
Связанные уроки
встречается в228

Что-то непонятно?

Задай вопрос по этому уроку. Вопросы анонимны и попадают напрямую автору — урок станет лучше.

хоткеи развернуть
поиск
K
пред. пьеса
k
след. пьеса
j
тиры
t
это меню
?
sources2
expand
  1. 01
  2. 02

Trademarks belong to their respective owners. Editorial reference only.