เอกสารนี้อธิบายแนวทางปฏิบัติแนะนำสำหรับการออกแบบ การติดตั้งใช้งาน การทดสอบ และการทำให้ 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