大規模なシステムにおいてオブザーバビリティを高めるということは、毎秒数百万のスパンとトレース、メトリクスを含む大量のテレメトリーデータストリームを処理することを意味します。このデータにより、New Relicは顧客にアプリケーションとインフラストラクチャのパフォーマンスに関するリアルタイムの洞察を提供することができます。しかし、その規模でリアルタイムにデータを取り込んで処理するには、特に分散トレーシングパイプラインの中核となる Redis などの高性能システムが関係する場合は、コストがかかります。

分散トレーシングは、複数のサービスにわたるリクエストを追跡し、分散システム全体にわたるエラーやパフォーマンス低下の根本原因を見つけるのに役立ちます。当社の分散トレーシングパイプラインチームは、複数のエージェントからのトレースデータを取り込み、変換、接続、要約するシステムを管理します。昨年、チームは全体的なアーキテクチャを簡素化し、運用の複雑さを軽減する新しいパイプラインを導入しました。再設計により、古いサービスを廃止し、責任の集約、最も重要な点として、 Infinite Tracing標準の分散トレーシングの両方を単一の統合パイプラインを通じて実行できるようになりました。その結果、よりクリーンで保守性と信頼性の高いシステムが実現しました。しかし、導入直後に、Redis のストレージとネットワークコストが大幅に増加したことに気付きました。

New Relicでは、自分たちが構築したすべてのものを自社プラットフォームで監視しています。そのため、コストが急増したとき、私たちはNew Relic自体を活用してデータを詳しく調べることにしました。その後、2段階の最適化の取り組みが続きました。まず、Redisクラスターをアップグレードして適切なサイズに設定することでRedisインフラストラクチャを調整し、次に、圧縮とメモリの使用をより効率的にするためにデータの保存方法を再考しました。

このブログでは、そのプロセスを詳しく説明します。何を変更し、何を学んだか、そしてそれらの変更によってどのようにRedisのコストを半分以上削減し、全体的なパフォーマンスと拡張性を向上させたのかについて説明します。

インフラストラクチャの最適化

コストの急上昇に初めて気づいたとき、私たちはまずインフラストラクチャレベルでの効率性の向上に取り組みました。私たちは、Redisの環境をパフォーマンスとコストの両面で可能な限り最適化したいと考えました。

私たちは直ちに2つの措置を講じました。

  • より優れたインスタンスタイプに移行する

    Redis クラスターを最新バージョンにアップグレードし、インスタンスタイプをm6からr6gに移行しました。どちらのインスタンスタイプもARMベースのEC2インスタンスの同じ世代に属していますが、r6gはメモリが最適化されています(vCPUあたり8GBのRAM、m6gでは4GB)。新しいパイプラインを数か月間実行した後、メモリ使用率に比べてCPU使用率が非常に低いことがわかりました。m6からr6gに切り替えることで、スループットを犠牲にすることなくクラスター内のノード数を削減でき、データ量が多くてもシンプルなRedisワークロードにより適合するようになりました。この変更は、Redisのバージョンをアップグレードする絶好の機会でもあり、Redis 7.xで強化された安定性と運用上の改善を利用できるようになりました。

  • 適切なサイズのRedisクラスター

    いくつかの運用セルでは、トラフィックの急増に対応するために、Redisクラスターが意図的に過剰にスケールされていました。これにより十分な余裕が生まれましたが、不必要なコストも発生しました。インフラストラクチャAWSインテグレーションからのテレメトリーデータを使用して、使用パターンとメモリ消費量を分析しました。これらの洞察に基づいて、安全なパフォーマンスマージンを維持しながら、実際の使用状況に合わせてクラスターを手動で縮小しました。

これら2つのステップにより、最初の段階の最適化、コスト削減、およびリソース利用の向上が実現しました。このプロセス中に、Redisにデータを保存する方法の改善の可能性がある別の領域も特定しました。これは、後にさらに大規模な最適化の取り組みの基盤となりました。

データの保存方法を再考する必要があった理由

Redisインフラストラクチャを最適化した後、さまざまな環境に変更を展開しながら、New Relicを使用してパフォーマンスを注意深く監視し続けました。すべてが順調に見えました。コストはすぐに改善され、メモリとネットワークの使用量は受信データ量に対して予想したものと一致しました。

しかし、New Relicでは、システムをより効率的にする方法を常に模索しています。こうした調査の途中で、エンジニアがRedisにデータがどのように保存されるかを調べていたところ、spanデータの圧縮方法に関する興味深い点に気づきました。

トレースパイプラインは、Kafkaバッチごとに複数のスパンを受信し、トレースキーごとにグループ化してから、それらのスパンをRedisにプッシュします。Redisでは、各トレースキーはスパンのリストにマッピングされ、Redisクライアントは、メモリ内のオブジェクトを保存用にシリアル化するときにこれを圧縮します。

encodedTraceKey: String = [non-empty list of compressed spans: ByteArray]

これにより、各スパンがRedisにプッシュされる前に圧縮されるようになります。

Redisは意図したとおりに機能し、各スパンは保存前に圧縮されていましたが、エンジニアは最適化の余地があるのではないかと考え始めました。個々のスパンを保存するのではなく、スパンのバッチをまとめて保存するとどうなるでしょうか?そうすれば、圧縮がより効率的になり、ストレージやメモリのオーバーヘッドが削減されるでしょうか?

Redisのバッチ処理によるストレージの再考

最適化の機会に気づいた後、次のステップは、スパンをグループ化することで圧縮がより効果的になるかどうかをテストすることでした。これは、Redisでのデータ表現を、個々のスパンのリストからシリアル化されたスパンのリストに切り替えることを意味します。この新しい構造では、複数のスパンがまとめて保存され、単一のユニットとして圧縮されるため、Redisに書き込まれたり、Redisから読み取られたりするペイロードの数が少なくなり、ペイロードが大きくなります。仮に、このアプローチにより圧縮がより効率的になり、Redisの操作全体が簡素化される可能性があります。

このアイデアを検証するために、当社のエンジニアは制御されたテストを実行し、バッチ処理が圧縮効率とリソース使用量にどのような影響を与えるかについての経験的データを収集しました。実験は 1 つのステージングセルと1つの本番セルに導入され、数時間実行されて、単一スパンストレージとバッチスパンストレージ間の圧縮動作を比較しました。結果はすぐに有望なものでした。平均すると、圧縮効率は本番環境で約20パーセントから42パーセントに、ステージング環境で28パーセントから52パーセントに向上しました。

セル

平均バッチ長

平均無圧縮サイズ

現行の平均圧縮サイズ

新しい平均圧縮サイズ

新しい圧縮比

test-cell-01

9.96

11.1KB

8.48KB

2.14KB

42.86%

test-cell-02

5.08

6.88KB

4.9KB

1.46KB

52.189%

さらに、トレースのサイズが数スパンから数千スパンに及ぶまで大きく変化するシナリオも検討しました。そこで私たちは、単一のスパントレースを考慮しない場合、どのような改善が見られるのかを尋ねました。その前提に基づいて分析すると、1つ以上の要素のバッチでは圧縮率が約68パーセント向上することがわかりました。

これらの調査結果により、バッチ処理によってRedisのメモリと帯域幅の使用量を大幅に削減できることが明確に示され、チームは自信を持ってより広範なテストを進めることができました。

影響の測定

Redis のバッチ処理が実装されると、ステージングから始めて本番環境に移行し、環境全体に段階的に展開しました。ロールアウトは機能フラグによって完全に制御されており、結果をリアルタイムで監視しながらバッチ処理を安全に有効または無効にすることができます。New Relicダッシュボードを使用して、導入の各段階でRedisのメモリ使用量、ネットワークトラフィック、および全体的なシステムパフォーマンスを追跡しました。

そして結果は期待を裏切らなかった。New Relicのデータ取り込みはセルアーキテクチャで動作します。つまり、取り込みは多くの異なる独立したクラスター(セル)に分割されます。そこで、細胞レベルでも結果を測定しました。ここで表示されるデータは単一のステージングセルから取得されたものであり、期待できるパフォーマンスの向上を明確に示しています。このセルでは、Redis のメモリ使用量が大幅に減少し、クラスター全体で使用される最大メモリが66%削減されました。サービス間のネットワークトラフィックも同様に改善され、 66%削減されました。

分散トレーシングパイプラインは全体を通じて信頼性の高いパフォーマンスを維持し、安定性やスループットを損なうことなく大幅な効率向上を実現できることが確認されました。

教訓

この最適化の過程で、小さなアーキテクチャ調整でも、大規模なコストとパフォーマンスを大幅に改善できることが示されました。詳細なオブザーバビリティデータと、データの保存および送信方法の慎重な検討を組み合わせることで、シンプルでありながら大きな効果をもたらす効率化の機会を発見しました。

  • インフラストラクチャの基礎から始める:より優れたインスタンスタイプにアップグレードし、クラスターのサイズを適切に調整することで、コードを大幅に変更することなく、すぐに成果を上げることができます。
  • インフラストラクチャの先を見る:場合によっては、データ表現はハードウェアと同じくらい重要になります。私たちの場合、シングルスパン圧縮からバッチ圧縮に切り替えることで、Redisの使用量が半分以上削減されました。
  • すべてを計測する:問題を特定し、修正を検証し、その影響を定量化する上で、オブザーバビリティが鍵となりました。

New Relicでは、顧客を支援するためだけでなく、自社のシステムの運用方法を継続的に改善するためにも、オブザーバビリティを適用することが重要だと考えています。この経験により、コードからインフラストラクチャまで、あらゆるレイヤーの可視性が、大規模で持続可能なパフォーマンスの基盤であることが再認識されました。

まとめ

大規模なコストの最適化は、1 つの大きな変更で済むことはほとんどありません。システムの各層がパフォーマンスにどのように貢献しているかを理解し、それらのインタラクションをより効率的にする実用的な方法を見つけることです。インフラストラクチャのチューニングとデータストレージへのよりスマートなアプローチを組み合わせることで、信頼性やパフォーマンスを犠牲にすることなく、Redisのメモリと帯域幅の使用量を66%削減し、年間コストを半分に削減できました。

この経験から、有意義な利益は、日常的なデザインの選択を見直すことから得られることが多いことがわかりました。同じアプローチはあらゆる大規模システムに適用できます。New Relicで独自のRedis およびストレージメトリックを調べると、目に見えないところに隠れている同様の機会が見つかるかもしれません。