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

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

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

  • להפחית את זמן המעבד (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 בהיקף גלובלי, כמו בדוגמה הבאה:

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 ומשתמשים במסגרת לבדיקת ביצועים כדי להפעיל אותה ב-QPS מסוים. אפשרות אחת היא Artillery, שאפשר להפעיל בשורה אחת:

$ artillery quick -d 300 -r 30 URL

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

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

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