इस दस्तावेज़ में, Cloud Functions को डिज़ाइन करने, लागू करने, टेस्ट करने, और डिप्लॉय करने के सबसे सही तरीकों के बारे में बताया गया है.
सही जवाब
इस सेक्शन में, Cloud Functions को डिज़ाइन और लागू करने के सबसे सही तरीकों के बारे में बताया गया है.
आईडम्पोटेंट फ़ंक्शन लिखना
आपके फ़ंक्शन को एक ही नतीजा देना चाहिए, भले ही उन्हें कई बार कॉल किया गया हो. इसकी मदद से, कोड के बीच में पिछला इनवोकेशन फ़ेल होने पर, इनवोकेशन को फिर से आज़माया जा सकता है. ज़्यादा जानकारी के लिए, इवेंट ट्रिगर करने वाले फ़ंक्शन को फिर से आज़माना लेख पढ़ें.
बैकग्राउंड में गतिविधियां शुरू न करें
बैकग्राउंड गतिविधि का मतलब है कि फ़ंक्शन के बंद होने के बाद होने वाली कोई भी गतिविधि.
फ़ंक्शन को कॉल करने की प्रोसेस तब पूरी होती है, जब फ़ंक्शन वैल्यू दिखाता है या किसी अन्य तरीके से पूरा होने का सिग्नल देता है. जैसे, Node.js के इवेंट-ड्रिवन फ़ंक्शन में callback आर्ग्युमेंट को कॉल करके. ग्रेसफ़ुल टर्मिनेशन के बाद, कोई भी कोड सीपीयू को ऐक्सेस नहीं कर सकता. साथ ही, उसमें कोई प्रोग्रेस नहीं होगी.
इसके अलावा, जब एक ही एनवायरमेंट में कोई दूसरा अनुरोध किया जाता है, तो बैकग्राउंड में चल रही गतिविधि फिर से शुरू हो जाती है. इससे नए अनुरोध में रुकावट आती है. इस वजह से, अलग तरह की समस्याएं और गड़बड़ियां हो सकती हैं. इनका पता लगाना मुश्किल होता है. फ़ंक्शन के बंद होने के बाद नेटवर्क को ऐक्सेस करने पर, कनेक्शन रीसेट हो जाते हैं. आम तौर पर, ऐसा होने पर ECONNRESET गड़बड़ी कोड दिखता है.
बैकग्राउंड में होने वाली गतिविधि का पता, अलग-अलग इनवोकेशन के लॉग से लगाया जा सकता है. इसके लिए, आपको ऐसी कोई भी चीज़ ढूंढनी होगी जिसे इनवोकेशन के खत्म होने की लाइन के बाद लॉग किया गया हो. बैकग्राउंड में होने वाली गतिविधि कभी-कभी कोड में ज़्यादा गहराई से छिपी हो सकती है. ऐसा खास तौर पर तब होता है, जब एसिंक्रोनस कार्रवाइयां मौजूद हों. जैसे, कॉलबैक या टाइमर. अपने कोड की समीक्षा करें, ताकि यह पक्का किया जा सके कि फ़ंक्शन को बंद करने से पहले सभी एसिंक्रोनस ऑपरेशन पूरे हो गए हों.
कुछ समय के लिए सेव की गई फ़ाइलें हमेशा मिटा दी जाती हैं
अस्थायी डायरेक्ट्री में लोकल डिस्क स्टोरेज, इन-मेमोरी फ़ाइल सिस्टम होता है. लिखी गई फ़ाइलें, आपके फ़ंक्शन के लिए उपलब्ध मेमोरी का इस्तेमाल करती हैं. साथ ही, कभी-कभी ये फ़ाइलें, फ़ंक्शन को बार-बार कॉल करने के दौरान भी बनी रहती हैं. इन फ़ाइलों को साफ़ तौर पर मिटाने में नाकाम रहने पर, मेमोरी से जुड़ी गड़बड़ी हो सकती है. साथ ही, ऐप्लिकेशन को फिर से शुरू करना पड़ सकता है.
Google Cloud Console में फ़ंक्शन की सूची में जाकर, किसी फ़ंक्शन को चुनें. इसके बाद, मेमोरी के इस्तेमाल का प्लॉट चुनें. इससे आपको उस फ़ंक्शन के इस्तेमाल की गई मेमोरी की जानकारी मिलेगी.
अगर आपको लंबे समय तक डेटा को सेव करके रखना है, तो Cloud Run Cloud Storage या NFS वॉल्यूम के साथ वॉल्यूम माउंट का इस्तेमाल करें.
पाइपलाइनिंग का इस्तेमाल करके, बड़ी फ़ाइलों को प्रोसेस करने के दौरान मेमोरी की ज़रूरत को कम किया जा सकता है. उदाहरण के लिए, Cloud Storage पर मौजूद किसी फ़ाइल को प्रोसेस किया जा सकता है. इसके लिए, एक रीड स्ट्रीम बनाएं, उसे स्ट्रीम-आधारित प्रोसेस से पास करें, और आउटपुट स्ट्रीम को सीधे Cloud Storage में लिखें.
Functions Framework
हमारा सुझाव है कि आप अपने पैकेज मैनेजर में Functions Framework लाइब्रेरी को शामिल करें. साथ ही, डिपेंडेंसी को Functions Framework के किसी खास वर्शन पर पिन करें. इससे यह पक्का किया जा सकेगा कि सभी एनवायरमेंट में एक जैसी डिपेंडेंसी इंस्टॉल की गई हैं.
इसके लिए, अपनी पसंद के वर्शन को लॉक फ़ाइल में शामिल करें. उदाहरण के लिए,
Node.js के लिए package-lock.json या Python के लिए requirements.txt.
अगर Functions Framework को साफ़ तौर पर डिपेंडेंसी के तौर पर लिस्ट नहीं किया गया है, तो इसे बिल्ड प्रोसेस के दौरान अपने-आप जोड़ दिया जाएगा. इसके लिए, उपलब्ध सबसे नए वर्शन का इस्तेमाल किया जाएगा.
टूल
इस सेक्शन में, Cloud Functions को लागू करने, उसकी जांच करने, और उससे इंटरैक्ट करने के लिए टूल इस्तेमाल करने के बारे में दिशा-निर्देश दिए गए हैं.
लोकल डेवलपमेंट
फ़ंक्शन को डिप्लॉय करने में थोड़ा समय लगता है. इसलिए, अक्सर फ़ंक्शन के कोड को स्थानीय तौर पर टेस्ट करना ज़्यादा तेज़ होता है.
Firebase डेवलपर, Firebase CLI Cloud Functions Emulator का इस्तेमाल कर सकते हैं.शुरू होने के दौरान डिप्लॉयमेंट के टाइमआउट से बचें
अगर फ़ंक्शन डिप्लॉयमेंट के दौरान टाइमआउट की गड़बड़ी होती है, तो इसका मतलब है कि डिप्लॉयमेंट प्रोसेस के दौरान, आपके फ़ंक्शन के ग्लोबल स्कोप कोड को एक्ज़ीक्यूट होने में बहुत ज़्यादा समय लग रहा है.
Firebase CLI में, डिप्लॉयमेंट के दौरान आपके फ़ंक्शन ढूंढने के लिए डिफ़ॉल्ट टाइमआउट होता है. अगर आपके फ़ंक्शन के सोर्स कोड में मौजूद इनिशियलाइज़ेशन लॉजिक (मॉड्यूल लोड करना, नेटवर्क कॉल करना वगैरह) इस टाइम आउट से ज़्यादा है, तो हो सकता है कि डिप्लॉयमेंट पूरा न हो.
टाइम आउट से बचने के लिए, इनमें से किसी एक रणनीति का इस्तेमाल करें:
(सुझाया गया) प्रोसेस शुरू करने में देरी करने के लिए, onInit() का इस्तेमाल करें
डिप्लॉयमेंट के दौरान, इनिशियलाइज़ेशन कोड को चलाने से रोकने के लिए, onInit() हुक का इस्तेमाल करें. onInit() हुक के अंदर मौजूद कोड सिर्फ़ तब चलेगा, जब फ़ंक्शन को Cloud Run फ़ंक्शन पर डिप्लॉय किया जाएगा. यह डिप्लॉयमेंट प्रोसेस के दौरान नहीं चलेगा.
Node.js
const { onInit } = require('firebase-functions/v2/core'); const { onRequest } = require('firebase-functions/v2/https'); // Example of a slow initialization task function slowInitialization() { // Simulate a long-running operation (e.g., loading a large model, network request). return new Promise(resolve => { setTimeout(() => { console.log("Slow initialization complete"); resolve("Initialized Value"); }, 20000); // Simulate a 20-second delay }); } let initializedValue; onInit(async () => { initializedValue = await slowInitialization(); }); exports.myFunction = onRequest((req, res) => { // Access the initialized value. It will be ready after the first invocation. res.send(`Value: ${initializedValue}`); });
Python
from firebase_functions.core import init from firebase_functions import https_fn import time # Example of a slow initialization task def _slow_initialization(): time.sleep(20) # Simulate a 20-second delay print("Slow initialization complete") return "Initialized Value" _initialized_value = None @init def initialize(): global _initialized_value _initialized_value = _slow_initialization() @https_fn.on_request() def my_function(req: https_fn.Request) -> https_fn.Response: # Access the initialized value. It will be ready after the first invocation. return https_fn.Response(f"Value: {_initialized_value}")
(दूसरा तरीका) डिवाइस खोजने का समय बढ़ाना
अगर onInit() का इस्तेमाल करने के लिए, कोड को फिर से फ़ैक्टर नहीं किया जा सकता, तो FUNCTIONS_DISCOVERY_TIMEOUT एनवायरमेंट वैरिएबल का इस्तेमाल करके, सीएलआई के डिप्लॉयमेंट टाइमआउट को बढ़ाया जा सकता है:
$ export FUNCTIONS_DISCOVERY_TIMEOUT=30
$ firebase deploy --only functions
ईमेल भेजने के लिए Sendgrid का इस्तेमाल करना
Cloud Functions, पोर्ट 25 पर आउटबाउंड कनेक्शन की अनुमति नहीं देता है. इसलिए, एसएमटीपी सर्वर से असुरक्षित कनेक्शन नहीं बनाए जा सकते. ईमेल भेजने के लिए, SendGrid जैसी तीसरे पक्ष की सेवा का इस्तेमाल करने का सुझाव दिया जाता है. Google Compute Engine के लिए, किसी इंस्टेंस से ईमेल भेजना ट्यूटोरियल में, ईमेल भेजने के अन्य विकल्प देखे जा सकते हैं.
परफ़ॉर्मेंस
इस सेक्शन में, परफ़ॉर्मेंस को ऑप्टिमाइज़ करने के सबसे सही तरीके बताए गए हैं.
कम संख्या में एक साथ अनुरोध न करें
कोल्ड स्टार्ट में ज़्यादा समय लगता है. इसलिए, लोड को मैनेज करने के लिए, हाल ही में शुरू किए गए इंस्टेंस का फिर से इस्तेमाल करना एक बेहतरीन ऑप्टिमाइज़ेशन है. कॉन्करेंसी को सीमित करने से, मौजूदा इंस्टेंस का इस्तेमाल सीमित हो जाता है. इसलिए, कोल्ड स्टार्ट की संख्या बढ़ जाती है.
एक साथ कई अनुरोधों को प्रोसेस करने की सुविधा से, हर इंस्टेंस के लिए कई अनुरोधों को कुछ समय के लिए रोका जा सकता है. इससे लोड में अचानक होने वाली बढ़ोतरी को आसानी से मैनेज किया जा सकता है.डिपेंडेंसी का इस्तेमाल सोच-समझकर करें
फ़ंक्शन स्टेटलेस होते हैं. इसलिए, एक्ज़ीक्यूशन एनवायरमेंट को अक्सर शुरू से ही शुरू किया जाता है. इसे कोल्ड स्टार्ट कहा जाता है. कोल्ड स्टार्ट होने पर, फ़ंक्शन के ग्लोबल कॉन्टेक्स्ट का आकलन किया जाता है.
अगर आपके फ़ंक्शन, मॉड्यूल इंपोर्ट करते हैं, तो उन मॉड्यूल को कॉन्टेंट लोड होने में लगने वाला समय, कोल्ड स्टार्ट के दौरान शुरू करने में लगने वाले इंतज़ार के समय में जुड़ सकता है. डिपेंडेंसी को सही तरीके से लोड करके और उन डिपेंडेंसी को लोड न करके जिनका इस्तेमाल आपका फ़ंक्शन नहीं करता है, इस लेटेन्सी को कम किया जा सकता है. साथ ही, फ़ंक्शन को डिप्लॉय करने में लगने वाले समय को भी कम किया जा सकता है.
ग्लोबल वैरिएबल का इस्तेमाल करके, आने वाले समय में ऑब्जेक्ट का दोबारा इस्तेमाल करना
इस बात की कोई गारंटी नहीं है कि फ़ंक्शन की स्थिति को आने वाले समय में इनवॉकेशन के लिए सुरक्षित रखा जाएगा. हालांकि, Cloud Functions अक्सर पिछले इनवोकेशन के एक्ज़ीक्यूशन एनवायरमेंट को रीसाइकल करता है. अगर किसी वैरिएबल को ग्लोबल स्कोप में सेट किया जाता है, तो उसकी वैल्यू को बाद के इनवोकेशन में फिर से इस्तेमाल किया जा सकता है. इसके लिए, वैल्यू को फिर से कैलकुलेट करने की ज़रूरत नहीं होती.
इस तरह, उन ऑब्जेक्ट को कैश मेमोरी में सेव किया जा सकता है जिन्हें हर फ़ंक्शन कॉल पर फिर से बनाने में ज़्यादा समय लगता है. ऐसे ऑब्जेक्ट को फ़ंक्शन बॉडी से ग्लोबल स्कोप में ले जाने से, परफ़ॉर्मेंस में काफ़ी सुधार हो सकता है. यहां दिए गए उदाहरण में, हर फ़ंक्शन इंस्टेंस के लिए सिर्फ़ एक बार हैवी ऑब्जेक्ट बनाया जाता है. साथ ही, इसे दिए गए इंस्टेंस तक पहुंचने वाले सभी फ़ंक्शन इनवोकेशन के साथ शेयर किया जाता है:
Node.js
console.log('Global scope'); const perInstance = heavyComputation(); const functions = require('firebase-functions'); exports.function = functions.https.onRequest((req, res) => { console.log('Function invocation'); const perFunction = lightweightComputation(); res.send(`Per instance: ${perInstance}, per function: ${perFunction}`); });
Python
import time from firebase_functions import https_fn # Placeholder def heavy_computation(): return time.time() # Placeholder def light_computation(): return time.time() # Global (instance-wide) scope # This computation runs at instance cold-start instance_var = heavy_computation() @https_fn.on_request() def scope_demo(request): # Per-function scope # This computation runs every time this function is called function_var = light_computation() return https_fn.Response(f"Instance: {instance_var}; function: {function_var}")
यह एचटीटीपी फ़ंक्शन, अनुरोध ऑब्जेक्ट (flask.Request) लेता है और जवाब के तौर पर टेक्स्ट या वैल्यू का कोई ऐसा सेट दिखाता है जिसे make_response का इस्तेमाल करके, Response ऑब्जेक्ट में बदला जा सकता है.
ग्लोबल स्कोप में नेटवर्क कनेक्शन, लाइब्रेरी रेफ़रंस, और एपीआई क्लाइंट ऑब्जेक्ट को कैश मेमोरी में सेव करना खास तौर पर ज़रूरी है. उदाहरणों के लिए, नेटवर्किंग को ऑप्टिमाइज़ करना देखें.
कम से कम इंस्टेंस की संख्या सेट करके, कोल्ड स्टार्ट की संख्या कम करना
डिफ़ॉल्ट रूप से, Cloud Functions, आने वाले अनुरोधों की संख्या के आधार पर इंस्टेंस की संख्या को बढ़ाता या घटाता है. इस डिफ़ॉल्ट सेटिंग को बदला जा सकता है. इसके लिए, आपको इंस्टेंस की कम से कम संख्या सेट करनी होगी. Cloud Functions को अनुरोधों को पूरा करने के लिए, इन इंस्टेंस को तैयार रखना होगा. कम से कम इंस्टेंस की संख्या सेट करने से, आपके ऐप्लिकेशन के कोल्ड स्टार्ट कम हो जाते हैं. अगर आपका ऐप्लिकेशन, इंतज़ार के समय के हिसाब से काम करता है, तो हमारा सुझाव है कि आप कम से कम इंस्टेंस सेट करें. साथ ही, लोड होने के समय पर ही शुरू होने की प्रोसेस पूरी करें.
इन रनटाइम विकल्पों के बारे में ज़्यादा जानने के लिए, स्केलिंग के व्यवहार को कंट्रोल करना लेख पढ़ें.कोल्ड स्टार्ट और इनिशियलाइज़ेशन के बारे में नोट
ग्लोबल वैरिएबल को लोड होने के समय ही सेट किया जाता है. इसके बिना, पहली बार किए गए अनुरोध को मॉड्यूल लोड करने और शुरू करने की प्रोसेस पूरी करनी होगी. इस वजह से, ज़्यादा समय लगेगा.
हालांकि, ग्लोबल वैरिएबल के इनिशियलाइज़ेशन का असर कोल्ड स्टार्ट पर भी पड़ता है. इस असर को कम करने के लिए, सिर्फ़ उन चीज़ों को शुरू करें जिनकी पहले अनुरोध के लिए ज़रूरत होती है. इससे पहले अनुरोध में लगने वाले समय को कम से कम रखा जा सकेगा.
यह खास तौर पर तब अहम होता है, जब आपने ऊपर बताए गए तरीके से, कम समय में पूरा होने वाले फ़ंक्शन के लिए कम से कम इंस्टेंस कॉन्फ़िगर किए हों. ऐसे में, कॉन्टेंट लोड होने में लगने वाले समय पर ही शुरू होने की प्रोसेस पूरी कर ली जाती है. साथ ही, काम का डेटा कैश मेमोरी में सेव कर लिया जाता है. इससे यह पक्का होता है कि पहले अनुरोध को यह प्रोसेस पूरी न करनी पड़े और उसे कम इंतज़ार के समय में पूरा किया जा सके.
अगर ग्लोबल स्कोप में वैरिएबल को शुरू किया जाता है, तो भाषा के आधार पर, वैरिएबल को शुरू होने में ज़्यादा समय लग सकता है. इससे दो तरह की समस्याएं हो सकती हैं: - कुछ भाषाओं और एसिंक लाइब्रेरी के कॉम्बिनेशन के लिए, फ़ंक्शन फ़्रेमवर्क एसिंक रूप से चल सकता है और तुरंत जवाब दे सकता है. इससे कोड बैकग्राउंड में चलता रहता है. इससे सीपीयू को ऐक्सेस न कर पाने जैसी समस्याएं हो सकती हैं. ऐसा होने से रोकने के लिए, आपको मॉड्यूल के शुरू होने पर ही इसे ब्लॉक कर देना चाहिए. इसके लिए, यहां दिया गया तरीका अपनाएं. इससे यह भी पक्का होता है कि जब तक शुरू करने की प्रोसेस पूरी नहीं हो जाती, तब तक अनुरोधों को पूरा नहीं किया जाता. - दूसरी ओर, अगर इनिशियलाइज़ेशन सिंक्रोनस है, तो इनिशियलाइज़ेशन में ज़्यादा समय लगने की वजह से कोल्ड स्टार्ट में ज़्यादा समय लगेगा. इससे लोड बढ़ने के दौरान, कम कंकरेंसी वाले फ़ंक्शन में समस्या आ सकती है.
एसिंक्रोनस node.js लाइब्रेरी को प्रीवार्म करने का उदाहरण
Firestore के साथ Node.js, एसिंक्रोनस Node.js लाइब्रेरी का एक उदाहरण है. min_instances का फ़ायदा पाने के लिए, यहां दिया गया कोड लोड होने के समय लोड और शुरू होने की प्रोसेस पूरी करता है. साथ ही, मॉड्यूल लोड होने पर ब्लॉक करता है.
इसमें टीएलए का इस्तेमाल किया जाता है. इसका मतलब है कि ES6 की ज़रूरत होती है. इसके लिए, node.js कोड के लिए .mjs एक्सटेंशन का इस्तेमाल किया जाता है या package.json फ़ाइल में type: module जोड़ा जाता है.
{ "main": "main.js", "type": "module", "dependencies": { "@google-cloud/firestore": "^7.10.0", "@google-cloud/functions-framework": "^3.4.5" } }
Node.js
import Firestore from '@google-cloud/firestore'; import * as functions from '@google-cloud/functions-framework'; const firestore = new Firestore({preferRest: true}); // Pre-warm firestore connection pool, and preload our global config // document in cache. In order to ensure no other request comes in, // block the module loading with a synchronous global request: const config = await firestore.collection('collection').doc('config').get(); functions.http('fetch', (req, res) => { // Do something with config and firestore client, which are now preloaded // and will execute at lower latency. });
ग्लोबल वैरिएबल को शुरू करने के उदाहरण
Node.js
const functions = require('firebase-functions'); let myCostlyVariable; exports.function = functions.https.onRequest((req, res) => { doUsualWork(); if(unlikelyCondition()){ myCostlyVariable = myCostlyVariable || buildCostlyVariable(); } res.status(200).send('OK'); });
Python
from firebase_functions import https_fn # Always initialized (at cold-start) non_lazy_global = file_wide_computation() # Declared at cold-start, but only initialized if/when the function executes lazy_global = None @https_fn.on_request() def lazy_globals(request): global lazy_global, non_lazy_global # This value is initialized only if (and when) the function is called if not lazy_global: lazy_global = function_specific_computation() return https_fn.Response(f"Lazy: {lazy_global}, non-lazy: {non_lazy_global}.")
यह एचटीटीपी फ़ंक्शन, लेज़ी-इनिशियलाइज़ किए गए ग्लोबल का इस्तेमाल करता है. यह एक अनुरोध ऑब्जेक्ट (flask.Request) लेता है और जवाब के तौर पर टेक्स्ट दिखाता है. इसके अलावा, यह वैल्यू का कोई ऐसा सेट भी दिखाता है जिसे make_response का इस्तेमाल करके Response ऑब्जेक्ट में बदला जा सकता है.
अगर आपने एक ही फ़ाइल में कई फ़ंक्शन तय किए हैं और अलग-अलग फ़ंक्शन अलग-अलग वैरिएबल का इस्तेमाल करते हैं, तो ऐसा करना खास तौर पर ज़रूरी है. लेज़ी इनिशियलाइज़ेशन का इस्तेमाल न करने पर, हो सकता है कि आप उन वैरिएबल पर संसाधन बर्बाद कर दें जिन्हें इनिशियलाइज़ किया गया है, लेकिन कभी इस्तेमाल नहीं किया गया.
अन्य संसाधन
"Google Cloud Performance Atlas" वीडियो में, परफ़ॉर्मेंस को ऑप्टिमाइज़ करने के बारे में ज़्यादा जानें. Cloud Functions कोल्ड बूट टाइम.