0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Apache Camel × Spring Boot の mock がメモリを食いつぶす!?本番で使ってはいけない理由

Last updated at Posted at 2025-04-20

F7FADB6D-C5F5-43BB-8F39-9722A9B4B38A.png

Apache Camelでアプリケーションを開発中、大量メッセージを処理させると ヒープメモリが急増する現象に遭遇しました。調査の結果、原因は mock: エンドポイントでした。

この記事では、mock: の用途・落とし穴・誤用パターンとその対策を XML DSL の例とともに紹介します。


mock: とは何か?

mock: は Camel における テスト専用エンドポイントです。
ユニットテストや統合テストで、処理対象のメッセージが 想定通りにルートを通過したかを確認する目的で使用されます。

サンプル(XML DSL)

<route id="sampleRoute">
  <from uri="direct:start"/>
  <to uri="mock:result"/>
</route>

このルートは direct:start に送ったメッセージが mock:result に届くかどうかを確認できます。テストコードは以下のようになります:

MockEndpoint mock = getMockEndpoint("mock:result");
mock.expectedBodiesReceived("Hello");
template.sendBody("direct:start", "Hello");
mock.assertIsSatisfied();

mock: がヒープを圧迫する理由

Camel の mock: は、受信した Exchange をすべて 内部のリストに保持します。
これはテスト後にメッセージ内容を検証するためですが、保持件数に制限がないため、大量のメッセージを受け取ると GC の対象にならず、ヒープに蓄積し続けるという性質があります。


JVMツールで観察したヒープ急増の様子

Kafka から100万件以上のメッセージを受信させるルートに mock: を入れた場合、以下のようなヒープ使用量の増加が確認されました。

IMG_0671.png

  • 青:使用中ヒープ
  • mock: を使っていた部分で右肩上がりのヒープ使用が発生
  • mock: を除去後、ヒープ使用が安定化

誤用パターンとリスク

誤用例1:負荷テストに mock: を使用

<route id="loadTestRoute">
  <from uri="kafka:topic.in"/>
  <to uri="mock:result"/>
</route>

→ Kafka から1万件以上流すと、mock: がすべて保持して OOM(OutOfMemoryError)


誤用例2:本番環境に mock: が残っていた

<route id="realRoute">
  <from uri="direct:process"/>
  <to uri="mock:intermediate"/>
  <to uri="bean:finalProcessor"/>
</route>

→ デバッグ目的で残した mock: に実データが流れ込み、本番稼働後にメモリ不足で障害発生


mock の内部構造に踏み込む

mock: は内部的に org.apache.camel.component.mock.MockEndpoint を使用しており、以下のように Exchange をメモリに保持し続ける構造になっています。

protected List<Exchange> exchanges = new CopyOnWriteArrayList<>();
  • 受信するたびに exchanges.add() が呼ばれる
  • reset() されない限り、保持し続ける
  • Exchange 内にはメッセージ本文やヘッダー、例外などすべてが保持される

保持するメモリサイズのイメージ

1件あたりのメッセージが 20KB 程度の場合:

20KB × 1,000,000件 = 約20GB(ヒープに保持)

→ GC の対象にならないため、JVMが持ちこたえられなくなります。


対策方法

対策1:条件で mock: を有効/無効に切り替える

<route id="safeRoute">
  <from uri="direct:start"/>
  <choice>
    <when>
      <simple>${header.debug} == 'true'</simple>
      <to uri="mock:result"/>
    </when>
    <otherwise>
      <to uri="log:realProcessing"/>
    </otherwise>
  </choice>
</route>

対策2:保持件数を制限する(※テスト限定)

mock.setRetainFirst(0);   // 最初のメッセージを保持しない
mock.setRetainLast(100);  // 最後の100件だけ保持

対策3:mock: を使用せず、log:bean: を使う

<route id="productionSafeRoute">
  <from uri="direct:start"/>
  <to uri="log:processed-message"/>
  <to uri="bean:finalProcessor"/>
</route>

→ ログやBeanで確認しつつ、メモリ使用を最小化。


まとめ

項目 内容
用途 テスト用(ユニット・結合)エンドポイント
動作 受け取ったすべてのメッセージをリストに保持
危険性 ヒープ圧迫 → GC不能 → OOM(OutOfMemoryError)
推奨 本番や負荷テストでは使用しないこと

結論

Apache Camel の mock: は便利なテストツールですが、使いどころを間違えるとメモリリークに近い状態を引き起こします。

以下のようなケースでは必ず使用を避けましょう:

  • 負荷テスト
  • 本番ルート
  • Kafka や HTTP で大量メッセージを処理するルート

"mock はテスト専用、実環境では封印" を鉄則として、安全なルート設計を心がけましょう。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?