🧰

レスポンスヘッダにプロファイラのURLを含めるとGraphQLのパフォーマンスチューニングに便利という話

2023/08/31に公開

こんにちは!株式会社アルダグラムのKANNAの開発お手伝いをさせて頂いているoubakiouです。

KANNAではサーバーサイドにRails+GraphQL Ruby、クライアントサイドでApollo Clientを利用していますが、どこの会社であれGraphQLであれRESTであれサービスが成長するとその裏側のパフォーマンスチューニングが必要になる場面が大なり小なり訪れると思います。KANNAもその例に漏れずユーザー数の増加や今までにない規模のお客様企業への導入に伴い、それまでは問題の無かったGraphQLクエリーのいくつかでレスポンス速度に問題が出始めていました。

本番環境においてはDatadogなどで監視環境を構築しスロークエリーやパフォーマンスの監視を行っていますが、開発環境においてはRails部分にrack-mini-profiler、graphql-ruby部分に独自Tracerを導入しパフォーマンス改善作業に利用する事にしました。体感上GraphQLクエリーのパフォーマンス問題のうち90%はSQLに、9%はメモリ(ARインスタンス数とか)に、残り1%はgraphql-rubyなどGraphQLサーバー自体に起因していますが、そのうち今回はSQL(rack-mini-profiler導入)についての話です。

rack-mini-profiler自体の導入手順については割愛しますが、通常であればrack-mini-profilerを導入するとRailsが返すHTMLをブラウザ表示した際に画面左上へこのようなスピードバッジと呼ばれる要約パネルが表示されるようになります。

このパネルからは更に実行されたSQL一覧画面や、プロファイラ(stackprofmemory_profiler)に移動して詳細を確認する事ができます。しかしGraphQLサーバーで利用する場合にはこのパネルがそのまま使えるわけではありません。公式でSPA向けにスクリプトタグを埋め込む方法が紹介されていますが我々のGraphQLクライアントはブラウザだけではないのでこれは利用せず、より汎用性の高そうなGraphQLのレスポンスヘッダにプロファイラページのURLを仕込む方法を取りました。

result = if Rails.env.development?
           # 開発環境であればレスポンスヘッダにプロファイラー表示のURL(ここではlocalhost決め打ち)を追加
           response.set_header('Access-Control-Expose-Headers', 'X-MiniProfiler-Url')
           response.set_header('X-MiniProfiler-Url', "http://127.0.0.1:8080/mini-profiler-resources/results?id=#{Rack::MiniProfiler.current.page_struct[:id]}")
           Rack::MiniProfiler.step("<h3 style=\"color:#00a26a\">#{operation_name}</h3>") do |_test|
             HogeSchema.execute(query, variables: variables, context: context, operation_name: operation_name)
           end
         else
           HogeSchema.execute(query, variables: variables, context: context, operation_name: operation_name)
         end

("X-"ヘッダは非推奨になったけど我々はブラウザベンダではないので・・)

GraphQLスキーマを実行している場所(この場合はRails上のGraphQL用controller)で上記のようにレスポンスヘッダへプロファイラのURLを仕込みます。クライアントサイドからは何らかの方法でこのヘッダを直接見てもよいのですが、Web版KANNAではApollo ClientのカスタムLinkを作成してレスポンスヘッダからクライアントサイドのコンソールに流しています。

// web版
const miniProfileLink = new ApolloLink((operation, forward) => {
    return forward(operation).map(response => {
      const context = operation.getContext()
      const {
        response: { headers }
      } = context

      if (headers) {
        const miniProfilerUrl = headers.get('X-MiniProfiler-Url')
        const logoUrl =
          'data:image/svg+xml;charset=utf8,%3Csvg%20viewBox%3D%中略'
        miniProfilerUrl &&
          console.log(
            `%c   %c${operation.operationName}\n\n%o\n`,
            `background-image: url('${logoUrl}');background-repeat: no-repeat; padding:1px;`,
            'color:#00a26a;font-weight:bold;',
            miniProfilerUrl
          )
      }
      return response
    })
  })

// 中略
ApolloLink.from([miniProfileLink, hogeLink, fugaLink])

あまり使う機会のないハックですがChrome(Chromium)ではconsoleにbackground-imageを表示する事ができます。

あとはこのリンクをクリックすれば、そのGraphQLクエリに対応したrack-mini-profilerの詳細ページを確認する事ができます

これで例え自身の管理下にない開発環境のGraphQLサーバーであったとしても気軽にSQLや実行時間を確認できるようになりました。パフォーマンスチューニングが捗りますね!

もっとアルダグラムエンジニア組織を知りたい人、ぜひ下記の情報をチェックしてみてください!

アルダグラム Tech Blog

Discussion