- 技術 💻
キャパシティ見積もりの計算方法|同時接続数・スループット・ディスク容量
キャパシティ見積もりとは、利用者数や件数といった業務の数字から、同時に処理する量(同時利用者数・毎秒リクエスト数)と、ためておく量(ディスク容量)を計算で求める作業です。 測定する前の設計段階で「どのくらいの大きさを用意するか」を説明できる形にするのが目的です。
この記事では、利用者数 → 同時利用者数 → ピーク時の毎秒リクエスト数 → 必要スレッド数・DB接続数 → ディスク容量までを一続きの式でつなぎ、入力値を変えて再計算できるPython関数と、表計算ソフトに貼れる見積もりシートを掲載します。行サイズはPostgreSQLとMySQLの実テーブルで確かめ、その値を式に入れています。
確認環境:Ubuntu 24.04 / Python 3.11.15 / PostgreSQL 16.15 / MySQL 8.0.46(InnoDB)。見積もりシートの数式は LibreOffice Calc 24.2 で計算し、Python の結果と一致することを確かめています。
キャパシティ見積もりの計算式の早見表
| 手順 | 求める値 | 計算式 | この記事の例 |
|---|---|---|---|
| 1 | 1日の利用者数 | 登録利用者数 × 1日に使う人の割合 | 200,000 × 0.3 = 60,000人 |
| 2 | ピーク1時間の利用者数 | 1日の利用者数 × ピーク1時間に来る割合 | 60,000 × 0.2 = 12,000人 |
| 3 | 同時利用者数 | ピーク1時間の利用者数 × 1回の利用時間(分)÷ 60 | 12,000 × 10 ÷ 60 = 2,000人 |
| 4 | 平均の毎秒リクエスト数 | 同時利用者数 × 1人が1分に送るリクエスト数 ÷ 60 | 2,000 × 4 ÷ 60 = 133.3件/秒 |
| 5 | ピーク時の毎秒リクエスト数 | 平均 × ピーク係数 | 133.3 × 2 = 266.7件/秒 |
| 6 | 設計上の毎秒リクエスト数 | ピーク時 × 安全率 | 266.7 × 1.3 = 346.7件/秒 |
| 7 | 必要スレッド数 | 設計上の毎秒リクエスト数 × 平均応答時間(秒)÷ 目標使用率(切り上げ) | 346.7 × 0.2 ÷ 0.7 → 100本 |
| 8 | 必要DB接続数 | 設計上の毎秒リクエスト数 × DB接続を握る時間(秒)÷ 目標使用率(切り上げ) | 346.7 × 0.05 ÷ 0.7 → 25本 |
| 9 | N月後の件数 | 開始時の件数 + 月ごとの増加件数の合計(増加率を掛けながら足す) | 36か月後 29,076,958件 |
| 10 | データ容量 | 件数 × 1行あたりのサイズ | 29,076,958 × 100バイト = 2.71GB |
| 11 | 必要ディスク容量 | データ容量 ×(1 + 索引の割増)×(1 + 空き領域の割増)× 安全率 | 2.71 × 1.7 × 1.2 × 1.3 = 7.18GB |
毎秒リクエスト数(スループット)は「同時利用者数」と「1人がどのくらいの間隔で操作するか」から決まり、スレッド数や接続数は「毎秒リクエスト数 × 1件にかかる時間」で決まります。 この2つの関係を押さえると、あとは掛け算と割り算だけです。
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「キャパシティ見積もりの計算方法|同時接続数・スループット・ディスク容量」
見積もりで使う3つの考え方
同時利用者数は「来る人数 × 滞在時間」
1時間に12,000人が来て、1人が10分間使うとします。ある瞬間に画面を開いている人数は、12,000人 × 10分 ÷ 60分 = 2,000人です。「一定時間に来る数 × 1つあたりの滞在時間 = 同時にいる数」という関係は、利用者数にも、スレッド数にも、DB接続数にもそのまま使えます。
| 対象 | 来る数 | 滞在時間 | 同時にいる数 |
|---|---|---|---|
| 利用者 | 12,000人/時 | 10分(1/6時間) | 2,000人 |
| リクエスト(スレッド) | 346.7件/秒 | 0.2秒 | 69.3本 |
| DBアクセス(接続) | 346.7件/秒 | 0.05秒 | 17.3本 |
同時利用者数と同時接続数は別の数字
同時利用者数は「画面を開いている人数」、同時接続数は「その瞬間にサーバーが処理している数」です。 画面を読んでいる間はサーバーに仕事が来ないので、処理中の数は利用者数よりずっと小さくなります。上の例では、同時利用者2,000人に対して、処理中のリクエストは約70件です。
一方で、WebSocket のように利用中ずっと接続を保つ方式では、同時利用者数がそのまま同時接続数になります。見積もる前に「どの数を指しているか」を表でそろえておくと、会話がかみ合います。
| 用語 | 意味 | 例の値 |
|---|---|---|
| 登録利用者数 | アカウントを持つ人数 | 200,000人 |
| 同時利用者数 | ある瞬間に利用中の人数 | 2,000人 |
| スループット | 1秒あたりに処理するリクエスト数 | 346.7件/秒(設計値) |
| 同時処理数 | ある瞬間に処理中のリクエスト数 | 69.3件 |
| 必要スレッド数 | 同時処理数を目標使用率で割り、余裕を持たせた数 | 100本 |
余裕は「ピーク係数」「安全率」「目標使用率」の3か所で持つ
余裕の持たせ方を1つの係数にまとめると、何に備えた余裕なのかが見えにくくなります。役割の違う3つに分けて書くと、あとから見直しやすくなります。
| 係数 | 何に備えるか | 掛ける場所 |
|---|---|---|
| ピーク係数 | 1時間の平均に対して、数分単位で集中する山 | 毎秒リクエスト数 |
| 安全率 | 入力値そのものの見込み違い(利用者数、利用時間など) | 毎秒リクエスト数、ディスク容量 |
| 目標使用率 | スレッドや接続を満杯にせず、待ちが出にくい範囲で使う | スレッド数、DB接続数 |
仮に置く値の一覧表
見積もりでは、まだ決まっていない値を仮に置いて計算を進めます。下の値はすべて「例」です。 類似システムのアクセスログや負荷テストの結果が手に入ったら、その値に置き換えてください。
| 項目 | この記事の値 | よく置かれる範囲の例 | 置き換えに使える情報 |
|---|---|---|---|
| 1日に使う人の割合 | 0.3 | 0.05〜0.5 | 類似システムの日別ログイン数 |
| ピーク1時間に来る割合 | 0.2 | 0.1〜0.3 | 時間帯別のアクセス数 |
| 1回の利用時間 | 10分 | 3〜30分 | セッションの開始から最後の操作まで |
| 1人が1分に送るリクエスト数 | 4回 | 1〜10回 | 画面遷移の間隔、1画面あたりのAPI数 |
| ピーク係数 | 2.0 | 1.5〜3.0 | 1分単位の最大値 ÷ 1時間平均 |
| 安全率 | 1.3 | 1.2〜2.0 | 入力値の確からしさで決める |
| 平均応答時間 | 0.2秒 | 0.05〜1.0秒 | 性能目標、または測定値 |
| DB接続を握る時間 | 0.05秒 | 0.01〜0.5秒 | 1リクエスト内のSQLの合計時間 |
| 目標使用率 | 0.7 | 0.5〜0.8 | 待ちをどこまで許すか |
| 月ごとの増加率 | 0.02 | 0〜0.05 | 利用者数の増加計画 |
| 索引の割増 | 0.7 | 0.3〜1.0 | 実テーブルの索引サイズ ÷ データサイズ |
| 空き領域の割増 | 0.2 | 0.1〜0.3 | 更新・削除の多さ |
仮に置いた値は、見積もりの結果と並べて一覧にしておくのがこつです。 「どの値を仮に置いたか」「何が手に入ったら置き換えるか」が残っていれば、数字が変わったときに計算をやり直すだけで済みます。
入力値を変えられるPython関数
早見表の11手順を、2つの関数にまとめました。estimate_load が利用者数からスレッド数・DB接続数まで、estimate_disk が件数からディスク容量までを計算します。標準ライブラリだけで動きます。
"""キャパシティ見積もり:利用者数からスレッド数・DB接続数・ディスク容量まで"""
import math
def estimate_load(
registered_users=200_000, # 登録利用者数
daily_active_ratio=0.30, # 1日に使う人の割合
peak_hour_ratio=0.20, # 1日の利用者のうちピーク1時間に来る割合
session_minutes=10, # 1回の利用時間(分)
requests_per_minute=4, # 利用中の1人が1分間に送るリクエスト数
peak_factor=2.0, # ピーク係数(1時間平均に対する瞬間の山)
safety_factor=1.3, # 安全率
response_sec=0.20, # 1リクエストの平均応答時間(秒)
db_sec=0.05, # うちDB接続を握っている時間(秒)
target_utilization=0.70, # 目標使用率(スレッド・接続をどこまで埋めるか)
):
dau = registered_users * daily_active_ratio
peak_hour_users = dau * peak_hour_ratio
concurrent_users = peak_hour_users * session_minutes / 60
avg_rps = concurrent_users * requests_per_minute / 60
peak_rps = avg_rps * peak_factor
design_rps = peak_rps * safety_factor
threads = math.ceil(design_rps * response_sec / target_utilization)
db_conns = math.ceil(design_rps * db_sec / target_utilization)
return {
"1日の利用者数": round(dau),
"ピーク1時間の利用者数": round(peak_hour_users),
"同時利用者数": round(concurrent_users),
"平均の毎秒リクエスト数": round(avg_rps, 1),
"ピーク時の毎秒リクエスト数": round(peak_rps, 1),
"設計上の毎秒リクエスト数": round(design_rps, 1),
"必要スレッド数": threads,
"必要DB接続数": db_conns,
}
def estimate_disk(
initial_rows=1_000_000, # 稼働開始時の件数
monthly_new_rows=540_000, # 最初の月に増える件数
monthly_growth=0.02, # 月ごとの増加率(前月比)
row_bytes=100, # 1行あたりのサイズ(実測値を入れる)
index_ratio=0.70, # 索引の割増(データに対する割合)
free_ratio=0.20, # 空き領域・更新による膨らみの割増
safety_factor=1.3, # 安全率
months=36,
):
rows = initial_rows
new_rows = monthly_new_rows
result = []
for m in range(0, months + 1):
if m > 0:
rows += new_rows
new_rows *= 1 + monthly_growth
data = rows * row_bytes
total = data * (1 + index_ratio) * (1 + free_ratio) * safety_factor
result.append((m, round(rows), data / 1024**3, total / 1024**3))
return result
if __name__ == "__main__":
print("== 負荷の見積もり ==")
for k, v in estimate_load().items():
print(f"{k}\t{v:,}")
for years in (3, 5):
print(f"\n== ディスク容量 {years}年分(月次) ==")
print("月\t件数\tデータGB\t必要GB")
for m, rows, data_gb, total_gb in estimate_disk(months=years * 12):
print(f"{m}\t{rows:,}\t{data_gb:.2f}\t{total_gb:.2f}")
capacity.py として保存し、次のコマンドで実行します。
python3 capacity.py
負荷の見積もりの実行結果です。
1日の利用者数 60,000
ピーク1時間の利用者数 12,000
同時利用者数 2,000
平均の毎秒リクエスト数 133.3
ピーク時の毎秒リクエスト数 266.7
設計上の毎秒リクエスト数 346.7
必要スレッド数 100
必要DB接続数 25
スレッド数とDB接続数の途中計算
- 設計上の毎秒リクエスト数:133.33 × 2.0 × 1.3 = 346.67件/秒
- スレッド数:346.67 × 0.2秒 = 69.33本(同時処理数)→ 69.33 ÷ 0.7 = 99.05 → 切り上げて100本
- DB接続数:346.67 × 0.05秒 = 17.33本 → 17.33 ÷ 0.7 = 24.76 → 切り上げて25本
アプリケーションサーバーを複数台に分ける場合は、この数を台数で割って1台あたりの設定値にします。たとえば4台なら、スレッドは1台25本、DB接続プールは1台7本(25 ÷ 4 = 6.25 を切り上げ)です。DB側の最大接続数は「1台あたりのプール数 × 台数」に、管理用やバッチ用の接続を足して決めます。
入力値を変えたときの変わり方
どの入力値が結果に効くかを見るために、1つずつ値を変えて計算しました。
from capacity import estimate_load, estimate_disk
cases = [
("基準", {}),
("ピーク係数 3", {"peak_factor": 3.0}),
("応答時間 0.5秒", {"response_sec": 0.5}),
("ピーク1時間の割合 0.3", {"peak_hour_ratio": 0.30}),
("目標使用率 0.5", {"target_utilization": 0.50}),
]
print("条件\t設計上の毎秒リクエスト数\tスレッド数\tDB接続数")
for name, kw in cases:
r = estimate_load(**kw)
print(f"{name}\t{r['設計上の毎秒リクエスト数']}\t{r['必要スレッド数']}\t{r['必要DB接続数']}")
print()
print("条件\t3年後の必要GB\t5年後の必要GB")
for name, kw in [("基準", {}), ("増加率 月3%", {"monthly_growth": 0.03}),
("行サイズ 200バイト", {"row_bytes": 200}), ("索引の割増 1.0", {"index_ratio": 1.0})]:
y3 = estimate_disk(months=36, **kw)[-1][3]
y5 = estimate_disk(months=60, **kw)[-1][3]
print(f"{name}\t{y3:.2f}\t{y5:.2f}")
条件 設計上の毎秒リクエスト数 スレッド数 DB接続数
基準 346.7 100 25
ピーク係数 3 520.0 149 38
応答時間 0.5秒 346.7 248 25
ピーク1時間の割合 0.3 520.0 149 38
目標使用率 0.5 346.7 139 35
条件 3年後の必要GB 5年後の必要GB
基準 7.18 15.46
増加率 月3% 8.69 21.99
行サイズ 200バイト 14.36 30.92
索引の割増 1.0 8.45 18.19
| 変えた値 | わかること |
|---|---|
| ピーク係数 2 → 3 | 毎秒リクエスト数が1.5倍になり、スレッド数もDB接続数も1.5倍になる |
| 応答時間 0.2秒 → 0.5秒 | 毎秒リクエスト数は同じでも、スレッド数が100本から248本に増える |
| ピーク1時間の割合 0.2 → 0.3 | ピーク係数を3にしたのと同じ結果になる |
| 目標使用率 0.7 → 0.5 | スレッド数が100本から139本に増える |
| 行サイズ 100 → 200バイト | ディスク容量がそのまま2倍になる |
応答時間はスレッド数に、行サイズはディスク容量に、そのまま比例して効きます。 この2つは測って確かめられる値なので、早い段階で実際の値に置き換えると見積もりの幅が狭まります。
表計算ソフトに貼れる見積もりシート(TSV)
同じ計算を、表計算ソフトで使える形にしました。下のブロックをコピーして A1 セルに貼り付けると、A〜D列に展開されます。B2〜B11、B20、B21、B23〜B27 が入力欄で、それ以外のB列は数式です。
項目 値 単位 式・メモ
登録利用者数 200000 人 入力
1日に使う人の割合 0.3 割合 入力(仮置き)
ピーク1時間に来る割合 0.2 割合 入力(仮置き)
1回の利用時間 10 分 入力(仮置き)
利用中の1人が1分に送るリクエスト数 4 回/分 入力(仮置き)
ピーク係数 2 倍 入力(仮置き)
安全率 1.3 倍 入力(仮置き)
平均応答時間 0.2 秒 入力(目標値または測定値)
DB接続を握る時間 0.05 秒 入力(目標値または測定値)
目標使用率 0.7 割合 入力(仮置き)
1日の利用者数 =B2*B3 人 登録利用者数×割合
ピーク1時間の利用者数 =B12*B4 人 1日の利用者数×割合
同時利用者数 =B13*B5/60 人 ピーク1時間の利用者数×利用時間÷60
平均の毎秒リクエスト数 =B14*B6/60 件/秒 同時利用者数×1分のリクエスト数÷60
ピーク時の毎秒リクエスト数 =B15*B7 件/秒 平均×ピーク係数
設計上の毎秒リクエスト数 =B16*B8 件/秒 ピーク×安全率
必要スレッド数 =ROUNDUP(B17*B9/B11,0) 本 設計値×応答時間÷目標使用率
必要DB接続数 =ROUNDUP(B17*B10/B11,0) 本 設計値×DB時間÷目標使用率
稼働開始時の件数 1000000 件 入力
1人が1日に登録する件数 0.3 件 入力(仮置き)
最初の月に増える件数 =B12*B21*30 件 1日の利用者数×件数×30日
月ごとの増加率 0.02 割合 入力(仮置き)
1行あたりのサイズ 100 バイト 入力(実テーブルで確かめた値)
索引の割増 0.7 割合 入力(実テーブルで確かめた値)
空き領域の割増 0.2 割合 入力(仮置き)
見積もる月数 36 か月 入力
その月の件数 =B20+B22*((1+B23)^B27-1)/B23 件 開始時+等比数列の和
データ容量 =B28*B24/1024^3 GB 件数×行サイズ
必要ディスク容量 =B29*(1+B25)*(1+B26)*B8 GB データ×索引×空き×安全率
LibreOffice Calc で計算した B列の値は次のとおりで、Python 関数の結果と一致します。
| 行 | 項目 | 計算結果 |
|---|---|---|
| 12 | 1日の利用者数 | 60000 |
| 13 | ピーク1時間の利用者数 | 12000 |
| 14 | 同時利用者数 | 2000 |
| 15 | 平均の毎秒リクエスト数 | 133.33 |
| 16 | ピーク時の毎秒リクエスト数 | 266.67 |
| 17 | 設計上の毎秒リクエスト数 | 346.67 |
| 18 | 必要スレッド数 | 100 |
| 19 | 必要DB接続数 | 25 |
| 22 | 最初の月に増える件数 | 540000 |
| 28 | その月の件数(36か月後) | 29076958.28 |
| 29 | データ容量 | 2.71 |
| 30 | 必要ディスク容量 | 7.18 |
28行目は、月ごとに増加率を掛けながら足した合計を1つの式で求めています。増える件数が毎月 (1 + 増加率) 倍になるので、Nか月分の合計は「最初の月の件数 ×((1 + 増加率) の N 乗 − 1)÷ 増加率」で計算できます。増加率を 0 にするとこの式は 0 で割ることになるため、増加なしで見積もるときは 28行目を =B20+B22*B27 に書き換えてください。
ディスク容量:行サイズを実テーブルで確かめる
ディスク容量の見積もりで結果を大きく左右するのは、1行あたりのサイズです。列の型から机上で足し算する方法もありますが、テスト用のテーブルに実データに近い行を入れて測るほうが、索引の割増も同時にわかって確実です。 ここでは注文テーブルに10万行を入れて確かめます。
PostgreSQL 16 で確かめる
CREATE TABLE orders (
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
member_id integer NOT NULL,
item_code varchar(20) NOT NULL,
quantity integer NOT NULL,
amount numeric(10,0) NOT NULL,
status varchar(10) NOT NULL,
note varchar(200),
ordered_at timestamp NOT NULL
);
CREATE INDEX idx_orders_member ON orders (member_id);
CREATE INDEX idx_orders_ordered ON orders (ordered_at);
INSERT INTO orders (member_id, item_code, quantity, amount, status, note, ordered_at)
SELECT (g % 20000) + 1,
'ITEM-' || lpad((g % 5000)::text, 6, '0'),
(g % 5) + 1,
((g % 90) + 10) * 100,
(ARRAY['new','paid','shipped','done'])[(g % 4) + 1],
CASE WHEN g % 10 = 0 THEN repeat('メモ', 20) END,
timestamp '2026-01-01' + (g || ' seconds')::interval
FROM generate_series(1, 100000) AS g;
ANALYZE orders;
-- 1行あたりの平均サイズ(行の中身)
SELECT count(*) AS row_count,
round(avg(pg_column_size(o.*))) AS avg_row_bytes
FROM orders AS o;
-- テーブル・索引の実サイズと、1行あたりに直した値
SELECT pg_relation_size('orders') AS heap_bytes,
pg_indexes_size('orders') AS index_bytes,
pg_total_relation_size('orders') AS total_bytes,
pg_size_pretty(pg_total_relation_size('orders')) AS total,
round(pg_relation_size('orders') / 100000.0) AS heap_per_row,
round(pg_indexes_size('orders')::numeric / pg_relation_size('orders'), 2) AS index_ratio;
-- 索引ごとの大きさ
SELECT indexrelname, pg_relation_size(indexrelid) AS bytes
FROM pg_stat_user_indexes WHERE relname = 'orders' ORDER BY 1;
row_count | avg_row_bytes
-----------+---------------
100000 | 86
(1 row)
heap_bytes | index_bytes | total_bytes | total | heap_per_row | index_ratio
------------+-------------+-------------+-------+--------------+-------------
9109504 | 6332416 | 15466496 | 15 MB | 91 | 0.70
(1 row)
indexrelname | bytes
--------------------+---------
idx_orders_member | 1810432
idx_orders_ordered | 2260992
orders_pkey | 2260992
(3 rows)
行の中身の平均は86バイト、テーブル全体を行数で割ると91バイトでした。 差の5バイトは、ページごとの管理領域などテーブルを格納するために加わる分です。見積もりには、テーブル全体を行数で割った値(91バイト)のほうを使います。索引は主キーを含めて3本で、データの0.70倍でした。
| 関数 | 返す値(PostgreSQL 16 ドキュメントの説明) |
|---|---|
pg_column_size(値) |
個々の値を格納するのに使うバイト数。テーブルの列に使うと圧縮後の大きさを反映する |
pg_relation_size(テーブル) |
テーブル本体(主データ部分)が使うディスク容量 |
pg_table_size(テーブル) |
索引を除いたテーブルの容量。TOAST、空き領域マップ、可視性マップを含む |
pg_indexes_size(テーブル) |
テーブルに付いた索引の合計容量 |
pg_total_relation_size(テーブル) |
索引とTOASTを含む全体。pg_table_size と pg_indexes_size の合計と同じ |
pg_size_pretty(バイト数) |
読みやすい単位に直す。単位は2のべき乗(1kB = 1024バイト) |
MySQL 8.0(InnoDB)で確かめる
CREATE DATABASE appdb CHARACTER SET utf8mb4;
USE appdb;
CREATE TABLE orders (
id bigint AUTO_INCREMENT PRIMARY KEY,
member_id int NOT NULL,
item_code varchar(20) NOT NULL,
quantity int NOT NULL,
amount decimal(10,0) NOT NULL,
status varchar(10) NOT NULL,
note varchar(200),
ordered_at datetime NOT NULL,
INDEX idx_orders_member (member_id),
INDEX idx_orders_ordered (ordered_at)
) ENGINE=InnoDB;
SET SESSION cte_max_recursion_depth = 100000;
INSERT INTO orders (member_id, item_code, quantity, amount, status, note, ordered_at)
WITH RECURSIVE seq (g) AS (SELECT 1 UNION ALL SELECT g + 1 FROM seq WHERE g < 100000)
SELECT (g % 20000) + 1,
CONCAT('ITEM-', LPAD(g % 5000, 6, '0')),
(g % 5) + 1,
((g % 90) + 10) * 100,
ELT((g % 4) + 1, 'new', 'paid', 'shipped', 'done'),
IF(g % 10 = 0, REPEAT('メモ', 20), NULL),
TIMESTAMPADD(SECOND, g, '2026-01-01')
FROM seq;
ANALYZE TABLE orders;
SELECT table_rows, avg_row_length, data_length, index_length,
data_length + index_length AS total_bytes,
ROUND(index_length / data_length, 2) AS index_ratio
FROM information_schema.tables
WHERE table_schema = 'appdb' AND table_name = 'orders';
SELECT COUNT(*) AS row_count FROM orders;
+------------+----------------+-------------+--------------+-------------+-------------+
| TABLE_ROWS | AVG_ROW_LENGTH | DATA_LENGTH | INDEX_LENGTH | total_bytes | index_ratio |
+------------+----------------+-------------+--------------+-------------+-------------+
| 99762 | 100 | 9977856 | 6324224 | 16302080 | 0.63 |
+------------+----------------+-------------+--------------+-------------+-------------+
+-----------+
| row_count |
+-----------+
| 100000 |
+-----------+
1行あたり100バイト、索引(主キー以外の2本)はデータの0.63倍でした。 TABLE_ROWS が 99762 で、COUNT(*) の 100000 と少し違う点に注目してください。MySQL 8.0 リファレンスマニュアルでは、InnoDB の TABLE_ROWS はSQLの最適化に使うおおよその値で、正確な件数には SELECT COUNT(*) を使うよう案内されています。
| 列 | InnoDB での意味(MySQL 8.0 リファレンスマニュアルの説明) |
|---|---|
TABLE_ROWS |
行数のおおよその値。実際の値と40〜50%違うこともある |
AVG_ROW_LENGTH |
平均の行の長さ |
DATA_LENGTH |
クラスタ化インデックス(主キー順に並んだデータ本体)に割り当てた領域のおおよその大きさ |
INDEX_LENGTH |
クラスタ化インデックス以外の索引に割り当てた領域のおおよその大きさ |
DATA_FREE |
割り当て済みで使われていないバイト数 |
これらの統計値はキャッシュされ、information_schema_stats_expiry の既定値は 86400秒(24時間)です。データを入れた直後に確かめるときは、上の例のように ANALYZE TABLE を実行してから参照します。
2つの製品の結果を見積もりに入れる
| 項目 | PostgreSQL 16 | MySQL 8.0(InnoDB) | 見積もりに入れた値 |
|---|---|---|---|
| 1行あたりのサイズ | 91バイト | 100バイト | 100バイト |
| 索引 ÷ データ | 0.70(主キーを含む3本) | 0.63(主キー以外の2本) | 0.7 |
| 10万行の合計 | 15,466,496バイト | 16,302,080バイト | ― |
PostgreSQL は主キーも独立した索引として数え、InnoDB は主キーがデータ本体に含まれるため、索引の割合の意味が少し違います。どちらの製品でも「合計 ÷ 行数」で1行あたりの総量を出しておくと、製品をまたいで比べられます(PostgreSQL 約155バイト、MySQL 約163バイト)。
3年分・5年分のディスク容量を月次で出す
行サイズ100バイト、索引の割増0.7、空き領域の割増0.2、安全率1.3を入れて、estimate_disk で月次の推移を出します。最初の月に増える件数は「1日の利用者数 60,000人 × 1人1日 0.3件 × 30日 = 540,000件」で、以降は毎月2%ずつ増えるとしています。容量の単位は 1GB = 1024の3乗バイトです。
3年分(36か月)の実行結果です。
月 件数 データGB 必要GB
0 1,000,000 0.09 0.25
1 1,540,000 0.14 0.38
2 2,090,800 0.19 0.52
3 2,652,616 0.25 0.66
4 3,225,668 0.30 0.80
5 3,810,182 0.35 0.94
6 4,406,385 0.41 1.09
7 5,014,513 0.47 1.24
8 5,634,803 0.52 1.39
9 6,267,499 0.58 1.55
10 6,912,849 0.64 1.71
11 7,571,106 0.71 1.87
12 8,242,528 0.77 2.04
13 8,927,379 0.83 2.20
14 9,625,927 0.90 2.38
15 10,338,445 0.96 2.55
16 11,065,214 1.03 2.73
17 11,806,518 1.10 2.92
18 12,562,649 1.17 3.10
19 13,333,902 1.24 3.29
20 14,120,580 1.32 3.49
21 14,922,991 1.39 3.69
22 15,741,451 1.47 3.89
23 16,576,280 1.54 4.09
24 17,427,806 1.62 4.30
25 18,296,362 1.70 4.52
26 19,182,289 1.79 4.74
27 20,085,935 1.87 4.96
28 21,007,654 1.96 5.19
29 21,947,807 2.04 5.42
30 22,906,763 2.13 5.66
31 23,884,898 2.22 5.90
32 24,882,596 2.32 6.15
33 25,900,248 2.41 6.40
34 26,938,253 2.51 6.65
35 27,997,018 2.61 6.91
36 29,076,958 2.71 7.18
5年分(60か月)のうち、37か月目以降です(36か月目までは上と同じ値です)。
月 件数 データGB 必要GB
37 30,178,497 2.81 7.45
38 31,302,067 2.92 7.73
39 32,448,109 3.02 8.01
40 33,617,071 3.13 8.30
41 34,809,412 3.24 8.60
42 36,025,601 3.36 8.90
43 37,266,113 3.47 9.20
44 38,531,435 3.59 9.52
45 39,822,064 3.71 9.84
46 41,138,505 3.83 10.16
47 42,481,275 3.96 10.49
48 43,850,900 4.08 10.83
49 45,247,918 4.21 11.18
50 46,672,877 4.35 11.53
51 48,126,334 4.48 11.89
52 49,608,861 4.62 12.25
53 51,121,038 4.76 12.63
54 52,663,459 4.90 13.01
55 54,236,728 5.05 13.40
56 55,841,463 5.20 13.79
57 57,478,292 5.35 14.20
58 59,147,858 5.51 14.61
59 60,850,815 5.67 15.03
60 62,587,831 5.83 15.46
| 時点 | 件数 | データ容量 | 必要ディスク容量 |
|---|---|---|---|
| 稼働開始 | 1,000,000 | 0.09GB | 0.25GB |
| 1年後 | 8,242,528 | 0.77GB | 2.04GB |
| 3年後 | 29,076,958 | 2.71GB | 7.18GB |
| 5年後 | 62,587,831 | 5.83GB | 15.46GB |
3年後から5年後の2年間で、必要な容量は7.18GBから15.46GBへと2倍以上になります。 毎月2%の増加でも、5年たつと1か月に増える件数は最初の約3.2倍(1.02の59乗)になるためです。月次で出しておくと、「何か月目に何GBを超えるか」がわかり、ディスクを足す時期を計画に入れられます。
テーブルが複数あるとき
テーブルごとに estimate_disk を呼び、同じ月の値を足します。件数の多い上位数テーブルで全体の大半を占めることが多いので、まず件数の多い順に並べ、上位のテーブルから行サイズを確かめると効率よく進められます。
テーブル以外に足す容量
データベースのディスクには、テーブルと索引のほかにも置くものがあります。次の表を見積もりシートの下に足して、合計を出します。
| 足すもの | 見積もりの考え方(例) |
|---|---|
| 更新ログ(PostgreSQL の WAL、MySQL のバイナリログ) | 1日の更新量 × 保持する日数 |
| バックアップ | 1回分の大きさ × 保持する世代数(別のディスクに置く場合も容量は必要) |
| 一時領域 | 大きな並べ替えや索引の作り直しで使う分。最大のテーブル1つ分を目安に置く |
| アプリケーションやDBのログ | 1日の出力量 × 保持する日数 |
| OSとソフトウェア本体 | 固定値として足す |
机上の見積もりと測定の使い分け
この記事の計算は、まだ動くものがない段階で大きさの見当をつけるためのものです。動くものができたら、測定で置き換えていきます。
| 段階 | やること | 得られるもの |
|---|---|---|
| 要件を決める段階 | 利用者数・件数・保存期間を聞き取り、仮の値で計算する | 桁の見当、機器やクラウドの概算 |
| 設計の段階 | テスト用テーブルで行サイズを確かめ、性能目標の応答時間を入れる | 設定値の初期値(スレッド数、接続プール数、ディスク容量) |
| テストの段階 | 負荷テストで応答時間と使用率を測り、入力値を置き換える | 見積もりと測定の差、設定値の見直し |
| 稼働後 | 月次の件数とディスク使用量を、見積もりの月次の表と見比べる | 増設時期の前倒し・後ろ倒しの判断 |
見積もりシートは作って終わりにせず、稼働後も月次の表と見比べる資料として使い続けると価値が続きます。
つまずきやすい点と見直し方
| つまずきやすい点 | 起きること | 見直し方 |
|---|---|---|
| 登録利用者数をそのまま同時利用者数にする | 必要な大きさが何十倍にもなる | 1日に使う割合、ピーク1時間の割合、利用時間の順に絞り込む |
| 1日の件数を86,400秒で割って毎秒の件数にする | 夜間も含めた平均になり、ピークが小さく出る | ピーク1時間の件数を3,600秒で割り、ピーク係数を掛ける |
| 行サイズを列の型の合計だけで出す | 索引や管理領域の分が抜ける | テスト用テーブルに行を入れ、合計サイズ ÷ 行数で確かめる |
| 件数の増加を毎月同じ数で見積もる | 利用者が増える計画のとき、後半の年が小さく出る | 月ごとの増加率を入れて月次で出す |
| 余裕を1つの係数にまとめる | 何に備えた余裕かが説明しにくい | ピーク係数・安全率・目標使用率に分けて書く |
| GBの単位を混ぜる | 1000の3乗と1024の3乗で約7%違う | シートのどこかに単位の定義を書いておく |
よくある質問
Q. 同時利用者数がわからないときは、どこから始めればよいですか? A. 登録利用者数(または想定する利用者数)から、1日に使う割合、ピーク1時間の割合、1回の利用時間の順に掛けていきます。似たシステムのアクセスログがあれば、時間帯別のアクセス数からピーク1時間の割合を直接求められます。
Q. 安全率はいくつにすればよいですか? A. 決まった値はなく、入力値の確からしさで決めます。ログなどの根拠がある値が多ければ小さく(例:1.2)、仮に置いた値が多ければ大きく(例:1.5〜2.0)置き、根拠が手に入るたびに見直します。
Q. スレッド数やDB接続数は、多めに設定しておけば安心ですか? A. 多ければよいとは限りません。スレッドや接続は1本ごとにメモリを使い、DB側にも最大接続数の上限があります。計算で求めた数を初期値にし、負荷テストで使用率を見ながら調整するのが確実です。
Q. ディスク容量は何年分を見積もればよいですか? A. 機器の入れ替え周期や契約期間に合わせるのが一般的です。クラウドのようにあとから広げられる環境では、まず1〜3年分を確保し、月次の表で増設の時期を決めておく方法もあります。
まとめ
- キャパシティ見積もりは「同時に処理する量」と「ためておく量」の2つを、業務の数字から計算で求める作業
- 同時にいる数 = 来る数 × 滞在時間。利用者数、スレッド数、DB接続数のどれにも同じ式が使える
- 毎秒リクエスト数は、同時利用者数 × 1人が1分に送る回数 ÷ 60に、ピーク係数と安全率を掛ける
- スレッド数・DB接続数は、毎秒リクエスト数 × 1件にかかる時間 ÷ 目標使用率を切り上げる
- ディスク容量は、件数 × 行サイズ ×(1 + 索引)×(1 + 空き領域)× 安全率。行サイズは実テーブルで確かめる
- 仮に置いた値は一覧表に残し、根拠が手に入ったら置き換えて再計算する
- 月次で出しておくと、増設の時期を計画に入れられ、稼働後の見比べにも使える
参考・出典
- PostgreSQL 16 Documentation「9.27. System Administration Functions」(Database Object Size Functions):https://www.postgresql.org/docs/16/functions-admin.html
- MySQL 8.0 Reference Manual「The INFORMATION_SCHEMA TABLES Table」:https://dev.mysql.com/doc/refman/8.0/en/information-schema-tables-table.html
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「キャパシティ見積もりの計算方法|同時接続数・スループット・ディスク容量」
株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。