AWS EC2上で運用しているCantaloupe IIIF画像サーバーで、初回タイルアクセスに8.8秒かかっていたため、いくつかのチューニングを行いました。最終的に0.84秒まで改善できたので、その過程を記録します。
環境
- AWS EC2 (us-east-1), CPU 2コア, メモリ 7.6GB
- Cantaloupe:
islandora/cantaloupe:6.3.12(最終的なバージョン) - ソース: S3Source
- フロントエンド: Traefik(Let’s Encrypt TLS終端)
- テスト画像: 46825×28127px の大判TIFファイル
ボトルネック分析
初期状態を調べると、以下の状況でした。
| 項目 | 状況 |
|---|---|
| JVMヒープ上限 | 512MB(コンテナメモリ2GBに対して少なめ) |
| FilesystemCache | 386MB / 13,950ファイル(稼働中) |
| キャッシュボリューム | 未永続化(docker-compose.ymlで起動していたため再起動ごとに消失) |
| ソース画像形式 | strip形式TIF(1タイル取得にS3から9チャンク×約2MBの読み込みが必要) |
特にソース画像の形式が問題で、strip形式TIFはIIIFのタイル配信に適していません。1タイルを返すために画像全体の広範囲をS3から読み込む必要がありました。
施策1: ピラミッドタイルTIFF変換
効果が最も大きかった施策です。libvipsのtiffsaveでピラミッドタイルTIFFに変換しました。
vips tiffsave input.tif output.tif --tile --pyramid --tile-width 256 --tile-height 256 --compression jpeg --Q 85
変換後のファイルサイズは219MB(Q85、ピラミッド付きタイルTIFF)。元のstrip TIFと比較すると、タイル単位でのランダムアクセスが可能になり、S3からの読み込みチャンク数が大幅に減りました。
変換前後の比較:
| 指標 | 変換前 (strip TIF) | 変換後 (ptif Q85) |
|---|---|---|
| 初回タイル (cold) | 8.8秒 | 0.7〜1.3秒 |
| 2回目タイル (cold, 別領域) | 1.7秒 | 0.7秒 |
| キャッシュ済みタイル | 0.04秒 | 0.01秒 |
| S3チャンク読み込み | 9回 | 少数 |
| 画像処理時間 | 1073ms | 510ms |
施策2: JVMヒープ増加
コンテナメモリ上限2GBに対してデフォルトヒープが512MBだったため、1536MBに増やしました。
islandora/cantaloupe:6.3.12の起動スクリプトはCANTALOUPE_HEAP_MINとCANTALOUPE_HEAP_MAX環境変数に対応しており、docker-compose.ymlに追記するだけで設定できます。
CANTALOUPE_HEAP_MIN: "512m"
CANTALOUPE_HEAP_MAX: "1536m"
なおJAVA_OPTS環境変数は起動スクリプトに参照されないため効きません。また2.0.10イメージではCANTALOUPE_HEAP_MAXに対応していないため、カスタム起動スクリプトのマウントが必要でした。
施策3: キャッシュボリューム永続化
docker-compose.prod.ymlにはcantaloupe_cache:/dataのボリューム設定が存在していましたが、docker-compose.ymlで起動していたため、コンテナ再起動のたびにFilesystemCacheが消失していました。正しいcomposeファイルで起動するよう修正しました。
施策4: Cantaloupeイメージのアップグレード
islandora/cantaloupe:2.0.10(Cantaloupe 5.0.5)からislandora/cantaloupe:6.3.12(Cantaloupe 5.0.7)に更新しました。
| 指標 | 2.0.10 (5.0.5) | 6.3.12 (5.0.7) |
|---|---|---|
| 初回タイル (cold) | 1.3秒 | 0.84秒 |
| 画像処理時間 | 510ms | 302ms |
マイナーバージョンアップですが、画像処理時間が約4割短縮されました。
施策5: CloudFront導入
体感改善として最も大きかった施策です。EC2がus-east-1にあるため、日本からのアクセスではRTT約146msが加算されていました。CloudFrontを前段に置くことで、キャッシュヒット時はエッジから直接配信されます。
変更前: ユーザー (日本) ──146ms往復──→ EC2 (us-east-1) → Cantaloupe → S3
変更後: ユーザー (日本) ──数ms──→ CloudFront東京エッジ ──→ EC2 (us-east-1) → Cantaloupe → S3
↑ キャッシュヒット時はここで返す
CloudFrontの主な設定:
- Origin: オリジンドメイン:443(HTTPS only)
- Cache TTL: MinTTL 1日, Default 30日, Max 1年
- Price Class: PriceClass_200
- HTTP/2 + HTTP/3有効
- ACM証明書: us-east-1で発行(CloudFrontの要件)
info.jsonの@id問題
CloudFront導入時にひとつ問題が発生しました。CantaloupeはリクエストのHostヘッダをもとにinfo.jsonの@idフィールドを生成します。CloudFrontからオリジンへのリクエストではHostがcantaloupe-origin...(オリジンドメイン)になるため、MiradorがそのURLを使ってオリジンに直接アクセスしてしまいます。
CANTALOUPE_BASE_URI環境変数でベースURLをCloudFrontドメインに固定することで解消しました。
CANTALOUPE_BASE_URI=https://iiif.example.com
Mirador並列タイルシミュレーション結果
Miradorが20タイルを並列リクエストするシナリオでの計測:
| シナリオ | 全体完了時間 |
|---|---|
| CloudFront miss(cold) | 6.2秒 |
| CloudFront hit(cached) | 1.3秒(US拠点から測定。日本からは0.1〜0.2秒程度) |
| Direct localhost(cold) | 4.4秒 |
効果がなかった施策
試したものの改善につながらなかった施策も記録しておきます。
CacheStrategy: StreamStrategyからCacheStrategyに変更しました。CacheStrategyはソース画像をローカルにフルダウンロードしてから処理するため、ソースが非タイルTIFの場合は逆に遅くなりました(4.8秒)。ピラミッドタイルTIFFに変換済みであれば効果があるかもしれませんが、今回は元に戻しました。
TurboJpegProcessor: Cantaloupe 5.0.5ではJavaバインディングとOS側のlibjpeg-turbo 2.1.5のAPIバージョンが一致せずフォールバックしていました。6.3.12でライブラリ互換性は解消されましたが、TurboJpegProcessorはTIFソースに対応していないため、結果としてJava2dProcessorが使われています。
今後の改善案
現時点で残っている改善案としては以下が考えられます。
- キャッシュのプリウォーム: よくアクセスされる画像タイルを事前リクエストするスクリプトを用意する
- サーバースペック増強: t3.large(2コア)→ t3.xlarge(4コア)でcold時の並列処理を改善する
- 東京リージョン移行: CloudFrontキャッシュヒット率が高ければ効果は限定的になるため、優先度は低めかもしれません
最終構成と総合結果
ユーザー → CloudFront (公開ドメイン)
↓ miss時のみ
Traefik (オリジンドメイン:443, Let's Encrypt TLS)
↓
Cantaloupe (islandora/cantaloupe:6.3.12, JVM heap 1536MB)
↓
S3 (us-east-1)
| 指標 | 改善前 | 改善後 |
|---|---|---|
| 初回タイル (cold) | 8.8秒 | 0.84秒 |
| Mirador並列20タイル (cold) | 推定15秒以上 | 6.2秒 |
| キャッシュ済みタイル(日本から) | 往復292ms+処理 | エッジから直接配信(0.1〜0.2秒) |
ピラミッドタイルTIFF変換が最も効果的で、次いでCloudFront導入が体感的な改善に寄与しました。同様の構成でCantaloupeを運用している場合、まずソース画像の形式を見直すのが手早い改善につながるようです。