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

הפשטות של 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.

גישה לממשקי ה-API של Google

בדוגמה הבאה נעשה שימוש ב-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) באותו דף.