새벽 3시, PagerDuty 알림이 울립니다. /var/lib/mysql 파티션 사용률이 93%입니다. df -h로 확인하니 20GB 파티션이 꽉 찼습니다. 전통 파티션 방식이라면 MySQL을 멈추고, 데이터를 임시 디스크에 옮기고, 파티션을 지웠다 새로 만들고, 다시 복원해야 합니다. 예상 다운타임 2~3시간, 새벽에 서비스 중단 공지를 써야 하는 상황.
같은 서버가 LVM으로 구성되어 있었다면 lvextend -L +20G -r /dev/vg_data/lv_mysql 한 줄, 3분 안에 끝납니다.
LVM & 볼륨 관리
- 1전통 파티션의 한계와 LVM이 온라인 볼륨 확장으로 해결하는 문제를 설명할 수 있다
- 2PV·VG·LV 3계층 구조를 이해하고 각 계층의 명령어를 구분할 수 있다
- 3pvcreate → vgcreate → lvcreate → mount → fstab 전체 흐름을 실습할 수 있다
- 4운영 중단 없이 lvextend와 resize2fs/xfs_growfs로 볼륨을 확장할 수 있다
- 5LVM 스냅샷으로 일관성 있는 백업 포인트를 생성하고 롤백할 수 있다
1. 기존 파티션의 한계와 LVM의 등장
dnf install -y lvm2 || apt-get install -y lvm2lsblk -f && pvs && vgs && lvsdnf install -y mdadm || apt-get install -y mdadm왜 LVM이 필요한가?
확대
새벽 3시, 데이터베이스 서버에서 PagerDuty 알림이 울렸습니다. /var/lib/mysql 파티션 사용률이 93%였고, df -h로 확인해보니 20GB짜리 파티션 하나가 꽉 차 있었습니다. 파티션을 늘리려면 MySQL을 멈추고 데이터를 임시 디스크에 옮겨두고, 파티션을 지웠다 새로 만든 뒤 데이터를 다시 복원해야 했습니다. 예상 다운타임은 2~3시간이었고, 새벽에 서비스 중단 공지를 써야 하는 상황이었습니다. 같은 서버가 LVM으로 구성되어 있었다면 lvextend -L +20G 한 줄로 온라인 확장이 가능했을 겁니다.
전통적인 디스크 파티션 방식은 1980년대 설계된 개념으로, 현대 클라우드 인프라의 유연한 스토리지 요구를 충족하기 어렵습니다.
전통 파티션 방식의 한계:
- 크기 변경 불가: 파티션을 생성하면 크기가 고정됩니다. 확장하려면 서비스를 중단해야 합니다.
- 단일 디스크 종속: 하나의 파티션은 하나의 물리 디스크에만 존재합니다. 디스크 용량 한계가 곧 파티션 한계입니다.
- 낭비 발생:
/home에 공간이 남아도/var가 꽉 차면 이동이 불가합니다. - 사전 계획 필수: 초기 설치 시 정확한 용량 예측이 필요합니다. 예측이 틀리면 대공사가 필요합니다.
LVM이 제공하는 해결책:
- 운영 중인 서비스를 중단하지 않고 볼륨 크기를 늘릴 수 있습니다.
- 여러 물리 디스크를 하나의 스토리지 풀로 통합합니다.
- 스냅샷 기능으로 특정 시점의 상태를 순간 포착합니다.
- 새 디스크를 기존 볼륨 그룹에 추가해 동적으로 용량을 확장합니다.
LVM이 실제로 쓰이는 환경 — 현장에서 LVM이 필요한 실제 시나리오들입니다:
데이터베이스 서버 → 데이터 증가에 따라 주기적으로 스토리지 확장 필요
로그 서버 → 예측 불가능한 로그 증가량
개발/스테이징 환경 → 다양한 크기의 볼륨을 유연하게 할당
백업 서버 → LVM 스냅샷으로 일관성 있는 백업 생성
실무 포인트: 대부분의 Linux 배포판은 기본 설치 시 LVM을 사용합니다.
df -h로 마운트 포인트를 확인하면/dev/mapper/...형태의 경로가 보이는데, 이것이 LVM 논리 볼륨입니다.
2. LVM 3계층 구조 이해
PV → VG → LV: LVM의 3단계 추상화
LVM 서버에서 /var/log가 꽉 차 로그 수집이 멈췄습니다. vgs로 확인하니 VG에 여유 공간이 충분했는데, 막상 어떤 명령어를 써야 하는지 감이 안 왔습니다. 검색하면 pvcreate, vgextend, lvextend가 동시에 나오는데 무엇이 먼저인지, PV·VG·LV 중 어디에 명령을 내려야 하는지 헷갈렸습니다. 이 세 계층의 역할을 한 번 정확히 구분해두면, 이후 LVM 명령어 전체가 자연스럽게 읽힙니다.
LVM은 물리 스토리지를 3단계로 추상화합니다. 이 계층 구조를 이해하면 LVM의 모든 명령어가 자연스럽게 이해됩니다.
처음 보면 PV, VG, LV라는 세 가지 약자가 낯설게 느껴집니다. 레고 블록에 비유하면 직관적으로 이해됩니다.
- PV(Physical Volume)는 레고 블록 한 조각입니다. 실제 물리 디스크나 파티션을 LVM이 인식할 수 있도록 등록하는 단계입니다.
pvcreate /dev/sdb1을 실행하기 전까지는 LVM 입장에서 그 디스크는 "존재하지 않는 것"과 같습니다. - VG(Volume Group)는 블록들을 담는 바구니입니다. 여러 PV를 하나의 스토리지 풀로 묶어줍니다. 500GB 디스크와 1TB 디스크를 묶어 1.5TB짜리 풀을 만드는 것이 VG 생성입니다. 이후 모든 용량 계산은 이 풀 기준으로 이루어집니다.
- LV(Logical Volume)는 바구니에서 꺼내 실제로 쓰는 조각입니다. VG 풀에서 원하는 크기만큼 떼어내 파일시스템을 올리는 공간입니다. MySQL 데이터용으로 15GB, 로그용으로 10GB처럼 필요에 따라 동적으로 생성하고 나중에 늘릴 수 있습니다.
이 3계층 구조 덕분에 LVM은 물리 경계를 넘어 볼륨을 자유롭게 늘리고 줄일 수 있습니다.
확대
확대
각 계층의 역할 — PV → VG → LV 세 계층이 어떤 역할을 하는지 핵심 명령어와 함께 정리했습니다:
| 계층 | 이름 | 역할 | 핵심 명령어 |
|---|---|---|---|
| 1단계 | Physical Volume (PV) | 물리 디스크/파티션을 LVM에 등록 | pvcreate |
| 2단계 | Volume Group (VG) | PV들을 하나의 스토리지 풀로 통합 | vgcreate, vgextend |
| 3단계 | Logical Volume (LV) | VG에서 원하는 크기만큼 논리 볼륨 할당 | lvcreate, lvextend |
PE(Physical Extent)란?
LVM은 디스크를 **PE(Physical Extent)**라는 고정 크기 블록으로 나눕니다.
# 실습 디렉토리 준비
mkdir -p /tmp/linux/part3/exam_15 && cd /tmp/linux/part3/exam_15
# PE 크기 확인
vgdisplay vg_data | grep "PE Size"
# PE Size 4.00 MiB
3. LVM 전체 구성 실습
빈 디스크가 마운트된 논리 볼륨이 되기까지 — pvcreate부터 온라인 확장까지 6단계
앞에서 PV·VG·LV가 "무엇"인지 봤다면, 이제 그것들이 실제로 "어떤 순서로" 쌓여 하나의 쓸 수 있는 볼륨이 되는지를 한 흐름으로 봅니다. 이 6단계를 머릿속에 그려 두면, pvcreate·vgextend·lvextend 중 무엇을 언제 쓰는지, 그리고 "확장했는데 df에 안 늘어난다" 같은 문제가 어느 계층에서 끊긴 것인지를 단계로 좁힐 수 있습니다. 아래 실습은 정확히 이 순서를 그대로 따라갑니다.
[빈 디스크] /dev/sdb (커널은 인식하지만 LVM은 아직 모름)
│
① pvcreate /dev/sdb → PV로 초기화 (LVM 라벨·메타데이터 기록)
│
② vgcreate vg_data /dev/sdb → VG(스토리지 풀)로 묶고, 전체를 PE(익스텐트)로 분할
│ → 이 시점부터 용량은 "물리 디스크"가 아니라 "VG의 Free PE"로 관리됨
│
③ lvcreate -L 15G -n lv_app vg_data → 풀에서 PE를 떼어 LV(가상 블록 장치) 생성
│ → /dev/vg_data/lv_app 이 생김 (아직 빈 블록, 파일시스템 없음)
│
④ mkfs.ext4 /dev/vg_data/lv_app → LV 위에 파일시스템 구조를 씀
│
⑤ mount /dev/vg_data/lv_app /data → 마운트 → 이제 /data 로 읽고 씀
│ (fstab에 UUID 등록 = 재부팅 후에도 유지)
│
⑥ (부족해지면) lvextend -L +5G -r ... → VG의 Free PE로 LV+파일시스템 온라인 확장
▼
[마운트된 볼륨] df -h /data 에 늘어난 용량이 보임
각 단계가 하는 일과, 막히면 나타나는 증상:
| 단계 | 하는 일 | 여기서 막히면 |
|---|---|---|
| ① pvcreate | 디스크·파티션에 LVM 라벨과 메타데이터를 써서 PV로 초기화. pvs에 나타남 | 기존 파일시스템 서명이 남아 있으면 경고 → wipefs로 정리 후 재시도. 마운트 중인 장치엔 실패 |
| ② vgcreate·vgextend | 여러 PV를 하나의 VG(풀)로 묶고 전체를 PE(기본 4MB) 단위로 분할. 용량은 이제 VG의 Free PE로 계산 | PV가 다른 VG에 이미 속하면 실패. vgs의 VFree가 0이면 LV를 못 만듦 → PV 추가 필요 |
| ③ lvcreate | VG의 Free PE를 떼어 LV(가상 블록 장치 /dev/vg/lv)를 만든다 | 요청 크기가 Free PE보다 크면 Insufficient free extents → -l 100%FREE로 남은 만큼만 쓰거나 VG 확장 |
| ④ mkfs | LV 위에 ext4·xfs 파일시스템 구조를 만든다. LV는 이때까지 빈 블록 | 포맷 없이 마운트 시도 → wrong fs type 에러. 포맷 대상 장치는 반드시 재확인 |
| ⑤ mount + fstab | 파일시스템을 디렉터리(/data)에 연결하고, UUID로 fstab 등록해 영속화 | fstab 오타·잘못된 UUID → 부팅 시 마운트 실패. mount -a로 사전 검증 |
| ⑥ lvextend + resize | VG Free PE로 LV를 늘리고 파일시스템도 확장(-r). 순서는 항상 LV 먼저·파일시스템 나중 | LV만 늘리고 파일시스템을 안 늘리면 df엔 그대로. VG Free가 0이면 확장 불가 → PV 먼저 추가 |
즉 "LVM 볼륨이 커진다"는 건 항상 아래에서 위로 흐릅니다 — VG에 Free PE가 있어야(②) LV를 늘리고, LV를 늘려야 그 위 파일시스템을 늘립니다(⑥). 그래서 "확장했는데 df에 안 늘어남"은 대부분 두 곳에서 끊깁니다: VG Free PE가 0이거나(→ pvcreate+vgextend로 디스크 추가), lvextend만 하고 resize2fs·xfs_growfs를 빼먹은 것(→ -r 옵션으로 한 번에). 스냅샷도 같은 구조 위에 삽니다 — 스냅샷은 원본 LV와 같은 VG의 Free PE를 빌려 CoW 영역으로 쓰므로, VG에 여유가 없으면 만들 수 없고 그 여유가 다 차면 스냅샷이 무효화됩니다. 문제를 만나면 pvs(디스크가 PV인가) → vgs(VFree가 있나) → lvs(LV·스냅샷 상태) → df -h(파일시스템까지 반영됐나) 순으로 계층을 따라 내려가며 어디서 끊겼는지 좁힙니다.
실습 전 루프백 이미지 파일을 생성하여 실제 디스크 없이 LVM을 실습할 수 있도록 환경을 준비합니다.
# 실습 디렉토리 준비
mkdir -p /tmp/linux/part3/exam_2
# 실습용 루프백 이미지 파일 생성 (실제 디스크 없이 LVM 실습)
dd if=/dev/zero of=/tmp/linux/part3/exam_2/disk1.img bs=1M count=500 2>/dev/null
dd if=/dev/zero of=/tmp/linux/part3/exam_2/disk2.img bs=1M count=500 2>/dev/null
LOOP1=$(sudo losetup -f --show /tmp/linux/part3/exam_2/disk1.img)
LOOP2=$(sudo losetup -f --show /tmp/linux/part3/exam_2/disk2.img)
echo "루프백 장치: $LOOP1, $LOOP2"
이제 실습을 진행합니다.
실습에서는 /dev/sdb, /dev/sdc 두 개의 디스크를 사용합니다. 가상 머신이나 클라우드 인스턴스에 추가 디스크를 연결한 후 시작합니다.
현재 디스크 상태 확인 — LVM 설정 전에 현재 디스크와 파티션 구성을 파악합니다:
# 연결된 모든 블록 장치 확인
lsblk
# 예시 출력:
# NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS
# sda 8:0 0 50G 0 disk
# ├─sda1 8:1 0 1G 0 part /boot
# └─sda2 8:2 0 49G 0 part /
# sdb 8:16 0 20G 0 disk ← 새로 추가된 디스크
# sdc 8:32 0 20G 0 disk ← 새로 추가된 디스크
# 디스크 상세 정보 확인
fdisk -l /dev/sdb
fdisk -l /dev/sdc
파티션 생성 (선택 사항):
물리 디스크 전체를 PV로 사용할 수도 있지만, 파티션을 먼저 생성하는 것이 일반적입니다. 파티션 타입을 Linux LVM (8e)으로 설정해야 합니다.
# /dev/sdb에 파티션 생성
fdisk /dev/sdb
# fdisk 대화형 명령:
# n → 새 파티션 생성
# p → Primary 파티션
# 1 → 파티션 번호 1
# Enter → 기본 시작 섹터
# Enter → 기본 끝 섹터 (전체 사용)
# t → 파티션 타입 변경
# 8e → Linux LVM 타입 코드
# w → 변경사항 저장
# /dev/sdc도 동일하게 진행
fdisk /dev/sdc
# 파티션 테이블 재로드
partprobe /dev/sdb
partprobe /dev/sdc
# 결과 확인
lsblk
# sdb 8:16 0 20G 0 disk
# └─sdb1 8:17 0 20G 0 part
# sdc 8:32 0 20G 0 disk
# └─sdc1 8:33 0 20G 0 part
참고: 파티션 없이 디스크 전체(
/dev/sdb)를 PV로 사용할 수도 있습니다. 이 경우 파티션 생성 단계를 건너뛰어도 됩니다.
- 먼저 pvdisplay 출력에서 'Free PE' 항목을 확인한다 — 그 다음 vgdisplay 에서 'Free PE / Size'를 읽는다. Free PE가 0이면 lvextend를 실행해도 확장 불가 — 이 경우 새 디스크를 pvcreate → vgextend 순으로 VG에 추가해야 한다
- lvextend 후 반드시 resize2fs(ext4) 또는 xfs_growfs(xfs)를 실행해야 한다 — lvextend만 하면 블록 디바이스 크기만 늘어나고 파일시스템은 그대로라 df -h 에서 여전히 이전 크기가 표시된다. 두 명령을 합쳐 'lvextend -L +20G -r' 로 한 번에 처리할 수 있다
- vgdisplay 에서 VG Size가 Alloc PE보다 10% 이상 크면 여유 있는 상태 — Alloc PE가 VG Size의 95% 이상이면 확장 전 새 PV 추가를 계획해야 한다
pvcreate 명령으로 파티션(또는 디스크)을 LVM의 Physical Volume으로 초기화합니다.
# PV 생성
pvcreate /dev/sdb1
# Physical volume "/dev/sdb1" successfully created.
pvcreate /dev/sdc1
# Physical volume "/dev/sdc1" successfully created.
# PV 목록 확인 (간략)
pvs
# 예시 출력:
# PV VG Fmt Attr PSize PFree
# /dev/sdb1 lvm2 --- <20.00g <20.00g
# /dev/sdc1 lvm2 --- <20.00g <20.00g
# PV 상세 정보 확인
pvdisplay /dev/sdb1
# 예시 출력:
# "/dev/sdb1" is a new physical volume of "<20.00 GiB"
# --- NEW Physical volume ---
# PV Name /dev/sdb1
# VG Name
# PV Size <20.00 GiB
# Allocatable NO
# PE Size 0
# Total PE 0
# Free PE 0
# Allocated PE 0
# PV UUID abc123-...
PV 생성 시 유용한 옵션: — PE 크기 조정 등 PV 생성 시 자주 쓰는 옵션입니다:
# PE 크기를 8MB로 지정하여 PV 생성
pvcreate --physicalextentsize 8M /dev/sdb1
# 기존 데이터가 있는 디스크에 강제 생성 (주의: 데이터 손실 가능)
pvcreate --force /dev/sdb1
# 여러 디스크를 한번에 PV로 생성
pvcreate /dev/sdb1 /dev/sdc1
vgcreate로 PV들을 묶어 Volume Group을 만들고, lvcreate로 실제 사용할 Logical Volume을 생성합니다.
Volume Group 생성 — 여러 PV를 하나의 VG로 묶어 큰 스토리지 풀을 만듭니다:
# vg_data라는 이름의 VG 생성 (두 PV를 하나의 풀로 통합)
vgcreate vg_data /dev/sdb1 /dev/sdc1
# 출력:
# Volume group "vg_data" successfully created
# VG 상태 확인 (간략)
vgs
# 예시 출력:
# VG #PV #LV #SN Attr VSize VFree
# vg_data 2 0 0 wz--n- <39.99g <39.99g
# VG 상세 정보 확인
vgdisplay vg_data
# 예시 출력:
# --- Volume group ---
# VG Name vg_data
# System ID
# Format lvm2
# VG Access read/write
# VG Status resizable
# MAX LV 0
# Cur LV 0
# Open LV 0
# Max PV 0
# Cur PV 2
# Act PV 2
# VG Size <39.99 GiB
# PE Size 4.00 MiB
# Total PE 10238
# Alloc PE / Size 0 / 0
# Free PE / Size 10238 / <39.99 GiB
# VG UUID xyz789-...
Logical Volume 생성 — VG에서 필요한 크기만큼 LV를 잘라냅니다:
# 15GB 크기의 lv_mysql 생성
lvcreate -L 15G -n lv_mysql vg_data
# 출력:
# Logical volume "lv_mysql" created.
# 10GB 크기의 lv_logs 생성
lvcreate -L 10G -n lv_logs vg_data
# VG의 남은 공간 전부를 lv_backup으로 사용
lvcreate -l 100%FREE -n lv_backup vg_data
# LV 상태 확인
lvs
# 예시 출력:
# LV VG Attr LSize Pool Origin Data% Meta% Move Log Cpy%Sync Convert
# lv_backup vg_data -wi-a----- <14.99g
# lv_logs vg_data -wi-a----- 10.00g
# lv_mysql vg_data -wi-a----- 15.00g
# 상세 LV 정보
lvdisplay /dev/vg_data/lv_mysql
lvcreate 옵션 정리 — 용량, 이름, 스냅샷 등 lvcreate 주요 옵션과 예시입니다:
| 옵션 | 설명 | 예시 |
|---|---|---|
-L 크기 | 절대 크기 지정 | -L 15G |
-l PE수 | PE 개수로 지정 | -l 3840 |
-l %VG | VG 비율로 지정 | -l 50%VG |
-l %FREE | 남은 공간 비율 | -l 100%FREE |
-n 이름 | LV 이름 지정 | -n lv_mysql |
LV는 파일시스템이 없는 빈 블록 장치입니다. 사용하려면 파일시스템을 생성하고 마운트 포인트에 연결해야 합니다.
파일시스템 생성 — LV에 ext4 또는 XFS 파일시스템을 생성합니다:
# ext4 파일시스템 생성 (범용적, 축소 가능)
mkfs.ext4 /dev/vg_data/lv_mysql
# 출력 예시:
# mke2fs 1.46.5 (30-Dec-2021)
# Creating filesystem with 3932160 4k blocks and 983040 inodes
# Filesystem UUID: a1b2c3d4-...
# Writing inode tables: done
# Creating journal (16384 blocks): done
# Writing superblocks and filesystem accounting information: done
# xfs 파일시스템 생성 (고성능, 대용량 파일 최적화, 확장만 가능)
mkfs.xfs /dev/vg_data/lv_logs
# ext4로 lv_backup 생성
mkfs.ext4 /dev/vg_data/lv_backup
마운트 포인트 생성 및 마운트 — 디렉터리를 만들고 LV를 마운트합니다:
# 마운트 포인트 디렉토리 생성
mkdir -p /var/lib/mysql
mkdir -p /var/log/app
mkdir -p /backup
# 임시 마운트 (재부팅 시 사라짐)
mount /dev/vg_data/lv_mysql /var/lib/mysql
mount /dev/vg_data/lv_logs /var/log/app
mount /dev/vg_data/lv_backup /backup
# 마운트 확인
df -hT | grep -E "vg_data|Filesystem"
# 출력 예시:
# Filesystem Type Size Used Avail Use% Mounted on
# /dev/mapper/vg_data-lv_mysql ext4 15G 24K 14G 1% /var/lib/mysql
# /dev/mapper/vg_data-lv_logs xfs 10G 105M 9.9G 2% /var/log/app
# /dev/mapper/vg_data-lv_backup ext4 15G 24K 14G 1% /backup
UUID 확인 및 /etc/fstab 등록:
재부팅 후에도 자동으로 마운트되도록 /etc/fstab에 등록합니다. 장치 경로 대신 UUID를 사용하는 것이 안전합니다.
# UUID 확인
blkid /dev/vg_data/lv_mysql
# /dev/mapper/vg_data-lv_mysql: UUID="a1b2c3d4-e5f6-7890-abcd-ef1234567890" BLOCK_SIZE="4096" TYPE="ext4"
blkid /dev/vg_data/lv_logs
# /dev/mapper/vg_data-lv_logs: UUID="b2c3d4e5-f6a7-8901-bcde-f12345678901" BLOCK_SIZE="512" TYPE="xfs"
blkid /dev/vg_data/lv_backup
# /dev/mapper/vg_data-lv_backup: UUID="c3d4e5f6-a7b8-9012-cdef-123456789012" BLOCK_SIZE="4096" TYPE="ext4"
# /etc/fstab 편집
# 형식: UUID=<uuid> <마운트포인트> <파일시스템타입> <옵션> <dump> <pass>
cat >> /etc/fstab << 'EOF'
UUID=a1b2c3d4-e5f6-7890-abcd-ef1234567890 /var/lib/mysql ext4 defaults 0 2
UUID=b2c3d4e5-f6a7-8901-bcde-f12345678901 /var/log/app xfs defaults 0 2
UUID=c3d4e5f6-a7b8-9012-cdef-123456789012 /backup ext4 defaults 0 2
EOF
# fstab 문법 검증 (마운트하지 않고 검사만)
mount -a --fake
# 오류 없으면 아무 출력 없이 종료됨
# 실제 마운트 적용 (언마운트 후 fstab 기준으로 다시 마운트)
umount /var/lib/mysql
mount -a
df -h /var/lib/mysql
fstab 옵션 설명 — /etc/fstab의 각 필드 의미와 권장값입니다:
| 필드 | 설명 | 권장값 |
|---|---|---|
| dump | 백업 유틸리티 사용 여부 | 0 (비활성) |
| pass | fsck 검사 순서 | 1 (루트), 2 (나머지), 0 (비활성) |
4. LV 및 VG 확장
LVM의 가장 강력한 기능 중 하나는 서비스를 중단하지 않고 볼륨을 확장하는 것입니다. LV 확장 후 반드시 파일시스템도 확장해야 합니다.
VG 여유공간 확인 — 확장 전에 VG에 남은 PE 수량을 확인합니다:
vgs vg_data
# VG #PV #LV #SN Attr VSize VFree
# vg_data 2 3 0 wz--n- <39.99g 0 ← VFree가 0이면 먼저 VG에 디스크 추가 필요
# 여유공간이 있을 경우 진행
vgs vg_data
# VG #PV #LV #SN Attr VSize VFree
# vg_data 2 2 0 wz--n- <39.99g 14.99g
ext4 볼륨 확장 (온라인) — 서비스 중단 없이 LV와 파일시스템을 동시에 확장합니다:
# lv_mysql을 5GB 추가 확장 (현재 15G → 20G)
lvextend -L +5G /dev/vg_data/lv_mysql
# 출력:
# Size of logical volume vg_data/lv_mysql changed from 15.00 GiB (3840 extents)
# to 20.00 GiB (5120 extents).
# Logical volume vg_data/lv_mysql successfully resized.
# ext4 파일시스템 온라인 확장 (마운트된 상태에서 가능)
resize2fs /dev/vg_data/lv_mysql
# 출력:
# resize2fs 1.46.5 (30-Dec-2021)
# Filesystem at /dev/mapper/vg_data-lv_mysql is mounted on /var/lib/mysql;
# on-line resizing required
# old_desc_blocks = 2, new_desc_blocks = 3
# The filesystem on /dev/mapper/vg_data-lv_mysql is now 5242880 (4k) blocks long.
# 확장 결과 확인
df -h /var/lib/mysql
# Filesystem Size Used Avail Use% Mounted on
# /dev/mapper/vg_data-lv_mysql 20G 24K 19G 1% /var/lib/mysql
xfs 볼륨 확장 (온라인):
xfs는 resize2fs가 아닌 xfs_growfs를 사용합니다. xfs는 온라인 확장만 가능하며 축소는 지원하지 않습니다.
# lv_logs를 3GB 추가 확장
lvextend -L +3G /dev/vg_data/lv_logs
# xfs 파일시스템 확장 (마운트 포인트를 인자로 사용)
xfs_growfs /var/log/app
# 출력:
# meta-data=/dev/mapper/vg_data-lv_logs isize=512 agcount=4, agsize=655360 blks
# = sectsz=512 attr=2, projid32bit=1
# data = bsize=4096 blocks=2621440, imaxpct=25
# = sunit=0 swidth=0 blks
# naming =version 2 bsize=4096 ascii-ci=0, ftype=1
# log =internal bsize=4096 blocks=2560, version=2
# realtime =none extsz=4096 blocks=0, rtextents=0
# data blocks changed from 2621440 to 3407872
# 확인
df -h /var/log/app
# Filesystem Size Used Avail Use% Mounted on
# /dev/mapper/vg_data-lv_logs 13G 105M 13G 1% /var/log/app
lvextend + 파일시스템 확장을 한 번에 — -r 옵션으로 LV 확장과 파일시스템 확장을 한 명령에 처리합니다:
# -r 옵션: lvextend 후 자동으로 resize2fs 또는 xfs_growfs 실행
lvextend -L +5G -r /dev/vg_data/lv_mysql
VG의 여유공간이 부족할 때 새 물리 디스크를 추가하여 스토리지 풀을 확장합니다. 이 과정도 서비스 중단 없이 진행됩니다.
새 디스크 인식 확인 — 추가한 디스크가 커널에 인식됐는지 확인합니다:
# 새로 추가된 디스크 확인 (예: /dev/sdd)
lsblk | grep sdd
# sdd 8:48 0 30G 0 disk
# 새 디스크에 파티션 생성 (또는 디스크 전체 사용)
fdisk /dev/sdd
# n → p → 1 → Enter → Enter → t → 8e → w
partprobe /dev/sdd
새 디스크를 PV로 등록 후 VG에 추가 — 새 PV를 기존 VG에 합쳐 스토리지 풀을 확장합니다:
# 새 파티션을 PV로 생성
pvcreate /dev/sdd1
# Physical volume "/dev/sdd1" successfully created.
# 기존 vg_data에 새 PV 추가
vgextend vg_data /dev/sdd1
# Volume group "vg_data" successfully extended
# VG 확장 결과 확인
vgs vg_data
# VG #PV #LV #SN Attr VSize VFree
# vg_data 3 3 0 wz--n- <69.98g <29.99g
# ↑ PV 3개, VFree가 30GB로 늘어남
# 전체 상태 한 번에 확인
pvs
# PV VG Fmt Attr PSize PFree
# /dev/sdb1 vg_data lvm2 a-- <20.00g 0
# /dev/sdc1 vg_data lvm2 a-- <20.00g 0
# /dev/sdd1 vg_data lvm2 a-- <30.00g <29.99g
vgs
# VG #PV #LV #SN Attr VSize VFree
# vg_data 3 3 0 wz--n- <69.98g <29.99g
lvs
# LV VG Attr LSize
# lv_backup vg_data -wi-ao---- <14.99g
# lv_logs vg_data -wi-ao---- 13.00g
# lv_mysql vg_data -wi-ao---- 20.00g
이제 lvextend로 기존 LV를 추가 확장하거나 새 LV를 생성할 수 있습니다.
5. LVM 스냅샷
LVM 스냅샷: 백업 전 일관성 있는 순간 포착
확대
MySQL이 운영 중인 볼륨을 rsync로 백업하면 파일 절반은 백업 전 상태, 나머지 절반은 백업 중 변경된 상태가 섞인 깨진 백업본이 만들어집니다. 데이터베이스를 중단하지 않고 일관성 있는 백업을 만들려면, 특정 시점의 상태를 순간적으로 고정한 뒤 그 복사본을 백업해야 합니다. LVM 스냅샷이 바로 이 역할을 합니다. FLUSH TABLES WITH READ LOCK으로 MySQL을 잠깐 멈추는 순간 스냅샷을 찍고 즉시 잠금을 해제하면, 이후 백업 작업이 몇 시간 걸리더라도 서비스에는 영향이 없습니다.
LVM 스냅샷은 특정 시점의 LV 상태를 순간 포착합니다. 데이터베이스 백업 시 특히 유용합니다. 데이터베이스를 잠깐 flush하고 스냅샷을 생성한 뒤 백업하면, 일관성 있는 백업본을 얻을 수 있습니다.
스냅샷 동작 원리 (COW: Copy-on-Write):
스냅샷 생성 시점에는 실제 데이터를 복사하지 않습니다. 원본 데이터가 변경될 때만 변경 전 데이터를 스냅샷 공간에 복사합니다. 따라서 스냅샷 생성은 매우 빠르고, 스냅샷 크기는 원본 LV보다 훨씬 작아도 됩니다.
확대
스냅샷 생성 및 활용 — LVM 스냅샷으로 배포 전 백업 시점을 만들고 롤백에 활용합니다:
# MySQL 데이터 flush (백업 일관성을 위해)
mysql -e "FLUSH TABLES WITH READ LOCK;"
# lv_mysql의 5GB 스냅샷 생성
lvcreate -L 5G -s -n lv_mysql_snap /dev/vg_data/lv_mysql
# 출력:
# Logical volume "lv_mysql_snap" created.
# MySQL 잠금 해제
mysql -e "UNLOCK TABLES;"
# 스냅샷 확인
lvs
# LV VG Attr LSize Origin Snap% Data%
# lv_mysql vg_data owi-aos--- 20.00g ← 원본
# lv_mysql_snap vg_data swi-a-s--- 5.00g lv_mysql 0.00 ← 스냅샷
# 스냅샷 마운트하여 백업
mkdir -p /mnt/snap_backup
mount -o ro /dev/vg_data/lv_mysql_snap /mnt/snap_backup
# rsync로 백업 진행 (서비스 영향 없음)
rsync -av /mnt/snap_backup/ /backup/mysql-$(date +%Y%m%d)/
# 백업 완료 후 스냅샷 제거
umount /mnt/snap_backup
lvremove /dev/vg_data/lv_mysql_snap
스냅샷 사용 시 주의사항:
- 스냅샷 공간이 가득 차면 스냅샷이 무효화됩니다. 원본 데이터 변경량을 고려하여 충분한 크기를 할당하세요.
lvs의Snap%컬럼으로 스냅샷 사용률을 모니터링하세요.- 스냅샷은 임시 목적으로만 사용하고, 장기 보관은 rsync 또는 tar로 실제 백업을 생성하세요.
6. 소프트웨어 RAID: mdadm
RAID 레벨 비교: 속도 vs 안정성 트레이드오프
확대
새 서버에 디스크를 여러 개 달아야 할 때, "그냥 전부 하나로 묶으면 안 되나요?"라는 질문이 나옵니다. 묶는 방식에 따라 디스크 하나가 죽었을 때 서버가 살아남는지 여부가 달라지고, 같은 디스크 4개를 써도 실제로 쓸 수 있는 용량이 완전히 달라집니다. 데이터베이스 서버인지 로그 수집 서버인지, 성능이 중요한지 데이터 안전성이 중요한지에 따라 RAID 레벨 선택이 달라집니다. 하드웨어 RAID 컨트롤러 없이도 mdadm으로 소프트웨어 RAID를 구성할 수 있으며, 클라우드 환경에서도 같은 개념이 그대로 적용됩니다.
RAID(Redundant Array of Independent Disks)는 여러 디스크를 하나처럼 사용하여 성능 향상 또는 데이터 이중화를 제공합니다. 하드웨어 RAID 컨트롤러 없이도 Linux에서 소프트웨어 RAID를 구성할 수 있습니다.
RAID 레벨 비교표 — RAID 0/1/5/6/10 각 레벨의 특성과 최소 디스크 수를 비교합니다:
| RAID 레벨 | 최소 디스크 | 내고장성 | 읽기 성능 | 쓰기 성능 | 용량 효율 | 주요 용도 |
|---|---|---|---|---|---|---|
| RAID 0 (스트라이핑) | 2개 | 없음 | 매우 높음 | 매우 높음 | 100% | 임시 데이터, 캐시 |
| RAID 1 (미러링) | 2개 | 1개 디스크 | 높음 (읽기 분산) | 동일 | 50% | OS, 중요 데이터 |
| RAID 5 (패리티) | 3개 | 1개 디스크 | 높음 | 중간 | (N-1)/N | 파일서버, NAS |
| RAID 10 (1+0) | 4개 | 각 미러에서 1개 | 매우 높음 | 높음 | 50% | 데이터베이스 |
RAID 구조 시각화 — RAID 1과 RAID 5의 데이터 분산 구조를 시각적으로 표현합니다:
확대
실무에서의 선택 기준:
- RAID 0: 재생성 가능한 임시 데이터 (빌드 캐시, 임시 파일 등)
- RAID 1: 부팅 디스크, 설정 파일, 소규모 중요 데이터
- RAID 5: 4TB 이상의 파일 서버 (단, 고장 시 rebuild 중 추가 고장 위험)
- RAID 10: 데이터베이스 서버 — 성능과 안전성 모두 중요
mdadm은 Linux 소프트웨어 RAID를 관리하는 표준 도구입니다. RAID 1(미러링) 구성을 실습합니다.
mdadm 설치 및 준비 — mdadm을 설치하고 실습용 루프백 디바이스를 준비합니다:
# RHEL/CentOS/Rocky Linux
dnf install -y mdadm
# Ubuntu/Debian
apt-get install -y mdadm
# 사용할 디스크 확인 (실습: /dev/sde, /dev/sdf)
lsblk | grep -E "sde|sdf"
RAID 1 어레이 생성 — 두 디스크를 미러링하는 RAID 1 어레이를 생성합니다:
# /dev/md0 이름으로 RAID 1 어레이 생성
mdadm --create /dev/md0 \
--level=1 \
--raid-devices=2 \
/dev/sde /dev/sdf
# 생성 시 확인 메시지:
# mdadm: Note: this array has metadata at the start and
# may not be suitable as a boot device. If you plan to
# store '/boot' on this device please ensure that
# your boot-loader understands md/v1.x metadata, or use
# --metadata=0.90
# Continue creating array? y
# mdadm: Increasingly creating RAID1 array /dev/md0
# RAID 동기화 진행 상황 확인 (초기 동기화 필요)
cat /proc/mdstat
# 출력 예시 (동기화 중):
# Personalities : [raid1]
# md0 : active raid1 sde[0] sdf[1]
# 20955136 blocks super 1.2 [2/2] [UU]
# [=========>...........] resync = 47.5% (9952640/20955136) finish=1.1min speed=168M/s
# 동기화 완료 후:
# md0 : active raid1 sde[0] sdf[1]
# 20955136 blocks super 1.2 [2/2] [UU]
# ← [UU]는 두 디스크 모두 정상 (Up)
# 어레이 상세 정보
mdadm --detail /dev/md0
# 출력:
# /dev/md0:
# Version : 1.2
# Creation Time : Thu Mar 26 10:00:00 2026
# Raid Level : raid1
# Array Size : 20955136 (19.99 GiB 21.46 GB)
# Used Dev Size : 20955136 (19.99 GiB 21.46 GB)
# Raid Devices : 2
# Total Devices : 2
# Persistence : Superblock is persistent
# Update Time : Thu Mar 26 10:05:00 2026
# State : clean
# Active Devices : 2
# Working Devices : 2
# Failed Devices : 0
# Spare Devices : 0
# Layout : -
# Chunk Size : -
# Name : server:0
# UUID : a1b2c3d4:e5f6a7b8:c9d0e1f2:a3b4c5d6
# Events : 18
# Number Major Minor RaidDevice State
# 0 8 64 0 active sync /dev/sde
# 1 8 80 1 active sync /dev/sdf
RAID 어레이에 파일시스템 생성 및 마운트 — RAID 어레이에 파일시스템을 만들고 영구 마운트합니다:
# RAID 장치에 ext4 파일시스템 생성
mkfs.ext4 /dev/md0
# 마운트 포인트 생성 및 마운트
mkdir -p /data/raid1
mount /dev/md0 /data/raid1
# mdadm 설정 파일 저장 (재부팅 후 자동 인식)
mdadm --detail --scan >> /etc/mdadm.conf
# 또는 Rocky Linux/RHEL의 경우:
mdadm --detail --scan >> /etc/mdadm/mdadm.conf
# fstab에 등록 (UUID 사용 권장)
blkid /dev/md0
# /dev/md0: UUID="d4e5f6a7-..." TYPE="ext4"
echo 'UUID=d4e5f6a7-... /data/raid1 ext4 defaults 0 2' >> /etc/fstab
디스크 고장 시뮬레이션 및 교체 — 디스크 하나를 강제로 고장 내고 핫스왑 교체 과정을 실습합니다:
# 디스크 고장 시뮬레이션 (/dev/sdf를 고장 상태로 표시)
mdadm /dev/md0 --fail /dev/sdf
# 상태 확인
cat /proc/mdstat
# md0 : active raid1 sde[0] sdf[1](F)
# 20955136 blocks super 1.2 [2/1] [U_]
# ← [U_]: sde는 정상(U), sdf는 고장(_)
# 고장 디스크 제거
mdadm /dev/md0 --remove /dev/sdf
# 새 디스크 준비 (실습에서는 /dev/sdg 사용)
# 실제 환경에서는 물리적으로 디스크를 교체한 후 진행
# 새 디스크 추가 (자동 rebuild 시작)
mdadm /dev/md0 --add /dev/sdg
# rebuild 진행 확인
cat /proc/mdstat
# md0 : active raid1 sdg[2] sde[0]
# 20955136 blocks super 1.2 [2/1] [U_]
# [========>............] recovery = 42.3% (8857856/20955136) finish=1.0min speed=195M/s
# rebuild 완료 후
# md0 : active raid1 sdg[1] sde[0]
# 20955136 blocks super 1.2 [2/2] [UU]
7. LVM 상태 모니터링 명령어 총정리
# ========== PV 관련 ==========
# PV 목록 간략히
pvs
# PV 상세 정보
pvdisplay
# 특정 PV 상세
pvdisplay /dev/sdb1
# PV 삭제 (VG에서 제거 후 가능)
pvremove /dev/sdb1
# ========== VG 관련 ==========
# VG 목록 간략히
vgs
# VG 상세 정보
vgdisplay
# 특정 VG 상세
vgdisplay vg_data
# VG에 PV 추가
vgextend vg_data /dev/sdd1
# VG에서 PV 제거 (데이터 이동 후)
pvmove /dev/sdb1 # 기존 PV의 데이터를 다른 PV로 이동
vgreduce vg_data /dev/sdb1
# VG 이름 변경
vgrename vg_data vg_production
# VG 삭제
vgremove vg_data
# ========== LV 관련 ==========
# LV 목록 간략히
lvs
# LV 상세 정보
lvdisplay
# 특정 LV 상세
lvdisplay /dev/vg_data/lv_mysql
# LV 이름 변경
lvrename vg_data lv_mysql lv_database
# LV 삭제 (언마운트 후)
umount /var/lib/mysql
lvremove /dev/vg_data/lv_mysql
# ========== 전체 구조 한 눈에 ==========
# pvs + vgs + lvs 통합 출력
lvm fullreport
새벽 2시, 모니터링 알림이 울립니다. /var/lib/mysql 파티션 사용률이 95%를 넘었습니다.
LVM이 없었다면 (전통 파티션):
- 서비스 중단 공지 발송
- MySQL 서비스 중지
- 데이터 임시 이동
- 파티션 재편성 또는 새 디스크로 데이터 마이그레이션
- 서비스 재시작
- 총 소요 시간: 2~4시간, 서비스 다운타임 발생
LVM이 있다면 — LVM 위에서 파티션 크기를 온라인으로 변경하는 방법입니다:
# 1. 상황 파악 (1분)
df -h /var/lib/mysql
vgs vg_data # VFree 확인
# 2a. VG에 여유공간이 있는 경우 (3분)
lvextend -L +20G -r /dev/vg_data/lv_mysql
# 완료. 서비스 중단 없음.
# 2b. VG가 꽉 찬 경우 (10분)
# 새 디스크 연결 → pvcreate → vgextend → lvextend -r
# 완료. 서비스 중단 없음.
총 소요 시간: 3~15분, 서비스 다운타임 0
이것이 현대 리눅스 시스템 관리자가 LVM을 기본으로 사용하는 이유입니다. 클라우드 환경(AWS EBS, GCP Persistent Disk)에서도 온라인 볼륨 확장이 가능하지만, 인스턴스 내부의 파일시스템까지 확장하려면 결국 LVM 또는 resize2fs/xfs_growfs 명령이 필요합니다.
8. 트러블슈팅
상황 — LV 확장 명령은 성공했는데 실제 사용 가능한 공간이 늘지 않았습니다:
lvextend -L +10G /dev/vg_data/lv_mysql
# Logical volume vg_data/lv_mysql successfully resized.
df -h /var/lib/mysql
# Filesystem Size Used Avail Use% Mounted on
# /dev/mapper/vg_data-lv_mysql 15G 14G 1.0G 94% /var/lib/mysql
# ← 여전히 15G로 표시됨!
원인 — lvextend는 LV(블록 장치) 크기만 늘립니다. 파일시스템은 그 위에 별도로 올라가 있어서, 파일시스템 드라이버에게 "공간이 늘었다"고 따로 알려야 합니다. -r 옵션을 생략하면 이 단계를 잊기 쉽습니다.
진단 — 파일시스템 타입을 먼저 확인합니다:
df -T /var/lib/mysql
# Filesystem Type Size Used Avail Use% Mounted on
# /dev/mapper/vg_data-lv_mysql ext4 15G 14G 1.0G 94% /var/lib/mysql
# ↑ 타입 확인 (ext4 또는 xfs)
해결 — 파일시스템 타입에 맞는 확장 명령을 실행합니다:
# ext4인 경우
resize2fs /dev/vg_data/lv_mysql
# resize2fs 1.46.5 (30-Dec-2021)
# Filesystem at /dev/mapper/vg_data-lv_mysql is mounted on /var/lib/mysql;
# on-line resizing required
# The filesystem on /dev/mapper/vg_data-lv_mysql is now 6553600 (4k) blocks long.
# xfs인 경우 (반드시 마운트된 상태에서, 마운트 포인트 경로 사용)
xfs_growfs /var/lib/mysql
# 결과 확인
df -h /var/lib/mysql
# Filesystem Size Used Avail Use% Mounted on
# /dev/mapper/vg_data-lv_mysql 25G 14G 11G 56% /var/lib/mysql
예방: lvextend에 항상 -r 옵션을 붙이면 파일시스템 확장까지 자동으로 처리됩니다.
lvextend -L +10G -r /dev/vg_data/lv_mysql
상황 — LV를 확장하려는데 VG 여유 공간이 없어 명령이 실패합니다:
lvextend -L +10G /dev/vg_data/lv_mysql
# Insufficient free space: 2560 extents needed, but only 0 available
원인 — VG 풀 자체가 꽉 찬 상태입니다. 물리 디스크를 새로 추가해 VG를 확장해야 합니다.
진단 — VFree가 0인지 확인합니다:
vgs vg_data
# VG #PV #LV #SN Attr VSize VFree
# vg_data 2 3 0 wz--n- <39.99g 0
# ↑ VFree 0: VG가 꽉 참
해결 — 새 디스크를 연결하고 PV → VG → LV 순으로 확장합니다:
# 1. 새 디스크 연결 확인
lsblk
# sdd 8:48 0 50G 0 disk ← 새로 추가된 디스크
# 2. 파티션 생성 (또는 디스크 전체 사용)
fdisk /dev/sdd
# n → p → 1 → Enter → Enter → t → 8e → w
partprobe /dev/sdd
# 디스크 전체를 바로 PV로 사용할 경우 파티션 불필요:
# pvcreate /dev/sdd
# 3. PV 생성
pvcreate /dev/sdd1
# Physical volume "/dev/sdd1" successfully created.
# 4. VG에 PV 추가
vgextend vg_data /dev/sdd1
# Volume group "vg_data" successfully extended
# 5. VG 여유공간 확인
vgs vg_data
# VG #PV #LV #SN Attr VSize VFree
# vg_data 3 3 0 wz--n- <89.98g <49.99g
# ↑ VFree 50GB 생김
# 6. LV 확장 진행
lvextend -L +10G -r /dev/vg_data/lv_mysql
# Logical volume vg_data/lv_mysql successfully resized.
반복 작업이라면 스크립트로 자동화할 수 있습니다:
#!/bin/bash
# 새 디스크를 VG에 추가하는 스크립트
NEW_DISK="/dev/sdd"
VG_NAME="vg_data"
# 파티션 생성
parted -s $NEW_DISK mklabel gpt mkpart primary 0% 100% set 1 lvm on
partprobe $NEW_DISK
# PV 생성 및 VG 확장
pvcreate ${NEW_DISK}1
vgextend $VG_NAME ${NEW_DISK}1
# 결과 확인
echo "=== 확장 완료 ==="
vgs $VG_NAME
상황 — 재부팅 후 시스템이 부팅되지 않고 Emergency Mode로 떨어집니다:
[FAILED] Failed to mount /var/lib/mysql.
See 'systemctl status var-lib-mysql.mount' for details.
[DEPEND] Dependency failed for Local File Systems.
You are in emergency mode. After logging in, type "journalctl -xb" to view
system logs, "systemctl reboot" to reboot, "systemctl default" or "exit"
to boot into default mode.
Give root password for maintenance (or press Control-D to continue):
원인 — /etc/fstab에 등록된 UUID가 실제 파일시스템 UUID와 다릅니다. LV를 삭제 후 재생성하거나 mkfs를 다시 실행하면 UUID가 바뀌는데, fstab이 이를 반영하지 못한 경우 발생합니다.
진단 — blkid로 실제 UUID를 확인하고 fstab과 비교합니다:
# Emergency Mode에서 root 암호 입력 후:
blkid
# /dev/mapper/vg_data-lv_mysql: UUID="NEW-UUID-HERE" TYPE="ext4"
cat /etc/fstab | grep mysql
# UUID=OLD-UUID-HERE /var/lib/mysql ext4 defaults 0 2
# ← UUID 불일치 확인
해결 — 루트를 읽기-쓰기로 리마운트 후 fstab을 수정합니다:
# 루트 파일시스템을 읽기-쓰기로 리마운트
mount -o remount,rw /
# fstab 수정 (실제 UUID로 교체)
sed -i 's/OLD-UUID-HERE/NEW-UUID-HERE/' /etc/fstab
# 마운트 테스트
mount -a
# 오류 없으면 성공
reboot
예방법:
# fstab 수정 후 항상 검증 실행
mount -a --fake # 실제 마운트 없이 문법 검사
mount -a # 실제 마운트로 오류 확인
# UUID를 fstab에 추가할 때는 blkid 출력을 직접 활용
blkid /dev/vg_data/lv_mysql | awk '{print $2}' | tr -d '"'
# UUID=a1b2c3d4-e5f6-7890-abcd-ef1234567890
긴급 복구 팁:
Emergency Mode에서 vi를 사용하기 어렵다면, 해당 라인을 주석처리하고 재부팅 후 정상 환경에서 수정하세요.
# fstab에서 문제 라인을 주석처리
sed -i '/lv_mysql/s/^/#/' /etc/fstab
reboot
# 정상 부팅 후 fstab 수정 및 수동 마운트
심화 — 스냅샷의 진짜 비용
심화: CoW 쓰기 증폭과 스냅샷 무효화 — 왜 스냅샷은 오래 두면 안 되는가
앞에서 스냅샷은 '순간 포착이라 빠르고 작아도 된다'고 했습니다. 생성은 그렇지만, 스냅샷이 살아 있는 동안의 비용은 다릅니다. 전통(thick) 스냅샷은 Copy-on-Write로 동작하는데, 그 대가가 원본 성능과 스냅샷 수명에 나타납니다.
- 쓰기 증폭(write amplification): 스냅샷이 존재하는 동안 원본의 한 블록을 처음 바꿀 때마다 커널은 '옛 블록을 읽어 → 스냅샷의 CoW 영역에 복사하고 → 원본에 새 블록을 쓰는' 3단계를 수행합니다. 원본 쓰기 1회가 실제로는 여러 I/O가 됩니다. 그래서 스냅샷을 켜 둔 채 원본에 대량 쓰기가 일어나면 원본 성능이 눈에 띄게 느려집니다 — 스냅샷은 '오래 두는 것'이 아니라 '백업 뜨고 바로 지우는 것'인 이유입니다.
- 무효화(invalidation): 스냅샷 크기(
-L)는 '원본에서 바뀔 데이터량'의 상한입니다. 원본 변경이 이 크기를 넘어서면 CoW 영역이 가득 차고, 스냅샷은 드롭(무효화) 됩니다.lvs의 Snap%가 100에 닿으면 그 스냅샷을 읽을 때 Input/output error가 나고, 진행 중이던 백업이 깨집니다(lvsattr에I표시). - 모니터링·수명:
lvs의 Snap%(Data%)를 주시하고, '백업 소요시간 × 원본 변경률'을 넘는 크기를 잡습니다. 무엇보다 백업이 끝나면 즉시lvremove로 지웁니다. - thin과의 차이: thin pool의 스냅샷은 별도 CoW 영역 대신 풀을 공유해 이 '스냅샷 크기 초과 무효화'가 다르게 동작하지만, 대신 풀 자체가 차면 전체가 위험합니다(앞의 Thin Provisioning 참고). 어느 쪽이든 '공간을 넘기면 조용히 위험'하다는 원리는 같습니다.
정리하면, 스냅샷은 '찍는 순간'이 아니라 '살아 있는 시간'이 비쌉니다. 짧게 쓰고 빨리 지우는 것이 성능과 안전 둘 다를 지키는 길입니다.
상황: DB 백업용으로 원본 LV의 스냅샷을 떠서 rsync로 백업하는데, 요즘 들어 야간 백업이 중간에 실패합니다. 스냅샷을 다시 읽으려니 Input/output error가 나고, lvs를 보면 그 스냅샷의 Snap%(Data%)가 100.00, Attr에 I가 붙어 있습니다.
원인: 스냅샷 크기(예: 5GB)를 원본의 야간 변경량이 초과했습니다. 스냅샷이 존재하는 동안 원본에 5GB가 넘게 쓰이자 CoW 영역이 가득 차 스냅샷이 무효화(드롭)됐고, 무효화된 스냅샷은 더 이상 읽을 수 없습니다. 데이터가 늘거나 야간 배치 작업량이 커지면서 서서히 임계를 넘긴 것입니다.
진단: lvs -o +snap_percent,lv_attr로 Snap%가 100인지, attr에 I(invalid)가 있는지 확인합니다. 백업 소요시간 동안의 원본 변경량(대략 iostat의 쓰기량이나 DB 배치 규모)을 스냅샷 크기와 비교합니다. 백업 실패 시각과 Snap% 100 도달 시각이 일치하면 확정입니다.
해결: 스냅샷 크기를 '백업 소요시간 × 원본 변경률'보다 넉넉히 잡습니다(급하면 lvextend로 스냅샷 자체를 키울 수도 있습니다). 더 근본적으로는 백업 시간을 줄이거나(증분·병렬), 변경량이 큰 환경이면 풀을 공유하는 thin 스냅샷으로 전환합니다. 그리고 스냅샷은 백업 직후 즉시 lvremove로 지워 CoW 부담·무효화 위험을 없애고, 재발 방지로 Snap%에 알람을 겁니다.
9. 실전 운영 패턴
대용량 데이터를 다루는 프로덕션 환경에서는 LVM과 RAID를 함께 사용합니다. RAID로 하드웨어 고장에 대비하고, LVM으로 유연한 용량 관리를 합니다.
일반적인 프로덕션 스토리지 구성 — 운영 서버에서 자주 쓰는 LVM+RAID 조합 예시입니다:
확대
모니터링 스크립트 예시 — VG 사용률을 주기적으로 점검하고 임계치 초과 시 알림을 보냅니다:
#!/bin/bash
# /usr/local/bin/check-lvm-health.sh
echo "=== LVM 상태 리포트 $(date) ==="
echo -e "\n[PV 상태]"
pvs
echo -e "\n[VG 상태]"
vgs
echo -e "\n[LV 상태]"
lvs
echo -e "\n[LV 사용률 (80% 이상 경고)]"
df -h | grep "/dev/mapper/" | while read line; do
usage=$(echo $line | awk '{print $5}' | tr -d '%')
mountpoint=$(echo $line | awk '{print $6}')
if [ "$usage" -ge 80 ]; then
echo "경고: $mountpoint → ${usage}% 사용 중"
fi
done
echo -e "\n[RAID 상태]"
cat /proc/mdstat
echo -e "\n[스냅샷 사용률]"
lvs -o lv_name,snap_percent --select 'lv_attr=~s'
Cron으로 정기 확인 등록 — 스토리지 모니터링 스크립트를 cron에 등록해 자동화합니다:
# 매 시간 상태 체크, 이상 시 이메일 발송
echo "0 * * * * root /usr/local/bin/check-lvm-health.sh | mail -s 'LVM Health Report' admin@company.com" \
>> /etc/cron.d/lvm-monitor
실무에서 자주 쓰는 명령어 조합 — LVM 운영에서 반복적으로 쓰는 명령어 패턴 모음입니다:
# LV 전체 사용률 한 눈에 보기
df -hT | grep mapper | sort -k5 -rh
# LV별 I/O 모니터링 (iostat 사용)
iostat -x /dev/mapper/vg_data-lv_mysql 1
# LVM 이벤트 로그 확인
lvmconfig --type current | head -50
journalctl -u lvm2-monitor.service
# PV 간 데이터 균등 배분
pvmove /dev/sdb1 # sdb1의 데이터를 VG 내 다른 PV로 분산
# VG 스캔 (새 디스크 연결 후 인식이 안 될 때)
vgscan
pvscan
10. 주요 명령어 빠른 참조
# ========== LVM 구성 전체 흐름 ==========
pvcreate /dev/sdb1 # 1. PV 생성
vgcreate vg_data /dev/sdb1 # 2. VG 생성
lvcreate -L 20G -n lv_data vg_data # 3. LV 생성
mkfs.ext4 /dev/vg_data/lv_data # 4. 파일시스템 생성
mount /dev/vg_data/lv_data /mnt/data # 5. 마운트
# ========== 용량 확장 ==========
lvextend -L +10G -r /dev/vg_data/lv_data # LV 확장 + 파일시스템 확장
pvcreate /dev/sdc1 && vgextend vg_data /dev/sdc1 # 새 디스크 추가
# ========== 상태 확인 ==========
pvs && vgs && lvs # 전체 요약
pvdisplay && vgdisplay && lvdisplay # 전체 상세
# ========== 스냅샷 ==========
lvcreate -L 5G -s -n snap /dev/vg_data/lv_data # 스냅샷 생성
lvremove /dev/vg_data/snap # 스냅샷 삭제
# ========== RAID ==========
mdadm --create /dev/md0 --level=1 --raid-devices=2 /dev/sde /dev/sdf
mdadm --detail /dev/md0 # RAID 상태
cat /proc/mdstat # RAID 동기화 진행 상황
mdadm /dev/md0 --fail /dev/sdf # 디스크 고장 처리
mdadm /dev/md0 --remove /dev/sdf # 고장 디스크 제거
mdadm /dev/md0 --add /dev/sdg # 새 디스크 추가 (rebuild 시작)
LVM Thin Provisioning — 용량 초과 리스크와 모니터링
확대
개발 서버에서 각 팀마다 20GB씩 할당해달라는 요청이 10개 들어왔는데 실제 물리 디스크는 100GB뿐일 때, 실제로 다들 5GB씩만 쓰고 있다면 Thin Provisioning으로 모두를 만족시킬 수 있습니다. 하지만 이 방식에는 함정이 있습니다. 실제 사용량이 물리 용량을 초과하는 순간 thin pool이 가득 차고, 이때 데이터 손실이 발생합니다. 경고 없이 가득 찬 게 아니라 — 모니터링이 없으면 경고 없이 조용히 서비스가 멈춥니다. 그래서 Thin Provisioning을 쓰는 환경에서는 Data%, Metadata% 모니터링이 선택이 아닌 필수입니다.
Thin Provisioning은 실제 사용량보다 큰 논리 볼륨을 선언할 수 있습니다. 스토리지 효율이 높아지지만 thin pool이 가득 차면 데이터 손실이 발생합니다.
Thin Pool 생성 — 오버커밋을 허용하는 Thin Provisioning LV 풀을 만듭니다:
# 10GB thin pool 생성 (VG에서 메타데이터 + 데이터 풀 분리)
sudo lvcreate -L 10G --thinpool thin_pool vg_data
# Thin Pool에서 20GB 논리 볼륨 생성 (실제 10GB 풀에서 오버프로비저닝)
sudo lvcreate -V 20G --thin -n lv_app vg_data/thin_pool
# Thin Pool 상태 확인
sudo lvs -o name,pool_lv,data_percent,metadata_percent vg_data
# LV Pool Data% Meta%
# lv_app thin_pool 45.23 12.11
# thin_pool 45.23 12.11
용량/메타데이터 고갈 모니터링 — Thin Pool이 가득 차기 전에 경보를 받아야 서비스 장애를 막을 수 있습니다:
# 위험 기준: Data% > 80%, Meta% > 50%
sudo lvs --noheadings -o data_percent,metadata_percent vg_data/thin_pool | \
awk '{
data=$1+0; meta=$2+0
if (data > 80) print "[경고] Thin Pool 데이터 " data "% 사용"
if (meta > 50) print "[경고] Thin Pool 메타데이터 " meta "% 사용"
}'
# Thin Pool 자동 확장 설정 (lvm.conf)
# thin_pool_autoextend_threshold = 80 # 80% 도달 시
# thin_pool_autoextend_percent = 20 # 현재 크기의 20% 추가
sudo lvmconfig --type diff | grep autoextend
메타데이터 고갈 복구 절차 — Thin Pool 메타데이터가 고갈됐을 때 복구하는 절차입니다:
# 메타데이터 영역 긴급 확장 (데이터는 보존)
sudo lvextend --poolmetadatasize +1G vg_data/thin_pool
# 데이터 영역 확장
sudo lvextend -L +5G vg_data/thin_pool
LVM 장애 대응 — pvmove·vgchange 실패·스냅샷 rollback
확대
디스크 하나가 이상 징후를 보여서 pvmove로 데이터를 안전하게 옮기는 중에 서버가 재부팅됐습니다. 재부팅 후 VG가 활성화되지 않아 lvs를 쳐도 아무것도 안 보입니다. 이 상황에서 어떻게 해야 할지 모르면 패닉 상태에서 잘못된 명령을 치다가 데이터를 날릴 수 있습니다. 또는 신규 배포 이후 서비스에 문제가 생겨서 스냅샷으로 롤백해야 하는데, 롤백 절차를 한 번도 연습해보지 않았다면 장애 상황에서 제대로 작동하지 않습니다. 이 ConceptBlock은 LVM 운영 중 실제로 마주치는 세 가지 장애 상황과 복구 절차를 다룹니다.
pvmove 실패 시 복구 — pvmove 중단 후 미완료 상태로 남은 LV를 복구합니다:
# pvmove 중 시스템 재부팅된 경우 — 재개 명령
sudo pvmove --abort # 진행 중인 pvmove 중단
sudo vgchange --sysinit vg_data # VG 상태 초기화
sudo pvmove /dev/sdb1 # 재시작
# pvmove 진행 상황 확인
sudo lvs -a -o name,copy_percent | grep pvmove
vgchange 활성화 실패 복구 — 부팅 후 VG가 활성화되지 않을 때 복구하는 방법입니다:
# 디바이스를 찾지 못해 VG 활성화 실패
# "Couldn't find device with uuid XXX"
sudo vgscan --mknodes # 디바이스 노드 재생성
sudo vgchange -ay vg_data # 강제 활성화
# 일부 PV 실패 시 partial 모드로 활성화 (데이터 손실 위험 — 긴급용)
sudo vgchange -ay --partial vg_data
LVM 스냅샷 merge/rollback — 스냅샷을 원본에 병합하거나 원본을 스냅샷 시점으로 롤백합니다:
# 스냅샷 생성 (변경 전)
sudo lvcreate -L 2G -s -n lv_app_snap /dev/vg_data/lv_app
# 롤백: 스냅샷으로 되돌리기
sudo umount /mnt/app
sudo lvconvert --merge vg_data/lv_app_snap
sudo lvchange -an vg_data/lv_app
sudo lvchange -ay vg_data/lv_app
sudo mount /dev/vg_data/lv_app /mnt/app
ext4 vs XFS 확장/축소 제약 — 두 파일시스템의 온라인 확장·축소 지원 여부를 비교합니다:
| 파일시스템 | 확장 | 축소 | 주의사항 |
|---|---|---|---|
| ext4 | 온라인 가능 (resize2fs) | 오프라인만 가능 | 마운트 해제 후 e2fsck 필수 |
| XFS | 온라인 가능 (xfs_growfs) | 불가 | XFS는 축소 자체가 지원되지 않음 |
# ext4 확장 (온라인)
sudo lvextend -L +5G vg_data/lv_app
sudo resize2fs /dev/vg_data/lv_app
# XFS 확장 (온라인)
sudo lvextend -L +5G vg_data/lv_app
sudo xfs_growfs /mnt/app # 마운트 포인트를 인수로 전달
# lvextend -r 옵션으로 자동 파일시스템 확장 (ext4/XFS 모두 지원)
sudo lvextend -r -L +5G vg_data/lv_app
XFS 볼륨 축소가 필요한 경우: 데이터 백업 → 볼륨 삭제 → 더 작은 볼륨 생성 → 데이터 복원 순서로 진행해야 합니다.
명령어·단축키 빠른 참조
이 모듈에서 다룬 LVM·RAID 볼륨 관리 명령을 실전 옵션과 함께 모았습니다. PV→VG→LV 순으로 만들고, 확장할 때는 항상 파일시스템까지 함께 늘리세요("예" 열 조합을 그대로 써도 됩니다).
| 명령어/단축키 | 용도 | 자주 쓰는 예 |
|---|---|---|
pvcreate | 물리 디스크·파티션을 LVM PV로 초기화 | pvcreate /dev/sdb1 (여러 개: pvcreate /dev/sdb1 /dev/sdc1) |
vgcreate | PV들을 묶어 VG(스토리지 풀) 생성 | vgcreate vg_data /dev/sdb1 /dev/sdc1 |
vgextend | 기존 VG에 새 PV를 추가해 풀 확장 | vgextend vg_data /dev/sdd1 |
lvcreate | VG에서 LV를 잘라내 할당 | lvcreate -L 15G -n lv_mysql vg_data / 남은 전부 -l 100%FREE |
lvcreate -s | 백업용 스냅샷 LV 생성 | lvcreate -L 5G -s -n lv_mysql_snap /dev/vg_data/lv_mysql |
lvextend | LV 확장(+ 파일시스템까지 -r) | lvextend -L +5G -r /dev/vg_data/lv_mysql |
resize2fs | ext4 파일시스템 온라인 확장 | resize2fs /dev/vg_data/lv_mysql |
xfs_growfs | xfs 파일시스템 온라인 확장(마운트 경로 인자) | xfs_growfs /var/log/app |
pvs / vgs / lvs | PV·VG·LV 상태 요약 확인 | vgs vg_data(VFree 확인), lvs -o +snap_percent,lv_attr |
vgdisplay | VG 상세(여유 PE·크기) 확인 | vgdisplay vg_data | grep "Free PE" |
lvremove | LV·스냅샷 삭제(언마운트 후) | lvremove /dev/vg_data/lv_mysql_snap |
pvmove | PV 데이터를 다른 PV로 이동(디스크 제거 전) | pvmove /dev/sdb1 |
mdadm --create | 소프트웨어 RAID 어레이 생성 | mdadm --create /dev/md0 --level=1 --raid-devices=2 /dev/sde /dev/sdf |
mdadm --fail/--remove/--add | RAID 디스크 핫스왑 교체 | mdadm /dev/md0 --fail /dev/sdf → --remove → --add /dev/sdg, cat /proc/mdstat로 rebuild 확인 |
관련 모듈로 더 깊이:
- 디스크·스토리지 관리 기본 명령어 — LVM 아래 물리 디스크·파티션·파일시스템의 기본 구조
- Read-only 파일시스템 에러 진단과 디스크 IO 성능 병목 잡기 — 확장한 볼륨에서 발생하는 파일시스템 손상·I/O 오류 진단
다음 모듈에서는 디스크 장애 진단 — smartctl, badblocks, fsck로 디스크 하드웨어 오류와 파일시스템 손상을 탐지하고 복구하는 방법을 다룹니다.