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

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

В этом случае N обозначает количество результатов, возвращенных из базы данных, а 1 обозначает начальный запрос, который был сделан.

Чтобы уточнить: проблема в том, что для каждого из возвращенных N результатов нам нужно будет сделать другой запрос, поэтому будет N запросов к базе данных плюс 1, который был первоначальным запросом.

Это легко может стать узким местом в производительности.

Пример ситуации N + 1

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

Вот пример псевдокода:

const students = `SELECT * FROM students`
students.forEach(student => 
  `SELECT * FROM grades WHERE student_id = ${student.student_id}`)

Традиционное решение проблемы N + 1

Общепринятое решение - группировать ваши запросы. Это означает, что вы составляете список идентификаторов результатов первоначального запроса, а затем ваш второй запрос динамически заполняется этим списком. Это решение сокращает количество запросов к базе данных с N + 1 до 2.

Пример псевдокода:

const students = `SELECT * FROM students`
const studentIds = students.map(student => student.student_id)
const grades = `SELECT * FROM grades WHERE student_id IN ${studentIds}`

Чтобы уточнить: в приведенном выше коде мы сначала запрашиваем всех студентов, а затем составляем список (также называемый массивом) всех их идентификаторов. Затем мы делаем запрос к базе данных для всех оценок, но также предоставляем ему наш список идентификаторов учащихся и просим базу данных отфильтровать результаты для нас по этим Идентификаторы.

Теперь это намного эффективнее!

Почему это решение не работает с GraphQL

Общепринятой реализацией GraphQL является Apollo, использующий Resolvers. Каждый преобразователь имеет доступ только к возвращаемому значению своего родителя, а это означает, что вы не можете пойти и составить список идентификаторов, как мы сделали выше, а затем передать их в пакетный запрос.

Так как же решить эту проблему? Мы используем пакет под названием DataLoader.

Как решить N + 1 задач в GraphQL

DataLoader - это пакет, который выполняет пакетную загрузку объектов из хранилища данных. Он также имеет кеш мемоизации, который не позволяет нашему приложению загружать один и тот же объект несколько раз из одного запроса GraphQL. Это также позволяет нам получать сразу несколько объектов с помощью пакетной операции, что позволяет избежать проблемы N + 1.

В следующей статье я подробно расскажу, как реализовать DataLoader в вашем проекте GraphQL.

Если вам понравилась эта статья, пожалуйста, аплодируйте! 👏🏼 Хлопки меня радуют;)