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 API | GraphQL | 改善 |
|---|---|---|---|
| 処理時間 | 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クエリの詳しい書き方などは、気が向けば別の記事に書こうかなと思います。

コメント