Sửa lỗi 'Exception: Too many simultaneous invocations' trong Google Apps Script

intermediate📗 Google Sheets2026-07-03| Google Apps Script / Google Sheets Editor / V8 Runtime

Error Message

Exception: Too many simultaneous invocations: Spreadsheet
#google-apps-script#google-sheets#concurrency#tối ưu hóa

Sự cố Production lúc 2 giờ sáng

Bạn vừa triển khai một công cụ Google Sheets cho khách hàng, và mọi thứ hoạt động hoàn hảo khi bạn tự test một mình. Nhưng ngay khi hai mươi người mở file cùng lúc, hoặc ai đó dán vào một bộ dữ liệu 5.000 dòng, script lập tức bị treo. Nhật ký thực thi hiện ra một thông báo duy nhất, vô cùng bực bội:

Exception: Too many simultaneous invocations: Spreadsheet

Google áp đặt một giới hạn cứng về số lượng phiên bản script có thể chạy đồng thời. Đối với hầu hết các tài khoản Google Workspace, giới hạn này là khoảng 30 lần thực thi đồng thời. Nếu vượt quá con số này—thường xảy ra khi dùng custom function hoặc trigger tần suất cao—Google sẽ hủy tiến trình để bảo vệ tài nguyên máy chủ của mình.

TL;DR: Cách khắc phục sự cố

  • Dùng LockService: Buộc các script phải chờ theo hàng đợi thay vì chạy đồng thời tất cả cùng lúc.
  • Batch hóa Custom Function: Đừng gọi hàm 100 lần cho 100 dòng. Hãy truyền cả vùng dữ liệu (A2:A101) và xử lý trong một lần duy nhất.
  • Dùng CacheService: Lưu kết quả trong 10 phút để các phép tính giống nhau không phải chạy lại script.

Nguyên nhân gây ra lỗi này

Hãy tưởng tượng bạn có một custom function như =GET_VAT(A1). Nếu bạn kéo công thức đó xuống 500 dòng, Google Sheets không chạy chúng lần lượt từng cái. Nó cố tính toán tất cả 500 cái cùng một lúc. Vì 500 lớn hơn nhiều so với ~30 slot được phép, spreadsheet bị quá tải và ném ra exception.

Cách 1: Xếp hàng với LockService

Hãy hình dung LockService như một máy phát số thứ tự ở quầy dịch vụ. Nó ngăn nhiều lần thực thi cùng cố ghi dữ liệu vào sheet tại cùng một thời điểm. Về bản chất, nó biến một đám hỗn loạn thành một hàng đợi gọn gàng.

function safeWriteToSheet(data) {
  const lock = LockService.getScriptLock();
  
  try {
    // Yêu cầu khóa và chờ tối đa 30 giây để các tiến trình khác hoàn thành
    lock.waitLock(30000); 
    
    const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Logs');
    sheet.appendRow([new Date(), data]);
    
    // Flush đảm bảo thay đổi được ghi trước khi khóa được giải phóng
    SpreadsheetApp.flush(); 
  } catch (e) {
    console.error('Lock timeout: ' + e.message);
    throw new Error('Máy chủ đang bận. Vui lòng chờ vài giây và thử lại.');
  } finally {
    // Luôn giải phóng khóa, kể cả khi code bị lỗi
    lock.releaseLock();
  }
}

Cách 2: Batch hóa theo vùng dữ liệu (Giải pháp tốt nhất)

Nếu lỗi xuất phát từ các công thức tùy chỉnh, LockService có thể khiến sheet của bạn bị chậm vì mỗi ô phải chờ đến lượt mình. Giải pháp thực sự là viết lại hàm để xử lý cả một vùng dữ liệu (Array) thay vì một giá trị đơn lẻ.

Cách làm không hiệu quả:

Công thức dùng trong 1.000 ô: =USD_TO_EUR(A2)

// TRÁNH DÙNG: Cách này kích hoạt 1.000 lần thực thi script riêng biệt
function USD_TO_EUR(value) {
  return value * 0.92;
}

Cách làm chuyên nghiệp:

Công thức chỉ dùng trong đúng 1 ô: =USD_TO_EUR(A2:A1001)

// KHUYẾN NGHỊ: Cách này chỉ kích hoạt đúng 1 lần thực thi để xử lý 1.000 dòng
function USD_TO_EUR(input) {
  if (Array.isArray(input)) {
    // Xử lý mảng 2 chiều được trả về từ vùng dữ liệu
    return input.map(row => row.map(cell => cell * 0.92));
  }
  return input * 0.92;
}

Bằng cách truyền cả vùng dữ liệu, bạn giảm số lần gọi từ 1.000 xuống còn 1. Đây là cách hiệu quả nhất để giữ dưới ngưỡng giới hạn đồng thời của Google.

Cách 3: Cache kết quả

Nếu script của bạn lấy dữ liệu từ API bên ngoài (như dịch vụ tỷ giá ngoại tệ hoặc thời tiết), đừng lấy cùng một dữ liệu hai lần. Dùng CacheService để lưu kết quả trong vài phút. Điều này ngăn người dùng mới kích hoạt một lần thực thi script mới cho dữ liệu mà bạn đã có sẵn.

function getCachedRate(currencyPair) {
  const cache = CacheService.getScriptCache();
  const cachedValue = cache.get(currencyPair);
  
  if (cachedValue) return cachedValue;
  
  // Nếu không có trong cache, thực hiện lời gọi API tốn kém
  const rate = fetchRateFromAPI(currencyPair);
  
  // Lưu vào cache trong 15 phút (900 giây)
  cache.put(currencyPair, rate, 900);
  return rate;
}

Kiểm tra: Stress test sau khi sửa

Đừng cho rằng đã xong chỉ vì nó hoạt động với bạn. Hãy thử ba bài kiểm tra sau:

  • Bài test Hoàn tác: Bôi đen 200 ô chứa custom function, xóa chúng đi, rồi nhấn Cmd+Z (hoặc Ctrl+Z). Thao tác này buộc một lần tính toán lại đồng loạt với khối lượng lớn.
  • Nhật ký thực thi: Mở trình soạn thảo Apps Script và nhấp vào Executions. Tìm các thời điểm bắt đầu trùng nhau. Nếu tất cả hiện "Completed" thay vì "Failed," nghĩa là waitLock của bạn đang hoạt động.
  • Bài test nhiều người dùng: Nhờ hai đồng nghiệp liên tục nhấn nút "Run" hoặc chỉnh sửa cùng một vùng dữ liệu cùng lúc. Nếu script xếp hàng đúng cách mà không bị crash, bạn đã sẵn sàng lên production.

Tổng kết

Lỗi "Too many simultaneous invocations" không phải lỗi trong code của bạn—đó là một vụ tắc đường. Dùng LockService để quản lý hàng đợi và batch hóa theo vùng dữ liệu để giảm số lượng "xe" trên đường. Chỉ hai thay đổi này thôi đã đủ giải quyết 95% vấn đề đồng thời trong Google Apps Script.

Related Error Notes