Skip to content
Menu

Linux Storage11 min read

LVM Fundamentals

Physical Volumes, Volume Groups, and Logical Volumes

What LVM Is

LVM (Logical Volume Manager) is an abstraction layer that sits between raw block devices and the filesystems running on top of them. Instead of binding a filesystem directly to a partition, LVM pools one or more block devices together and carves out virtual block devices of arbitrary size from that pool.

Why It Exists

Partitions are static. Resizing them is fragile and usually offline. LVM makes the mapping from physical capacity to exposed volumes a runtime concern:

  • Resize volumes online — grow (and sometimes shrink) while in use
  • Add / remove disks without re-partitioning the filesystem
  • Move data between disks while applications keep writing (pvmove)
  • Snapshot at the block layer for backups and rollback
  • Thin-provision — allocate on demand, over-commit capacity (see Thin Provisioning & Snapshots)
  • RAID via dm-raid with the same kernel code as mdadm

Note

Under the hood: LVM is built entirely on device-mapper. Every LV is a dm device with a specific target (linear, striped, raid, thin, mirror, cache…). See Device Mapper for the primitives — this page focuses on how LVM composes them into a usable system.

The Three-Level Model

LVM has exactly three logical layers. Master the boundaries between them and the rest of the command surface follows.

Filesystem  (ext4 / xfs / btrfs)
Logical Volume (LV) — /dev/vg/lv — virtual block device
Volume Group (VG) — allocation pool of Physical Extents
Physical Volume (PV) — initialized block device
Block device (disk, partition, md, LUKS mapping…)

Physical Volume (PV)

A PV is any block device that has been initialized for LVM use. It can be a whole disk, a partition, an mdadm RAID array, a LUKS mapping, an iSCSI LUN, anything that exposes a stable block interface. pvcreate writes a small metadata header (the LVM label) at the start of the device and divides the rest into fixed-size Physical Extents (PEs).

console
bash
root@host:~# pvcreate /dev/sdb1
  Physical volume "/dev/sdb1" successfully created.

root@host:~# pvs
  PV         VG    Fmt  Attr PSize    PFree
  /dev/sdb1  data  lvm2 a--  <1.82t   0
  /dev/sdc1  data  lvm2 a--  <1.82t   120.00g
  /dev/sdd1        lvm2 ---   931.51g 931.51g

Volume Group (VG)

A VG is a pool built from one or more PVs. Once a PV is in a VG, its PEs become indistinguishable members of a single allocation pool — the VG doesn’t care which disk they live on (unless you tell it to care, via allocation policies or striping). The VG is the unit you name meaningfully: data, vg_ssd, pve, nvme0

console
bash
root@host:~# vgcreate data /dev/sdb1 /dev/sdc1
  Volume group "data" successfully created

root@host:~# vgs
  VG    #PV #LV #SN Attr   VSize  VFree
  data    2   0   0 wz--n- 3.64t  3.64t

Logical Volume (LV)

An LV is a virtual block device allocated from a VG. It has a size, a type (linear, striped, raid, thin, mirror, cache), and appears in /dev as both /dev/<vg>/<lv> (symlink) and /dev/mapper/<vg>-<lv> (the actual dm node).

console
bash
root@host:~# lvcreate -L 100G -n mydata data
  Logical volume "mydata" created.

root@host:~# ls -l /dev/data/mydata /dev/mapper/data-mydata
lrwxrwxrwx 1 root root  7 Apr 20 10:00 /dev/data/mydata -> ../dm-3
lrwxrwxrwx 1 root root  7 Apr 20 10:00 /dev/mapper/data-mydata -> ../dm-3

root@host:~# mkfs.xfs /dev/data/mydata && mount /dev/data/mydata /data

Physical Extents (PE)

The PE is LVM’s unit of allocation. Default size is 4 MiB (configurable at vgcreate time with -s). Every allocation decision — creating an LV, extending one, moving data — happens in whole PEs.

  • A 100 GiB LV = 25,600 PEs at the default 4 MiB
  • The VG tracks the free PE count across all member PVs
  • Linear LVs claim contiguous PEs (on a single PV when possible, spanning PVs when needed)
  • Striped LVs interleave PEs across PVs in a round-robin pattern

Think of the VG as a grid of PE cells distributed across PVs. An LV is a set of cells marked as belonging to it. The mapping lives in the VG metadata — not in any on-disk structure of the LV itself.

PE grid — simplified view of a VG with 2 PVs

bash
              PV1 (/dev/sdb1)                   PV2 (/dev/sdc1)
         ┌───┬───┬───┬───┬───┬───┐         ┌───┬───┬───┬───┬───┬───┐
         │ A │ A │ A │ A │ B │ B │         │ B │ B │ C │ C │ . │ . │
         ├───┼───┼───┼───┼───┼───┤         ├───┼───┼───┼───┼───┼───┤
         │ A │ A │ A │ A │ . │ . │         │ . │ . │ C │ C │ . │ . │
         └───┴───┴───┴───┴───┴───┘         └───┴───┴───┴───┴───┴───┘

         A = LV "mydata"   (8 PEs, linear on PV1)
         B = LV "logs"     (4 PEs, spans PV1 → PV2, linear)
         C = LV "scratch"  (4 PEs, on PV2)
         . = free PE

LV Types

The LV type chooses which dm target backs the volume. The default is linear; everything else you ask for explicitly.

Type dm target Purpose Create
linear (default) dm-linear Contiguous PE allocation, may span multiple PVs lvcreate -L 100G -n lv vg
striped dm-stripe Interleave PEs across N PVs for parallel I/O lvcreate -L 100G -i 4 -I 64 -n lv vg
mirror (legacy) dm-mirror Synchronous copies — prefer raid1 lvcreate --type mirror -m1 -L 100G…
raid1/4/5/6/10 dm-raid (MD code) Modern RAID levels via the kernel MD stack lvcreate --type raid5 -i 3 -L 300G…
thin dm-thin Allocate on demand from a thin pool See thin-provisioning-snapshots.html
cache dm-cache Front a slow LV with an SSD-backed cache pool See thin/snapshots page
snapshot (traditional) dm-snapshot Short-lived CoW overlay of an origin LV lvcreate -s -L 10G -n snap vg/origin

Tip

Striping tuning: -i N is the stripe count (number of PVs), -I stripesize is in KiB (default 64). Choose a stripe size that matches the dominant I/O unit — larger for sequential, smaller for random.

LVM Metadata

LVM metadata describes the entire VG: PV UUIDs, PE allocations, LV names, types, and extent maps. It is stored on every PV in the VG by default (redundant copies), in a human-readable text format inside the first few MiB of each PV. When you run any LVM command, it reads metadata from a quorum of PVs and writes updates back atomically.

  • Number of copies: one per PV by default; tunable with --metadatacopies
  • On-disk format: plain text (looks like a config file)
  • Backups: every change is also dumped to /etc/lvm/backup/<vg> and archives to /etc/lvm/archive/
console
bash
root@host:~# vgcfgbackup -f /tmp/vg-data.txt data
  Volume group "data" successfully backed up.

root@host:~# pvck --dump metadata /dev/sdb1 | head -20
data {
    id = "aB3cD4-ef5G-..."
    seqno = 17
    format = "lvm2"
    status = ["RESIZEABLE", "READ", "WRITE"]
    extent_size = 8192        # 4 MiB in sectors
    physical_volumes {
        pv0 { id = "..." device = "/dev/sdb1" ... }
        pv1 { id = "..." device = "/dev/sdc1" ... }
    }
    logical_volumes {
        mydata { segment_count = 1 ... }
    }
}

Tip

Recovery: a wiped PV header can often be restored with vgcfgrestore -f /etc/lvm/archive/<vg>_NNNN.vg <vg> as long as the PV’s data area hasn’t been overwritten.

Command Cheat Sheet

LVM commands follow a strict verb-per-layer naming scheme. Once you see the pattern, the whole interface memorises itself.

Layer Inspect Create / Modify Activate / Move / Remove
PV pvs, pvdisplay, pvck pvcreate, pvresize, pvchange pvmove, pvremove
VG vgs, vgdisplay, vgck vgcreate, vgextend, vgreduce, vgrename vgchange -a y|n, vgremove
LV lvs, lvdisplay lvcreate, lvextend, lvreduce, lvresize, lvrename, lvconvert lvchange -a y|n, lvremove

Tip

Shortcut flags: -o +<field> adds columns to pvs/vgs/lvs. Try lvs -o +devices,stripes,lv_attr,lv_layout for a denser view.

Online Resize

Growing a volume

Two steps: extend the LV at the block layer, then grow the filesystem. LVM can do both at once with -r (--resizefs).

console
bash
root@host:~# lvextend -L +50G /dev/data/mydata
  Size of logical volume data/mydata changed from 100.00 GiB to 150.00 GiB.
  Logical volume data/mydata successfully resized.

root@host:~# # ext4
root@host:~# resize2fs /dev/data/mydata
root@host:~# # xfs (must be mounted)
root@host:~# xfs_growfs /data

root@host:~# # one-shot: grow LV and filesystem together
root@host:~# lvextend -r -L +50G /dev/data/mydata

Shrinking a volume (ext4 only)

Warning

XFS cannot be shrunk. Full stop. If you need a smaller volume, xfsdump to another volume, recreate at the target size, xfsrestore. btrfs supports shrink natively (btrfs filesystem resize).

Warning

Always back up first. Shrink is the one LVM operation that can easily lose data if the filesystem ends up smaller than the LV. The rule is: filesystem size ≤ LV size, never the other way around.

console
bash
root@host:~# umount /data
root@host:~# e2fsck -f /dev/data/mydata                 # required before resize2fs shrink
root@host:~# resize2fs -p /dev/data/mydata 80G           # shrink FS first
root@host:~# lvreduce -L 80G /dev/data/mydata            # then shrink LV
root@host:~# e2fsck -f /dev/data/mydata
root@host:~# mount /dev/data/mydata /data

Online Data Migration: pvmove

pvmove relocates PEs from one PV to another while the LV stays online. It uses a temporary dm-mirror to duplicate writes to both source and destination until all PEs are copied, then atomically switches the LV’s extent map to point at the new PV. Applications see no interruption.

Typical uses: retiring a failing disk, swapping to a larger drive, consolidating data onto faster storage, evacuating a PV before vgreduce.

console
bash
root@host:~# # move everything off /dev/sdb1 onto /dev/sdd1
root@host:~# pvmove /dev/sdb1 /dev/sdd1
  /dev/sdb1: Moved: 0.12%
  /dev/sdb1: Moved: 17.34%
  /dev/sdb1: Moved: 54.81%
  /dev/sdb1: Moved: 100.00%

root@host:~# # now safe to drop the old PV
root@host:~# vgreduce data /dev/sdb1
root@host:~# pvremove /dev/sdb1

Tip

Resumable: pvmove is interruptible. If the host reboots mid-move, run pvmove with no args on the next boot and it picks up where it left off.

Adding a Disk to a VG

The canonical growth workflow: initialise the new disk as a PV, add it to the VG, then extend the LV to consume the new free space (and grow the filesystem).

console
bash
root@host:~# pvcreate /dev/sde
root@host:~# vgextend data /dev/sde
root@host:~# vgs
  VG    #PV #LV #SN Attr   VSize  VFree
  data    3   4   0 wz--n- 5.46t  1.82t

root@host:~# lvextend -r -l +100%FREE /dev/data/mydata

Note

Whole disk vs. partition: using the whole disk (/dev/sde) as a PV is simpler and avoids alignment pitfalls. Partitioning first (/dev/sde1) is needed only if something else will share the disk, or if you want a bootable GPT for BIOS constraints.

Activation & Boot

LVs are inactive until LVM creates the dm nodes for them. On boot, the lvm2-*.service / lvm2-activation-*.service units (invoked by udev and systemd generators) scan available PVs and activate any VG they can assemble.

console
bash
root@host:~# vgchange -a y data                  # activate all LVs in vg "data"
root@host:~# vgchange -a n data                  # deactivate
root@host:~# vgchange --activationmode partial   # activate a degraded RAID LV

/boot on LVM

GRUB2 can read LVM (including thin pools on recent versions) and boot a kernel from an LV. In practice most installers still put /boot on a plain partition — it keeps recovery simple (any Linux live image can mount it), avoids coupling GRUB to LVM quirks, and makes Secure Boot / boot-chain updates easier.

LVM RAID vs. mdadm

LVM RAID and mdadm both sit on the same kernel code — the MD/dm-raid personality. There is no performance or reliability difference. The distinction is operational:

LVM RAID (dm-raid)
  • RAID is an LV type; add/remove members with LVM commands
  • Metadata lives in the VG alongside LV maps
  • Integrates with pvmove, thin pools, caching
  • lvcreate --type raid5 -i 3 -L 300G -n rlv vg
mdadm
  • Standalone subsystem — exposes /dev/mdX
  • Metadata at the start/end of each member (0.90, 1.x)
  • Simpler for plain block-level RAID with no LVM on top
  • Most distro installers use mdadm + LVM (md as a PV)

Traditional (Non-thin) Snapshots

The classic LVM snapshot is a copy-on-write overlay on top of an origin LV. You allocate a separate LV to hold the CoW exception store; LVM intercepts writes to the origin and copies the old block into the snapshot LV before overwriting. Reading from the snapshot returns origin blocks unless they’ve been overwritten — in which case the original value comes from the snapshot’s store.

console
bash
root@host:~# lvcreate -L 10G -s -n mydata_snap /dev/data/mydata
  Logical volume "mydata_snap" created.

root@host:~# mount -o ro /dev/data/mydata_snap /mnt/snap
root@host:~# tar czf /backup/snap.tgz /mnt/snap
root@host:~# umount /mnt/snap && lvremove /dev/data/mydata_snap

Warning

The snapshot LV can fill up. Its size must accommodate every block changed on the origin while the snapshot lives. If the exception store fills, the snapshot becomes invalid and is dropped — any reader of it gets I/O errors. Monitor lvs -o name,snap_percent.

Warning

Performance cost: every origin write now triggers a read-old-block + write-to-snapshot before the actual write. Fine for minutes-to-hours, not for long-lived snapshots. For anything persistent or at scale, use thin snapshots instead.

Common Storage Stacks

LVM plays well with mdadm, LUKS, and anything that looks like a block device. The ordering determines what each layer can do.

Stack A — Classic full-disk encryption with RAID

bash
disks → mdadm (RAID1/5/6) → LUKS → LVM (PV/VG/LVs) → filesystem

• mdadm handles redundancy below encryption (one LUKS unlock for the array)
• LVM carves out volumes inside the encrypted container
• Each filesystem is on its own LV, still shares the single unlock

Stack B — Per-volume encryption

bash
disks → LVM (PV/VG) → LUKS (per LV) → filesystem

• Each LV can be encrypted with its own key
• Resizing is trickier (LUKS header has fixed-size slots; cryptsetup resize required)
• Useful when only some volumes need encryption

Stack C — Proxmox default on a single disk

bash
disk → partitions → LVM-thin pool → raw LVs for VMs → filesystems inside the VM

• /boot on its own partition; root on an LV
• data VG "pve" uses a thin pool; each VM disk is a thin LV in that pool
• Snapshots / clones of VM disks are thin snapshots

Tip

Order matters for flexibility: put LVM above LUKS if you want one unlock to expose many volumes, below LUKS if you want to resize the unlocked container easily. Put LVM above mdadm so the PV is the md device — redundancy stays invisible to LVM.

Solidnines — solidnines.com