Device Mapper / LVM 심화
Linux 커널의 Device Mapper(DM) 프레임워크를 블록 디바이스 가상화와 데이터 서비스 계층 관점에서 심층 분석합니다. 핵심 구조체(mapped_device, dm_table, dm_target, target_type)와 I/O remap 경로, dm-linear/striped/crypt/thin/cache/verity/snapshot 타겟별 내부 동작과 메타데이터 특성, LVM2 및 dmsetup 사용자 공간 도구와 커널 경계, md RAID와의 책임 분리, 장애 복구 시나리오와 일관성 보장 포인트, tracepoint·dmstats 기반 성능 관측, 실제 운영에서 자주 발생하는 병목과 튜닝 절차까지 실무 중심으로 종합적으로 다룹니다.
struct bio, blk-mq, I/O 스케줄러 개념을 이해하고 있다고 가정합니다. 커널 모듈 개발 경험이 있으면 코드 예제를 이해하는 데 도움이 됩니다.
핵심 요약
- Device Mapper — 물리 블록 디바이스 위에 가상 블록 디바이스를 생성하는 프레임워크입니다.
- LVM2 — DM 위에 구현된 논리 볼륨 관리자. 디스크 크기 조정, 스냅샷 등을 제공합니다.
- dm-crypt — 블록 레벨 투명 암호화. LUKS가 이를 활용합니다.
- dm-thin — Thin provisioning. 실제 사용량보다 큰 볼륨을 생성(오버커밋)합니다.
- dm-verity — 블록 무결성 검증. 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 디바이스를 쌓아 복잡한 스토리지 토폴로지 구성
- 런타임 재구성: 매핑 테이블을 원자적으로 교체하여 온라인 상태에서 스토리지 레이아웃 변경
- 투명한 bio 리매핑: 상위 파일시스템은 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으로, 가상 디바이스의 시작 섹터를 기준으로 한 상대 오프셋을 계산합니다. 이에 하위 디바이스의 시작 오프셋(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) 단위로 여러 하위 디바이스에 라운드 로빈 분산하여 대역폭을 극대화합니다.
스트라이프 레이아웃
/* 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 데이터를 암호화하고, 읽기 시 복호화합니다. 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 제출 → 완료 콜백에서
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 | 높음 (워터마크 공격 방지) |
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: 논리 볼륨 합계가 물리 풀보다 커도 됨. 실제 쓰기 시점까지 할당 지연
# 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를 캐시 계층으로 배치하여 성능을 향상시킵니다. 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는 블록 디바이스의 무결성을 읽기 시점에 검증합니다. 데이터 블록마다 해시를 사전에 계산하여 해시 트리(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 파티션의 런타임 무결성을 보장한다. 부트로더가 VBMeta 파티션의 RSA 서명을 검증하고, 각 파티션의 해시 트리 루트 해시를 커널에 전달한다.
AVB의 VBMeta 구조, 롤백 보호, 부트 흐름 등 심화 내용은 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 타겟
디바이스 트리 시각화
# 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 볼륨 생성 전체 과정
빈 디스크에서 시작하여 마운트 가능한 논리 볼륨을 만드는 완전한 절차입니다:
# 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() 시스템 콜로 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 타겟과 구성별 성능 특성을 벤치마크 데이터로 비교합니다.
처리량 비교 (순차 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 | 단편화, 메타 탐색 증가 |
| 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이 권장됩니다.
성능 튜닝과 디버깅
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인지 확인합니다. 마운트, 스왑, 다른 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 |
마운트 상태, 프로세스가 사용 중 | 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 메모리 계층화 |
관련 문서
Device Mapper/LVM과 관련된 다른 주제를 더 깊이 이해하고 싶다면 다음 문서를 참고하세요.