こんにちは!Wantedly Visitの推薦基盤チームで三週間インターンをしていた大山です。
今回のインターンで、過去の特定の日のgRPCリクエストを模倣して同じリクエストを再び投げるリクエスト再現ツールを作ったのでここで紹介させていただきます。
何を作ったか
- gRPCリクエストしたいサービス・メソッド・日時が書かれたjsonファイルを読み込んで、それに従ってgRPCのリクエストを送るツール (request-repeater)
- BigQuery上の推薦基盤サービスvisit-recommendation-projectのアクセスログから上記jsonファイルを作成するツール (bq-to-request-file)
以下、上記括弧内の名称をそれぞれのツールの呼称とします。
背景
このようなツールを作るにあたって以下のような背景がありました。
- 本番環境と同じリクエストを用いてqa環境に対して負荷をかけたい
- 推薦基盤サービスvisit-recommendation-projectは、エンドポイントが少ない代わりにパラメータがとても複雑なので全てのケースをテストで試すことが難しいので本番と同じリクエストパターンを用いてチェックしたい
request-repeater
以下、request-repeaterの詳細について説明します。
最初にも書いた通り、これはgRPCリクエストしたいサービス・メソッド・日時が書かれたjsonファイルを読み込んで、それに従ってgRPCのリクエストを送るツールです。
任意のgRPCサービスで用いることができるよう、OSSを意識して設計しました。
まず始めに、以下が送りたいリクエストが記されたjsonファイルの例です。
[
{
"method":"/grpc.service1/grpcMethod1",
"body":"{}",
"schedule":"2021-04-01T15:00:00.082507Z"
},
{
"method":"/grpc.service2/grpcMethod2",
"body":"{
/"userId/":/"000001/",
/"per/":/"100/",
/"page/":/"1/"
}",
"schedule":"2021-04-01T15:00:00.175275Z"
},
...
]
jsonの形式としては、"method", "body", "schedule"の3つのキーを持った辞書のリストを想定しています。
各キーに関して簡単に説明します。
- "method":送りたいgRPCサービスとメソッドを"/"で区切ったもの
- "body":”method” で指定したgRPCメソッドでおくるパラメータ
- "schedule":このツールを用いてリクエストを送りたいタイムスタンプ
上記例のようなjsonファイルをあらかじめ用意して、コマンドラインオプションにて対象のgRPCサーバーのホスト・ポートとjsonファイルのパスを指定することでリクエストが開始されます。
実装詳細
以下が簡単な実装方針です。
- jsonをunmarshalしてGoのスライス(可変長配列) として保持
- (cronのように定期実行を行える) time.Tickerを用いて毎秒、スライスの中でその1秒間に送るべきリクエストをgoroutineをもちいて並列に送る
方針として数秒の時間の誤差は許容するため2のような実装方針を採用しました。
また2のgRPCリクエストの送信に関してはgRPCのServer Reflectionを用いました。
一般的なgRPC通信では、送受信するパラメータのスキーマを定義したprotoファイルをサーバー・クライアント両方で共有し、そのprotoファイルから自動生成したメソッドを用いて通信を行います。ただし今回このツールでは任意のgRPCサーバーを対象にリクエストを送ることを想定しているため毎回サーバー・クライアント間でprotoファイルを共有するというような作業は省きたいです。
Server Reflectionはクライアントがprotoファイルをもっていなくても対象サーバーにアクセスすることでprotoファイルと同等の内容を取得できるという機能です。今回はgRPCのこの機能を用いて任意のサーバーへのアクセスをprotoファイルなしで実現しています。ただし、このServer Reflectionは言語ごとに実装の有無は異なるため注意が必要です。
具体的な実装に関しては、Server Reflection機能を用いて実装されているgRPCクライアントツールのgiroやevansを参考にしました。(ちなみにgiroは自分のメンターの一條さん (@rerost) が作成したツールです!)
giroに関しては以下の記事も参考にしました。
またrequest-repeaterでは対象サーバーは1つとしているのでgRPCのコネクションは1つのみ張ってそれを繰り返し利用しています。
コネクションがなんらかの理由で切断されても自動的に再接続を試みます。
課題
ある一定数数以上のリクエストとなると一つのgRPCコネクションでは全てリクエストできないため、複数のコネクションを張ることを考える必要があります。
また今回json上のリクエストは一度にすべて読み込んでメモリに乗せるためリクエスト量はメモリに乗り切る量を想定しています。
まとめ
qa環境に実際のリクエストを流して動作を検証したいという背景から過去のリクエストを模倣するgRPCリクエスト再現ツールを作りました。
実装に関してはgoroutineとgRPCのServer Reflection機能を使って実現しました。
期間としては三週間お世話になりましたが、得るものがとても多くとても充実していて一瞬に感じられました!個人的にはメンターの方が強くてご迷惑をおかけしながらもサポートしていただけて非常に貴重な経験になったなと感じております。また一週間現地出社していたのですが、おしゃれなお店が並ぶ街の中のおしゃれなオフィスでとても感動しました笑 本当にお世話になりました!
余談ですが、今回初めてGoを書くにあたって、jsonからリクエストを読み込む際にスタックメモリにすべてのリクエストが乗るのか?グローバル変数もしくはmalloc等でヒープにメモリ領域を確保しないといけないのか?という場面がありました。ですがGoではローカル変数も関数の戻り値として使う場合などではヒープに確保されるようで、そのような心配は杞憂に終わりGoのコンパイラは優秀だなと感じました。
以上、リクエスト再現ツールを実装した話でした。ありがとうございました。