web-dev-qa-db-ja.com

大きなデータを解決すると、Apolloサーバーのパフォーマンスが低下する

大きなデータを解決するとき、リゾルバーからクライアントに結果を返す瞬間から、パフォーマンスが非常に遅いことに気づきました。

私はapollo-serverが私の結果を反復処理して型をチェックすると想定しています...

私の製品では、UIでチャートを描画するために一度に使用されるため、大量のデータを一度に返す必要があります。データをスライスできるページネーションオプションはありません。

私はapollo-serverが原因で速度が低下しているのではなく、リゾルバオブジェクトの作成ではないと考えています。

リゾルバーがオブジェクトを作成するのにかかった時間と、高速であり、ボトルネックではないことに注意してください。

測定方法がわからないapollo-serverによるその後の操作には、かなりの時間がかかります。

これで、カスタムスカラー型JSONを返すバージョンができました。応答ははるかに高速です。しかし、私は自分のSeriesタイプを返すことを本当に好みます。

ネットワークパネルを見て、2つのタイプ(SeriesJSON)の違いを測定します。

aMOUNTが500に設定され、タイプがSeriesの場合、約1.5秒(つまり秒)かかります

aMOUNTが500に設定され、タイプがJSONの場合、約150ミリ秒(高速!)

aMOUNTが1000に設定されていて、タイプがSeriesの場合、非常に遅い...

aMOUNTが10000に設定されており、タイプがSeriesの場合、JavaScriptヒープがメモリ不足になります(これは、残念ながら私たちの製品で発生していることです)


また、apollo-serverのパフォーマンスをexpress-graphqlと比較しました。後者は高速に動作しますが、カスタムスカラーJSONを返すほど高速ではありません。

aMOUNTを500に設定すると、apollo-server、ネットワークに1.5秒かかります

aMOUNTを500に設定すると、express-graphql、ネットワークに800ミリ秒かかります

aMOUNTが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.log("resolver took: ", after);

      return result
    }
  }
};

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

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


Playgroundのgqlクエリ(シリーズタイプの場合):

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

Playgroundのgqlクエリ(カスタムスカラータイプJSONの場合):

query {
  complex
}

これが実際の例です:

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

どんなリード/アイデアも大歓迎です!

6
sergelerner

コメントの要約

このデータ構造/タイプ:

  • 個別のエンティティではありません。
  • 一連の[グループ化された]データのみ。
  • 正規化は必要ありません。
  • アポロキャッシュで正常に正規化されません(idフィールドなし)。

このようにこのデータセットはgraphQLが設計されたものではありません。もちろん、graphQLはこのデータのフェッチに引き続き使用できますが、型の解析/マッチングは無効にする必要があります。

カスタムスカラータイプgraphql-type-json)が解決策になることがあります。ハイブリッドソリューションが必要な場合は、Group.values as json(代わりにSeries全体)。正規化されたキャッシュ[アクセス]を使用する場合は、グループにidフィールドが必要です。

オルタナティブ

apollo-link-rest 「純粋な」jsonデータ(ファイル)をフェッチし、タイプの解析/マッチングをクライアント側のみに残します。

より高度な代替

1つのgraphqlエンドポイントを使用したい場合...独自のリンクを作成します-ディレクティブを使用します-'jsonを要求し、入力します'-上記の2つの組み合わせ。 de-/serializersとの残りのリンクのようなSth。


どちらの方法でも-なぜ本当に必要なのですか?描画のためだけですか?努力する価値はありません。ページネーションはありませんが、うまくいけばストリーミング(ライブ更新?)できるが「気分が悪い」。

0
xadm