前回の記事では、Google Drive APIの使い方としてバッチリクエストを使用することでネットワーク上のオーバーヘッドを抑え、パフォーマンスを向上させる方法をご紹介しました。
前回の記事
バッチリクエストは、複数のリクエストを1つのリクエストにまとめることによって、個別でリクエストした場合のオーバーヘッドを抑えることは可能でしたが、それぞれのリクエストに要する時間自体を短縮できるものではありませんでした。
今回の記事では、バッチリクエストを小分けにし、並列に要求を行うことによって、全体の実行時間を短縮した方法のご紹介です。
API呼び出し方法による実行時間イメージ
1つのバッチリクエストに複数(最大100個)のGoogle Drive APIを詰め込んで実行した場合、Google Drive上では直列に処理されるため、それなりに時間がかかっていました。
一方、APIを小分けにして、複数のバッチリクエストを並列に実行してみたところ、Google Drive上も並列に処理され、処理時間を短縮することができました。
サンプルコード
以下のサンプルは、20個のファイルを4つのバッチリクエストに分けて、並列実行するためのサンプルコードです。
- Google Drive APIを使えるようにプロジェクトは作成されているものとします。
- JavaScriptです。
クライアントライブラリ(googleapis)を使う場合
const {google} = require('googleapis');
const {OAuth2} = google.auth;
// 認証情報を取得する関数
async function getAuth() {
const oAuth2Client = new OAuth2(
'YOUR_CLIENT_ID',
'YOUR_CLIENT_SECRET',
'YOUR_REDIRECT_URL'
);
oAuth2Client.setCredentials({
refresh_token: 'YOUR_REFRESH_TOKEN'
});
return oAuth2Client;
}
// バッチリクエストでファイルをコピーする関数(クライアントライブラリ版)
async function batchCopyFiles(auth, fileIds) {
const driveService = google.drive({version: 'v3', auth});
const batch = new google.BatchRequest();
fileIds.forEach(fileId => {
batch.add(driveService.files.copy({
fileId: fileId,
requestBody: {
name: `YOUR_FILE_NAME_${fileId}`
}
}));
});
try {
const response = await batch.execute();
response.forEach((res, index) => {
if (res.status === 200) {
console.log(`API ${index + 1}が成功しました:`, res.data);
} else {
console.error(`API ${index + 1}が失敗しました:`, res.statusText);
}
});
} catch (error) {
console.error('バッチリクエストは失敗しました:', error);
}
}
// メイン関数
async function main() {
const auth = await getAuth();
// 20個のファイルを4つのバッチリクエストに分けるため、5ファイル×4つの配列を用意
const fileIds = [
['FILE_01', 'FILE_02', 'FILE_03', 'FILE_04', 'FILE_05'],
['FILE_06', 'FILE_07', 'FILE_08', 'FILE_09', 'FILE_10'],
['FILE_11', 'FILE_12', 'FILE_13', 'FILE_14', 'FILE_15'],
['FILE_16', 'FILE_17', 'FILE_18', 'FILE_19', 'FILE_20']
];
const promises = [];
// 合計20回のコピーを4バッチリクエストに分けて実行する
for (let i = 0; i < fileIds.length; i++) {
// 1バッチリクエストに5回分のコピー処理を詰める
promises.push(batchCopyFiles(auth, fileIds[i]));
}
await Promise.all(promises);
console.log('全てのバッチリクエストが完了しました');
}
main();
fetchで呼び出す場合
const {OAuth2} = require('google-auth-library');
const fetch = require('node-fetch');
// 認証情報を取得する関数
async function getAuthToken() {
const oAuth2Client = new OAuth2(
'YOUR_CLIENT_ID',
'YOUR_CLIENT_SECRET',
'YOUR_REDIRECT_URL'
);
oAuth2Client.setCredentials({
refresh_token: 'YOUR_REFRESH_TOKEN'
});
const token = await oAuth2Client.getAccessToken();
return token.token;
}
// バッチリクエストでファイルをコピーする関数(fetch版)
async function batchCopyFilesWithFetch(authToken, fileIds) {
const url = 'https://www.googleapis.com/batch/drive/v3';
const boundary = 'batch_boundary';
let body = '';
fileIds.forEach(fileId => {
body += `--${boundary}\n`;
body += 'Content-Type: application/http\n\n';
body += `POST /drive/v3/files/${fileId}/copy\n`;
body += 'Content-Type: application/json\n\n';
body += JSON.stringify({ name: `YOUR_FILE_NAME_${fileId}` }) + '\n\n';
});
body += `--${boundary}--`;
try {
const response = await fetch(url, {
method: 'POST',
headers: {
'Authorization': `Bearer ${authToken}`,
'Content-Type': `multipart/mixed; boundary=${boundary}`
},
body: body
});
if (!response.ok) {
throw new Error('バッチリクエストの実行に失敗しました');
} else {
console.log('バッチリクエストの実行に成功しました');
}
const data = await response.text();
const parts = data.split(`--${boundary}`);
parts.forEach((part, index) => {
if (part.includes('HTTP/1.1 200 OK')) {
console.log(`API ${index + 1}が成功しました:`, part);
} else if (part.includes('HTTP/1.1')) {
console.error(`API ${index + 1}が失敗しました:`, part);
}
});
} catch (error) {
console.error('バッチリクエストの実行に失敗しました:', error);
}
}
// メイン関数
async function main() {
const authToken = await getAuthToken();
// 20個のファイルを4つのバッチリクエストに分けるため、5ファイル×4つの配列を用意
const fileIds = [
['FILE_01', 'FILE_02', 'FILE_03', 'FILE_04', 'FILE_05'],
['FILE_06', 'FILE_07', 'FILE_08', 'FILE_09', 'FILE_10'],
['FILE_11', 'FILE_12', 'FILE_13', 'FILE_14', 'FILE_15'],
['FILE_16', 'FILE_17', 'FILE_18', 'FILE_19', 'FILE_20']
];
const promises = [];
// 合計20回のコピーを4バッチリクエストに分けて実行する
for (let i = 0; i < fileIds.length; i++) {
// 1バッチリクエストに5回分のコピー処理を詰める
promises.push(batchCopyFilesWithFetch(authToken, fileIds[i]));
}
await Promise.all(promises);
console.log('全てのバッチリクエストが完了しました');
}
main();
まとめ
上記サンプルをベースに、複数のバッチリクエストに小分けにして並列実行した場合、バッチリクエストに全て詰め込んで実行した場合に比べて、実行時間は 約34% 速くなりました。
バッチ処理を並列化することで、APIのパフォーマンスが改善するかどうかは、それぞれのアプリケーションでの検証が必要ですが、同じリクエストでも、呼び出し方を工夫することにより、実行時間を短縮できることが分かりました。
注意事項
並列実行のバッチリクエストに格納するAPIが多くなると、単位時間の間に実行するAPI数が増えるため、実行上限に到達し、処理が失敗する可能性があります。