Почему я получаю тайм-аут и документирую недостающие ошибки при параллельных вызовах Apps Script API? - PullRequest
1 голос
/ 08 мая 2020

Я пишу надстройку Google Sheets, которая копирует некоторые данные из одной электронной таблицы в другую, а затем переформатирует их. Используемые наборы данных часто бывают большими (~ 100 тыс. Строк), поэтому, чтобы избежать превышения 6-минутного предела тайм-аута, я разбиваю данные на фрагменты, а затем запускаю функцию копирования данных параллельно для каждого фрагмента, используя вызовы google.script.run из на стороне клиента.

В моем наборе данных из ~ 100 тыс. строк первая пара фрагментов, которые необходимо завершить, успешно копируются, а остальные выдают ошибку «Время ожидания электронных таблиц службы истекло при доступе к документу с идентификатором [электронная таблица id]. "

client-side console logging

А вот как это выглядит на панели инструментов Apps Script:

apps script dashboard executions - timeout error

Меня сбивают с толку ошибки тайм-аута, потому что:

  1. Я успешно запустил скрипт для набора данных, который содержит 5000 строк
  2. Панель инструментов скрипта приложений показывает, что выполнения завершились с ошибкой до 6 минут (скорее, 4-5 минут)
  3. Ведение журнала панели инструментов скрипта приложений показывает успешное ведение журнала неудачных (тайм-аут) выполнений. Регистрация происходит после операции setValues ​​() (см. Код ниже); единственное, что происходит после регистрации, - это возврат, поэтому я не понимаю, как он мог успешно войти в журнал, а затем время ожидания (я думал, что скрипт приложений был синхронным ... но, может быть, я ошибаюсь?)

Я также не уверен в этих «неперехваченных» ошибках, но они, похоже, отображаются на панели управления как «Документ [идентификатор электронной таблицы] отсутствует (возможно, он был удален или у вас нет доступа для чтения ?) "

apps script dashboard executions - missing document error

Это документ, в который я копирую, и я подтвердил, что он все еще существует на моем Диске, и я могу открыть и просмотрите данные, которые были успешно скопированы. Может ли документ go «отсутствовать», если слишком много экземпляров скрипта пытаются получить к нему доступ одновременно?

Я экспериментировал с меньшими размерами блоков (1000 и 2000 строк) и получал те же типы ошибок .

Вот как выглядит мой Javascript на стороне клиента:

// This function is the success handler that runs after another function (which grabs the total # of rows
// from the sheet to be copied, and then creates the new spreadsheet to be copied into) completes
function dataParamsSuccess(dataParameters) {
      // dataParameters = [busHrs, outputSsUrl, lastRow, maxRows, maxColumns]
      var busHrs = dataParameters[0];
      var outputSsUrl = dataParameters[1];
      var lastRow = dataParameters[2];
      var maxRows = dataParameters[3];
      var maxColumns = dataParameters[4];
      console.log(maxRows);
      console.log(maxColumns);

      // Set chunk size
      var chunkSize = 5000; // number of rows in chunk

      // Determine number of chunks
      var numChunks = Math.ceil(lastRow / chunkSize);
      var lastChunkSize = lastRow % chunkSize;
      if ((numChunks-1) * chunkSize + lastChunkSize == lastRow) {
        console.log("Math checks out");
      } else {
        console.log("oops, check your math");
      }

      // Generate status message
      var statusHtml = numChunks + " chunks to be copied";
      for (i=0; i<numChunks; i++) {
        var chunkNum = i+1;
        var chunkNumStr = chunkNum.toString();
        statusHtml += "<div id=\"chunk" + chunkNumStr + "Status\"></div>";
      }
      document.getElementById("statusMsg").innerHTML = statusHtml;

      var startRow = 1;
      // Call copyData once for each chunk
      for (i=0; i<numChunks; i++) {
        var chunkNum = i+1;
        var chunkNumStr = chunkNum.toString();
        var chunkDivId = "chunk" + chunkNumStr + "Status";

        if (chunkNum==numChunks) { // if this is the last chunk, chunk size is smaller
          chunkSize = lastChunkSize;
        }

        var copyParams = [chunkNum, chunkSize, startRow, outputSsUrl];
        google.script.run
          .withSuccessHandler(copyChunkSuccess)
          .copyData(copyParams);
        document.getElementById(chunkDivId).innerHTML = "Chunk " + chunkNumStr + " copying in progress";
        startRow += chunkSize;
        console.log("startRow: " + startRow.toString());

      }

      // Haven't gotten to the part where I figure out what to do after all chunks are complete yet
    }

А вот вызываемая функция скрипта приложений на стороне сервера:

function copyData(copyParams) {

  try {
    // copyParams = [chunkNum, chunkSize, startRow, outputSsUrl]
    var chunkNum = copyParams[0];
    var chunkSize = copyParams[1];
    var startRow = copyParams[2];
    var outputSsUrl = copyParams[3];
    var lastRow = startRow + chunkSize;

    // Get input and output sheets
    var dataSheet = SpreadsheetApp.getActiveSheet();
    var outputSpreadsheet = SpreadsheetApp.openByUrl(outputSsUrl);
    var outputSheet = outputSpreadsheet.getActiveSheet();

    // Copy values
    var values = dataSheet.getRange(startRow, 1, chunkSize, 22).getValues();
    outputSheet.getRange(startRow, 1, chunkSize, 22).setValues(values);

    // Logging
    var dataSpreadsheetId = dataSheet.getParent().getId();
    var outputSpreadsheetId = outputSpreadsheet.getId();
    console.log("Chunk " + chunkNum.toString() + " (rows " + startRow.toString() + " through " + lastRow.toString() + ") copied successfully");
    return [chunkNum, startRow, lastRow, "success"];
  } catch(e) {
    return [chunkNum, startRow, lastRow, e.message]; // Return error to client-side; server-side logging is taking too long
  }
}

1 Ответ

2 голосов
/ 08 мая 2020

Как насчет этого ответа?

По моему опыту, даже когда используется служба электронных таблиц, когда непрерывный доступ происходит с асинхронным процессом, я столкнулся с такой проблемой. В то время я использовал сервис блокировки и setTimeout. Но я не уверен, может ли этот метод решить вашу проблему. Поэтому, пожалуйста, протестируйте следующую модификацию. Здесь я хотел бы предложить использовать службу блокировки для стороны скрипта Google Apps и setTimeout для стороны Javascript. Когда ваш скрипт изменяется, он становится следующим.

Последовательность этого обходного пути выглядит следующим образом.

Последовательность:

  1. 10 воркеров отправляются в Google Apps Сторона скрипта.
  2. После отправки 10 воркеров он ждет 5 секунд.
  3. На стороне скрипта Google Apps принято 10 воркеров. И они обрабатываются службой блокировки.
  4. Через 5 секунд на стороне Javascript отправляются следующие 10 рабочих процессов.

В этом цикле запускается скрипт.

Сторона скрипта Google Apps:

Измените copyData следующим образом.

function copyData(copyParams) {
  var lock = LockService.getDocumentLock();
  if (lock.tryLock(10000)) {
    try {
      // copyParams = [chunkNum, chunkSize, startRow, outputSsUrl]
      var chunkNum = copyParams[0];
      var chunkSize = copyParams[1];
      var startRow = copyParams[2];
      var outputSsUrl = copyParams[3];
      var lastRow = startRow + chunkSize;

      // Get input and output sheets
      var dataSheet = SpreadsheetApp.getActiveSheet();
      var outputSpreadsheet = SpreadsheetApp.openByUrl(outputSsUrl);
      var outputSheet = outputSpreadsheet.getActiveSheet();

      // Copy values
      var values = dataSheet.getRange(startRow, 1, chunkSize, 22).getValues();
      outputSheet.getRange(startRow, 1, chunkSize, 22).setValues(values);

      // Logging
      var dataSpreadsheetId = dataSheet.getParent().getId();
      var outputSpreadsheetId = outputSpreadsheet.getId();
      console.log("Chunk " + chunkNum.toString() + " (rows " + startRow.toString() + " through " + lastRow.toString() + ") copied successfully");
      return [chunkNum, startRow, lastRow, "success"];
    } catch(e) {
      return [chunkNum, startRow, lastRow, e.message]; // Return error to client-side; server-side logging is taking too long
    } finally {
      lock.releaseLock();
    }
  }
}

HTML & Javascript сторона:

Измените dataParamsSuccess следующим образом.

// This function is the success handler that runs after another function (which grabs the total # of rows
// from the sheet to be copied, and then creates the new spreadsheet to be copied into) completes

async function dataParamsSuccess(dataParameters) {  // <--- Modified
  const wait = (s) => new Promise(r => setTimeout(r, s));  // <--- Added

  // dataParameters = [busHrs, outputSsUrl, lastRow, maxRows, maxColumns]
  var busHrs = dataParameters[0];
  var outputSsUrl = dataParameters[1];
  var lastRow = dataParameters[2];
  var maxRows = dataParameters[3];
  var maxColumns = dataParameters[4];
  console.log(maxRows);
  console.log(maxColumns);

  // Set chunk size
  var chunkSize = 5000; // number of rows in chunk

  // Determine number of chunks
  var numChunks = Math.ceil(lastRow / chunkSize);
  var lastChunkSize = lastRow % chunkSize;
  if ((numChunks - 1) * chunkSize + lastChunkSize == lastRow) {
    console.log("Math checks out");
  } else {
    console.log("oops, check your math");
  }

  // Generate status message
  var statusHtml = numChunks + " chunks to be copied";
  for (i = 0; i < numChunks; i++) {
    var chunkNum = i + 1;
    var chunkNumStr = chunkNum.toString();
    statusHtml += "<div id=\"chunk" + chunkNumStr + "Status\"></div>";
  }
  document.getElementById("statusMsg").innerHTML = statusHtml;

  var count = 0;  // <--- Added
  var startRow = 1;
  // Call copyData once for each chunk
  for (i = 0; i < numChunks; i++) {
    count++;  // <--- Added
    var chunkNum = i + 1;
    var chunkNumStr = chunkNum.toString();
    var chunkDivId = "chunk" + chunkNumStr + "Status";

    if (chunkNum == numChunks) { // if this is the last chunk, chunk size is smaller
      chunkSize = lastChunkSize;
    }

    var copyParams = [chunkNum, chunkSize, startRow, outputSsUrl];
    google.script.run
      .withSuccessHandler(copyChunkSuccess)
      .copyData(copyParams);

    if (count == 10) {  // <--- Added
      console.log("wait");
      await wait(5000);
      count = 0;
    }

    document.getElementById(chunkDivId).innerHTML = "Chunk " + chunkNumStr + " copying in progress";
    startRow += chunkSize;
    console.log("startRow: " + startRow.toString());

  }

  // Haven't gotten to the part where I figure out what to do after all chunks are complete yet
}

Примечание:

  • Я не уверен, подходит ли 5000 из await wait(5000) для вашей ситуации. Поэтому, пожалуйста, измените это значение путем тестирования в вашей ситуации. В текущем значении 5000 составляет 5 секунд.

Ссылка:

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...