Что такое проблема N+1

Ответ

Проблема N+1 возникает, когда для получения связанных данных выполняется один основной запрос, а затем N дополнительных запросов для каждой записи из основного результата. Это приводит к неоптимальной работе приложения.

Пример с React и GraphQL:

// Плохо: запрашиваем пользователей, затем отдельно их заказы
const { data: users } = useQuery(GET_USERS);
users.map(user => (
  <div key={user.id}>
    {user.name}
    <Orders userId={user.id} /> // Внутри Orders свой запрос GET_ORDERS
  </div>
));

Решение - загружать связанные данные одним запросом:

query GetUsersWithOrders {
  users {
    id
    name
    orders {
      id
      total
    }
  }
}

Проблема характерна для ORM, GraphQL, REST API. Оптимизировать можно через:

  • Жадную загрузку (eager loading)
  • Пакетные запросы
  • Даталоадеры (GraphQL)

Ответ 18+ 🔞

А, слушай, вот эта ваша проблема N+1 — это же классика жанра, просто пиздец! Представь себе: ты такой красавчик, делаешь один запрос, получаешь, скажем, список пользователей. А потом, как идиот, для каждого пользователя по отдельности лезешь в базу за его заказами. Получается один главный запрос и потом ещё N (по количеству пользователей) дополнительных. Это ж овердохуища запросов, сервер просто ляжет и скорчится, как манда с ушами!

Вот смотри, как это выглядит в коде, если делать по-распиздяйски:

// Плохо: запрашиваем пользователей, затем отдельно их заказы
const { data: users } = useQuery(GET_USERS);
users.map(user => (
  <div key={user.id}>
    {user.name}
    <Orders userId={user.id} /> // Внутри Orders свой запрос GET_ORDERS
  </div>
));

Видишь? Получили юзеров, а потом для каждого юзера компонент Orders полезет делать свой собственный запрос. Это ж ебаный цирк, а не оптимизация! Сервер будет охуевать от такого напора.

А решение-то проще пареной репы, ёпта! Надо не распыляться, а взять и за один раз всё, что нужно, выгрести. Как шваброй по полу. В GraphQL это делается элементарно:

query GetUsersWithOrders {
  users {
    id
    name
    orders {  // Всё! Заказы сразу прицепляются к пользователю в одном запросе.
      id
      total
    }
  }
}

Вот и вся магия. Один запрос — и у тебя уже есть пользователи вместе с их заказами. Никакой лишней возни.

Эта проблема, кстати, вездесущая, как хитрая жопа. Встречается и в ORM, и в REST API. Но и решений на неё — хоть залейся. Можно использовать жадную загрузку (eager loading), чтобы связанные данные подтягивались сразу. Можно делать пакетные запросы, чтобы вместо тысячи мелких — один здоровенный. В том же GraphQL есть даталоадеры, которые такие N+1 запросы в фоновом режиме собирают в пачки и оптимизируют.

Главное — не быть как тот Герасим из рассказа, который всё по одному делал. Думай головой, э бошка думай! Сделал один раз — и свободен.