はじめに
私は自社の開発業務において、ビジネスチャットツールであるChatworkの情報をベースに
Google Apps Script(以下、GAS)でスクリプトを作成し社員や管理部署の恒久対応の自動化を行っています。
GASでChatwork APIと連携した自動処理を組んだ際にメインロジックは問題なかったものの意図せずエラーとなってしまうことがありました。
今回はその2つの壁について解説します。
-
GASの実行時間が6分で強制終了される
→ GASの6分制限問題 -
Chatwork APIの呼び出し制限(429エラー)に引っかかる
→ ChatworkAPIの429エラー問題
この記事では、実際に月次で連携したいGoogle Drive上のファイルのURLをChatworkでDM送信する処理を組んだ際に発生した事象、行った対策を紹介します。
※また今回は主にChatworkのエラーによるAPI呼び出し制限に着目した記事となっていますが、
他のビジネスチャットツールAPIによっては別の対策が必要となる点には注意してください。
GASの6分制限問題
GASには1回の実行につき最大6分という制限があります。
これを引いてしまうとスクリプトエラー「Exceeded maximum execution time」として処理が強制的に終了してしまいます。
処理に時間がかかる場合には、6分以内には収まらず、スクリプトエラーとなってしまいます。
自身の作成したスクリプトでは社員全員の処理を行っていましたが、社員数が増えたことでこのエラーを誘発してしまいました。
対策:進捗をスプレッドシートに保存して再開する
処理を「フェーズ」に分割し、どこまで処理したかをスプレッドシートに記録しておきます。
(例:「社員情報取得フェーズ」や「ドライブの処理」などに分ける。)
それにより時間切れになったらトリガーで再起動し、続きから処理を再開します。
厳密には時間切れになる前(6分であれば5分などで通信や処理の遅れによるバッファを確保)にスプレッドシート上にフェーズを出力しておきます。
フェーズ設計の例
// 社員情報の取得
const PHASE_CONTACTS = 'phase1';
// Google DriveのURL記入
const PHASE_URLS = 'phase2';
// DMを送信
const PHASE_SENDING = 'phase3';
// 全処理の完了
const PHASE_COMPLETE = 'complete';
進捗の保存・読み込み
詳細は長くなるため本記事では省略しますが、saveProgressでは以下の引数を用いて進捗を保存し、
次回実行時に読み込むことで処理を途中から再開できるようにしています。
phase:処理フェーズ
lastProcessedRow:最終処理行(ユーザー)
apiCallCount:APIの実行カウント数
startTime:処理開始時刻
totalUsers:総社員数
// 進捗保存
function saveProgress(phase, lastProcessedRow, apiCallCount, startTime, totalUsers) {
progressManager.save(phase, lastProcessedRow, apiCallCount, startTime, totalUsers);
}
// 進捗読み込み
function getProgress() {
return progressManager.get();
}
処理ループ内での中断チェック
残り実行時間を監視して、時間切れが近づいたらその時点の行番号を保存して終了します。
for (let i = resumeFromRow; i < totalCount; i++) {
// 処理実行時間・API制限チェック
const stopResult = limiter.checkAndStop(i);
if (stopResult) {
// ここまでの処理結果をシートへ書き込む
sheet.getRange(2, 1, values.length, REQUIRED_COLS).setValues(values);
SpreadsheetApp.flush();
return stopResult;
}
// 他の処理
}
トリガーによる自動再開
また、GASで処理を中断した際には自動では実行されないため、
次回の実行を処理実行時間(1分後)、API制限チェック(5分後)に遅延しトリガーを設定する関数も用意しています。
function scheduleNextRun(delayMs) {
// delayMs後にrunAllProcessesを起動するワンタイムトリガーを設定
triggerManager.schedule(delayMs);
}
これによりメイン処理の先頭で進捗を読み込み、どのフェーズから再開するか判定しトリガーを再設定することで、
処理が途中から断続的に再開されます。
ChatworkAPIの429エラー問題
ChatworkのAPIには呼び出し回数の上限があります。
これを超過すると429 Too Many Requestsとして処理が強制的に終了してしまいます。
Chatworkにおいて1コンタクトあたりのAPI呼び出し回数は以下のようになっているようです。
| 処理内容 | API呼び出し |
|---|---|
| コンタクト&ルーム一覧取得 | 1回 |
| DMルーム特定(全DMルームのメンバーを1件ずつ確認) | DMルーム数分 |
| DM送信 | 1回/人 |
DMルームの特定が最もAPI消費が多く、コンタクト数 × DMルーム数が膨らむと簡単に制限に達してしまいます。
今回はDMの送信処理も合わせて行っているスクリプトであったため、こちらに引っかかってしまいました。
対策1:API呼び出し回数をカウントして上限前に停止する
安全マージンを設けた上限を定数で管理します。
// 安全マージン20を残して280に設定
const API_CALL_LIMIT = 280;
// 制限時は5分待機
const API_WAIT_TIME = 300000;
// API呼び出しのたびにカウントをインクリメント
limiter.incrementApi(1);
// ループ内で定期的にチェック
const stopResult = limiter.checkAndStop(i);
if (stopResult) {
// 進捗を保存して一旦終了。待機後に再開トリガーをセット
return stopResult;
}
対策2:リトライ処理(指数バックオフ)
429が返ってきた場合に即エラーにせず、待機してリトライします。
const result = UtilityFunction.ApiRetryHelper.fetchWithRetry(
endpoint,
options,
// 最大リトライ回数
MAX_RETRY,
// 待機時間(ミリ秒)
API_WAIT_TIME
);
if (!result.success) {
log(`APIエラー: ${result.error}`);
}
対策3:各API呼び出しの間にsleepを入れる
送信先であるDMのルーム特定のために社員を1件ずつ取得する処理では、
連続呼び出しになりやすいため意図的に間隔を空けます。
function findDmRoomIdWithLimiter(rooms, targetAccountId, ...) {
for (const room of rooms) {
// 1秒待機
Utilities.sleep(1000);
const result = UtilityFunction.ApiRetryHelper.fetchWithRetry(
`${apiUrl}/rooms/${room.room_id}/members`,
{ method: 'get', headers: { 'X-ChatWorkToken': apiToken } },
MAX_RETRY,
API_WAIT_TIME
);
limiter.incrementApi(1);
if (result.success) {
const members = JSON.parse(result.response.getContentText());
if (members.some(m => m.account_id === targetAccountId)) {
return { roomId: room.room_id, stopResult: null };
}
}
}
return { roomId: null, stopResult: null };
}
上記の3パターンがありましたが、今回は最初の説明にあるGASの実行制限も併せて考慮しなければならないため
主には「対策1:API呼び出し回数をカウントして上限前に停止する」を採用し、
指定した呼び出し回数に達する前に再起動トリガーを設定し待機することでどちらのエラーも回避可能にしています。
まとめ
| 問題 | 対策 | 参考リンク |
|---|---|---|
| GASの6分制限 | フェーズ分割 + 進捗保存 + トリガーによる自動再開 | developers.google.com |
| ChatworkのAPI制限(429) | 呼び出し回数カウント ( + リトライ処理 + sleep) | chatwork.com |
今回はGASの仕様である「6分制限」とChatwork APIの「呼び出し回数制限(429)」に関しての回避策を立ててみました。
どちらの問題においても言えるのは「処理を途中で止めて、後で続きから再開できる設計にする」という考え方が基本となっています。
同じようにChatwork API連携の自動処理をGASで組む場合の参考になれば幸いです。
