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

এই ডকুমেন্টে 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)।

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

টুল

এই বিভাগে 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 Performance Atlas" ভিডিওতে পারফর্ম্যান্স অপ্টিমাইজ করা সম্পর্কে আরও জানুন Cloud Functions কোল্ড বুট টাইম।