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

API

GraphQL N+1: чтение кода

Суть Читай реальный код резолверов и DataLoader, предсказывай поведение N+1 или корректности и выбирай фикс с наибольшим рычагом.
Высота — путь к senior
НольJuniorMiddleSenior
Ты на senior-высоте — в орбите
◷ 14 min

Проблемы N+1 диагностируются в коде резолверов, в batch-функциях и на слое валидации — не в прозе. Прочитай каждый сниппет, предскажи, что он делает под 50 постов или враждебного клиента, и выбери фикс, который senior-инженер делает первым.

Цель

Отработай цикл, который ты запускаешь на каждом ревью GraphQL: замечай пер-полевой fetch, проверяй контракт batch и подтверждай гейт по форме запроса — до того как лог БД вспыхнет в production.

Сниппет 1 — резолвер, выстреливающий N+1

const resolvers = {
  Query: {
    posts: () => db.query('SELECT * FROM posts LIMIT 50'),
  },
  Post: {
    // вызывается по разу на пост, в изоляции
    author: (post) => db.query(
      'SELECT * FROM users WHERE id = $1', [post.authorId]
    ),
  },
};
Викторина

Для posts { title author { name } } на 50 постов сколько запросов к БД сработает и какой структурный фикс?

Сниппет 2 — batch-функция

async function batchAuthors(ids) {
  const rows = await db.query(
    'SELECT id, name FROM users WHERE id = ANY($1)', [ids]
  );
  // возвращает строки в том порядке, что выбрал Postgres
  return rows;
}
Викторина

Эта batch-функция возвращает сырые строки. В чём скрытый баг и как его фиксить?

Сниппет 3 — one-to-many loader

async function batchTags(postIds) {
  const rows = await db.query(
    'SELECT post_id, tag FROM tags WHERE post_id = ANY($1)', [postIds]
  );
  const map = new Map();
  rows.forEach(r => {
    if (!map.has(r.post_id)) map.set(r.post_id, []);
    map.get(r.post_id).push(r.tag);
  });
  return postIds.map(id => map.get(id));   // посты без тегов?
}
Викторина

У поста с нулём тегов нет строк, поэтому map.get(id) — undefined. Что ломается и каков фикс?

Сниппет 4 — гейт валидации

const server = new ApolloServer({
  schema,
  validationRules: [depthLimit(10)],   // ограничен только depth
});
Викторина

Этот сервер ограничивает depth до 10, но больше ничего. Клиент шлёт 5-уровневый запрос с first: 100 на каждом уровне. Что проходит и какого слоя не хватает?

Итог

Каждый инцидент N+1 в GraphQL читается в коде: пер-полевой резолвер, тянущий по строке на родителя (51 запрос → DataLoader), batch-функция, доверяющая порядку строк БД (порча → Map по ключу), one-to-many loader, теряющий пустые массивы (ошибка валидации → дефолт в []), и слой валидации, ограничивающий depth, но не разрастание строк (утечка 100^5 → мультипликативный complexity плюс алиас-капы). Читай резолвер, проверяй контракт batch, подтверждай гейт — затем перетрассируй счётчики резолверов, чтобы убедиться, что фикс держится.

Продолжить восхождение ↑GraphQL N+1: сбатчь и закали API
хоткеи развернуть
поиск
K
пред. пьеса
k
след. пьеса
j
тиры
t
это меню
?
sources3
expand
  1. 01
  2. 02
  3. 03

Trademarks belong to their respective owners. Editorial reference only.