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

![F7FADB6D-C5F5-43BB-8F39-9722A9B4B38A.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4063686/880ee69e-cc8e-46d5-b447-51aa401575c1.png)

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

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

---

## `mock:` とは何か？

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

### サンプル（XML DSL）

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

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

```java
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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4063686/a5d6eb3e-221d-46d6-81f0-daba66b80271.png)

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

---

## 誤用パターンとリスク

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

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

→ Kafka から1万件以上流すと、`mock:` がすべて保持して **OOM（OutOfMemoryError）**。

---

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

```xml
<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` をメモリに保持し続ける構造**になっています。

```java
protected List<Exchange> exchanges = new CopyOnWriteArrayList<>();
```

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

---

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

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

```
20KB × 1,000,000件 = 約20GB（ヒープに保持）
```

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

---

## 対策方法

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

```xml
<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：保持件数を制限する（※テスト限定）

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

---

### 対策3：`mock:` を使用せず、`log:` や `bean:` を使う

```xml
<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 はテスト専用、実環境では封印"** を鉄則として、安全なルート設計を心がけましょう。