Device Mapper / LVM
Linux 커널의 Device Mapper(DM) 프레임워크를 블록 디바이스 가상화(Virtualization)와 데이터 서비스 계층 관점에서 심층 분석합니다. 핵심 구조체(mapped_device, dm_table, dm_target, target_type)와 I/O remap 경로, dm-linear/striped/crypt/thin/cache/verity/snapshot 타겟별 내부 동작과 메타데이터 특성, LVM2 및 dmsetup 사용자 공간(User Space) 도구와 커널 경계, md RAID와의 책임 분리, 장애 복구 시나리오와 일관성 보장 포인트, tracepoint·dmstats 기반 성능 관측, 실제 운영에서 자주 발생하는 병목(Bottleneck)과 튜닝 절차까지 실무 중심으로 종합적으로 다룹니다.
struct bio, blk-mq, I/O 스케줄러(Scheduler) 개념을 이해하고 있다고 가정합니다. 커널 모듈(Kernel Module) 개발 경험이 있으면 코드 예제를 이해하는 데 도움이 됩니다.
핵심 요약
- Device Mapper — 물리 블록 디바이스 위에 가상 블록 디바이스를 생성하는 프레임워크입니다.
- LVM2 — DM 위에 구현된 논리 볼륨 관리자. 디스크 크기 조정, 스냅샷 등을 제공합니다.
- dm-crypt — 블록 레벨 투명 암호화(Encryption). LUKS가 이를 활용합니다.
- dm-thin — Thin provisioning. 실제 사용량보다 큰 볼륨을 생성(오버커밋)합니다.
- dm-verity — 블록 무결성(Integrity) 검증. Android의 파티션 검증에 사용됩니다.
단계별 이해
- DM 확인 —
dmsetup ls로 현재 시스템의 DM 디바이스 목록을 확인합니다.lsblk에서TYPE이lvm,crypt인 것이 DM 디바이스입니다. - LVM 기초 — PV(Physical Volume) → VG(Volume Group) → LV(Logical Volume) 계층으로 디스크를 관리합니다.
pvs,vgs,lvs명령어로 각 계층을 확인합니다. - 매핑 테이블 —
dmsetup table로 DM 디바이스의 매핑 규칙을 볼 수 있습니다.각 줄은 "시작 섹터, 길이, 타겟 타입, 타겟 인자"로 구성됩니다.
- 실전 활용 — LVM으로 볼륨 확장(
lvextend), 스냅샷 생성, LUKS로 디스크 암호화 등을 수행합니다.이 모든 기능이 Device Mapper 프레임워크 위에서 동작합니다.
Device Mapper 개요
Device Mapper(DM)는 Linux 커널의 블록 디바이스 가상화 프레임워크입니다. 하나 이상의 물리 블록 디바이스 위에 가상 블록 디바이스를 생성하고, I/O 요청을 변환(매핑)하여 하위 디바이스에 전달합니다. LVM2, LUKS 암호화, dm-verity 무결성 검증, thin provisioning 등 현대 Linux 스토리지 스택의 핵심 기능이 모두 DM 위에 구축되어 있습니다.
핵심 특징
- 모듈러 타겟 아키텍처: 매핑 로직이 타겟 드라이버로 분리되어 새로운 기능을 독립적으로 추가 가능
- 스택 가능: DM 디바이스 위에 또 다른 DM 디바이스를 쌓아 복잡한 스토리지 토폴로지(Topology) 구성
- 런타임 재구성: 매핑 테이블을 원자적(Atomic)으로 교체하여 온라인 상태에서 스토리지 레이아웃 변경
- 투명한 bio 리매핑: 상위 파일시스템(Filesystem)은 DM 디바이스를 일반 블록 디바이스로 인식
역사
Device Mapper는 2003년 Linux 2.6.0에서 Joe Thornber, Alasdair Kergon 등이 도입했습니다. 기존 LVM1의 커널 드라이버를 대체하며, 더 범용적인 블록 매핑 프레임워크로 설계되었습니다. 이후 dm-crypt(2.6.4), dm-snapshot(2.6.0), dm-thin(3.2), dm-cache(3.9), dm-verity(3.4) 등이 추가되었습니다.
DM 아키텍처
Device Mapper의 커널 코드는 drivers/md/ 디렉터리에 위치합니다. 핵심 구조체 네 가지가 DM의 뼈대를 이룹니다.
mapped_device
struct mapped_device는 하나의 가상 블록 디바이스를 나타냅니다. /dev/dm-N 또는 /dev/mapper/이름으로 사용자 공간에 노출됩니다.
/* drivers/md/dm-core.h */
struct mapped_device {
struct dm_table __rcu *map; /* 현재 활성 매핑 테이블 (RCU 보호) */
struct gendisk *disk; /* 블록 디바이스 디스크 구조체 */
struct request_queue *queue; /* 블록 I/O 요청 큐 */
unsigned long flags; /* DMF_* 플래그 */
struct mutex suspend_lock; /* suspend/resume 동기화 */
atomic_t holders; /* 참조 카운트 */
struct bio_set bs; /* bio 할당 풀 */
struct bio_set io_bs; /* I/O 전용 bio 풀 */
/* ... */
};
dm_table
struct dm_table은 가상 디바이스의 전체 섹터 공간을 여러 타겟으로 분할하는 매핑 테이블입니다. 테이블 교체는 원자적(atomic swap)으로 이루어져 I/O 중단 없이 온라인 재구성이 가능합니다.
/* drivers/md/dm-table.c */
struct dm_table {
struct mapped_device *md; /* 소속 mapped_device */
unsigned int num_targets; /* 타겟 개수 */
struct dm_target *targets; /* 타겟 배열 */
struct dm_dev **devices; /* 참조된 하위 디바이스 배열 */
fmode_t mode; /* 읽기/쓰기 모드 */
/* ... */
};
dm_target
struct dm_target은 매핑 테이블 내의 하나의 연속 섹터 구간을 표현합니다. 각 타겟은 시작 섹터, 길이, 그리고 실제 매핑 로직을 수행하는 target_type을 가집니다.
/* include/linux/device-mapper.h */
struct dm_target {
struct dm_table *table; /* 소속 테이블 */
struct target_type *type; /* 타겟 유형 (linear, crypt 등) */
sector_t begin; /* 가상 디바이스 내 시작 섹터 */
sector_t len; /* 섹터 수 */
unsigned int max_io_len; /* bio 분할 단위 */
void *private; /* 타겟 드라이버 전용 데이터 */
char *error; /* 에러 메시지 */
/* ... */
};
target_type
struct target_type은 타겟 드라이버의 오퍼레이션 테이블입니다. 새로운 DM 타겟을 구현하려면 이 구조체를 정의하고 dm_register_target()으로 등록합니다.
/* include/linux/device-mapper.h */
struct target_type {
uint64_t features;
const char *name; /* "linear", "crypt" 등 */
struct module *module;
/* 생명주기 콜백 */
dm_ctr_fn ctr; /* 생성자: 테이블 로드 시 호출 */
dm_dtr_fn dtr; /* 소멸자: 테이블 해제 시 호출 */
dm_map_fn map; /* bio 매핑 함수 (핵심!) */
dm_clone_and_map_fn clone_and_map_rq; /* request 기반 매핑 */
/* 선택적 콜백 */
dm_presuspend_fn presuspend;
dm_postsuspend_fn postsuspend;
dm_preresume_fn preresume;
dm_resume_fn resume;
dm_status_fn status; /* dmsetup status 응답 */
dm_message_fn message; /* dmsetup message 처리 */
dm_io_hints_fn io_hints; /* I/O 힌트 (정렬, 제한 등) */
/* ... */
};
DM_MAPIO_SUBMITTED(bio가 타겟에 의해 제출됨), DM_MAPIO_REMAPPED(bio가 리매핑되어 DM 코어가 제출), DM_MAPIO_KILL(bio를 에러로 완료), DM_MAPIO_REQUEUE(bio를 재큐잉).
타겟 등록/해제
/* 타겟 드라이버 모듈 초기화 */
static struct target_type my_target = {
.name = "my_target",
.version = {1, 0, 0},
.module = THIS_MODULE,
.ctr = my_ctr,
.dtr = my_dtr,
.map = my_map,
.status = my_status,
};
static int __init my_init(void)
{
return dm_register_target(&my_target);
}
static void __exit my_exit(void)
{
dm_unregister_target(&my_target);
}
module_init(my_init);
module_exit(my_exit);
MODULE_LICENSE("GPL");
dm-linear
dm-linear은 가장 단순한 DM 타겟으로, 가상 디바이스의 섹터 범위를 물리 디바이스의 연속된 섹터 범위에 1:1로 매핑합니다. LVM2가 논리 볼륨을 구성할 때 가장 기본적으로 사용하는 타겟입니다.
매핑 원리
/* drivers/md/dm-linear.c */
struct linear_c {
struct dm_dev *dev; /* 하위 디바이스 */
sector_t start; /* 하위 디바이스 시작 오프셋 */
};
static int linear_map(struct dm_target *ti, struct bio *bio)
{
struct linear_c *lc = ti->private;
/* bio의 대상 디바이스와 섹터를 리매핑 */
bio_set_dev(bio, lc->dev->bdev);
bio->bi_iter.bi_sector = lc->start +
dm_target_offset(ti, bio->bi_iter.bi_sector);
return DM_MAPIO_REMAPPED;
}
dm_target_offset()은 bio->bi_iter.bi_sector - ti->begin으로, 가상 디바이스의 시작 섹터를 기준으로 한 상대 오프셋(Offset)을 계산합니다. 이에 하위 디바이스의 시작 오프셋(lc->start)을 더하면 물리 섹터가 됩니다.
사용 예제
# /dev/sda의 섹터 0~2097151 (1GB)을 가상 디바이스로 매핑
$ echo "0 2097152 linear /dev/sda 0" | dmsetup create my_linear
# 테이블 확인
$ dmsetup table my_linear
0 2097152 linear 8:0 0
# 두 디바이스를 연결하여 하나의 큰 가상 디바이스 생성
$ dmsetup create my_concat <<EOF
0 2097152 linear /dev/sda1 0
2097152 4194304 linear /dev/sdb1 0
EOF
# 결과: /dev/mapper/my_concat = sda1(1GB) + sdb1(2GB) = 3GB 가상 디바이스
dm-striped
dm-striped는 RAID-0 스트라이핑을 구현합니다. I/O를 청크(stripe) 단위로 여러 하위 디바이스에 라운드 로빈(Round Robin) 분산하여 대역폭(Bandwidth)을 극대화합니다.
스트라이프 레이아웃
/* drivers/md/dm-stripe.c */
struct stripe {
struct dm_dev *dev;
sector_t physical_start;
};
struct stripe_c {
uint32_t stripes; /* 스트라이프 수 */
uint32_t chunk_size; /* 청크 크기 (섹터) */
sector_t stripe_width; /* 전체 스트라이프 폭 */
struct stripe stripe_devs[0]; /* 유연 배열 */
};
static int stripe_map(struct dm_target *ti, struct bio *bio)
{
struct stripe_c *sc = ti->private;
sector_t offset = dm_target_offset(ti, bio->bi_iter.bi_sector);
uint32_t chunk = sector_div(offset, sc->chunk_size);
uint32_t stripe_idx = sector_div(chunk, sc->stripes);
bio_set_dev(bio, sc->stripe_devs[stripe_idx].dev->bdev);
bio->bi_iter.bi_sector = sc->stripe_devs[stripe_idx].physical_start +
chunk * sc->chunk_size + offset;
return DM_MAPIO_REMAPPED;
}
사용 예제
# 2개 디바이스, 64KB 청크 스트라이핑
$ echo "0 4194304 striped 2 128 /dev/sda1 0 /dev/sdb1 0" | dmsetup create my_stripe
# ^시작 ^길이 ^타입 ^N ^청크 ^디바이스1 ^디바이스2
# 128 섹터 = 128 * 512 = 64KB 청크
# 스트라이프 상태 확인
$ dmsetup status my_stripe
0 4194304 striped 2 128 A A
# A = 활성(Active), D = 비활성(Dead)
dm-crypt
dm-crypt는 블록 레벨 투명 암호화를 제공합니다. 쓰기 시 bio 데이터를 암호화하고, 읽기 시 복호화(Decryption)합니다. LUKS(Linux Unified Key Setup)의 커널 측 백엔드입니다.
암호화 아키텍처
/* drivers/md/dm-crypt.c */
struct crypt_config {
struct dm_dev *dev; /* 하위 디바이스 */
sector_t start; /* 데이터 시작 오프셋 */
struct crypt_iv_operations *iv_gen_ops; /* IV 생성 방식 */
struct crypto_skcipher *tfm; /* 암호화 변환 */
unsigned int iv_size; /* IV 바이트 수 */
sector_t iv_offset; /* IV 계산용 섹터 오프셋 */
struct workqueue_struct *io_queue; /* I/O 워크큐 */
struct workqueue_struct *crypt_queue; /* 암/복호화 워크큐 */
u8 key[0]; /* 암호화 키 */
};
I/O 경로
- 쓰기: bio 수신 → 새 bio 할당(bounce buffer) →
crypt_queue에서 데이터 암호화 → 하위 디바이스에 암호화된 bio 제출 - 읽기: 하위 디바이스에 bio 제출 → 완료 콜백(Callback)에서
crypt_queue로 복호화 → 원래 bio 완료
no_read_workqueue, no_write_workqueue 옵션으로 워크큐 우회가 가능합니다.
LUKS 사용 예제
# LUKS2 포맷 (AES-XTS-plain64, 512비트 키)
$ cryptsetup luksFormat --type luks2 --cipher aes-xts-plain64 \
--key-size 512 --hash sha256 /dev/sda2
# 볼륨 열기 → /dev/mapper/cryptroot 생성
$ cryptsetup luksOpen /dev/sda2 cryptroot
# 내부적으로 생성되는 DM 테이블 확인
$ dmsetup table cryptroot
0 41943040 crypt aes-xts-plain64 0000...0000 0 8:2 4096
# ^cipher ^key(hex) ^iv ^dev ^offset
# 파일시스템 생성 및 마운트
$ mkfs.ext4 /dev/mapper/cryptroot
$ mount /dev/mapper/cryptroot /mnt/secure
# 볼륨 닫기
$ umount /mnt/secure
$ cryptsetup luksClose cryptroot
# 성능 벤치마크: 워크큐 최적화 옵션
$ cryptsetup open --perf-no_read_workqueue --perf-no_write_workqueue \
/dev/sda2 cryptroot
IV(Initialization Vector) 생성 방식
| IV 모드 | 설명 | 보안 수준 |
|---|---|---|
plain | 섹터 번호를 32비트로 사용 | 낮음 (4TB 이하만) |
plain64 | 섹터 번호를 64비트로 사용 | 표준 |
essiv | Encrypted Salt-Sector IV | 높음 (워터마크(Watermark) 공격 방지) |
benbi | Big-Endian Narrow Block IV | 특수 용도 |
random | 랜덤 IV (쓰기 전용) | 높음 (읽기 불가) |
dm-thin (Thin Provisioning)
dm-thin은 씬 프로비저닝과 스냅샷을 제공합니다. 실제 물리 공간을 사전에 할당하지 않고, 쓰기 시점에 동적으로 블록을 할당합니다. Copy-on-Write(CoW) 기반 스냅샷도 지원합니다.
핵심 개념
- thin-pool: 물리 블록 풀. 데이터 디바이스(data_dev)와 메타데이터 디바이스(metadata_dev)로 구성
- thin volume: 풀에서 블록을 할당받는 가상 디바이스. 논리 크기가 물리 크기보다 클 수 있음
- 메타데이터: B-tree 기반으로 논리 블록 → 물리 블록 매핑을 관리 (on-disk persistent)
- overprovisioning: 논리 볼륨 합계가 물리 풀보다 커도 됨. 실제 쓰기 시점까지 할당 지연(Latency)
# thin-pool 생성
# metadata: 256MB, data: 100GB, 블록 크기: 64KB
$ dmsetup create thin_meta --table "0 524288 linear /dev/sdc1 0"
$ dmsetup create thin_data --table "0 209715200 linear /dev/sdc2 0"
$ dmsetup create thin_pool --table \
"0 209715200 thin-pool /dev/mapper/thin_meta /dev/mapper/thin_data 128 0"
# ^block_size(섹터)
# thin volume 생성 (ID=0, 논리 크기 500GB — 물리 100GB보다 큼!)
$ dmsetup message thin_pool 0 "create_thin 0"
$ dmsetup create thin_vol0 --table \
"0 1048576000 thin /dev/mapper/thin_pool 0"
# 스냅샷 생성 (ID=1, 소스=ID 0)
$ dmsetup message thin_pool 0 "create_snap 1 0"
$ dmsetup create thin_snap1 --table \
"0 1048576000 thin /dev/mapper/thin_pool 1"
# 풀 사용량 확인
$ dmsetup status thin_pool
0 209715200 thin-pool 42 310/524288 12048/209715200 - rw no_discard_passdown
# ^tx ^meta사용/전체 ^data사용/전체
thin_pool_autoextend_threshold 옵션을 활용할 수 있습니다.
dm-cache / dm-writecache
dm-cache는 느린 HDD 앞에 빠른 SSD를 캐시(Cache) 계층으로 배치하여 성능을 향상시킵니다. dm-writecache는 쓰기 전용 캐싱에 특화된 타겟입니다.
dm-cache 구조
- origin device: 원본 데이터가 저장되는 느린 디바이스 (HDD)
- cache device: 핫 데이터를 캐시하는 빠른 디바이스 (SSD)
- metadata device: 캐시 매핑과 더티 비트를 저장
- 정책(policy): 캐시 교체 알고리즘 (
smq= Stochastic Multi-Queue, 기본값)
캐시 모드
| 모드 | 읽기 | 쓰기 | 특징 |
|---|---|---|---|
writeback | 캐시 히트 시 SSD에서 | SSD에 먼저 기록 | 최고 성능, SSD 장애 시 데이터 손실 위험 |
writethrough | 캐시 히트 시 SSD에서 | SSD + HDD 동시 기록 | 안전하지만 쓰기 성능 향상 없음 |
passthrough | 항상 HDD에서 | 항상 HDD에 | 캐시 워밍만 수행, 마이그레이션용 |
# dm-cache 설정 (writeback 모드, smq 정책)
$ dmsetup create cache_meta --table "0 8192 linear /dev/nvme0n1p1 0"
$ dmsetup create cache_data --table "0 209715200 linear /dev/sda 0"
$ dmsetup create cache_ssd --table "0 41943040 linear /dev/nvme0n1p2 0"
$ dmsetup create my_cache --table \
"0 209715200 cache /dev/mapper/cache_meta /dev/mapper/cache_ssd \
/dev/mapper/cache_data 128 1 writeback smq 0"
# ^meta ^cache(SSD)
# ^origin(HDD) ^block_sectors ^feature_count ^mode ^policy
# 캐시 통계 확인
$ dmsetup status my_cache
0 209715200 cache 8 42/512 128 1638400/3276800 256 7890 1234 5 2 1 writeback 2 \
migration_threshold 2048 smq 0 rw -
# read_hits write_hits read_misses write_misses demotions promotions
dm-writecache
dm-writecache는 쓰기 I/O만 SSD(또는 PMEM)에 캐시하는 단순한 타겟입니다. dm-cache보다 오버헤드가 적고, 특히 DAX 가능 PMEM 디바이스와 결합하면 매우 낮은 쓰기 지연을 달성합니다.
# dm-writecache (SSD 모드)
$ dmsetup create my_wc --table \
"0 209715200 writecache s /dev/sda /dev/nvme0n1p1 4096 0"
# ^type(s=SSD,p=PMEM) ^origin ^cache ^block_size
# PMEM 모드 (DAX 가능, 더 낮은 지연)
$ dmsetup create my_wc_pmem --table \
"0 209715200 writecache p /dev/sda /dev/pmem0 4096 0"
dm-verity
dm-verity는 블록 디바이스의 무결성을 읽기 시점에 검증합니다. 데이터 블록마다 해시(Hash)를 사전에 계산하여 해시 트리(Merkle Tree)를 구성하고, 읽기 I/O가 발생하면 해시를 검증하여 변조를 탐지합니다. Android의 Verified Boot와 Chrome OS의 dm-verity가 대표적인 사용 사례입니다.
해시 트리 구조
- Merkle Hash Tree 구조
- [Root Hash] ← 단일 값, 커널 커맨드 라인에 전달
- / \
- [H01] [H23] ← Level 1: 하위 해시들의 해시
- / \ / \
- [H0] [H1] [H2] [H3] ← Level 0: 데이터 블록의 해시
- | | | |
- [D0] [D1] [D2] [D3] ← 데이터 블록 (4KB 각)
커널 구현 핵심
/* drivers/md/dm-verity-target.c */
struct dm_verity {
struct dm_dev *data_dev; /* 검증 대상 데이터 디바이스 */
struct dm_dev *hash_dev; /* 해시 트리 저장 디바이스 */
struct crypto_ahash *tfm; /* 해시 알고리즘 (sha256 등) */
u8 *root_digest; /* 루트 해시 */
u8 *salt; /* 솔트 */
unsigned int data_dev_block_bits;
unsigned int hash_dev_block_bits;
unsigned int hash_per_block_bits;
sector_t hash_start; /* 해시 트리 시작 섹터 */
unsigned int levels; /* 해시 트리 깊이 */
/* ... */
};
사용 예제
# 해시 트리 생성 (veritysetup)
$ veritysetup format /dev/sda1 /dev/sda2
# VERITY header information for /dev/sda2
# UUID: 12345678-abcd-1234-abcd-123456789abc
# Hash type: 1
# Data block size: 4096
# Hash block size: 4096
# Hash algorithm: sha256
# Salt: abcdef0123456789...
# Root hash: a1b2c3d4e5f6... ← 이 값을 보관!
# 검증 디바이스 활성화
$ veritysetup open /dev/sda1 verified_root /dev/sda2 \
a1b2c3d4e5f6...
# DM 테이블 확인
$ dmsetup table verified_root
0 2097152 verity 1 8:1 8:2 4096 4096 262144 1 sha256 \
a1b2c3d4e5f6... abcdef0123456789...
# ^ver ^data ^hash ^dbs ^hbs ^blocks ^hs ^alg ^root_hash ^salt
# Android 부팅 시 커널 커맨드 라인 예:
# dm="1 vroot none ro 1,0 2097152 verity 1 /dev/sda1 /dev/sda2 ..."
Android dm-verity — AVB 연동
Android Verified Boot(AVB)는 dm-verity를 핵심으로 사용하여 system, vendor 파티션의 런타임 무결성을 보장합니다. 부트로더(Bootloader)가 VBMeta 파티션의 RSA 서명을 검증하고, 각 파티션의 해시 트리 루트 해시를 커널에 전달합니다.
AVB의 VBMeta 구조, 롤백(Rollback) 보호, 부트 흐름 등 내용은 Android 커널 — AVB와 Secure Boot를 참고하라.
dm-snapshot
dm-snapshot은 Copy-on-Write(CoW) 기반 스냅샷을 제공합니다. 원본 디바이스의 특정 시점 상태를 보존하면서 원본에 대한 쓰기를 허용합니다. 변경된 블록만 별도 COW 디바이스에 저장하므로 공간 효율적입니다.
스냅샷 유형
| 타겟 | 역할 | 설명 |
|---|---|---|
snapshot-origin | 원본 | 원본 디바이스를 감싸며, 쓰기 시 스냅샷의 COW 디바이스에 원래 데이터 복사 |
snapshot | 스냅샷 | 특정 시점의 읽기 전용(또는 R/W) 뷰. COW 디바이스에 변경 블록 저장 |
snapshot-merge | 병합 | 스냅샷의 변경 사항을 원본에 병합 |
# 스냅샷 COW 디바이스 준비 (원본의 10% 정도)
$ lvcreate -L 1G -n cow_store vg0
# 원본에 snapshot-origin 적용
$ dmsetup create origin --table "0 20971520 snapshot-origin /dev/vg0/data"
# 스냅샷 생성 (persistent = 재부팅 후 유지, 청크 크기 8 섹터 = 4KB)
$ dmsetup create snap1 --table \
"0 20971520 snapshot /dev/vg0/data /dev/vg0/cow_store P 8"
# ^P=persistent, N=non-persistent
# 스냅샷 사용량 확인
$ dmsetup status snap1
0 20971520 snapshot 1024/2097152 16
# ^사용/전체(청크) ^메타데이터(청크)
dm-thin의 씬 스냅샷을 권장합니다.
LVM2 (Logical Volume Manager)
LVM2는 Device Mapper 위에 구축된 사용자 공간 볼륨 관리 도구입니다. 물리 디스크를 유연하게 파티셔닝하고, 온라인 확장/축소, 스냅샷, 씬 프로비저닝, 미러링, 캐싱 등을 제공합니다.
계층 구조
/*
* LVM2 계층:
*
* Physical Volume (PV) ← 물리 디스크 또는 파티션
* │
* Volume Group (VG) ← PV들의 풀 (하나의 스토리지 풀)
* │
* Logical Volume (LV) ← VG에서 할당된 가상 볼륨
* │
* /dev/mapper/vg-lv ← DM 디바이스로 노출
*
* PV 내부: PE(Physical Extent) 단위로 분할 (기본 4MB)
* LV: 하나 이상의 PE를 할당받아 구성
*/
주요 명령어
# ===== Physical Volume (PV) =====
$ pvcreate /dev/sda1 /dev/sdb1 /dev/sdc1 # PV 초기화
$ pvdisplay /dev/sda1 # PV 정보
$ pvs # PV 요약 목록
$ pvmove /dev/sda1 /dev/sdd1 # PV 간 데이터 이동 (온라인)
# ===== Volume Group (VG) =====
$ vgcreate my_vg /dev/sda1 /dev/sdb1 # VG 생성
$ vgextend my_vg /dev/sdc1 # VG에 PV 추가
$ vgreduce my_vg /dev/sda1 # VG에서 PV 제거 (pvmove 선행)
$ vgs # VG 요약 목록
# ===== Logical Volume (LV) =====
$ lvcreate -L 50G -n root my_vg # 50GB LV 생성
$ lvcreate -l 100%FREE -n data my_vg # 남은 공간 전부 사용
$ lvextend -L +20G /dev/my_vg/root # 20GB 확장
$ resize2fs /dev/my_vg/root # ext4 파일시스템도 확장
$ lvreduce -L -10G /dev/my_vg/data # 축소 (파일시스템 먼저 축소!)
$ lvremove /dev/my_vg/data # LV 삭제
# ===== LVM 스냅샷 =====
$ lvcreate -L 5G -s -n root_snap /dev/my_vg/root # CoW 스냅샷
$ lvconvert --merge /dev/my_vg/root_snap # 스냅샷을 원본에 병합
# ===== LVM Thin Provisioning =====
$ lvcreate -L 100G --thinpool thin_pool my_vg # thin-pool 생성
$ lvcreate -V 500G --thin -n thin_vol my_vg/thin_pool # 씬 볼륨
$ lvcreate -s /dev/my_vg/thin_vol -n thin_snap # 씬 스냅샷 (공간 0)
# ===== LVM Cache (dm-cache 기반) =====
$ lvcreate -L 20G -n cache_data my_vg /dev/nvme0n1p1 # 캐시 데이터
$ lvcreate -L 256M -n cache_meta my_vg /dev/nvme0n1p1 # 캐시 메타
$ lvconvert --type cache-pool --poolmetadata my_vg/cache_meta my_vg/cache_data
$ lvconvert --type cache --cachepool my_vg/cache_data my_vg/root
# 이제 my_vg/root는 NVMe 캐싱된 LV
LVM2 내부 동작
LVM2는 사용자 공간의 lvm2 도구들과 커널의 Device Mapper를 연결합니다. libdevmapper가 /dev/mapper/control을 통해 DM ioctl을 호출하여 매핑 테이블을 로드합니다.
/* LVM2가 내부적으로 생성하는 DM 테이블 예시 */
# 단순 선형 LV (하나의 PV에서 연속 할당)
$ dmsetup table my_vg-root
0 104857600 linear 8:1 2048
# 여러 PV에 걸친 LV (다중 linear 세그먼트)
$ dmsetup table my_vg-data
0 52428800 linear 8:1 104859648
52428800 52428800 linear 8:17 2048
104857600 52428800 linear 8:33 2048
# 미러 LV (dm-raid1)
$ dmsetup table my_vg-mirror
0 104857600 raid raid1 3 0 region_size 1024 2 8:1 2048 8:17 2048
MD (Multiple Devices) - 소프트웨어 RAID
MD(Multiple Devices)는 커널 내장 소프트웨어 RAID 드라이버입니다. Device Mapper와는 별도의 프레임워크이지만 drivers/md/ 디렉터리를 공유하며, LVM2와 결합하여 사용되는 경우가 많습니다.
RAID 레벨
| 레벨 | 최소 디스크 | 용량 효율 | 내결함성 | 특징 |
|---|---|---|---|---|
RAID 0 | 2 | 100% | 없음 | 스트라이핑, 최대 성능 |
RAID 1 | 2 | 50% | 1디스크 | 미러링, 읽기 성능 향상 |
RAID 5 | 3 | (N-1)/N | 1디스크 | 분산 패리티, 범용 |
RAID 6 | 4 | (N-2)/N | 2디스크 | 이중 패리티, 대용량 스토리지 |
RAID 10 | 4 | 50% | 미러당 1 | 미러+스트라이프, 고성능+안정성 |
커널 핵심 구조체
/* drivers/md/md.h */
struct mddev {
struct gendisk *gendisk; /* /dev/mdN */
struct md_personality *pers; /* RAID 성격 (레벨별 ops) */
int level; /* RAID 레벨 */
int raid_disks; /* 활성 디스크 수 */
sector_t dev_sectors; /* 각 디스크 사용 섹터 */
int chunk_sectors; /* 스트라이프 청크 크기 */
struct list_head disks; /* md_rdev 리스트 */
struct md_thread *thread; /* 리싱크/복구 스레드 */
unsigned long recovery; /* 복구 상태 비트맵 */
struct bitmap *bitmap; /* 쓰기 인텐트 비트맵 */
/* ... */
};
/* 각 디스크 장치 */
struct md_rdev {
struct block_device *bdev; /* 블록 디바이스 */
sector_t sectors; /* 사용 가능 섹터 */
int raid_disk; /* 배열 내 인덱스 */
int desc_nr; /* 슈퍼블록 내 번호 */
unsigned long flags; /* In_sync, Faulty, ... */
struct list_head same_set; /* mddev->disks 연결 */
/* ... */
};
mdadm 사용 예제
# RAID 5 생성 (3 디스크 + 1 스페어)
$ mdadm --create /dev/md0 --level=5 --raid-devices=3 \
--spare-devices=1 /dev/sd{a,b,c,d}1
# RAID 상태 확인
$ cat /proc/mdstat
Personalities : [raid6] [raid5] [raid4]
md0 : active raid5 sdc1[2] sdb1[1] sda1[0]
2093056 blocks super 1.2 level 5, 512k chunk, algorithm 2 [3/3] [UUU]
$ mdadm --detail /dev/md0
# 디스크 장애 시뮬레이션
$ mdadm --fail /dev/md0 /dev/sdb1
$ mdadm --remove /dev/md0 /dev/sdb1
# 새 디스크로 교체 (자동 리빌드)
$ mdadm --add /dev/md0 /dev/sde1
# 리빌드 진행률 확인
$ cat /proc/mdstat
md0 : active raid5 sde1[4] sdc1[2] sda1[0]
2093056 blocks super 1.2 level 5, 512k chunk, algorithm 2 [3/2] [U_U]
[====>................] recovery = 22.3% (234112/1046528) finish=1.2min
# 비트맵(Write-Intent Bitmap) 추가 — 리빌드 시간 단축
$ mdadm --grow /dev/md0 --bitmap=internal
# RAID 정보를 설정 파일에 저장
$ mdadm --detail --scan >> /etc/mdadm/mdadm.conf
# RAID 10 (near layout) 생성
$ mdadm --create /dev/md1 --level=10 --raid-devices=4 \
--layout=n2 /dev/sd{a,b,c,d}2
# n2 = near-2 copies (각 청크를 2개 디스크에 미러)
MD vs DM RAID
dm-raid)으로 감싸서 사용합니다. lvconvert --type raid1 같은 명령이 내부적으로 dm-raid 테이블을 생성합니다. dm-raid는 MD의 md_personality를 재사용하면서도 DM의 테이블 교체, 스택킹 기능을 활용합니다.
dmsetup 도구
dmsetup은 Device Mapper를 직접 제어하는 저수준 사용자 공간 도구입니다. LVM2, cryptsetup, veritysetup 등 고수준 도구가 내부적으로 libdevmapper를 통해 동일한 ioctl을 호출합니다.
핵심 명령
# ===== 디바이스 관리 =====
$ dmsetup create <name> --table "<table>" # DM 디바이스 생성
$ dmsetup remove <name> # 디바이스 제거
$ dmsetup suspend <name> # I/O 일시 중지
$ dmsetup resume <name> # I/O 재개
$ dmsetup reload <name> --table "<table>" # 테이블 교체 (suspend→reload→resume)
# ===== 정보 조회 =====
$ dmsetup ls # 모든 DM 디바이스 목록
$ dmsetup table <name> # 매핑 테이블
$ dmsetup status <name> # 런타임 상태
$ dmsetup info <name> # 디바이스 정보 (major, minor, open count 등)
$ dmsetup deps <name> # 의존하는 하위 디바이스
# ===== 고급 =====
$ dmsetup message <name> 0 "<msg>" # 타겟에 메시지 전달
$ dmsetup wait <name> <event_nr> # 이벤트 대기
$ dmsetup targets # 등록된 타겟 유형 목록
# ===== 실전 예: 온라인 테이블 교체 =====
# 기존: sda에 매핑 → 새로: sdb에 매핑
$ dmsetup suspend my_dev
$ dmsetup reload my_dev --table "0 2097152 linear /dev/sdb 0"
$ dmsetup resume my_dev
# I/O 중단 없이 매핑 대상 변경 완료
테이블 형식
# DM 테이블 라인 형식:
# <시작섹터> <길이(섹터)> <타겟타입> <타겟별 인자...>
#
# 예시:
0 2097152 linear /dev/sda 0
# 섹터 0부터 2097152개 섹터(1GB)를 /dev/sda의 섹터 0에 선형 매핑
0 1048576 crypt aes-xts-plain64 <key_hex> 0 /dev/sda 0
# AES-XTS 암호화 매핑
0 2097152 striped 2 128 /dev/sda 0 /dev/sdb 0
# 2-way 스트라이프, 128 섹터 청크
0 2097152 thin-pool /dev/mapper/meta /dev/mapper/data 128 0
# thin-pool 타겟
디바이스 트리(Device Tree) 시각화
# DM 디바이스 의존 트리 확인
$ dmsetup ls --tree
my_vg-root (253:0)
└─ (8:1)
my_vg-home (253:1)
├─ (8:1)
└─ (8:17)
cryptroot (253:2)
└─ my_vg-root (253:0)
└─ (8:1)
실전 시나리오 예제
이론을 넘어 실제 운영 환경에서 자주 사용하는 DM/LVM 구성을 end-to-end 예제로 학습합니다.
LVM 볼륨 생성 전체 과정
빈 디스크에서 시작하여 마운트(Mount) 가능한 논리 볼륨을 만드는 완전한 절차입니다:
# 1. Physical Volume (PV) 생성
# /dev/sdb를 LVM이 사용할 수 있도록 초기화
$ pvcreate /dev/sdb
Physical volume "/dev/sdb" successfully created.
# PV 확인
$ pvdisplay /dev/sdb
"/dev/sdb" is a new physical volume of "500.00 GiB"
--- NEW Physical volume ---
PV Name /dev/sdb
VG Name
PV Size 500.00 GiB
Allocatable NO
PE Size 0
Total PE 0
Free PE 0
# 2. Volume Group (VG) 생성
# 여러 PV를 묶어 하나의 스토리지 풀로 만듦
$ vgcreate data_vg /dev/sdb
Volume group "data_vg" successfully created
# 여러 디스크를 하나의 VG로 묶기
$ vgcreate data_vg /dev/sdb /dev/sdc /dev/sdd
# VG 상태 확인
$ vgdisplay data_vg
--- Volume group ---
VG Name data_vg
System ID
Format lvm2
VG Size <500.00 GiB
PE Size 4.00 MiB
Total PE 127999
Alloc PE / Size 0 / 0
Free PE / Size 127999 / <500.00 GiB
# 3. Logical Volume (LV) 생성
# VG에서 필요한 크기만큼 할당
$ lvcreate -L 100G -n mysql_data data_vg
Logical volume "mysql_data" created.
# 여러 LV 생성 (용도별 분리)
$ lvcreate -L 200G -n app_data data_vg
$ lvcreate -L 50G -n logs data_vg
# VG의 남은 공간 전체 사용
$ lvcreate -l 100%FREE -n backup data_vg
# 4. 파일 시스템 생성
$ mkfs.ext4 /dev/data_vg/mysql_data
mke2fs 1.46.5 (30-Dec-2021)
Creating filesystem with 26214400 4k blocks and 6553600 inodes
# 5. 마운트
$ mkdir -p /var/lib/mysql
$ mount /dev/data_vg/mysql_data /var/lib/mysql
# 6. 영구 마운트 설정 (/etc/fstab)
$ echo "/dev/data_vg/mysql_data /var/lib/mysql ext4 defaults 0 2" >> /etc/fstab
# 7. 볼륨 크기 조정 (온라인)
# VG에 여유 공간이 있으면 LV 확장 가능
$ lvextend -L +50G /dev/data_vg/mysql_data
Size of logical volume data_vg/mysql_data changed from 100.00 GiB to 150.00 GiB.
# 파일 시스템도 확장
$ resize2fs /dev/data_vg/mysql_data
resize2fs 1.46.5 (30-Dec-2021)
Filesystem at /dev/data_vg/mysql_data is mounted on /var/lib/mysql; on-line resizing required
Performing an on-line resize of /dev/data_vg/mysql_data to 39321600 (4k) blocks.
VG 확장: 공간이 부족하면 새 디스크를 VG에 추가할 수 있습니다: vgextend data_vg /dev/sde. 기존 LV는 그대로 유지되며, 추가된 공간을 사용하여 LV를 확장하거나 새 LV를 생성할 수 있습니다.
dm-crypt LUKS 암호화 볼륨 구성
민감한 데이터를 블록 레벨에서 암호화하는 LUKS (Linux Unified Key Setup) 전체 설정 과정입니다:
# 1. LUKS 볼륨 초기화 (주의: 디스크 데이터 삭제됨)
$ cryptsetup luksFormat /dev/sdc
WARNING!
========
This will overwrite data on /dev/sdc irrevocably.
Are you sure? (Type 'yes' in capital letters): YES
Enter passphrase for /dev/sdc: ********
Verify passphrase: ********
# 2. LUKS 볼륨 열기 (매핑)
$ cryptsetup open /dev/sdc encrypted_data
Enter passphrase for /dev/sdc: ********
# /dev/mapper/encrypted_data가 생성됨 (평문 디바이스)
$ ls -l /dev/mapper/encrypted_data
lrwxrwxrwx 1 root root 7 Feb 16 10:00 /dev/mapper/encrypted_data -> ../dm-0
# 3. 파일 시스템 생성 (평문 디바이스에)
$ mkfs.ext4 /dev/mapper/encrypted_data
# 4. 마운트
$ mkdir /mnt/secure
$ mount /dev/mapper/encrypted_data /mnt/secure
# 5. 암호화 상태 확인
$ cryptsetup status encrypted_data
/dev/mapper/encrypted_data is active.
type: LUKS2
cipher: aes-xts-plain64
keysize: 512 bits
key location: keyring
device: /dev/sdc
sector size: 512
offset: 32768 sectors
size: 1953493088 sectors
mode: read/write
# 6. 성능 최적화 옵션 (재마운트 시)
$ cryptsetup close encrypted_data
$ cryptsetup open --perf-no_read_workqueue \
--perf-no_write_workqueue \
--perf-submit_from_crypt_cpus \
/dev/sdc encrypted_data
# 7. 자동 마운트 설정 (/etc/crypttab)
# 부팅 시 비밀번호 입력 없이 키 파일 사용
$ dd if=/dev/urandom of=/root/luks.key bs=512 count=4
$ chmod 400 /root/luks.key
$ cryptsetup luksAddKey /dev/sdc /root/luks.key
Enter any existing passphrase: ********
# /etc/crypttab 추가
# encrypted_data /dev/sdc /root/luks.key luks
# /etc/fstab 추가
# /dev/mapper/encrypted_data /mnt/secure ext4 defaults 0 2
키 관리 중요: /root/luks.key 파일을 분실하면 데이터를 복구할 수 없습니다. 안전한 별도 위치에 백업하고, 파일 권한을 400으로 설정하여 root만 읽을 수 있도록 해야 합니다.
dm-thin 스냅샷 기반 백업
Thin Provisioning을 사용한 효율적인 스냅샷 백업 구성:
# 1. Thin Pool 생성
# 메타데이터 볼륨과 데이터 볼륨 준비
$ lvcreate -L 1G -n thin_meta data_vg
$ lvcreate -L 100G -n thin_data data_vg
# Thin Pool로 변환
$ lvconvert --type thin-pool --poolmetadata data_vg/thin_meta data_vg/thin_data
Converted data_vg/thin_data to thin pool.
# 2. Thin Volume 생성 (실제 할당은 필요 시만)
$ lvcreate -V 50G --thin -n db_vol data_vg/thin_data
Logical volume "db_vol" created.
# 파일 시스템 생성 및 마운트
$ mkfs.ext4 /dev/data_vg/db_vol
$ mount /dev/data_vg/db_vol /var/lib/postgresql
# 3. 스냅샷 생성 (순간 복사)
# COW(Copy-on-Write)로 변경 블록만 저장
$ lvcreate -s -n db_backup_snap data_vg/db_vol
Logical volume "db_backup_snap" created.
# 스냅샷은 생성 시점의 상태를 유지
$ mount /dev/data_vg/db_backup_snap /mnt/backup
# 4. 백업 실행 (스냅샷에서)
$ tar czf /backups/db_$(date +%F).tar.gz -C /mnt/backup .
# 5. 백업 완료 후 스냅샷 제거
$ umount /mnt/backup
$ lvremove -f data_vg/db_backup_snap
# 6. Thin Pool 사용량 모니터링
$ lvs -o +data_percent,metadata_percent data_vg/thin_data
LV VG Attr LSize Pool Origin Data% Meta%
thin_data data_vg twi-aotz-- 100.00g 45.23 12.34
# 7. 자동 확장 설정 (/etc/lvm/lvm.conf)
# thin_pool_autoextend_threshold = 80
# thin_pool_autoextend_percent = 20
dm-cache 하이브리드 스토리지 구성
느린 HDD와 빠른 SSD를 결합하여 성능과 용량을 모두 확보하는 구성:
# 환경: /dev/sdb (4TB HDD), /dev/nvme0n1 (512GB NVMe SSD)
# 1. PV 생성
$ pvcreate /dev/sdb /dev/nvme0n1
# 2. VG 생성 (HDD + SSD 함께)
$ vgcreate hybrid_vg /dev/sdb /dev/nvme0n1
# 3. 데이터 LV 생성 (HDD에)
$ lvcreate -L 2T -n data hybrid_vg /dev/sdb
# 4. 캐시 메타데이터 LV (SSD에, 작은 크기)
$ lvcreate -L 8G -n cache_meta hybrid_vg /dev/nvme0n1
# 5. 캐시 데이터 LV (SSD에, 큰 크기)
$ lvcreate -L 200G -n cache_data hybrid_vg /dev/nvme0n1
# 6. 캐시 풀 생성
$ lvconvert --type cache-pool --poolmetadata hybrid_vg/cache_meta hybrid_vg/cache_data
Converted hybrid_vg/cache_data to cache pool.
# 7. 데이터 LV에 캐시 연결
$ lvconvert --type cache --cachepool hybrid_vg/cache_data hybrid_vg/data
Logical volume hybrid_vg/data is now cached.
# 8. 캐시 정책 설정 (writeback = 쓰기도 캐싱)
$ lvchange --cachepolicy smq --cachesettings 'migration_threshold=2048' hybrid_vg/data
# 9. 캐시 상태 모니터링
$ lvs -o +cache_used_blocks,cache_total_blocks,cache_read_hits,cache_read_misses
LV VG Attr LSize Cache Used Total Reads Hits Misses
data hybrid_vg Cwi-a-C--- 2.00t 45.2% 52428800 98.5% 1.5%
# 10. 캐시 통계 (dmsetup)
$ dmsetup status hybrid_vg-data
0 4194304000 cache 8 2345/52428800 512 23456/98765 12345 67890 ...
# ^메타 ^데이터 ^블록 ^캐시적중 ^미스
캐시 정책: smq (Stochastic Multiqueue)가 기본이며 대부분의 워크로드에 적합합니다. writeback 모드는 쓰기도 캐싱하여 성능이 극대화되지만, 전원 장애 시 데이터 손실 위험이 있으므로 UPS나 배터리 백업이 권장됩니다.
커널 내부 구현
I/O 경로: bio 리매핑
DM 디바이스에 bio가 제출되면 다음 경로를 거칩니다:
dm_submit_bio(): DM의submit_bio콜백. RCU로 현재dm_table을 참조__split_and_process_bio(): bio가 여러 타겟에 걸치면 분할__map_bio(): 각 타겟의map()함수 호출- 타겟이
DM_MAPIO_REMAPPED반환 →submit_bio_noacct()로 하위 디바이스에 재제출 - 완료 시
clone_endio()→ 원래 bio의bi_end_io호출
/* drivers/md/dm.c — 핵심 I/O 경로 (간략화) */
static void dm_submit_bio(struct bio *bio)
{
struct mapped_device *md = bio->bi_bdev->bd_disk->private_data;
struct dm_table *map;
rcu_read_lock();
map = rcu_dereference(md->map);
if (unlikely(!map)) {
rcu_read_unlock();
bio_io_error(bio);
return;
}
__split_and_process_bio(md, map, bio);
rcu_read_unlock();
}
static void __map_bio(struct dm_target_io *tio)
{
struct dm_target *ti = tio->ti;
struct bio *clone = &tio->clone;
int r;
r = ti->type->map(ti, clone); /* 타겟의 map() 콜백 호출 */
switch (r) {
case DM_MAPIO_SUBMITTED:
break; /* 타겟이 직접 제출함 */
case DM_MAPIO_REMAPPED:
submit_bio_noacct(clone); /* 리매핑 후 재제출 */
break;
case DM_MAPIO_KILL:
case DM_MAPIO_REQUEUE:
/* 에러 또는 재큐잉 처리 */
break;
}
}
DM ioctl 인터페이스
사용자 공간은 /dev/mapper/control (misc device, major 10, minor 236)을 통해 DM을 제어합니다. ioctl() 시스템 콜(System Call)로 struct dm_ioctl을 전달합니다.
/* include/uapi/linux/dm-ioctl.h — 주요 ioctl 명령 */
#define DM_DEV_CREATE _IOWR(DM_IOCTL, DM_DEV_CREATE_CMD, ...)
#define DM_DEV_REMOVE _IOWR(DM_IOCTL, DM_DEV_REMOVE_CMD, ...)
#define DM_TABLE_LOAD _IOWR(DM_IOCTL, DM_TABLE_LOAD_CMD, ...)
#define DM_DEV_SUSPEND _IOWR(DM_IOCTL, DM_DEV_SUSPEND_CMD, ...)
#define DM_TABLE_STATUS _IOWR(DM_IOCTL, DM_TABLE_STATUS_CMD, ...)
/* DM 디바이스 생성 시퀀스:
* 1. DM_DEV_CREATE — 빈 mapped_device 생성
* 2. DM_TABLE_LOAD — 매핑 테이블 로드 (inactive table)
* 3. DM_DEV_SUSPEND — resume 플래그 설정하여 활성화
*
* 테이블 교체:
* 1. DM_TABLE_LOAD — 새 테이블을 inactive로 로드
* 2. DM_DEV_SUSPEND — suspend (기존 I/O 완료 대기)
* 3. DM_DEV_SUSPEND — resume (새 테이블 활성화)
*/
struct dm_ioctl {
__u32 version[3]; /* ioctl 프로토콜 버전 */
__u32 data_size; /* 전체 버퍼 크기 */
__u32 data_start; /* 데이터 시작 오프셋 */
__u32 target_count; /* 타겟 수 */
__s32 open_count; /* 열린 참조 수 */
__u32 flags; /* DM_*_FLAG */
__u32 event_nr; /* 이벤트 번호 */
__u32 dev; /* major:minor */
char name[128]; /* 디바이스 이름 */
char uuid[129]; /* UUID */
/* ... */
};
Suspend/Resume 메커니즘
DM의 suspend/resume은 테이블 원자적 교체의 핵심입니다:
/* suspend 과정 (간략화) */
static int dm_suspend(struct mapped_device *md, unsigned int suspend_flags)
{
/* 1. DMF_BLOCK_IO_FOR_SUSPEND 설정 → 새 bio를 보류 */
set_bit(DMF_BLOCK_IO_FOR_SUSPEND, &md->flags);
/* 2. 진행 중인 I/O 완료 대기 */
dm_wait_for_completion(md, TASK_INTERRUPTIBLE);
/* 3. 타겟의 postsuspend 콜백 호출 */
dm_table_postsuspend_targets(map);
return 0;
}
/* resume 과정 (간략화) */
static int dm_resume(struct mapped_device *md)
{
struct dm_table *map = md->new_map; /* inactive → active */
/* 1. 새 테이블을 active로 교체 (RCU) */
rcu_assign_pointer(md->map, map);
md->new_map = NULL;
/* 2. 타겟의 resume 콜백 호출 */
dm_table_resume_targets(map);
/* 3. DMF_BLOCK_IO_FOR_SUSPEND 해제 → 보류된 bio 재제출 */
clear_bit(DMF_BLOCK_IO_FOR_SUSPEND, &md->flags);
return 0;
}
DM 스택킹
DM 디바이스는 다른 DM 디바이스 위에 쌓을 수 있습니다. 실제 프로덕션에서 흔히 볼 수 있는 스택:
성능 비교
다양한 DM 타겟과 구성별 성능 특성을 벤치마크 데이터로 비교합니다.
처리량(Throughput) 비교 (순차 I/O)
1GB 파일 순차 읽기/쓰기 벤치마크 (fio, direct I/O, 4K 블록 크기):
| 구성 | 순차 읽기 (MB/s) | 순차 쓰기 (MB/s) | 랜덤 읽기 (IOPS) | 랜덤 쓰기 (IOPS) |
|---|---|---|---|---|
| Raw 디바이스 (baseline) | 550 | 520 | 95K | 85K |
| dm-linear (단일 디스크) | 548 | 518 | 94K | 84K |
| dm-striped (2 디스크) | 1050 | 980 | 180K | 160K |
| dm-striped (4 디스크) | 2100 | 1920 | 350K | 310K |
| dm-crypt (AES-XTS 256) | 485 | 420 | 82K | 68K |
| dm-crypt + AES-NI | 540 | 510 | 92K | 81K |
| dm-cache (80% 적중률) | 1200 | 1050 | 220K | 180K |
| dm-thin (오버헤드) | 530 | 480 | 88K | 75K |
측정 환경: Intel Xeon E5-2680 v4, 512GB RAM, NVMe SSD (Samsung 970 PRO). 벤치마크는 fio --direct=1 --rw=read --bs=4k --numjobs=16 --iodepth=32로 측정했습니다.
dm-crypt 암호화 오버헤드
암호화 알고리즘 및 키 크기별 성능 차이:
| 암호화 방식 | 하드웨어 가속 | 처리량 (MB/s) | 오버헤드 | CPU 사용률 |
|---|---|---|---|---|
| 암호화 없음 (baseline) | - | 550 | 0% | 5% |
| AES-XTS 128 | 없음 | 380 | -31% | 85% |
| AES-XTS 256 | 없음 | 350 | -36% | 90% |
| AES-XTS 128 | AES-NI | 530 | -4% | 12% |
| AES-XTS 256 | AES-NI | 510 | -7% | 15% |
| ChaCha20 | 없음 | 420 | -24% | 65% |
| Serpent-XTS 256 | 없음 | 180 | -67% | 98% |
# dm-crypt 성능 벤치마크 실행
$ cryptsetup benchmark
# Tests are approximate using memory only (no storage IO).
PBKDF2-sha1 1847619 iterations per second for 256-bit key
PBKDF2-sha256 2389045 iterations per second for 256-bit key
PBKDF2-sha512 1654321 iterations per second for 256-bit key
PBKDF2-ripemd160 1234567 iterations per second for 256-bit key
PBKDF2-whirlpool 876543 iterations per second for 256-bit key
argon2i 8 iterations, 1048576 memory, 4 parallel threads (CPUs) for 256-bit key (requested 2000 ms time)
argon2id 8 iterations, 1048576 memory, 4 parallel threads (CPUs) for 256-bit key (requested 2000 ms time)
# Algorithm | Key | Encryption | Decryption
aes-cbc 128b 1200.0 MiB/s 3800.0 MiB/s
serpent-cbc 128b 180.0 MiB/s 650.0 MiB/s
twofish-cbc 128b 220.0 MiB/s 380.0 MiB/s
aes-cbc 256b 1000.0 MiB/s 3200.0 MiB/s
serpent-cbc 256b 180.0 MiB/s 650.0 MiB/s
twofish-cbc 256b 220.0 MiB/s 380.0 MiB/s
aes-xts 256b 3500.0 MiB/s 3600.0 MiB/s
serpent-xts 256b 650.0 MiB/s 640.0 MiB/s
twofish-xts 256b 370.0 MiB/s 380.0 MiB/s
aes-xts 512b 3200.0 MiB/s 3300.0 MiB/s
serpent-xts 512b 650.0 MiB/s 640.0 MiB/s
twofish-xts 512b 370.0 MiB/s 380.0 MiB/s
# 실제 디스크 성능 측정 (암호화 볼륨)
$ dd if=/dev/zero of=/dev/mapper/encrypted bs=1M count=1024 oflag=direct
1024+0 records in
1024+0 records out
1073741824 bytes (1.1 GB, 1.0 GiB) copied, 2.1 s, 511 MB/s
dm-thin 오버헤드 분석
Thin Provisioning의 메타데이터 관리 및 블록 할당 오버헤드:
| 시나리오 | 처리량 (MB/s) | 지연 시간 (ms) | 오버헤드 원인 |
|---|---|---|---|
| LVM 일반 볼륨 (baseline) | 550 | 0.18 | - |
| Thin 볼륨 (블록 미할당) | 480 | 0.24 | 첫 쓰기 시 블록 할당 |
| Thin 볼륨 (블록 할당 완료) | 540 | 0.19 | 메타데이터 업데이트만 |
| Thin 스냅샷 (COW 발생) | 420 | 0.32 | 원본 블록 복사 + 메타 갱신 |
| Thin Pool 70% 사용 시 | 510 | 0.21 | 단편화(Fragmentation), 메타 탐색 증가 |
| Thin Pool 95% 사용 시 | 380 | 0.45 | 여유 블록 부족, 재할당 빈번 |
Thin Pool 용량 관리: 사용률이 80%를 넘으면 성능이 저하되기 시작합니다. thin_pool_autoextend_threshold=70로 설정하여 자동 확장을 활성화하고, dmeventd 데몬을 실행하여 모니터링하세요.
dm-cache 캐시 적중률 영향
캐시 적중률에 따른 성능 변화 (HDD + SSD 캐시 구성):
| 캐시 적중률 | 읽기 처리량 (MB/s) | 읽기 지연 (ms) | 쓰기 처리량 (MB/s) | 설명 |
|---|---|---|---|---|
| 0% (캐시 미사용) | 120 | 8.5 | 110 | HDD 직접 액세스 |
| 20% | 240 | 6.2 | 180 | 일부 핫 데이터 캐싱 |
| 50% | 480 | 3.1 | 350 | 워킹셋의 절반 캐싱 |
| 80% | 820 | 1.2 | 680 | 대부분 SSD에서 처리 |
| 95% | 1050 | 0.6 | 920 | 거의 모든 액세스 캐싱 |
| 100% (SSD만) | 1200 | 0.4 | 1100 | SSD 네이티브 성능 |
# 캐시 적중률 실시간 모니터링
$ watch -n 1 'lvs -o +cache_read_hits,cache_read_misses,cache_write_hits,cache_write_misses'
# dmsetup으로 상세 통계 확인
$ dmsetup status my_vg-cached_vol
0 419430400 cache 8 234567/524288 128 98765432/100234567 12345678 5432109 ...
# ^메타사용 ^블록 ^적중 ^미스 ^더티블록 ^프로모션
# 적중률 계산
# 읽기 적중률 = 98765432 / (98765432 + 12345678) * 100 = 88.9%
# fio로 워크로드별 캐시 효과 측정
$ fio --name=random_read --rw=randread --bs=4k --size=10G \
--numjobs=4 --iodepth=32 --direct=1 \
--filename=/dev/my_vg/cached_vol
RAID 레벨별 성능 비교
소프트웨어 RAID (md) 레벨에 따른 처리량과 용량 효율:
| RAID 레벨 | 디스크 수 | 읽기 (MB/s) | 쓰기 (MB/s) | 가용 용량 | 장점/단점 |
|---|---|---|---|---|---|
| RAID 0 | 4 | 2200 | 2100 | 4TB (100%) | 최고 성능, 내결함성 없음 |
| RAID 1 | 2 | 550 | 520 | 1TB (50%) | 미러링, 읽기 성능 향상 |
| RAID 5 | 4 | 1650 | 420 | 3TB (75%) | 용량 효율적, 쓰기 느림 |
| RAID 6 | 4 | 1450 | 380 | 2TB (50%) | 2개 디스크 장애 허용 |
| RAID 10 | 4 | 1100 | 1050 | 2TB (50%) | 성능+안정성 균형 |
RAID 5/6 쓰기 페널티: 패리티 계산으로 인해 쓰기 성능이 크게 저하됩니다. RAID 5는 랜덤 쓰기 시 4배 I/O 증폭(read-modify-write), RAID 6은 6배 증폭이 발생합니다. 쓰기 중심 워크로드에는 RAID 10이 권장됩니다.
성능 튜닝과 디버깅(Debugging)
DM 성능 파라미터
| 파라미터 | 경로 / 설정 | 설명 |
|---|---|---|
| nr_requests | /sys/block/dm-*/queue/nr_requests | 최대 요청 큐 깊이 |
| read_ahead_kb | /sys/block/dm-*/queue/read_ahead_kb | 미리 읽기 크기 |
| scheduler | /sys/block/dm-*/queue/scheduler | DM 디바이스의 I/O 스케줄러 (보통 none) |
| max_sectors_kb | /sys/block/dm-*/queue/max_sectors_kb | 최대 I/O 크기 |
| dm-crypt workqueue | --perf-no_read_workqueue | 읽기 워크큐 바이패스 |
| dm-thin block size | thin-pool 생성 시 지정 | 씬 풀 블록 크기 (64KB~1MB) |
dm-crypt 성능 최적화
# 1. 워크큐 바이패스 (커널 5.9+)
$ cryptsetup open --perf-no_read_workqueue --perf-no_write_workqueue \
--perf-submit_from_crypt_cpus /dev/sda2 cryptroot
# 2. 하드웨어 암호화 가속 확인
$ grep -m1 aes /proc/cpuinfo
flags : ... aes ...
# AES-NI 지원 확인
# 3. 암호화 벤치마크
$ cryptsetup benchmark
# PBKDF2-sha256 1234567 iterations per second for 256-bit key
# aes-xts 512b 4500.0 MiB/s 4600.0 MiB/s
# aes-xts 256b 5200.0 MiB/s 5300.0 MiB/s
# 4. I/O 스케줄러: DM 디바이스는 none 권장 (하위 디바이스에서 스케줄링)
$ echo none > /sys/block/dm-0/queue/scheduler
dm-thin 모니터링
# thin-pool 사용량 모니터링 스크립트
$ dmsetup status my_vg-thin_pool
0 209715200 thin-pool 1 42/524288 150234/209715200 - rw no_discard_passdown
# ^tx ^meta ^data
# 사용률 계산
# 메타데이터: 42/524288 = 0.008%
# 데이터: 150234/209715200 = 0.07%
# LVM thin 자동 확장 설정 (/etc/lvm/lvm.conf)
# thin_pool_autoextend_threshold = 70 (70% 사용 시)
# thin_pool_autoextend_percent = 20 (20% 확장)
# dmeventd 데몬 확인 (자동 확장 트리거)
$ systemctl status dm-event.service
디버깅 기법
# 1. DM 커널 로그 확인
$ dmesg | grep -i "device-mapper\|dm-"
# 2. DM 디바이스 상세 정보
$ dmsetup info -c
Name Maj Min Stat Open Targ Event UUID
my_vg-root 253 0 L--w 1 1 0 LVM-...
cryptroot 253 1 L--w 1 1 0 CRYPT-...
# 3. bio 트레이싱 (BPF 기반)
$ bpftrace -e 'kprobe:dm_submit_bio {
@[comm] = count();
}'
# 4. dm-crypt I/O 지연 측정
$ bpftrace -e '
kprobe:crypt_endio { @start[arg0] = nsecs; }
kretprobe:crypt_endio /@start[arg0]/ {
@lat = hist(nsecs - @start[arg0]);
delete(@start[arg0]);
}'
# 5. blktrace로 DM 디바이스 추적
$ blktrace -d /dev/dm-0 -o dm_trace
$ blkparse -i dm_trace
# 6. DM 통계 (dmstats)
$ dmstats create --alldevices
$ dmstats print --alldevices
# reads, writes, io_ticks, queue_size 등 세부 통계
$ dmstats delete --alldevices
자주 발생하는 문제
dmsetup info에서 Suspended 상태 확인 — 의도치 않은 suspend가 원인일 수 있음cat /proc/mdstat으로 MD 리빌드 진행 중인지 확인- dm-thin의 경우 풀 공간 고갈 여부 (
dmsetup status) - dm-crypt 워크큐 병목 (
/proc/pressure/io확인) iostat -x 1로 하위 디바이스의%util,await모니터링
dmsetup remove 시 "Device or resource busy" 에러가 발생하면, dmsetup info의 Open count가 0인지 확인합니다. 마운트, 스왑(Swap), 다른 DM 스택, 또는 udev 규칙이 디바이스를 열고 있을 수 있습니다. dmsetup remove --force는 deferred removal을 사용하며, 마지막 참조가 해제될 때 제거됩니다.
커널 설정 옵션
# DM 관련 커널 설정 (make menuconfig)
# Device Drivers → Multiple devices driver support (RAID and LVM)
CONFIG_BLK_DEV_DM=y # Device Mapper 코어
CONFIG_DM_CRYPT=m # dm-crypt
CONFIG_DM_SNAPSHOT=m # dm-snapshot
CONFIG_DM_THIN_PROVISIONING=m # dm-thin
CONFIG_DM_CACHE=m # dm-cache
CONFIG_DM_WRITECACHE=m # dm-writecache
CONFIG_DM_VERITY=y # dm-verity (부트 검증용이므로 빌트인 권장)
CONFIG_DM_VERITY_FEC=y # dm-verity FEC
CONFIG_DM_MIRROR=m # dm-mirror (dm-raid1)
CONFIG_DM_RAID=m # dm-raid (LVM RAID)
CONFIG_DM_ZERO=m # dm-zero (/dev/zero 유사)
CONFIG_DM_DELAY=m # dm-delay (I/O 지연 주입, 테스트용)
CONFIG_DM_FLAKEY=m # dm-flakey (장애 시뮬레이션, 테스트용)
# MD (소프트웨어 RAID)
CONFIG_MD=y # MD 코어
CONFIG_BLK_DEV_MD=y # MD 블록 디바이스
CONFIG_MD_RAID0=m # RAID 0
CONFIG_MD_RAID1=m # RAID 1
CONFIG_MD_RAID456=m # RAID 4/5/6
CONFIG_MD_RAID10=m # RAID 10
CONFIG_MD_AUTODETECT=y # 부팅 시 자동 감지
테스트용 DM 타겟
# dm-flakey: 장애 시뮬레이션
# 60초 정상 → 5초 동안 모든 쓰기 실패
$ dmsetup create flakey_test --table \
"0 2097152 flakey /dev/sda1 0 60 5"
# ^dev ^off ^up ^down
# dm-delay: I/O 지연 주입
# 읽기 100ms, 쓰기 200ms 지연
$ dmsetup create delay_test --table \
"0 2097152 delay /dev/sda1 0 100 /dev/sda1 0 200"
# ^dev ^off ^read_ms ^dev ^off ^write_ms
# dm-zero: /dev/zero와 유사한 블록 디바이스 (읽기=0, 쓰기=무시)
$ dmsetup create zero_dev --table "0 2097152 zero"
# dm-error: 모든 I/O를 에러로 반환 (에러 처리 테스트)
$ dmsetup create error_dev --table "0 2097152 error"
트러블슈팅
Device Mapper와 LVM 사용 중 발생하는 일반적인 문제와 해결 방법을 정리합니다.
일반적인 문제와 해결책
| 문제 | 증상 | 원인 | 해결 방법 |
|---|---|---|---|
| LVM 메타데이터 손상 | Couldn't find device with uuid |
전원 장애, 디스크 오류 | vgcfgrestore로 백업에서 복구 |
| Thin Pool 가득 참 | dm-thin: Data device full |
오버프로비저닝 초과 | lvextend로 풀 확장 또는 볼륨 삭제 |
| dm-crypt 성능 저하 | 암호화 볼륨 느림 (< 100MB/s) | 워크큐 병목, AES-NI 미사용 | --perf-* 옵션, 하드웨어 가속 확인 |
| VG 활성화 실패 | Volume group not found |
PV 서명 누락, 디스크 미인식 | pvscan --cache, vgscan 재실행 |
| 스냅샷 용량 부족 | snapshot overflow |
변경량이 스냅샷 크기 초과 | lvextend로 스냅샷 확장 (가능 시) |
| RAID 동기화 느림 | resync=0.1% 장시간 정체 |
기본 속도 제한 (1MB/s) | /proc/sys/dev/raid/speed_limit_* 조정 |
| dm 디바이스 제거 불가 | Device or resource busy |
마운트 상태, 프로세스(Process)가 사용 중 | lsof /dev/dm-*로 확인 후 종료 |
LVM 메타데이터 복구
LVM 메타데이터가 손상되었을 때 복구하는 절차입니다:
# 1. 메타데이터 손상 증상 확인
$ vgs
/dev/sdb: read failed after 0 of 4096 at 0: Input/output error
Couldn't find device with uuid abcd-1234-...
# 2. 백업 메타데이터 위치 확인
$ ls -lt /etc/lvm/archive/
# VG 설정 변경 시마다 자동 백업됨
-rw------- 1 root root 1234 Feb 15 10:00 data_vg_00042-1234567890.vg
-rw------- 1 root root 1234 Feb 14 15:30 data_vg_00041-1234567889.vg
# 3. 가장 최근 백업으로 복구
$ vgcfgrestore -f /etc/lvm/archive/data_vg_00042-1234567890.vg data_vg
Restored volume group data_vg.
# 4. VG 재활성화
$ vgchange -ay data_vg
2 logical volume(s) in volume group "data_vg" now active
# 5. 메타데이터 백업 수동 생성 (예방)
$ vgcfgbackup data_vg
Volume group "data_vg" successfully backed up.
# 백업 파일 위치: /etc/lvm/backup/data_vg
# 6. 디스크 교체 후 메타데이터 복구
# 새 디스크에 PV 생성
$ pvcreate /dev/sdc --restorefile /etc/lvm/backup/data_vg --uuid abcd-1234-...
$ vgcfgrestore -f /etc/lvm/backup/data_vg data_vg
메타데이터 백업 중요성: /etc/lvm/archive/와 /etc/lvm/backup/은 별도 디스크에도 백업하세요. VG 구조 변경 전에 항상 vgcfgbackup을 실행하여 최신 백업을 유지하는 것이 권장됩니다.
Thin Pool 용량 부족 대응
Thin Pool이 가득 차면 모든 thin 볼륨의 I/O가 정지됩니다. 긴급 복구 절차:
# 1. 경고 메시지 확인
$ dmesg | tail -20
[12345.678] dm-3: thin: Data device (dm-4) discard unsupported: Disabling discard passdown.
[12356.789] dm-3: thin: 253:4: reached low water mark for data device: sending event.
[12367.890] dm-3: thin: 253:4: switching pool to out-of-data-space (queue IO) mode
# 2. Thin Pool 상태 확인
$ dmsetup status data_vg-thin_pool
0 209715200 thin-pool 1 524287/524288 209715000/209715200 - rw ...
# ^메타 99.9% ^데이터 99.99% 사용
# 3. 긴급 대응: 풀 확장
# VG에 여유 공간이 있는 경우
$ lvextend -L +100G data_vg/thin_pool
Size of logical volume data_vg/thin_pool_tdata changed from 200 GiB to 300 GiB.
# 4. VG 여유 공간 없는 경우: 새 디스크 추가
$ pvcreate /dev/sdd
$ vgextend data_vg /dev/sdd
$ lvextend -L +100G data_vg/thin_pool
# 5. 자동 확장 설정 (/etc/lvm/lvm.conf)
$ vi /etc/lvm/lvm.conf
# activation {
# thin_pool_autoextend_threshold = 70 # 70% 사용 시 확장
# thin_pool_autoextend_percent = 20 # 20% 확장
# }
# 6. dmeventd 데몬 활성화 (자동 확장 트리거)
$ systemctl enable --now dm-event.service
$ systemctl status dm-event.service
# 7. 용량 모니터링 스크립트
#!/bin/bash
# /usr/local/bin/check-thin-pool.sh
USAGE=$(dmsetup status data_vg-thin_pool | awk '{split($7,a,"/"); print int(a[1]/a[2]*100)}')
if [ $USAGE -gt 80 ]; then
echo "WARNING: Thin pool at ${USAGE}%" | mail -s "LVM Alert" admin@example.com
fi
# cron으로 5분마다 실행
# */5 * * * * /usr/local/bin/check-thin-pool.sh
dm-crypt 성능 문제 진단
암호화 볼륨이 예상보다 느릴 때 확인할 사항:
# 1. AES-NI 하드웨어 가속 확인
$ grep -m1 aes /proc/cpuinfo
flags : ... aes ...
# 'aes' 플래그가 있으면 AES-NI 지원
# 없으면 BIOS에서 활성화하거나 CPU 교체 필요
# 2. 암호화 모듈 로드 확인
$ lsmod | grep aes
aesni_intel 368640 10
aes_x86_64 20480 1 aesni_intel
crypto_simd 16384 1 aesni_intel
cryptd 24576 2 crypto_simd,ghash_clmulni_intel
# 3. cryptsetup 성능 옵션 확인
$ cryptsetup status encrypted_vol
/dev/mapper/encrypted_vol is active.
type: LUKS2
cipher: aes-xts-plain64
keysize: 512 bits
# 워크큐 바이패스 옵션 확인 불가 (재마운트 필요)
# 4. 성능 최적화 재마운트
$ umount /mnt/encrypted
$ cryptsetup close encrypted_vol
# 커널 5.9+ 에서 워크큐 바이패스 (극적인 성능 향상)
$ cryptsetup open --perf-no_read_workqueue \
--perf-no_write_workqueue \
--perf-submit_from_crypt_cpus \
/dev/sdc encrypted_vol
$ mount /dev/mapper/encrypted_vol /mnt/encrypted
# 5. I/O 스케줄러 최적화
# dm-crypt는 하위 디바이스에서 스케줄링하므로 none 권장
$ cat /sys/block/dm-0/queue/scheduler
[none] mq-deadline kyber
# 이미 none이면 OK
$ echo none > /sys/block/dm-0/queue/scheduler
# 6. 벤치마크 비교
# 암호화 없는 raw 디바이스
$ dd if=/dev/zero of=/dev/sdc bs=1M count=1024 oflag=direct
1073741824 bytes copied, 1.8 s, 596 MB/s
# 암호화 볼륨
$ dd if=/dev/zero of=/dev/mapper/encrypted_vol bs=1M count=1024 oflag=direct
1073741824 bytes copied, 2.1 s, 511 MB/s
# 14% 오버헤드 (AES-NI 사용 시 정상)
VG 활성화 문제 해결
부팅 시 또는 수동으로 VG를 활성화할 수 없을 때:
# 1. PV 인식 확인
$ pvs
WARNING: Device for PV abcd-1234 not found or rejected by a filter.
PV VG Fmt Attr PSize PFree
/dev/sdb data_vg lvm2 a-- 500.00g 100.00g
[unknown] data_vg lvm2 a-m 500.00g 0
# 2. PV 캐시 갱신
$ pvscan --cache
pvscan[12345] PV /dev/sdb online.
pvscan[12345] PV /dev/sdc online.
# 3. VG 스캔
$ vgscan
Found volume group "data_vg" using metadata type lvm2
# 4. VG 활성화
$ vgchange -ay data_vg
2 logical volume(s) in volume group "data_vg" now active
# 5. 필터 문제 확인 (/etc/lvm/lvm.conf)
$ vi /etc/lvm/lvm.conf
# devices {
# filter = [ "a|/dev/sd.*|", "r|.*|" ] # /dev/sd* 만 허용
# # multipath 환경에서는 주의 필요
# }
# 6. udev 규칙 갱신 (디바이스 이름 변경 시)
$ udevadm trigger
$ udevadm settle
# 7. 부팅 시 자동 활성화 설정
# /etc/fstab 예제
/dev/data_vg/mysql_data /var/lib/mysql ext4 defaults 0 2
# initramfs에 LVM 포함 (루트 파일 시스템이 LVM인 경우)
$ update-initramfs -u
RAID 동기화 속도 최적화
소프트웨어 RAID (md) 재구축이 너무 느릴 때:
# 1. 현재 동기화 상태 확인
$ cat /proc/mdstat
Personalities : [raid1] [raid5]
md0 : active raid5 sdd1[3] sdc1[2] sdb1[1] sda1[0]
2930277376 blocks super 1.2 level 5, 512k chunk, algorithm 2 [4/3] [UUU_]
[>....................] recovery = 0.1% (987654/976759168) finish=1234.5min speed=12345K/sec
# 2. 속도 제한 확인
$ cat /proc/sys/dev/raid/speed_limit_min
1000 # 최소 1MB/s (너무 낮음)
$ cat /proc/sys/dev/raid/speed_limit_max
200000 # 최대 200MB/s
# 3. 속도 제한 증가 (일시적)
$ echo 50000 > /proc/sys/dev/raid/speed_limit_min # 50MB/s
$ echo 500000 > /proc/sys/dev/raid/speed_limit_max # 500MB/s
# 4. 영구 설정 (/etc/sysctl.conf)
dev.raid.speed_limit_min = 50000
dev.raid.speed_limit_max = 500000
# 5. 적용
$ sysctl -p
# 6. stripe_cache_size 증가 (RAID 5/6만 해당)
$ cat /sys/block/md0/md/stripe_cache_size
256 # 기본값 (페이지 수)
# RAM 여유 있으면 증가 (성능 향상)
$ echo 8192 > /sys/block/md0/md/stripe_cache_size
# 7. 재구축 진행 상황 모니터링
$ watch -n 5 'cat /proc/mdstat'
# 8. I/O 우선순위 낮춤 (운영 중 재구축 시)
# 재구축 스레드의 ionice 조정
$ ps aux | grep "\[md.*_resync\]"
$ ionice -c 3 -p [PID] # idle 클래스로 변경
재구축 시간 vs 시스템 부하: speed_limit_max를 높이면 재구축은 빨라지지만 일반 I/O 성능이 저하됩니다. 운영 중에는 50-100MB/s, 유지보수 시간에는 500MB/s+ 권장입니다.
진단 명령어 모음
문제 진단 시 유용한 명령어 체크리스트:
#!/bin/bash
# DM/LVM 진단 스크립트
echo "=== 1. 물리 볼륨 상태 ==="
pvs -o +pv_used,pv_free,pv_attr
echo -e "\n=== 2. 볼륨 그룹 상태 ==="
vgs -o +vg_free,vg_extent_count,vg_free_count
echo -e "\n=== 3. 논리 볼륨 상태 ==="
lvs -o +lv_size,lv_attr,data_percent,metadata_percent
echo -e "\n=== 4. Thin Pool 상세 ==="
lvs -a -o +devices,pool_lv,data_percent,metadata_percent | grep thin
echo -e "\n=== 5. DM 디바이스 목록 ==="
dmsetup ls --tree
echo -e "\n=== 6. DM 타겟 상태 ==="
dmsetup status
echo -e "\n=== 7. 커널 로그 (최근 20줄) ==="
dmesg | grep -i "dm-\|lvm\|device-mapper" | tail -20
echo -e "\n=== 8. /dev/mapper 심볼릭 링크 ==="
ls -l /dev/mapper/
echo -e "\n=== 9. RAID 상태 ==="
cat /proc/mdstat
echo -e "\n=== 10. 블록 디바이스 트리 ==="
lsblk -o NAME,SIZE,TYPE,MOUNTPOINT,FSTYPE
dm-pcache: 영구 메모리 캐시 (v6.18+)
커널 6.18에서 도입된 dm-pcache는 영구 메모리(NVDIMM, CXL 메모리)를 블록 디바이스의 읽기/쓰기 캐시로 활용하는 Device Mapper 타겟입니다. 기존 dm-cache/dm-writecache와 달리 영구 메모리의 바이트 주소 지정 특성을 최대한 활용합니다.
| 구분 | dm-cache | dm-writecache | dm-pcache (v6.18+) |
|---|---|---|---|
| 캐시 미디어 | SSD | NVDIMM/SSD | NVDIMM/CXL 메모리 |
| 캐시 대상 | 읽기+쓰기 | 쓰기 전용 | 읽기+쓰기 |
| 접근 방식 | 블록 I/O | DAX (바이트 주소) | DAX (바이트 주소) |
| 전원 손실 안전 | 저널/메타데이터 | 영구 메모리 특성 활용 | 영구 메모리 + 메타데이터 보호 |
| 적합한 환경 | SSD 계층화 | 쓰기 가속 | CXL 메모리 계층화 |
dm-thin 내부 구조
dm-thin은 Linux 스토리지 스택에서 가장 복잡한 DM 타겟 중 하나입니다. 메타데이터 풀, 데이터 풀, B-tree 기반 매핑, on-demand provisioning, overprovisioning을 심층적으로 분석합니다.
thin-pool 내부 구조
thin-pool은 두 개의 별도 디바이스로 구성됩니다:
- metadata device: B-tree 기반 매핑 정보, 참조 카운트(Reference Count), 공간 맵을 저장. 트랜잭션(Transaction) 저널링(Journaling)으로 크래시 일관성 보장
- data device: 실제 사용자 데이터가 저장되는 블록 풀. 고정 크기 블록(기본 64KB) 단위로 관리
B-tree 구조와 매핑
dm-thin의 메타데이터는 2단계 B-tree로 관리됩니다. 첫 번째 레벨은 thin device ID를 키로, 두 번째 레벨은 논리 블록 번호를 키로 사용합니다.
/* drivers/md/persistent-data/dm-btree.h */
/* 2-레벨 B-tree: device_id → (logical_block → physical_block) */
struct dm_btree_info {
struct dm_transaction_manager *tm;
unsigned int levels; /* 2 for thin */
struct dm_btree_value_type value_type[2];
};
/* thin 매핑 엔트리: 물리 블록 번호 + 플래그 */
struct disk_block_time {
__le64 block_time; /* 상위 24비트: 시간, 하위 40비트: 물리 블록 번호 */
};
/* 스냅샷 시 btree 노드를 COW — 공유 서브트리의 refcount 증가 */
오버프로비저닝과 공간 관리
thin-pool의 핵심 특성은 오버프로비저닝입니다. 논리 볼륨의 합계가 물리 풀 크기를 초과할 수 있으며, 실제 쓰기 시점까지 블록 할당을 지연합니다.
# 오버프로비저닝 비율 확인
$ lvs -o +lv_size,data_percent,metadata_percent vg0/thin_pool
LV VG Attr LSize Data% Meta%
thin_pool vg0 twi-a-t--- 100.00g 42.35 12.50
# thin volume들의 논리 합계
$ lvs -o lv_name,lv_size --select 'pool_lv=thin_pool'
LV LSize
vol_a 500.00g # 논리 500GB
vol_b 300.00g # 논리 300GB
snap_a 500.00g # 논리 500GB (스냅샷)
# 논리 합계 1300GB / 물리 100GB = 오버프로비저닝 비율 13:1
# 자동 확장 설정 (/etc/lvm/lvm.conf)
thin_pool_autoextend_threshold = 70 # 70% 사용 시 자동 확장
thin_pool_autoextend_percent = 20 # 20%씩 확장
# TRIM/discard 지원으로 공간 회수
$ fstrim -v /mnt/thin_vol
/mnt/thin_vol: 48.3 GiB (51858432000 bytes) trimmed
thin_pool_autoextend_threshold는 데이터와 메타데이터 모두에 적용해야 합니다. lvs -o metadata_percent로 정기적으로 확인하세요.
dm-cache SMQ 정책
dm-cache의 기본 캐시 교체 정책인 SMQ(Stochastic Multi-Queue)는 Linux 4.0에서 도입되었으며, 기존 MQ 정책을 대체합니다. 적중 빈도를 확률적으로 추적하여 낮은 메모리 오버헤드로 높은 적중률을 달성합니다.
SMQ vs MQ 비교
| 특성 | MQ (레거시) | SMQ (기본) |
|---|---|---|
| 메모리 사용 | 캐시 블록당 ~88바이트 | 캐시 블록당 ~4바이트 |
| 적중 추적 | 정확한 카운터 | 확률적 카운터 (8레벨) |
| 큐 구조 | hot/warm/cold 3단계 | hot/warm/cold + multiqueue |
| 프로모션 지연 | 설정 가능 | 적응형 (워크로드 기반) |
| 성능 (대규모) | 메모리 압박 시 저하 | 일정하게 유지 |
Dirty Block Writeback
SMQ는 백그라운드 writeback을 통해 dirty 블록을 origin 디바이스에 기록합니다. writeback 정책은 캐시 사용률과 dirty 비율에 따라 동적으로 조정됩니다.
# writeback 관련 파라미터 조정
$ dmsetup message my_cache 0 "migration_threshold 2048"
# 한 번에 마이그레이션할 최대 섹터 수 (기본 2048 = 1MB)
# dirty 블록 비율 확인
$ dmsetup status my_cache
0 419430400 cache 8 12345/65536 128 204800/409600 156789 43210 12345 567 89 1 writeback 2 migration_threshold 2048 smq 0 rw -
# ^dirty ^read_hits ^write_hits ^read_miss ^write_miss
# 수동 flush (모든 dirty 블록을 origin에 기록)
$ dmsetup message my_cache 0 "write_promote_threshold 0"
$ dmsetup message my_cache 0 "write_promote_threshold 8" # 원복
# cleaner 정책으로 전환 (전체 writeback 후 캐시 제거용)
$ lvchange --cachepolicy cleaner vg0/cached_lv
migration_threshold를 높이면 배경 마이그레이션 처리량이 증가하지만 전경 I/O에 영향을 줄 수 있습니다. SSD 수명을 고려하여 writethrough 모드도 검토하세요.
dm-snapshot COW 메커니즘
dm-snapshot의 Copy-on-Write(COW) 메커니즘은 원본 디바이스의 특정 시점 상태를 효율적으로 보존합니다. 쓰기 시점에만 변경될 블록을 복사하므로 초기 스냅샷 생성이 즉시 완료됩니다.
COW 쓰기 경로
예외 저장소(Exception Store)
예외 저장소는 원본 청크와 COW 청크 간의 매핑을 관리합니다. Persistent 모드에서는 COW 디바이스의 헤더 영역에 예외 테이블이 기록되어 재부팅 후에도 유지됩니다.
/* drivers/md/dm-snap.c */
struct dm_exception {
struct rb_node node; /* 레드-블랙 트리 노드 */
chunk_t old_chunk; /* 원본 청크 번호 */
chunk_t new_chunk; /* COW 디바이스 내 청크 번호 */
};
/* on-disk 예외 포맷 */
struct disk_exception {
__le64 old_chunk;
__le64 new_chunk;
};
/* 스냅샷 읽기 시: 예외 테이블 조회 → COW 또는 원본에서 읽기 */
스냅샷 Merge
snapshot-merge 타겟은 스냅샷의 변경 사항을 원본에 병합합니다. LVM의 lvconvert --merge가 이를 활용합니다.
# 스냅샷 병합 (다음 활성화 시 자동 시작)
$ lvconvert --merge vg0/snap1
Merging of volume vg0/snap1 started.
vg0/origin: Merged: 23.45%
# merge 진행률 확인
$ lvs -o +lv_merge_failed,merge_percent vg0/origin
LV Attr Mrg%
origin Owi-a-s--- 45.20
# dmsetup으로 merge 상태 확인
$ dmsetup status origin
0 20971520 snapshot-merge 45/100 16
dm-era와 dm-clone
dm-era는 블록 변경 추적(Change Tracking)을 제공하며, dm-clone은 원격 디바이스의 온라인 복제를 지원합니다. 두 타겟 모두 증분 백업과 재해 복구에 핵심적인 역할을 합니다.
dm-era: 변경 추적
dm-era는 각 데이터 블록이 마지막으로 쓰인 era(시대) 번호를 추적합니다. 특정 era 이후 변경된 블록만 식별하여 증분 백업의 효율을 극대화합니다.
# dm-era 디바이스 생성
$ dmsetup create era_meta --table "0 8192 linear /dev/sdd1 0"
$ dmsetup create era_data --table "0 209715200 linear /dev/sdd2 0"
$ dmsetup create my_era --table \
"0 209715200 era /dev/mapper/era_meta /dev/mapper/era_data 128"
# ^block_size(섹터)
# 현재 era 확인
$ dmsetup status my_era
0 209715200 era 128 5 - -
# ^block_size ^current_era
# era를 다음으로 진행 (체크포인트)
$ dmsetup message my_era 0 "checkpoint"
# era 3 이후 변경된 블록 목록 조회
$ dmsetup message my_era 0 "era_is_after 3"
# 비트맵 형태로 변경 블록 반환 → 증분 백업 도구에서 활용
# 증분 백업 워크플로:
# 1. 백업 시작 전 checkpoint
# 2. 이전 era 이후 변경된 블록만 추출
# 3. 변경된 블록만 백업 대상에 복사
dm-clone: 온라인 복제
dm-clone(Linux 5.4+)은 원격 또는 로컬의 소스 디바이스를 대상 디바이스에 백그라운드로 복제하면서, 동시에 대상 디바이스에 대한 I/O를 허용합니다.
dm-zoned
dm-zoned(Linux 4.13+)은 Zoned Block Device(ZBD)를 기존 파일시스템과 호환되도록 해주는 DM 타겟입니다. SMR(Shingled Magnetic Recording) HDD나 ZNS(Zoned Namespaces) SSD의 순차 쓰기 제약을 투명하게 처리합니다.
Zone 유형과 매핑
| Zone 유형 | 쓰기 방식 | dm-zoned 역할 |
|---|---|---|
| Conventional | 랜덤 쓰기 가능 | 메타데이터 + 버퍼(Buffer)로 활용 |
| Sequential Required | 순차 쓰기만 허용 | 데이터 저장, write pointer 관리 |
| Sequential Preferred | 순차 권장 | 순차 zone으로 취급 |
# dm-zoned 디바이스 생성 (SMR HDD)
$ dmzadm --format /dev/sdb
# → conventional zone 자동 감지, 메타데이터 초기화
# dm-zoned 활성화
$ dmzadm --start /dev/sdb
# → /dev/dm-N 생성, ext4 마운트 가능
# zone 정보 확인
$ blkzone report /dev/sdb | head -20
start: 0x000000000, len 0x080000, cap 0x080000, wptr 0x000000 reset:0 non-seq:0, zcond: 0(nw) [type: 1(CONVENTIONAL)]
start: 0x000080000, len 0x080000, cap 0x080000, wptr 0x020000 reset:0 non-seq:0, zcond: 2(oi) [type: 2(SEQ_WRITE_REQUIRED)]
# 캐시 디바이스 추가 (SSD를 conventional zone 대용)
$ dmzadm --format --cache=/dev/nvme0n1p1 /dev/sdb
# SSD를 버퍼 zone으로 활용 → 랜덤 쓰기 성능 대폭 향상
bio Remapping 경로
Device Mapper의 핵심은 bio 리매핑입니다. 상위 파일시스템이 제출한 bio가 DM 레이어를 통과하여 하위 물리 디바이스에 도달하는 전체 경로를 추적합니다.
bio 분할(Splitting) 메커니즘
bio가 여러 타겟에 걸칠 때 DM은 bio를 분할합니다. 각 타겟의 max_io_len도 분할 단위에 영향을 줍니다.
/* drivers/md/dm.c — bio 분할 핵심 */
static void __split_and_process_bio(
struct mapped_device *md,
struct dm_table *map,
struct bio *bio)
{
struct clone_info ci;
init_clone_info(&ci, md, map, bio);
while (ci.sector_count) {
struct dm_target *ti = dm_table_find_target(map, ci.sector);
sector_t len = min(ci.sector_count,
max_io_len_target_boundary(ti, ci.sector));
/* clone bio 생성, 타겟 범위로 자르기 */
__clone_and_map_data_bio(&ci, ti, ci.sector, &len);
ci.sector += len;
ci.sector_count -= len;
}
}
완료 경로와 에러 처리
clone bio의 완료는 clone_endio()에서 처리됩니다. 타겟의 end_io() 콜백이 있으면 호출하고, 원래 bio의 참조 카운트를 감소시킵니다.
/* clone_endio — clone bio 완료 콜백 */
static void clone_endio(struct bio *bio)
{
struct dm_target_io *tio = clone_to_tio(bio);
struct dm_target *ti = tio->ti;
dm_endio_fn endio = ti->type->end_io;
if (endio) {
int r = endio(ti, bio, &tio->info);
if (r == DM_ENDIO_REQUEUE) {
/* bio를 재큐잉 */
return;
}
}
/* 원래 bio의 참조 카운트 감소 */
dec_pending(tio->io, bio->bi_status);
free_tio(tio);
}
중첩 DM 스택
프로덕션 환경에서는 여러 DM 타겟을 중첩하여 복합 스토리지 서비스를 구성합니다. 대표적인 조합으로 LVM thin + dm-crypt + dm-integrity 다층 구성이 있습니다.
실전 다층 구성 예제
# 실전 4계층 DM 스택 구성
# 물리 → dm-integrity → dm-crypt → dm-thin → 파일시스템
# 1. dm-integrity: 데이터 무결성 (체크섬)
$ integritysetup format /dev/sda1 --integrity sha256
$ integritysetup open /dev/sda1 integrity_sda1
# 2. dm-crypt: LUKS 암호화 (dm-integrity 위에)
$ cryptsetup luksFormat /dev/mapper/integrity_sda1
$ cryptsetup open /dev/mapper/integrity_sda1 crypt_sda1
# 3. LVM thin-pool (dm-crypt 위에)
$ pvcreate /dev/mapper/crypt_sda1
$ vgcreate secure_vg /dev/mapper/crypt_sda1
$ lvcreate -L 80G -T secure_vg/thin_pool
$ lvcreate -V 200G -T secure_vg/thin_pool -n app_data
# 결과 스택 확인
$ lsblk /dev/sda1
NAME MAJ:MIN RM SIZE TYPE MOUNTPOINT
sda1 8:1 0 100G part
└─integrity_sda1 253:0 0 99G dm
└─crypt_sda1 253:1 0 99G crypt
└─secure_vg-thin_pool_tdata 253:2 0 80G lvm
└─secure_vg-app_data 253:4 0 200G lvm /data
# dmsetup으로 전체 스택 트리 확인
$ dmsetup ls --tree
secure_vg-app_data (253:4)
└─secure_vg-thin_pool (253:3)
└─secure_vg-thin_pool_tdata (253:2)
└─crypt_sda1 (253:1)
└─integrity_sda1 (253:0)
└─ (8:1)
중첩 스택 성능 영향
| 스택 깊이 | 구성 | 순차 읽기 (MB/s) | 랜덤 4K IOPS | 추가 지연 (μs) |
|---|---|---|---|---|
| 0 (Raw) | 물리 디바이스 | 550 | 95K | 0 |
| 1 | dm-linear (LVM) | 548 | 94K | ~2 |
| 2 | + dm-crypt (AES-NI) | 530 | 88K | ~15 |
| 3 | + dm-integrity (sha256) | 410 | 62K | ~50 |
| 4 | + dm-thin | 380 | 55K | ~70 |
dm-integrity가 포함될 경우 여분의 bio 할당 풀(bioset)이 필요하여 메모리 사용량도 증가합니다. /proc/meminfo의 Slab 사용량을 모니터링하세요.
dm ioctl 인터페이스
DM의 사용자 공간 인터페이스는 /dev/mapper/control 문자 디바이스를 통한 ioctl() 호출로 구현됩니다. dmsetup과 libdevmapper가 이 인터페이스를 사용합니다.
주요 ioctl 명령
| 명령 | 기능 | dmsetup 대응 |
|---|---|---|
DM_VERSION | DM 인터페이스 버전 확인 | dmsetup version |
DM_DEV_CREATE | 빈 mapped_device 생성 | dmsetup create (1단계) |
DM_TABLE_LOAD | inactive 테이블 로드 | dmsetup create (2단계) |
DM_DEV_SUSPEND | suspend 또는 resume | dmsetup suspend/resume |
DM_DEV_REMOVE | DM 디바이스 제거 | dmsetup remove |
DM_TABLE_STATUS | 타겟 상태 조회 | dmsetup status |
DM_TARGET_MSG | 타겟에 메시지 전달 | dmsetup message |
DM_LIST_DEVICES | 전체 DM 디바이스 목록 | dmsetup ls |
DM_TABLE_DEPS | 의존 디바이스 조회 | dmsetup deps |
DM_DEV_RENAME | DM 디바이스 이름 변경 | dmsetup rename |
디바이스 생성 시퀀스
libdevmapper API
libdevmapper는 DM ioctl을 C 라이브러리로 추상화합니다. LVM2, cryptsetup, multipath-tools 등이 이 라이브러리를 사용합니다.
/* libdevmapper를 사용한 DM 디바이스 생성 예제 */
#include <libdevmapper.h>
int create_linear_device(const char *name,
const char *underlying_dev,
uint64_t size_sectors)
{
struct dm_task *dmt;
char params[256];
/* 1. CREATE 태스크 */
dmt = dm_task_create(DM_DEVICE_CREATE);
dm_task_set_name(dmt, name);
/* 2. 매핑 테이블 추가 */
snprintf(params, sizeof(params), "%s 0", underlying_dev);
dm_task_add_target(dmt, 0, size_sectors, "linear", params);
/* 3. 실행 (ioctl 호출) */
dm_task_run(dmt);
dm_task_destroy(dmt);
return 0;
}
DM 버전 관리
DM ioctl 인터페이스는 version[3] 필드로 커널과 사용자 공간 간 호환성을 관리합니다. 주요 버전이 다르면 ioctl이 거부됩니다.
# DM 버전 확인
$ dmsetup version
Library version: 1.02.197 (2024-03-01)
Driver version: 4.48.0
# 커널 DM 타겟 버전 확인
$ dmsetup targets
thin-pool v1.24.0
thin v1.24.0
cache v2.3.0
crypt v1.24.0
linear v1.4.0
striped v1.6.0
verity v1.10.0
snapshot v1.16.0
snapshot-origin v1.9.0
snapshot-merge v1.5.0
era v1.0.0
clone v1.0.0
integrity v1.13.0
dmstats 성능 분석
dmstats는 DM 디바이스의 영역별 I/O 통계를 수집하고 분석하는 도구입니다. 히스토그램, 히트맵, 실시간(Real-time) 모니터링을 통해 성능 병목을 정밀하게 진단할 수 있습니다.
영역 기반 통계
dmstats는 DM 디바이스를 사용자 정의 영역으로 분할하고, 각 영역별로 독립적인 I/O 카운터를 유지합니다.
# 디바이스 전체를 1GB 영역으로 분할하여 통계 수집
$ dmstats create --segments vg0/data
vg0-data: Created new region with 1 area(s) as region ID 0
# 또는 커스텀 영역 크기
$ dmstats create --start 0 --length 1G --step 256M vg0/data
# 0~1GB 구간을 256MB 단위 4개 영역으로 분할
# 통계 출력
$ dmstats print --allregions vg0/data
vg0-data RgID RgSta RgSize Reads Rd Mrg Rd Sect Rd Ticks Writes ...
0 0 1.00g 42560 123 12345678 89012 31245 ...
1 1.00g 1.00g 15890 45 4567890 34567 28901 ...
# 실시간 모니터링 (1초 간격)
$ dmstats report --interval 1 --count 10 vg0/data
# iostat과 유사하지만 DM 디바이스 영역별로 세분화
히스토그램과 히트맵
# I/O 지연 히스토그램 설정
$ dmstats create --start 0 --length 100G --step 1G \
--histogram "100us,500us,1ms,5ms,10ms" vg0/data
# 히스토그램 출력
$ dmstats print --histogram vg0/data
Region Area 100us 500us 1ms 5ms 10ms >10ms
0 0 78.2% 15.1% 4.5% 1.8% 0.3% 0.1%
0 1 82.5% 12.3% 3.1% 1.5% 0.4% 0.2%
# 실시간 히트맵 생성 (파일 출력)
$ dmstats report --interval 1 -o name,region_id,reads,writes,read_sectors \
--separator ',' vg0/data > /tmp/dm_heatmap.csv
# perf와 결합한 상세 분석
$ perf trace -e 'block:block_bio_remap' -p $(pgrep -f dm_worker)
# DM bio 리매핑 이벤트 실시간 추적
# BPF 기반 DM 레이턴시 추적
$ bpftrace -e 'kprobe:dm_submit_bio { @start[tid] = nsecs; }
kretprobe:dm_submit_bio /@start[tid]/ {
@latency = hist(nsecs - @start[tid]);
delete(@start[tid]);
}'
dmstats의 영역 통계는 커널 내부 per-CPU 카운터를 사용하므로 오버헤드가 매우 낮습니다(~1% 미만). 프로덕션 환경에서도 안전하게 활성화할 수 있습니다. 히트맵 데이터를 기반으로 dm-cache 배치를 최적화하면 SSD 용량 대비 최대 효과를 얻을 수 있습니다.
LVM2와 DM 매핑 관계
LVM2는 사용자 공간 도구이며, 실제 블록 매핑은 모두 커널의 Device Mapper를 통해 수행됩니다. LVM의 PV/VG/LV 개념이 어떻게 DM 테이블로 변환되는지 상세히 분석합니다.
PV/VG/LV → DM 테이블 변환
LVM 메타데이터 포맷
LVM2의 메타데이터는 텍스트 기반 포맷으로 PV의 첫 번째 메가바이트 영역에 저장됩니다. 사람이 읽을 수 있으며, 이중 복사본으로 안전성을 보장합니다.
# LVM 메타데이터 직접 확인
$ pvck --dump metadata /dev/sda1 | head -40
my_vg {
id = "xxxx-xxxx-xxxx-xxxx"
seqno = 42
format = "lvm2"
status = ["RESIZEABLE", "READ", "WRITE"]
extent_size = 8192 # 4MB in sectors
physical_volumes {
pv0 {
id = "yyyy-yyyy-yyyy-yyyy"
device = "/dev/sda1"
pe_start = 2048
pe_count = 25599
}
}
logical_volumes {
data {
id = "zzzz-zzzz-zzzz-zzzz"
status = ["READ", "WRITE", "VISIBLE"]
segment_count = 2
segment1 {
start_extent = 0
extent_count = 1280 # 5GB (1280 * 4MB)
type = "striped"
stripe_count = 1
stripes = ["pv0", 0] # → dm-linear /dev/sda1 offset
}
segment2 {
start_extent = 1280
extent_count = 1280
type = "striped"
stripe_count = 1
stripes = ["pv1", 0] # → dm-linear /dev/sdb1 offset
}
}
}
}
# segment → DM 테이블 변환 규칙:
# segment.type="striped", stripe_count=1 → dm-linear
# segment.type="striped", stripe_count>1 → dm-striped
# segment.type="thin-pool" → dm thin-pool
# segment.type="cache-pool" → dm-cache
Segment에서 DM 테이블로
LVM의 각 LV segment는 DM 매핑 테이블의 한 줄에 대응합니다. dmsetup table과 lvs --segments를 비교하면 변환 관계를 명확히 볼 수 있습니다.
# LV의 segment 목록
$ lvs --segments -o +seg_pe_ranges,seg_type my_vg/data
LV Type #Str #Seg PE Ranges
data striped 1 1 /dev/sda1:0-1279
data striped 1 2 /dev/sdb1:0-1279
# 대응하는 DM 테이블
$ dmsetup table my_vg-data
0 10485760 linear 8:1 2048 # segment1: sda1, PE 0~1279
10485760 10485760 linear 8:17 2048 # segment2: sdb1, PE 0~1279
# PE 번호 → 섹터 변환:
# PE 0 = PV 시작(pe_start=2048) + 0 * extent_size(8192) = 섹터 2048
# segment 길이 = extent_count(1280) * extent_size(8192) = 10485760 섹터
# Thin Pool의 경우
$ dmsetup table my_vg-thin_pool
0 104857600 thin-pool 253:3 253:2 128 0
# ^meta ^data ^block_size
$ dmsetup table my_vg-thin_vol
0 209715200 thin 253:4 0
# ^pool ^thin_id
lvmpolld와 dmeventd
LVM2는 비동기 작업과 이벤트 모니터링을 위해 두 개의 데몬을 사용합니다.
| 데몬 | 역할 | 주요 기능 |
|---|---|---|
lvmpolld | 비동기 작업 폴링(Polling) | pvmove, snapshot merge, mirror 동기화 진행률 추적 |
dmeventd | DM 이벤트 감시 | thin-pool 자동 확장, 스냅샷 공간 부족 경고, mirror 장애 처리 |
# dmeventd 이벤트 모니터링 플러그인 확인
$ ls /usr/lib64/device-mapper/libdevmapper-event-lvm2*.so
libdevmapper-event-lvm2mirror.so
libdevmapper-event-lvm2snapshot.so
libdevmapper-event-lvm2thin.so
libdevmapper-event-lvm2raid.so
# thin-pool 자동 확장 설정 확인
$ grep -A2 'thin_pool_autoextend' /etc/lvm/lvm.conf
thin_pool_autoextend_threshold = 70
thin_pool_autoextend_percent = 20
# dmeventd가 thin-pool을 모니터링하는지 확인
$ lvs -o +lv_modules,monitored my_vg/thin_pool
LV Modules Monitored
thin_pool thin-pool,thin monitored
/etc/lvm/backup/에 자동 백업합니다. vgcfgbackup/vgcfgrestore로 수동 백업/복원이 가능하며, 메타데이터 손상 시 pvcreate --restorefile로 복구할 수 있습니다.
HW RAID 컨트롤러 커널 통합
엔터프라이즈 서버와 NAS 환경에서는 소프트웨어 RAID(MD/DM) 대신 전용 HW RAID 컨트롤러를 사용하여 호스트 CPU 부하를 줄이고, BBU(Battery Backup Unit)/FBWC(Flash-Backed Write Cache) 기반의 안전한 쓰기 캐시를 활용합니다. 이 섹션에서는 리눅스 커널의 HW RAID 컨트롤러 드라이버 아키텍처, BBU 모니터링, 성능 최적화 방법을 다룹니다.
HW RAID 컨트롤러 아키텍처
리눅스 커널은 주요 HW RAID 컨트롤러 벤더별로 전용 SCSI 드라이버를 제공합니다. 이 드라이버들은 SCSI mid-layer와 통합되어 HW RAID 컨트롤러가 관리하는 가상 디스크(Virtual Disk)를 일반 SCSI 블록 디바이스로 노출합니다.
| 드라이버 | 벤더 | 지원 RAID 레벨 | 최대 디스크 수 | BBU/FBWC 지원 |
|---|---|---|---|---|
megaraid_sas | Broadcom (LSI/Avago) | 0, 1, 5, 6, 10, 50, 60 | 240 | BBU, CacheVault (FBWC) |
smartpqi | Microchip (Adaptec) | 0, 1, 5, 6, 10, 50, 60, ADG | 255 | ZMCP (FBWC) |
hpsa / smartpqi | HPE Smart Array | 0, 1, 5, 6, 10, 50, 60, ADG | 128 | FBWC, Smart 캐시 |
mpt3sas | Broadcom (LSI SAS) | 0, 1, 10 (IR 모드) | 1024 (IT 모드) | 제한적 |
HW RAID와 리눅스 소프트웨어 RAID(MD/DM)의 핵심 차이를 비교하면 다음과 같습니다.
| 항목 | HW RAID (megaraid_sas 등) | SW RAID (MD/DM) |
|---|---|---|
| 패리티 연산 | 전용 XOR/PQ 엔진 (호스트 CPU 무부하) | 호스트 CPU 사용 |
| 쓰기 캐시 보호 | BBU/FBWC로 정전 시 데이터 보호 | 없음 (write-back 비권장) |
| 디스크 장애 감지 | 컨트롤러 펌웨어 자체 모니터링 | 커널 SCSI 에러 핸들링 |
| 핫스페어 | 펌웨어 레벨 자동 리빌드 | mdadm --add로 수동/자동 |
| 이식성 | 동일 컨트롤러 필요 | 아무 리눅스에서 인식 |
| 비용 | 컨트롤러 + BBU 추가 비용 | 무료 |
megaraid_sas 드라이버 구조
megaraid_sas는 Broadcom(구 LSI/Avago) MegaRAID SAS 컨트롤러의 리눅스 커널 드라이버입니다. MFI(MegaRAID Firmware Interface)에서 Fusion Architecture(Invader/Ventura/Aero)로 진화하며 고성능 I/O 경로를 제공합니다.
커널 소스 분석: struct megasas_instance 핵심 필드
/* drivers/scsi/megaraid/megaraid_sas.h */
struct megasas_instance {
struct pci_dev *pdev;
u32 unique_id; /* 컨트롤러 고유 식별자 */
u32 fw_support_ieee; /* IEEE SGL 지원 여부 */
u32 max_fw_cmds; /* 펌웨어 최대 동시 명령 수 */
u32 max_sectors_per_req; /* 요청당 최대 섹터 수 */
struct megasas_register_set __iomem *reg_set;
struct megasas_ctrl_info *ctrl_info_buf;
/* Fusion Architecture 관련 */
struct fusion_context *ctrl_context;
u32 adapter_type; /* MFI / Thunderbolt / Fusion / Ventura / Aero */
/* I/O 경로 */
struct megasas_cmd **cmd_list;
struct list_head cmd_pool;
spinlock_t mfi_pool_lock;
/* SCSI Host 연동 */
struct Scsi_Host *host;
u8 max_scsi_cmds;
/* BBU/캐시 상태 */
u32 is_imr; /* iMR (integrated MegaRAID) 여부 */
u8 disableOnlineCtrlReset;
atomic_t fw_outstanding; /* 펌웨어 미완료 명령 카운터 */
};
코드 설명
-
핵심
megasas_instance는 각 MegaRAID 컨트롤러의 런타임 상태를 관리합니다.adapter_type필드가 MFI(레거시)인지 Fusion(최신)인지에 따라 I/O 경로가 달라집니다. Fusion Architecture에서는ctrl_context(fusion_context)가 MPI(Message Passing Interface) 프레임 기반의 고속 I/O를 담당합니다. -
핵심
fw_outstanding원자 카운터는 현재 펌웨어에 제출되었지만 아직 완료되지 않은 명령 수를 추적하며,max_fw_cmds를 초과하면 새 명령 제출을 대기시킵니다.
I/O 요청의 처리 경로는 다음과 같습니다.
- SCSI mid-layer가
scsi_cmnd를megaraid_sas_queuecommand()에 전달 - 드라이버가 SCSI CDB를 MPI 프레임(RAID 또는 SCSI I/O Request)으로 변환
- MPI 프레임을 컨트롤러의 Request Descriptor 큐에 기록 (PCIe MMIO)
- 컨트롤러 펌웨어가 XOR/PQ 엔진으로 패리티 연산, 캐시 관리 수행
- 완료 시 MSI-X 인터럽트로 드라이버에 통보,
megasas_complete_cmd()호출
megaraid_sas는 RAID 가상 디스크 외에 개별 물리 디스크를 JBOD(Just a Bunch Of Disks)로 직접 노출하는 패스스루 모드를 지원합니다. 이 경우 물리 디스크가 /dev/sdX로 직접 나타나며, Linux MD/DM으로 소프트웨어 RAID를 구성할 수 있습니다. storcli /cN set jbod=on으로 활성화합니다.
BBU/FBWC 모니터링
HW RAID 컨트롤러의 쓰기 캐시는 BBU(Battery Backup Unit) 또는 FBWC(Flash-Backed Write Cache, 슈퍼캐패시터 기반)로 보호됩니다. 정전 시 캐시 데이터를 보존하여 데이터 무결성을 보장하는 핵심 구성요소입니다.
| 항목 | BBU (배터리) | FBWC (슈퍼캐패시터) |
|---|---|---|
| 보호 방식 | 배터리 전원으로 DRAM 유지 (48~72시간) | 슈퍼캐패시터로 NAND에 플러시 (수초) |
| 수명 | 2~3년 (교체 필요) | 5~7년 (장수명) |
| 학습 주기 | 90일마다 충방전 학습 필요 | 불필요 |
| 온도 민감도 | 높음 (고온 시 수명 단축) | 낮음 |
storcli(또는 레거시 megacli)를 사용하여 BBU 상태를 모니터링할 수 있습니다.
# BBU 상태 전체 조회
$ storcli /c0/bbu show all
Controller = 0
Status = Success
BBU_Info :
========
---------------------------------------------------------------
Property Value
---------------------------------------------------------------
Type BBU
Voltage 3845 mV
Current 0 mA
Temperature 25 C
Battery State Optimal
Design Capacity 1400 mAh
Full Charge Capacity 1317 mAh
Remaining Capacity 1280 mAh
Relative State of Charge 97%
Charging Status None
Learn Cycle Active No
Next Learn time 2026-04-15 03:00:00
# 캐시 정책 확인 (Write-back / Write-through 상태)
$ storcli /c0/v0 show all | grep -E 'Cache|WB|WT'
Current Cache Policy = WriteBack, ReadAhead, Direct, No Write Cache if Bad BBU
Default Cache Policy = WriteBack, ReadAhead, Direct, No Write Cache if Bad BBU
# BBU 학습 주기 강제 시작 (유지보수 시간에 실행)
$ storcli /c0/bbu start learn
# sysfs를 통한 SCSI 호스트 정보 확인
$ cat /sys/class/scsi_host/host0/board_name
MegaRAID SAS 9361-8i
$ cat /sys/class/scsi_host/host0/fw_version
4.680.00-8548
"No Write Cache if Bad BBU" 정책이 기본 설정입니다. 프로덕션 환경에서는 학습 주기를 업무 외 시간으로 예약하세요.
sysfs를 통해 커널 레벨에서 접근 가능한 주요 속성은 다음과 같습니다.
| sysfs 경로 | 설명 |
|---|---|
/sys/class/scsi_host/hostN/board_name | 컨트롤러 모델명 |
/sys/class/scsi_host/hostN/fw_version | 펌웨어 버전 |
/sys/class/scsi_host/hostN/cmd_per_lun | LUN당 최대 동시 명령 수 |
/sys/class/scsi_host/hostN/can_queue | 호스트 어댑터 최대 큐 깊이 |
/sys/block/sdX/queue/write_cache | 가상 디스크 쓰기 캐시 상태 |
HW RAID 성능 최적화
HW RAID 컨트롤러의 성능은 캐시 정책, Read-ahead 설정, Stripe 크기에 따라 크게 달라집니다. 워크로드에 맞는 최적 설정을 선택해야 합니다.
| 설정 항목 | 옵션 | 권장 용도 |
|---|---|---|
| Read-ahead 정책 | Normal | 순차 읽기 워크로드 (영상 스트리밍, 백업) |
Adaptive | 혼합 워크로드 (컨트롤러 자동 판단) | |
Disabled | 랜덤 I/O 위주 (데이터베이스) | |
| Stripe 크기 | 64K | 데이터베이스, 랜덤 I/O (작은 블록 다수) |
256K | 범용 파일 서버, 혼합 워크로드 | |
1M | 대용량 순차 I/O (영상 편집, 백업) | |
| Cache 정책 | Write-through | BBU 없는 환경 (안전 우선) |
Write-back | BBU 정상 시 기본 (성능 + 안전) | |
Force Write-back | BBU 상태 무시 강제 WB (위험) | |
| I/O 정책 | Direct I/O | 대용량 순차 I/O (캐시 우회) |
Cached I/O | 반복 읽기 패턴, 작은 파일 다수 |
# 가상 디스크 캐시 정책을 Write-back + Adaptive Read-ahead로 설정
$ storcli /c0/v0 set wrcache=wb rdcache=ra iopolicy=direct
# Stripe 크기 확인 (VD 생성 후 변경 불가 — 생성 시 지정)
$ storcli /c0/v0 show all | grep "Strip Size"
Strip Size = 256 KB
# 성능 모니터링: I/O 통계 확인
$ storcli /c0 show perfmode
$ iostat -x /dev/sda 1 5
"No Write Cache if Bad BBU"(기본 정책)가 설정된 상태에서 BBU가 방전되거나 학습 주기에 진입하면, 컨트롤러가 자동으로 Write-through 모드로 전환됩니다. 이 경우 쓰기 성능이 5~10배 이상 저하될 수 있습니다. FBWC(CacheVault) 기반 컨트롤러는 학습 주기가 불필요하여 이 문제가 발생하지 않으므로, 안정적인 성능이 필요한 환경에서는 FBWC 컨트롤러를 권장합니다.
NAS 환경에서의 HW RAID
NAS(Network Attached Storage) 환경에서 HW RAID를 도입할 때는 컨트롤러 선택, RAID 레벨, 암호화 조합을 종합적으로 고려해야 합니다.
HW RAID 컨트롤러 선택 기준:
- 캐시 크기: 2GB 이상 DDR4 캐시 권장 (대규모 NAS에서 write coalescing 효과)
- BBU vs FBWC: FBWC(CacheVault/ZMCP) 모델 선호 — 학습 주기 없음, 장수명, 고온 내구성
- SAS 포트 수: 디스크 수에 맞는 포트 확보 (8i, 16i, 24i 등)
- 컨트롤러 CPU: RAID 5/6 패리티 연산 성능에 직접 영향 — 듀얼코어 이상 권장
RAID 레벨 워크로드별 선택:
| 워크로드 | 권장 RAID | 이유 |
|---|---|---|
| 파일 서버 (SMB/NFS) | RAID 5 / RAID 6 | 용량 효율, 순차 I/O 위주 |
| 가상화 (iSCSI LUN) | RAID 10 | 랜덤 I/O 성능 우선 |
| 감시 카메라 (영상 저장) | RAID 5 | 순차 쓰기, 용량 극대화 |
| 데이터베이스 (MySQL/PostgreSQL) | RAID 10 | 랜덤 R/W, 저지연 |
| 백업/아카이브 | RAID 6 | 대용량, 이중 패리티 안전 |
/dev/sdX) 위에 dm-crypt로 암호화 계층을 추가할 수 있습니다. 이 경우 RAID 패리티 연산은 HW가 처리하고, 암호화만 호스트 CPU가 담당합니다. AES-NI 지원 CPU에서는 암호화 오버헤드가 5% 미만입니다. 단, 컨트롤러 자체 SED(Self-Encrypting Drive) 기능과 병행하면 이중 암호화로 불필요한 오버헤드가 발생하므로 주의하세요. 자세한 내용은 NAS HW 오프로드를 참고하세요.
참고 링크
- Kernel.org — Device Mapper Documentation — 리눅스 커널 공식 Device Mapper 관리자 가이드입니다
- Bootlin Elixir — drivers/md/dm.c — Device Mapper 코어의 핵심 소스 코드입니다
- Bootlin Elixir — drivers/md/dm-crypt.c — dm-crypt 암호화 타겟의 구현 코드입니다
- Bootlin Elixir — drivers/md/dm-thin.c — 씬 프로비저닝(thin provisioning) 타겟의 구현 코드입니다
- LWN: Device mapper multipath — dm-multipath의 동작 원리와 경로 선택 알고리즘을 설명하는 기사입니다
- LWN: dm-integrity and dm-verity — 무결성 검증 타겟의 설계와 활용 사례를 분석합니다
- LVM2 프로젝트 (sourceware.org) — LVM2 및 device-mapper 유저스페이스 도구의 공식 프로젝트 페이지입니다
- cryptsetup (GitLab) — LUKS/dm-crypt 관리 도구
cryptsetup의 공식 저장소입니다 - Bootlin Elixir — dm-verity-target.c — dm-verity 타겟의 구현 코드로, Android Verified Boot에서 사용됩니다
- man7.org — dmsetup(8) — Device Mapper 저수준 관리 도구
dmsetup의 매뉴얼 페이지입니다
관련 문서
Device Mapper/LVM과 관련된 다른 주제를 더 깊이 이해하고 싶다면 다음 문서를 참고하세요.