फायरबेस क्लाउड फ़ंक्शंस बहुत धीमा है


131

हम एक ऐसे अनुप्रयोग पर काम कर रहे हैं जो नए फायरबेस क्लाउड फ़ंक्शन का उपयोग करता है। वर्तमान में क्या हो रहा है कि एक लेन-देन कतार नोड में रखा गया है। और फिर फ़ंक्शन उस नोड को निकालता है और सही नोड में डालता है। यह ऑफ़लाइन काम करने की क्षमता के कारण लागू किया गया है।

हमारी वर्तमान समस्या फ़ंक्शन की गति है। समारोह में लगभग 400ms लगते हैं, इसलिए यह ठीक है। लेकिन कभी-कभी फ़ंक्शंस में बहुत लंबा समय लगता है (लगभग 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;
}

क्या उपरोक्त '' एक बार () कॉल के वादे को वापस नहीं करना सुरक्षित है?
जाजगिल

जवाबों:


111

यहाँ फायरबैशर

ऐसा लगता है कि आप फ़ंक्शन की एक तथाकथित ठंड की शुरुआत का अनुभव कर रहे हैं।

जब आपके फ़ंक्शन को कुछ समय में निष्पादित नहीं किया गया है, तो क्लाउड फ़ंक्शंस इसे एक मोड में रखता है जो कम संसाधनों का उपयोग करता है। फिर जब आप फ़ंक्शन को फिर से हिट करते हैं, तो यह इस मोड से पर्यावरण को पुनर्स्थापित करता है। इसे पुनर्स्थापित करने में लगने वाला समय एक निश्चित लागत (जैसे कंटेनर बहाल करना) और एक हिस्सा चर लागत (जैसे यदि आप बहुत सारे नोड मॉड्यूल का उपयोग करते हैं, तो इसमें अधिक समय लग सकता है)।

डेवलपर अनुभव और संसाधन उपयोग के बीच सर्वश्रेष्ठ मिश्रण सुनिश्चित करने के लिए हम इन कार्यों के प्रदर्शन की लगातार निगरानी कर रहे हैं। इसलिए समय के साथ इन सुधारों की अपेक्षा करें।

अच्छी खबर यह है कि आपको केवल विकास के दौरान ही इसका अनुभव करना चाहिए। एक बार जब आपके कार्यों को उत्पादन में अक्सर ट्रिगर किया जा रहा है, तो संभावना है कि वे शायद ही कभी फिर से एक ठंड शुरू कर देंगे।


3
मॉडरेटर नोट : इस पोस्ट पर सभी ऑफ विषय टिप्पणियाँ हटा दी गई हैं। कृपया स्पष्टीकरण का अनुरोध करने के लिए टिप्पणियों का उपयोग करें या केवल सुधार का सुझाव दें। यदि आपके पास संबंधित लेकिन अलग प्रश्न है, तो एक नया प्रश्न पूछें , और संदर्भ प्रदान करने में मदद करने के लिए इस पर एक लिंक शामिल करें।
भार्गव राव

55

मई 2020 अपडेट करें मगनप द्वारा टिप्पणी के लिए धन्यवाद - नोड 10+ में FUNCTION_NAMEइसे बदल दिया गया है K_SERVICE( FUNCTION_TARGETयह स्वयं फ़ंक्शन है, यह नाम नहीं है, प्रतिकृति है ENTRY_POINT)। नीचे दिए गए कोड के नमूने नीचे दिए गए हैं।

Https://cloud.google.com/functions/docs/migrating/nodejs-runtimes#nodejs-10-changes पर अधिक जानकारी

अपडेट - ऐसा लगता है कि इनमें से बहुत सी समस्याओं का हल छिपे चर का उपयोग करके 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.......

फिर आपकी सभी फाइलें लोड हो जाएंगी, और उन सभी फाइलों की आवश्यकताओं को भी लोड किया जाएगा, जिसके परिणामस्वरूप बहुत सारे ओवरहेड और आपके सभी कार्यों के लिए आपके वैश्विक दायरे को प्रदूषित करेंगे।

इसके बजाय अपने को अलग करने के रूप में शामिल हैं:

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

यह केवल आवश्यक फ़ाइल को लोड करेगा जब उस फ़ंक्शन को विशेष रूप से कहा जाता है; आपको अपने वैश्विक दायरे को बहुत अधिक साफ रखने की अनुमति देता है, जिसके परिणामस्वरूप तेजी से कोल्ड-बूट का परिणाम होना चाहिए।


यह मैंने क्या किया है की तुलना में एक बहुत कम समाधान के लिए अनुमति देना चाहिए (हालांकि नीचे विवरण अभी भी रखती है)।


मूल उत्तर

ऐसा लगता है कि फ़ाइलों की आवश्यकता है और वैश्विक दायरे में हो रही सामान्य आरंभिकता शीत-बूट के दौरान धीमी गति का एक बड़ा कारण है।

जैसा कि एक परियोजना को अधिक कार्य मिलते हैं, वैश्विक गुंजाइश अधिक से अधिक प्रदूषित हो जाती है और समस्या को और अधिक बदतर बना देती है - खासकर यदि आप अपने कार्यों को अलग-अलग फ़ाइलों में विभाजित करते हैं (जैसे कि Object.assign(exports, require('./more-functions.js'));आपके द्वारा उपयोग करके index.js

मैंने नीचे दिए गए तरीके से अपनी सभी आवश्यकताएं पूरी करके और फिर उस फ़ाइल के लिए किसी भी फ़ंक्शन परिभाषा के अंदर पहली पंक्ति के रूप में कॉल करके शीत-बूट प्रदर्शन में भारी लाभ देखने में कामयाब रहा है। उदाहरण के लिए:

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

मैंने इस तकनीक को एक परियोजना में 8 फाइलों में ~ 30 कार्यों के साथ लागू करने पर लगभग 7-8 से नीचे 2-3 तक सुधार देखा है। ऐसा लगता है कि कार्यों को कम-से-कम ठंडा करने की आवश्यकता होती है (संभवतः स्मृति का कम उपयोग होने के कारण?)

दुर्भाग्य से यह अभी भी उपयोगकर्ता-सामना उत्पादन उपयोग के लिए HTTP कार्यों को मुश्किल से उपयोगी बनाता है।

फायरबस टीम की उम्मीद भविष्य में कुछ कार्यों की उचित ढलान की अनुमति देने के लिए है ताकि केवल प्रासंगिक मॉड्यूल को प्रत्येक फ़ंक्शन के लिए लोड करने की आवश्यकता हो।


हे टायरिस, मैं समय ऑपरेशन के साथ एक ही मुद्दे का सामना कर रहा हूं, मैं आपके समाधान को लागू करने की कोशिश कर रहा हूं। बस समझने की कोशिश कर रहा है, कौन init फ़ंक्शन को कॉल करता है और कब?
मैनस्पो

हाय @ AdirZoari, init () और आगे का उपयोग करने की मेरी व्याख्या शायद सबसे अच्छा अभ्यास नहीं है; इसका मूल्य बस मुख्य समस्या के बारे में मेरे निष्कर्षों को प्रदर्शित करना है। आप छिपे हुए वैरिएबल को देखने से बेहतर होंगे process.env.FUNCTION_NAMEऔर उस फंक्शन के लिए जरूरी फाइलों को सशर्त रूप से शामिल करना। Github.com/firebase/functions-samples/issues/… पर टिप्पणी इस काम का वास्तव में अच्छा विवरण देती है! यह सुनिश्चित करता है कि वैश्विक गुंजाइश विधियों से प्रदूषित न हो और अप्रासंगिक कार्यों में शामिल हो।
टाइरिस

1
हाय @davidverweij, मुझे नहीं लगता कि यह आपके कार्यों के दो बार या समानांतर चलने की संभावना के संदर्भ में मदद करेगा। ऐसे कई कार्यों (एक ही कार्य, या अलग-अलग) के रूप में आवश्यकतानुसार ऑटो स्केल कार्य किसी भी समय समानांतर में चल सकता है। इसका मतलब है कि आपको डेटा सुरक्षा पर विचार करना होगा और लेनदेन का उपयोग करने पर विचार करना होगा। इसके अलावा, अपने लेखों पर संभवतः दो बार चलने वाले इस लेख को देखें: cloud.google.com/blog/products/serverless/…
Tyris

1
नोटिस FUNCTIONS_NAMEकेवल नोड 6 और 8 के साथ मान्य है, जैसा कि यहां बताया गया है: cloud.google.com/functions/docs/… । नोड 10 का उपयोग करना चाहिएFUNCTION_TARGET
मगनपप

1
धन्यवाद अद्यतन @maganap के लिए, ऐसा लग रहा है का उपयोग करना चाहिए K_SERVICEपर doco के अनुसार cloud.google.com/functions/docs/migrating/... - मैं अपने जवाब को नवीनीकृत किया है।
टायरिस

7

मैं फायरस्टार क्लाउड फ़ंक्शन के साथ समान मुद्दों का सामना कर रहा हूं। सबसे बड़ा प्रदर्शन है। विशेष रूप से शुरुआती चरण के स्टार्टअप के मामले में, जब आप "सुस्त" ऐप्स को देखने के लिए अपने शुरुआती ग्राहकों को बर्दाश्त नहीं कर सकते। उदाहरण के लिए एक साधारण प्रलेखन पीढ़ी समारोह यह देता है:

- समारोह निष्पादन 9522 एमएस लिया, स्थिति कोड के साथ समाप्त: 200

तब: मेरे पास एक straighforward नियम और शर्तें पृष्ठ थीं। मेघ कार्यों के साथ ठंड शुरू होने के कारण निष्पादन में भी 10-15 सेकंड लगते हैं। फिर मैंने इसे एपेंजिन कंटेनर पर होस्ट किए गए नोड.जेएस ऐप में स्थानांतरित कर दिया। 2-3 सेकंड का समय कम हो गया है।

मैं फायरस्टार के साथ मोंगोडब की कई विशेषताओं की तुलना कर रहा हूं और कभी-कभी मुझे भी आश्चर्य होता है कि क्या मेरे उत्पाद के इस शुरुआती चरण के दौरान मुझे एक अलग डेटाबेस में भी जाना चाहिए। फायरस्टार में मुझे जो सबसे बड़ी सलाह मिली, वह थी ऑनर्रीट की ट्रिगर कार्यक्षमता, डॉक्यूमेंट ऑब्जेक्ट्स का ऑनडेट।

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

मूल रूप से अगर आपकी साइट के स्थिर हिस्से हैं जो कि एपेंगाइन पर्यावरण के लिए बंद हो सकते हैं, तो शायद बुरा विचार नहीं है।


1
मुझे नहीं लगता कि जहां तक ​​डायनेमिक उपयोगकर्ता का सामना करने वाली सामग्री का प्रदर्शन करने के लिए फायरबेस फ़ंक्शंस उद्देश्य के लिए फिट हैं। हम पासवर्ड रीसेट जैसी चीजों के लिए थोड़े HTTP फ़ंक्शन का उपयोग करते हैं, लेकिन सामान्य तौर पर यदि आपके पास डायनामिक कंटेंट है, तो इसे एक्सप्रेस ऐप के रूप में कहीं और परोसें (या एक अलग भाषा का उपयोग करें)।
टाइरिस

2

मैंने इन चीजों को भी किया है, जो कार्य को गर्म करने के बाद प्रदर्शन में सुधार करता है, लेकिन ठंड की शुरुआत मुझे मार रही है। मेरे द्वारा सामना किए गए अन्य मुद्दों में से एक cors के साथ है, क्योंकि यह काम करने के लिए क्लाउड फ़ंक्शंस में दो ट्रिप लेता है। मुझे यकीन है कि मैं इसे ठीक कर सकता हूं, हालांकि।

जब आपके पास इसके प्रारंभिक (डेमो) चरण में एक ऐप होता है जब इसका अक्सर उपयोग नहीं किया जाता है, तो प्रदर्शन बहुत अच्छा नहीं होगा। यह एक ऐसी चीज है जिस पर विचार किया जाना चाहिए, क्योंकि शुरुआती उत्पाद के साथ शुरुआती समर्थकों को संभावित ग्राहकों / निवेशकों के सामने अपना सर्वश्रेष्ठ देखने की जरूरत है। हम तकनीक से प्यार करते थे इसलिए हम पुराने ट्राय-एंड-ट्रू फ्रेमवर्क से चले गए, लेकिन हमारा ऐप इस बिंदु पर बहुत सुस्त है। मैं इसे बेहतर बनाने के लिए कुछ वार्म-अप रणनीतियों की कोशिश करने जा रहा हूं


हम हर एक कार्य को जगाने के लिए क्रोन-जॉब का परीक्षण कर रहे हैं। हो सकता है कि यह दृष्टिकोण आपकी भी मदद करे।
जेसुअस फुएंटेस

अरे @ जेसुसफेंटेस मैं सोच रहा था कि क्या इस समारोह में जागना आपके लिए काम कर रहा है। एक पागल समाधान की तरह लगता है: डी
एलेक्जेंड्रा ज़ावली

1
हाय @ एलेक्ज़ेंडर, दुख की बात है कि हमारे पास अभी तक इसे करने का समय नहीं था, लेकिन यह हमारी सर्वोच्च प्राथमिकता सूची में है। यह सैद्धांतिक रूप से काम करना चाहिए, हालांकि। समस्या onCall फ़ंक्शंस के साथ आती है, जिसे Firebase App से लॉन्च करने की आवश्यकता होती है। शायद उन्हें ग्राहक से हर एक्स मिनट बुला रहा है? हम देखेंगे।
जेसुइस फुएंटेस

1
@ अलेक्जेंडर हम Stackoverflow के बाहर एक बातचीत होगी? हम नए दृष्टिकोणों के साथ एक-दूसरे की मदद कर सकते हैं।
जेसुइस फुएंटेस

1
@ अलेक्जेंडर हमने अभी तक इस 'वेकअप' वर्कअराउंड का परीक्षण नहीं किया था, लेकिन हमने पहले से ही अपने कार्यों को यूरोप-वेस्ट 1 में तैनात कर दिया था। फिर भी, अस्वीकार्य समय।
जेसुइस फुएंटेस

0

अद्यतन / संपादित करें: नया सिंटैक्स और अपडेट MAY2020 आ रहा है

मैंने अभी-अभी एक पैकेज प्रकाशित किया है better-firebase-functions, यह आपके फ़ंक्शन निर्देशिका को स्वचालित रूप से खोजता है और कोल्ड-बूट प्रदर्शन को बेहतर बनाने के लिए एक दूसरे से कार्यों को अलग करते हुए, आपके निर्यात ऑब्जेक्ट में सभी पाए गए फ़ंक्शन को सही ढंग से घोंसला करता है।

यदि आप आलसी-लोड और कैश करते हैं तो केवल मॉड्यूल के दायरे में प्रत्येक फ़ंक्शन के लिए आपको निर्भरता की आवश्यकता होती है, आप पाएंगे कि यह तेजी से बढ़ते प्रोजेक्ट पर अपने कार्यों को बेहतर ढंग से कुशल बनाए रखने का सबसे सरल और आसान तरीका है।

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

दिलचस्प .. मैं 'बेहतर-फायरबेस-फ़ंक्शंस' का रेपो कहाँ देख सकता हूँ?
जेरी गोयल

1
github.com/gramstr/better-firebase-functions - कृपया इसे देखें और मुझे बताएं कि आप क्या सोचते हैं! के रूप में अच्छी तरह से योगदान करने के लिए स्वतंत्र महसूस :)
1:43 पर जॉर्ज 43
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.