【書き起こし】fake clock microservice -時刻をハックしてテストする方法- – vvakame / Hiraku Nakano / Hiroyuki Tanaka【Merpay & Mercoin Tech Fest 2023】

Merpay & Mercoin Tech Fest 2023 は、事業との関わりから技術への興味を深め、プロダクトやサービスを支えるエンジニアリングを知ることができるお祭りで、2023年8月22日(火)からの3日間、開催しました。セッションでは、事業を支える組織・技術・課題などへの試行錯誤やアプローチを紹介していきました。
この記事は、「fake clock microservice -時刻をハックしてテストする方法-」の書き起こしです。

@vvakame:「fake clock microservice -時刻をハックしてテストする方法-」というセッションを始めたいと思います。よろしくお願いします。

@vvakame:僕は@vvakameと申します。メルペイ Solutions Teamで社内ツールなどを作っている関係上、いろいろなマイクロサービスの時刻に関わる設定を変更した上でデータを作りたいというニーズがある当事者の1人でございます。

@hiraku:スライドの真ん中に写っている、@hirakuです。メルペイではCredit Designというチームに所属してまして、メルペイのあと払いや与信関係のサービスのバックエンドエンジニアをやっております。

@tanaka0325:@tanaka0325と申します。2021年1月にメルペイに入社して、今はCredit Design Teamのバックエンドエンジニアをやっています。日々信用を創造して、なめらかな社会を作っています。よろしくお願いします。

@vvakame:おふたりはCredit Design Teamからということで、メルペイの中でも最も業務ドメインが複雑と言われるCredit Design Teamが、いかにして今まで苦しんできたかという話を今日していこうかなという感じですね。

本セッションの構成は、最初に前提となる時刻とテストの問題について解説をし、それに対する解決策・fakeclock serviceについてご説明して最後に我々3人でトークセッションをしていこうと思います。まずは前提の共有からやっていきたいと思います。

@hiraku:最初に、まずこの問題のことを前提知識としてインプットしていただければと思います。現在時刻に対応するロジックはあちこちにあると思います。

これはメルペイのあと払いのヘルプページから取ってきた画像です。8月1日に購入して、8月31日に締めがあって、9月30日までに支払うという2ヶ月間のライフサイクルがあるみたいなことが、金融領域を扱う我々メルペイにとってありがちなんです。これを現実時間で、もしテストしようとしたら2ヶ月間のQAが期間が必要になってしまいます。

修正したら、すぐリリースしたいので、素早く効率的にテストしようと思うと、「時刻をいじってしまおう」と考えるわけです。

8月1日に時刻をいじった状態で購入をテストして、9月1日に変更して請求のバッチをテストします。9月20日を超えて10月1日になると、延滞状態になりますので、その状態をテストするというイメージです。

これは、実際にメルペイの社内でもあちこちで実装されていて、テスト環境限定のAPIがあります。debug.SetNow APIが各所に実装されており、日付時刻を設定してあげると、その通りに時刻を固定できます。

この方法には、いくつか実は問題点がありました。

まず一番大きいのが、弊社はマイクロサービスアーキテクチャを採用していて、マイクロサービスが複数あることがすごく問題になっています。一つの機能を実現するのに何個もマイクロサービスが関わっているので、それぞれに対してSetNowを叩いていかないと求めた状態になりません。また、設定漏れも起きがちです。そして、それぞれのマイクロサービスでAPIの実装するのも、無駄だという問題がありました。

もし一つだけ設定を忘れた場合、デバッグがとても大変になります。

本番だと絶対にあり得ないのでそういうことを配慮したコードはなかなか書いてないんです。他のマイクロサービスと時間がずれてるという状態が、いかなる問題を引き起こすのかは、起きてみないとわからないレベルです。

昔あったのが、スナップショットのレコードを保存しようとしてたんだけど、時間が完全に固定されていたので、2件挿入しようとすると、レコードが重複してエラーになってしまったことがあります。

もう一つの問題が、テスト環境全体に対して時刻を設定しているため、テスト担当者が複数人いて、同時に複数のテストケースを実行しようとすると、Aさんが8月1日に、その直後にBさんが8月15日に日付を上書きすると、2人とも困ってしまいます。

これを並行してテストするために、テスト環境をそれぞれ人数分用意し、頑張って実行しています。環境をいっぱい用意するのは大変ですし、無駄が多いなという問題があります。

@vvakame:Aさんの環境は安定しているけど、Bさんの環境は不安定で、これはバグなのか迷ってしまうこともあります。

@hiraku:まとめていくと、この辺を求めているわけです。並行してテストしたいので、一度の操作で、各マイクロサービスの時刻を一気に変更したいし、環境ごとではなくてリクエストごとに時刻を操作したい。環境をいっぱい用意するのではなく気軽に時刻を操作できるようになりたいということです。

そこで我々の方でいろいろ検討し、タイトルにもあったfake clock serviceに行き着きました。

それまでにあがった検討案として、最初が「環境をいっぱい用意する」の亜種という形で「必要なタイミングでマイクロサービス群を複製する」という案がありました。ボタンを押すと、マイクロサービス群が一気に立ち上がるような環境があればいいんじゃないかということは、アイディアとしてありました。これは本番環境にすごく近い状態にできますし、安定してるのではないかと考えました。しかし、環境を1から立ち上げようとするとどんなに最適化しても数分はかかってしまうので、なかなか気軽にテストできる状況にはなりませんでした。

もう一つが、「メタデータとして伝播させる」ということ。

マイクロサービスのサービス内容は、たいてい何らかのリクエストから始まります。HTTPのリクエストだったらHTTPヘッダー、そういったメタデータ部分に、現在時刻の情報を含め、最初に受け取ったサービスがメタデータを後ろのマイクロサービスに伝播させれば、全てのマイクロサービスで、リクエスト単位で時刻設定できるんじゃないかという考えに至りました。

これでうまくいくと思ったのですが、1個問題がありました。社外システムが途中に挟まっているというケースがあったのです。

我々だけで作っていない部分がいくつかあります。メルペイの社内であれば融通が利くのですが、社外のシステムにおいてはそうはいかず、社外システムのコールバックが本当の現在時刻で返ってきてしまいました。

あと社内であっても、どこかのサービスが工数や優先度の問題で、メタデータ伝播の実装がなかなかできないとなると、裏側のサービスがいつまでたってもメタデータを受け取れないという問題がありました。部分的に導入していくというのが厳しいという問題になりましたね。

そして出てくるのが、マイクロサービスを立ててしまおうという案です。マイクロサービスアーキテクチャの問題はマイクロサービスを立てれば解決するというアイディアです。

真ん中にfake clock serviceという新しいマイクロサービスを1個立てます。これが、全ての時刻のマスターのように振る舞います。ただ、一個のサービスがマスタークロックを持ってます、というだけでは他のサービスが全部同じ時刻に切り替わってしまって並列度が上げられないので、時刻の管理をユーザー単位にしようということで、ユーザーIDをキーにして時刻を取れる仕組みにしました。ユーザーさえ違えば、別の時刻帯をそれぞれ管理できるようにしました。

大体良さそうだったんです。ユーザー単位で大体テストしているので、並列度が欲しければ、ユーザーをいっぱい作ってしまえばいいという話です。

メタデータの問題だと外部システムが途中に挟み込んであるので困ったことがありましたが、こっちの案はもし外部システムから戻ってきたタイミングで、改めてfake clock serviceに問い合わせしに行くと時刻を強制的に復元して、また元のロジックに戻っていけるというメリットがあります。

また、外部システムと同じで途中に未実装のサービスがあっても、導入済みサービスだけでも恩恵がある状態を作れるので、部分的に導入しやすいという面もあります。

ただ、問題としては、こんなサービスを本番には作るわけにはいかないので本番環境と構成が異なります。また、このfake clock serviceがテスト環境限定の単一障害点となり、これが落ちると全部テスト環境がうまく動かなくなってしまうというデメリットがあります。

現状は、導入を進めている最中です。

今ちょうどこれから各マイクロサービスに導入していくフェーズです。運用が開始されてからの知見が実はないんです。ステータスとしてはSDKとマイクロサービス本体が実現されているのと、user-tkoolに関しては稼働しています。

@vvakame:user-tkoolとは、社内用のデバッグ操作をSlackコマンドでできる便利なサービスです。オプションを設定するインターフェースはすでに作ってあるというイメージです。

参考記事:テスト用お客さまデータ作りツール user-tkool の近況

@hiraku:user-tkoolの中でいろいろ時刻をセットして、一つだけ購入したことのあるユーザーを作ることもできるので、内部的に時刻を操作する部分を使っています。
ただ、マイクロサービスに導入するときに、検証をするのが大変なので、導入が止まっているのが現状です。

@vvakame:ここで、質問をいただいています。「内部のアクセスが1hop増えるからレスポンスが遅くなる可能性があるのではないですか」という質問がありました。

これについてはとりあえず機能を達成してQAの工数を圧縮できるのがまずは優先で、将来的にそうなったらメタデータ型との複合で、サービスに設定値を取りに行かなくてもメタデータにあればすぐ返せるという実装も考えています。

@hiraku:負荷テストで性能を測定したい場合は、テスト環境であってもfake clock serviceの設定を切ることもできるのでその辺で調整していこうかなと考えています。そもそも一度の負荷試験で日付をまたぐことはないと思います。

@vvakame:次の質問です。「マイクロサービスは、現在時刻を取るときに常にユーザーIDを渡していますか?」。そうですね、マイクロサービス間の通信には内部的なアクセストークンが発行され、そこにuser customer IDが常に含まれているので、gRPCインターセプターなどのLayerでアクセス元のIDを確認できるようになっています。

次の質問「各マイクロサービスはどのタイミングで fakeclockにリクエストするんでしょう?」というのも大体gRPC インターセプターでサーバーにリクエストを受け取ったタイミングで1回だけ取りに行きます。

@tanaka0325:今はユーザーIDでとりあえず始めていますが、ユーザーIDがキーじゃないサービスもあるかも知れません。一意のキーであれば何でも良いので、今後はその対応もしていこうかなという話は出ています。

@vvakame:ここからはパネルディスカッションに入ります。

トピックはこちらです。まずは、「今どこまで進んでいるのか」。先ほど話がありましたね。

言い訳をさせていただくと、Merpay & Mercoin Tech Festのスピーカーが募集されたタイミングで、「fake clockはどうですか」と打診されたとき、その頃には最低一つか二つぐらいのマイクロサービスには導入が終わってるだろうと高を括っていましたが、案外進みませんでした。

@hiraku:マイクロサービス本体は@vvakameさんが入ってきてくれてからは、サクサク進んでそれは問題なかったんですけど、導入側が大変でした。

@vvakame:次のテーマ「導入時のQAどうするのか問題」ともつながります。一応一つのマイクロサービスに対して導入のプルリクエストは作りましたが、これをマージするときにどうやってQAをするのかという課題が持ち上がりました。

@tanaka0325:単純に考えると影響範囲があるところはQAしたいという気持ちがあるので、どこが影響範囲なんだというと、時刻は取り扱うところはありとあらゆる箇所に散らばっているので極論全部という話になってしまいます。
それを全部やると、いつまでたってもリリースできないので、これは何か考えないとなということで今議論しています。

@vvakame:SetNowを呼ぶところは構造上は少ないんですけど、Goのtime.Now()のようなものはコード上にたくさん散らばっているので、その無影響確認と、本当に時刻が変更できているという影響確認の両方が難しいです。

@hiraku:過去のQAに関しては、当然時刻操作しながらするQAシナリオがいっぱいあったので、元のマイクロサービスごとに実装されていたfakeclockのシステムを使っていたのですが、それを置き換える形になるので、テスト自体にも影響があります。
書き換えたコード自体もテスト対象だし、テスト自体も書き換えないといけない。単純な無影響確認とも言い切れません。

@vvakame:品質担保は我々金融の決済領域なので、非常に神経質に行っています。QAは工数が厳しそうですよね。とはいえ、fakeclock serviceの導入はQAの効率化のためにもQAのエンジニアの方々からも切望されているので、何とか前に進まないといけませんね。

@tanaka0325:ここで、関連の質問をいただきました。「進まなかった理由はありますか」という質問です。今話した部分で、金融だからQAは大変なのと、テスト自体も直すという作業があること、そしてこれとは別に今まで通りの施策は進んでいて、その作業もありつつ、プラスアルファでこのような作業を行うという点で、バランスをとっていくことが難しかったからです。

@hiraku:導入に関しても、プルリクエストで一気に書き換えてリリースするのではなく、もう少しプロジェクト的に考えて、少しずつ分割してやっていかないと厳しいということで、作戦を変更して進めています。

@vvakame:ではマイクロサービスとE2Eテストについてはいかがですか?E2Eテストがしっかり揃っているとやりやすいけど、でもE2Eをいっぱい書くためには、便利なfake clock serviceが必要という問題があります。

@tanaka0325:厳しいですね。今もE2Eテストはあって、fake clock serviceの前はどうやっていたかというと、環境が人数ごとにあったように、E2E用にもありました。とあるサービスによっては、fake clockで時刻をいじるためだけに複製した環境が何十個もあったりしました。
環境をメンテナンスするだけでも大変だったので、E2E観点でもマイクロサービスは早く移行したいという気はしています。

@hiraku:逆にE2Eテストがコードベースで仕上がっているマイクロサービス機能群に関しては、あちこちでSetNowを叩きまくらないといけない件に関しても、そんなに困りません。そのため、優先度を下げてもいいという温度感になっています。
手動テストがメインな部分はE2Eテストが欲しいですが、手動テストがメインだと、こういう置き換えがとてもしづらいというデッドロックがあります。

@vvakame:続いて、グループ全体から見た位置づけについて話しますか。
グループ全体から見た位置づけとしては、メルペイの特にCredit Design Teamが非常に複雑なので、Credit Design Teamの要求を満たせるサービスであれば、グループ全体の要求を満たせる可能性が高いというコメントをもらったことがあって、納得しました。

ただこれが全体に広がるかは、Credit Design Teamのユースケースをケーススタディとしてちゃんと使えるかが関わってきます。

@tanaka0325:あと払いは、絶対に時刻が関係するので、Credit Design Teamが一番使うと思います。メルペイ自体の機能が増えてきたり、複雑度が増してきて、他のマイクロサービスがCredit Design Teamに依存したり、時刻に関連するマイクロサービスができたりしていて、今後はより他のチームでも必要性が増してくると思います。なるべく早くCredit Design Teamで成功事例を作って、他のチームでも使えるように広げていきたいです。

@vvakame:最後に、質問をいただいています。「修正したNowから徐々に時間が経過してほしいという、相対的な時間指定ができるようになりますか?」ということです。これは今回の実装からできるようになりました。

続いて、「実装優先を決断するのはどなたですか?」という質問をいただいています。Credit Design Teamはどういう感じですか。

@hiraku:意見を出すという意味では、みんな関わってはいますよね。

@tanaka0325:優先度づけのときに事前にエンジニアリングのヘッドや、PMのヘッドなど、いろいろな人が集まって各状況を整理してみんなで優先度を決めるので、特段誰かが決めるというよりは、割とみんなで決めることが多いです。

@vvakame:fakeclock serviceのような新しい解決手段を提案したり考えたりするのは、メルカリグループの場合はボトムアップ的に行っています。そういう意味ではこういった新サービスを作ろうというのは我々で決断して勝手に実行します。

我々はこういったことを一緒にやってくださる仲間の募集しています。「新しいソリューションを考えたい」という方がいらっしゃったら、ぜひ応募してください。

最後の締めとして、新しくこういった時刻系の実装をしなければいけない人たちに対して一言ずつアドバイスをいただけますか。

@tanaka0325:DBのカラムでcreated_atなどのシステム時刻が自動で入るものがよくあると思うのですが、あれはシステム時刻のためのもので、ロジックにあれを使うと、時刻を操作したいときに操作できなくなってしまうんです。

その場合は専用のカラムを準備して、アプリケーション側で操作できるようにしておくことは大事だと思います。

@hiraku:後から導入しようとするとすごい大変だぞということを伝えたいです。早い段階で、時刻操作の問題が今後どんどん厳しくなっていくから、早めに解決手段をとりましょうという判断ができていたら良かったと思います。

@vvakame:僕からの皆さんのアドバイスとしては、マイクロサービスをやらないと組織がどうしようもならなくなるまでは、モノレポや一つのリポジトリでやった方が時刻操作も楽なので、マイクロサービスにしないで良いのであればマイクロサービスにするのはやめましょうということです。

以上、fakeclock serviceについての発表とパネルディスカッションでした。ありがとうございました。

  • X
  • Facebook
  • linkedin
  • このエントリーをはてなブックマークに追加