메모리가 부족한 서버나 컨테이너 환경에서는 스왑을 켜는 순간 디스크 I/O 지연이 그대로 애플리케이션 응답 속도로 이어진다. 반대로 스토리지 비용을 아끼려고 압축을 걸고 싶어도, 파일시스템 레벨 압축(Btrfs, ZFS 등)은 해당 파일시스템을 쓸 때만 적용되고 다른 파일시스템이나 로우 블록 디바이스에는 손을 댈 수 없다는 한계가 있다. 이런 문제를 파일시스템보다 한 단계 아래인 block 레이어에서 해결하려는 시도가 zram, dm-vdo, dm-compress다. 셋 다 압축을 수행하지만 목적지(RAM vs 영구 스토리지)와 커널 병합 여부가 전혀 다르다. 이 글에서는 zram과 dm-vdo, 그리고 병합에는 실패했지만 설계상 참고할 가치가 있는 dm-compress를 중심으로 리눅스 커널이 block 레이어에서 압축을 지원하는 방식을 정리한다.
zram: 압축된 RAM 블록 디바이스
zram(과거 이름 compcache)은 RAM의 일부를 압축된 블록 디바이스로 노출하는 커널 모듈이다. /dev/zram0 같은 블록 디바이스를 만들어 스왑 영역으로 쓰거나 일반 파일시스템을 얹어 쓸 수 있는데, 실무에서 압도적으로 많이 쓰이는 용도는 스왑이다. 디스크 스왑은 랜덤 I/O 지연이 병목이 되지만, zram 스왑은 압축·해제 연산이 CPU에서 끝나기 때문에 디스크보다 훨씬 빠르고, 압축률이 2~3배 정도만 나와도 실질 메모리 용량을 늘리는 효과를 낸다. 안드로이드, 라즈베리파이 같은 메모리 제약 환경은 물론, 최근에는 클라우드 인스턴스에서도 OOM killer 발동을 늦추기 위해 zram 스왑을 기본값으로 켜두는 배포판이 늘고 있다.
내부적으로 zram은 페이지 단위(4KB)로 데이터를 압축해 zsmalloc이라는 전용 슬랩 할당자에 저장한다. 압축 알고리즘은 LZO, LZO-RLE, LZ4, LZ4HC, Zstandard(zstd), 842 중에서 고를 수 있고 런타임에 교체할 수 있다. LZ4는 속도가 빠르고, zstd는 압축률이 더 좋은 대신 CPU를 더 쓰는 식으로 트레이드오프가 갈리기 때문에 CPU 코어 수와 메모리 압박 정도에 따라 선택이 달라진다.
커널 6.x 계열에서는 CONFIG_ZRAM_MULTI_COMP 옵션으로 1차 알고리즘 외에 최대 3개의 2차(recompression) 알고리즘을 추가로 지정할 수 있다. 자주 접근하지 않는 “idle” 페이지를 더 느리지만 압축률이 높은 알고리즘으로 재압축해 메모리 사용량을 추가로 줄이는 방식이다. 여기에 CONFIG_ZRAM_WRITEBACK을 켜면 압축해도 잘 줄지 않는(incompressible) 페이지나 idle 페이지를 backing device(디스크)로 밀어내 RAM을 더 확보할 수 있다. 6.16부터는 writeback 인터페이스가 key=value 형식으로 정리되고 page_indexes로 범위를 지정할 수 있게 되어, 어떤 페이지를 내보낼지 더 세밀하게 제어할 수 있다.
# zram 모듈 로드 및 압축 알고리즘 확인
sudo modprobe zram
cat /sys/block/zram0/comp_algorithm
# 압축 알고리즘 지정 (재부팅 없이 교체 가능)
echo zstd | sudo tee /sys/block/zram0/comp_algorithm
# 크기 지정 (예: 4GB) 후 스왑으로 사용
echo 4G | sudo tee /sys/block/zram0/disksize
sudo mkswap /dev/zram0
sudo swapon -p 100 /dev/zram0
# 압축 통계 확인
cat /sys/block/zram0/mm_stat
주의할 점은 zram이 RAM 위에서 동작하는 만큼 데이터가 휘발성이라는 것이다. 재부팅하면 zram 디바이스에 있던 내용은 그대로 사라진다. 영구 저장이 목적이라면 zram이 아니라 아래에서 다룰 dm-vdo 같은 device-mapper 계열 솔루션을 써야 한다.
dm-vdo: 중복제거와 압축을 지원하는 device-mapper 타겟
dm-vdo(Virtual Data Optimizer)는 device-mapper 프레임워크 위에서 동작하는 커널 타겟으로, 인라인 중복제거(deduplication)·압축·씬 프로비저닝(thin provisioning)을 영구 블록 스토리지에 제공한다. zram이 RAM을 아끼기 위한 임시 압축 계층이라면, dm-vdo는 디스크나 SSD 위에서 실제로 저장 공간을 줄이는 것이 목적이다. 파일시스템과 무관하게 동작하기 때문에 ext4, XFS 등 어떤 파일시스템을 그 위에 올려도 압축·중복제거 효과를 그대로 받을 수 있다.
dm-vdo는 UDS(Universal Deduplication Service) 인덱스로 새로 들어오는 데이터 블록의 해시를 기존에 저장된 블록들과 비교해 중복 여부를 판단한다. 일치하는 블록이 있으면 실제 데이터를 다시 쓰지 않고 논리 블록 주소(LBA)를 기존 물리 블록에 매핑만 추가하는 방식으로 저장 공간을 아낀다. 압축은 LZ4 알고리즘으로 수행되며, packer라는 구성 요소가 압축된 여러 블록을 모아 하나의 4KB 물리 블록 안에 최대 14개까지 채워 넣는다. 개별 블록을 압축한 뒤 그대로 저장하면 블록 경계마다 남는 공간이 낭비되는데, packer가 이를 묶어서 저장하기 때문에 실제 압축 이득을 온전히 살릴 수 있다.
dm-vdo는 LVM과 통합되어 있어 lvconvert/lvchange 명령으로 VDO 풀 볼륨을 다루는 것이 가장 일반적인 사용법이다.
# 기존 볼륨 그룹 위에 VDO 풀 논리볼륨 생성 (씬 프로비저닝 가상 크기 지정)
sudo lvconvert --type vdo-pool -n vdo_lv -V 200G vg_data/vdopool0
# 압축/중복제거 개별 on-off (기본값: 중복제거 on, 압축 off)
sudo lvchange --compression y vg_data/vdopool0
sudo lvchange --deduplication y vg_data/vdopool0
# 파일시스템 생성 및 마운트
sudo mkfs.xfs /dev/vg_data/vdo_lv
sudo mount /dev/vg_data/vdo_lv /mnt/vdo
# 절감 효과 확인
sudo vdostats --human-readable
UDS 인덱스와 packer 연산은 CPU와 별도 인덱스 메모리를 소모하기 때문에, 백업 서버나 VM 이미지 저장소처럼 중복 데이터 비율이 높은 워크로드에서 이득이 크고, 이미 무작위성이 높은 데이터(암호화된 데이터, 이미 압축된 미디어 파일)에는 효과가 거의 없다.
dm-compress: 병합되지 못한 inplace 압축 타겟
dm-compress(원래 이름 dm-compression, 이후 dm-inplace-compress)는 Shaohua Li가 처음 작성하고 Ram Pai가 이어받아 다듬은 device-mapper 타겟으로, 2013년부터 2017년까지 dm-devel/LKML에 여러 차례 패치 시리즈로 올라왔다. dm-vdo가 중복제거까지 포괄하는 종합 솔루션이라면, dm-compress는 목적이 단순했다. 블록을 커널 crypto API의 압축 알고리즘으로 눌러 쓰기만 함으로써 SSD에 실제로 쓰이는 데이터량을 줄이고, 그 결과로 가비지 컬렉션 부담과 쓰기 증폭(write amplification)을 낮춰 SSD 수명과 쓰기 속도를 개선하는 것이 목표였다.
이름의 “inplace”는 압축된 블록을 원래 블록이 있던 자리 근처에 그대로 채워 넣는 방식을 가리킨다. dm-vdo의 packer처럼 여러 블록을 별도 영역에 재배치하는 대신, 블록 매핑을 단순하게 유지하면서 압축 이득만 취하는 절충안이었다. ext4 파일시스템과 스왑 디바이스를 백엔드로 하는 시나리오에서 테스트됐고, 스왑으로 쓸 때 메모리 할당 실패로 인한 데드락을 피하기 위해 할당 시 슬립하지 않는(non-sleeping) 옵션도 추가됐다.
패치는 v1부터 v4까지 리뷰를 거치며 다듬어졌지만 끝내 메인라인에 병합되지 않았다. 이후 Permabit의 VDO 코드가 dm-vdo로 커널 6.9에 병합되면서 압축+중복제거를 아우르는 표준 솔루션 자리를 차지했고, dm-compress는 Ram Pai의 GitHub 저장소(rampai/dm_inplace_compression)에 out-of-tree 코드로만 남아 있다. 실무에서 새로 도입할 때는 사용할 수 없고, block 레이어 압축을 설계할 때 “블록 매핑을 단순하게 유지할 것인가, 재배치까지 감수하고 압축 이득을 극대화할 것인가”라는 트레이드오프를 보여주는 사례로 참고하면 된다.
zram, dm-vdo, dm-compress 비교
| 구분 | zram | dm-vdo | dm-compress |
|---|---|---|---|
| 대상 계층 | RAM (휘발성) | 블록 디바이스 (영구 저장) | 블록 디바이스 (영구 저장) |
| 주 목적 | 스왑/메모리 확장 | 스토리지 절감 (중복제거+압축) | SSD 쓰기량 절감 |
| 압축 알고리즘 | LZO, LZO-RLE, LZ4, LZ4HC, zstd, 842 | LZ4 고정 | 커널 crypto API 알고리즘 |
| 중복제거 | 미지원 | UDS 인덱스 기반 지원 | 미지원 |
| 재부팅 시 데이터 | 소멸 | 유지 | 유지 |
| 커널 병합 상태 | 병합됨 (오래된 in-tree) | 병합됨 (커널 6.9) | 병합 실패, out-of-tree |
| 주요 사용처 | 모바일, 라즈베리파이, 클라우드 인스턴스 | 백업 서버, VM 이미지 스토리지 | (참고용, 실무 사용 불가) |
주의사항
zram은 압축·해제가 매 페이지 폴트마다 CPU에서 일어나므로, 코어 수가 적은 시스템에서 zstd처럼 무거운 알고리즘을 쓰면 오히려 스왑 지연이 늘어날 수 있다. 메모리 압박이 심하지 않은 서버라면 LZ4나 LZO-RLE로 시작해 mm_stat의 압축률과 CPU 사용률을 보고 조정하는 편이 안전하다.
dm-vdo는 UDS 인덱스가 상주 메모리를 차지하고, 중복제거·압축 연산이 쓰기 경로에 끼어들기 때문에 순수 쓰기 처리량은 원본 블록 디바이스보다 떨어진다. 압축 대상 데이터가 이미 랜덤에 가깝다면(암호화된 볼륨, 미디어 파일 저장소) 압축은 끄고 중복제거만 쓰는 편이 오버헤드 대비 이득이 크다. 또한 VDO 풀은 씬 프로비저닝이 기본이라 실제 물리 용량을 넘는 논리 볼륨을 만들 수 있는데, 예상보다 압축·중복제거 효율이 낮으면 풀이 꽉 차 쓰기 실패가 발생할 수 있으므로 vdostats로 여유 공간을 주기적으로 확인해야 한다.
마무리
zram, dm-vdo, dm-compress는 같은 “block 레이어 압축”이라는 카테고리로 묶이지만 해결하는 문제와 현재 쓸 수 있는지 여부가 다르다. 메모리가 부족해 스왑 지연이 문제라면 zram으로 RAM을 늘린 것 같은 효과를 볼 수 있고, 디스크 용량과 백업 비용이 문제라면 dm-vdo로 파일시스템 레이어와 무관하게 압축·중복제거를 적용할 수 있다. dm-compress는 메인라인에 없으므로 실무에 바로 쓸 수는 없지만, “매핑을 단순하게 유지할 것인가 재배치까지 감수할 것인가”라는 설계 트레이드오프를 보여준다는 점에서 dm-vdo의 packer 설계를 이해하는 데 도움이 된다. 파일시스템 자체 압축 기능(Btrfs compress 옵션 등)을 쓸 수 없는 상황, 혹은 여러 파일시스템에 걸쳐 일관된 정책을 적용하고 싶은 상황에서는 zram이나 dm-vdo 같은 block 레이어 압축이 더 유연한 선택지가 된다.
참고
- zram: Compressed RAM-based block devices — The Linux Kernel documentation
- dm-vdo — The Linux Kernel documentation
- Design of dm-vdo — The Linux Kernel documentation
- lvmvdo(7) – Linux manual page
- zram – ArchWiki
- DM: dm-inplace-compress: inplace compressed DM target [LWN.net]
- rampai/dm_inplace_compression – GitHub