ネットワークを最適化する(第 1 世代)
Cloud Run functions を使用すると、サーバーレス環境でコードをすばやく開発して実行できます。中程度の規模では、関数のランニング コストが低いため、コードの最適化の重要性が見過ごされがちです。しかし、デプロイが拡大するにつれて、コードの最適化の重要性が高まります。
このドキュメントでは、関数用にネットワークを最適化する方法について説明します。ネットワークの最適化には次のようなメリットがあります。
- 各関数呼び出しで新しい接続を確立するために要する CPU 時間を短縮する。
- 接続や DNS 割り当てが不足する可能性を減らす。
持続的接続を維持する
このセクションでは、ファンクション内で持続的な接続を維持する方法の例を示して説明します。持続的な接続を維持できないと、接続割り当てがすぐに使い果たされてしまう場合があります。
このセクションでは、次のシナリオを取り上げます。
- HTTP/S
- Google API
HTTP/S リクエスト
以下の最適化コード スニペットでは、関数が呼び出されるたびに新しい接続を作成するのではなく、永続的な接続を維持する方法を示しています。
Node.js
Python
Go
PHP
Guzzle PHP HTTP Framework を使用して HTTP リクエストを送信することをおすすめします。このフレームワークは永続的な接続を自動的に処理します。
Google API へのアクセス
以下の例では Cloud Pub/Sub を使用していますが、このアプローチは Cloud Natural Language や Cloud Spanner など、他のクライアント ライブラリでも使用できます。パフォーマンスの改善度は、特定のクライアント ライブラリの現在の実装状態によって変わることがあります。
Pub/Sub クライアント オブジェクトを作成すると、呼び出しごとに 1 つの接続と 2 つの DNS クエリが行われます。不要な接続と DNS クエリを行わないようにするには、下の例のように、グローバル スコープで Pub/Sub クライアント オブジェクトを作成します。
Node.js
Python
Go
// Package contexttip is an example of how to use Pub/Sub and context.Context in // a Cloud Function. package contexttip import ( "context" "encoding/json" "fmt" "log" "net/http" "os" "sync" "cloud.google.com/go/pubsub" "github.com/GoogleCloudPlatform/functions-framework-go/functions" ) // client is a global Pub/Sub client, initialized once per instance. var client *pubsub.Client var once sync.Once // createClient creates the global pubsub Client func createClient() { // GOOGLE_CLOUD_PROJECT is a user-set environment variable. var projectID = os.Getenv("GOOGLE_CLOUD_PROJECT") // err is pre-declared to avoid shadowing client. var err error // client is initialized with context.Background() because it should // persist between function invocations. client, err = pubsub.NewClient(context.Background(), projectID) if err != nil { log.Fatalf("pubsub.NewClient: %v", err) } } func init() { // register http function functions.HTTP("PublishMessage", PublishMessage) } type publishRequest struct { Topic stringjson:"topic"
Message stringjson:"message"
} // PublishMessage publishes a message to Pub/Sub. PublishMessage only works // with topics that already exist. func PublishMessage(w http.ResponseWriter, r *http.Request) { // use of sync.Once ensures client is only created once. once.Do(createClient) // Parse the request body to get the topic name and message. p := publishRequest{} if err := json.NewDecoder(r.Body).Decode(&p); err != nil { log.Printf("json.NewDecoder: %v", err) http.Error(w, "Error parsing request", http.StatusBadRequest) return } if p.Topic == "" || p.Message == "" { s := "missing 'topic' or 'message' parameter" log.Println(s) http.Error(w, s, http.StatusBadRequest) return } m := &pubsub.Message{ Data: []byte(p.Message), } // Publish and Get use r.Context() because they are only needed for this // function invocation. If this were a background function, they would use // the ctx passed as an argument. id, err := client.Topic(p.Topic).Publish(r.Context(), m).Get(r.Context()) if err != nil { log.Printf("topic(%s).Publish.Get: %v", p.Topic, err) http.Error(w, "Error publishing message", http.StatusInternalServerError) return } fmt.Fprintf(w, "Message published: %v", id) }
アウトバウンド接続のリセット
関数から VPC とインターネットの両方への接続ストリームは、基盤となるインフラストラクチャの再起動や更新が発生すると、停止して置き替えられる場合があります。アプリケーションで長期間の接続を再利用する場合は、切断された接続の再利用を避けるため、接続を再確立してアプリケーションを構成することをおすすめします。
関数に負荷テストを行う
関数で実行する接続の平均数を測定するには、HTTP 関数としてデプロイしてから、パフォーマンス テスト フレームワークを使用して特定の QPS で呼び出します。1 つの選択肢として Artillery があります。これは次のように 1 行で呼び出すことができます。
$ artillery quick -d 300 -r 30 URL
このコマンドは、指定された URL を 30 QPS で 300 秒間フェッチします。
テストを実行した後、コンソールの Cloud Run functions の API 割り当てページで接続割り当ての使用状況を確認します。 Google Cloud 使用状況が常に 30(またはその倍数)前後の場合、すべての呼び出しで 1 つ(または複数)の接続が確立されています。コードを最適化した後は、いくつかの接続(10~30)がテストの開始時にのみ発生していることがわかります。
同じページの CPU 割り当てプロットで、最適化前後の CPU コストを比較することもできます。