今までの記事一覧
- GASでGoogleForm回答を取得するなら lastRow?(e)?試してみた
- onFormSubmit(e)を手動実行でデバッグする方法
- どっちを使う?onFormSubmit(e)の values と namedValues の違いと使い分け
- onFormSubmit(e) の e.values 配列順のしくみ
- Googleフォームで質問を変えても壊れない!cleanFormData(e)でnamedValues防御力をアップ
- Googleフォームの質問変更に負けない!「部分一致」と「秘密の暗号」でcleanFormData(e)の防御力を鉄壁に
- 手動コピペはもう卒業!Googleフォームの回答別に処理を自動仕分け
- Googleフォームで同時に大量送信されても踏ん張る!LockServiceで順番制御!try - catch - finally でバトンを繋げ! <この記事
- LockServiceでは順番は守れない?受付番号で順序を保証する方法
前提
この記事は、フォーム回答を保存している スプレッドシート側のGAS を前提にしています。
トリガーは以下を設定しています。
- スプレッドシートから
- フォーム送信時
おさらい
前回はif文を使ってフォームの回答内容によって条件分岐し、該当シートにデータを書き分け、該当担当者にメールを送る自動仕分け機を作りました。
ところが別々の人が同時にフォームを送信し、データの順番が逆転するなど不具合が発生!
受付順に処理を進めたかった部署からクレームが(泣)
今回はフォーム送信が殺到しても順番整理をしてくれるLockServiceを使ってみましょう。
なんで順番が狂うの?
- GASは送信された順ではなく、プログラムの中で 「書き込み(
appendRowやsetValuesなど)」が実行された順にシートを埋めていきます。
送信された瞬間に別々のレーンで処理が走るので、道路状況や処理の内容によって追い越しが発生することがあるのです。
特に、メール送信などの「外の世界とやり取りする処理」が入ると、道路状況(サーバーの混雑具合)に左右されやすくなります。
Aさんがメールを送るのに手こずっている間に、後から来たBさんがスイスイと先にシートへ書き込んでしまう……。これが「追い越し」の正体です。
整理券システム:LockService
前の処理が完了するまで次の人はストップさせる機能です。
書き方は簡単。はじめに鍵をかけて最後に鍵を開けるだけです。
function onFormSubmit(e) {
//★ここで鍵をかける
const lock = LockService.getScriptLock();
lock.waitLock(30000); //鍵をかける最大時間。1秒は1000
//ここから先にやりたい処理を書く
const formData = cleanFormData(e);
//
//
//
//★鍵を開ける
lock.releaseLock();
上のコードは、前の処理が終わるまで最大30秒待つ、というものです。
これで順番どおり処理をすすめることができます。
30秒過ぎたらどうなるの?
そこな!
前の人が詰まっていると、後ろに並んでいる人は自分の制限時間(30秒など)が来た瞬間に「タイムアウト」で脱落してしまいます。
混雑がひどいと、数名が連続してエラーになる「ドミノ倒し」のような状態が起きることも。放っておいてもいつかは復旧しますが、その間に脱落したデータは消えたまま……。
「いつの間にか数件だけデータが抜けていた」。これが一番怖い。
じゃあ時間を伸ばせばいいやん
それがどうやら30秒までしかダメという情報もあるのです。
欲張って待ち時間を長くしすぎると、今度はGAS自体の「6分の壁(実行制限)」にぶつかってしまう。
※これについては以下の記事で実験してみました。
LockServiceの待機限界は30秒じゃない?AIの回答を疑って1800秒実行の壁に挑戦
try - catch - finally でバトンをつなげ
タイムアウトや予期せぬエラーが起きても、止まることなく後続にバトン(鍵)を繋ぐ、
もし脱落者が出ても、脱落した瞬間に気づく、そんな仕組みを作ってみましょう。
lock.waitLock(30000)とlock.releaseLock()の外側をtry - catchで囲みます。
try{ ... }:この{ ... }の中に、失敗する可能性のある処理を書きます。
catch{ ... }:この{ ... }の中に、エラーした場合の処理を書きます。(自分宛にメールするとか)
finally{ ... }:この{ ... }の中に、エラーが起きても起きなくてもやってほしいことを書きます。(鍵を開ける、とか)
function onFormSubmit(e) {
//★ここで鍵をかける
const lock = LockService.getScriptLock();
try { //★★ tryこの中でエラーが起きたら「catch」に入る
lock.waitLock(30000); //鍵をかける最大時間。1秒は1000
//ここから先にやりたい処理を書く
const formData = cleanFormData(e); //前回の続きだとするとこれ
//
//
//
} catch (err) { //★★「try」の中でエラーが起きたらここに入る
const respondent = e.namedValues ? e.namedValues['名前'][0] : "不明なユーザー";
const errorText = respondent + " さんの処理に失敗しました。";
console.error(errorText + err);
GmailApp.sendEmail("自分のメールアドレス",
"【至急】フォーム仕分け失敗",
errorText + "\n\nエラー内容: " + err);
} finally { //★★エラーが起きても起きなくてもこれをやる
// 【最重要】何があっても最後は「鍵を返す」
// これを忘れると、次の人が永遠に中に入れません。
lock.releaseLock();
}
}
-
tryとcatchの間でエラーなし → → → → → → → →finally -
tryとcatchの間でエラーあり →catch(メール飛ばす) →finally
この場合のエラーはタイムアウトに限らずどんなエラーも含みます。
try - catch方式挙動
1番走者~3番走者がほぼ同時にスタートしたとします。
-
パターン1
- 1番走者 10秒で完走
- 2番走者 エラーで脱落
- 3番走者 15秒で完走
処理は「1番走者」「3番走者」の順でされます。
2番走者はcatchに入る。
-
パターン2
- 1番走者 50秒で完走
- 2番走者
- 3番走者
処理は「1番走者」だけ。後ろは全部エラー(タイムアウト)として
catchに入る。
-
パターン3
- 1番走者 15秒で完走
- 2番走者 25秒で完走
- 3番走者
処理は「1番走者」「2番走者」だけ。
「3番走者」は30秒待ったけど待ちきれず(タイムアウト)catchに入る。
つまり
- finallyがあるから:前の人がコケても「0秒」で次へバトンを渡せる(Googleの回収を待たなくていい)。
- catchがあるから:脱落者が出た瞬間にメールが飛ぶ。「あ、混雑で1件漏れたな」とすぐ手動で救済できる。
どこで鍵をかける?
LockServiceを置く場所によって、守れるものが変わります。
「送信された順番」を死守:
プログラムの最初の方にLockを入れましょう。
※ただし、一人あたりのロック時間が長くなるので、後ろの人がタイムアウト(脱落)しやすくなります。
「タイムアウト(脱落)」を最小限に:
シートへの書き込みなど、順番が狂うと困る最小限の範囲だけをLockで囲みましょう。
※ただし、Lockにたどり着くまでの処理速度の差で、わずかに順番が入れ替わるリスクがあります。
現場のルールが「受付順に厳しい」のか「1件も漏らしたくない」のかに合わせて、最適な場所を選んでみてくださいね。
まとめ
というわけで今回は、同時多発的にフォームを送信されてもLockServiceを使って交通整理をする方法、
そして、前の走者がエラーで倒れてもすぐにメールで知らせることができ、後続の走者にもバトンをつなぐことができるtry - catch - finallyを使ってみました。
おまけ
lock.waitLock(30000)この待ち時間、エディタでは300000(300秒)なんてのもできるんだけど、GASの6分制限を考えると、30秒くらいまでにしておくのが実務的には安全なようです。
待機に5分使ったら、自分の番が回ってきたときは残り1分。自分の書き込みの途中で6分の壁にぶち当たったらそっちのほうがデータが壊れるリスク大です。
なにより、中でコケてても6分間気づかないのはちょっと不便でしょ。
この記事を書いていて、LockServiceについていろいろ疑問が出てきたので、こちらに別記事を作って検証してみることにしました。興味があれば覗いてみてくださいね。
LockServiceの待機限界は30秒じゃない?AIの回答を疑って1800秒実行の壁に挑戦
LockServiceの順番待ち エラー・タイムアウト・鍵の返し忘れで暴かれた鍵取りゲームの実態