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

เอกสารนี้จะอธิบายแนวทางปฏิบัติแนะนำสำหรับการออกแบบ การติดตั้งใช้งาน การทดสอบ และการทำให้ 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 มักจะนำสภาพแวดล้อมการดำเนินการของคำขอก่อนหน้านี้มาใช้ซ้ำ หากคุณประกาศตัวแปรในขอบเขตรวม คุณจะใช้ค่าของตัวแปรซ้ำในการเรียกใช้ครั้งต่อๆ ไปได้โดยไม่ต้องคำนวณใหม่

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

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

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

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

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

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

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

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