PUE·WUE 계산하기: Python CSV로 단위·기간·결측값 검증하기
PUE는 시설 전체 에너지를 IT 장비 에너지로 나누고, WUE는 정해진 범위의 물 사용량을 IT 전력량으로 나눕니다. CSV를 계산할 때는 단위와 측정 기간부터 맞춰야 합니다. 월별 PUE·WUE를 단순평균하거나, 빈 물 사용량을 0으로 채우면 계산이 끝나도 의미가 달라질 수 있습니다. 이번에는 그런 입력을 확인하는 작은 Python 계산기를 만듭니다.
PUE가 1보다 작게 나오면 시설 총소비량 대신 순구매량·GPU 보드 사용량 등을 넣었는지 먼저 확인하세요. 원자료를 보존한 채 단위·기간·계량 범위를 대조합니다. 값을 1로 강제로 맞추거나 결측값을 0으로 바꾸지 않습니다.
기준일: 2026년 10월 28일. Windows·Python 3.14.3에서 표준 라이브러리만 사용해 행동 검사 24개와 아래 CSV의 명령행 실행을 검증했습니다. 예제는 모두 자체 제작한 가상 월별 집계입니다. 실제 데이터센터 계량·냉각 장비·GPU·LLM 실험은 하지 않았습니다.
목차 · 계산 전에 구분할 용어
- 시설 전체 에너지: IT 장비와 냉각·전원 손실 등 정해진 시설 경계의 합계입니다.
- kW·kWh: kW는 전력, kWh는 기간에 걸쳐 사용한 전력량입니다. 같은 숫자가 아닙니다.
- 측정 범위: 어느 시설·계량기·물 용도를 포함했는지 정한 경계입니다.
- 가중 합계: 각 월의 분자·분모를 각각 더한 뒤 나누는 방식입니다.
- 결측값: 관측하지 못한 값입니다. 실제 0과 구분합니다.
1. 무엇을 나눌지 먼저 고정합니다
NREL·DOE 설계 가이드의 8장은 연간 시설 전체 에너지와 IT 장비 에너지로 PUE를 정의합니다. 냉각 전력량을 IT 전력량으로 나눈 값에 PUE라는 이름을 붙이면 안 됩니다. 시설 전체 에너지에는 IT 에너지도 포함되기 때문입니다. 또한 PUE는 지원 인프라의 효율 지표이며, 같은 전력량으로 유용한 답을 얼마나 만들었는지는 별도 문제입니다.
Microsoft의 WUE 설명은 냉각·가습에 쓰인 연간 물의 리터 수를 연간 IT 장비 전력량으로 나눕니다. 비교 자료가 취수량인지 소비량인지, 냉각만 포함하는지 다른 용도도 포함하는지 확인해야 합니다. 아래 프로그램은 그 정의를 자동으로 판별하지 않고, 입력한 water_basis를 결과에 남깁니다.
PUE = 시설 전체 에너지 / IT 장비 에너지
WUE = 정의한 물 사용량[L] / IT 전력량[kWh]
기간 비율 = 기간의 분자 합계 / 같은 기간의 분모 합계
이 실습은 시설 에너지가 모두 전기로 계량된 경우를 가정합니다. 다른 에너지원이 있거나 순구매 전력만 측정했다면 정식 PUE 보고에 필요한 전체 에너지 경계와 다를 수 있습니다. 그런 자료를 그대로 넣고 인증된 PUE라고 부르지 않습니다.
GPU 모니터링 값만 확보했다면 GPU 사용률·요청 성과·에너지 범위를 나눠 읽는 글을 먼저 확인하세요. GPU 보드 전력량은 시설 전체 전력량을 대신하지 않습니다.
2. kW를 kWh 칸에 넣지 않습니다
| 입력 단위 | 이 계산기의 처리 |
|---|---|
| kWh | 전력량 그대로 사용 |
| MWh | 1,000을 곱해 kWh로 통일 |
| L | 물 부피 그대로 사용 |
| m3 | 1,000을 곱해 L로 통일 |
| kW·MW | 전력량이 아니므로 거부 |
kW에서 kWh를 구하려면 해당 구간의 시간과 전력 변화가 필요합니다. 한 시점의 100kW를 월간 100kWh로 옮기지 마세요. 이 계산기는 순간 전력을 적분하거나 누적 계량기 지침의 차이를 구하는 도구가 아닙니다. 먼저 신뢰할 수 있는 월간 전력량을 준비해야 합니다.
① 원자료와 계량 범위 보존
↓ ② 같은 시설·같은 월·같은 물 기준 확인
↓ ③ kWh와 L로 변환
↓ ④ 분자 합계 ÷ IT 전력량 합계
↓ ⑤ 기간·미확인 항목을 결과와 함께 보관
직접 만든 텍스트 도식입니다. 숫자가 맞는지 확인하는 절차이며, 계량기 정확도나 실제 경계를 자동으로 검증한다는 뜻은 아닙니다.
3. 가상 월별 CSV를 준비합니다
아래를 example.csv로 저장하세요. 모든 행은 해당 월 전체 기간의 확정 집계라는 가정입니다. UTF-8 또는 UTF-8 BOM을 지원하며 구분자는 쉼표입니다. 필드 순서까지 아래 헤더와 같아야 합니다. 숫자에는 천 단위 쉼표를 넣지 않습니다.
month,scope_id,facility,facility_unit,it,it_unit,water,water_unit,water_basis
2026-01,demo-a,200,MWh,100,MWh,10,m3,consumption
2026-02,demo-a,1080000,kWh,900000,kWh,180000,L,consumption
scope_id는 같은 시설·계량기 경계·물 용도를 묶는 식별자입니다. 예제의 demo-a는 실제 사업장 이름이 아닙니다. water_basis의 consumption은 소비량, withdrawal은 취수량이라는 입력 선언입니다. 두 기준이 섞이면 합산하지 않습니다. 실제 자료의 정의를 확인하지 않고 문자열만 통일하는 방법은 올바른 수정이 아닙니다.
1월은 시설 200MWh·IT 100MWh·물 10m³, 2월은 시설 1,080,000kWh·IT 900,000kWh·물 180,000L를 가정했습니다. 서로 다른 단위를 의도적으로 섞었지만, 계산 전에 같은 단위로 바꿉니다. 두 행은 실측이나 특정 업체의 성능이 아닙니다.
4. 계산기를 저장하고 실행합니다
다음 전체 코드를 같은 폴더의 pue_wue.py로 저장하세요. Python 표준 라이브러리만 사용합니다. csv.DictReader 문서에 맞춰 헤더로 열을 읽고 newline=''로 엽니다. CSV에서 읽은 문자열을 Decimal로 바꾸며, NaN·Infinity도 입력으로 허용하지 않습니다. 연산 정밀도는 28자리이므로 반복소수가 무한히 정확한 문자열로 출력되는 것은 아닙니다.
import csv
import json
import sys
from datetime import datetime
from decimal import Decimal, DecimalException, localcontext
FIELDS = ['month', 'scope_id', 'facility', 'facility_unit', 'it',
'it_unit', 'water', 'water_unit', 'water_basis']
ENERGY = {'kWh': Decimal(1), 'MWh': Decimal(1000)}
WATER = {'L': Decimal(1), 'm3': Decimal(1000)}
def quantity(row, name, factors, allow_zero=False):
text = row[name].strip()
if not text:
raise ValueError(f'{name}: missing value is not zero')
value = Decimal(text)
if not value.is_finite() or value < 0 or (value == 0 and not allow_zero):
raise ValueError(f'{name}: invalid finite quantity')
unit = row[name + '_unit'].strip()
if unit not in factors:
raise ValueError(f'{name}: unsupported unit {unit!r}')
return value * factors[unit]
def calculate(file):
with open(file, encoding='utf-8-sig', newline='') as handle:
reader = csv.DictReader(handle, strict=True)
if reader.fieldnames != FIELDS:
raise ValueError('header must match the documented schema')
rows = list(reader)
if not rows:
raise ValueError('CSV has no data rows')
months, scopes, bases = set(), set(), set()
facility = it = water = Decimal(0)
with localcontext() as context:
context.prec = 28
for row in rows:
if set(row) != set(FIELDS) or any(v is None for v in row.values()):
raise ValueError('row has missing or extra cells')
month = row['month'].strip()
date = datetime.strptime(month, '%Y-%m')
if date.strftime('%Y-%m') != month or month in months:
raise ValueError('month must be unique YYYY-MM')
months.add(month)
scope = row['scope_id'].strip()
basis = row['water_basis'].strip()
if not scope or basis not in {'consumption', 'withdrawal'}:
raise ValueError('scope_id and explicit water_basis are required')
scopes.add(scope)
bases.add(basis)
f = quantity(row, 'facility', ENERGY)
i = quantity(row, 'it', ENERGY)
w = quantity(row, 'water', WATER, allow_zero=True)
if f < i:
raise ValueError('facility energy below IT: check meter boundaries')
facility, it, water = facility + f, it + i, water + w
if len(scopes) != 1 or len(bases) != 1:
raise ValueError('do not combine different scopes or water bases')
ordered = sorted(months)
indices = [int(m[:4]) * 12 + int(m[5:]) for m in ordered]
if any(b - a != 1 for a, b in zip(indices, indices[1:])):
raise ValueError('months must be consecutive; missing months are not zero')
annual = (len(ordered) == 12 and ordered[0].endswith('-01')
and ordered[-1] == ordered[0][:4] + '-12')
def text(value):
result = format(value, 'f')
return result.rstrip('0').rstrip('.') if '.' in result else result
return {'months': ordered, 'scope_id': next(iter(scopes)),
'water_basis': next(iter(bases)), 'complete_calendar_year': annual,
'facility_kwh': text(facility), 'it_kwh': text(it),
'water_l': text(water), 'period_pue': text(facility / it),
'period_water_l_per_it_kwh': text(water / it)}
if __name__ == '__main__':
try:
if len(sys.argv) != 2:
raise ValueError('usage: python pue_wue.py input.csv')
result = calculate(sys.argv[1])
print(json.dumps(result, ensure_ascii=False, indent=2))
except (ValueError, OSError, csv.Error, DecimalException) as error:
print(f'ERROR: {error}', file=sys.stderr)
sys.exit(2)
Windows PowerShell에서 두 파일이 있는 폴더로 이동한 뒤 실행합니다.
python pue_wue.py example.csv
검증 환경에서 종료 코드는 0이었고, 출력 JSON의 주요 값은 다음과 같았습니다.
"facility_kwh": "1280000"
"it_kwh": "1000000"
"water_l": "190000"
"period_pue": "1.28"
"period_water_l_per_it_kwh": "0.19"
"complete_calendar_year": false
시설 합계는 1,280,000kWh, IT 합계는 1,000,000kWh이므로 기간 PUE는 1.28입니다. 물 190,000L를 IT 1,000,000kWh로 나눈 기간 물 비율은 0.19L/kWh입니다. 시설 전력량을 물 비율의 분모로 넣지 않습니다.
5. 월별 비율을 단순평균하면 왜 달라질까요
1월 PUE는 2.00, 2월 PUE는 1.20입니다. 이를 단순평균하면 1.60이지만, 두 달의 IT 전력량이 다르므로 전체 기간의 1.28과 일치하지 않습니다. 전체 기간의 답은 월별 분자·분모를 각각 합산한 뒤 나눠 구합니다. 분모가 모두 같으면 단순평균도 일치할 수 있지만, 일반 규칙으로 가정하면 안 됩니다.
물 비율도 같은 문제입니다. 1월은 0.10L/kWh, 2월은 0.20L/kWh로 단순평균 0.15가 나오지만 기간 합산값은 0.19입니다. 이번 숫자는 1월·2월 두 달이므로 연간 실적이라고 부르지 않습니다. 출력 키에도 period라는 이름을 붙였습니다.
complete_calendar_year는 같은 해 1~12월의 연속된 라벨 12개가 있는지만 확인합니다. 각 행이 월 전체를 빠짐없이 측정했는지, 누락된 계량기가 없는지, 공식 보고 범위에 맞는지는 사람이 별도로 확인해야 합니다. true라고 나왔다고 공식 연간 PUE·WUE가 인증되는 것은 아닙니다.
6. 오류를 건너뛰기보다 원자료로 돌아갑니다
| 거부하는 입력 | 먼저 확인할 것 |
|---|---|
| 빈 값·NaN·Infinity | 미관측 원인과 집계 완료 여부 |
| IT 0·음수, 물 음수 | 사용량인지 계량기 지침인지 |
| 시설 에너지 < IT | 순구매량·부분 시설·기간 불일치 |
| 같은 월 중복·중간 월 누락 | 행 중복과 월별 집계 범위 |
| 범위·물 기준 혼용 | 같은 집계로 볼 근거가 있는지 |
입력이 거부되면 결과 JSON을 출력하지 않고 ERROR: 메시지와 종료 코드 2를 남깁니다. 검증하려고 빈 물 값을 0으로 채워 통과시키지 마세요. 반대로 실제로 계량된 물 사용량이 0이면 입력 0을 허용합니다. 이는 해당 물 범위의 0일 뿐, 발전·제조·건설·생활용수까지 전부 0이라는 뜻이 아닙니다.
데이터센터 여러 곳을 한 파일로 합산하거나 서로 다른 계량 범위를 섞는 기능은 넣지 않았습니다. 실무에서는 범위별로 분리한 뒤, 합칠 수 있는 자료인지 먼저 판단하세요. 이 계산기는 센서 수집기·이상 탐지기·공식 환경 보고 검증기가 아닙니다.
Appendix · 실행한 것과 남는 한계
Windows·Python 3.14.3에서 CLI 정상 출력, 단위 혼용, 가중 합계, BOM, 실제 0, 월 순서, 연간 라벨과 잘못된 입력 거부를 포함한 행동 검사 24개를 통과했습니다. Python 3.14 문서는 확인 시점에 3.14.10으로 표시됐습니다. 문서의 패치 버전과 실행 버전을 구분하며 다른 OS·버전에서 실행했다고 주장하지 않습니다.
가상 CSV만 실행했고 실제 시설 에너지·물·AI 요청을 측정하지 않았습니다. 두 달의 계산 결과를 연간 수치로 확대하거나, 계산된 비율만으로 냉각 설비의 우열·절감액·물 부족·AI 서비스 품질을 판단할 수 없습니다. 공백과 단위의 모순을 줄이는 작은 확인 도구로 사용하세요.
- NREL·DOE: Best Practices Guide for Energy-Efficient Data Center Design — 표지 개정일2024-07; URL 경로의2025-07과 구분
- Microsoft: 에너지·물 효율 측정 정의 — 게시일미표기;2026-10-28확인
- Python 3.14: csv.DictReader와 파일 열기 — 확인시문서3.14.10;실행3.14.3과패치버전구분
- Python 3.14: Decimal과 유한값·연산 정밀도 — 확인시문서3.14.10;실행3.14.3
댓글
댓글 쓰기
안녕하세요 :)