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

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

ความถูกต้อง

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

เขียนฟังก์ชัน Idempotent

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

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

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

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

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

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

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

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

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

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

Functions Framework

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

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

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

เครื่องมือ

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

การพัฒนาภายในเครื่อง

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

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

หลีกเลี่ยงการหมดเวลาในการทำให้ใช้งานได้ระหว่างการเริ่มต้น

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

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

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

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

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() ไม่ได้ คุณสามารถเพิ่มการหมดเวลาในการทำให้ใช้งานได้ของ 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) เมื่อเกิด Cold Start ขึ้น ระบบจะประเมินบริบทส่วนกลางของฟังก์ชัน

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

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

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

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

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

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

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

หมายเหตุเกี่ยวกับ Cold Start และการเริ่มต้น

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

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

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

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

ตัวอย่างการเตรียมใช้งานล่วงหน้าของไลบรารี 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.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 นี้ใช้ตัวแปรส่วนกลางที่เริ่มต้นแบบ Lazy ฟังก์ชันนี้ใช้ออบเจ็กต์คำขอ (flask.Request) และแสดงผลข้อความตอบกลับ หรือชุดค่าใดก็ได้ที่ เปลี่ยนเป็นออบเจ็กต์ Response ได้โดยใช้ make_response

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

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

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