กลเม็ดเคล็ดลับ

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

ความถูกต้อง

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

เขียนฟังก์ชันเอกลักษณ์

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

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

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

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

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

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

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

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

อย่าพยายามเขียนนอกไดเรกทอรีชั่วคราว และโปรดใช้เมธอดที่ไม่เกี่ยวข้องกับแพลตฟอร์ม/ระบบปฏิบัติการเพื่อสร้างเส้นทางของไฟล์

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

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

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

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

เครื่องมือ

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

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

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

นักพัฒนาซอฟต์แวร์ Firebase ใช้โปรแกรมจำลอง Cloud Functions ของ Firebase CLI ได้

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

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

ประสิทธิภาพ

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

ใช้ทรัพยากร Dependency อย่างชาญฉลาด

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

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

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

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

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

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 ในขอบเขตรวม ดูการเพิ่มประสิทธิภาพเครือข่ายสำหรับตัวอย่าง

การเริ่มต้นแบบ Lazy Loading ของตัวแปรร่วม

หากคุณเริ่มต้นตัวแปรในขอบเขตรวม โค้ดเริ่มต้นจะดำเนินการผ่านการเรียกใช้ Cold Start เสมอ ซึ่งจะเพิ่มเวลาในการตอบสนองของฟังก์ชัน ในบางกรณี ปัญหานี้ทำให้การเรียกใช้บริการหมดเวลาเป็นช่วงๆ หากไม่มีการจัดการอย่างเหมาะสมในบล็อก try/catch หากออบเจ็กต์บางรายการไม่ได้ใช้ในเส้นทางโค้ดทั้งหมด ลองเริ่มต้นใช้งานแบบ Lazy Loading ดังนี้

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

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

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

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

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

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

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