В эталонных реализациях эталонной реализации GraphQL ожидается возвращение Iterable, как вернуть асинхронный Iterable? - PullRequest
0 голосов
/ 30 сентября 2018

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

// root resolver
function allPersons(...) {
  [...]
  return {
    nextId: 1,
    maxId: 10000000, 
    [Symbol.iterator]: () => { return this },
    next: function() {
      let nextRes = { done: true, value: null }
      if (this.nextId <= this.maxId) {
        nextRes.value = sequelize.models.person.findById(this.currId)
        nextRes.done = false
        this.nextId = this.nextId + 1
      }
      return nextRes
    }
}

Вышеописанное работает, потому что построенное Sequelize Promise возвращается как next() '* value.Когда это значение-Promise разрешается, он выбирает одну запись из базовой реляционной базы данных.Таким образом, я синхронно строю асинхронно извлекаю данные.Это работает только потому, что каждый выбор не зависит от других.В частности, перед выполнением следующей операции не нужно редактировать await.Однако выборка за строкой реляционной базы данных технически неэффективна и фактически является анти-паттерном.Таким образом, я хотел бы реализовать буфер, который выбирает пакеты, скажем, по 10 тыс. Строк, обслуживает их до тех пор, пока пакет не станет пустым, а затем извлекает следующий.Однако из-за введенной зависимости асинхронных событий для ее реализации потребуется асинхронный итератор (Symbol.asyncIterator).

Что нужно сделать, чтобы сделать GraphQL's reference implementation (graphql-js и / или express-graphql) принять асинхронный итератор?Обращаем ваше внимание, что я бы хотел избежать использования Apollo GraphQL .

. Может ли Object-Stream стать возможным решением?

Помощь будет высоко ценится..

Ответы [ 2 ]

0 голосов
/ 01 октября 2018

Половина решения: использовать потоки и преобразовать их в синхронный итератор

Поскольку ожидается, что преобразователи GraphQL будут возвращать синхронные итераторы, потоки можно использовать для подачи своих данных в такой итератор,Рассмотрим следующее решение оригинального примера, как указано в вопросе.Обратите внимание, что популярный ORM Sequelize не поддерживает потоки, и поэтому здесь используется другой пакет узлов knex.

// Setup:
const knex = require('knex')
var dbCon = knex({
  client: 'pg',
  connection: {} // Define host, user, password, db (see knex docu)
})

// Get records as stream
var peopleStream = dbCon.select('*').from('people').stream()

// Serve stream within an synchronous iterator
var iter = {
  [Symbol.iterator]: () => {
    return this
  },
  next: function() {
    let v = peopleStream.read() || null
    console.log(JSON.stringify(v)) // Check, if it works.
    return {
      done: v === null,
      value: v
    }
  }
} 

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

0 голосов
/ 30 сентября 2018

GraphQL.js использует iterall под капотом.Для поддержки асинхронных итераций базовый код должен будет использовать метод forAwaitEach из этой библиотеки вместо метода forEach, который используется сейчас.Это может быть возможно, но я не уверен, что это не нарушит другие функции.

Если все, что вы хотите сделать, это извлечь все people в некоторых кусках произвольного размера, вам не нужночтобы сделать что-то особенно необычное:

async function getAllPeople () {
  const chunkSize = 10000
  const startId = 1
  const endId = await sequelize.models.person.max('id')
  const people = []

  let lower = startId
  let upper = startId + chunkSize

  while (upper < (endId + 1)) {
    const chunk = await sequelize.models.person.findAll({
      where: {
        id: {
          [Op.and]: {
            [Op.gte]: lower,
            [Op.lt]: upper,
          }
        }
      },
    })
    people.push(chunk)
    lower = lower + chunkSize
    upper = upper + chunkSize
  }

  return people
}

РЕДАКТИРОВАТЬ: Чтобы обойти проблему с памятью, вам придется эффективно разбить полезную нагрузку на несколько ответов и иметь возможность вернуть их обратно.вместе на стороне клиента.В дорожной карте Аполлона есть директива @stream, которая делает именно это, и я думаю, что некоторые люди экспериментировали с ней, но я думаю, что может пройти некоторое время, прежде чем мы увидим зрелую реализацию этого.@defer имеет аналогичный механизм и в настоящее время поддерживается Apollo, но работает на уровне разрешения, поэтому в данном случае это не очень поможет.

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

Я думаю, к сожалению, самое простое решение с учетом нынешнего инструментария - просто реализовать разбиение на страницы назапрос и клиентский кусок вместе общий результат.

...