Огневая база облачных функций идет очень медленно


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

наша текущая проблема-это скорость функции. Сама функция занимает около 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;
}
5 80

5 ответов:

огнемет здесь

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

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

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

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

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

обновление с кодом - например, если у вас есть следующий файл индекса:

...
exports.doSomeThing = require('./doSomeThing');
exports.doSomeThingElse = require('./doSomeThingElse');
exports.doOtherStuff = require('./doOtherStuff');
// and more.......

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

вместо разделения вашего включает в себя как:

if (!process.env.FUNCTION_NAME || process.env.FUNCTION_NAME === 'doSomeThing') {
  exports.doSomeThing = require('./doSomeThing');
}
if (!process.env.FUNCTION_NAME || process.env.FUNCTION_NAME === 'doSomeThingElse') {
  exports. doSomeThingElse = require('./doSomeThingElse');
}
if (!process.env.FUNCTION_NAME || process.env.FUNCTION_NAME === 'doOtherStuff') {
  exports. doOtherStuff = require('./doOtherStuff');
}

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


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


Оригинальный Ответ

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

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

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

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

Я столкнулся с аналогичными проблемами с функциями firestore cloud. Самое большое-это производительность. Особенно в случае ранних стартапов, когда вы не можете позволить своим ранним клиентам видеть "вялые" приложения. Простая функция генерации документации, например, дает следующее:

-- выполнение функции заняло 9522 МС, завершилось кодом состояния: 200

затем: у меня была прямая страница условий и положений. С облачными функциями выполнение из-за холодного запуска займет 10-15 секунд даже время от времени. Затем я переместил его в узел.приложение js, размещенное в контейнере appengine. Время сократилось до 2-3 секунд.

я сравнивал многие функции mongodb с firestore, и иногда мне тоже интересно, должен ли я на этом раннем этапе моего продукта также перейти к другой базе данных. Самым большим adv, который у меня был в firestore, была функция триггера onCreate, onUpdate документа объекты.

https://db-engines.com/en/system/Google + облако + Firestore%3BMongoDB

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

еще одна проблема-бесплатный план (Spark).

spark

Как только я переключаюсь на платный план (Blaze в моем случае), то мои функции начинают работать быстро.

blaze

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

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