- 設計
スケールアップとスケールアウトの違い|負荷分散方式の一覧と選び方
スケールアップは1台の性能(CPU・メモリ)を上げること、スケールアウトは台数を増やして処理を分け合うことです。 スケールアウトでは、増やした台にリクエストを配る役目のロードバランサが必要になり、「どの台に送るか」を決める負荷分散方式を選ぶことになります。
この記事は振り分け方式に絞り、スケールアップとスケールアウトの判定表、負荷分散方式の一覧、そのまま貼れる nginx の upstream 設定、実際に振り分けた件数、Python の簡易シミュレーション、スティッキーセッションとセッション外部化の選び方、L4 と L7 の違いをまとめます。
確認環境(2026-10-06):Ubuntu 24.04、nginx 1.24.0、Python 3.11.15。振り分けの件数は、同じマシン上に確認用のサーバーを3つ立てて nginx 経由でリクエストを送った結果です。nginx 1.24.0 に含まれない設定(least_time、sticky)は、nginx 公式ドキュメント(ngx_http_upstream_module)の記載に沿っています。
この記事の結論
- 状態を持たない処理(Web・API のサーバー)はスケールアウト。 どの台が受けても同じ結果になるので、台数を増やせます。
- 単一ノードに縛られる DB の書き込みは、まずスケールアップ。 書き込みを受ける台が1つの構成では、台数を増やしても書き込みは分かれません。
- 振り分け方式は、迷ったらラウンドロビン。 各台の性能が同じで、処理時間もそろっているなら、これで均等になります。
- 処理時間がばらつくなら最小接続。 遅い処理を抱えた台を避けて送れます。
- 同じ利用者を同じ台に送り続けたいときは、まずセッション外部化を検討し、難しいときにスティッキーセッションを使います。
スケールアップとスケールアウトの判定表
| 対象 | まず選ぶ方法 | 理由 |
|---|---|---|
| 状態を持たない Web・API サーバー | スケールアウト | どの台が受けても同じ結果になる |
| セッションをサーバーのメモリに持つ Web サーバー | セッション外部化のうえでスケールアウト | そのまま増やすと、台が変わるたびにログインが切れる |
| DB の書き込み(書き込みを受ける台が1つの構成) | スケールアップ | 台を増やしても書き込み先は1つのまま |
| DB の読み取り | スケールアウト(読み取り用の複製を増やす) | 読み取りは複数の台に分けられる |
| バッチ処理(データを分割できる) | スケールアウト | 範囲ごとに別の台で処理できる |
| バッチ処理(1本の流れで順に処理する) | スケールアップ | 分けられない処理は1台の速さで決まる |
| キャッシュ・検索など、もともと複数台構成を前提にした製品 | スケールアウト | 製品が分散の仕組みを持っている |
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「スケールアップとスケールアウトの違い|負荷分散方式の一覧と選び方」
スケールアップとスケールアウトの違い
| 比べる点 | スケールアップ(垂直) | スケールアウト(水平) |
|---|---|---|
| 増やすもの | 1台の CPU・メモリ・ディスク性能 | 台数 |
| アプリの変更 | ほぼ不要 | 状態を持たない作りにする必要がある |
| 上限 | 用意できる最大の機種まで | 振り分けと共有部分(DB など)が許す範囲まで |
| 作業中の停止 | 機種の入れ替えで止まることがある | 動かしたまま台を足せる |
| 1台が止まったとき | その1台が止まると全体が止まる | 残りの台で続けられる |
| 必要な仕組み | なし | ロードバランサ、セッションの置き場所 |
| 縮めるとき | 機種を下げる(再び入れ替え) | 台を減らす |
スケールアウトを選ぶと、「どの台に送るか」を決める仕組みが新しく必要になります。 ここから先は、その振り分け方式の話です。
負荷分散方式の一覧表
nginx の upstream ブロックに書く設定と合わせた一覧です。「1.24.0 で確認」の列は、今回の環境で動かせたかどうかです。
| 方式 | 送り先の決め方 | nginx の書き方 | 向いている場面 | 1.24.0 で確認 |
|---|---|---|---|---|
| ラウンドロビン | 順番に1件ずつ | (何も書かない。既定) | 各台の性能が同じで、処理時間がそろっている | 動作 |
| 重み付きラウンドロビン | 重みの比で順番に | server ... weight=3; |
台ごとに性能が違う。新しい台へ少しずつ流したい | 動作 |
| 最小接続 | 処理中の接続がいちばん少ない台へ | least_conn; |
処理時間がばらつく。長い接続がある | 動作 |
| IP ハッシュ | 送信元 IP アドレスから決める | ip_hash; |
同じ送信元を同じ台へ送りたい | 動作 |
| ハッシュ(任意のキー) | 指定したキー(URL、Cookie、パラメータなど)から決める | hash $cookie_sessionid consistent; |
利用者やキーごとに同じ台へ送りたい | 動作 |
| ランダム(2台から選ぶ) | 無作為に2台を選び、接続の少ないほうへ | random two least_conn; |
ロードバランサが複数あり、それぞれが振り分ける | 動作 |
| 応答時間 | 平均応答時間が短く、接続が少ない台へ | least_time header; |
台ごとの速さに差がある | 1.24.0 には含まれない(公式ドキュメントの記載では、1.31.0 より前は商用版のみ) |
| Cookie によるスティッキー | nginx が発行した Cookie で同じ台へ | sticky cookie srv_id path=/; |
セッションをサーバーに持つアプリ | 1.24.0 には含まれない(公式ドキュメントの記載では、1.29.6 より前は商用版のみ) |
表計算ソフトに貼れる形(タブ区切り)です。
方式 送り先の決め方 nginxの書き方 向いている場面
ラウンドロビン 順番に1件ずつ (既定) 性能が同じで処理時間がそろっている
重み付きラウンドロビン 重みの比で順番に server ... weight=3; 台ごとに性能が違う
最小接続 処理中の接続がいちばん少ない台へ least_conn; 処理時間がばらつく
IPハッシュ 送信元IPアドレスから決める ip_hash; 同じ送信元を同じ台へ
ハッシュ 指定したキーから決める hash $cookie_sessionid consistent; キーごとに同じ台へ
ランダム(2台から選ぶ) 無作為に2台選び接続の少ないほうへ random two least_conn; ロードバランサが複数ある
応答時間 平均応答時間が短く接続が少ない台へ least_time header; 台ごとの速さに差がある
Cookieによるスティッキー nginxが発行したCookieで同じ台へ sticky cookie srv_id path=/; セッションをサーバーに持つ
条件から選ぶ判断表
| 条件 | 選ぶ方式 |
|---|---|
| 特に事情がない。各台の性能が同じ | ラウンドロビン |
| 台ごとに CPU やメモリが違う | 重み付きラウンドロビン |
| 処理時間が短いものと長いものが混ざる | 最小接続 |
| WebSocket など、つながったままの接続が多い | 最小接続 |
| キャッシュを台ごとに持っていて、同じキーは同じ台で受けたい | ハッシュ(consistent 付き) |
| セッションをサーバーのメモリに持っていて、すぐには変えられない | Cookie によるスティッキー、または Cookie の値でハッシュ |
| 送信元 IP アドレスしか手がかりがない | IP ハッシュ(同じ拠点からの利用が多いと偏る点に注意) |
nginx の upstream 設定(そのまま貼れる形)
確認に使った最小の設定ファイルです。upstream app { ... } の中を入れ替えると方式が変わります。
worker_processes 1;
pid /home/taro/lbtest/nginx.pid;
error_log /home/taro/lbtest/logs/error.log;
events { worker_connections 256; }
http {
access_log off;
upstream app {
# ここに方式を書く(何も書かなければラウンドロビン)
server 127.0.0.1:10091;
server 127.0.0.1:10092;
server 127.0.0.1:10093;
}
server {
listen 127.0.0.1:10090;
location / {
proxy_pass http://app;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
}
パスは一般的な名前に置き換えています。nginx -t -c 設定ファイル で構文を確かめ、nginx -c 設定ファイル で起動します。
nginx: the configuration file ./full.conf syntax is ok
nginx: configuration file ./full.conf test is successful
方式ごとの upstream ブロックです。
# 1. ラウンドロビン(既定)
upstream app {
server 127.0.0.1:10091;
server 127.0.0.1:10092;
server 127.0.0.1:10093;
}
# 2. 重み付きラウンドロビン(app1 に 3、ほかに 1 ずつ)
upstream app {
server 127.0.0.1:10091 weight=3;
server 127.0.0.1:10092;
server 127.0.0.1:10093;
}
# 3. 最小接続
upstream app {
least_conn;
server 127.0.0.1:10091;
server 127.0.0.1:10092;
server 127.0.0.1:10093;
}
# 4. IP ハッシュ
upstream app {
ip_hash;
server 127.0.0.1:10091;
server 127.0.0.1:10092;
server 127.0.0.1:10093;
}
# 5. ハッシュ(URL の user パラメータで決める。Cookie なら $cookie_sessionid)
upstream app {
hash $arg_user consistent;
server 127.0.0.1:10091;
server 127.0.0.1:10092;
server 127.0.0.1:10093;
}
# 6. ランダム(2台を選んで接続の少ないほうへ)
upstream app {
random two least_conn;
server 127.0.0.1:10091;
server 127.0.0.1:10092;
server 127.0.0.1:10093;
}
# 7. 予備の台(backup)と、一時的に外す台(down)
upstream app {
server 127.0.0.1:10091 max_fails=1 fail_timeout=10s;
server 127.0.0.1:10092 down;
server 127.0.0.1:10093 backup;
}
server に付けられる主なパラメータです(公式ドキュメントの記載)。
| パラメータ | 意味 | 既定値 |
|---|---|---|
weight=数 |
重み | 1 |
max_conns=数 |
その台への同時接続数の上限 | 0(上限なし) |
max_fails=数 |
fail_timeout の間にこの回数つながらなければ、利用できないと見なす | 1 |
fail_timeout=時間 |
回数を数える時間、かつ利用できないと見なす時間 | 10秒 |
backup |
予備の台。ほかの台が利用できないときだけ使う(hash・ip_hash・random とは併用できない) | - |
down |
振り分けから外す | - |
nginx 1.24.0 で実際に振り分けた件数
確認用のサーバーと、振り分け先を数えるクライアントです。どちらも標準ライブラリだけで動きます。
# backend.py : 3つのポートで待ち受け、自分の名前を返す確認用サーバー
import sys, time, threading
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
DELAY = {"app1": 0.02, "app2": 0.02, "app3": 0.02}
def make(name):
class H(BaseHTTPRequestHandler):
protocol_version = "HTTP/1.0"
def do_GET(self):
time.sleep(DELAY[name])
body = name.encode()
self.send_response(200)
self.send_header("Content-Length", str(len(body)))
self.end_headers()
self.wfile.write(body)
def log_message(self, *a):
pass
return H
if __name__ == "__main__":
stopped = set()
for arg in sys.argv[1:]: # 例: app3=0.3 で app3 だけ遅くする / app1=stop で app1 を起動しない
k, v = arg.split("=")
if v == "stop":
stopped.add(k)
else:
DELAY[k] = float(v)
for name, port in [("app1", 10091), ("app2", 10092), ("app3", 10093)]:
if name in stopped:
continue
srv = ThreadingHTTPServer(("127.0.0.1", port), make(name))
threading.Thread(target=srv.serve_forever, daemon=True).start()
threading.Event().wait()
# client.py : 振り分け先を数える。 使い方: python3 client.py 件数 同時実行数 [送信元IP...]
import sys, time, socket, collections
from concurrent.futures import ThreadPoolExecutor
def get(src="127.0.0.1", path="/"):
s = socket.create_connection(("127.0.0.1", 10090), source_address=(src, 0))
s.sendall(f"GET {path} HTTP/1.0\r\nHost: localhost\r\n\r\n".encode())
data = b""
while chunk := s.recv(4096):
data += chunk
s.close()
return data.split(b"\r\n\r\n", 1)[1].decode()
if __name__ == "__main__":
n, conc = int(sys.argv[1]), int(sys.argv[2])
t0 = time.time()
with ThreadPoolExecutor(conc) as ex:
res = list(ex.map(lambda i: get(), range(n)))
c = collections.Counter(res)
print(" ".join(f"{k}={c[k]}" for k in sorted(c)), f"所要時間={time.time() - t0:.2f}秒")
ラウンドロビンと重み付き
python3 backend.py &
python3 client.py 12 1 # ラウンドロビン:12件を1件ずつ
python3 client.py 10 1 # 重み付き(3:1:1):10件
python3 client.py 100 1 # 重み付き(3:1:1):100件
app1=4 app2=4 app3=4 所要時間=0.26秒
app1=6 app2=2 app3=2 所要時間=0.21秒
app1=60 app2=20 app3=20 所要時間=2.12秒
重み 3:1:1 は、ちょうど 60%・20%・20% になりました。
1台だけ遅いとき:ラウンドロビンと最小接続の差
app3 だけ1件あたり 0.3 秒かかるようにし(ほかは 0.02 秒)、120件を同時6件で送りました。
python3 backend.py app3=0.3 &
python3 client.py 120 6
| 方式 | app1 | app2 | app3(遅い台) | 所要時間 |
|---|---|---|---|---|
| ラウンドロビン | 40 | 40 | 40 | 2.43秒 |
| 最小接続(least_conn) | 58 | 58 | 4 | 0.62秒 |
| ランダム(random two least_conn)1回目 | 57 | 54 | 9 | 0.99秒 |
| ランダム(random two least_conn)2回目 | 49 | 62 | 9 | 0.95秒 |
ラウンドロビンは遅い台にも同じ40件を送るので、全体が遅い台に引っ張られます。最小接続は、処理中の接続が残っている app3 を避け、所要時間は約4分の1になりました。
IP ハッシュ:同じ送信元は同じ台へ
送信元の IP アドレスを変えながら、3回ずつ送った結果です。
# ipcheck.py : 送信元 IP アドレスごとの振り分け先を表示する(ip_hash の確認用)
from client import get
for ip in ["127.0.0.1", "127.0.0.2", "127.0.0.200", "127.0.1.1", "127.0.2.1", "127.0.3.1", "127.1.0.1", "127.2.0.1"]:
print(ip.ljust(12), [get(ip) for _ in range(3)])
127.0.0.1 ['app3', 'app3', 'app3']
127.0.0.2 ['app3', 'app3', 'app3']
127.0.0.200 ['app3', 'app3', 'app3']
127.0.1.1 ['app1', 'app1', 'app1']
127.0.2.1 ['app2', 'app2', 'app2']
127.0.3.1 ['app3', 'app3', 'app3']
127.1.0.1 ['app2', 'app2', 'app2']
127.2.0.1 ['app1', 'app1', 'app1']
127.0.0.1・127.0.0.2・127.0.0.200 は、すべて同じ app3 になりました。公式ドキュメントに「IPv4 アドレスは先頭の3オクテットをハッシュのキーに使う」と書かれているとおりで、4つ目の数字だけが違う送信元は、同じ台に集まります。
ハッシュ:キーごとに同じ台へ
hash $arg_user consistent; で、URL の user パラメータごとに送った結果です。
# keycheck.py : URL の user パラメータごとの振り分け先を表示する(hash の確認用)
from client import get
for u in ["u001", "u002", "u003", "u004", "u005", "u006"]:
print(u, [get(path=f"/?user={u}") for _ in range(3)])
u001 ['app3', 'app3', 'app3']
u002 ['app1', 'app1', 'app1']
u003 ['app2', 'app2', 'app2']
u004 ['app1', 'app1', 'app1']
u005 ['app1', 'app1', 'app1']
u006 ['app1', 'app1', 'app1']
同じキーは毎回同じ台に届きます。6個のキーでは app1 に4個が集まりました。キーの数が少ないうちは均等になりません。
1台が止まったとき
app1 を起動しない状態(python3 backend.py app1=stop)で送った結果です。
| 設定 | 結果 |
|---|---|
| ラウンドロビン(3台)で 12件 | app2=6 app3=6(止まった台を外して残りで分ける) |
| 設定7(app1 が通常、app2 が down、app3 が backup)で、全台が動いているとき 6件 | app1=6(予備の app3 には届かない) |
| 設定7で、app1 が止まっているとき 6件 | app3=6(予備の app3 が受ける) |
このとき、nginx のエラーログには次の行が残ります。利用者にはエラーを返さず、別の台へ送り直しています。
[error] 5128#5128: *1 connect() failed (111: Connection refused) while connecting to upstream, client: 127.0.0.1, server: , request: "GET / HTTP/1.0", upstream: "http://127.0.0.1:10091/", host: "localhost"
Python の簡易シミュレーションで方式別の偏りを見る
台数や到着の条件を変えて比べたいときは、シミュレーションが手軽です。3000件のリクエストを、処理時間のばらつき(9割は短く、1割は長い)と、送信元の偏り(3割が同じ IP アドレス)を付けて流します。
# sim.py : 振り分け方式ごとの偏りを比べる簡易シミュレーション(標準ライブラリのみ)
import heapq, random, zlib, itertools
SERVERS = ["app1", "app2", "app3"]
WORKERS = 4 # 1台が同時に処理できる件数
N = 3000 # リクエスト数
def make_requests(seed=1):
rnd = random.Random(seed)
t, reqs = 0.0, []
for _ in range(N):
t += rnd.expovariate(100) # 平均 100件/秒で到着
# 処理時間:9割は 0.02〜0.04 秒、1割は 0.5〜1.0 秒(ばらつきが大きい)
work = rnd.uniform(0.5, 1.0) if rnd.random() < 0.1 else rnd.uniform(0.02, 0.04)
# 送信元:3割は同じ拠点(1つの IP アドレス)、残りは 200 個の IP アドレスに分散
ip = "10.0.0.1" if rnd.random() < 0.3 else f"10.0.{rnd.randrange(1, 201)}.1"
reqs.append((t, work, ip))
return reqs
def simulate(method, reqs, seed=2):
rnd = random.Random(seed)
rr = itertools.cycle(SERVERS)
busy = {s: [] for s in SERVERS} # 処理中・待ちの終了時刻(ヒープ)
free_at = {s: [0.0] * WORKERS for s in SERVERS} # 各ワーカーが空く時刻
count = {s: 0 for s in SERVERS}
peak = {s: 0 for s in SERVERS}
total = 0.0
for t, work, ip in reqs:
for s in SERVERS: # 終わったものを取り除く
while busy[s] and busy[s][0] <= t:
heapq.heappop(busy[s])
if method == "ラウンドロビン":
s = next(rr)
elif method == "ランダム":
s = rnd.choice(SERVERS)
elif method == "最小接続":
s = min(SERVERS, key=lambda x: len(busy[x]))
elif method == "IPハッシュ":
s = SERVERS[zlib.crc32(ip.encode()) % len(SERVERS)]
w = min(range(WORKERS), key=lambda i: free_at[s][i])
start = max(t, free_at[s][w])
end = start + work
free_at[s][w] = end
heapq.heappush(busy[s], end)
count[s] += 1
peak[s] = max(peak[s], len(busy[s]))
total += end - t
return count, peak, total / len(reqs)
reqs = make_requests()
print("方式\tapp1\tapp2\tapp3\t最大の同時処理数\t平均応答時間(秒)")
for m in ["ラウンドロビン", "ランダム", "最小接続", "IPハッシュ"]:
count, peak, avg = simulate(m, reqs)
print(m, *[count[s] for s in SERVERS], max(peak.values()), f"{avg:.3f}", sep="\t")
方式 app1 app2 app3 最大の同時処理数 平均応答時間(秒)
ラウンドロビン 1000 1000 1000 75 0.400
ランダム 1011 975 1014 64 0.358
最小接続 1024 963 1013 17 0.153
IPハッシュ 690 736 1574 382 2.404
| 読み取れること | 数字 |
|---|---|
| ラウンドロビンは件数がぴったり均等でも、応答時間は均等にならない | 件数は 1000 ずつ、最大の同時処理数は 75 |
| 最小接続は件数に少し差が出るが、待ちが短い | 最大の同時処理数 17、平均 0.153秒 |
| IP ハッシュは、同じ送信元が多いと1台に集まる | app3 に 1574件、平均 2.404秒 |
件数の均等さと、負荷の均等さは別のものです。 処理時間がそろっていれば件数をそろえるだけで足り、ばらつくときは「いま抱えている件数」を見る方式が合います。この結果は上の条件での計算値で、条件を変えると数字は変わります。
スティッキーセッションとセッション外部化の違いと選び方
ログイン状態などのセッションをサーバーのメモリに持っていると、次のリクエストが別の台に届いたときにセッションが見つかりません。対応は2つあります。
| 比べる点 | スティッキーセッション | セッション外部化 |
|---|---|---|
| 考え方 | 同じ利用者を同じ台に送り続ける | セッションを全台から見える場所(キャッシュサーバーや DB)に置く |
| 変えるところ | ロードバランサの設定 | アプリのセッションの保存先 |
| 台が止まったとき | その台にいた利用者のセッションは消える | 別の台が同じセッションを読める |
| 台を増やしたとき | 新しい台には新しい利用者しか来ない | すぐに全体で分け合える |
| 負荷の偏り | 利用の多い利用者が同じ台に重なることがある | 方式を自由に選べる(最小接続など) |
| 追加で必要なもの | なし | セッションの置き場所と、その運用 |
| 条件 | 選ぶもの |
|---|---|
| これから作る。台を増減させたい | セッション外部化 |
| 台の入れ替え(デプロイ)のたびにログインが切れるのを避けたい | セッション外部化 |
| 既存のアプリで、セッションの保存先をすぐには変えられない | スティッキーセッション |
| 台ごとにキャッシュを温めていて、同じ利用者は同じ台のほうが速い | スティッキーセッション(外部化と併用もできる) |
nginx でのスティッキーセッションの書き方です。sticky cookie は、nginx が発行した Cookie で送り先を覚えます(公式ドキュメントの記載。1.29.6 より前は商用版のみ)。
upstream app {
server 127.0.0.1:10091;
server 127.0.0.1:10092;
sticky cookie srv_id expires=1h path=/;
}
nginx 1.24.0 でこの設定を読ませると、次のメッセージが出ます(ファイルのパスは置き換えています)。
[emerg] 3932#3932: unknown directive "sticky" in ./sticky.conf:9
1.24.0 では、アプリが発行するセッション Cookie の値をハッシュのキーにすると、同じ動きになります。
upstream app {
hash $cookie_sessionid consistent;
server 127.0.0.1:10091;
server 127.0.0.1:10092;
server 127.0.0.1:10093;
}
Cookie の値を変えて3回ずつ送った結果と、app2 を止めたあとの結果です。
aaa111 ['app3', 'app3', 'app3']
bbb222 ['app2', 'app2', 'app2']
ccc333 ['app2', 'app2', 'app2']
ddd444 ['app1', 'app1', 'app1']
-- app2 を止めたあと
aaa111 ['app3', 'app3', 'app3']
bbb222 ['app1', 'app1', 'app1']
ccc333 ['app1', 'app1', 'app1']
ddd444 ['app1', 'app1', 'app1']
app2 にいた bbb222 と ccc333 は app1 に移り、ほかの利用者の送り先は変わりませんでした。 ただし、移った2人のセッションは app1 のメモリにはありません。ここを埋めるのがセッション外部化です。
L4 と L7 の違い
ロードバランサは、通信のどの層を見て振り分けるかで2種類に分かれます。
| 比べる点 | L4(トランスポート層) | L7(アプリケーション層) |
|---|---|---|
| 見るもの | IP アドレスとポート番号 | URL、ヘッダー、Cookie など HTTP の中身 |
| 振り分けの単位 | 接続(TCP・UDP) | リクエスト(HTTP) |
| パスやホスト名での振り分け | できない | できる |
| Cookie によるスティッキー | できない(送信元 IP アドレスなどで代用) | できる |
| HTTP 以外の通信(DB 接続、メールなど) | 扱える | 扱えない(HTTP 向け) |
| nginx での書き方 | stream { upstream ... } |
http { upstream ... } |
nginx の公式ドキュメントでは、L4 にあたる stream の upstream で使える方式は hash・least_conn・least_time・random で、ip_hash はありません。送信元で固定したいときは hash $remote_addr consistent; と書く例が載っています。
| 条件 | 選ぶもの |
|---|---|
| URL のパスで送り先を分けたい(/api は API サーバーへ、など) | L7 |
| Cookie で同じ台に送りたい | L7 |
| HTTP 以外のプロトコルを振り分けたい | L4 |
| 中身を見ずに、接続をそのまま渡したい | L4 |
確かめて分かったこと
- IP ハッシュは、IPv4 アドレスの先頭3オクテットで決まります。 127.0.0.1 と 127.0.0.200 は同じ台になりました。同じネットワークからの利用が多いシステムでは、IP ハッシュにすると1台に集まります。
- 1台だけ遅いとき、ラウンドロビンと最小接続では所要時間が約4倍違いました(2.43秒と 0.62秒)。件数は均等でも、遅い台に同じだけ送ることが全体の時間を決めます。
- 重み 3:1:1 は、10件でも 6・2・2 と正確に配られました。 nginx の重み付きラウンドロビンは、少ない件数でも比が崩れません。
- 止まった台は、設定を変えなくても振り分けから外れました。 既定の max_fails=1・fail_timeout=10秒が働き、エラーログに記録を残して別の台へ送り直します。
least_timeとstickyは、nginx 1.24.0 ではunknown directiveになります。 使う前にnginx -vで版を確かめ、公式ドキュメントの「どの版から使えるか」の記載と見比べます。consistent付きのハッシュでは、1台を止めても、ほかの台に割り当てられていたキーの送り先は変わりませんでした。
よくある質問
Q. スケールアップとスケールアウトの違いは? A. スケールアップは1台の性能を上げること、スケールアウトは台数を増やすことです。状態を持たない Web・API サーバーはスケールアウト、書き込みを受ける台が1つの DB はまずスケールアップが合います。
Q. ラウンドロビンと最小接続の違いは? A. ラウンドロビンは順番に配り、最小接続は処理中の接続が少ない台に配ります。処理時間がそろっていればラウンドロビン、ばらつくなら最小接続を選びます。
Q. スティッキーセッションとは? A. 同じ利用者のリクエストを同じサーバーに送り続ける仕組みです。セッションをサーバーのメモリに持つアプリで使います。台が止まるとその台のセッションは消えるので、台を増減させるならセッション外部化が合います。
Q. L4 ロードバランサと L7 ロードバランサの違いは? A. L4 は IP アドレスとポート番号で接続を振り分け、L7 は URL やヘッダーなど HTTP の中身を見てリクエストを振り分けます。パスや Cookie で分けたいなら L7 です。
Q. nginx で least_time や sticky が使えないときは? A. nginx の版を確かめます。公式ドキュメントでは、least_time は 1.31.0 より前、sticky は 1.29.6 より前は商用版のみと書かれています。代わりに least_conn や、Cookie の値をキーにした hash が使えます。
まとめ
- スケールアップは1台を強くする、スケールアウトは台数を増やす。 状態を持たない処理はスケールアウト、書き込み先が1つの DB はまずスケールアップです。
- 振り分け方式は、迷ったらラウンドロビン、処理時間がばらつくなら最小接続を選びます。
- 台ごとに性能が違うなら重み付き、同じキーを同じ台に送りたいならハッシュです。
- IP ハッシュは先頭3オクテットで決まるので、同じネットワークからの利用が多いと偏ります。
- セッションは外部化が基本、すぐに変えられないときにスティッキーセッションを使います。
- パスや Cookie で分けるなら L7、HTTP 以外や接続単位なら L4 です。
- 件数の均等さと負荷の均等さは別です。実際に流すか、シミュレーションで確かめてから決めます。
参考・出典
- nginx documentation「Module ngx_http_upstream_module」 https://nginx.org/en/docs/http/ngx_http_upstream_module.html
- nginx documentation「Module ngx_stream_upstream_module」 https://nginx.org/en/docs/stream/ngx_stream_upstream_module.html
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「スケールアップとスケールアウトの違い|負荷分散方式の一覧と選び方」
株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。