TT Lab
はじめる
学ぶ 学習パス コース

分散トレーシングが切れる場所

traceparent のほかに一緒に流れる二つ

TT Labで続きを見る

一言でいうと

同じリクエストには、traceparentのほかにbaggageとtracestateも一緒に流れます。1つは自分たちが決めた業務の値を下流へ運び、もう1つはトレーシングツール同士がやり取りするための場所です。どちらも外から入ってくる文字列だという点は同じです。

なぜ必要なのか

決済のトレースを開いて「このリクエストはどのテナントのものか」を問う瞬間、ほとんどのチームが行き詰まります。ルートスパンにはテナントが付いているのに、6ホップ下のデータベーススパンにはありません。そのスパンだけを見ても、どの顧客の遅いクエリかわからないため、人が親をさかのぼってルートを探し、そこで値を読みます。トレース1つならできますが、1万件を集計するとなるとできません。

baggageは、まさにその場面のためにあります。一度入れておくと、そのリクエストから広がるすべての下流呼び出しのヘッダーに、同じ値が載ります。ところが、ここには2つの落とし穴があります。1つ目、baggageに入れた値は、自然にはスパン属性になりません。コンテキストの中にあるだけなので、各サービスが取り出して自分のスパンに書き写して初めて、クエリできるデータになります。2つ目、そのヘッダーはすべての下流リクエストに載ります。1つのリクエストが下流を40回呼べば、同じバイトが40回送られます。

tracestateは性質が異なります。業務の値を運ぶ場所ではなく、トレーシングツールが自分の状態を書き残す場所です。両者を混ぜて使うと、規格が保証するサイズの中に自分たちの値が収まらず、静かに切り捨てられます。

どう動くのか

W3C Baggage規格は、ヘッダーをkey=valueのリストとして定義しています。キーはRFC 7230のトークンで、値は決められたASCIIの範囲を外れる場合、パーセントエンコードする必要があります。サイズのルールはBaggage 3.3.2 Limitsにあります。作られたbaggage文字列が項目64個以下かつ8192バイト以下である間は、プラットフォームがすべての項目を伝達しなければなりません。その条件を超えると、どの項目を捨てるかは実装が決め、規格は順序すら定めていません。つまり、64個と8192バイトは上限ではなく、保証が切れる地点です。

PythonのSDKのW3CBaggagePropagatorは、ここに独自の上限をさらに設けています。項目1つが4096文字を超えるとその項目を除外し、全体が8192バイトを超えるとそこで切り捨てます。重要なのは、例外が出ないことです。9千文字の値をbaggageに入れても、プログラムは何も言わずに動き、ヘッダーにはその項目だけが抜けた状態で出ていきます。下流から「ときどき値が空になっている」という報告が来たら、この箇所を疑う必要があります。

tracestateのルールはTrace Context 3.3.1.5にあります。リストは最大32項目、値は印字可能なASCIIで最大256文字(カンマと等号は使えません)、ベンダーは合計で最低512文字は伝達しなければなりません。切り捨てる必要があるときは項目を丸ごと捨てますが、128文字を超える項目を先に捨て、そのあとは末尾から捨てます。そして、自分たちの項目を入れたり変更したりしたなら、その項目は一番左に移します。左が「今traceparentを書いたシステム」の位置だからです。自分たちが触っていない項目の順序はそのままにしておかないと、他のツールの相関が生き残りません。

traceparentの4つの欄の読み方は、このコースの前のラボですでに扱いました。ここではその行を分解しません。ただし、tracestateはtraceparentなしで来たら捨てなければならない、という規格の1行だけは覚えておいてください。

信頼境界が最後の要素です。公開APIの手前で受け取ったbaggageは、他人が書いた文字列です。そのままスパン属性に移すと、メトリクスのカーディナリティを他人が決めることになり、そのまま下流へ流すと、社内のサービスがそれを自分たちの値として読んでしまいます。そのため境界では許可リストで受け取ります。知っているキーだけ、決まった形の値だけ、決まった個数までです。規格も同じ方向で書いています。baggageに機密を載せない、または信頼境界を越えるリクエストにはbaggageが載らないようにする、というものです。認定資格コースのコンテキスト境界ラボが、出ていく側で宛先ごとに何を載せるかを選ぶ作業を扱うとすれば、ここで作るフィルターは入ってくる側です。他人が送ってきたもののうち何を自分たちのものとして受け入れるかを決める場所です。

現場での姿

あるチームは、デバッグを楽にするために、リクエスト本文の要約をbaggageに入れました。開発環境ではうまく動いていたのに、本番では下流呼び出しが多い経路だけ遅延が増えました。値1つが800バイトで、そのリクエストは下流を30回呼んでいました。24キロバイトがリクエスト1件ごとにネットワークへ余分に出ていて、プロキシのヘッダーサイズ制限に引っかかった経路では502が出ました。baggageに何を入れるかは、「この値をすべての下流が使うか」で分けます。

別の事故は反対側でした。パートナー企業のリクエストを受けるゲートウェイで、入ってきたbaggageをそのままスパン属性に移したところ、そのうち1つがリクエストごとに異なるIDでした。属性1つで時系列が数十万本に分かれ、ストレージコストが数日で数倍になりました。ゲートウェイに許可リストを入れ、値の形まで検査するようにしたところ、その日のうちに止まりました。外から来たコンテキストはデータではなく入力です。

次のラボですること

baggageを入れて次のサービスへ流し、それが自然にはスパン属性にならないことを、2つのスパンで自分で確認します。候補の束4つのヘッダーバイト数を測り、どれが規格の保証範囲を外れるかを見て、9千文字の値が静かに消えることも確認します。次に、信頼境界の外から来たbaggageに許可リストフィルターを自分で作り、何が残り何が捨てられたかをスパンに残します。最後に、tracestateを規格どおりに読み、入ってきた項目の順序を保ったまま自分たちの項目を先頭に入れる変換を作ってルールファイルに固め、2つ目のサービスに適用します。