Подтвердить что ты не робот

Облачные функции Firebase очень медленные

Мы работаем над приложением, которое использует новые облачные функции firebase. Что происходит в настоящее время, так это то, что транзакция помещается в очередь node. И тогда функция удаляет этот node и помещает его в правильный node. Это было реализовано из-за возможности работать в автономном режиме.

Наша текущая проблема - это скорость функции. Сама функция занимает около 400 мс, так что хорошо. Но иногда функции занимают очень много времени (около 8 секунд), а запись уже добавлена ​​в очередь.

Мы подозреваем, что серверу требуется время для загрузки, потому что когда мы делаем действие еще раз после первого. Это занимает меньше времени.

Есть ли способ исправить эту проблему? Здесь я добавил код нашей функции. Мы подозреваем, что в этом нет ничего плохого, но мы добавили его на всякий случай.

const functions = require('firebase-functions');
const admin = require('firebase-admin');
const database = admin.database();

exports.insertTransaction = functions.database
    .ref('/userPlacePromotionTransactionsQueue/{userKey}/{placeKey}/{promotionKey}/{transactionKey}')
    .onWrite(event => {
        if (event.data.val() == null) return null;

        // get keys
        const userKey = event.params.userKey;
        const placeKey = event.params.placeKey;
        const promotionKey = event.params.promotionKey;
        const transactionKey = event.params.transactionKey;

        // init update object
        const data = {};

        // get the transaction
        const transaction = event.data.val();

        // transfer transaction
        saveTransaction(data, transaction, userKey, placeKey, promotionKey, transactionKey);
        // remove from queue
        data[`/userPlacePromotionTransactionsQueue/${userKey}/${placeKey}/${promotionKey}/${transactionKey}`] = null;

        // fetch promotion
        database.ref(`promotions/${promotionKey}`).once('value', (snapshot) => {
            // Check if the promotion exists.
            if (!snapshot.exists()) {
                return null;
            }

            const promotion = snapshot.val();

            // fetch the current stamp count
            database.ref(`userPromotionStampCount/${userKey}/${promotionKey}`).once('value', (snapshot) => {
                let currentStampCount = 0;
                if (snapshot.exists()) currentStampCount = parseInt(snapshot.val());

                data[`userPromotionStampCount/${userKey}/${promotionKey}`] = currentStampCount + transaction.amount;

                // determines if there are new full cards
                const currentFullcards = Math.floor(currentStampCount > 0 ? currentStampCount / promotion.stamps : 0);
                const newStamps = currentStampCount + transaction.amount;
                const newFullcards = Math.floor(newStamps / promotion.stamps);

                if (newFullcards > currentFullcards) {
                    for (let i = 0; i < (newFullcards - currentFullcards); i++) {
                        const cardTransaction = {
                            action: "pending",
                            promotion_id: promotionKey,
                            user_id: userKey,
                            amount: 0,
                            type: "stamp",
                            date: transaction.date,
                            is_reversed: false
                        };

                        saveTransaction(data, cardTransaction, userKey, placeKey, promotionKey);

                        const completedPromotion = {
                            promotion_id: promotionKey,
                            user_id: userKey,
                            has_used: false,
                            date: admin.database.ServerValue.TIMESTAMP
                        };

                        const promotionPushKey = database
                            .ref()
                            .child(`userPlaceCompletedPromotions/${userKey}/${placeKey}`)
                            .push()
                            .key;

                        data[`userPlaceCompletedPromotions/${userKey}/${placeKey}/${promotionPushKey}`] = completedPromotion;
                        data[`userCompletedPromotions/${userKey}/${promotionPushKey}`] = completedPromotion;
                    }
                }

                return database.ref().update(data);
            }, (error) => {
                // Log to the console if an error happened.
                console.log('The read failed: ' + error.code);
                return null;
            });

        }, (error) => {
            // Log to the console if an error happened.
            console.log('The read failed: ' + error.code);
            return null;
        });
    });

function saveTransaction(data, transaction, userKey, placeKey, promotionKey, transactionKey) {
    if (!transactionKey) {
        transactionKey = database.ref('transactions').push().key;
    }

    data[`transactions/${transactionKey}`] = transaction;
    data[`placeTransactions/${placeKey}/${transactionKey}`] = transaction;
    data[`userPlacePromotionTransactions/${userKey}/${placeKey}/${promotionKey}/${transactionKey}`] = transaction;
}
4b9b3361

Ответ 1

firebaser здесь

Похоже, вы испытываете так называемый холодный запуск функции.

Когда ваша функция не была выполнена за какое-то время, Cloud Function помещает ее в режим, в котором используется меньше ресурсов. Затем, когда вы снова нажмете на функцию, она восстанавливает среду из этого режима. Время, необходимое для восстановления, состоит из фиксированной стоимости (например, восстановления контейнера) и стоимости переменной детали (например, если вы используете много модулей node, это может занять больше времени).

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

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

Ответ 2

Обновление - похоже, что многие из этих проблем могут быть решены с помощью скрытой переменной process.env.FUNCTION_NAME, как показано здесь: https://github.com/firebase/functions-samples/issues/170#issuecomment-323375462

Это должно обеспечить гораздо более аккуратное решение, чем то, что я сделал ниже (хотя приведенное ниже объяснение сохраняется).


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

По мере того как проект получает больше функций, глобальная область загрязняется все больше и больше усугубляет проблему, особенно если вы используете свои функции в отдельных файлах (например, используя Object.assign(exports, require('./more-functions.js')); в index.js.

Мне удалось добиться огромных успехов в производительности холодовой загрузки, переместив все мои требования в метод init, как показано ниже, а затем назвал его первой линией внутри любого определения функции для этого файла. Например:

const functions = require('firebase-functions');
const admin = require('firebase-admin');
// Late initialisers for performance
let initialised = false;
let handlebars;
let fs;
let path;
let encrypt;

function init() {
  if (initialised) { return; }

  handlebars = require('handlebars');
  fs = require('fs');
  path = require('path');
  ({ encrypt } = require('../common'));
  // Maybe do some handlebars compilation here too

  initialised = true;
}

Я видел улучшения примерно с 7-8 до 2-3 лет при применении этого метода к проекту с ~ 30 функциями по 8 файлам. Это также, по-видимому, приводит к тому, что функции требуют более холодной загрузки (возможно, из-за более низкого использования памяти?)

К сожалению, это все еще делает HTTP-функции едва пригодными для использования, ориентированного на пользователя.

В надежде, что команда Firebase планирует в будущем предусмотреть надлежащее определение функций, чтобы для каждой функции приходилось загружать только соответствующие модули.