NAS、ラック、UPSを別々に選ぶと、容量は足りても置けない、載っても配線できない、停電時に安全に止められない、という食い違いが起きます。 この記事では、RAID後の容量、機器と配線の置き方、壁コンセント側の実測Wを一枚の比較表へまとめ、自宅ラボの構成を同じ順序で選べるようにします。 最後のスクリプトを使えば、実効容量、UPSで受ける必要がある観測ピークW、月間kWhの単純外挿を手元のログから再計算できます。
課題:NAS・ラック・電力は一つの構成として選ぶ
NAS選びで最初に見るのは、製品ページの最大容量ではなく、RAIDを組んだ後に残る容量です。ベイ数だけを増やしても、冗長化方式とドライブ本数を決めなければ、置けるデータ量は決まりません。
次に、そのNASをどこへ置くかを決めます。10インチラックは省スペースですが、「10インチ」という呼び名だけでは収納可否を判定できません。棚の有効幅と奥行、背面コネクタの曲げ代、排熱、前面からのドライブ交換をまとめて確認します。具体的な棚寸法と機種例は、GeeekPi 10インチラックにNASとミニPCを載せる構成例に分けました。
最後が電源です。UPSはVAだけでなくW定格を見て、停電時に残す機器を先に決めます。ホームラボの消費電力と発熱を実測するでは測定全体を扱い、SwitchBotプラグミニで自宅サーバーの消費電力を記録する方法では系統を分けて記録する手順を扱っています。
ケミは次の順番で選びます。
- 置きたいデータ量と増加量から、RAID後の必要容量を出す。
- NAS、スイッチ、ミニPC、電源アダプターを、ケーブル込みの実寸で棚へ割り付ける。
- 一日分の実測Wを取り、UPSへ残す系統と停電時に止める系統を分ける。
設計判断:容量・設置・電力の比較表を一枚にする
| 判断軸 | 小さく始める | 余裕を持たせる | このラボでの判断 |
|---|---|---|---|
| NAS | 少ないベイ数、単一パリティ | 多いベイ数、二重パリティ | QNAP TVS-h874-i5-12400の8ベイを維持する |
| 実効容量 | ドライブ1本分を冗長化へ回す | ドライブ2本分を冗長化へ回す | 12TB×8本の二重パリティで、算術上は72TB(約65.5TiB) |
| ラック | 浅型の棚へ小型機器だけ載せる | 奥行と配線空間を広く取る | 8ベイNASはラック外、10インチ側はミニPCとスイッチに分ける |
| ネットワーク | 2.5GbE中心 | 必要な区間だけ10GbE | NAS本体の買い増しより先に、遅い区間を切り分ける |
| UPS | 守る機器を一つの系統へ絞る | ストレージ系とネットワーク系を分ける | NAS・ストレージ系とネットワーク系で停止順序を分ける |
| 電力計測 | UPS系統を一日記録する | UPS内外を別のログにする | UPS系統とサーバ(UPSなし)系統を別々に集計する |
NASは「最大容量」ではなく、現在の役割から決める
現行NASは QNAP TVS-h874-i5-12400 です。Core i5-12400、64GB RAM、NVMeは WD Red SN700 2TB×2、HDDは Seagate Exos ST12000NM001G 12TB×8 のRAID 6/RAIDZ2構成です。
2026年7月4日の読み取りでは、HDD側のzpool2は87T、使用率15%、ONLINEでした。HDDの公称容量は12TB×8本で96TBです。二重パリティへ2本分を回す単純計算では、構成実効容量は 12TB×(8−2)=72TB、2進単位では約65.5TiBになります。
この72TBは比較用の算術値です。NAS OSの予約領域、ファイルシステム、スナップショットの消費分は含めません。選定時は同じ計算方法で候補を並べ、導入後の空き容量はNASの管理画面で別に確認します。
容量、速度、故障は別の課題です。空き容量があるのに遅いならネットワークとワークロードを測り、ドライブ警告が出ているなら交換手順を先に決めます。SSDキャッシュや10GbEは、原因が一致した区間へだけ足します。
10インチラックは本体の幅ではなく、配線と保守まで含めて決める
10インチラックの候補を比べるときは、外形寸法ではなく、実際に使える棚面を基準にします。次の条件を一つでも満たせない機器は、ラック外へ置きます。
- 本体のゴム脚が棚面に残り、重心が棚の内側にある。
- 背面の電源、RJ45、SFP+ケーブルを曲げても排気を塞がない。
- ドライブを前面から抜く空間があり、ラックごと動かさず交換できる。
- 重いNASを下へ置き、ミニPCやスイッチの荷重をNASへ重ねない。
現行の8ベイQNAPは、10インチラックへ押し込む前提にしません。10インチ側はミニPC、スイッチ、短い配線、電源アダプターの整理に使い、NASは冷却とドライブ交換を優先して外置きにします。
UPSはカタログ定格より先に、守る系統を決める
このラボでは、APC Smart-UPS SMT1000J(1000VA / 670W)をNAS・ストレージ系、APCの正弦波550クラスをネットワーク系に分けています。
壁コンセント側の系統ログは、個々のUPS出力を示すものではありません。そのため、系統全体の最大Wを一台のUPSの負荷率へ読み替えません。先に「停電時にも残す機器」を系統単位で決め、その後に各UPS出口の負荷と必要な停止時間を確認します。
再現手順:容量を計算し、ラックを採寸し、一日分のWを測る
RAID後の容量を同じ単位で計算する
同容量HDDなら、単一パリティは (本数−1)×1本の容量、二重パリティは (本数−2)×1本の容量 で比較できます。製品ページのTBとOS表示のTiBは単位が違うため、両方を併記します。
現行構成は12TB×8本の二重パリティなので、比較用の算術値は72TB、約65.5TiBです。ここからNAS OSの予約領域と、データ増加・スナップショット・リビルド中に必要な空きを別枠で考えます。
ラックはケーブルを挿した状態で採寸する
NAS、スイッチ、ミニPC、ACアダプターをミリ単位で測ります。幅は棚の有効幅、奥行は最も飛び出すコネクタ込み、高さはゴム脚と吸排気の空間込みで記録します。
棚面と同じ大きさの紙や段ボールへ機器とケーブルを実際に置くと、背面端子の干渉を購入前に見つけられます。ラックの目的は見た目を揃えることではなく、冷却、配線、交換作業を同じ手順で繰り返せるようにすることです。
UPS内外を別々に記録する
UPS系統とサーバ(UPSなし)系統を別のスマートプラグで記録します。最低限必要な列は、時刻とWです。一日分から有効サンプル数、最小、中央値、平均、95%点、最大を出します。
UPS選びでは最大だけを見ません。平常時は平均、よくある上振れは95%点、観測した下限のW予算は最大で確認します。バックアップ、scrub、起動時の上振れと、停止処理に必要な時間は別に試します。
結果:実測Wから現在の構成を判断する
2026年8月11日00:00〜23:59 JSTのログを集計しました。各ファイルは1分ごとの1440行で、UPS系統は数値Wが入った1439件を集計し、サーバ(UPSなし)系統とメインPC系統は1440件を集計しました。
| 系統 | 有効サンプル | 最小W | 中央値W | 平均W | 95%点W | 最大W |
|---|---|---|---|---|---|---|
| UPS | 1439 / 1440 | 201.7 | 216.5 | 219.1 | 252.4 | 297.1 |
| サーバ(UPSなし) | 1440 / 1440 | 43.7 | 47.3 | 48.3 | 60.6 | 84.0 |
| メインPC | 1440 / 1440 | 129.2 | 132.1 | 131.1 | 132.2 | 134.5 |
平均が30日続く単純外挿では、UPS系統が157.7kWh、サーバ(UPSなし)系統が34.8kWhです。電気料金単価は契約ごとに違うため、ここでは金額へ変換しません。
この結果から、現在の構成は次のように判断できます。
- 8ベイQNAPは維持する。実機のzpool2はONLINEで、先に容量・性能・保守・バックアップのどこが不足しているかを分ける。
- 10インチラックは8ベイNASの収納先にしない。ミニPCとスイッチの整理へ使い、NASは外置きにする。
- UPS系統で観測した下限のW予算は297.1W。ただし複数機器をまとめた入口計測なので、各UPSの負荷率は出口ごとに測る。
- UPS外のサーバ系統は平均48.3W。停電時に止めてよい機器を明示し、守る対象を増やす前に停止順序を決める。
NAS、ラック、UPSの選定は、最大スペックの比較ではなく、データ量、ケーブル込みの実寸、実測Wを同時に満たす構成を探す作業です。
コピペで再現
次の lab_budget.py はPython標準ライブラリだけで動きます。RAID後の比較用容量を計算し、1行1JSONの電力ログから統計、観測ピークW、月間kWhの単純外挿を出します。
#!/usr/bin/env python3
import argparse
import json
import math
import statistics
from pathlib import Path
def watts(path):
values = []
invalid = 0
for line in Path(path).read_text(encoding="utf-8").splitlines():
try:
value = json.loads(line).get("weight_w")
except (json.JSONDecodeError, AttributeError):
invalid += 1
continue
if isinstance(value, (int, float)) and not isinstance(value, bool):
values.append(float(value))
else:
invalid += 1
if not values:
raise SystemExit(f"no numeric weight_w samples: {path}")
return values, invalid
def describe(label, path, days):
values, invalid = watts(path)
ordered = sorted(values)
p95 = ordered[math.ceil(len(ordered) * 0.95) - 1]
average = statistics.fmean(values)
monthly_kwh = average * 24 * days / 1000
print(
f"{label} valid={len(values)} invalid={invalid} "
f"min_w={min(values):.1f} median_w={statistics.median(values):.1f} "
f"avg_w={average:.1f} p95_w={p95:.1f} max_w={max(values):.1f} "
f"monthly_kwh={monthly_kwh:.1f}"
)
return max(values)
parser = argparse.ArgumentParser()
parser.add_argument("--drive-tb", type=float, required=True)
parser.add_argument("--drive-count", type=int, required=True)
parser.add_argument("--parity-count", type=int, required=True)
parser.add_argument("--ups-log", required=True, metavar="LABEL=PATH")
parser.add_argument("--load", action="append", default=[], metavar="LABEL=PATH")
parser.add_argument("--days", type=int, default=30)
args = parser.parse_args()
if args.drive_count <= args.parity_count:
parser.error("drive-count must be greater than parity-count")
if args.days <= 0:
parser.error("days must be positive")
usable_tb = args.drive_tb * (args.drive_count - args.parity_count)
usable_tib = usable_tb * 1_000_000_000_000 / 1_099_511_627_776
print(f"raid_usable_tb={usable_tb:.1f}")
print(f"raid_usable_tib={usable_tib:.1f}")
ups_label, ups_path = args.ups_log.split("=", 1)
ups_peak = describe(ups_label, ups_path, args.days)
print(f"ups_observed_peak_budget_w={ups_peak:.1f}")
for item in args.load:
label, path = item.split("=", 1)
describe(label, path, args.days)
実行コマンドです。
$ chmod +x lab_budget.py
$ ./lab_budget.py --drive-tb 12 --drive-count 8 --parity-count 2 --ups-log UPS=ups-wall-power-20260811.jsonl --load サーバ=server-noups-wall-power-20260811.jsonl --days 30
raid_usable_tb=72.0
raid_usable_tib=65.5
UPS valid=1439 invalid=1 min_w=201.7 median_w=216.5 avg_w=219.1 p95_w=252.4 max_w=297.1 monthly_kwh=157.7
ups_observed_peak_budget_w=297.1
サーバ valid=1440 invalid=0 min_w=43.7 median_w=47.3 avg_w=48.3 p95_w=60.6 max_w=84.0 monthly_kwh=34.8
前提条件:
- Python 3が使えること。追加パッケージは不要です。
- 電力ログが1行1JSONで、数値の
weight_wを持つこと。 --drive-tbはドライブ1本の公称TB、--drive-countは同容量ドライブ本数、--parity-countは冗長化へ回す本数として入力すること。--ups-logには停電時に守る系統のログを指定し、追加の系統は--loadで指定すること。
ハマり所:
- 算出する容量はパリティ控除後の算術値で、NAS OSの予約領域、ファイルシステム、スナップショットは含みません。
- TBは10進、TiBは2進です。同じ数字として比較しません。
ups_observed_peak_budget_wはログで観測した下限です。未観測の起動・バックアップ・scrubの上振れと、停止に必要な時間を別に確認します。- スマートプラグのWは接続した系統の合計です。個別機器や各UPS出口の負荷率へ読み替えません。
- 一日平均から出した月間kWhは単純外挿です。季節、処理内容、起動停止で変わります。
この検証を回している環境
この検証は、自宅の常設ラボ(使い捨てVM/LXCを回す母艦+GPU+VLAN分離ネットワーク)で動かしています。使っている機材と選定理由、全体構成は1本にまとめています。
ラボ構成のまとめを見る →