[ZFS 트러블슈팅] 캐시(cache) 키워드 누락으로 잘못 추가된 VDEV 복구기 (RAIDZ2 전체 백업 및 재구성)

서버의 메인보드를 교체하면서 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)하고 올바르게 재생성하는 것이 유일하고 안전한 해결책입니다.

  1. 풀을 읽기 전용(readonly=on)으로 전환해 추가 쓰기 및 데이터 분산 오염 방지
  2. 여분의 스토리지에 zfs send로 전체 스냅샷 스트림 백업
  3. 기존 풀 언마운트 및 파괴(zpool destroy)
  4. 올바른 raidz2 + cache 구조로 풀 재생성
  5. 백업 데이터 복원(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. 교훈 및 요약

  1. zpool add 명령어를 칠 때는 엔터를 누르기 전 cache나 log 같은 VDEV 타입 키워드가 제대로 들어갔는지 두 번, 세 번 확인하자.
  2. 실수로 잘못 추가했더라도 당황해서 디스크를 뽑거나 강제 삭제(destroy -f)부터 하지 말고, 즉시 readonly=on으로 잠근 뒤 백업 공간부터 확보하면 데이터는 100% 살릴 수 있다.
  3. 중요 작업 전에는 sudo zpool checkpoint tank0 명령어로 체크포인트를 걸어두면, 실수했을 때 백업/복원 노가다 없이 즉시 롤백할 수 있으니 적극 활용하자.