Dapr アプリケーションのサイドカーとして導入される daprd
は Invoke 時のメソッド名ごとにメトリクスを作って内部的に保持してしまいます。
そのため、下記ドキュメントのように Invoke 時のメソッド名を checkout/{orderId}
のような可変にすると、メトリクスが増え続けて不都合が生じるように思います。
例えば、固定であれば checkout
という単一のメトリクスで済んだものを、可変にする事で checkout/1
や checkout/2
のような無意味なメトリクスを次々と増やしていく事になり、消費メモリが徐々に増えてしまうはずです。
そこで、今回は Dapr で Invoke 時のメソッド名を可変にする事で消費メモリにどう影響するのか検証してみました。
はじめに
下記 2つの Dapr アプリケーション order と checkout を用いて、order から checkout を Invoke する際にメソッド名を可変にするかどうかで order 側の daprd
の消費メモリがどのように変化するのか検証します。
order アプリケーション
import { DaprServer, HttpMethod } from '@dapr/dapr'
import { randomUUID } from 'crypto'
const run = async () => {
const server = new DaprServer()
const opts = { method: HttpMethod.GET }
// 固定版( /checkout )
const check1 = (_) => server.client.invoker.invoke('checkout', 'checkout')
// 可変版( /checkout/{uuid} )
const check2 = (_) => server.client.invoker.invoke('checkout', `checkout/${randomUUID()}`)
await server.invoker.listen('check1', check1, opts)
await server.invoker.listen('check2', check2, opts)
await server.start()
}
run().catch(err => {
console.error(err)
process.exit(1)
})
FROM node:20-alpine
WORKDIR /app
COPY ./package*.json ./
RUN npm install
COPY . .
CMD ["node", "index.mjs"]
checkout アプリケーション
const http = require('http')
const server = http.createServer((req, res) => {
res.end(`path:${req.url}`)
})
server.listen(3000)
FROM node:20-alpine
WORKDIR /app
COPY . .
CMD ["node", "index.js"]
検証
検証は以下の 3パターンで実施しました。
- (a) 固定のメソッド名を使用
- (b) 可変のメソッド名を使用
- (c) daprd のメトリクスを無効化(注)した状態で可変のメソッド名を使用
(注) dapr.io/enable-metrics を false に設定
下記スクリプトで 3000 回のアクセスを実施した後、しばらく待ってから kubectl top pod --containers
を実施して daprd の消費メモリを取得します。これを 3回繰り返す事にしました。
テスト用スクリプト
import http from 'k6/http'
import { check, sleep } from 'k6'
export const options = {
vus: 20,
iterations: 3000
}
export default function() {
const r = http.get('http://127.0.0.1:3001/check1')
check(r, { 'status 200': (r) => r.status == 200 })
sleep(0.1)
}
...省略
export default function() {
const r = http.get('http://127.0.0.1:3001/check2')
check(r, { 'status 200': (r) => r.status == 200 })
sleep(0.1)
}
Kubernetes マニフェストの内容は次の通りです。
order 側は Heap の情報を取得できるようにプロファイリングを有効化(dapr.io/enable-profiling: "true"
)しています。
Kubernetes マニフェスト
kind: Service
apiVersion: v1
metadata:
name: order
labels:
app: order
spec:
selector:
app: order
ports:
- protocol: TCP
port: 3001
targetPort: 3000
type: LoadBalancer
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: order
labels:
app: order
spec:
replicas: 1
selector:
matchLabels:
app: order
template:
metadata:
labels:
app: order
annotations:
dapr.io/enabled: "true"
dapr.io/app-id: "order"
dapr.io/app-port: "3000"
dapr.io/enable-metrics: "true"
dapr.io/metrics-port: "9090"
dapr.io/enable-profiling: "true"
spec:
containers:
- name: order
image: order
env:
- name: APP_PORT
value: "3000"
ports:
- containerPort: 3000
imagePullPolicy: Never
kind: Service
apiVersion: v1
metadata:
name: checkout
labels:
app: checkout
spec:
selector:
app: checkout
ports:
- protocol: TCP
port: 3002
targetPort: 3000
type: LoadBalancer
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: checkout
labels:
app: checkout
spec:
replicas: 1
selector:
matchLabels:
app: checkout
template:
metadata:
labels:
app: checkout
annotations:
dapr.io/enabled: "true"
dapr.io/app-id: "checkout"
dapr.io/app-port: "3000"
dapr.io/enable-metrics: "true"
dapr.io/metrics-port: "9090"
spec:
...省略
(a) 固定メソッド名
kubectl apply
で上記のマニフェストを適用します。
この時点での消費メモリは以下のようになりました。
$ kubectl top pod --containers
POD NAME CPU(cores) MEMORY(bytes)
checkout-5b95fbcf-lgsf5 checkout 1m 7Mi
checkout-5b95fbcf-lgsf5 daprd 3m 19Mi
order-7d8b9fddd9-pb4f8 daprd 2m 19Mi
order-7d8b9fddd9-pb4f8 order 0m 25Mi
k6_check1.js
を 3回実行した結果がそれぞれ次のようになりました。
$ kubectl top pod --containers
POD NAME CPU(cores) MEMORY(bytes)
checkout-5b95fbcf-lgsf5 checkout 1m 8Mi
checkout-5b95fbcf-lgsf5 daprd 4m 22Mi
order-7d8b9fddd9-pb4f8 daprd 5m 28Mi
order-7d8b9fddd9-pb4f8 order 0m 24Mi
$ kubectl top pod --containers
POD NAME CPU(cores) MEMORY(bytes)
checkout-5b95fbcf-lgsf5 checkout 1m 10Mi
checkout-5b95fbcf-lgsf5 daprd 3m 29Mi
order-7d8b9fddd9-pb4f8 daprd 3m 26Mi
order-7d8b9fddd9-pb4f8 order 1m 63Mi
$ kubectl top pod --containers
POD NAME CPU(cores) MEMORY(bytes)
checkout-5b95fbcf-lgsf5 checkout 0m 10Mi
checkout-5b95fbcf-lgsf5 daprd 4m 29Mi
order-7d8b9fddd9-pb4f8 daprd 3m 25Mi
order-7d8b9fddd9-pb4f8 order 0m 24Mi
daprd の消費メモリが多少増えた程度で特に問題は無さそうです。
Heap Profile の取得
pprof で daprd(order側) の Heap 情報を取得できるようにポート 7777
へ port-forward します。
$ kubectl port-forward order-7d8b9fddd9-pb4f8 7777
pprof で Heap の top を取得したところ、次のような結果となりました。
$ go tool pprof -top http://localhost:7777/debug/pprof/heap
...省略
File: daprd
Type: inuse_space
Time: Jun 4, 2023 at 10:22pm (JST)
Showing nodes accounting for 11575.30kB, 100% of 11575.30kB total
flat flat% sum% cum cum%
2056.32kB 17.76% 17.76% 2056.32kB 17.76% github.com/aws/aws-sdk-go/aws/endpoints.init
1577.92kB 13.63% 31.40% 1577.92kB 13.63% github.com/denisenkom/go-mssqldb/internal/cp.init
1089.33kB 9.41% 40.81% 1089.33kB 9.41% github.com/valyala/fasthttp/stackless.NewFunc
1057.98kB 9.14% 49.95% 1057.98kB 9.14% regexp/syntax.(*compiler).inst (inline)
578.66kB 5.00% 54.95% 578.66kB 5.00% google.golang.org/protobuf/internal/strs.(*Builder).AppendFullName
553.04kB 4.78% 59.72% 553.04kB 4.78% google.golang.org/protobuf/reflect/protoregistry.(*Types).register
544.67kB 4.71% 64.43% 544.67kB 4.71% github.com/xdg-go/stringprep.init
524.09kB 4.53% 68.96% 524.09kB 4.53% k8s.io/apimachinery/pkg/runtime.(*Scheme).AddKnownTypeWithName
516.64kB 4.46% 73.42% 516.64kB 4.46% runtime.procresize
514.63kB 4.45% 77.87% 514.63kB 4.45% math/rand.newSource (inline)
...省略
(b) 可変メソッド名
念の為、Kubectl delete
してから再度マニフェストを apply しました。
$ kubectl top pod --containers
POD NAME CPU(cores) MEMORY(bytes)
checkout-5b95fbcf-rxlck checkout 1m 7Mi
checkout-5b95fbcf-rxlck daprd 3m 19Mi
order-7d8b9fddd9-lgq7z daprd 3m 19Mi
order-7d8b9fddd9-lgq7z order 1m 24Mi
可変メソッド名で Invoke する k6_check2.js
を 3回実行した結果がそれぞれ次のようになりました。
$ kubectl top pod --containers
POD NAME CPU(cores) MEMORY(bytes)
checkout-5b95fbcf-rxlck checkout 1m 9Mi
checkout-5b95fbcf-rxlck daprd 3m 46Mi
order-7d8b9fddd9-lgq7z daprd 5m 47Mi
order-7d8b9fddd9-lgq7z order 1m 25Mi
$ kubectl top pod --containers
POD NAME CPU(cores) MEMORY(bytes)
checkout-5b95fbcf-rxlck checkout 0m 10Mi
checkout-5b95fbcf-rxlck daprd 7m 60Mi
order-7d8b9fddd9-lgq7z daprd 2m 69Mi
order-7d8b9fddd9-lgq7z order 0m 62Mi
$ kubectl top pod --containers
POD NAME CPU(cores) MEMORY(bytes)
checkout-5b95fbcf-rxlck checkout 1m 10Mi
checkout-5b95fbcf-rxlck daprd 3m 75Mi
order-7d8b9fddd9-lgq7z daprd 3m 80Mi
order-7d8b9fddd9-lgq7z order 1m 62Mi
懸念していた通り、(a) とは違って daprd の消費メモリが増え続ける結果となりました。
基本的には、daprd のコンテナが OOM Killer によって kill される(再起動になる)まで消費メモリが増え続けていくようです。
Heap Profile の取得
(a) と同様に Heap の top を取得したところ、次のような結果となりました。
$ go tool pprof -top http://localhost:7777/debug/pprof/heap
...省略
Showing nodes accounting for 36870.64kB, 100% of 36870.64kB total
flat flat% sum% cum cum%
10242.39kB 27.78% 27.78% 10242.39kB 27.78% go.opencensus.io/stats/view.newDistributionData
4096.34kB 11.11% 38.89% 18284.11kB 49.59% go.opencensus.io/stats/view.(*viewInternal).addSample
3584.34kB 9.72% 48.61% 3584.34kB 9.72% go.opencensus.io/stats/view.decodeTags
3433.37kB 9.31% 57.92% 14187.78kB 38.48% go.opencensus.io/stats/view.(*collector).addSample
2562.46kB 6.95% 64.87% 2562.46kB 6.95% github.com/aws/aws-sdk-go/aws/endpoints.init
1577.92kB 4.28% 69.15% 1577.92kB 4.28% github.com/denisenkom/go-mssqldb/internal/cp.init
1024.94kB 2.78% 71.93% 1024.94kB 2.78% regexp/syntax.(*compiler).inst (inline)
1024.11kB 2.78% 74.71% 1024.11kB 2.78% regexp/syntax.(*parser).newRegexp (inline)
1024.05kB 2.78% 77.49% 4608.38kB 12.50% go.opencensus.io/stats/view.(*collector).collectedRows
600.58kB 1.63% 79.12% 1113.21kB 3.02% github.com/go-playground/validator/v10.init
...省略
(a) とは大きく異なり、メトリクス系のデータ(go.opencensus.io/stats
)に大きく占有されています。
(c) 可変メソッド名 + メトリクス無効(enable-metrics:"false")
dapr.io/enable-metrics
を false
にした場合に変化があるのかを下記マニフェストを使って検証しました。
...省略
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: order
labels:
app: order
spec:
replicas: 1
selector:
matchLabels:
app: order
template:
metadata:
labels:
app: order
annotations:
dapr.io/enabled: "true"
dapr.io/app-id: "order"
dapr.io/app-port: "3000"
dapr.io/enable-metrics: "false"
dapr.io/enable-profiling: "true"
spec:
...省略
結果は次の通りです。
dapr.io/enable-metrics
を false
にしてもメトリクス自体は無効化されず、(b) と同様に消費メモリが増え続けました。
$ kubectl top pod --containers
POD NAME CPU(cores) MEMORY(bytes)
checkout-5b95fbcf-tj7jt checkout 1m 7Mi
checkout-5b95fbcf-tj7jt daprd 3m 19Mi
order-956fc649f-7n8sw daprd 3m 19Mi
order-956fc649f-7n8sw order 1m 24Mi
$ kubectl top pod --containers
POD NAME CPU(cores) MEMORY(bytes)
checkout-5b95fbcf-tj7jt checkout 1m 9Mi
checkout-5b95fbcf-tj7jt daprd 4m 44Mi
order-956fc649f-7n8sw daprd 2m 52Mi
order-956fc649f-7n8sw order 1m 25Mi
$ kubectl top pod --containers
POD NAME CPU(cores) MEMORY(bytes)
checkout-5b95fbcf-tj7jt checkout 0m 9Mi
checkout-5b95fbcf-tj7jt daprd 3m 60Mi
order-956fc649f-7n8sw daprd 3m 71Mi
order-956fc649f-7n8sw order 0m 62Mi
$ kubectl top pod --containers
POD NAME CPU(cores) MEMORY(bytes)
checkout-5b95fbcf-tj7jt checkout 0m 10Mi
checkout-5b95fbcf-tj7jt daprd 3m 73Mi
order-956fc649f-7n8sw daprd 3m 86Mi
order-956fc649f-7n8sw order 1m 62Mi
Heap Profile の取得
こちらも (b) と同様の結果となりました。
$ go tool pprof -top http://localhost:7777/debug/pprof/heap
...省略
Showing nodes accounting for 44173.14kB, 100% of 44173.14kB total
flat flat% sum% cum cum%
16900.43kB 38.26% 38.26% 16900.43kB 38.26% go.opencensus.io/stats/view.newDistributionData
4291.71kB 9.72% 47.98% 21704.16kB 49.13% go.opencensus.io/stats/view.(*collector).addSample
3584.30kB 8.11% 56.09% 25288.46kB 57.25% go.opencensus.io/stats/view.(*viewInternal).addSample
3072.70kB 6.96% 63.05% 3072.70kB 6.96% go.opencensus.io/stats/view.(*DistributionData).clone
2366.89kB 5.36% 68.40% 2366.89kB 5.36% github.com/denisenkom/go-mssqldb/internal/cp.init
2082.37kB 4.71% 73.12% 2082.37kB 4.71% regexp/syntax.(*compiler).inst (inline)
2049.48kB 4.64% 77.76% 2049.48kB 4.64% github.com/aws/aws-sdk-go/aws/endpoints.init
1536.06kB 3.48% 81.23% 1536.06kB 3.48% internal/reflectlite.Swapper
1028.89kB 2.33% 83.56% 1028.89kB 2.33% regexp.onePassCopy
1024.34kB 2.32% 85.88% 1024.34kB 2.32% google.golang.org/protobuf/internal/impl.legacyLoadMessageInfo
...省略
まとめ
- Invoke 時のメソッド名を可変にするのは避けた方が無難
- 可変にするとメトリクスが増え、消費メモリが無駄に増え続ける事になりかねない
- dapr.io/enable-metrics を false に設定しても、メトリクス自体は無効化されない