概要
firebase で Cloud Functions を使ったときに、JavaScript と golang で作った function とで性能に差がでるかを簡単に確認したのでそのメモ
firebase の Cloud Functions で Go言語使える?
らしい。のでやってみました。
やり方としては、以下参考の「go言語でCloud Functions + FireStoreをhttpで使ってみた」を参照しつつ、ざっくりとは次の手順で環境を構築しました
- firebase のコンソールでプロジェクトを作成
- firebase に javascript 版の Cloud Functions の関数をデプロイする
- golang で Cloud Functions の関数を作成し、GCP にデプロイする
- firebase のプロジェクトを使いまわして、GCP の Cloud Functions を作成するのがミソ
参考
測定結果
メモリは、なんとなく128MBに変更しました。測定は、vegeta を利用しました。
結果は、レイテンシが JavaScript 版が平均 5.138s に対して、Go 言語版が 161.252ms と 32倍の差がでました
あと、GCP のコンソールから目分量ですが、メモリとアクティブインスタンス数に大きな違いがでました
- | JS | Go | 比較 |
---|---|---|---|
メモリ | 100MB | 15MB | 6.6倍 |
アクティブインスタンス | 50 | 3 | 16倍 |
参考
JavaScript 版
10rps を 1秒間
gosshys@gosshysnoMacBook-Air perf % vegeta attack --rate 10 -duration=1s -targets=target.txt | vegeta report
Requests [total, rate, throughput] 10, 11.10, 1.17
Duration [total, attack, wait] 8.57s, 900.524ms, 7.669s
Latencies [min, mean, 50, 90, 95, 99, max] 3.124s, 4.734s, 3.826s, 7.376s, 7.669s, 7.669s, 7.669s
Bytes In [total, mean] 5920, 592.00
Bytes Out [total, mean] 400, 40.00
Success [ratio] 100.00%
Status Codes [code:count] 200:10
Error Set:
10rps を 10秒間
gosshys@gosshysnoMacBook-Air perf % vegeta attack --rate 10 -duration=10s -targets=target.txt | vegeta report
Requests [total, rate, throughput] 100, 10.10, 6.08
Duration [total, attack, wait] 16.45s, 9.901s, 6.549s
Latencies [min, mean, 50, 90, 95, 99, max] 2.439s, 5.138s, 4.602s, 8.011s, 8.437s, 9.379s, 9.571s
Bytes In [total, mean] 59200, 592.00
Bytes Out [total, mean] 4000, 40.00
Success [ratio] 100.00%
Status Codes [code:count] 200:100
Error Set:
golang 版
10rps を 1秒間
gosshys@gosshysnoMacBook-Air perf % vegeta attack --rate 10 -duration=1s -targets=target_go.txt | vegeta report
Requests [total, rate, throughput] 10, 11.18, 9.01
Duration [total, attack, wait] 1.11s, 894.571ms, 215.591ms
Latencies [min, mean, 50, 90, 95, 99, max] 135.716ms, 259.473ms, 178.427ms, 563.063ms, 691.467ms, 691.468ms, 691.468ms
Bytes In [total, mean] 610, 61.00
Bytes Out [total, mean] 0, 0.00
Success [ratio] 100.00%
Status Codes [code:count] 200:10
Error Set:
10rps を 10秒間
gosshys@gosshysnoMacBook-Air perf % vegeta attack --rate 10 -duration=10s -targets=target_go.txt | vegeta report
Requests [total, rate, throughput] 100, 10.10, 9.96
Duration [total, attack, wait] 10.044s, 9.9s, 143.807ms
Latencies [min, mean, 50, 90, 95, 99, max] 142.071ms, 161.252ms, 148.575ms, 163.501ms, 184.75ms, 623.21ms, 942.147ms
Bytes In [total, mean] 6100, 61.00
Bytes Out [total, mean] 0, 0.00
Success [ratio] 100.00%
Status Codes [code:count] 200:100
Error Set:
付録
測定に利用した functions
リクエストで指定された url にアクセスして、OGP(https://ogp.me/)を metaタグから抜き出して返却する関数
JavaScript
JS の書き方が悪いせいで遅くなっていたらすみません
const functions = require("firebase-functions");
const axios = require('axios');
const jsdom = require('jsdom');
const { JSDOM } = jsdom;
exports.getOgpFromExternalWebsite = functions.https.onCall(async (data, context) => {
if (!data.url) {
console.log("not exists url");
return {};
}
const headers = {'User-Agent': 'bot'};
const res = await axios.get(data.url, {headers: headers});
const html = res.data;
const dom = new JSDOM(html);
const meta = dom.window.document.head.querySelectorAll("meta");
const ogp = {};
let i = 0, len = meta.length;
for (; i < len; i++) {
if (!meta[i].hasAttribute("property")) {
continue;
}
const property = meta[i].getAttribute("property");
const content = meta[i].getAttribute("content");
ogp[property] = content;
}
if (Object.keys(ogp).length===0) {
return {};
}
return ogp;
})
golang
package ogp
import (
"fmt"
"golang.org/x/net/html"
"golang.org/x/net/html/atom"
"log"
"net/http"
)
type OGType int
const (
Type OGType = iota
Title
Description
URL
Image
)
type OG struct {
Type,
Title,
Description,
URL, Image string
}
func findMeta(node *html.Node, og OG) OG {
for c := node.FirstChild; c != nil; c = c.NextSibling {
if c.Type == html.ElementNode {
if c.DataAtom == atom.Meta {
var val, t string
for _, attr := range c.Attr {
if attr.Key == "property" {
t = attr.Val
continue
}
if attr.Key == "content" {
val = attr.Val
}
}
switch t {
case "og:type":
og.Type = val
case "og:title":
og.Title = val
case "og:description":
og.Description = val
case "og:url":
og.URL = val
case "og:image":
og.Image = val
}
}
if og.Type != "" && og.Title != "" && og.Description != "" && og.URL != "" && og.Image != "" {
break
}
og = findMeta(c, og)
}
}
return og
}
func OGPGet(w http.ResponseWriter, r *http.Request) {
url := r.URL.Query().Get("url")
if url == "" {
w.Write([]byte("welcome"))
return
}
resp, err := http.Get(url)
if err != nil {
fmt.Fprintf(w, "err: %+v", err)
return
}
defer resp.Body.Close()
node, err := html.Parse(resp.Body)
if err != nil {
log.Fatal(err)
}
og := OG{}
og = findMeta(node, og)
fmt.Fprintf(w, "url:%s, og:%+v", url, og)
}
GCP にデプロイするコマンド
gcloud functions deploy OGPGet --runtime go116 --trigger-http --project <firebase のプロジェクトID>
測定に利用したリクエスト (vegeta の targetsファイル)
JavaScript
※ 東京リージョンに作成したかったが、なぜか us-central1 に作成されたようです
target.txt
POST https://us-central1-<firebase のプロジェクトID>.cloudfunctions.net/getOgpFromExternalWebsite
Content-Type: application/json
@req.json
req.json
{"data":{"url": "https://yahoo.co.jp"}}
golang
target_go.txt
GET https://us-central1-<firebase のプロジェクトID>.cloudfunctions.net/OGPGet?url=https%3A%3A%2F%2Fyahoo.co.jp