GASのLockServiceを正しく使う:3種類のロックの違いと選び方

Google Apps Script(GAS)で、スプレッドシートなどの共有リソースにアクセスする処理を書く場合、複数の実行が同時に走ると処理が競合してしまうことがあります。これを防ぐための仕組みがLockServiceです。

この記事では、LockServiceが提供する3種類のロックの違いと、それぞれをどう使い分ければいいのかを整理します。

目次

なぜ排他制御が必要なのか

GASのトリガー(フォーム送信時、時間主導型など)は、条件が重なれば同時に複数実行されることがあります。同じスプレッドシートに対して複数の処理が同時に読み書きを行うと、

  • 片方の書き込みがもう片方に上書きされる
  • 本来処理されるはずのデータが処理されずに残る

といった不整合が起きる可能性があります。こうした問題を防ぐために、「一度に1つの処理だけが該当コードを実行できるようにする」仕組みが排他制御であり、GASではLockServiceを使って実装します。

LockServiceの基本的な使い方

LockServiceはどのロックを使う場合でも、基本の使い方は共通しています。

function sample() {
  const lock = LockService.getScriptLock(); // ここで種類を選ぶ
  try {
    lock.waitLock(30000); // 最大30秒、ロック解放を待つ
    // ロックを取得できたら実行したい処理
    doSomething();
  } catch (e) {
    // 指定時間内にロックを取得できなかった場合の処理
    console.error("ロック取得に失敗しました: " + e);
  } finally {
    lock.releaseLock(); // 必ず解放する
  }
}

主なメソッドは以下の3つです。

  • waitLock(timeoutInMillis):指定時間、ロックが解放されるのを待つ。取得できなければ例外をスローする
  • tryLock(timeoutInMillis):指定時間試みて、取得できたかどうかを真偽値で返す(例外を投げない)
  • releaseLock():ロックを解放する

waitLockは失敗時に例外処理で分岐したい場合、tryLockは成否だけ見て処理を分けたい場合、というのがおおよその使い分けです。

3種類のロックの違い

LockServiceにはgetScriptLock() /getDocumentLock() /getUserLock()の3つがあり、それぞれ「どの範囲で排他制御をかけるか」が異なります。

ロック範囲主な用途
getScriptLock()実行中のスクリプトプロジェクト全体どのユーザー・どのトリガー経由でも、そのスクリプトの実行を1つに絞りたいとき
getDocumentLock()紐づいているドキュメント(スプレッドシート・ドキュメント・フォームなど)コンテナバインド型のスクリプトで、そのドキュメントに対する処理を1つに絞りたいとき
getUserLock()実行しているユーザー同一ユーザーが複数の場所から同じ処理を実行することを防ぎたいとき

getScriptLock()

スクリプトプロジェクト単位でロックがかかります。誰が・どのトリガー経由で実行しても、そのスクリプトのコードが同時に複数実行されるのを防げます。スタンドアロンスクリプトでもコンテナバインド型スクリプトでも使えます。

getDocumentLock()

スプレッドシートやドキュメントなど、コンテナバインド型スクリプトが紐づいている「ドキュメント」単位でロックがかかります。同じスクリプトのコードでも、紐づくドキュメントが違えば別々にロックが管理されるのが特徴です。

getUserLock()

実行しているユーザー単位でロックがかかります。同じユーザーが複数のタブや別の場所から同じ処理を呼び出してしまうケースを防ぎたいときに向いています。

ライブラリとして使うときの注意点

LockServiceを使ったコードをライブラリ化して、複数の別々のスプレッドシートから呼び出す構成にする場合は、どのロックを選ぶかで挙動が変わってきます。

ポイントは、getScriptLock()getDocumentLock()とで「何を1つの単位とみなすか」が違うことです。

  • getScriptLock():スクリプト単位でロックを取るので、どのドキュメントに対する処理かを区別しません。そのため、ライブラリとして複数のスプレッドシートから呼び出すと、別々のスプレッドシートに対する処理同士でも同じ1つのロックを取り合い、片方がもう片方の完了を待たされます(=お互いをブロックしてしまいます)。
  • getDocumentLock():ドキュメントごとに独立したロックを取ります。同じドキュメントに対しては同時に1つしか実行させませんが、別々のドキュメントに対する処理は並行して実行できます。

waitLockの待機時間をどう決めるか

waitLock()に渡す秒数は、以下のようなバランスで決めるのが基本です。

  • 短すぎる:同時実行が少し重なっただけでロック取得に失敗し、処理が失敗しやすくなる
  • 長すぎる:GAS自体の実行時間制限(通常のアカウントで6分、Google Workspaceアカウントで30分)に近づいてしまう

目安としては、「1回の処理にかかる時間 × 想定される同時実行の最大件数 + 余裕分」で考えるとよいでしょう。例えば1回の処理が5秒程度で、同時に来ても多くて2件程度という想定であれば、10秒あれば足りる計算になりますが、突発的なケースも考慮して30秒程度に設定する、といった具合です。

まとめ

LockServiceを使う際は、

  1. どの範囲を守りたいのか(ロックの粒度:Script / Document / User)
  2. どのくらい待つのが妥当か(waitLockの秒数)

の2点を、自分のユースケースに合わせて設計することが重要です。特にライブラリとして複数のスクリプトプロジェクトから呼ばれるコードを書く場合は、ロックの粒度を誤ると排他制御が意図した範囲で効いていない、という事態にもなりかねません。GASでスプレッドシートを扱う自動化処理を作る際の参考になれば幸いです。

CTA
  • URLをコピーしました!
  • URLをコピーしました!
この記事を書いた人
目次