Cloud Run Functions의 단순성을 활용하여 빠르게 코드를 개발하고 서버리스 환경에서 실행할 수 있습니다. 중간 규모에서는 함수 실행 비용이 적으며 코드 최적화의 우선순위가 높아 보이지 않을 수 있습니다. 하지만 배포 규모가 커짐에 따라 코드 최적화가 점점 중요해집니다.
이 문서에서는 함수의 네트워킹을 최적화하는 방법을 설명합니다. 네트워킹 최적화의 장점은 다음과 같습니다.
이 섹션에서는 함수에서 지속적인 연결을 유지하는 방법의 예를 보여줍니다. 지속적인 연결을 유지하지 못하면 연결 할당량이 빠르게 소진될 수 있습니다.
이 섹션에서 다루는 상황은 다음과 같습니다.
HTTP/S
Google API
HTTP/S 요청
아래의 최적화된 코드 스니펫은 함수 호출마다 새 연결을 만드는 대신 지속적인 연결을 유지하는 방법을 보여줍니다.
Node.js
constfetch=require('node-fetch');consthttp=require('http');consthttps=require('https');constfunctions=require('@google-cloud/functions-framework');consthttpAgent=newhttp.Agent({keepAlive:true});consthttpsAgent=newhttps.Agent({keepAlive:true});/** * HTTP Cloud Function that caches an HTTP agent to pool HTTP connections. * * @param {Object} req Cloud Function request context. * @param {Object} res Cloud Function response context. */functions.http('connectionPooling',async(req,res)=>{try{// TODO(optional): replace this with your own URL.consturl='https://www.example.com/';// Select the appropriate agent to use based on the URL.constagent=url.includes('https')?httpsAgent:httpAgent;constfetchResponse=awaitfetch(url,{agent});consttext=awaitfetchResponse.text();res.status(200).send(`Data: ${text}`);}catch(err){res.status(500).send(`Error: ${err.message}`);}});
Python
importfunctions_frameworkimportrequests# Create a global HTTP session (which provides connection pooling)session=requests.Session()@functions_framework.httpdefconnection_pooling(request):""" HTTP Cloud Function that uses a connection pool to make HTTP requests. Args: request (flask.Request): The request object. <http://flask.pocoo.org/docs/1.0/api/#flask.Request> Returns: The response text, or any set of values that can be turned into a Response object using `make_response` <http://flask.pocoo.org/docs/1.0/api/#flask.Flask.make_response>. """# The URL to send the request tourl="http://example.com"# Process the requestresponse=session.get(url)response.raise_for_status()return"Success!"
Go
// Package http provides a set of HTTP Cloud Functions samples.packagehttpimport("fmt""net/http""time""github.com/GoogleCloudPlatform/functions-framework-go/functions")varurlString="https://example.com"// client is used to make HTTP requests with a 10 second timeout.// http.Clients should be reused instead of created as needed.varclient=&http.Client{Timeout:10*time.Second,}funcinit(){functions.HTTP("MakeRequest",MakeRequest)}// MakeRequest is an example of making an HTTP request. MakeRequest uses a// single http.Client for all requests to take advantage of connection// pooling and caching. See https://godoc.org/net/http#Client.funcMakeRequest(whttp.ResponseWriter,r*http.Request){resp,err:=client.Get(urlString)iferr!=nil{http.Error(w,"Error making request",http.StatusInternalServerError)return}ifresp.StatusCode!=http.StatusOK{msg:=fmt.Sprintf("Bad StatusCode: %d",resp.StatusCode)http.Error(w,msg,http.StatusInternalServerError)return}fmt.Fprintf(w,"ok")}
Pub/Sub 클라이언트 객체를 만들면 호출당 연결은 1회, DNS 쿼리는 2회가 발생합니다. 불필요한 연결과 DNS 쿼리를 방지하기 위해 다음 샘플과 같이 전역 범위에서 Pub/Sub 클라이언트 객체를 만듭니다.
Node.js
constfunctions=require('@google-cloud/functions-framework');const{PubSub}=require('@google-cloud/pubsub');constpubsub=newPubSub();/** * HTTP Cloud Function that uses a cached client library instance to * reduce the number of connections required per function invocation. * * @param {Object} req Cloud Function request context. * @param {Object} req.body Cloud Function request context body. * @param {String} req.body.topic The Cloud Pub/Sub topic to publish to. * @param {Object} res Cloud Function response context. */functions.http('gcpApiCall',(req,res)=>{consttopic=pubsub.topic(req.body.topic);constdata=Buffer.from('Test message');topic.publishMessage({data},err=>{if(err){res.status(500).send(`Error publishing the message: ${err}`);}else{res.status(200).send('1 message published');}});});
Python
importosimportfunctions_frameworkfromgoogle.cloudimportpubsub_v1# Create a global Pub/Sub client to avoid unneeded network activitypubsub=pubsub_v1.PublisherClient()@functions_framework.httpdefgcp_api_call(request):""" HTTP Cloud Function that uses a cached client library instance to reduce the number of connections required per function invocation. Args: request (flask.Request): The request object. Returns: The response text, or any set of values that can be turned into a Response object using `make_response` <http://flask.pocoo.org/docs/1.0/api/#flask.Flask.make_response>. """""" The `GCP_PROJECT` environment variable is set automatically in the Python 3.7 runtime. In later runtimes, it must be specified by the user upon function deployment. See this page for more information: https://cloud.google.com/functions/docs/configuring/env-var#python_37_and_go_111 """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 requestdata=b"Test message"pubsub.publish(topic_path,data=data)return"1 message published"
Go
// Package contexttip is an example of how to use Pub/Sub and context.Context in// a Cloud Function.packagecontexttipimport("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.varclient*pubsub.Clientvaroncesync.Once// createClient creates the global pubsub ClientfunccreateClient(){// GOOGLE_CLOUD_PROJECT is a user-set environment variable.varprojectID=os.Getenv("GOOGLE_CLOUD_PROJECT")// err is pre-declared to avoid shadowing client.varerrerror// client is initialized with context.Background() because it should// persist between function invocations.client,err=pubsub.NewClient(context.Background(),projectID)iferr!=nil{log.Fatalf("pubsub.NewClient: %v",err)}}funcinit(){// register http functionfunctions.HTTP("PublishMessage",PublishMessage)}typepublishRequeststruct{Topicstring`json:"topic"`Messagestring`json:"message"`}// PublishMessage publishes a message to Pub/Sub. PublishMessage only works// with topics that already exist.funcPublishMessage(whttp.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{}iferr:=json.NewDecoder(r.Body).Decode(&p);err!=nil{log.Printf("json.NewDecoder: %v",err)http.Error(w,"Error parsing request",http.StatusBadRequest)return}ifp.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())iferr!=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에서 호출합니다. 사용 가능한 방법 중 하나는 Artillery이며 한 줄로 호출할 수 있습니다.
$ artillery quick -d 300 -r 30 URL
이 명령어는 300초 동안 30QPS로 해당 URL을 가져옵니다.
테스트를 수행한 후 Google Cloud 콘솔의 Cloud Run Functions API 할당량 페이지에서 연결 할당량의 사용량을 확인하세요. 사용량이 지속적으로 30 내외라면 호출할 때마다 1번 연결하는 것이고, 30의 배수라면 여러 번 연결하는 것입니다. 코드를 최적화한 후에는 테스트를 시작할 때만 10~30 정도의 적은 연결 수가 표시되어야 합니다.
또한 같은 페이지의 CPU 할당량 플롯에서 최적화 전후의 CPU 비용을 비교할 수 있습니다.
[[["이해하기 쉬움","easyToUnderstand","thumb-up"],["문제가 해결됨","solvedMyProblem","thumb-up"],["기타","otherUp","thumb-up"]],[["이해하기 어려움","hardToUnderstand","thumb-down"],["잘못된 정보 또는 샘플 코드","incorrectInformationOrSampleCode","thumb-down"],["필요한 정보/샘플이 없음","missingTheInformationSamplesINeed","thumb-down"],["번역 문제","translationIssue","thumb-down"],["기타","otherDown","thumb-down"]],["최종 업데이트: 2024-11-20(UTC)"],[[["This document outlines networking optimization strategies specifically for Cloud Run functions (1st gen), which is crucial for scalability and cost-effectiveness."],["Maintaining persistent connections, rather than creating new ones for each function invocation, significantly reduces CPU time and minimizes the risk of exceeding connection or DNS quotas."],["For HTTP/S requests, using persistent connection pooling, as demonstrated in the Node.js, Python, Go, and PHP examples, is essential to optimizing connection management."],["When interacting with Google APIs, like Cloud Pub/Sub, creating client objects in global scope avoids unnecessary connection and DNS queries, which also leads to more optimized networking performance."],["It is important to load test your functions to measure connection usage, and if you see connection resets, re-establish them to avoid dead connections, optimizing your function's overall performance."]]],[]]