共有 Vector クラスターのメモリは毎日約 10% 増加し、週に 1 回再起動されます。割り当てトレースは常に上流の変換を指しますが、実際にメモリを保持するのは datadog_metrics シンクです。累積値シリーズごとに保存される増分変換ベースラインには TTL も容量制限もありません。この記事では、発見から寄り道、測位、解決までのプロセスを記録します。
AIが生成したステートメント: この記事の内容はAIによって生成またはAIによって支援されたものです。審査にはご注意ください。
今日、Blog Parkチームが公開した「Agent’s experience should not just stop in the chat box: cnblogs-publish is online」を見て、実際にcnblogs-publishを使って記事を投稿してみたいと思い、この記事を思いつきました。
問題: アグリゲーターのメモリが増加するだけで減少しない
私たちのインジケーター リンクは 2 層の Vector です。各 ECS タスクは Vector サイドカー (以下、エージェントと呼びます) を実行し、アプリケーション インジケーターと Vector 独自の内部メトリクスを収集し、ECS Service Connect ポーリングを通じてそれらをマルチコピー共有 Vector クラスター (以下、アグリゲーターと呼びます) に送信します。ECS Service Connect ポーリングは、それによって使用されます。 datadog_metrics シンクは一律に Datadog に送信されます。

アグリゲーターのメモリはゆっくりと上昇しています。再起動後は約 15%、その後毎日約 10% ポイントずつ上昇し、約 7 日で 90% まで上昇し、自動再起動がトリガーされてから再び起動します。

最初の試み: リソースを追加し、疑わしいコンポーネントを見つける
この問題は2025年5月に発見され、当時は毎日約3%上昇していました。
メモリを追加します。 タスク メモリが 2 GB、4 GB、8 GB に増加しました。これは再起動サイクルを長くするだけです。漏れとリソースの量が直線的に増加しても、再起動が遅れるだけです。
疑わしいコンポーネントを見つけます。 問題vectordotdev/vector#23093が上流で提起されました。 2025 年 8 月に 0.48 にアップグレードされ、割り当てトレースが有効になります (--allocation-tracing)戻る、vector_component_allocated_bytes 表示メモリが 1 つに増加 route 変換という名前で、273 MB から 1.1 GB に増加しました。しかし、メンテナはこう答えました。
の
Route変換はイベントごとの状態を維持しません。 …バックプレッシャーがあるかどうかを理解するために、使用率とバッファーのメトリックもチェックすることをお勧めします。
バッファインジケーターは正常ですが、スレッドは壊れています。
その後、アップストリームはこの問題を次のようにリンクしました aggregate 変換のメモリ最適化 (#24357、#24943、v0.55 にマージ)。 0.56 にアップグレードした後も、リークは同じままです。
このラウンドにはまだ説明されていない現象が 1 つ残っています。datadog_metrics シンク Events In そして Events Out いいえ、送信するものは常に受信するものよりも少なくなります。
別の観点から見ると、割り当て追跡記録は「誰が割り当てたか」を記録します。
2026 年 10 月に再調査し、まずテスト環境でアグリゲーターを最後に再起動する前後の各コンポーネントの割り当てバイトを確認します。

私たちはその中間にいる route 後ろに1つ追加しました aggregate(内部メトリクスの処理)、最も増加したコンポーネントは route に置き換えられる aggregate;route 逆に私はフラットです。datadog_metrics シンクの知名度も上がってきており、段階的に進んでいます。シンクのバッファーには常に数百のイベントしかなく、1 MB 未満であるため、バックログがなくなりました。
「疑わしいコンポーネント」はトポロジの変化に応じて変化し、問題がこれらのコンポーネント自体にあるわけではない可能性があることを示しています。割り当てトレースを紹介する Vector の公式ブログには、次のように明確に記載されています。
Vector は、各割り当て/割り当て解除をキャプチャし、それを現在実行中のコンポーネントに関連付けるカスタム メモリ アロケーター実装を使用します。
現在、コンポーネント間のメモリ所有権の追跡はサポートされていません。メモリ使用量のメトリクスを見ると、そのほとんどは、イベントをバッチ処理しているシンクではなく、イベントを作成した、またはイベントを処理したコンポーネントに起因していることがわかります。
つまり、イベントが上流の変換で作成され、下流のシンクに渡された場合でも、長期間保存されていたとしても、このメモリは依然として上流の名前で記録されます。

これらの上流コンポーネントのステータスを見てみましょう。
routeこれはシャントのみを行い、状態は保存しません。メンテナもそれを確認しています。aggregateデフォルトの自動モードでは、フラッシュされるたびに内部マップがクリアされます (std::mem::take(&mut self.map));remap状態は保存されません。
割り当てられたメモリは解放されず、ダウンストリームでのみ使用できます。 datadog_metrics シンクホールド。
根本原因: datadog_metrics シンクの MetricSet には上限がない
Vector のインジケーターは、アブソリュートとインクリメンタルの 2 つのタイプに分類されます。 Datadog のカウント タイプでは増分をレポートする必要があるため、 datadog_metrics シンクは送信前に正規化を行い、蓄積された値を増分に変換します。変換では、各シリーズの前の値を記憶する必要があります。この状態には状態があります。 MetricSet 内部。
以下のソースコードはすべてVector v0.56.0です。
シンクがノーマライザーを構築するときは、デフォルトの構成が使用されます。
.normalized_with_default::()
最終的に得られるものは MetricSet::default()デフォルト設定は 3 つすべてですが、 None:
#[derive(Debug, Clone, Copy, Default)]
pub struct MetricSetSettings {
pub max_bytes: Option,
pub max_events: Option,
pub time_to_live: Option,
}
容量ポリシーがないと、最下層は無制限になります。 IndexMapTTL がなければクリーンアップは行われません。datadog_metrics シンクにはそれを変更するための設定項目はありません。
DatadogMetricsNormalizer コールツーカウンター、ヒストグラム、分布 make_incremental入力が累積値である場合にのみ状態が変更されます。
pub fn make_incremental(&mut self, metric: Metric) -> Option {
self.maybe_cleanup();
match metric.kind() {
MetricKind::Absolute => self.absolute_to_incremental(metric),
MetricKind::Incremental => Some(metric),
}
}
| シンクに入るインジケーター | ステータスをMetricSetに保存するかどうか |
|---|---|
| 累積値カウンタ/ヒストグラム/分布 | ライブシリーズごとに 1 つのベースライン、決してクリーンアップされない |
| インクリメントカウンター/ヒストグラム | 保存せず、そのまま渡します |
| 累積値ゲージ | 存在しません |
| インクリメンタルゲージ | 保存(逆方向変換) |
absolute_to_incremental シリーズを初めて表示するときは、ベースラインのみが記録され、出力は実行されません。
None => {
// No reference so store this and emit nothing
self.insert(metric, timestamp);
None
}
ソース コードのコメントによると、これは、Vector が再起動後に累積値を大きな増分として送信するのを防ぐためです。したがって、シリーズが常に変化している場合、シンクによって送信されるイベントは受信されるイベントよりも常に少なくなります。これが第一ラウンドに残ったものです。 Events In ≠ Events Out 答え。
比較するには、prometheus_remote_write シンクは同じメカニズム (逆方向、累積値への増分) を使用しますが、 expire_metrics_secs文書には次のように書かれています。
設定されていない場合、一意の増分メトリックをこのシンクに送信すると、メモリが無制限に増加します。
datadog_metrics 私も同じ問題を抱えていますが、スイッチがありません。
私たちのシナリオに当てはまる: 古いバージョンのエージェントによって報告される内部メトリックは累積値であり、タスク レベルのタグ (ecs_task_id、host 待って)。サービスがデプロイ、拡張、または再起動されるたびに、一連の新しいバッチが生成されます。古いシリーズは表示されなくなりますが、ベースラインは常に残ります。 MetricSet 内部。メモリは「タスク置換の累積数」に応じて直線的に増加し、アグリゲータを再起動することによってのみクリアできます。
検証: 最小限の複製
他の要因を排除するために、docker compose を使用して最小限の再現を行いました (完全な構成については #23093 のコメントを参照してください): Vector 0.56.0、open --allocation-tracing,datadog_metrics シンクは、すべてのリクエストに対して 202 を返すモックを指します。 Lua 変換は、1 秒あたり 200 個のカウンタ/ヒストグラム シリーズを生成します。タグは ECS タスクを模倣しており、比較のために 3 つのグループに分割されています。
- 腹筋運動: 2秒ごとに変化する累積値
ecs_task_id; - 腹筋固定: 累計値、タグは固定です。
- チャーンの増加:増分値、2秒ごとに変化
ecs_task_id。

「累積値 + シリーズの継続的な置換」のみにより、メモリは 20 分で 50 MiB から 619 MiB (約 1.7 GiB/h) まで増加し続けます。概算では各シリーズ約4.9KB程度常駐します。シリーズが固定されているか、シンクに入る前にすでに増分値である場合、メモリはフラットになります。
同じグループ内で割り当てトレースを分割すると、オンラインでも「誤解を招く」状況が再現されます。

メモリは主に上流の Lua 変換名 (470 MiB) で記録され、シンク名は 61 MiB のみで、段階的に増加します。これは、ハッシュ テーブルの容量を 2 倍にする特性と一致しています。
解決策: エージェント側で累積値を増分に変換します。
シンクは累積値の状態のみを保存するため、アグリゲーターに入るインジケーターはすでに増分値になっています。

この変換がエージェント側で行われるのには、より根本的な理由があります。正しさ。アグリゲーターには複数のコピーがあります。前のものはポーリング負荷分散です。同じシリーズの 2 つの隣接する点は、異なるコピーに分類される場合があります。各コピーはシーケンスの一部のみを参照し、計算された増分自体が間違っています。エージェントは単一のインスタンスであり、完全なシーケンスを取得できます。それを増分に変換してポーリングした後、増分は直接追加でき、どのコピーが割り当てられているかは関係ありません。
現在、Vector には「累積値から増分への」変換が組み込まれておらず、Lua v2 変換を使用して一時的に実装されています。カウンタのコアロジック:
-- key = namespace + name + 排序后的 tags
local prev_val = prev[key]
prev[key] = cur
if prev_val == nil then return end -- 第一个点只记录基线
local delta = cur - prev_val
if delta < 0 then return end -- counter 重置,丢弃这一个点
m.kind = "incremental"
m.counter.value = delta
emit(event)
ヒストグラムはバケットごとに減算され、最初に表示されたとき、バケットの境界が変化したとき、または値が小さくなったとき、ポイントは破棄されます。ルアで prev テーブルもクリーンアップされませんが、エージェントは 1 つのタスクのみを実行し、シリーズ コレクションは基本的にそのライフ サイクル中に固定され、無制限に増加することはありません。
アグリゲータのメモリを本当にフラット化するには、以下が必要です全て すべてのエージェントはこの変換を含むバージョンにアップグレードされ、アグリゲーターが再起動されて既存の状態がクリアされます。アップグレードは依然として進行中であり、上記の生産曲線の最後のサイクルは依然として上昇しています。
上流側では、#23093 で根本原因の分析と再現を追加し、それ以来 (2026 年 11 月 11 日現在) いくつかの関連開発を行ってきました。
| PR | コンテンツ | 州 |
|---|---|---|
| #27042 | 与える datadog_metrics シンクプラス expire_metrics_secs |
オープン、レビュー待ち |
| #26080 | internal_metrics 新しいソース emit_incrementalインクリメントを直接出力します |
マスターにマージされていますが、まだリリースされていません |
| #24839 | 内蔵 absolute_to_incremental 変身 |
開ける |
#26080 のリリース後、内部メトリクス部分を直接使用できるようになります emit_incremental ルアを置き換えます。
まとめ
- 割り当てトレースでは、「誰がそれを保持しているか」ではなく、「誰が割り当てたか」が記録されます。 特定のコンポーネント名のメモリが増加し続けます。まずステータスがあるかどうかを確認します。そうでない場合は、下流に移動してホルダーを見つけます。
- シンクには無制限の状態が存在する場合もあります。 累積値と増分値を相互に換算する場合は、シリーズごとにステータスを記録する必要があります。シリーズを頻繁に入れ替える場合、TTLなしの状態は漏れます。
Events In ≠ Events Outが手がかりです。 これは、シンクがイベントを「飲み込んでいる」ことを示しており、これは多くの場合、状態遷移を意味します。- ステートフルな遷移は、完全なシーケンスが利用可能な場所に配置されます。 エージェント側で増分に変換すると、リークがなくなるだけでなく、下流ユーザーが自由に拡張および縮小できるようになります。