GitHub REST APIからGraphQLへ移行して7分→20秒に高速化した話

GitHub REST APIからGraphQLへ移行して7分→20秒に高速化した話 Git
スポンサーリンク

GitHubリポジトリの開発活動を分析するWebアプリを開発中、データ取得に7分もかかる問題に直面しました。そこで、REST APIからGraphQL APIに移行した結果、7分 → 約20秒と約20倍の高速化を達成しました。

この記事では、なぜそこまで遅かったのか、どう改善したのかを実測データとともに解説します。

背景:何を作っていたか

チームの開発パフォーマンスを可視化するツールを作っていました。その中の機能の一つとして、PRのスループット分析(どれくらいの速度でPRがマージされているか)を実装しようとしていました。

取得が必要だったデータは以下の通りです。

  • コミット履歴(139件)
  • PR一覧とその詳細情報(165件、うちマージ済み147件)
  • 各PRの作成日時・マージ日時
  • 変更行数(追加・削除)
  • レビューコメント(251件)

これらのデータを取得するために、最初はGitHub REST APIを使っていました。

問題:なぜ7分もかかっていたのか

いっぱいAPIを叩いていた

GitHub REST APIでPRの詳細情報を取得するには、複数のエンドポイントを叩く必要があります。

GET /repos/{owner}/{repo}/pulls               → PR一覧(基本情報のみ)
GET /repos/{owner}/{repo}/pulls/{pull_number} → PR詳細(変更行数など)

PR一覧のエンドポイント(pulls.list)では、変更行数(additions/deletions)が含まれていません。スループット分析には変更行数が必要なため、マージ済みPRごとに詳細エンドポイント(pulls.get)を叩く必要がありました。

実際のリクエスト数

実際に分析した対象は、コミット139件、PR165件(うちマージ済み147件)のリポジトリでした。

REST APIでは以下のリクエストが発生していました。

コミット一覧:        2リクエスト
コミット詳細:        139リクエスト
PR一覧:             2リクエスト
PR詳細:             147リクエスト
レビューコメント:    165リクエスト(PRごと)
─────────────────────────────────────────
合計:               約455リクエスト

さらに、レート制限に引っかからないよう待機処理も入れていたため、実際の処理時間は7分にも及んでいました。

解決策:GraphQL APIへの移行

GraphQLなら1回で全部取れる

GitHub GraphQL API(v4)では、1回のリクエストで必要なデータをすべて指定して取得できます。

query {
  repository(owner: "owner", name: "repo") {
    defaultBranchRef {
      target {
        ... on Commit {
          history(first: 100) {
            nodes {
              oid
              message
              additions      # REST APIでは別リクエストが必要だった
              deletions      # REST APIでは別リクエストが必要だった
              changedFilesIfAvailable
            }
          }
        }
      }
    }
  }
}

移行後のリクエスト数

コミット:           2リクエスト
PR:                2リクエスト
レビューコメント:    165リクエスト(PRごと、並列処理)
─────────────────────────────────────────
合計:               約170リクエスト

リクエスト数は約300回から約170回に減りましたが、レビューコメント取得は一気に取得できなかったので、並列処理を行うことで実行時間を短縮しました。

結果:Before/After比較

実際にコミット139件、PR165件のリポジトリを分析した結果です。

指標REST APIGraphQL改善
処理時間7分約20秒約20倍高速化
リクエスト数約455回約170回約60%

GraphQLにしたけどリクエスト数を抑えられなかった箇所もあるので、処理時間は並列化の恩恵も大きいのかなとも思います。

移行で気をつけたこと

1. インターフェースを変えない

今回の移行では、アプリケーション内部のインターフェースを変更しませんでした。

// このインターフェースは移行前後で同じ
interface IGitHubRepository {
  getPullRequests(
    owner: string,
    repo: string,
    sinceDate?: Date
  ): Promise<Result<PullRequest[]>>;
}

REST APIを呼び出していた実装クラスの中身だけをGraphQLに置き換えたため、呼び出し側のコードは一切変更不要でした。

Clean Architectureでインターフェースと実装を分離していたことが、スムーズな移行につながりました。

2. テストの互換性

既存のテストは30件ありましたが、すべてそのまま通りました。

変更したのはモックのレスポンス形式くらいです。インターフェースを変更してないので、テストの期待値(「PRが正しく取得できること」など)は同じであるため、テストコードの修正は最小限で済みました。

3. GraphQLでも1回100件の制限がある

「GraphQLなら1回で全部取れる」と思いがちですが、GitHub GraphQL APIも1回のリクエストで取得できるのは最大100件です。

# first: 100 が上限
pullRequests(first: 100, after: $cursor) {
  nodes { ... }
  pageInfo {
    hasNextPage
    endCursor  # 次ページ取得用のカーソル
  }
}

165件のPRを取得するには、REST APIと同様に2回のリクエストが必要でした。

ただし、GraphQLの強みは1回のリクエストで詳細情報まで含められる点です。REST APIでは「一覧取得 → 詳細取得」と2段階必要だったのが、GraphQLでは1段階で済みます。

まとめ

GitHub REST APIからGraphQL APIへの移行で、リポジトリ分析処理を7分 → 約20秒に高速化できました。

以下のような状況なら、GraphQL移行を検討する価値があります。

  • 複数のエンドポイントを順番に叩いている
  • リストの各要素に対して詳細取得をしている
  • レスポンスに不要なフィールドが多く含まれている

GraphQLクエリの詳しい書き方などは、気が向けば別の記事に書こうかなと思います。

このブログでは、【ConoHa WING】を使っています

このブログでは、【ConoHa WING】を使っています
ブログ村を利用しています
素人エンジニアの苦悩 - にほんブログ村
PVアクセスランキング にほんブログ村
Git
スポンサーリンク
スポンサーリンク
シェアする
amateur_engineerをフォローする
素人エンジニアの苦悩

コメント

タイトルとURLをコピーしました