Shadow Testingで実現する安心な機能リプレイス
ログラスの龍島(@hryushm)です。最近は家でミニトマトを育てていましたが、この暑さで枯れてきてしまったのが悲しいです。
本記事では、参照系機能の大規模リプレイスを行った際に、Shadow Testingを導入して得られた知見を共有します。上手く活用できれば非常に強力なテスト手法だと感じたので参考になれば幸いです。
Shadow Testingとは
Shadow Testingは、本番環境のトラフィックを新旧両方のシステムに流し、結果を比較検証する手法です。ユーザーへのレスポンスは旧システムから返すため、エンドユーザーへの影響なく新システムの動作を実環境で検証できます。「Dark Launch」や「Traffic Shadowing」とも呼ばれます。
なぜShadow Testingが必要だったのか
今回、アプリケーションの分析機能のパフォーマンス改善のため、Query Serviceを根本的に異なる仕組みで再実装することにしました。このプロジェクトでは以下の2つの大きな不安がありました。
1. 処理結果の同一性への不安
参照、分析機能という性質上、ユーザーのリクエストパターンは非常に複雑でした。単体テストや結合テストである程度の品質は確保できますが、「エッジケースで異なるレスポンスを返していないか?」という不安が残ります。
2. パフォーマンス改善への不安
そもそもの目的がパフォーマンス改善でしたが、以下の点が心配でした。
- 意図したケースでパフォーマンス改善するか?
- 特定のパターンで大きく悪化してしまうものはないか?
これらの不安への対処としてShadow Testingが有効だと考え、トライしました。
Shadow Testingの実践
システム構成イメージ

今回のShadow Testingを実現する構成のポイントは下記です。
- 旧Query Serviceと同一のリクエストを新Query Serviceにも送信
- ユーザーへのレスポンスは旧Query Serviceのものを使用
- 新旧両方でログを出力し、実行結果とパフォーマンスを計測
- ダッシュボードで差分を可視化
実際どうだったか
処理結果の検証

ダッシュボードイメージ
実装中からShadow Testingを開始したことで、結果が一致していないリクエストが多い状況から少なくなっていく様を可視化することができました。
未実装な機能が多いうちは半分以上のリクエストが一致しなかったため、詳細な分析は行わず、一致するリクエストが大半を占めるようになった段階で一致しないリクエストを分析。認識できていないものがないかを確認できました。
この過程で旧Query Serviceの軽微な不具合も発見できました。エッジケースで旧Query Serviceが正しいハンドリングをできておらず、新Query Serviceでは正しく処理できていたため差分が発生したのです。このような副次的な発見もShadow Testingの価値の一つです。
一致しないものを0にできれば既存のリクエストがカバーできていることの安心感はかなり大きいです。
また一致しないものが減っていくことでリリースに向けて着実に進められている感覚をチームが持てたこと意図せず感じた効果でした。
パフォーマンス検証

ダッシュボードイメージ 青が旧、赤が新、旧QueryServiceのレイテンシ順
旧Query Serviceに対して大きく性能改善していることを確認することができました。事前に性能検証はしていたものの、実際のトラフィックで問題ないことが確認できた安心感は大きかったです。実装中の早期からこの傾向を確認できていたため、実装しきれば必ず改善効果がある確信を持ちながら進めることができました。
Shadow Testingが機能した理由
Shadow Testingが特に効果的だった最大の理由は、対象が参照系の機能だったことです。
- データベースへの変更が発生しない
- 新Query Serviceを並行実行してもデータ不整合が起きない
もし更新系の機能だった場合、検証用のデータベースを用意するなどの工夫が必要なため、投資対効果が悪化する可能性が高いです。
導入時の注意点
実践からShadow Testingを導入する際の注意点も得られました。
差分の判定
「何をもって処理の結果が一致しているか」の基準設定が重要です。例えば下記のような微妙な差異が生まれる可能性があります。
- 浮動小数点の有効桁数
- ソート順の違い
- タイムスタンプの微妙なズレ
今回もログに残す有効桁数やソート順(QueryServiceの仕様としては不定)が一致しておらず、分析時に苦労しました。
また処理結果を全てログに残すことは容量の都合から難しい場合もあります。その場合はハッシュ値を全体比較用に残しつつ、数値配列であればmin, maxなどの統計情報を残すことでどのように一致していないのかを確認しやすくするなどの工夫ができます。
万能なテストではない
Shadow Testingはリグレッションテストの一種であり、良くも悪くも実施中に受け取ったリクエストにおいて新旧での差分がないことの確認しかできません。
そのタイミングでリクエストされなかったパターンは保証できないため、滅多にリクエストがないが想定されるパターンがある場合は他のテスト手法でカバーされる必要があります。
今回の実践でも旧Query Serviceで実施されているアサーションベースなテストを新Query Serviceでも通した上でShadow Testingも実施することで補完しています。
まとめ
大規模なリファクタリングやリプレイスを行う際、Shadow Testingは非常に有効なテスト戦略です。特に参照系の機能では、実装コストに対して得られる安心感が大きく、投資対効果の高い手法と言えます。
「本当に新旧で同じなのか?」という不安を払拭し、自信を持ってリリースを迎えられるようにしましょう!
Discussion