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

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

Читай реальный код резолверов и DataLoader, предсказывай поведение N+1 или корректности и выбирай фикс с наибольшим рычагом.

API Senior ◷ 14 min
Уровень
ОсновыJuniorMiddleSenior

Проблемы 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, подтверждай гейт — затем перетрассируй счётчики резолверов, чтобы убедиться, что фикс держится.

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

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

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

Trademarks belong to their respective owners. Editorial reference only.