Низкая производительность сервера Apollo при разрешении больших данных

При разрешении больших данных я замечаю очень медленную производительность, с момента возврата результата от моего преобразователя клиенту.

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

В моем продукте мне приходится возвращать сразу большой объем данных, так как он используется сразу для рисования диаграммы в пользовательском интерфейсе. Для меня нет опции разбивки на страницы, где я могу нарезать данные.

Я подозреваю, что медлительность исходит из apollo-server, а не из-за создания моего объекта распознавателя.

Обратите внимание, что я регистрирую время, необходимое преобразователю для создания объекта, это быстро, а не узкое место.

Более поздние операции, выполняемые apollo-server, которые я не знаю, как измерить, занимают очень много времени.

Теперь у меня есть версия, в которой я возвращаю пользовательский скалярный тип JSON, ответ намного быстрее. Но я действительно предпочитаю возвращать свой тип Series.

Я измеряю разницу между двумя типами (Series и JSON), глядя на сетевую панель.

когда СУММА установлена ​​на 500 и тип Series, это занимает ~ 1,5 с (то есть секунды)

когда AMOUNT установлено на 500, а тип JSON, это занимает ~ 150 мс (быстро!)

когда СУММА установлена ​​на 1000, а тип Series, это очень медленно...

когда AMOUNT установлено на 10000, а тип — Series, я получаю кучу JavaScript из памяти (к сожалению, это то, что мы наблюдаем в нашем продукте)


Я также сравнил производительность apollo-server с express-graphql, последний работает быстрее, но все же не так быстро, как возвращает пользовательский скалярный JSON.

когда AMOUNT установлено на 500, apollo-server, сеть занимает 1,5 секунды

когда AMOUNT установлено на 500, express-graphql, сеть занимает 800 мс

когда СУММА установлена ​​​​на 1000, apollo-server, сеть занимает 5,4 с.

когда AMOUNT установлено на 1000, express-graphql, сеть занимает 3,4 с.


Стек:

"dependencies": {
  "apollo-server": "^2.6.1",
  "graphql": "^14.3.1",
  "graphql-type-json": "^0.3.0",
  "lodash": "^4.17.11"
}

Код:

const _ = require("lodash");
const { performance } = require("perf_hooks");
const { ApolloServer, gql } = require("apollo-server");
const GraphQLJSON = require('graphql-type-json');

// The GraphQL schema
const typeDefs = gql`
  scalar JSON

  type Unit {
    name: String!
    value: String!
  }

  type Group {
    name: String!
    values: [Unit!]!
  }

  type Series {
    data: [Group!]!
    keys: [Unit!]!
    hack: String
  }

  type Query {
    complex: Series
  }
`;

const AMOUNT = 500;

// A map of functions which return data for the schema.
const resolvers = {
  Query: {
    complex: () => {
      let before = performance.now();

      const result = {
        data: _.times(AMOUNT, () => ({
          name: "a",
          values: _.times(AMOUNT, () => (
            {
              name: "a",
              value: "a"
            }
          )),
        })),
        keys: _.times(AMOUNT, () => ({
          name: "a",
          value: "a"
        }))
      };

      let after = performance.now() - before;

      console.info("resolver took: ", after);

      return result
    }
  }
};

const server = new ApolloServer({
  typeDefs,
  resolvers: _.assign({ JSON: GraphQLJSON }, resolvers),
});

server.listen().then(({ url }) => {
  console.info(`? Server ready at ${url}`);
});


Запрос gql для Playground (для серии):

query {
  complex {
    data {
      name
      values {
        name
        value
      }
    }
    keys {
      name
      value
    }
  }
}

Запрос gql для игровой площадки (для пользовательского скалярного типа JSON):

query {
  complex
}

Вот рабочий пример:

https://codesandbox.io/s/apollo-server-performance-issue-i7fk7

Любые выводы/идеи будут высоко оценены!

не связан с graphql - вы тестируете только производительность node js (создание объекта) - таким образом вы даже можете копать криптовалюту в распознавателе и обвинять graphql

xadm 02.06.2019 19:46

@xadm Я не думаю, что это связано с graphql, я этого не говорил. Я думаю, что это связано со следующей операцией apollo-server (независимо от того, является ли она gql lib, если это поможет) после того, как я создаю объект в своем распознавателе. Мое создание объекта происходит быстро, то, что происходит дальше, происходит медленно, вплоть до нехватки памяти... Я думаю, что мой пример stringify доказывает это. У меня вопрос, как преодолеть этот предел?

sergelerner 03.06.2019 08:04

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

xadm 03.06.2019 08:15

@xadm Я не знаю, как измерить общий результат процесса, поскольку, я полагаю, это происходит внутри внутреннего кода apollo-server. Я измерил время создания моего объекта-преобразователя, которое я записываю, как я уже писал, вы можете увидеть это в примере. Еще одна вещь, которую я смог измерить, — это сетевое время и разные результаты, когда я строю объект, а не нет. Что касается того, нужно ли мне все это сразу, ну, прямо сейчас да, это часть графика пользовательского интерфейса, который я рисую на клиенте, или таблица с множеством столбцов. К сожалению, нет опции разбивки на страницы, которая могла бы позволить мне получать части.

sergelerner 03.06.2019 08:54

вероятно, вам не нужны мелкие детализированные данные - вы можете использовать пользовательские скалярные типы для возврата всей серии как одного объекта - если вам действительно нужна подробная грануляция, вы можете сделать это позже, только на стороне клиента

xadm 03.06.2019 09:36

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

sergelerner 03.06.2019 09:45

можно использовать подписки, опросы

xadm 03.06.2019 09:47

Пользовательский скалярный JSON @xadm — лучшее решение, чем мой хак stringify, а также такой же быстрый. Все еще не чувствую себя хорошо, так как по пути я теряю большую часть концепции gql.

sergelerner 03.06.2019 10:26

@sergelerner У меня нет ответа, но мы сталкиваемся с похожими проблемами. Хотел бы спросить, что вы считаете «большими данными»? Сколько документов вы запрашиваете? Я имею в виду не возвращение, но каков размер БД... Приблизительно... Спасибо.

PeS 10.09.2019 03:53
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Что такое Apollo Client и зачем он нужен?
Что такое Apollo Client и зачем он нужен?
Apollo Client - это полнофункциональный клиент GraphQL для JavaScript-приложений, который упрощает получение, управление и обновление данных в...
9
9
7 009
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Сводка комментариев

Эта структура/типы данных:

  • не являются отдельными субъектами;
  • просто ряд [сгруппированных] данных;
  • не нуждаются в нормализации;
  • не будет правильно нормализован в кеше аполлона (без полей id);

Таким образом, этот набор данных не предназначен для graphQL. Конечно, для извлечения этих данных можно использовать graphQL, но синтаксический анализ/сопоставление типов следует отключить.

Использование пользовательские скалярные типы (graphql-type-json) может быть решением. Если вам нужно какое-то гибридное решение — вы можете ввести Group.values как json (вместо целого Series). Группы по-прежнему должны иметь поле id, если вы хотите использовать нормализованный кеш [доступ].

Альтернатива

Вы можете использовать apollo-link-rest для извлечения «чистых» данных (файла) json, оставляя синтаксический анализ/сопоставление типов только на стороне клиента.

Более продвинутая альтернатива

Если вы хотите использовать одну конечную точку graphql... напишите собственную ссылку — используйте директивы — «попросите json, напечатайте» — сочетание двух вышеперечисленных. Как и в остальных ссылках с де-/сериализаторами.


В обоих вариантах - зачем тебе это на самом деле? Просто для рисования? Не стоит усилий. Без разбивки на страницы, но, надеюсь, в потоковом режиме (обновления в реальном времени?) ... без курсоров ... загружать больше (подписки / опросы) по ... последнее обновление? Выполнимо, но «не чувствую себя хорошо».

что вы подразумеваете под «этот набор данных не предназначен для graphQL»? был бы рад понять больше. Спасибо!

sergelerner 04.06.2019 16:26
Ответ принят как подходящий

Есть связанная открытая проблема здесь. Ли Байрон подытожил это очень хорошо:

I think the TL;DR of this issue is that GraphQL has some overhead and that reducing that overhead is non-trivial and removing it completely may not be an option. Ultimately GraphQL.js is still responsible for making API boundary guarantees about the shape and type of the returned data and by design does not trust the underlying systems. In other words GraphQL.js does runtime type checking and sub-selection and this has some cost.

Преимущества, которые предлагает GraphQL (проверка, дополнительный выбор и т. д.), неизбежно влекут за собой некоторые накладные расходы, поскольку требуют дополнительной обработки данных, которые вы возвращаете. И, к сожалению, эти накладные расходы зависят от размера данных. Я предполагаю, что если бы вы реализовали конечную точку REST, которая поддерживала бы частичные ответы и выполняла проверку ответов, используя что-то вроде Swagger или Joi, вы бы столкнулись с похожей проблемой.

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

Как правило, большие наборы данных, подобные этому, следует разбивать путем реализации разбивки на страницы. Если это не вариант, следующим лучшим подходом будет использование пользовательский скаляр. Самым большим недостатком этого подхода является то, что клиенты, использующие ваш API, не смогут запрашивать определенные поля внутри возвращаемого вами объекта JSON. Помимо исправление GraphQL.js, нет другой альтернативы для ускорения ответов и сокращения использования памяти.

Другие вопросы по теме