OpenZFS on Windows를 게임 저장소로 테스트해봤습니다. 물리 디스크를 통째로 주는 대신 VHDX 파일 안에 풀을 만들고, 부팅할 때 알아서 붙게 했습니다. 옵션을 고르는 과정과 자동 마운트에서 격은 삽질을 정리합니다. (HDD에서 ZFS를 쓸 때의 단점은 예전 글 참고)
🧭 용도
- 게임 설치용. 업데이트할 때만 쓰고, 플레이할 때는 사실상 읽기만 합니다.
- Windows에서만 씁니다. 리눅스와 풀을 주고받을 계획은 없음.
📚 공식 권장 vs 옵션 덕지덕지 버전
OpenZFS on Windows 위키의 권장 커맨드는 깔끔합니다.
zpool create -O casesensitivity=insensitive -O normalization=formD -O compression=lz4 -O atime=off -o ashift=12 tank PHYSICALDRIVE1반면 어떤 블로그에는 이런 풀옵션 버전이 올라와 있었습니다.
zpool create -O casesensitivity=insensitive -o ashift=12 \
-O acltype=posixacl -O xattr=sa -O atime=off -O relatime=off \
-O recordsize=256k -O dnodesize=auto -O normalization=formD \
-o feature@zilsaxattr=disabled -o feature@head_errlog=disabled \
-o feature@blake3=disabled -o feature@block_cloning=disabled \
-O compression=zstd-3 zfswin disk이게 다 필요한지 하나씩 봤습니다.
🔍 옵션별 판단
| 옵션 | 의미 | 판단 |
|---|---|---|
casesensitivity=insensitive | 대소문자 구분 안 함 | Windows면 필수 |
normalization=formD | 유니코드 파일명 정규화 | 포함 |
ashift=12 | 4K 섹터 정렬 | 명시 권장 |
atime=off | 접근 시간 기록 끄기 | 포함 |
relatime=off | atime 완화 모드 끄기 | atime=off면 의미 없음 |
acltype=posixacl, xattr=sa | 리눅스(Samba 등)용 튜닝 | Windows 전용이면 불필요 |
dnodesize=auto | large_dnode 기능 활성화 | 다른 구현과의 호환성에 영향, 빼기 |
recordsize=256k | 블록 상한(기본 128K) | 아래에서 |
compression | lz4 vs zstd-3 | 아래에서 |
feature 플래그 4개(zilsaxattr, head_errlog, blake3, block_cloning)는 모두 OpenZFS 2.2에서 들어온 기능입니다. 켜져 있으면 2.2보다 오래된 구현에서는 풀을 import할 수 없으니, 구버전 리눅스와 풀을 주고받을 거라면 끄는 게 맞습니다. 공유하지 않는다면 굳이 끌 필요는 없는데, block_cloning만은 2.2 초기에 데이터 손상 이슈가 있었던 기능이라 안정성 차원에서 꺼두었습니다.
🤔 recordsize를 256k로 키울까?
게임 에셋은 수 GB짜리 아카이브(.pak 등)를 쭉 읽는 패턴이라 큰 블록이 유리해 보입니다. 몇 가지 확인해보니:
- recordsize는 상한선입니다. recordsize보다 작은 파일은 파일 크기에 맞는 블록 하나로 저장되니, 작은 파일 때문에 공간이 낭비되지는 않습니다.
- ARC도 블록 단위로 캐시합니다. 블록이 커지면 같은 ARC 크기에 담기는 블록 개수는 줄어듭니다.
- ARC는 기본적으로 압축된 채로 블록을 들고 있습니다. 다만 게임 에셋은 이미 압축된 경우가 많아서 여기서 얻는 건 별로 없습니다.
결론적으로 순차 읽기 위주라 128K든 256K든 체감 차이는 미미할 것 같아서, 기본값 128K를 그대로 두었습니다. 필요하면 나중에 데이터셋 단위로 바꿀 수도 있고요.
🗜️ lz4 vs zstd-3
zstd-3이 압축률은 좋지만, lz4는 해제가 아주 빠릅니다. 읽기가 대부분인 게임 용도에서는 lz4가 맞고, 어차피 에셋이 이미 압축돼 있어서 zstd의 압축률 이득도 크지 않을 겁니다.
✅ 최종 커맨드
VHDX를 만들어 붙이면 새 디스크로 보입니다. PowerShell Get-Disk의 Number가 n이면 PHYSICALDRIVEn입니다. 풀 이름은 vhdxo로 했습니다.
zpool create ^
-O casesensitivity=insensitive ^
-O normalization=formD ^
-O compression=lz4 ^
-O atime=off ^
-o ashift=12 ^
-o feature@block_cloning=disabled ^
vhdxo PHYSICALDRIVEn공식 권장에 block_cloning 비활성화만 더한 겁니다.
🔁 부팅할 때 자동으로 붙이기
VHDX는 재부팅하면 분리됩니다. 그래서 부팅 때마다 VHDX를 붙이고 zpool import를 해줘야 합니다. 작업 스케줄러로 하되, 스크립트 파일 없이 인라인으로 끝내고 싶었습니다.
삽질 1: schtasks
처음엔 schtasks로 등록하려고 /tr "powershell -Command \"...\""처럼 썼는데 /sc가 없다는 에러가 났습니다. CMD는 \"를 이스케이프로 보지 않아서 따옴표 짝이 꼬이고, 뒤에 오는 /sc까지 /tr 인자에 삼켜져 버린 겁니다. 게다가 schtasks는 작업 하나에 동작(action)을 하나밖에 못 넣습니다.
PowerShell의 Register-ScheduledTask를 쓰면 동작도 트리거도 여러 개 넣을 수 있고, 동작은 적은 순서대로 실행됩니다. 두 작업을 따로 만들고 지연 시간으로 순서를 맞출 필요가 없어집니다.
삽질 2: Mount-VHD가 없다
Mount-VHD는 Hyper-V PowerShell 모듈에 들어 있어서, 없으면 먼저 켜야 합니다(재부팅 필요).
dism /online /enable-feature /featurename:Microsoft-Hyper-V-Management-PowerShell /all /norestartHyper-V 기능을 건드리기 싫다면 diskpart 스크립트(select vdisk file=... / attach vdisk)로 대체할 수 있는데, 이때는 스크립트 파일이 하나 필요합니다.
삽질 3: 시작 트리거만으로는 안 붙었다
제 환경에서는 ‘시작 시’ 트리거만으로는 풀이 안 붙어서 ‘로그온 시’ 트리거도 같이 걸었습니다. 이미 붙어 있을 때 두 번째로 실행되면 Mount-VHD와 import가 “이미 있음” 에러를 내고 끝나니 해는 없습니다.
최종 등록 스크립트 (관리자 PowerShell)
$triggers = @(
New-ScheduledTaskTrigger -AtStartup
New-ScheduledTaskTrigger -AtLogOn
)
$actions = @(
New-ScheduledTaskAction -Execute "powershell" -Argument "-Command Mount-VHD -Path 'O:\zfs.vhdx'"
New-ScheduledTaskAction -Execute "zpool" -Argument "import vhdxo"
)
$principal = New-ScheduledTaskPrincipal -UserId "SYSTEM" -RunLevel Highest
Register-ScheduledTask -TaskName "ZFS Auto Mount" -Trigger $triggers -Action $actions -Principal $principal📝 정리
- Windows 전용 풀이면 공식 권장 옵션 +
block_cloning끄기면 충분합니다. 나머지는 리눅스용 튜닝이거나 구버전 호환용입니다. - recordsize는 상한선이고, 순차 읽기 위주라면 기본 128K로도 충분합니다.
- 읽기 위주면 lz4.
- VHDX 자동 마운트는
schtasks대신Register-ScheduledTask. 동작 여러 개를 순서대로 넣을 수 있습니다.

답글 남기기