Тайм-аут функции Firebase при вызове устанавливается много раз в дБ в реальном времени - PullRequest
0 голосов
/ 23 октября 2019

Вкратце: у меня есть запланированная функция firebase, которая загружает файл, преобразует его в куски JSON и импортирует его в базу данных в реальном времени. Я дал функции 2 ГБ памяти и 540 секунд (9 минут, максимум), чтобы сделать свою работу, но она все еще заканчивается в тайм-аут около 50% времени. Должна быть утечка, но она, похоже, находится в моем слепом пятне.

Подробно: у меня есть следующая запланированная функция, которая запускается раз в час. Функция вызывает метод updateDatabase, который, в свою очередь, загружает файл размером около 35 МБ с внешнего сервера (используя метод getNewData. Этот файл представляет собой разделенный пробелами файл (неважно, ребенок), содержащий 2500 строк и 2500столбцы данных. Мне нужно, чтобы каждая из этих точек данных в базе данных позже быстро их считывала.

Сначала я просто преобразовал это в json и попытался импортировать это в базу данных, так как 35 МБ не показалосьэто большое дело. Однако это заставляло функцию исчерпывать память каждый раз. Поэтому я решил разделить ее на более мелкие части.

Поэтому, когда файл загружен, я сначала разбил его на строки. Затем я перебираю строки, делю их на столбцы и выполняю набор в базе данных реального времени по ссылке /grid/[currentrow]. Это означает, что я каждый раз вызываю set 2500 раз. Я пытался использовать await для каждой строки и использовать Promise.all() (текущая версия, но иногда иногда кажется, что оба зависают. Я не получаю никаких ошибок в журнале, просто тайм-аут, когда 540 сеconds прошли.

Запланированная функция:

exports.scheduledDataUpdate = functions
  .region("europe-west1")
  .runWith({
    timeoutSeconds: 540,
    memory: '2GB'
  })
  .pubsub.schedule("0 * * * *")
  .onRun(async () => {
    try {
      await updateDatabase();
      console.log('Database updated');
      return true;
    } catch(error) {
      console.error(error);
      return false;
    }
  });

, которая вызывает метод updateDatabase:

async function updateDatabase() {
  let data;
  try {
    data = await getNewData().catch(err => console.error(err));
  } catch(error) {
    console.error(error);
    return null;
  }
  console.log('Data download complete');

  const lines = data.split("\n"); // split the data into rows (2500)

  lines.forEach((line, r) => {
    const cols = line.split(/\s+/); // split the row into columns (2500)
    dbUpdates.push(admin.database().ref(`/grid/${r}`).set(cols).then(() => {
      if(r > 1 && r % 500 === 0) {
        console.log(`updated row ${r}`); // just to get some info in logs whether some rows were processed
      }
      return true;
    }).catch((error) => {
      console.error(`Error updating row ${r} -- ${error}`);
    }));
    return true;
  });

  return await Promise.all(dbUpdates);

и, если вам интересно, методgetNewData:

const dataUrl = 'https://some.server/somefile'; // I guess you guess this is different in my code (it is)
async function getNewData() {
  console.log('Start download of data');
  return await new Promise((resolve, reject) => {
    https.get(dataUrl, response => {
      if (response.statusCode === 200) {
        let data = "";
        return response
          .on("data", chunk => {
            data += chunk;
          })
          .on("end", () => {
            resolve(data);
          })
          .on("error", error => {
            reject(error);
            console.error(`error while downloading data: ${data}`);
          });
      } else {
        switch (response.statusCode) {
          case 301:
          case 302:
          case 303:
          case 307:
            console.warning(`Redirected to ${response.headers.location} [${response.statusCode}]`)
            return getNewData(response.headers.location);
          default:
            return reject(new Error(`Could not download new data (error ${response.statusCode})`));
        }
      }
    })
  });
}

Когда я смотрю на квоты, которые предлагает FireBase (я работаю на Blaze, оплачиваю подписку), это действительно не должно быть большой проблемой для запуска,Понятно, что я что-то упускаю или совершаю какую-то глупую ошибку, но для жизни я этого не вижу.

Обновление 1 Пример журнала с тайм-аутом (задано @FrankvanPuffelen)

7:15:03.224 p.m.     scheduledDataUpdate      Function execution started
7:15:03.546 p.m.     scheduledDataUpdate      Start download of data
7:15:08.035 p.m.     scheduledDataUpdate      Data download complete
7:22:28.390 p.m.     scheduledDataUpdate      updated row 500
7:24:03.244 p.m.     scheduledDataUpdate      Function execution took 540021 ms, finished with status: 'timeout'

1 Ответ

0 голосов
/ 31 октября 2019

Как вы можете видеть в комментариях к исходным вопросам, Фрэнк ван Пуффелен предположил, что этот вариант использования просто больше, чем может обработать среда функций. Я сделал много профилирования, и я могу только сделать вывод, что это действительно так. Поэтому я попробовал следующее:

  1. Вместо того, чтобы пытаться обновлять 2500 строк с каждыми 2500 элементами каждый час (таким образом, 2500 сетов с объектом из 2500 элементов), я разбил его на куски по 500строки каждые 12 минут. Это несколько помогает, поскольку теперь функция может обновлять всю базу данных каждый час, хотя и с некоторой задержкой для некоторых строк (что в моем случае хорошо);без тайм-аута. К сожалению, каждый из этих прогонов займет около семи минут. Это добавляет к процессорным секундам совсем немного. Мы не говорим об огромных суммах денег здесь, но это будет означать 40-50 долларов в месяц. Поэтому я хотел попытаться сделать это немного быстрее.
  2. Я понял, что довольно много полей (примерно 35%, как оказалось) содержат значение -1,00, что в основном означает «нет данных». (кстати, каждый элемент данных - это положительное число с двумя десятичными знаками). Вместо того чтобы добавить их в базу данных, я решил, что это можно решить в конце, когда мы запросим данные позже. Если значение не существует в базе данных, это означает, что у нас было значение -1,00 для начала. Поэтому для каждой строки (или строки, если хотите) я создаю объект только с теми полями, которые имеют положительное значение. Затем я запустил все строки один раз, поэтому пустое поле было удалено. И низко и вот, после этого все стало быстро, и я имею в виду очень быстро. Вместо 7 минут на 50 строк, теперь мы закончим менее чем за 10 секунд.

Так что да, размер ваших данных сильно влияет на производительность, еще раз спасибо @frankvanpuffelen за подсказкув правильном направлении!

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