Firebase bulut işlevleri çok yavaş


131

Yeni firebase bulut işlevlerini kullanan bir uygulama üzerinde çalışıyoruz. Şu anda olan şey, kuyruk düğümüne bir işlemin yerleştirilmesidir. Ve sonra işlev bu düğümü kaldırır ve doğru düğüme yerleştirir. Bu, çevrimdışı çalışma yeteneği nedeniyle uygulandı.

Şu anki sorunumuz, fonksiyonun hızıdır. Fonksiyonun kendisi yaklaşık 400 ms sürer, bu yüzden sorun değil. Ancak bazen giriş kuyruğa zaten eklenmişken işlevler çok uzun zaman alır (yaklaşık 8 saniye).

Sunucunun yeniden başlatılmasının zaman aldığından şüpheleniyoruz çünkü ilk eylemden sonra eylemi bir kez daha yaptığımızda. Daha az zaman alır.

Bu sorunu gidermenin herhangi bir yolu var mı? Buraya fonksiyonumuzun kodunu ekledim. Bunda yanlış bir şey olmadığından şüpheleniyoruz, ancak her ihtimale karşı ekledik.

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;
}

Yukarıdaki 'bir kez ()' çağrısına geri dönmemek güvenli midir?
jazzgil

Yanıtlar:


112

itfaiye burada

Görünüşe göre, işlevin sözde soğuk başlangıcını yaşıyorsunuz.

İşleviniz bir süre çalıştırılmadığında, Cloud Functions onu daha az kaynak kullanan bir moda geçirir. Ardından işleve tekrar bastığınızda, ortamı bu moddan geri yükler. Geri yükleme süresi, sabit bir maliyetten (örneğin, kabı geri yükleme) ve bir parça değişken maliyetten (örneğin, çok sayıda düğüm modülü kullanıyorsanız, daha uzun sürebilir) oluşur.

Geliştirici deneyimi ve kaynak kullanımı arasında en iyi karışımı sağlamak için bu işlemlerin performansını sürekli olarak izliyoruz. Bu zamanların zamanla iyileşmesini bekleyin.

İyi haber şu ki, bunu yalnızca geliştirme sırasında deneyimlemelisiniz. İşlevleriniz üretim sırasında sık sık tetiklendikten sonra, muhtemelen bir daha soğuk bir başlangıç ​​yapmayacaklardır.


3
Moderatör Notu : Bu gönderideki tüm konu dışı yorumlar kaldırılmıştır. Lütfen yorumları yalnızca açıklama istemek veya iyileştirme önermek için kullanın. İlgili ancak farklı bir sorunuz varsa, yeni bir soru sorun ve bağlam sağlamaya yardımcı olması için buna bir bağlantı ekleyin.
Bhargav Rao

55

Güncelleme Mayıs 2020 maganap tarafından yapılan yorum için teşekkürler - Düğüm 10+ FUNCTION_NAMEile değiştirilir K_SERVICE( FUNCTION_TARGETişlevin kendisidir, adı değil, yerine geçer ENTRY_POINT). Aşağıdaki kod örnekleri aşağıda güncellenmiştir.

Https://cloud.google.com/functions/docs/migrating/nodejs-runtimes#nodejs-10-changes adresinde daha fazla bilgi

Güncelleme - bu sorunların çoğu process.env.FUNCTION_NAMEburada görüldüğü gibi gizli değişken kullanılarak çözülebilecek gibi görünüyor: https://github.com/firebase/functions-samples/issues/170#issuecomment-323375462

Kodla güncelle - Örneğin, aşağıdaki dizin dosyanız varsa:

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

Daha sonra tüm dosyalarınız yüklenecek ve tüm bu dosyaların gereksinimleri de yüklenecek, bu da çok fazla ek yüke neden olacak ve tüm işlevleriniz için genel kapsamınızı kirletecektir.

Bunun yerine içeriğinizi şu şekilde ayırmak:

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

Bu, yalnızca gerekli dosya (lar) ı bu işlev özel olarak çağrıldığında yükleyecektir; küresel kapsamınızı daha temiz tutmanıza olanak tanır ve bu da daha hızlı soğuk başlatmalara neden olur.


Bu, aşağıda yaptığımdan çok daha temiz bir çözüme izin vermelidir (aşağıdaki açıklama hala geçerli olsa da).


Orijinal Cevap

Görünüşe göre dosya gerektiriyor ve küresel kapsamda gerçekleşen genel başlatma, soğuk başlatma sırasında yavaşlamanın büyük bir nedeni.

Bir proje daha fonksiyonlarını alır gibi küresel kapsam sorun çözülemez hale gittikçe daha kirleniyor - özellikle kapsam eğer işlevlerini ayrı dosyalar halinde (örneğin kullanarak olarak Object.assign(exports, require('./more-functions.js'));Gözlerinde farklı index.js.

Tüm gereksinimlerimi aşağıdaki gibi bir init yöntemine taşıyarak ve ardından bu dosya için herhangi bir işlev tanımının içindeki ilk satır olarak adlandırarak soğuk başlatma performansında büyük kazançlar görmeyi başardım. Örneğin:

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;
}

Bu tekniği 8 dosyada ~ 30 işleve sahip bir projeye uygularken yaklaşık 7-8 saniyeden 2-3 saniyeye kadar iyileştirmeler gördüm. Bu aynı zamanda, işlevlerin daha az sıklıkta soğuk başlatılması gerekmesine neden oluyor gibi görünüyor (muhtemelen daha düşük bellek kullanımı nedeniyle?)

Ne yazık ki bu, HTTP işlevlerini, kullanıcıya yönelik üretim kullanımı için zar zor kullanılabilir hale getiriyor.

Firebase ekibinin, gelecekte her işlev için yalnızca ilgili modüllerin yüklenmesi gerekecek şekilde işlevlerin uygun şekilde kapsamına alınmasına izin verecek bazı planları olmasını umuyoruz.


Hey Tyris, zaman işlemiyle aynı sorunla karşılaşıyorum, çözümünüzü uygulamaya çalışıyorum. sadece anlamaya çalışıyorum, init işlevini kim ve ne zaman arıyor?
Manspof

Merhaba @AdirZoari, init () ve benzerlerini kullanma açıklamam muhtemelen en iyi uygulama değildir; değeri sadece temel sorunla ilgili bulgularımı göstermektir. Gizli değişkene bakmanız process.env.FUNCTION_NAMEve bunu, o işlev için gereken dosyaları koşullu olarak dahil etmek için kullanmanız çok daha iyi olur . Github.com/firebase/functions-samples/issues/… adresindeki yorum, bu çalışmanın gerçekten iyi bir tanımını veriyor! Global kapsamın yöntemlerle kirlenmemesini ve ilgisiz fonksiyonları içermesini sağlar.
Tyris

1
Merhaba @davidverweij, bunun, işlevlerinizin iki kez veya paralel olarak çalışması olasılığı açısından yardımcı olacağını düşünmüyorum. İşlevler gerektiğinde otomatik ölçeklenir, böylece birden çok işlev (aynı işlev veya farklı işlevler) herhangi bir zamanda paralel olarak çalıştırılabilir. Bu, veri güvenliğini göz önünde bulundurmanız ve işlemleri kullanmayı düşünmeniz gerektiği anlamına gelir. Ayrıca, muhtemelen iki kez çalışan işlevlerinizle ilgili bu makaleye göz atın
Tyris

1
Bildirim FUNCTIONS_NAME, burada açıklandığı gibi yalnızca 6. ve 8. düğümler için geçerlidir: cloud.google.com/functions/docs/… . Düğüm 10 kullanmalıFUNCTION_TARGET
maganap

1
@Maganap güncellemesi için teşekkürler, doco'ya K_SERVICEgöre cloud.google.com/functions/docs/migrating/… adresinde kullanılması gerektiği anlaşılıyor - Cevabımı güncelledim.
Tyris

7

Firestore bulut işlevleriyle ilgili benzer sorunlarla karşılaşıyorum. En büyüğü performanstır. Özellikle erken aşamadaki başlangıçlar söz konusu olduğunda, ilk müşterilerinizin "yavaş" uygulamaları görmesini karşılayamadığınızda. Örneğin, basit bir belge oluşturma işlevi şunu sağlar:

- İşlevin yürütülmesi 9522 ms sürdü, durum koduyla tamamlandı: 200

Sonra: Basit bir şartlar ve koşullar sayfam vardı. Bulut işlevleriyle, soğuk çalıştırmadan kaynaklanan yürütme bazen bile 10-15 saniye sürer. Daha sonra onu appengine kapsayıcısında barındırılan bir node.js uygulamasına taşıdım. Süre 2-3 saniyeye indi.

Mongodb'un birçok özelliğini firestore ile karşılaştırıyorum ve bazen ürünümün bu erken aşamasında farklı bir veritabanına geçmem gerekip gerekmediğini merak ediyorum. Firestore'da sahip olduğum en büyük avantaj, belge nesnelerinin onCreate, onUpdate'i tetikleme işleviydi.

https://db-engines.com/en/system/Google+Cloud+Firestore%3BMongoDB

Temel olarak, sitenizin appengine ortamına yüklenebilecek statik bölümleri varsa, belki de kötü bir fikir değildir.


1
Firebase Functions'ın dinamik kullanıcıya dönük içeriği görüntüleme açısından amaca uygun olduğunu düşünmüyorum. Parola sıfırlama gibi şeyler için az miktarda HTTP işlevi kullanıyoruz, ancak genel olarak dinamik içeriğiniz varsa, bunu başka bir yerde ekspres uygulama olarak sunun (veya bir diff dili kullanın).
Tyris

2

Bunları da yaptım, bu işlevler ısındığında performansı artırıyor, ancak soğuk başlangıç ​​beni öldürüyor. Karşılaştığım diğer sorunlardan biri de korselerde, çünkü işi tamamlamak için bulut işlevlerine iki kez gitmek gerekiyor. Eminim bunu düzeltebilirim.

Sık kullanılmadığında erken (demo) aşamasında bir uygulamanız olduğunda, performans çok iyi olmayacaktır. Erken ürünü erken benimseyenlerin potansiyel müşterilerin / yatırımcıların önünde ellerinden gelenin en iyisini yapmaları gerektiğinden, bu dikkate alınması gereken bir şeydir. Teknolojiyi sevdik, bu yüzden denenmiş ve doğrulanmış eski çerçevelerden geçiş yaptık, ancak uygulamamız bu noktada oldukça yavaş görünüyor. Daha sonra daha iyi görünmesi için bazı ısınma stratejileri deneyeceğim


Her bir işlevi uyandırmak için bir cron-job'ı test ediyoruz. Belki bu yaklaşım size de yardımcı olur.
Jesús Fuentes

hey @ JesúsFuentes Sadece işlevi uyandırmanın sizin için işe yarayıp yaramadığını merak ediyordum. Çılgın bir çözüm gibi görünüyor: D
Alexandr Zavalii

1
Merhaba @Alexandr, ne yazık ki bunu yapacak vaktimiz olmadı, ancak bu bizim öncelikli listemizde. Yine de teorik olarak çalışması gerekir. Sorun, bir Firebase Uygulamasından başlatılması gereken onCall işlevleriyle birlikte gelir. Belki müşteriden her X dakikada bir onları arıyor? Göreceğiz.
Jesús Fuentes

1
@Alexandr Stackoverflow dışında bir konuşma yapalım mı? Yeni yaklaşımlarla birbirimize yardımcı olabiliriz.
Jesús Fuentes

1
@Alexandr Bu 'uyandırma' geçici çözümünü henüz test etmedik, ancak işlevlerimizi zaten europe-west1'e yerleştirdik. Yine de kabul edilemez zamanlar.
Jesús Fuentes

0

GÜNCELLEME / DÜZENLEME: MAYIS2020'de yeni sözdizimi ve güncellemeler geliyor

Adında bir paket yayınladım better-firebase-functions, otomatik olarak işlev dizininizi arar ve bulunan tüm işlevleri dışa aktarılan nesnenize yerleştirirken, soğuk başlatma performansını iyileştirmek için işlevleri birbirinden izole eder.

Modül kapsamındaki her işlev için yalnızca ihtiyaç duyduğunuz bağımlılıkları yavaş yükler ve önbelleğe alırsanız, işlevlerinizi hızla büyüyen bir projeye göre en iyi şekilde verimli tutmanın en basit ve en kolay yolu olduğunu göreceksiniz.

import { exportFunctions } from 'better-firebase-functions'
exportFunctions({__filename, exports})

ilginç .. 'daha iyi-firebase-fonksiyonlarının' deposunu nerede görebilirim?
JerryGoyal

1
github.com/gramstr/better-firebase-functions - lütfen kontrol edin ve ne düşündüğünüzü bana bildirin! Katkıda bulunmaktan da çekinmeyin :)
George43g
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.