メインコンテンツまでスキップ

アプリケーション層のチューニング

コードレベルでのパフォーマンス改善手法を学びます。


┌─────────────────────────────────────────────────────────────┐
│ 「コードのどこが遅いの?」 │
├─────────────────────────────────────────────────────────────┤
│ │
│ APMを見たら、あるエンドポイントが遅い。 │
│ DBでもネットワークでもなさそう。アプリケーション層だ。 │
│ │
│ でも、コードのどこが遅い? │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ ・ループの中で何度もDB呼んでない?(N+1) │ │
│ │ ・同じ計算を何度もしてない?(キャッシュ) │ │
│ │ ・外部API呼び出しを直列にしてない?(非同期) │ │
│ │ ・毎回接続を張り直してない?(接続プール) │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ アプリケーション層は「自分でコントロールできる」領域 │
│ │
└─────────────────────────────────────────────────────────────┘

このレイヤーのキー要素

┌─────────────────────────────────────────────────────────────┐
│ アプリケーション層で押さえるべきポイント │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │キャッシュ│ │非同期処理│ │ N+1問題 │ │接続プール│ │
│ └────┬────┘ └────┬────┘ └────┬────┘ └────┬────┘ │
│ │ │ │ │ │
│ ↓ ↓ ↓ ↓ │
│ 同じ計算を 待ち時間を クエリ数を 接続確立 │
│ 繰り返さない 有効活用 減らす コストを削減 │
│ │
│ 効果: キャッシュ > N+1解消 > 非同期化 > 接続プール調整 │
│ │
└─────────────────────────────────────────────────────────────┘

アプリケーション層のボトルネック

┌─────────────────────────────────────────────────────────────┐
│ よくあるボトルネック │
├─────────────────────────────────────────────────────────────┤
│ │
│ CPU bound: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ ・重い計算処理 │ │
│ │ ・非効率なアルゴリズム │ │
│ │ ・過度なオブジェクト生成(GC負荷) │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ I/O bound: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ ・外部API呼び出しの待ち │ │
│ │ ・DB接続の待ち │ │
│ │ ・ファイルI/Oの待ち │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ Lock contention: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ ・synchronizedでの待ち │ │
│ │ ・共有リソースへのアクセス競合 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘

CPUバウンドな処理

症状: CPU使用率が高い、スケールアウトしても改善しない

よくあるCPUバウンドな処理

処理
暗号処理JWT署名/検証、パスワードハッシュ(bcrypt)、TLSハンドシェイク
シリアライズJSON/XMLのパース、大きなオブジェクトの変換
正規表現複雑なパターンマッチング
アルゴリズム非効率な実装(O(N²)など)

対策

対策内容
アルゴリズム改善O(N²) → O(N log N)、データ構造の見直し
キャッシュ同じ計算結果を再利用(署名済みトークンなど)
非同期/並列化複数コアを活用(ただしアムダールの法則に注意)
スケールアウトCPU追加で対応(最終手段)

認証サーバーの特性: JWT署名、bcrypt、暗号化などCPUバウンドな処理が多い。最終的にはCPUがボトルネックになりやすい。


キャッシュ

症状: 同じデータを何度も取得している、DBへのクエリ数が多い

キャッシュの効果

状態1回目2回目以降
キャッシュなしDB問い合わせ 50msDB問い合わせ 50ms
キャッシュありDB問い合わせ 50msキャッシュヒット 1ms

キャッシュの種類

種類速度共有用途
ローカル(Caffeine等)最速(μs)サーバー内のみ頻繁にアクセスするデータ
分散(Redis等)速い(ms)サーバー間で共有セッション、共有データ
CDN速いエッジ静的コンテンツ

キャッシュの注意点

  • 整合性: データ更新時にキャッシュも更新/削除、TTLを適切に設定
  • キャッシュスタンピード: キャッシュ切れに大量リクエストが殺到 → ロック、早期更新で対策
  • メモリ圧迫: 最大サイズを設定、LRUで古いものを削除、ヒット率を監視

非同期処理

症状: 外部API呼び出しが多い、待ち時間が長い、レスポンスが遅い

同期 vs 非同期

方式API-A (100ms)API-B (80ms)合計
同期(直列)→ 完了後に→ 実行180ms
非同期(並列)→ 同時に実行→ 同時に実行100ms

使いどころ

  • 複数の外部API呼び出し
  • 独立した複数のDB問い合わせ
  • レスポンスに含めない処理(ログ、通知等)

注意点

  • 依存関係がある処理は並列化できない
  • エラーハンドリングが複雑になる
  • デバッグが難しくなる

N+1問題

症状: 同じパターンのクエリが大量に発行される、DBへの往復回数が多い

N+1問題とは

-- 1回目: 注文一覧を取得
SELECT * FROM orders;

-- N回: 各注文のユーザーを個別に取得(100件あれば100回)
SELECT * FROM users WHERE id = 1;
SELECT * FROM users WHERE id = 2;
SELECT * FROM users WHERE id = 3;
...

100件のorderがあれば101回のクエリ。1回のクエリが1msでも、合計100ms以上かかる。

解決策

-- JOINで1回で取得
SELECT * FROM orders JOIN users ON orders.user_id = users.id;

-- または IN句でまとめて取得
SELECT * FROM users WHERE id IN (1, 2, 3, ...);

見つけ方

  • クエリログで同じパターンのクエリが大量に出ていないか確認
  • APMでDB呼び出し回数を監視

オブジェクト生成の最適化

症状: GCが頻発する、STW(Stop The World)でレイテンシが不安定

問題

大量のオブジェクト生成 → GC頻発 → Stop The World → レイテンシ悪化

対策

パターン改善方法
String連結をループ内でStringBuilder使用
Boxingの多用プリミティブ型を使う(Integer → int)
同じオブジェクトを毎回生成定数化、キャッシュして再利用

注意: 可読性を犠牲にしすぎない。プロファイラで確認してから最適化する。


接続プール

症状: 接続確立に時間がかかる、接続待ちでタイムアウト

接続プールの効果

状態接続確立クエリ合計
プールなし10ms(毎回)5ms15ms
プールあり0ms(再利用)5ms5ms

設定のポイント

設定意味目安
最小接続数常に保持する接続数ウォームアップ用
最大接続数上限リトルの法則で見積もる
タイムアウト接続取得の待ち時間短すぎるとエラー頻発

HikariCPの目安: maximumPoolSize = コア数 × 2 + ディスク数(実測で調整が必要)


まとめ

効果の大きいものから

優先度対策効果
1N+1問題の解消
2キャッシュの導入
3非同期処理中(I/Oバウンドに効果)
4接続プールの調整
5オブジェクト生成の最適化小〜中

心得

  • まず計測: プロファイラでホットスポットを特定、推測で最適化しない
  • 可読性とのバランス: 過度な最適化は保守性を下げる、ボトルネックでない箇所は最適化しない

次のステップ