1. はじめに

最終更新日: 2022 年 7 月 14 日
アプリケーションのオブザーバビリティ
オブザーバビリティと継続的プロファイラ
オブザーバビリティは、システムの属性を表す用語です。オブザーバビリティを備えたシステムでは、チームがシステムを積極的にデバッグできます。このコンテキストでは、オブザーバビリティの 3 つの柱であるログ、指標、トレースは、システムがオブザーバビリティを取得するための基本的な計測です。
また、オブザーバビリティの 3 つの柱に加えて、継続的プロファイリングはオブザーバビリティのもう 1 つの重要なコンポーネントであり、業界でユーザーベースを拡大しています。Cloud Profiler は、アプリケーションのコールスタックのパフォーマンス指標を簡単にドリルダウンできるインターフェースを提供します。
この Codelab はシリーズのパート 2 で、継続的プロファイラ エージェントの計測について説明します。パート 1 では、OpenTelemetry と Cloud Trace を使用した分散トレースについて説明します。パート 1 では、マイクロサービスのボトルネックを特定する方法について詳しく説明します。
作成するアプリの概要
この Codelab では、Google Kubernetes Engine クラスタで実行される「Shakespeare アプリケーション」(Shakesapp)のサーバー サービスで継続的プロファイラ エージェントを計測します。Shakesapp のアーキテクチャは次のとおりです。

- Loadgen が HTTP でクエリ文字列をクライアントに送信する
- クライアントは、loadgen からサーバーへのクエリを gRPC で渡します。
- サーバーはクライアントからクエリを受け取り、Google Cloud Storage からテキスト形式のシェイクスピアの全作品を取得し、クエリを含む行を検索して、一致した行の数をクライアントに返します。
パート 1 では、ボトルネックがサーバー サービスのどこかに存在することがわかりましたが、正確な原因を特定できませんでした。
学習内容
- Profiler エージェントを埋め込む方法
- Cloud Profiler でボトルネックを調査する方法
この Codelab では、アプリケーションで継続的プロファイラ エージェントを計装する方法について説明します。
必要なもの
- Go の基本的な知識
- Kubernetes の基礎知識
2. 設定と要件
セルフペース型の環境設定
Google アカウント(Gmail または Google Apps)をお持ちでない場合は、1 つ作成する必要があります。Google Cloud Platform のコンソール(console.cloud.google.com)にログインし、新しいプロジェクトを作成します。
すでにプロジェクトが存在する場合は、コンソールの左上にあるプロジェクト選択プルダウン メニューをクリックします。

表示されたダイアログで [NEW PROJECT] ボタンをクリックして、新しいプロジェクトを作成します。

まだプロジェクトが存在しない場合は、次のような最初のプロジェクトを作成するためのダイアログが表示されます。

続いて表示されるプロジェクト作成ダイアログでは、新しいプロジェクトの詳細を入力できます。

プロジェクト ID を忘れないようにしてください。プロジェクト ID は、すべての Google Cloud プロジェクトを通じて一意の名前にする必要があります(上記の名前はすでに使用されているため使用できません)。以降、この Codelab では PROJECT_ID と呼びます。
次に、Google Cloud リソースを使用し、Cloud Trace API を有効にするために、Developers Console で課金を有効にする必要があります。

この Codelab の操作をすべて行っても、費用は数ドル程度です。ただし、その他のリソースを使いたい場合や、実行したままにしておきたいステップがある場合は、追加コストがかかる可能性があります(このドキュメントの最後にある「クリーンアップ」セクションをご覧ください)。Google Cloud Trace、Google Kubernetes Engine、Google Artifact Registry の料金は、公式ドキュメントに記載されています。
- Google Cloud のオペレーション スイートの料金 | オペレーション スイート
- 料金 | Kubernetes Engine ドキュメント
- Artifact Registry の料金 | Artifact Registry のドキュメント
Google Cloud Platform の新規ユーザーの皆さんには、$300 の無料トライアルをご利用いただけます。その場合は、この Codelab を完全に無料でご利用いただけます。
Google Cloud Shell の設定
Google Cloud と Google Cloud Trace はノートパソコンからリモートで操作できますが、この Codelab では、Google Cloud Shell(Cloud 上で動作するコマンドライン環境)を使用します。
この Debian ベースの仮想マシンには、必要な開発ツールがすべて用意されています。仮想マシンは Google Cloud で稼働し、永続的なホーム ディレクトリが 5 GB 用意されているため、ネットワークのパフォーマンスと認証が大幅に向上しています。つまり、この Codelab に必要なのはブラウザだけです(Chromebook でも動作します)。
Cloud Console から Cloud Shell を有効にするには、[Cloud Shell をアクティブにする]
をクリックします(環境のプロビジョニングと接続に若干時間を要します)。


Cloud Shell に接続すると、すでに認証は完了しており、プロジェクトに各自の PROJECT_ID が設定されていることがわかります。
gcloud auth list
コマンド出力
Credentialed accounts: - <myaccount>@<mydomain>.com (active)
gcloud config list project
コマンド出力
[core] project = <PROJECT_ID>
なんらかの理由でプロジェクトが設定されていない場合は、次のコマンドを実行します。
gcloud config set project <PROJECT_ID>
PROJECT_ID が見つからない場合は、設定手順で使用した ID を確認するか、Cloud Console ダッシュボードで検索します。

Cloud Shell では、デフォルトで環境変数もいくつか設定されます。これらの変数は、以降のコマンドを実行する際に有用なものです。
echo $GOOGLE_CLOUD_PROJECT
コマンド出力
<PROJECT_ID>
最後に、デフォルトのゾーンとプロジェクト構成を設定します。
gcloud config set compute/zone us-central1-f
さまざまなゾーンを選択できます。詳細については、リージョンとゾーンをご覧ください。
Go 言語の設定
この Codelab では、すべてのソースコードに Go を使用します。Cloud Shell で次のコマンドを実行し、Go のバージョンが 1.17 以降であることを確認します。
go version
コマンド出力
go version go1.18.3 linux/amd64
Google Kubernetes クラスタを設定する
この Codelab では、Google Kubernetes Engine(GKE)でマイクロサービスのクラスタを実行します。この Codelab のプロセスは次のとおりです。
- ベースライン プロジェクトを Cloud Shell にダウンロードする
- マイクロサービスをコンテナにビルドする
- コンテナを Google Artifact Registry(GAR)にアップロードする
- コンテナを GKE にデプロイする
- トレース計測用にサービスのソースコードを変更する
- ステップ 2 に進みます
Kubernetes Engine を有効にする
まず、Shakesapp が GKE で実行される Kubernetes クラスタを設定します。そのため、GKE を有効にする必要があります。[Kubernetes Engine] メニューに移動し、[有効にする] ボタンを押します。

これで、Kubernetes クラスタを作成する準備が整いました。
Kubernetes クラスタを作成する
Cloud Shell で次のコマンドを実行して、Kubernetes クラスタを作成します。Artifact Registry リポジトリの作成に使用するリージョンにゾーン値が含まれていることを確認してください。リポジトリ リージョンがゾーンをカバーしていない場合は、ゾーン値 us-central1-f を変更します。
gcloud container clusters create otel-trace-codelab2 \ --zone us-central1-f \ --release-channel rapid \ --preemptible \ --enable-autoscaling \ --max-nodes 8 \ --no-enable-ip-alias \ --scopes cloud-platform
コマンド出力
Note: Your Pod address range (`--cluster-ipv4-cidr`) can accommodate at most 1008 node(s). Creating cluster otel-trace-codelab2 in us-central1-f... Cluster is being health-checked (master is healthy)...done. Created [https://container.googleapis.com/v1/projects/development-215403/zones/us-central1-f/clusters/otel-trace-codelab2]. To inspect the contents of your cluster, go to: https://console.cloud.google.com/kubernetes/workload_/gcloud/us-central1-f/otel-trace-codelab2?project=development-215403 kubeconfig entry generated for otel-trace-codelab2. NAME: otel-trace-codelab2 LOCATION: us-central1-f MASTER_VERSION: 1.23.6-gke.1501 MASTER_IP: 104.154.76.89 MACHINE_TYPE: e2-medium NODE_VERSION: 1.23.6-gke.1501 NUM_NODES: 3 STATUS: RUNNING
Artifact Registry と skaffold の設定
これで、デプロイの準備が整った Kubernetes クラスタができました。次に、コンテナを push してデプロイするためのコンテナ レジストリを準備します。これらの手順では、Artifact Registry(GAR)を設定し、それを使用するように skaffold を設定する必要があります。
Artifact Registry の設定
[Artifact Registry] のメニューに移動し、[有効にする] ボタンを押します。

しばらくすると、GAR のリポジトリ ブラウザが表示されます。[リポジトリを作成] ボタンをクリックして、リポジトリの名前を入力します。

この Codelab では、新しいリポジトリに trace-codelab という名前を付けます。アーティファクトの形式は「Docker」、ロケーション タイプは「リージョン」です。Google Compute Engine のデフォルト ゾーンに設定したゾーンに近いリージョンを選択します。たとえば、上記の例では「us-central1-f」を選択したので、ここでは「us-central1(アイオワ)」を選択します。[作成] ボタンをクリックします。

リポジトリ ブラウザに「trace-codelab」が表示されます。

後でここに戻ってレジストリ パスを確認します。
Skaffold の設定
Skaffold は、Kubernetes で実行されるマイクロサービスの構築に取り組む際に便利なツールです。一連のコマンドで、アプリケーションのコンテナのビルド、push、デプロイのワークフローを処理します。Skaffold はデフォルトで Docker Registry をコンテナ レジストリとして使用するため、コンテナを push するときに GAR を認識するように Skaffold を構成する必要があります。
Cloud Shell をもう一度開き、Skaffold がインストールされていることを確認します。(Cloud Shell はデフォルトで環境に skaffold をインストールします)。次のコマンドを実行して、skaffold のバージョンを確認します。
skaffold version
コマンド出力
v1.38.0
これで、skaffold が使用するデフォルトのリポジトリを登録できます。レジストリ パスを取得するには、Artifact Registry ダッシュボードに移動し、前の手順で設定したリポジトリの名前をクリックします。

ページの上部にパンくずリストが表示されます。
アイコンをクリックして、レジストリ パスをクリップボードにコピーします。

コピーボタンをクリックすると、ブラウザの下部に次のようなメッセージが表示されたダイアログが表示されます。
「us-central1-docker.pkg.dev/psychic-order-307806/trace-codelab」がコピーされました
Cloud Shell に戻ります。ダッシュボードからコピーした値を使用して skaffold config set default-repo コマンドを実行します。
skaffold config set default-repo us-central1-docker.pkg.dev/psychic-order-307806/trace-codelab
コマンド出力
set value default-repo to us-central1-docker.pkg.dev/psychic-order-307806/trace-codelab for context gke_stackdriver-sandbox-3438851889_us-central1-b_stackdriver-sandbox
また、レジストリを Docker 構成に構成する必要があります。次のコマンドを実行します。
gcloud auth configure-docker us-central1-docker.pkg.dev --quiet
コマンド出力
{
"credHelpers": {
"gcr.io": "gcloud",
"us.gcr.io": "gcloud",
"eu.gcr.io": "gcloud",
"asia.gcr.io": "gcloud",
"staging-k8s.gcr.io": "gcloud",
"marketplace.gcr.io": "gcloud",
"us-central1-docker.pkg.dev": "gcloud"
}
}
Adding credentials for: us-central1-docker.pkg.dev
これで、次のステップに進み、GKE で Kubernetes コンテナを設定できます。
概要
この手順では、Codelab 環境を設定します。
- Cloud Shell を設定する
- コンテナ レジストリ用の Artifact Registry リポジトリを作成した
- コンテナ レジストリを使用するように Skaffold を設定する
- コードラボのマイクロサービスが実行される Kubernetes クラスタを作成した
次のステップ
次のステップでは、サーバー サービスで継続的プロファイラ エージェントを計測します。
3. マイクロサービスをビルド、push、デプロイする
Codelab の資料をダウンロードする
前のステップでは、この Codelab の前提条件をすべて設定しました。これで、これらの上にマイクロサービス全体を実行する準備が整いました。Codelab の資料は GitHub でホストされているため、次の git コマンドを使用して Cloud Shell 環境にダウンロードします。
cd ~ git clone https://github.com/ymotongpoo/opentelemetry-trace-codelab-go.git cd opentelemetry-trace-codelab-go
プロジェクトのディレクトリ構造は次のようになります。
.
├── README.md
├── step0
│ ├── manifests
│ ├── proto
│ ├── skaffold.yaml
│ └── src
├── step1
│ ├── manifests
│ ├── proto
│ ├── skaffold.yaml
│ └── src
├── step2
│ ├── manifests
│ ├── proto
│ ├── skaffold.yaml
│ └── src
├── step3
│ ├── manifests
│ ├── proto
│ ├── skaffold.yaml
│ └── src
├── step4
│ ├── manifests
│ ├── proto
│ ├── skaffold.yaml
│ └── src
├── step5
│ ├── manifests
│ ├── proto
│ ├── skaffold.yaml
│ └── src
└── step6
├── manifests
├── proto
├── skaffold.yaml
└── src
- manifests: Kubernetes マニフェスト ファイル
- proto: クライアントとサーバー間の通信の proto 定義
- src: 各サービスのソースコードのディレクトリ
- skaffold.yaml: skaffold の構成ファイル
この Codelab では、step4 フォルダにあるソースコードを更新します。また、step[1-6] フォルダのソースコードを参照して、最初からの変更を確認することもできます。(パート 1 ではステップ 0 ~ 4 を、パート 2 ではステップ 5 ~ 6 を説明します)。
skaffold コマンドを実行する
これで、作成した Kubernetes クラスタにコンテンツ全体をビルド、push、デプロイする準備が整いました。複数の手順が含まれているように見えますが、実際には skaffold がすべてを実行します。次のコマンドで試してみましょう。
cd step4 skaffold dev
コマンドを実行するとすぐに、docker build のログ出力が表示され、レジストリに正常に push されたことを確認できます。
コマンド出力
... ---> Running in c39b3ea8692b ---> 90932a583ab6 Successfully built 90932a583ab6 Successfully tagged us-central1-docker.pkg.dev/psychic-order-307806/trace-codelab/serverservice:step1 The push refers to repository [us-central1-docker.pkg.dev/psychic-order-307806/trace-codelab/serverservice] cc8f5a05df4a: Preparing 5bf719419ee2: Preparing 2901929ad341: Preparing 88d9943798ba: Preparing b0fdf826a39a: Preparing 3c9c1e0b1647: Preparing f3427ce9393d: Preparing 14a1ca976738: Preparing f3427ce9393d: Waiting 14a1ca976738: Waiting 3c9c1e0b1647: Waiting b0fdf826a39a: Layer already exists 88d9943798ba: Layer already exists f3427ce9393d: Layer already exists 3c9c1e0b1647: Layer already exists 14a1ca976738: Layer already exists 2901929ad341: Pushed 5bf719419ee2: Pushed cc8f5a05df4a: Pushed step1: digest: sha256:8acdbe3a453001f120fb22c11c4f6d64c2451347732f4f271d746c2e4d193bbe size: 2001
すべてのサービス コンテナの push が完了すると、Kubernetes Deployment が自動的に開始されます。
コマンド出力
sha256:b71fce0a96cea08075dc20758ae561cf78c83ff656b04d211ffa00cedb77edf8 size: 1997 Tags used in deployment: - serverservice -> us-central1-docker.pkg.dev/psychic-order-307806/trace-codelab/serverservice:step4@sha256:8acdbe3a453001f120fb22c11c4f6d64c2451347732f4f271d746c2e4d193bbe - clientservice -> us-central1-docker.pkg.dev/psychic-order-307806/trace-codelab/clientservice:step4@sha256:b71fce0a96cea08075dc20758ae561cf78c83ff656b04d211ffa00cedb77edf8 - loadgen -> us-central1-docker.pkg.dev/psychic-order-307806/trace-codelab/loadgen:step4@sha256:eea2e5bc8463ecf886f958a86906cab896e9e2e380a0eb143deaeaca40f7888a Starting deploy... - deployment.apps/clientservice created - service/clientservice created - deployment.apps/loadgen created - deployment.apps/serverservice created - service/serverservice created
デプロイ後、各コンテナの stdout に出力された実際のアプリケーション ログは次のようになります。
コマンド出力
[client] 2022/07/14 06:33:15 {"match_count":3040}
[loadgen] 2022/07/14 06:33:15 query 'love': matched 3040
[client] 2022/07/14 06:33:15 {"match_count":3040}
[loadgen] 2022/07/14 06:33:15 query 'love': matched 3040
[client] 2022/07/14 06:33:16 {"match_count":3040}
[loadgen] 2022/07/14 06:33:16 query 'love': matched 3040
[client] 2022/07/14 06:33:19 {"match_count":463}
[loadgen] 2022/07/14 06:33:19 query 'tear': matched 463
[loadgen] 2022/07/14 06:33:20 query 'world': matched 728
[client] 2022/07/14 06:33:20 {"match_count":728}
[client] 2022/07/14 06:33:22 {"match_count":463}
[loadgen] 2022/07/14 06:33:22 query 'tear': matched 463
この時点では、サーバーからのメッセージを確認する必要があります。これで、サービスの分散トレース用に OpenTelemetry を使用してアプリケーションの計測を開始する準備が整いました。
サービスの計測を開始する前に、Ctrl+C キーを押してクラスタをシャットダウンしてください。
コマンド出力
...
[client] 2022/07/14 06:34:57 {"match_count":1}
[loadgen] 2022/07/14 06:34:57 query 'what's past is prologue': matched 1
^CCleaning up...
- W0714 06:34:58.464305 28078 gcp.go:120] WARNING: the gcp auth plugin is deprecated in v1.22+, unavailable in v1.25+; use gcloud instead.
- To learn more, consult https://cloud.google.com/blog/products/containers-kubernetes/kubectl-auth-changes-in-gke
- deployment.apps "clientservice" deleted
- service "clientservice" deleted
- deployment.apps "loadgen" deleted
- deployment.apps "serverservice" deleted
- service "serverservice" deleted
概要
このステップでは、環境で Codelab の資料を準備し、skaffold が想定どおりに実行されることを確認しました。
次のステップ
次のステップでは、トレース情報を計測するように loadgen サービスのソースコードを変更します。
4. Cloud Profiler エージェントの計測
継続的プロファイリングのコンセプト
継続的プロファイリングのコンセプトを説明する前に、まずプロファイリングのコンセプトを理解する必要があります。プロファイリングは、アプリケーションを動的に分析する(動的プログラム分析)方法の 1 つです。通常、アプリケーション開発中に負荷テストなどのプロセスで実行されます。これは、特定の期間中に CPU やメモリの使用量などのシステム指標を測定する単発のアクティビティです。プロファイル データを収集した後、デベロッパーはコードからデータを分析します。
継続的プロファイリングは、通常のプロファイリングの拡張アプローチです。実行時間の長いアプリケーションに対して短いウィンドウ プロファイルを定期的に実行し、大量のプロファイル データを収集します。次に、バージョン番号、デプロイゾーン、測定時間など、アプリケーションの特定の属性に基づいて統計分析を自動的に生成します。このコンセプトの詳細については、ドキュメントをご覧ください。
ターゲットは実行中のアプリケーションであるため、プロファイル データを定期的に収集し、統計データを後処理するバックエンドに送信する方法があります。これは Cloud Profiler エージェントであり、まもなくサーバー サービスに埋め込むことになります。
Cloud Profiler エージェントを埋め込む
Cloud Shell の右上にあるボタン
を押して、Cloud Shell エディタを開きます。左側のペインのエクスプローラから step4/src/server/main.go を開き、メイン関数を見つけます。
step4/src/server/main.go
func main() {
...
// step2. setup OpenTelemetry
tp, err := initTracer()
if err != nil {
log.Fatalf("failed to initialize TracerProvider: %v", err)
}
defer func() {
if err := tp.Shutdown(context.Background()); err != nil {
log.Fatalf("error shutting down TracerProvider: %v", err)
}
}()
// step2. end setup
svc := NewServerService()
// step2: add interceptor
interceptorOpt := otelgrpc.WithTracerProvider(otel.GetTracerProvider())
srv := grpc.NewServer(
grpc.UnaryInterceptor(otelgrpc.UnaryServerInterceptor(interceptorOpt)),
grpc.StreamInterceptor(otelgrpc.StreamServerInterceptor(interceptorOpt)),
)
// step2: end adding interceptor
shakesapp.RegisterShakespeareServiceServer(srv, svc)
healthpb.RegisterHealthServer(srv, svc)
if err := srv.Serve(lis); err != nil {
log.Fatalf("error serving server: %v", err)
}
}
main 関数には、Codelab パート 1 で行った OpenTelemetry と gRPC の設定コードがいくつかあります。ここで、Cloud Profiler エージェントの計測を追加します。initTracer() の場合と同様に、読みやすさのために initProfiler() という関数を記述できます。
step4/src/server/main.go
import (
...
"cloud.google.com/go/profiler" // step5. add profiler package
"cloud.google.com/go/storage"
...
)
// step5: add Profiler initializer
func initProfiler() {
cfg := profiler.Config{
Service: "server",
ServiceVersion: "1.0.0",
NoHeapProfiling: true,
NoAllocProfiling: true,
NoGoroutineProfiling: true,
NoCPUProfiling: false,
}
if err := profiler.Start(cfg); err != nil {
log.Fatalf("failed to launch profiler agent: %v", err)
}
}
profiler.Config{} オブジェクトで指定されたオプションを詳しく見てみましょう。
- サービス: プロファイラ ダッシュボードで選択して切り替えることができるサービス名
- ServiceVersion: サービス バージョン名。この値に基づいて、プロファイル データセットを比較できます。
- NoHeapProfiling: メモリ消費量のプロファイリングを無効にします
- NoAllocProfiling: メモリ割り当てプロファイリングを無効にします
- NoGoroutineProfiling: goroutine プロファイリングを無効にします。
- NoCPUProfiling: CPU プロファイリングを無効にします
この Codelab では、CPU プロファイリングのみを有効にします。
あとは、この関数を main 関数で呼び出すだけです。インポート ブロックで Cloud Profiler パッケージをインポートしてください。
step4/src/server/main.go
func main() {
...
defer func() {
if err := tp.Shutdown(context.Background()); err != nil {
log.Fatalf("error shutting down TracerProvider: %v", err)
}
}()
// step2. end setup
// step5. start profiler
go initProfiler()
// step5. end
svc := NewServerService()
// step2: add interceptor
...
}
go キーワードを使用して initProfiler() 関数を呼び出していることに注意してください。profiler.Start() はブロックされるため、別の goroutine で実行する必要があります。これでビルドの準備が整いました。デプロイの前に go mod tidy を実行してください。
go mod tidy
新しいサーバー サービスを使用してクラスタをデプロイします。
skaffold dev
通常、Cloud Profiler でフレーム グラフが表示されるまでに数分かかります。上部の検索ボックスに「profiler」と入力し、Profiler のアイコンをクリックします。

次のフレームグラフが表示されます。

概要
このステップでは、Cloud Profiler エージェントをサーバー サービスに埋め込み、フレーム グラフが生成されることを確認しました。
次のステップ
次のステップでは、フレーム グラフを使用してアプリケーションのボトルネックの原因を調べます。
5. Cloud Profiler のフレームグラフを分析する
フレームグラフとは
フレームグラフは、プロファイル データを可視化する方法の 1 つです。詳細については、こちらのドキュメントをご覧ください。概要は次のとおりです。
- 各バーは、アプリケーション内のメソッド/関数呼び出しを表します。
- 垂直方向はコールスタックです。コールスタックは上から下に伸びます
- 横方向はリソース使用量を示します。長いほど、リソース使用量が多いことを示します。
これを踏まえて、取得したフレームグラフを見てみましょう。

フレーム グラフの分析
前のセクションで、フレームグラフの各バーは関数/メソッドの呼び出しを表し、その長さは関数/メソッドのリソース使用量を表すことを学びました。Cloud Profiler のフレームグラフでは、バーが長さの降順で左から右に並べ替えられます。グラフの左上から確認を開始できます。

この例では、grpc.(*Server).serveStreams.func1.2 が CPU 時間の大部分を消費していることが明確に示されています。呼び出しスタックを上から下まで確認すると、時間の大部分が main.(*serverService).GetMatchCount で費やされています。これは、サーバー サービスの gRPC サーバー ハンドラです。
GetMatchCount の下に、regexp 関数(regexp.MatchString と regexp.Compile)が並んでいます。これらは標準パッケージに含まれているため、パフォーマンスなど、さまざまな観点から十分にテストされているはずです。しかし、この結果は、regexp.MatchString と regexp.Compile で CPU 時間リソースの使用率が高いことを示しています。これらの事実を踏まえると、regexp.MatchString の使用がパフォーマンスの問題に関連していると想定できます。関数が使用されているソースコードを読んでみましょう。
step4/src/server/main.go
func (s *serverService) GetMatchCount(ctx context.Context, req *shakesapp.ShakespeareRequest) (*shakesapp.ShakespeareResponse, error) {
resp := &shakesapp.ShakespeareResponse{}
texts, err := readFiles(ctx, bucketName, bucketPrefix)
if err != nil {
return resp, fmt.Errorf("fails to read files: %s", err)
}
for _, text := range texts {
for _, line := range strings.Split(text, "\n") {
line, query := strings.ToLower(line), strings.ToLower(req.Query)
isMatch, err := regexp.MatchString(query, line)
if err != nil {
return resp, err
}
if isMatch {
resp.MatchCount++
}
}
}
return resp, nil
}
regexp.MatchString が呼び出される場所です。ソースコードを読むと、関数がネストされた for ループ内で呼び出されていることがわかります。そのため、この関数の使用が正しくない可能性があります。regexp の GoDoc を調べてみましょう。

ドキュメントによると、regexp.MatchString は呼び出しごとに正規表現パターンをコンパイルします。リソースの大量消費の原因は次のとおりです。
概要
このステップでは、フレームグラフを分析してリソース消費の原因を想定しました。
次のステップ
次のステップでは、サーバー サービスのソースコードを更新し、バージョン 1.0.0 からの変更を確認します。
6. ソースコードを更新してフレーム グラフの差分を取得する
ソースコードを更新する
前のステップでは、regexp.MatchString の使用量がリソースの大量消費に関係しているという前提で作業しました。この問題を解決しましょう。コードを開き、その部分を少し変更します。
step4/src/server/main.go
func (s *serverService) GetMatchCount(ctx context.Context, req *shakesapp.ShakespeareRequest) (*shakesapp.ShakespeareResponse, error) {
resp := &shakesapp.ShakespeareResponse{}
texts, err := readFiles(ctx, bucketName, bucketPrefix)
if err != nil {
return resp, fmt.Errorf("fails to read files: %s", err)
}
// step6. considered the process carefully and naively tuned up by extracting
// regexp pattern compile process out of for loop.
query := strings.ToLower(req.Query)
re := regexp.MustCompile(query)
for _, text := range texts {
for _, line := range strings.Split(text, "\n") {
line = strings.ToLower(line)
isMatch := re.MatchString(line)
// step6. done replacing regexp with strings
if isMatch {
resp.MatchCount++
}
}
}
return resp, nil
}
ご覧のとおり、正規表現パターンのコンパイル プロセスが regexp.MatchString から抽出され、ネストされた for ループの外に移動されました。
このコードをデプロイする前に、initProfiler() 関数のバージョン文字列を更新してください。
step4/src/server/main.go
func initProfiler() {
cfg := profiler.Config{
Service: "server",
ServiceVersion: "1.1.0", // step6. update version
NoHeapProfiling: true,
NoAllocProfiling: true,
NoGoroutineProfiling: true,
NoCPUProfiling: false,
}
if err := profiler.Start(cfg); err != nil {
log.Fatalf("failed to launch profiler agent: %v", err)
}
}
では、その仕組みを見てみましょう。skaffold コマンドを使用してクラスタをデプロイします。
skaffold dev
しばらくしてから、Cloud Profiler ダッシュボードを再読み込みして、その様子を確認します。

バージョンを "1.1.0" に変更して、バージョン 1.1.0 のプロファイルのみが表示されるようにしてください。GetMatchCount のバーの長さが短くなり、CPU 時間の使用率が低下したことがわかります。

単一バージョンのフレーム グラフを見るだけでなく、2 つのバージョン間の差分を比較することもできます。

[Compare to] プルダウン リストの値を [Version] に変更し、[Compared version] の値を元のバージョンである [1.0.0] に変更します。

次のようなフレームグラフが表示されます。グラフの形状は 1.1.0 と同じですが、色分けが異なります。比較モードでは、色の意味は次のとおりです。
- 青: 削減された値(リソース消費量)
- オレンジ: 獲得した値(リソース消費量)
- Gray: どちらとも言えない
凡例を踏まえて、関数を詳しく見てみましょう。拡大するバーをクリックすると、スタック内の詳細が表示されます。[main.(*serverService).GetMatchCount] バーをクリックしてください。棒にカーソルを合わせると、比較の詳細が表示されます。

合計 CPU 時間が 5.26 秒から 2.88 秒に短縮されたことがわかります(合計は 10 秒 = サンプリング ウィンドウ)。これは大きな改善です。
これで、プロファイル データの分析からアプリケーションのパフォーマンスを改善できるようになりました。
概要
このステップでは、サーバー サービスを編集し、Cloud Profiler の比較モードで改善を確認しました。
次のステップ
次のステップでは、サーバー サービスのソースコードを更新し、バージョン 1.0.0 からの変更を確認します。
7. 追加の手順: Trace ウォーターフォールで改善を確認する
分散トレースと継続的プロファイリングの違い
Codelab のパート 1 では、リクエストパスのマイクロサービス全体でボトルネック サービスを特定できること、特定のサービスでボトルネックの正確な原因を特定できないことを確認しました。このパート 2 の Codelab では、継続的プロファイリングを使用すると、コールスタックから単一のサービス内のボトルネックを特定できることを学びました。
このステップでは、分散トレース(Cloud Trace)のウォーターフォール グラフを確認し、継続的プロファイリングとの違いを確認します。
このウォーターフォール グラフは、クエリ「love」を含むトレースの 1 つです。合計で約 6.7 秒(6,700 ミリ秒)かかっています。

これは、同じクエリの改善後です。ご覧のとおり、合計レイテンシは 1.5 秒(1,500 ミリ秒)になり、以前の実装から大幅に改善されています。

ここで重要なのは、分散トレースのウォーターフォール チャートでは、スパンをあらゆる場所に計測しない限り、コールスタック情報が利用できないことです。また、分散トレースはサービス間のレイテンシにのみ焦点を当てますが、継続的プロファイリングは単一サービスのコンピューティング リソース(CPU、メモリ、OS スレッド)に焦点を当てます。
別の側面では、分散トレースはイベントベースであり、継続的プロファイルは統計的です。トレースごとにレイテンシ グラフが異なり、レイテンシの変化の傾向を取得するには、分布などの別の形式が必要です。
概要
このステップでは、分散トレースと継続的プロファイリングの違いを確認しました。
8. 完了
OpenTelemery で分散トレースを作成し、Google Cloud Trace でマイクロサービス全体のリクエスト レイテンシを確認しました。
拡張演習については、次のトピックを各自で試してください。
- 現在の実装では、ヘルスチェックによって生成されたすべてのスパンが送信されます。(
grpc.health.v1.Health/Check)Cloud Trace からこれらのスパンを除外するにはどうすればよいですか?ヒントはこちらをご覧ください。 - イベントログとスパンを関連付け、Google Cloud Trace と Google Cloud Logging での動作を確認します。ヒントはこちらをご覧ください。
- 一部のサービスを別の言語のサービスに置き換え、その言語の OpenTelemetry で計測してみます。
また、この後でプロファイラについて学習する場合は、パート 2 に進んでください。その場合は、以下のクリーンアップ セクションをスキップできます。
クリーンアップ
この Codelab の後、Kubernetes クラスタを停止し、プロジェクトを削除して、Google Kubernetes Engine、Google Cloud Trace、Google Artifact Registry で予期しない料金が発生しないようにしてください。
まず、クラスタを削除します。skaffold dev でクラスタを実行している場合は、Ctrl+C キーを押すだけです。skaffold run でクラスタを実行している場合は、次のコマンドを実行します。
skaffold delete
コマンド出力
Cleaning up... - deployment.apps "clientservice" deleted - service "clientservice" deleted - deployment.apps "loadgen" deleted - deployment.apps "serverservice" deleted - service "serverservice" deleted
クラスタを削除したら、メニュー ペインから [IAM と管理] > [設定] を選択し、[シャットダウン] ボタンをクリックします。

次に、ダイアログのフォームにプロジェクト ID(プロジェクト名ではない)を入力し、シャットダウンを確認します。