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

הפשטות של 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. בסביבות זמן ריצה מאוחרות יותר, חשוב לציין את זה בפריסה של הפונקציה. הגדרת משתני סביבה

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

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

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

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

$ artillery quick -d 300 -r 30 URL

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

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

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