เอกสารนี้อธิบายแนวทางปฏิบัติแนะนำสำหรับการออกแบบ การติดตั้งใช้งาน การทดสอบ และการทำให้ 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
เพื่อให้มั่นใจว่ามีการติดตั้งทรัพยากร Dependency เดียวกันอย่างสอดคล้องกันในสภาพแวดล้อมต่างๆ เราขอแนะนำให้คุณรวมไลบรารี Functions Framework ไว้ในเครื่องมือจัดการแพ็กเกจและปักหมุดทรัพยากร Dependency ไว้กับ Functions Framework เวอร์ชันที่เฉพาะเจาะจง
โดยระบุเวอร์ชันที่ต้องการในไฟล์ล็อกที่เกี่ยวข้อง (เช่น package-lock.json สำหรับ Node.js หรือ requirements.txt สำหรับ Python)
หากไม่ได้ระบุ Functions Framework เป็นทรัพยากร Dependency อย่างชัดเจน ระบบจะเพิ่มเฟรมเวิร์กดังกล่าวโดยอัตโนมัติในระหว่างกระบวนการบิลด์โดยใช้เวอร์ชันล่าสุดที่พร้อมใช้งาน
เครื่องมือ
ส่วนนี้จะแสดงหลักเกณฑ์เกี่ยวกับวิธีใช้เครื่องมือเพื่อติดตั้งใช้งาน ทดสอบ และโต้ตอบกับ Cloud Functions
การพัฒนาในพื้นที่
การติดตั้งใช้งานฟังก์ชันใช้เวลาสักครู่ ดังนั้นการทดสอบโค้ดของฟังก์ชันในเครื่องจึงมักจะเร็วกว่า
นักพัฒนาแอป Firebase สามารถใช้โปรแกรมจำลอง Cloud Functions ของ Firebase CLIหลีกเลี่ยงการหมดเวลาการติดตั้งใช้งานระหว่างการเริ่มต้น
หากการติดตั้งใช้งานฟังก์ชันไม่สำเร็จเนื่องจากข้อผิดพลาดเกี่ยวกับระยะหมดเวลา อาจหมายความว่าโค้ดขอบเขตส่วนกลางของฟังก์ชันใช้เวลานานเกินไปในการดำเนินการระหว่างกระบวนการติดตั้งใช้งาน
Firebase CLI มีระยะหมดเวลาเริ่มต้นสำหรับการค้นหาฟังก์ชันระหว่างการติดตั้งใช้งาน หากตรรกะการเริ่มต้นในซอร์สโค้ดของฟังก์ชัน (การโหลดโมดูล การเรียกใช้เครือข่าย และอื่นๆ) เกินระยะหมดเวลานี้ การทำให้ใช้งานได้อาจไม่สำเร็จ
หากต้องการหลีกเลี่ยงการหมดเวลา ให้ใช้กลยุทธ์ข้อใดข้อหนึ่งต่อไปนี้
(แนะนำ) ใช้ onInit() เพื่อเลื่อนการเริ่มต้น
ใช้ฮุก onInit() เพื่อหลีกเลี่ยงการเรียกใช้โค้ดการเริ่มต้นระหว่างการติดตั้งใช้งาน โค้ดภายในฮุก onInit() จะทำงานเมื่อมีการติดตั้งใช้งานฟังก์ชันในฟังก์ชัน Cloud Run เท่านั้น ไม่ใช่ในระหว่างกระบวนการติดตั้งใช้งานเอง
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 เวลาในการเปิดเครื่องใหม่