אופטימיזציה של הרשת

הפשטות של Cloud Functions מאפשרת לפתח קוד במהירות ולהריץ אותו בסביבה ללא שרת. בקנה מידה בינוני, העלות של הפעלת פונקציות נמוכה, ואולי אופטימיזציה של הקוד לא נראית כעדיפות גבוהה. עם זאת, ככל שהפריסה מתרחבת, האופטימיזציה של הקוד הופכת לחשובה יותר ויותר.

במסמך הזה מוסבר איך לבצע אופטימיזציה של הרשתות לפונקציות. אלה כמה מהיתרונות של אופטימיזציה של הרשתות:

  • הפחתת זמן המעבד (CPU) שנדרש ליצירת חיבורים יוצאים חדשים בכל קריאה לפונקציה.
  • צמצום הסבירות לכך שתחרגו ממכסות החיבור או ה-DNS.

שמירה על חיבורים מתמידים

בקטע הזה מפורטות דוגמאות לשמירה על חיבורים מתמידים בפונקציה. אם לא תעשו זאת, יכול להיות שתגיעו למכסות החיבור במהירות.

בקטע הזה נסביר על התרחישים הבאים:

  • HTTP/S
  • Google APIs

בקשות HTTP/S

קטע הקוד המותאם שבהמשך מראה איך לשמור על חיבורים עקביים במקום ליצור חיבור חדש בכל קריאה לפונקציה:

Node.js

const http = require('http');
const functions = require('firebase-functions');

// Setting the `keepAlive` option to `true` keeps
// connections open between function invocations
const agent = new http.Agent({keepAlive: true});

exports.function = functions.https.onRequest((request, response) => {
    req = http.request({
        host: '',
        port: 80,
        path: '',
        method: 'GET',
        agent: agent, // Holds the connection open after the first invocation
    }, res => {
        let rawData = '';
        res.setEncoding('utf8');
        res.on('data', chunk => { rawData += chunk; });
        res.on('end', () => {
            response.status(200).send(`Data: ${rawData}`);
        });
    });
    req.on('error', e => {
        response.status(500).send(`Error: ${e.message}`);
    });
    req.end();
});

Python

from firebase_functions import https_fn
import requests

# Create a global HTTP session (which provides connection pooling)
session = requests.Session()

@https_fn.on_request()
def connection_pooling(request):

    # The URL to send the request to
    url = "http://example.com"

    # Process the request
    response = session.get(url)
    response.raise_for_status()
    return https_fn.Response("Success!")
    

פונקציית ה-HTTP הזו משתמשת במאגר חיבורים כדי לשלוח בקשות HTTP. הפונקציה מקבלת אובייקט בקשה (flask.Request) ומחזירה את טקסט התגובה, או כל קבוצת ערכים שאפשר להפוך לאובייקט Response באמצעות make_response.

גישה ל-Google APIs

בדוגמה הבאה נעשה שימוש ב-Cloud Pub/Sub, אבל הגישה הזו תקפה גם לספריות לקוח אחרות, למשל Cloud Natural Language או Cloud Spanner. חשוב לזכור שהשיפורים בביצועים עשויים להיות תלויים בהטמעה הנוכחית של ספריות לקוח מסוימות.

יצירת אובייקט לקוח Pub/Sub יוצרת חיבור אחד ושתי שאילתות DNS לכל קריאה. כדי להימנע מחיבורים ומשאילתות DNS מיותרים, יוצרים את אובייקט הלקוח של Pub/Sub ברמת ה-global, כפי שמוצג בדוגמה הבאה:

node.js

const PubSub = require('@google-cloud/pubsub');
const functions = require('firebase-functions');
const pubsub = PubSub();

exports.function = functions.https.onRequest((req, res) => {
    const topic = pubsub.topic('');

    topic.publish('Test message', err => {
        if (err) {
            res.status(500).send(`Error publishing the message: ${err}`);
        } else {
            res.status(200).send('1 message published');
        }
    });
});

Python

import os

from firebase_functions import https_fn
from google.cloud import pubsub_v1

# from firebase_functions import https_fn
# Create a global Pub/Sub client to avoid unneeded network activity
pubsub = pubsub_v1.PublisherClient()

@https_fn.on_request()
def gcp_api_call(request):

    project = os.getenv("GCP_PROJECT")
    request_json = request.get_json()

    topic_name = request_json["topic"]
    topic_path = pubsub.topic_path(project, topic_name)

    # Process the request
    data = b"Test message"
    pubsub.publish(topic_path, data=data)

    return https_fn.Response("1 message published")
    

פונקציית ה-HTTP הזו משתמשת במכונה של ספריית לקוח שנשמרה במטמון כדי לצמצם את מספר החיבורים הנדרשים לכל קריאה לפונקציה. הפונקציה מקבלת אובייקט בקשה (flask.Request) ומחזירה את טקסט התגובה, או כל קבוצת ערכים שאפשר להפוך לאובייקט Response באמצעות make_response.

משתנה הסביבה GCP_PROJECT מוגדר באופן אוטומטי בסביבת זמן הריצה של Python 3.7. בסביבות זמן ריצה מאוחרות יותר, חשוב לציין את זה בפריסה של הפונקציה. הגדרת משתני סביבה

חיבורים יוצאים

זמני תפוגה של בקשות יוצאות

יש זמן קצוב לתפוגה אחרי 10 דקות של זמן חוסר פעילות לבקשות מהפונקציה לרשת ה-VPC. לבקשות מהפונקציה לאינטרנט, יש זמן קצוב לתפוגה אחרי 20 דקות של זמן חוסר פעילות.

איפוס חיבורים יוצאים

לפעמים, כשהתשתית הבסיסית מופעלת מחדש או מתעדכנת, יכול להיות שהזרמים של החיבור מהפונקציה לרשת ה-VPC ולאינטרנט יופסקו ויוחלפו. אם האפליקציה שלכם משתמשת שוב בחיבור לטווח ארוך, מומלץ להגדיר אותה כך שתקים מחדש את החיבורים כדי למנוע שימוש חוזר בחיבור לא תקין.

בדיקת עומס של הפונקציה

כדי למדוד את מספר החיבורים שהפונקציה מבצעת בממוצע, פשוט פורסים אותה כפונקציית HTTP ומשתמשים במסגרת לבדיקת ביצועים כדי להפעיל אותה בקצב מסוים של בקשות לשנייה. אפשרות אחת היא Artillery, שאפשר להפעיל אותה בשורה אחת:

$ artillery quick -d 300 -r 30 URL

הפקודה הזו מאחזרת את כתובת ה-URL שצוינה בקצב של 30 שאילתות לשנייה למשך 300 שניות.

אחרי ביצוע הבדיקה, כדאי לבדוק את השימוש במכסת החיבורים בדף Cloud Functions API quota במסוף Cloud. אם השימוש הוא בערך 30 (או מכפיל של 30) באופן עקבי, אתם יוצרים חיבור אחד (או כמה חיבורים) בכל קריאה. אחרי האופטימיזציה של הקוד, אמורות להתרחש כמה חיבורים (10 עד 30) רק בתחילת הבדיקה.

אפשר גם להשוות את עלות המעבד (CPU) לפני ואחרי האופטימיזציה בתרשים המכסות של המעבד (CPU) באותו דף.