Haeminway haeminway
한국어
Back to Tech Notes
1 min read

Concurrent Writes to the Same Sheet Break: LockService and the 30-Run Ceiling

When users and triggers write the same asset, numbers collide and rows get overwritten. Guard with tryLock + finally + flush, and design for the 30-per-user concurrency ceiling.

Wrap read-modify-write operations in a lock. For append, number issuance, or status change: “read the current value → change it → write it”: two overlapping runs read the same value and one overwrites the other.

Why it matters

A reference number gets issued twice, or one of two simultaneous submissions vanishes. It’s invisible most of the time and blows up exactly when users pile in, so it’s hard to reproduce and erodes trust.

Lock types and the standard pattern

TypeSerializes
getScriptLock()One critical section across the whole project
getDocumentLock()Within the same document only (can be null in standalone)
getUserLock()The same user’s executions only
function issueNumber() {
  const lock = LockService.getScriptLock();
  if (!lock.tryLock(10000)) throw new Error("could not acquire lock");
  try {
    const sheet = SpreadsheetApp.getActive().getSheetByName("Counter");
    const next = Number(sheet.getRange("A1").getValue()) + 1;
    sheet.getRange("A1").setValue(next);
    SpreadsheetApp.flush();   // commit before release
    return next;
  } finally {
    lock.releaseLock();       // always in finally
  }
}

Rules:

  • Prefer tryLock(timeoutMs) (never wait forever).
  • Always put releaseLock() in finally.
  • Call SpreadsheetApp.flush() before releasing to commit the write, or the next run reads a stale value.
  • Don’t make network calls, run long computations, or wait on a user inside the lock.

Deeper: the 30-run ceiling

Concurrent executions cap at about 30 per user. Counting LockService waiting, design for roughly 60 lossless concurrent users. Beyond that is the signal to move to an external DB or a queue. It’s safest to flag this limit during the sales/contract stage.

One line to keep: shared writes need tryLock + finally + flush; design the concurrency limit up front.

Frequently asked questions

Why does concurrent writing to the same sheet corrupt data in GAS?
Two overlapping runs read the same current value, so one overwrites the other — causing duplicate reference numbers or silently dropping a submission.
What are the required rules for using LockService correctly?
Use tryLock(timeoutMs) to acquire (never wait forever), always place releaseLock() in a finally block, and call SpreadsheetApp.flush() before releasing to commit the write.
How many concurrent executions does GAS support and what should I design for?
Concurrent executions cap at about 30 per user. Accounting for LockService wait time, design for roughly 60 lossless concurrent users; beyond that, move to an external DB or queue.