kakts-log

programming について調べたことを整理していきます

Google apps script のLock制御について

この投稿はGoogle Apps Script Advent Calendar 2016の17日の投稿です。

Google apps scriptとは

developers.google.com Googleスプレッドシートや、ドキュメントなど、Google関連のサービスにおいてGoogle apps script(以下GAS)を使ってシートの編集、 web api呼び出しなど色々なことができるようになっています。
私も特に職場でスプレッドシートを使うことが多く、GASを使ってデータ集計やサービスで使うマスタデータの入力、さらにはjson吐き出し機能など作って作業の自動化を行っています。

GASのLock機能について

スプレッドシートやドキュメントなどのリソースでGASを使う際に、複数ユーザが同じリソースに対して同時にスクリプトを走らせてしまう場合があり、場合によってこれが非常に問題になる場合があります。
共有リソースでの同時アクセス作業に対して起こりうる共通の悩みだと思うのですが、GASでは、これを防ぐためのLock機能を提供しています。

GASのLock Class と Lock Service

Lock Service  |  Apps Script  |  Google Developers
GASではこのLock機能を実現するために Lock ClassとLock Serviceを提供しています。
スクリプトに対するLockを取得したり、処理が終わったらLock状態を解除したりする基本的な機能があります。 Lockの粒度としては、以下の3が提供されているので用途に応じて自由に使えます。

GASのLock機能を使ってみる

Lock機能の便利さを実感するために実際に使ってみます。 上記の3種類のロックについてそれぞれGoogleスプレッドシートを用いて試してみます。

ドキュメントに対するLock

GoogleスプレッドシートGoogle docsなどの各リソース(ドキュメント)に対するLockをかけます。 1つのリソース全体にLockをかけるということは、Lockがかかっている間はすべてのユーザが並列にコードを実行できないことを意味しています。 ドキュメントに対するLockをかけるには、LockService.getDocumentLock()でドキュメントLock用のインスタンスを取得した後に Lock.tryLock(timeoutInMIllis) か Lock.waitLock(timeoutInMillis)を呼び出す必要があります。
ある単一のドキュメントに対するロックなので、他のドキュメントで同じスクリプトを実行している場合は問題なく実行できます。

今回はテストのために、2つのアカウントで別々に同じスプレッドシートを開いた状態でドキュメントに対するLockがかかっているかをチェックします。 ユーザA,Bがいるとして以下のアクションを行って確認します。 ①ユーザA: Datetime更新(Lock中に10秒間sleepさせる) ②ユーザB: ユーザAのアクション直後にDatetime更新ボタンを押す

スクリプトは以下のとおりです。

function onOpen() {
  var ss = SpreadsheetApp.getActiveSpreadsheet();

  ss.addMenu("test", [
    {name: "Datetime更新", functionName: "changeDateTime"}
  ]);
}

function changeDateTime() {
  var docLock = LockService.getDocumentLock();
  // ドキュメントに対してLockがかかっているかチェック
  if (docLock.tryLock(500)) {
    var ss = SpreadsheetApp.getActiveSpreadsheet();
    var sheet = ss.getSheets()[0];

    var rangeLockStatus = sheet.getRange("A1");
    rangeLockStatus.setValue("ドキュメントロック開始");
    var range = sheet.getRange("A2");
    range.setValue(Date.now());

    // Lockかけた状態のまま10秒待つ
    Utilities.sleep(10000);

    // Lockの解放
    docLock.releaseLock();
    rangeLockStatus.setValue("ドキュメントロック終了");
  }
}

この通りに実行すると、最初にユーザAの実行に対してLockがかかり、ユーザBはLockを1秒間待っても解除されないので実行せずに終了します Lock系の処理を行う際に他の言語でも共通なのですが、Lockするべき処理が終わった場合はちゃんとLock解除は絶対必須です。

スクリプトに対するLock

続いてスクリプトに対するLockですが、複数ドキュメントで同じスクリプトを実行している場合に有効なLockです。
前述したドキュメントに対するLockと何が違うかというと、ドキュメントLockは1ドキュメント内で有効なLockですが、
スクリプトLockは、 共通ライブラリなどで複数のドキュメントが同じスクリプトを実行している場合、あるドキュメントでLockを取得している間他のドキュメントからはLock解除を待たないといけないです。

スクリプトは以下のとおりです。

function onOpen() {
  var ss = SpreadsheetApp.getActiveSpreadsheet();

  ss.addMenu("test", [
    {name: "Datetime更新", functionName: "changeDateTime"}
  ]);
}

function changeDateTime() {
  var scriptLock = LockService.getScriptLock();
  // スクリプトに対してLockがかかっているかチェック
  if (scriptLock.tryLock(500)) {
    var ss = SpreadsheetApp.getActiveSpreadsheet();
    var sheet = ss.getSheets()[0];

    var rangeLockStatus = sheet.getRange("A1");
    rangeLockStatus.setValue("スクリプトロック開始");
    var range = sheet.getRange("A2");
    range.setValue(Date.now());

    // Lockかけた状態のまま10秒待つ
    Utilities.sleep(10000);

    // Lockの解放
    scriptLock.releaseLock();
    rangeLockStatus.setValue("スクリプトロック終了");
  }
}

ドキュメントLockと異なるのは、Lockオブジェクトを取得するときにLockService.getScriptLock()を呼び出すようになるだけです。
戻り値としてLockオブジェクトが変えるのは変わらないので、ロックをかけたり解除する操作は変わりません。

ユーザLock

最後に、ユーザに対するLockですが、同一ユーザのスクリプト実行に対するLockです。 ユーザ毎にスクリプトのLock状態をもっているため、 ユーザAとユーザBが同時にスクリプトを実行できます。

スクリプトは以下のとおりです。

function onOpen() {
  var ss = SpreadsheetApp.getActiveSpreadsheet();

  ss.addMenu("test", [
    {name: "Datetime更新", functionName: "changeDateTime"}
  ]);
}

function changeDateTime() {
  var userLock = LockService.getUserLock();
  // ユーザに対してLockがかかっているかチェック
  if (userLock.tryLock(5000)) {
    var ss = SpreadsheetApp.getActiveSpreadsheet();
    var sheet = ss.getSheets()[0];

    var rangeLockStatus = sheet.getRange("A1");
    rangeLockStatus.setValue("ユーザロック開始");
    var range = sheet.getRange("A2");
    range.setValue(Date.now());

    // Lockかけた状態のまま10秒待つ
    Utilities.sleep(10000);

    // Lockの解放
    userLock.releaseLock();
    rangeLockStatus.setValue("ユーザロック終了");
  }
}

これもLockオブジェクトの取得にLockService.getUserLock()するところのみ変わっています。

まとめ

Google apps script が3種類の粒度でLock機能を提供していて、今回は使い方を紹介しました。
ドキュメント・スクリプトLockはかなり使えそうですが、正直ユーザLockは使いみちがイメージできなかったですが
現在業務や個人で使っているスクリプトで、Lock機能を使えそうなので導入してみます。

次回のGoogle Apps Script Advent Calendar 2016の投稿は、 @HomMarkHuntさんになります。