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:
を入れた場合、以下のようなヒープ使用量の増加が確認されました。
- 青:使用中ヒープ
-
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 はテスト専用、実環境では封印" を鉄則として、安全なルート設計を心がけましょう。