서버의 메인보드를 교체하면서 ZFS 풀을 다시 구성했는데, 그 때 실수했다. 급한 마음에 인터넷 검색을 했는데, 오라클 가라사대…
ZFS 로그 또는 캐시 장치는 zpool add
명령을 사용하여 기존 ZFS 저장소 풀에 추가할 수 있습니다. zpool add 명령을 사용할 때는 각별히 신중해야 합니다. 실수로 로그 장치를 일반 풀 장치로 추가하면 처음부터 다시 풀을 삭제하고 복원해야 합니다. 개별 로그 장치 자체는 풀에서 제거할 수 있습니다.
https://docs.oracle.com/cd/E56343_01/html/E53992/gnjlj.html
랍신다…
그래서 Gemini 에게 물어봐서 어떻게든 해결했다. 이 아래는 Gomini 가 내용을 정리해 준 걸 고대로 가져다 붙인거다.
홈서버에서 ZFS 풀(tank0)의 읽기 성능 향상을 위해 NVMe SSD를 L2ARC 캐시로 추가하려다, 명령어에 cache 키워드를 빼먹는 아찔한 실수를 저질렀습니다. 다행히 데이터 손실 없이 전체 백업 후 풀을 재구성하여 무사히 복구한 과정을 기록으로 남깁니다.
1. 사건의 발단: cache 키워드 누락
원래 입력했어야 할 명령어는 다음과 같습니다.
Bash
sudo zpool add tank0 cache /dev/nvme0n1
하지만 급한 마음에 cache를 빼먹고 아래처럼 실행해 버렸습니다.
Bash
sudo zpool add -f tank0 /dev/nvme0n1
무엇이 문제인가?
zpool status로 확인해 보니, NVMe SSD가 별도의 cache 섹션이 아니라 기존 RAIDZ2 그룹과 동급의 최상위 단일 VDEV로 편입되어 버렸습니다.
Plaintext
config:
NAME STATE READ WRITE CKSUM
tank0 ONLINE 0 0 0
raidz2-0 ONLINE 0 0 0
... (기존 HDD 6개)
nvme0n1 ONLINE 0 0 0 <-- 최상위 VDEV로 들어감!
- 치명적 위험: 디스크 2개 고장까지 버티는
raidz2풀에 단일 디스크(nvme0n1)가 스트라이프(RAID 0 형태)로 묶이면서, 이 NVMe SSD 단 1개만 고장 나도 약 2TB에 달하는 풀 전체 데이터가 증발하는 단일 장애점(SPOF)이 생겨버렸습니다. - 제거 불가: ZFS의
zpool remove명령은 모든 최상위 VDEV가 동일한 타입(단일 디스크 또는 미러)일 때만 작동합니다. 기존 풀이raidz2였기 때문에cannot remove nvme0n1: invalid config에러가 발생하며 제거가 불가능했습니다.
2. 해결 전략
RAIDZ가 포함된 풀에서는 잘못 추가된 최상위 VDEV를 빼낼 수 없으므로, 풀을 백업한 뒤 파괴(Destroy)하고 올바르게 재생성하는 것이 유일하고 안전한 해결책입니다.
- 풀을 읽기 전용(
readonly=on)으로 전환해 추가 쓰기 및 데이터 분산 오염 방지 - 여분의 스토리지에
zfs send로 전체 스냅샷 스트림 백업 - 기존 풀 언마운트 및 파괴(
zpool destroy) - 올바른
raidz2+cache구조로 풀 재생성 - 백업 데이터 복원(
zfs receive) 및 권한 복구
3. 단계별 복구 과정
1단계: 서비스 중지 및 읽기 전용(Read-Only) 잠금
새로운 파일이 쓰여 NVMe로 데이터가 분산되는 것을 막기 위해 관련 서비스를 내리고 풀을 읽기 전용으로 잠갔습니다.
Bash
# 서비스 중지
sudo systemctl stop mariadb gitea calibre-web komga apache2 docker
# 풀 전체 읽기 전용 전환
sudo zfs set readonly=on tank0
2단계: 재귀 스냅샷 생성 및 전체 백업 (zfs send)
약 2.05TB의 데이터를 옮기기 위해 여유 공간이 3.4TB 남아있던 보조 볼륨(/mnt/syn2)을 백업 저장소로 활용했습니다.
Bash
# 전체 하위 데이터셋 포함 재귀 스냅샷 생성
sudo zfs snapshot -r tank0@migration
# 백업 파일로 스트림 전송 (약 1시간 30분~2시간 소요)
sudo zfs send -pvR tank0@migration | sudo tee /mnt/syn2/zfs_backup/tank0_full.zfs > /dev/null
zfs list -t snapshot으로 모든 데이터셋의 스냅샷 생성을 확인한 뒤 전송을 시작했습니다. 백업은 순차 읽기 위주라 초당 300~400MB/s 속도로 비교적 빠르게 완료되었습니다.
3단계: 기존 풀 해체 및 NVMe 초기화
백업 파일 용량이 정상(약 2TB)인 것을 확인한 뒤, 마운트를 해제하고 풀을 삭제했습니다. (pool is busy가 뜰 경우 fuser 등으로 점유 프로세스를 정리해야 합니다.)
Bash
# 모든 데이터셋 강제 언마운트
sudo zfs unmount -f -a
# 풀 삭제
sudo zpool destroy -f tank0
# NVMe에 남은 기존 ZFS 메타데이터 지우기
sudo wipefs -a /dev/nvme0n1
4단계: 풀 재생성 및 정식 cache 등록
기존 6개의 하드디스크로 raidz2 풀을 다시 만들고, 이번에는 반드시 cache 키워드를 포함하여 NVMe를 등록했습니다. (추후 디스크 순서 꼬임 방지를 위해 /dev/disk/by-id/ 경로 사용을 권장합니다.)
Bash
# RAIDZ2 풀 생성
sudo zpool create -o ashift=12 tank0 raidz2 <디스크1> <디스크2> <디스크3> <디스크4> <디스크5> <디스크6>
# L2ARC 캐시 추가
sudo zpool add tank0 cache /dev/nvme0n1
sudo zpool status tank0 명령으로 raidz2-0 아래에 디스크 6개가, 그리고 별도의 cache 항목 아래에 nvme0n1이 정상적으로 자리 잡은 것을 확인했습니다.
5단계: 데이터 복원 (zfs receive) 및 마무리
백업해 둔 스트림 파일을 새 풀로 밀어 넣어 복원했습니다.
Bash
sudo zfs receive -Fv tank0 < /mnt/syn2/zfs_backup/tank0_full.zfs
💡 복원 시 참고 사항 (매우 오래 걸립니다!) 백업할 때는 2시간도 안 걸렸지만, 복원할 때는 RAIDZ2의 실시간 이중 패리티 계산과 Nextcloud 등에 포함된 수백만 개의 자잘한 파일(Small Files) 랜덤 쓰기 때문에 시간이 훨씬 오래 걸렸습니다(약 10시간 이상 소요). 또한
zpool iostat에서alloc용량이 원본(2.05TB)보다 큰 약 3.0TB 수준까지 올라가는데, 이는 패리티 공간이 합산된 정상 수치입니다.
복원이 끝난 뒤에는 백업 당시 걸어두었던 readonly 속성을 해제해야 정상적인 쓰기가 가능합니다.
Bash
# 읽기 전용 해제 (하위 데이터셋까지 일괄 적용)
sudo zfs set readonly=off tank0
sudo zfs inherit -r readonly tank0
# 마운트 적용 및 서비스 재가동
sudo zfs mount -a
sudo systemctl start mariadb gitea calibre-web komga apache2 docker
4. 교훈 및 요약
zpool add명령어를 칠 때는 엔터를 누르기 전cache나log같은 VDEV 타입 키워드가 제대로 들어갔는지 두 번, 세 번 확인하자.- 실수로 잘못 추가했더라도 당황해서 디스크를 뽑거나 강제 삭제(
destroy -f)부터 하지 말고, 즉시readonly=on으로 잠근 뒤 백업 공간부터 확보하면 데이터는 100% 살릴 수 있다. - 중요 작업 전에는
sudo zpool checkpoint tank0명령어로 체크포인트를 걸어두면, 실수했을 때 백업/복원 노가다 없이 즉시 롤백할 수 있으니 적극 활용하자.
