6
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

GASの処理高速化を行った話

Last updated at Posted at 2025-12-11

この記事は、 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

このようなサマリーがメールで頻繁に届くようになりました。

あまりにも頻繁にエラーが発生して鬱陶しいため、本気で処理高速化に取り組むことにしました。

遅くなっていた原因

問題のコードを確認したところ、以下のような問題点が見つかりました。

  1. ネストされた for ループ: O(n²) の計算量で絵文字を比較していた
  2. スプレッドシートへの頻繁なアクセス: 1件ずつデータを読み書きしていた
  3. 非効率な差分検出: 全データを毎回比較していた

特に1番目の問題が深刻で、9000件 × 9000件 の比較処理が走っていたため、
計算量が膨大になっていました。

処理高速化のアプローチ

GAS の処理高速化のために、以下のアプローチを試みました。

  1. for ループの最適化: 不要なネストループを削減し、計算量を O(n²) から O(n) に改善
  2. データの保存方法の見直し: スプレッドシートへのアクセス回数を減らすため、データを一括で読み書きするように変更
  3. 比較アルゴリズムの改善: 新規絵文字の検出方法を見直し、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);

スプレッドシート書き込みメソッドの実装

SheetController.js
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 採用サイト
是非ともよろしくお願いします。

6
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?