পরামর্শ ও কৌশল

এই ডকুমেন্টে Cloud Functions ডিজাইন, প্রয়োগ, পরীক্ষা ও ডিপ্লয় করার পেশাদার পদ্ধতি বর্ণনা করা হয়েছে।

সঠিকতা

এই বিভাগে, ডিজাইন ও প্রয়োগ করার জন্য সাধারণ পেশাদার পদ্ধতি বর্ণনা করা হয়েছে Cloud Functions।

আইডেমপোটেন্ট ফাংশন লেখা

আপনার ফাংশন একাধিকবার কল করা হলেও একই ফলাফল দিতে হবে। এর ফলে, আপনার কোডের মধ্যে কোনও জায়গায় আগের ইনভোকেশন ব্যর্থ হলে, আপনি আবার ইনভোকেশন করার চেষ্টা করতে পারবেন। আরও তথ্যের জন্য, ইভেন্ট-ড্রিভেন ফাংশন আবার চেষ্টা করা দেখুন।

ব্যাকগ্রাউন্ড অ্যাক্টিভিটি শুরু করবেন না

আপনার ফাংশন শেষ হয়ে যাওয়ার পরে যা কিছু ঘটে, তা হল ব্যাকগ্রাউন্ড অ্যাক্টিভিটি। ফাংশন রিটার্ন করলে বা অন্য কোনওভাবে সম্পূর্ণ হওয়ার ইঙ্গিত দিলে, ফাংশন ইনভোকেশন শেষ হয়ে যায়, যেমন Node.js ইভেন্ট-ড্রাইভেন ফাংশনে callback আর্গুমেন্ট কল করার মাধ্যমে। বন্ধ করার পরে রান করা কোনও কোড CPU অ্যাক্সেস করতে পারবে না এবং কোনও প্রগ্রেস করতে পারবে না।

এছাড়াও, একই এনভায়রনমেন্টে পরবর্তী ইনভোকেশন এক্সিকিউট করা হলে, আপনার ব্যাকগ্রাউন্ড অ্যাক্টিভিটি আবার শুরু হয়ে যায়, যা নতুন ইনভোকেশনে বাধা দেয়। এর ফলে অপ্রত্যাশিত আচরণ ও এমন সমস্যা হতে পারে যা শনাক্ত করা কঠিন। ফাংশন শেষ হওয়ার পরে নেটওয়ার্ক অ্যাক্সেস করলে সাধারণত কানেকশন রিসেট হয়ে যায় (ECONNRESET সমস্যার কোড)।

ইনডিভিজুয়াল ইনভোকেশন থেকে পাওয়া লগে ব্যাকগ্রাউন্ড অ্যাক্টিভিটি প্রায়ই শনাক্ত করা যায়, ইনভোকেশন শেষ হয়ে গেছে, এই কথাটি লেখা লাইনের পরে যা কিছু লগ করা হয়েছে তা খুঁজে পাওয়ার মাধ্যমে এটি করা হয়। ব্যাকগ্রাউন্ড অ্যাক্টিভিটি কখনও কখনও কোডের মধ্যে আরও গভীরে লুকানো থাকতে পারে, বিশেষ করে কলব্যাক বা টাইমারের মতো অ্যাসিঙ্ক্রোনাস অপারেশন উপস্থিত থাকলে। ফাংশন বন্ধ করার আগে সব অ্যাসিঙ্ক্রোনাস অপারেশন সম্পূর্ণ হয়েছে কিনা তা নিশ্চিত করতে আপনার কোড পর্যালোচনা করুন।

সবসময় অস্থায়ী ফাইল মুছে দেয়

অস্থায়ী ডিরেক্টরিতে লোকাল ডিস্ক স্টোরেজ হল ইন-মেমরি ফাইলসিস্টেম। আপনার লেখা ফাইল ফাংশনের জন্য উপলভ্য মেমরি ব্যবহার করে এবং কখনও কখনও ইনভোকেশনগুলির মধ্যে থেকে যায়। এইসব ফাইল স্পষ্টভাবে মুছে ফেলতে না পারলে, শেষ পর্যন্ত মেমরি ফুরিয়ে যাওয়া সংক্রান্ত সমস্যা এবং পরবর্তী কোল্ড স্টার্ট হতে পারে।

Google Cloud কনসোলে ফাংশনের তালিকা থেকে কোনও ফাংশন বেছে নিয়ে মেমরি ব্যবহার প্লট বেছে নিলে, সেই ফাংশন কতটা মেমরি ব্যবহার করেছে তা দেখতে পাবেন।

দীর্ঘমেয়াদী স্টোরেজে অ্যাক্সেস পেতে চাইলে, Cloud Run ভলিউম মাউন্ট সহ Cloud Storage অথবা NFS ভলিউম ব্যবহার করার কথা বিবেচনা করুন।

পাইপলাইনিং ব্যবহার করে বড় ফাইল প্রসেস করার সময় মেমরি সংক্রান্ত প্রয়োজনীয়তা কমাতে পারবেন। যেমন, আপনি একটি রিড স্ট্রিম তৈরি করে Cloud Storage-এ কোনও ফাইল প্রসেস করতে পারেন, সেটি স্ট্রিম-ভিত্তিক প্রসেসের মাধ্যমে পাস করতে পারেন এবং আউটপুট স্ট্রিম সরাসরি Cloud Storage-এ লিখতে পারেন।

ফাংশন ফ্রেমওয়ার্ক

বিভিন্ন এনভায়রনমেন্টে একই ডিপেন্ডেন্সি যাতে ধারাবাহিকভাবে ইনস্টল করা যায় তা নিশ্চিত করতে, আমরা সাজেস্ট করি যে আপনার প্যাকেজ ম্যানেজারে Functions Framework লাইব্রেরি যোগ করুন এবং Functions Framework-এর নির্দিষ্ট ভার্সনে ডিপেন্ডেন্সি পিন করুন।

এটি করতে, প্রাসঙ্গিক লক ফাইলে আপনার পছন্দের ভার্সন অন্তর্ভুক্ত করুন (যেমন, Node.js-এর জন্য package-lock.json বা Python-এর জন্য requirements.txt)।

Functions Framework স্পষ্টভাবে নির্ভরতা হিসেবে তালিকাভুক্ত না থাকলে, উপলভ্য লেটেস্ট ভার্সন ব্যবহার করে বিল্ড প্রসেস চলাকালীন এটি অটোমেটিক যোগ করা হবে।

টুল

এই বিভাগে, Cloud Functions-এর সাথে ইন্টার‍্যাক্ট, টেস্ট ও ইমপ্লিমেন্ট করার জন্য টুল কীভাবে ব্যবহার করতে হয় সেই বিষয়ে নির্দেশিকা দেওয়া হয়েছে।

স্থানীয় উন্নয়ন

ফাংশন ডিপ্লয় করতে কিছুটা সময় লাগে, তাই আপনার ফাংশনের কোড লোকাল মেশিনে পরীক্ষা করে দেখলে তা অনেক দ্রুত হয়।

Firebase ডেভেলপাররা Firebase CLI Cloud Functions এমুলেটর ব্যবহার করতে পারেন।

ইনিশিয়ালাইজেশনের সময় ডেপ্লয়মেন্ট টাইম-আউট এড়ানো

টাইম-আউট সংক্রান্ত সমস্যার কারণে আপনার ফাংশন ডেপ্লয়মেন্ট ব্যর্থ হলে, এর অর্থ হল যে ডেপ্লয়মেন্ট প্রসেস চলাকালীন আপনার ফাংশনের গ্লোবাল স্কোপ কোড এক্সিকিউট হতে অনেক বেশি সময় লাগছে।

Firebase CLI-তে, ডিপ্লয়মেন্টের সময় আপনার ফাংশন খুঁজে পাওয়ার জন্য ডিফল্ট টাইম-আউট থাকে। আপনার ফাংশনের সোর্স কোডে (মডিউল লোড করা, নেটওয়ার্ক কল করা ইত্যাদি) ইনিশিয়ালাইজেশন লজিক এই টাইম-আউট অতিক্রম করলে, ডিপ্লয়মেন্ট ব্যর্থ হতে পারে।

টাইম-আউট এড়াতে, নিম্নলিখিত কৌশলগুলির মধ্যে একটি ব্যবহার করুন:

ডেপ্লয়মেন্টের সময় ইনিশিয়ালাইজেশন কোড রান করা এড়াতে 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 এনভায়রনমেন্ট ভেরিয়েবল ব্যবহার করে CLI-এর ডেপ্লয়মেন্ট টাইমআউট বাড়াতে পারবেন:

$ export FUNCTIONS_DISCOVERY_TIMEOUT=30
$ firebase deploy --only functions

ইমেল পাঠানোর জন্য Sendgrid ব্যবহার করা

Cloud Functions পোর্ট ২৫-এ আউটবাউন্ড কানেকশন অনুমোদন করে না, তাই আপনি SMTP সার্ভারের সাথে সুরক্ষিত নয় এমন কানেকশন তৈরি করতে পারবেন না। ইমেল পাঠানোর জন্য সাজেস্ট করা পদ্ধতি হল 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}")
  

এই HTTP ফাংশন একটি অনুরোধ অবজেক্ট (flask.Request) নেয় এবং প্রতিক্রিয়া টেক্সট বা make_response ব্যবহার করে Response অবজেক্টে পরিণত করা যায় এমন কোনও ভ্যালুর সেট রিটার্ন করে।

গ্লোবাল স্কোপে নেটওয়ার্ক কানেকশন, লাইব্রেরি রেফারেন্স এবং API ক্লায়েন্ট অবজেক্ট ক্যাশে করা বিশেষভাবে গুরুত্বপূর্ণ। উদাহরণের জন্য নেটওয়ার্কিং অপ্টিমাইজ করা দেখুন।

ইনস্ট্যান্সের ন্যূনতম সংখ্যা সেট করে কোল্ড স্টার্ট কমানো

ডিফল্ট হিসেবে, Cloud Functions, ইনকামিং অনুরোধের সংখ্যার উপর ভিত্তি করে ইনস্ট্যান্সের সংখ্যা স্কেল করে। Cloud Functions-কে অনুরোধ পরিবেশন করার জন্য সর্বনিম্ন যতগুলি ইনস্ট্যান্স রেডি রাখতে হবে তা সেট করে আপনি এই ডিফল্ট আচরণ পরিবর্তন করতে পারেন। ইনস্ট্যান্সের ন্যূনতম সংখ্যা সেট করলে আপনার অ্যাপ্লিকেশনের কোল্ড স্টার্ট কমে যায়। আপনার অ্যাপ্লিকেশন লেটেন্সি-সংবেদনশীল হলে, আমরা ন্যূনতম সংখ্যক ইনস্ট্যান্স সেট করার এবং লোড করার সময় ইনিশিয়ালাইজেশন সম্পূর্ণ করার সাজেশন দিই।

এইসব রানটাইম বিকল্প সম্পর্কে আরও তথ্যের জন্য কন্ট্রোল স্কেলিং আচরণ দেখুন।

কোল্ড স্টার্ট ও ইনিশিয়ালাইজেশন সংক্রান্ত নোট

লোড করার সময় গ্লোবাল ইনিশিয়ালাইজেশন হয়। এটি না থাকলে, প্রথম অনুরোধ সম্পূর্ণ করতে ইনিশিয়ালাইজেশন ও মডিউল লোড করতে হবে, এর ফলে বেশি লেটেন্সি হবে।

তবে, গ্লোবাল ইনিশিয়ালাইজেশনের প্রভাব কোল্ড স্টার্টের উপরেও পড়ে। এই প্রভাব কমাতে, প্রথম অনুরোধের জন্য যা প্রয়োজন তা শুধুমাত্র ইনিশিয়ালাইজ করুন, যাতে প্রথম অনুরোধের লেটেন্সি যতটা সম্ভব কম রাখা যায়।

লেটেেন্সি-সংবেদনশীল ফাংশনের জন্য উপরে বর্ণিত ন্যূনতম ইনস্ট্যান্স কনফিগার করলে এটি বিশেষ গুরুত্বপূর্ণ। সেই পরিস্থিতিতে, লোড করার সময় ইনিশিয়ালাইজেশন সম্পূর্ণ করা এবং প্রয়োজনীয় ডেটা ক্যাশে করা নিশ্চিত করে যে প্রথম অনুরোধে এটি করতে হবে না এবং কম লেটেন্সির সাথে পরিবেশন করা হবে।

আপনি গ্লোবাল স্কোপে ভেরিয়েবল ইনিশিয়ালাইজ করলে, ভাষার উপর নির্ভর করে, দীর্ঘ ইনিশিয়ালাইজেশন সময়ের ফলে দুটি আচরণ হতে পারে: - ভাষা ও অ্যাসিঙ্ক লাইব্রেরির কিছু কম্বিনেশনের জন্য, ফাংশন ফ্রেমওয়ার্ক অ্যাসিঙ্ক্রোনাসভাবে রান করতে এবং অবিলম্বে রিটার্ন করতে পারে, এর ফলে কোড ব্যাকগ্রাউন্ডে চলতে থাকে, এর ফলে CPU অ্যাক্সেস করতে না পারার মতো সমস্যা হতে পারে। এটি এড়াতে, আপনাকে মডিউল ইনিশিয়ালাইজেশনের সময় ব্লক করতে হবে, নিচে এর বিবরণ দেওয়া হল। এছাড়াও, এটি নিশ্চিত করে যে ইনিশিয়ালাইজেশন সম্পূর্ণ না হওয়া পর্যন্ত অনুরোধ পরিবেশন করা হবে না। - অন্যদিকে, ইনিশিয়ালাইজেশন সিঙ্ক্রোনাস হলে, দীর্ঘ ইনিশিয়ালাইজেশন সময়ের কারণে কোল্ড স্টার্টের সময় বেশি লাগবে, যা লোড বেড়ে যাওয়ার সময় বিশেষ করে কম কনকারেন্সি ফাংশনের ক্ষেত্রে সমস্যা হতে পারে।

অ্যাসিঙ্ক node.js লাইব্রেরি প্রিওয়ার্ম করার উদাহরণ

Firestore সহ Node.js হল অ্যাসিঙ্ক Node.js লাইব্রেরির একটি উদাহরণ। min_instances-এর সুবিধা নিতে, নিচের কোডটি লোড করার সময় লোডিং ও ইনিশিয়ালাইজেশন সম্পূর্ণ করে, মডিউল লোডিংয়ে বাধা দেয়।

TLA ব্যবহার করা হয়, যার অর্থ হল 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}.")
  

এই HTTP ফাংশন লেজি-ইনিশিয়ালাইজড গ্লোবাল ব্যবহার করে। এটি একটি রিকোয়েস্ট অবজেক্ট (flask.Request) নেয় এবং রেসপন্স টেক্সট বা ভ্যালুর কোনও সেট রিটার্ন করে যা make_response ব্যবহার করে Response অবজেক্টে পরিবর্তন করা যায়।

আপনি যদি একটি ফাইলে একাধিক ফাংশন সংজ্ঞায়িত করেন এবং বিভিন্ন ফাংশন বিভিন্ন ভেরিয়েবল ব্যবহার করে, তাহলে এটি বিশেষভাবে গুরুত্বপূর্ণ। আপনি লেজি ইনিশিয়ালাইজেশন ব্যবহার না করলে, ইনিশিয়ালাইজ করা হয়েছে কিন্তু কখনও ব্যবহার করা হয়নি এমন ভেরিয়েবলের জন্য রিসোর্স নষ্ট হতে পারে।

অতিরিক্ত সম্পদ

"Google Cloud পারফর্ম্যান্স অ্যাটলাস" ভিডিও থেকে পারফর্ম্যান্স অপ্টিমাইজ করা সম্পর্কে আরও জানুন Cloud Functions কোল্ড বুট টাইম।