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 기반 성능 관측, 실제 운영에서 자주 발생하는 병목과 튜닝 절차까지 실무 중심으로 종합적으로 다룹니다.

관련 표준: LVM2 Specification, Device Mapper Interface — 블록 디바이스 매핑 계층의 인터페이스 규격입니다. 종합 목록은 참고자료 — 표준 & 규격 섹션을 참고하세요.
선행 지식: 이 문서는 Block I/O 서브시스템struct bio, blk-mq, I/O 스케줄러 개념을 이해하고 있다고 가정합니다. 커널 모듈 개발 경험이 있으면 코드 예제를 이해하는 데 도움이 됩니다.
전제 조건: Block I/O 서브시스템VFS 문서를 먼저 읽으세요. 스토리지 경로는 큐잉, 병합, 플러시 정책이 연쇄적으로 동작하므로, 요청 수명주기와 완료 경로를 먼저 추적해야 합니다.

핵심 요약

  • Device Mapper — 물리 블록 디바이스 위에 가상 블록 디바이스를 생성하는 프레임워크입니다.
  • LVM2 — DM 위에 구현된 논리 볼륨 관리자. 디스크 크기 조정, 스냅샷 등을 제공합니다.
  • dm-crypt — 블록 레벨 투명 암호화. LUKS가 이를 활용합니다.
  • dm-thin — Thin provisioning. 실제 사용량보다 큰 볼륨을 생성(오버커밋)합니다.
  • dm-verity — 블록 무결성 검증. Android의 파티션 검증에 사용됩니다.

단계별 이해

  1. DM 확인dmsetup ls로 현재 시스템의 DM 디바이스 목록을 확인합니다.

    lsblk에서 TYPElvm, crypt인 것이 DM 디바이스입니다.

  2. LVM 기초 — PV(Physical Volume) → VG(Volume Group) → LV(Logical Volume) 계층으로 디스크를 관리합니다.

    pvs, vgs, lvs 명령어로 각 계층을 확인합니다.

  3. 매핑 테이블dmsetup table로 DM 디바이스의 매핑 규칙을 볼 수 있습니다.

    각 줄은 "시작 섹터, 길이, 타겟 타입, 타겟 인자"로 구성됩니다.

  4. 실전 활용 — LVM으로 볼륨 확장(lvextend), 스냅샷 생성, LUKS로 디스크 암호화 등을 수행합니다.

    이 모든 기능이 Device Mapper 프레임워크 위에서 동작합니다.

Device Mapper 개요

Device Mapper(DM)는 Linux 커널의 블록 디바이스 가상화 프레임워크입니다. 하나 이상의 물리 블록 디바이스 위에 가상 블록 디바이스를 생성하고, I/O 요청을 변환(매핑)하여 하위 디바이스에 전달합니다. LVM2, LUKS 암호화, dm-verity 무결성 검증, thin provisioning 등 현대 Linux 스토리지 스택의 핵심 기능이 모두 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) 등이 추가되었습니다.

Filesystem / Application Device Mapper Layer mapped_device (/dev/dm-0) dm_table (매핑 테이블) dm_target[0]: linear dm_target[1]: crypt dm_target[2]: linear /dev/sda1 /dev/sda2 /dev/sdb1 DM은 가상 디바이스의 섹터 범위를 물리 디바이스에 매핑합니다

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 힌트 (정렬, 제한 등) */
    /* ... */
};
map() 반환값: 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 경로

성능 참고: dm-crypt는 쓰기마다 bounce buffer를 할당하고 memcpy를 수행합니다. AES-NI/ARMv8 Crypto Extension 같은 하드웨어 가속이 없으면 CPU 오버헤드가 상당합니다. 최신 커널(5.4+)에서는 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비트로 사용표준
essivEncrypted Salt-Sector IV높음 (워터마크 공격 방지)
benbiBig-Endian Narrow Block IV특수 용도
random랜덤 IV (쓰기 전용)높음 (읽기 불가)

dm-thin (Thin Provisioning)

dm-thin은 씬 프로비저닝과 스냅샷을 제공합니다. 실제 물리 공간을 사전에 할당하지 않고, 쓰기 시점에 동적으로 블록을 할당합니다. Copy-on-Write(CoW) 기반 스냅샷도 지원합니다.

핵심 개념

# 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사용/전체
overprovisioning 위험: thin-pool의 물리 공간이 고갈되면 I/O 에러가 발생하고, 파일시스템이 read-only로 전환될 수 있습니다. 프로덕션 환경에서는 반드시 모니터링과 자동 확장 정책을 구성하세요. LVM2의 thin_pool_autoextend_threshold 옵션을 활용할 수 있습니다.

dm-cache / dm-writecache

dm-cache는 느린 HDD 앞에 빠른 SSD를 캐시 계층으로 배치하여 성능을 향상시킵니다. dm-writecache는 쓰기 전용 캐싱에 특화된 타겟입니다.

dm-cache 구조

캐시 모드

모드읽기쓰기특징
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 ..."
dm-verity + FEC: Linux 4.6부터 Forward Error Correction(FEC)을 지원하여, 소수의 손상된 블록을 자동 복구할 수 있습니다. Android에서 OTA 업데이트 후 일부 블록 손상이 발생해도 부팅이 가능하도록 사용됩니다.

Android dm-verity 심화 — AVB 연동

Android Verified Boot(AVB)는 dm-verity를 핵심으로 사용하여 system, vendor 파티션의 런타임 무결성을 보장한다. 부트로더가 VBMeta 파티션의 RSA 서명을 검증하고, 각 파티션의 해시 트리 루트 해시를 커널에 전달한다.

AVB VBMeta → dm-verity 검증 체인 부트로더 (ABL) 내장 공개키로 신뢰 앵커(RoT) 확인 RSA-4096 서명 검증 vbmeta 파티션 RSA-4096 서명 + hash/hashtree descriptor 목록 boot hash descriptor GKI 커널 이미지 SHA-256 vendor_boot hash descriptor 벤더 ramdisk / DTB system / vendor hashtree descriptor dm-verity 해시 트리 루트 dm-verity (커널 런타임) 블록 읽기 시마다 SHA-256 검증 + FEC(Forward Error Correction) 오류 복구 androidboot.veritymode=enforcing system_a (활성) EROFS / ext4 블록 현재 부팅 슬롯 system_b (비활성) OTA 업데이트 대상 쓰기 후 슬롯 전환 검증 성공 → 정상 부팅 FEC → 소규모 손상 복구 복구 불가 → I/O 에러, 슬롯 폴백

AVB의 VBMeta 구조, 롤백 보호, 부트 흐름 등 심화 내용은 Android 커널 — AVBSecure 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
#                    ^사용/전체(청크)  ^메타데이터(청크)
COW 공간 고갈: COW 디바이스가 가득 차면 스냅샷이 무효화(invalid)됩니다. 스냅샷은 임시 용도(백업, 테스트)로 사용하고, 장기 보존에는 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를 할당받아 구성
 */
PV: /dev/sda1 PV: /dev/sdb1 PV: /dev/sdc1 VG: my_vg (300GB pool) LV: root (50GB) LV: home (100GB) LV: data (150GB) /dev/mapper/my_vg-root /dev/mapper/my_vg-home /dev/mapper/my_vg-data

주요 명령어

# ===== 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 02100%없음스트라이핑, 최대 성능
RAID 1250%1디스크미러링, 읽기 성능 향상
RAID 53(N-1)/N1디스크분산 패리티, 범용
RAID 64(N-2)/N2디스크이중 패리티, 대용량 스토리지
RAID 10450%미러당 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: LVM2는 MD의 RAID 기능을 Device Mapper 타겟(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가 제출되면 다음 경로를 거칩니다:

  1. dm_submit_bio(): DM의 submit_bio 콜백. RCU로 현재 dm_table을 참조
  2. __split_and_process_bio(): bio가 여러 타겟에 걸치면 분할
  3. __map_bio(): 각 타겟의 map() 함수 호출
  4. 타겟이 DM_MAPIO_REMAPPED 반환 → submit_bio_noacct()로 하위 디바이스에 재제출
  5. 완료 시 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 디바이스 위에 쌓을 수 있습니다. 실제 프로덕션에서 흔히 볼 수 있는 스택:

Device Mapper 스택 아키텍처 Application → VFS → DM 레이어 스택 → 물리 블록 디바이스 애플리케이션 / VFS 파일시스템 read()/write() — /dev/mapper/vg-root 마운트 dm-linear — /dev/mapper/vg-root LVM LV: PE 블록 → 물리 주소 매핑 (오버헤드 극소) dm-crypt — /dev/mapper/cryptroot LUKS 암호화: AES-XTS-256 + AES-NI 하드웨어 가속 dm-cache — /dev/mapper/vg-crypt_data SSD 캐시 레이어: 핫 블록 → SSD / 콜드 블록 → HDD (80% 적중률 시 1200MB/s↑) md RAID5 — /dev/md0 소프트웨어 RAID: 패리티 분산 / 오류 복구 / 스트라이핑 sda / sda1 8:0 disk 1TB RAID5 멤버 디스크 sdb / sdb1 8:16 disk 1TB RAID5 멤버 디스크 sdc / sdc1 8:32 disk 1TB RAID5 멤버 디스크 ※ lsblk로 스택 확인: sda1 → md0 → vg-data (253:0) → cryptdata (253:1) dm-crypt / dm-cache / dm-linear은 /dev/mapper/* 로 노출 (253:x major 번호)

성능 비교

다양한 DM 타겟과 구성별 성능 특성을 벤치마크 데이터로 비교합니다.

처리량 비교 (순차 I/O)

1GB 파일 순차 읽기/쓰기 벤치마크 (fio, direct I/O, 4K 블록 크기):

구성순차 읽기 (MB/s)순차 쓰기 (MB/s)랜덤 읽기 (IOPS)랜덤 쓰기 (IOPS)
Raw 디바이스 (baseline)55052095K85K
dm-linear (단일 디스크)54851894K84K
dm-striped (2 디스크)1050980180K160K
dm-striped (4 디스크)21001920350K310K
dm-crypt (AES-XTS 256)48542082K68K
dm-crypt + AES-NI54051092K81K
dm-cache (80% 적중률)12001050220K180K
dm-thin (오버헤드)53048088K75K
💡

측정 환경: 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)-5500%5%
AES-XTS 128없음380-31%85%
AES-XTS 256없음350-36%90%
AES-XTS 128AES-NI530-4%12%
AES-XTS 256AES-NI510-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)5500.18-
Thin 볼륨 (블록 미할당)4800.24첫 쓰기 시 블록 할당
Thin 볼륨 (블록 할당 완료)5400.19메타데이터 업데이트만
Thin 스냅샷 (COW 발생)4200.32원본 블록 복사 + 메타 갱신
Thin Pool 70% 사용 시5100.21단편화, 메타 탐색 증가
Thin Pool 95% 사용 시3800.45여유 블록 부족, 재할당 빈번
⚠️

Thin Pool 용량 관리: 사용률이 80%를 넘으면 성능이 저하되기 시작합니다. thin_pool_autoextend_threshold=70로 설정하여 자동 확장을 활성화하고, dmeventd 데몬을 실행하여 모니터링하세요.

dm-cache 캐시 적중률 영향

캐시 적중률에 따른 성능 변화 (HDD + SSD 캐시 구성):

캐시 적중률읽기 처리량 (MB/s)읽기 지연 (ms)쓰기 처리량 (MB/s)설명
0% (캐시 미사용)1208.5110HDD 직접 액세스
20%2406.2180일부 핫 데이터 캐싱
50%4803.1350워킹셋의 절반 캐싱
80%8201.2680대부분 SSD에서 처리
95%10500.6920거의 모든 액세스 캐싱
100% (SSD만)12000.41100SSD 네이티브 성능
# 캐시 적중률 실시간 모니터링
$ 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 04220021004TB (100%)최고 성능, 내결함성 없음
RAID 125505201TB (50%)미러링, 읽기 성능 향상
RAID 5416504203TB (75%)용량 효율적, 쓰기 느림
RAID 6414503802TB (50%)2개 디스크 장애 허용
RAID 104110010502TB (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/schedulerDM 디바이스의 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 sizethin-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

자주 발생하는 문제

I/O 행(hang) 진단: DM 디바이스에서 I/O가 멈추면 다음을 확인하세요:
  • dmsetup info에서 Suspended 상태 확인 — 의도치 않은 suspend가 원인일 수 있음
  • cat /proc/mdstat으로 MD 리빌드 진행 중인지 확인
  • dm-thin의 경우 풀 공간 고갈 여부 (dmsetup status)
  • dm-crypt 워크큐 병목 (/proc/pressure/io 확인)
  • iostat -x 1로 하위 디바이스의 %util, await 모니터링
DM 디바이스 제거 실패: 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-cachedm-writecachedm-pcache (v6.18+)
캐시 미디어SSDNVDIMM/SSDNVDIMM/CXL 메모리
캐시 대상읽기+쓰기쓰기 전용읽기+쓰기
접근 방식블록 I/ODAX (바이트 주소)DAX (바이트 주소)
전원 손실 안전저널/메타데이터영구 메모리 특성 활용영구 메모리 + 메타데이터 보호
적합한 환경SSD 계층화쓰기 가속CXL 메모리 계층화
CXL 메모리 활용: CXL(Compute Express Link) 메모리 확장 장치가 보급됨에 따라, DRAM보다 저렴하지만 빠른 CXL 메모리를 dm-pcache로 스토리지 캐시에 활용하는 구성이 가능해집니다. DRAM → CXL 메모리 → SSD → HDD의 다계층 스토리지 구성을 지원합니다.

Device Mapper/LVM과 관련된 다른 주제를 더 깊이 이해하고 싶다면 다음 문서를 참고하세요.