เคล็ดลับและคำแนะนำ

เอกสารนี้อธิบายแนวทางปฏิบัติแนะนำสำหรับการออกแบบ การติดตั้งใช้งาน การทดสอบ และการนำ Cloud Functions ไปใช้งาน

ความถูกต้อง

ส่วนนี้จะอธิบายแนวทางปฏิบัติแนะนำทั่วไปสำหรับการออกแบบและการใช้งาน Cloud Functions

เขียนฟังก์ชันที่ idempotent

ฟังก์ชันควรให้ผลลัพธ์เดียวกันแม้ว่าจะเรียกใช้หลายครั้งก็ตาม ซึ่งจะช่วยให้คุณลองเรียกใช้อีกครั้งได้หากการเรียกใช้ก่อนหน้านี้ไม่สําเร็จขณะประมวลผลโค้ด ดูข้อมูลเพิ่มเติมได้ที่ การลองใช้ฟังก์ชันที่ทำงานตามเหตุการณ์อีกครั้ง

ไม่เริ่มกิจกรรมในเบื้องหลัง

กิจกรรมเบื้องหลังคือสิ่งที่เกิดขึ้นหลังจากที่ฟังก์ชันสิ้นสุดลง การเรียกใช้ฟังก์ชันจะเสร็จสมบูรณ์เมื่อฟังก์ชันแสดงผลหรือส่งสัญญาณการทำงานเสร็จสิ้น เช่น การเรียกใช้อาร์กิวเมนต์ callback ในฟังก์ชันที่ทำงานตามเหตุการณ์ของ Node.js โค้ดที่ทำงานหลังจากการสิ้นสุดอย่างราบรื่นจะเข้าถึง CPU ไม่ได้และจะไม่ทํางานใดๆ

นอกจากนี้ เมื่อเรียกใช้ครั้งถัดไปในสภาพแวดล้อมเดียวกัน กิจกรรมเบื้องหลังจะกลับมาทำงานต่อ ซึ่งจะรบกวนการเรียกใช้ใหม่ ซึ่งอาจทําให้เกิดลักษณะการทํางานที่ไม่คาดคิดและข้อผิดพลาดที่วิเคราะห์ได้ยาก การเข้าถึงเครือข่ายหลังจากฟังก์ชันสิ้นสุดลงมักจะทําให้การเชื่อมต่อรีเซ็ต (ECONNRESET รหัสข้อผิดพลาด)

กิจกรรมเบื้องหลังมักตรวจพบได้ในบันทึกจากคําเรียกแต่ละรายการ โดยค้นหาสิ่งที่บันทึกไว้หลังบรรทัดที่ระบุว่าคําเรียกเสร็จสิ้นแล้ว บางครั้งกิจกรรมเบื้องหลังอาจฝังอยู่ในโค้ดลึกลงไป โดยเฉพาะเมื่อมีการดำเนินการแบบไม่พร้อมกัน เช่น การเรียกกลับหรือตัวจับเวลา ตรวจสอบโค้ดเพื่อให้แน่ใจว่าการดำเนินการแบบไม่สอดคล้องกันทั้งหมดเสร็จสิ้นแล้วก่อนที่จะสิ้นสุดฟังก์ชัน

ลบไฟล์ชั่วคราวเสมอ

พื้นที่เก็บข้อมูลในดิสก์ในเครื่องของไดเรกทอรีชั่วคราวคือระบบไฟล์ในหน่วยความจำ ไฟล์ที่คุณเขียนจะใช้หน่วยความจําที่มีให้ฟังก์ชัน และบางครั้งจะยังคงอยู่ระหว่างการเรียกใช้ การลบไฟล์เหล่านี้อย่างชัดเจนไม่สำเร็จอาจส่งผลให้เกิดข้อผิดพลาดหน่วยความจำไม่เพียงพอและระบบต้องเริ่มต้นใหม่ในภายหลัง

คุณสามารถดูหน่วยความจำที่ฟังก์ชันแต่ละรายการใช้ได้โดยเลือกฟังก์ชันนั้นในรายการฟังก์ชันในคอนโซล Google Cloud แล้วเลือกผังการใช้หน่วยความจำ

หากต้องการเข้าถึงพื้นที่เก็บข้อมูลระยะยาว ให้ลองใช้Cloud Runการต่อเชื่อมวอลุ่มด้วยCloud Storageหรือวอลุ่ม NFS

คุณลดความต้องการหน่วยความจำได้เมื่อประมวลผลไฟล์ขนาดใหญ่โดยใช้ไปป์ไลน์ เช่น คุณสามารถประมวลผลไฟล์ใน Cloud Storage ได้โดยการสร้างสตรีมการอ่าน ส่งผ่านกระบวนการที่อิงตามสตรีม และเขียนสตรีมเอาต์พุตไปยัง Cloud Storage โดยตรง

เฟรมเวิร์กฟังก์ชัน

เราขอแนะนำให้คุณรวมไลบรารี Functions Framework ไว้ในเครื่องมือจัดการแพ็กเกจและปักหมุดการพึ่งพาไว้กับ Functions Framework เวอร์ชันที่เจาะจง เพื่อให้มั่นใจว่ามีการนําเข้าการพึ่งพาเดียวกันอย่างสอดคล้องกันในสภาพแวดล้อมต่างๆ

โดยให้ระบุเวอร์ชันที่ต้องการในไฟล์ล็อกที่เกี่ยวข้อง (เช่น package-lock.json สำหรับ Node.js หรือ requirements.txt สำหรับ Python)

หากไม่ได้ระบุเฟรมเวิร์กฟังก์ชันเป็นข้อกำหนดอย่างชัดแจ้ง ระบบจะเพิ่มเฟรมเวิร์กดังกล่าวโดยอัตโนมัติในระหว่างกระบวนการสร้างโดยใช้เวอร์ชันล่าสุดที่มี

เครื่องมือ

ส่วนนี้จะให้หลักเกณฑ์เกี่ยวกับวิธีใช้เครื่องมือเพื่อติดตั้งใช้งาน ทดสอบ และโต้ตอบกับ Cloud Functions

การพัฒนาในพื้นที่

การปรับใช้งานฟังก์ชันจะใช้เวลาสักครู่ การทดสอบโค้ดของฟังก์ชันในเครื่องจึงมักจะเร็วกว่า

นักพัฒนาแอป Firebase สามารถใช้โปรแกรมจำลอง Cloud FunctionsFirebase CLI

หลีกเลี่ยงการหมดเวลาของการติดตั้งใช้งานระหว่างการเริ่มต้น

หากการทําให้ฟังก์ชันใช้งานได้ไม่สําเร็จเนื่องจากข้อผิดพลาดเกี่ยวกับเวลาหมด แสดงว่าโค้ดขอบเขตส่วนกลางของฟังก์ชันใช้เวลาในการดำเนินการนานเกินไปในระหว่างกระบวนการทําให้ใช้งานได้

Firebase CLI มีระยะหมดเวลาเริ่มต้นสำหรับการค้นหาฟังก์ชันระหว่างการทำให้ใช้งานได้ หากตรรกะการเริ่มต้นในซอร์สโค้ดของฟังก์ชัน (การโหลดข้อบังคับ การเรียกใช้เครือข่าย และอื่นๆ) ใช้เวลาเกินการหมดเวลานี้ การทำให้ใช้งานได้อาจล้มเหลว

หากต้องการหลีกเลี่ยงการหมดเวลา ให้ใช้กลยุทธ์ข้อใดข้อหนึ่งต่อไปนี้

ใช้ฮุก onInit() เพื่อหลีกเลี่ยงการเรียกใช้โค้ดเริ่มต้นระหว่างการทําให้ใช้งานได้ โค้ดภายในฮุก onInit() จะทำงานก็ต่อเมื่อมีการทำให้ฟังก์ชันใช้งานได้ใน Cloud Run เท่านั้น ไม่ใช่ระหว่างกระบวนการทำให้ใช้งานได้

Node.jsPython
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}`);
});
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() ได้ คุณสามารถเพิ่มการหมดเวลาของการนำส่ง CLI โดยใช้ตัวแปรสภาพแวดล้อม FUNCTIONS_DISCOVERY_TIMEOUT ดังนี้

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

ใช้ Sendgrid เพื่อส่งอีเมล

Cloud Functions ไม่อนุญาตให้มีการเชื่อมต่อขาออกในพอร์ต 25 คุณจึงทำการเชื่อมต่อที่ไม่ปลอดภัยกับเซิร์ฟเวอร์ SMTP ไม่ได้ วิธีส่งอีเมลที่แนะนําคือใช้บริการของบุคคลที่สาม เช่น SendGrid คุณดูตัวเลือกอื่นๆ ในการส่งอีเมลได้ในบทแนะนำการส่งอีเมลจากอินสแตนซ์สำหรับ Google Compute Engine

ประสิทธิภาพ

ส่วนนี้จะอธิบายแนวทางปฏิบัติแนะนำในการเพิ่มประสิทธิภาพ

หลีกเลี่ยงการทำงานพร้อมกันต่ำ

เนื่องจาก Cold Start มีค่าใช้จ่ายสูง ความสามารถในการนำอินสแตนซ์ที่เพิ่งเริ่มต้นใช้งานไปใช้งานซ้ำในช่วงที่มีปริมาณงานเพิ่มขึ้นจึงเป็นการเพิ่มประสิทธิภาพที่ยอดเยี่ยมในการจัดการกับภาระงาน การจำกัดการทำงานพร้อมกันจะจำกัดวิธีใช้ประโยชน์จากอินสแตนซ์ที่มีอยู่ จึงทำให้เกิด Cold Start มากขึ้น

การเพิ่มการเกิดขึ้นพร้อมกันช่วยเลื่อนคําขอหลายรายการต่ออินสแตนซ์ออกไปได้ ซึ่งทำให้จัดการกับปริมาณงานที่เพิ่มขึ้นได้ง่ายขึ้น

ใช้ Dependency อย่างชาญฉลาด

เนื่องจากฟังก์ชันไม่มีสถานะ สภาพแวดล้อมการดําเนินการจึงมักจะเริ่มต้นจากต้น (ในช่วงที่เรียกว่าการเริ่มต้นแบบเย็น) เมื่อเกิด Cold Start ระบบจะประเมินบริบทส่วนกลางของฟังก์ชัน

หากฟังก์ชันนําเข้าโมดูล เวลาในการโหลดโมดูลเหล่านั้นอาจเพิ่มเวลาในการตอบสนองของการเรียกใช้ระหว่างการเริ่มต้นระบบแบบเย็น คุณสามารถลดเวลาในการตอบสนองนี้ รวมถึงเวลาที่จำเป็นในการทำให้ฟังก์ชันใช้งานได้ โดยการโหลดไลบรารีอย่างถูกต้องและไม่โหลดไลบรารีที่ฟังก์ชันไม่ได้ใช้

ใช้ตัวแปรส่วนกลางเพื่อใช้ออบเจ็กต์ซ้ำในการเรียกใช้ในอนาคต

ไม่มีการรับประกันว่าระบบจะเก็บสถานะของฟังก์ชันไว้สำหรับการเรียกใช้ในอนาคต อย่างไรก็ตาม Cloud Functions มักนําสภาพแวดล้อมการดําเนินการของการเรียกใช้ก่อนหน้ามาใช้ซ้ำ หากคุณประกาศตัวแปรในขอบเขตส่วนกลาง ระบบจะใช้ค่าของตัวแปรนั้นซ้ำในการเรียกใช้ครั้งต่อๆ ไปได้โดยที่ไม่ต้องคํานวณใหม่

วิธีนี้ช่วยให้คุณแคชออบเจ็กต์ที่อาจสร้างใหม่ได้ยากในแต่ละการเรียกใช้ฟังก์ชัน การย้ายออบเจ็กต์ดังกล่าวจากเนื้อหาฟังก์ชันไปยังขอบเขตส่วนกลางอาจส่งผลให้ประสิทธิภาพดีขึ้นอย่างมาก ตัวอย่างต่อไปนี้จะสร้างออบเจ็กต์ขนาดใหญ่เพียงครั้งเดียวต่ออินสแตนซ์ของฟังก์ชัน และแชร์ออบเจ็กต์นั้นกับการเรียกใช้ฟังก์ชันทั้งหมดที่เข้าถึงอินสแตนซ์ที่ระบุ

Node.jsPython
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}`);
});
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) และแสดงผลข้อความตอบกลับ หรือชุดค่าผสมใดๆ ที่เปลี่ยนเป็นออบเจ็กต์ Response ได้โดยใช้ make_response

การแคชการเชื่อมต่อเครือข่าย การอ้างอิงไลบรารี และออบเจ็กต์ไคลเอ็นต์ API ในขอบเขตส่วนกลางมีความสําคัญอย่างยิ่ง ดูตัวอย่างได้ที่การเพิ่มประสิทธิภาพเครือข่าย

ลดจำนวน Cold Start โดยการกําหนดจํานวนอินสแตนซ์ขั้นต่ำ

โดยค่าเริ่มต้น Cloud Functions จะปรับขนาดจำนวนอินสแตนซ์ตามจำนวนคำขอขาเข้า คุณเปลี่ยนลักษณะการทำงานเริ่มต้นนี้ได้โดยการตั้งค่าจำนวนอินสแตนซ์ขั้นต่ำที่ Cloud Functions ต้องพร้อมให้บริการคำขอ การตั้งค่าจำนวนอินสแตนซ์ขั้นต่ำจะลดจำนวนครั้งที่แอปพลิเคชันต้องทำการ Cold Start เราขอแนะนำให้ตั้งค่าจำนวนอินสแตนซ์ขั้นต่ำ และทำการเริ่มต้นให้เสร็จสมบูรณ์ในเวลาที่โหลด หากแอปพลิเคชันของคุณมีความไวต่อเวลาในการตอบสนอง

ดูข้อมูลเพิ่มเติมเกี่ยวกับตัวเลือกรันไทม์เหล่านี้ได้ที่หัวข้อควบคุมลักษณะการการปรับขนาด

หมายเหตุเกี่ยวกับ Cold Start และการจัดเตรียม

การเริ่มต้นแบบรวมจะเกิดขึ้นเมื่อโหลด หากไม่มีแคชนี้ คําขอแรกจะต้องทําการเริ่มต้นและโหลดโมดูลให้เสร็จสมบูรณ์ จึงจะทําให้เกิดความล่าช้ามากขึ้น

อย่างไรก็ตาม การจัดเตรียมเริ่มต้นแบบรวมก็มีผลต่อ Cold Start ด้วย หากต้องการลดผลกระทบนี้ ให้เริ่มต้นเฉพาะสิ่งที่จําเป็นสําหรับคําขอแรกเพื่อรักษาเวลาในการตอบสนองของคําขอแรกให้ต่ำที่สุด

ซึ่งสำคัญมากในกรณีที่คุณกําหนดค่าอินสแตนซ์ขั้นต่ำตามที่อธิบายไว้ข้างต้นสําหรับฟังก์ชันที่ไวต่อเวลาในการตอบสนอง ในกรณีนี้ การทำข้อมูลเริ่มต้นให้เสร็จสมบูรณ์ในเวลาที่โหลดและการแคชข้อมูลที่เป็นประโยชน์จะช่วยให้คำขอแรกไม่ต้องทำขั้นตอนดังกล่าวและแสดงผลโดยมีเวลาในการตอบสนองต่ำ

หากคุณเริ่มต้นตัวแปรในขอบเขตส่วนกลาง เวลาที่ใช้ในการเริ่มต้นอาจส่งผลให้เกิดลักษณะการทำงาน 2 แบบ ทั้งนี้ขึ้นอยู่กับภาษาที่ใช้ - สำหรับชุดค่าผสมบางอย่างของภาษาและไลบรารีแบบแอสซิงค์ เฟรมเวิร์กฟังก์ชันอาจทำงานแบบแอสซิงค์และแสดงผลทันที ซึ่งจะทำให้โค้ดทำงานต่อไปในเบื้องหลัง ซึ่งอาจทำให้เกิดปัญหาต่างๆ เช่น ไม่สามารถเข้าถึง CPU คุณควรบล็อกการเริ่มต้นโมดูลตามที่อธิบายไว้ด้านล่างเพื่อหลีกเลี่ยงปัญหานี้ ซึ่งจะช่วยให้มั่นใจว่าระบบจะไม่แสดงคําขอจนกว่าการเริ่มต้นจะเสร็จสมบูรณ์ - ในทางกลับกัน หากการเริ่มต้นเป็นแบบซิงค์ เวลาเริ่มต้นที่นานจะทำให้การเริ่มต้นแบบ Cold นานขึ้น ซึ่งอาจเป็นปัญหาได้ โดยเฉพาะกับฟังก์ชันการทำงานพร้อมกันต่ำในช่วงที่มีการโหลดสูง

ตัวอย่างการอุ่นเครื่องไลบรารี Node.js แบบไม่พร้อมกัน

Node.js ที่มี Firestore เป็นตัวอย่างของไลบรารี Node.js แบบอะซิงโครนัส หากต้องการใช้ประโยชน์จาก min_instances โค้ดต่อไปนี้จะโหลดและเริ่มต้นให้เสร็จสมบูรณ์ในเวลาที่โหลด โดยบล็อกการโหลดโมดูล

ใช้ TLA ซึ่งหมายความว่าต้องใช้ ES6 โดยใช้ส่วนขยาย .mjs สำหรับโค้ด node.js หรือเพิ่ม type: module ลงในไฟล์ package.json

{
  "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.jsPython
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');
});
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) และแสดงผลข้อความตอบกลับ หรือชุดค่าผสมใดๆ ที่เปลี่ยนให้เป็นออบเจ็กต์ Response ได้โดยใช้ make_response

ซึ่งสำคัญอย่างยิ่งหากคุณกำหนดฟังก์ชันหลายรายการในไฟล์เดียว และฟังก์ชันต่างๆ ใช้ตัวแปรที่แตกต่างกัน คุณอาจสิ้นเปลืองทรัพยากรไปกับตัวแปรที่เริ่มต้นแต่ไม่เคยใช้ เว้นแต่จะใช้การเริ่มต้นแบบเลื่อนเวลา

แหล่งข้อมูลเพิ่มเติม

ดูข้อมูลเพิ่มเติมเกี่ยวกับการเพิ่มประสิทธิภาพในวิดีโอ "Google Cloud Performance Atlas" Cloud Functions เวลาในการบูตเย็น