#はじめに

「1台のサーバーでは処理しきれない」——アクセスが増えたとき、どうすればいいのでしょうか?

ロードバランサーは、複数のサーバーにリクエストを分散させる仕組みです。可用性の向上、スケーラビリティの確保、障害耐性の向上を実現します。

この記事を読むと、以下のことができるようになります:

  • ロードバランサーの役割を理解できる
  • 主要な分散アルゴリズムを把握できる
  • セッション維持の方法がわかる

#ロードバランサーとは

ロードバランサーとは、複数のサーバーにトラフィックを分散させる仕組みです。

                         ┌→ [サーバー1]
[クライアント] → [LB] ────→ [サーバー2]
                         └→ [サーバー3]

1台のサーバーに負荷が集中することを防ぎ、システム全体の処理能力を向上させます。

#ロードバランサーの種類

#L4ロードバランサー(トランスポート層)

TCP/UDPレベルで動作します。

[クライアント] ──TCP──> [L4 LB] ──TCP──> [サーバー]

特徴:

  • IPアドレスとポート番号で振り分け
  • 高速、低レイテンシー
  • HTTPの内容は見られない

#L7ロードバランサー(アプリケーション層)

HTTP/HTTPSレベルで動作します。

[クライアント] ──HTTP──> [L7 LB] ──HTTP──> [サーバー]

                    リクエスト内容を解析

特徴:

  • URL、ヘッダー、Cookieで振り分け可能
  • パスベースルーティング
  • SSLターミネーション
  • L4より処理コストが高い

#L4 vs L7

項目L4L7
動作レイヤーTCP/UDPHTTP/HTTPS
振り分け基準IP、ポートURL、ヘッダー、Cookie
速度高速やや遅い
機能シンプル高機能
用途汎用Webアプリケーション

#分散アルゴリズム

#ラウンドロビン(Round Robin)

順番にリクエストを振り分けます。

リクエスト1 → サーバー1
リクエスト2 → サーバー2
リクエスト3 → サーバー3
リクエスト4 → サーバー1  ← 一巡して最初に戻る
upstream backend {
    server 192.168.1.10:3000;
    server 192.168.1.11:3000;
    server 192.168.1.12:3000;
    # デフォルトでラウンドロビン
}

メリット: シンプル、均等に分散 デメリット: サーバーの処理能力差を考慮しない

#重み付きラウンドロビン(Weighted Round Robin)

サーバーの処理能力に応じて重みを設定します。

サーバー1(weight=3): 3リクエスト
サーバー2(weight=2): 2リクエスト
サーバー3(weight=1): 1リクエスト
upstream backend {
    server 192.168.1.10:3000 weight=3;
    server 192.168.1.11:3000 weight=2;
    server 192.168.1.12:3000 weight=1;
}

用途: 異なるスペックのサーバーが混在する場合

#最小接続数(Least Connections)

現在の接続数が最も少ないサーバーに振り分けます。

サーバー1: 10接続
サーバー2: 5接続  ← 次のリクエストはここへ
サーバー3: 8接続
upstream backend {
    least_conn;
    server 192.168.1.10:3000;
    server 192.168.1.11:3000;
    server 192.168.1.12:3000;
}

メリット: 負荷が不均一な場合に効果的 用途: 処理時間が一定でないリクエスト

#IPハッシュ

クライアントIPをハッシュ化して振り分け先を決定します。

IP: 192.168.1.100 → hash → サーバー2
IP: 192.168.1.101 → hash → サーバー1
IP: 192.168.1.100 → hash → サーバー2  ← 同じIPは同じサーバーへ
upstream backend {
    ip_hash;
    server 192.168.1.10:3000;
    server 192.168.1.11:3000;
    server 192.168.1.12:3000;
}

メリット: セッション維持が容易 デメリット: NAT環境で偏りが発生する可能性

#ランダム

ランダムにサーバーを選択します。

upstream backend {
    random;
    server 192.168.1.10:3000;
    server 192.168.1.11:3000;
}

#ヘルスチェック

ヘルスチェックとは、サーバーが正常に動作しているか定期的に確認する仕組みです。

[LB] ──ヘルスチェック──> [サーバー1] ✅ 正常
[LB] ──ヘルスチェック──> [サーバー2] ❌ 異常 → 振り分け対象から除外
[LB] ──ヘルスチェック──> [サーバー3] ✅ 正常

#パッシブヘルスチェック

実際のリクエストの結果からサーバーの状態を判断します。

upstream backend {
    server 192.168.1.10:3000 max_fails=3 fail_timeout=30s;
    server 192.168.1.11:3000 max_fails=3 fail_timeout=30s;
}
  • max_fails: 失敗回数の閾値
  • fail_timeout: 失敗カウントのリセット時間、および異常判定後の除外時間

#アクティブヘルスチェック

定期的にヘルスチェック用のリクエストを送信します。

# nginx Plus(商用版)の場合
upstream backend {
    zone backend 64k;
    server 192.168.1.10:3000;
    server 192.168.1.11:3000;

    health_check interval=5s fails=3 passes=2;
}

#ヘルスチェックエンドポイント

アプリケーション側でヘルスチェック用のエンドポイントを用意します。

// Express.js の例
app.get('/health', (req, res) => {
  // データベース接続など依存サービスの確認
  const isHealthy = checkDependencies();

  if (isHealthy) {
    res.status(200).json({ status: 'healthy' });
  } else {
    res.status(503).json({ status: 'unhealthy' });
  }
});

#セッション維持(スティッキーセッション)

同じユーザーのリクエストを同じサーバーに振り分ける仕組みです。

#必要な理由

セッション情報がサーバーのメモリに保存されている場合、別のサーバーにリクエストが振り分けられると、セッションが失われます。

リクエスト1 → サーバー1(ログイン、セッション作成)
リクエスト2 → サーバー2(セッションがない!ログアウト状態に)

#方法1: Cookieベース

ロードバランサーがCookieを設定し、振り分け先を記録します。

1. 初回リクエスト
   [LB] → サーバー1を選択
   [LB] ← Set-Cookie: SERVERID=server1

2. 以降のリクエスト
   [クライアント] → Cookie: SERVERID=server1
   [LB] → サーバー1へ転送

#方法2: IPハッシュ

前述のIPハッシュアルゴリズムを使用します。

#方法3: セッションの外部化

セッションデータを外部ストレージ(Redis等)に保存し、どのサーバーからでもアクセスできるようにします。

[サーバー1] ─┐
[サーバー2] ─┼→ [Redis(セッションストア)]
[サーバー3] ─┘
// Express.js + Redis の例
const session = require('express-session');
const RedisStore = require('connect-redis').default;
const { createClient } = require('redis');

const redisClient = createClient({ url: 'redis://localhost:6379' });

app.use(session({
  store: new RedisStore({ client: redisClient }),
  secret: 'your-secret',
  resave: false,
  saveUninitialized: false,
}));

推奨: セッションの外部化が最もスケーラブル

#クラウドサービスのロードバランサー

#AWS

サービス種類用途
ALBL7Webアプリケーション
NLBL4高パフォーマンス、固定IP
CLBL4/L7レガシー(非推奨)

#GCP

サービス種類用途
HTTP(S) Load BalancingL7グローバル分散
TCP/UDP Load BalancingL4リージョン内分散

#Azure

サービス種類用途
Application GatewayL7Webアプリケーション
Load BalancerL4汎用

#高可用性の実現

#ロードバランサーの冗長化

ロードバランサー自体が単一障害点にならないよう、冗長化します。

                  ┌→ [LB1 (Active)]
[DNS/VIP] ────────┤
                  └→ [LB2 (Standby)]

                    障害時に自動切り替え

#グレースフルシャットダウン

サーバーを停止する際、処理中のリクエストを完了させてから停止します。

1. ヘルスチェックを失敗させる
2. 新規リクエストの振り分けを停止
3. 処理中のリクエストが完了するまで待機
4. サーバーを停止
upstream backend {
    server 192.168.1.10:3000;
    server 192.168.1.11:3000 down;  # 振り分け対象から除外
}

#DevToolsでの確認

#レスポンスヘッダー

ロードバランサーが追加するヘッダーを確認できます。

# AWSのALB
X-Amzn-Trace-Id: Root=1-xxxxx

# Cloudflare
CF-Ray: xxxxxxxxx

#接続先サーバーの確認

アプリケーションでサーバー情報をヘッダーに含めると、デバッグに便利です。

app.use((req, res, next) => {
  res.set('X-Server-Id', process.env.SERVER_ID);
  next();
});

#まとめ

  • ロードバランサーは複数サーバーにトラフィックを分散
  • L4(TCP)とL7(HTTP)の2種類がある
  • 分散アルゴリズム: ラウンドロビン、最小接続数、IPハッシュなど
  • ヘルスチェックで異常サーバーを自動除外
  • セッション維持: Cookieベース、IPハッシュ、外部セッションストア
  • セッションの外部化がスケーラブルな解決策

#次のステップ

ロードバランサーの基礎を理解したところで、次はエッジコンピューティングについて学びましょう。ユーザーに近い場所でコードを実行し、レイテンシーを削減する方法を紹介します。

#参考リンク