この記事は、 Supership株式会社 Advent Calendar 2025 - Qiita の12日目の記事になります。
はじめに
Supership株式会社 の @tanjo です。
Google Apps Script(以下 GAS)快適に使えていますか?
- GAS の実行時間制限に引っかかってしまった。
- スプレッドシートへのアクセスが遅くてタイムアウトしてしまった。
- 大量のデータを処理したいけど、処理が終わらない。
なんてことはあると思います。
いくらコードを書いても、実行時間制限に引っかかっては意味がありません。
闇雲にコードを書くだけでは解決しないので、
ちょっとした工夫で処理速度を改善してみました。
今回は、GAS の処理高速化を行って、実際にタイムアウト問題を解決した話を共有します。
背景
絵文字が9000個以上登録されている Slack ワークスペースで、GAS を使って新規に追加された絵文字を検出し、Slack チャンネルに通知する仕組みを運用していました。
しかし、絵文字の数が多いため、GAS の実行時間制限(6分)に達してしまい、処理が途中で停止してしまう問題が発生しました。
実際に、以下のようなエラーが頻繁に発生していました。
| エラー メッセージ | カウント |
|---|---|
| サービスで 1 日に使用しているコンピュータ時間が長すぎます | 1 |
| ドキュメントにアクセス中に スプレッドシート のサービスに接続できなくなりました。(行 40、ファイル「main」) | 2 |
| 起動時間の最大値を超えました | 16 |
このようなサマリーがメールで頻繁に届くようになりました。
あまりにも頻繁にエラーが発生して鬱陶しいため、本気で処理高速化に取り組むことにしました。
遅くなっていた原因
問題のコードを確認したところ、以下のような問題点が見つかりました。
- ネストされた for ループ: O(n²) の計算量で絵文字を比較していた
- スプレッドシートへの頻繁なアクセス: 1件ずつデータを読み書きしていた
- 非効率な差分検出: 全データを毎回比較していた
特に1番目の問題が深刻で、9000件 × 9000件 の比較処理が走っていたため、
計算量が膨大になっていました。
処理高速化のアプローチ
GAS の処理高速化のために、以下のアプローチを試みました。
- for ループの最適化: 不要なネストループを削減し、計算量を O(n²) から O(n) に改善
- データの保存方法の見直し: スプレッドシートへのアクセス回数を減らすため、データを一括で読み書きするように変更
- 比較アルゴリズムの改善: 新規絵文字の検出方法を見直し、Set の difference メソッドを活用
これらの改善により、処理速度の向上を図りました。
結果として、おおよそ 10秒程度 に収まるようになり、タイムアウトの問題が完全に解消されました。
それでは、具体的にどのように改善したか見ていきましょう。
高速化の具体例
1. for ループの最適化と比較アルゴリズムの改善
最大の問題は、ネストされた for ループによる絵文字の比較でした。
計算量が O(n²) になっていたため、9000件 × 9000件 の比較処理が発生し、処理時間が爆発的に増加していました。
改善前のコード
// 問題点: O(n²) の計算量
// 9000件 × 9000件 = 81,000,000回 の比較が発生
const localEmojis = getLocalDataEmojiList(); // 9000件
const slackEmojis = getSlackApiEmojiList(); // 9000件
const newEmojis = [];
for (const slackEmoji of slackEmojis) {
let found = false;
for (const localEmoji of localEmojis) { // ← ここがネストループ
if (slackEmoji.key === localEmoji.key) {
found = true;
break;
}
}
if (!found) {
newEmojis.push(slackEmoji);
}
}
改善後のコード
Set の difference メソッドを使用することで、計算量を O(n) に改善しました。
// 改善点: O(n) の計算量
// 9000件 + 9000件 = 18,000回 の処理で完了
// 1. スプレッドシートからローカル絵文字キーを取得
const localEmojiKeySet = new Set(Object.keys(getLocalDataEmojiList()));
// 2. Slack API からリモート絵文字を取得
const slackEmojiKeySet = new Set(Object.keys(getSlackApiEmojiList()));
// 3. Set の差分を取得(新規絵文字のみ)
const newEmojis = slackEmojiKeySet.difference(localEmojiKeySet);
// 4. Slack に通知
notifyNewEmoji(Array.from(newEmojis));
Set.difference() とは?
difference()は Set インスタンスのメソッドで、集合を一つ受け取り、この Set に含まれており、与えられた集合に含まれない要素を含む新しい Set を返します。
参考: Set.prototype.difference() - JavaScript | MDN
絵文字のキーはユニークなので、Set の差分演算を使うことができます。
これで新規絵文字を簡単かつ高速に検出できました。
2. データの保存方法の見直し
スプレッドシートへのアクセスは、GAS の処理で最も時間がかかる部分です。
1件ずつアクセスすると、9000件の絵文字を処理するのに 9000回 もアクセスが発生していました。
改善前のコード
// 問題点: 1件ずつスプレッドシートにアクセス
// 9000件 × 2列 = 18,000回 のアクセスが発生!
const sheet = getSheet();
for (let i = 0; i < emojiList.length; i++) {
sheet.getRange(i + 1, 1).setValue(emojiList[i].key); // 1回目のアクセス
sheet.getRange(i + 1, 2).setValue(emojiList[i].content); // 2回目のアクセス
}
改善後のコード
データを配列にまとめて、setValues メソッドで一括書き込みに変更しました。
// 改善点: 一括でスプレッドシートにアクセス
// クリア1回 + 書き込み1回 = 2回 のアクセスで完了!
// 1. Slack API から絵文字リストを取得
const slackEmoji = getRemoteEmojiList();
// 2. オブジェクトを配列形式に変換
const emojiArray = Object.entries(slackEmoji);
// 3. スプレッドシートに一括書き込み
const sheetController = new SheetController();
sheetController.setEmojiListRange(emojiArray);
スプレッドシート書き込みメソッドの実装
class SheetController {
setEmojiListRange(emojiList) {
const sheet = this.getEmojiListSheet();
const lastRow = sheet.getLastRow();
// 既存データをクリア(1回のアクセス)
if (lastRow > 0) {
sheet.getRange(1, 1, lastRow, 2).clear();
}
// 新しいデータを一括で書き込み(1回のアクセス)
if (emojiList.length > 0) {
sheet.getRange(1, 1, emojiList.length, 2).setValues(emojiList);
}
}
}
改善のポイント
-
setValue()→setValues()に変更することで、一括書き込みを実現 - 配列データを事前に準備してから、1回の API コールで書き込み
- スプレッドシートへのアクセス回数: 18,000回 → 2回 に削減
- 結果として処理速度が劇的に向上
改善結果
これらの改善により、以下のような結果が得られました。
- 処理時間: 6分以上 → 約10秒
- エラー発生率: ほぼ毎日 → 0回(改善後、エラーは一度も発生していません)
- 計算量: O(n²) → O(n)
処理時間が 約1/36 に短縮され、タイムアウトの問題が完全に解消されました。
おわりに
今回は GAS の処理高速化について、実際の問題を解決した事例を紹介しました。
GAS の処理高速化は、工夫次第で大幅に改善できることがわかりました。
ループの最適化、データ保存方法の見直し、比較アルゴリズムの改善などを行うことで、
実行時間制限に達することなく、大量のデータを効率的に処理できるようになります。
特に重要なポイントは以下の3つです。
- 計算量を意識する: O(n²) のアルゴリズムは避け、Set や Map を活用して O(n) に改善
- スプレッドシートへのアクセスを最小限に: 一括読み書きで劇的に高速化
- 適切なデータ構造を選ぶ: 配列よりも Set/Map の方が早い
もし GAS の実行時間に悩んでいる方がいれば、
まずは計算量とスプレッドシートへのアクセス回数を見直してみてください。
意外と簡単に改善できるかもしれません。
ちょっとした工夫で快適な GAS ライフを楽しみましょう。
この文章の推敲にはAIを活用しています。そのため、過去の記事とは少し文体が異なりますが内容は確認済みです。
最後に宣伝です。
Supershipではプロダクト開発やサービス開発に関わる人を絶賛募集しております。
ご興味がある方は以下リンクよりご確認ください。
Supership 採用サイト
是非ともよろしくお願いします。