NVMe (Non-Volatile Memory Express)

NVMe는 PCIe에 직접 연결된 고속 저장 장치를 위해 설계된 저지연·고병렬 프로토콜이며, Linux에서는 blk-mq와 결합해 높은 IOPS와 낮은 tail latency를 동시에 추구합니다. 이 문서는 NVMe 스펙 변천과 커맨드 세트, 컨트롤러/네임스페이스/큐 구조, PRP/SGL 데이터 경로, 인터럽트·폴링·NUMA 배치 전략, nvme/nvme-core 드라이버 내부 동작, NVMe-oF 전송 계층(TCP/RDMA/FC), ZNS와 고급 기능, 보안·가상화·멀티패스·장애 복구, sysfs/nvme-cli 관측 지표, 워크로드 기반 성능 튜닝과 실전 트러블슈팅까지 운영에 필요한 내용을 end-to-end로 종합적으로 다룹니다.

관련 표준: NVMe Specification 2.1 (NVMe 인터페이스), NVMe-oF Specification 1.1 (Fabrics 전송), NVMe-MI (Management Interface) — 종합 목록은 참고자료 — 표준 & 규격 섹션을 참고하세요.
전제 조건: Block I/O 서브시스템VFS 문서를 먼저 읽으세요. 스토리지 경로는 큐잉, 병합, 플러시 정책이 연쇄적으로 동작하므로, 요청 수명주기와 완료 경로를 먼저 추적해야 합니다.

핵심 요약

  • SQ/CQ 큐 모델 — 호스트와 컨트롤러가 공유 메모리의 원형 버퍼를 통해 커맨드를 교환합니다. 최대 65,535개 I/O 큐 쌍을 지원합니다.
  • Admin Queue + I/O Queue — Admin Queue(SQ0/CQ0)는 컨트롤러 관리 전용이고, I/O Queue는 데이터 읽기/쓰기를 처리합니다.
  • Linux NVMe 드라이버drivers/nvme/host/에 공통(core.c), PCIe(pci.c), RDMA, TCP, FC 전송 계층이 분리되어 있습니다.
  • blk-mq 매핑 — NVMe I/O 큐 쌍이 blk-mq HW 큐에 1:1 매핑되며, blk-mq tag이 NVMe command_id에 직접 대응합니다.
  • NVMe-oF — NVMe 프로토콜을 네트워크(RDMA/TCP/FC)로 확장하여 원격 스토리지에 로컬과 동일한 의미론으로 접근합니다.
  • ZNS — 순차 쓰기 전용 존으로 네임스페이스를 분할하여 SSD 내부 GC를 최소화하고 쓰기 증폭을 줄입니다.

단계별 이해

  1. NVMe 개념 파악 — NVMe가 AHCI/SCSI 대비 어떤 구조적 이점을 가지는지 이해합니다.

    핵심: 다수의 병렬 큐, 간결한 커맨드 셋, PCIe 직결로 인한 저지연.

  2. 큐 모델 이해 — SQ/CQ 원형 버퍼, Doorbell 레지스터, Phase Tag의 동작을 파악합니다.

    nvme list로 시스템의 NVMe 디바이스를 확인하고, nvme id-ctrl로 컨트롤러 정보를 조회합니다.

  3. 드라이버 구조 파악 — Linux NVMe 드라이버의 계층 구조(core/pci/multipath/fabrics)를 이해합니다.

    lsmod | grep nvme로 로드된 NVMe 관련 모듈을 확인합니다.

  4. 관리와 모니터링 — nvme-cli로 SMART 정보를 확인하고, fio로 성능을 벤치마크합니다.

    nvme smart-log /dev/nvme0으로 건강 상태를, iostat -x 1으로 I/O 통계를 모니터링합니다.

NVMe 개요와 스펙 변천

NVMe 등장 배경

NAND 플래시 기반 SSD가 HDD를 대체하면서, 기존 스토리지 인터페이스의 한계가 드러났습니다. AHCI(Advanced Host Controller Interface)는 HDD의 단일 회전 미디어를 위해 설계되어 단 하나의 커맨드 큐(깊이 32)만 지원합니다. SCSI는 다중 큐를 지원하지만, 복잡한 명령 변환 계층(SAM/SPC/SBC)과 프로토콜 오버헤드가 μs 단위 지연이 가능한 플래시 미디어에 병목이 됩니다.

NVMe는 이 문제를 근본적으로 해결합니다:

NVMe 스펙 버전 변천사

버전연도주요 변경사항
1.02011최초 릴리즈. SQ/CQ 큐 모델, PRP, Admin/IO 커맨드 셋 정의
1.12012SGL(Scatter-Gather List) 지원, 다중 네임스페이스, 예약(Reservation)
1.22014네임스페이스 관리(Create/Delete/Attach), End-to-End 데이터 보호
1.32017Sanitize, Boot Partition, Virtualization Enhancements, Device Self-Test
1.42019Multipath 강화(ANA), Persistent Event Log, NVM Sets, Endurance Groups
2.02021커맨드 셋 분리(NVM/ZNS/KV), Rotational Media 지원, I/O Command Set 독립
2.12024Copy Offload 개선, Endurance Group 관리 향상, 다양한 TP(Technical Proposal) 통합

NVMe vs SCSI vs AHCI 비교

항목NVMeSCSI (SAS)AHCI (SATA)
인터페이스PCIe (x4 Gen4 = 8 GB/s)SAS (12 Gb/s = 1.2 GB/s)SATA (6 Gb/s = 600 MB/s)
최대 큐 수65,5351 (태그 큐잉으로 확장)1
큐 깊이65,536254 (TCQ)32 (NCQ)
커맨드 크기64B (고정)16-32B (가변)32B (FIS)
일반 지연10-20 μs50-100 μs50-100 μs
CPU 효율높음 (MMIO 직접)중간 (HBA 변환)낮음 (레거시 PIO/DMA)
멀티코어 확장성우수 (per-CPU 큐)제한적불가

NVMe 하드웨어 아키텍처

M.2 NVMe 폼 팩터

NVMe SSD는 주로 M.2 M-key 폼 팩터로 제공됩니다. M-key 커넥터는 PCIe x4 레인을 지원하여 NVMe 프로토콜의 전체 대역폭을 활용할 수 있습니다.

M.2 폼 팩터 상세: B-key/M-key/B+M-key의 시각적 비교, SATA M.2와 NVMe M.2의 구분법, mSATA 레거시 등 M.2 폼 팩터의 종합적인 내용은 SATA / AHCI — M.2 폼 팩터 섹션을 참고하세요.

SQ/CQ 큐 모델

NVMe 컨트롤러는 PCIe 기반 메모리 매핑 I/O(MMIO)를 사용합니다. 호스트와 컨트롤러는 공유 메모리에 위치한 Submission Queue(SQ)Completion Queue(CQ)를 통해 커맨드를 교환합니다.

Host (CPU / Memory) Admin SQ (SQ0) Admin CQ (CQ0) I/O SQ 1 I/O CQ 1 I/O SQ 2 I/O CQ 2 ⋮ (per-CPU 큐 쌍) I/O SQ n I/O CQ n PRP / SGL (DMA 주소 목록) NVMe Controller Doorbell Registers (BAR0) SQ Tail DB × N | CQ Head DB × N Command Arbitration FTL (Flash Translation Layer) NAND Channel 0 NAND Channel n DRAM Cache / HMB MMIO Doorbell Write DMA CQ Entry + MSI-X

컨트롤러 레지스터와 BAR 레이아웃

NVMe 컨트롤러의 레지스터는 PCIe BAR0에 메모리 매핑됩니다. 핵심 레지스터:

오프셋레지스터크기설명
0x00CAP8BController Capabilities — 최대 큐 엔트리 수(MQES), Doorbell 간격(DSTRD), 타임아웃(TO), 지원 커맨드 셋
0x08VS4BVersion — NVMe 스펙 버전 (예: 0x00010400 = 1.4)
0x0CINTMS4BInterrupt Mask Set — 특정 인터럽트 벡터 비활성화
0x10INTMC4BInterrupt Mask Clear — 인터럽트 벡터 활성화
0x14CC4BController Configuration — EN(활성화), I/O SQE/CQE 크기, 메모리 페이지 크기, 커맨드 셋
0x1CCSTS4BController Status — RDY(준비), CFS(치명적 오류), SHST(셧다운 상태)
0x24AQA4BAdmin Queue Attributes — Admin SQ/CQ 크기
0x28ASQ8BAdmin Submission Queue Base Address
0x30ACQ8BAdmin Completion Queue Base Address
0x1000+SQ/CQ Doorbells4B each각 큐의 Tail(SQ)/Head(CQ) Doorbell. 간격 = 4 << DSTRD
NVMe 컨트롤러 초기화 순서 (nvme_pci_enable) 1단계: CAP 레지스터 읽기 cap = lo_hi_readq(bar + NVME_REG_CAP) q_depth = NVME_CAP_MQES(cap) + 1 db_stride = 1 << NVME_CAP_STRIDE(cap) CAP 레지스터 (64비트) bits[11:0] MQES: 최대 큐 엔트리 수-1 bits[35:32] DSTRD: Doorbell stride bits[47:37] TO: Ready 타임아웃 2단계: Admin Queue 생성 nvme_alloc_queue(dev, 0, NVME_AQ_DEPTH) AQA 레지스터: Admin SQ/CQ 크기 설정 ASQ/ACQ: DMA 주소 등록 Admin Queue (QID=0) ASQ: Admin Submission Queue (64KB) ACQ: Admin Completion Queue Identify/Set Features 명령 사용 3단계: CC 레지스터 설정 (활성화) CC = NVME_CC_ENABLE | NVME_CC_CSS_NVM /* NVM command set */ | NVME_CC_MPS(page_shift) | NVME_CC_ARB_RR | NVME_CC_SHN_NONE CC 레지스터 (Controller Config) EN(bit0): 컨트롤러 Enable CSS: Command Set Selected (NVM) MPS: 메모리 페이지 크기 IOSQES/IOCQES: SQE/CQE 크기(log2) 4단계: CSTS.RDY=1 폴링 대기 (타임아웃: CAP.TO × 500ms) → 초기화 완료

인터럽트 모델 (MSI-X / Polling)

NVMe는 세 가지 인터럽트 전달 방식을 지원합니다:

방식설명지연CPU 사용
MSI-X큐별 독립 인터럽트 벡터. IRQ affinity로 CPU에 분산. 기본 모드~2-5 μs낮음
Polling (io_poll)인터럽트 없이 CPU가 CQ를 직접 확인. nvme.poll_queues로 활성화<1 μs높음 (busy-wait)
Interrupt CoalescingSet Features 0x08로 다수 완료를 모아 단일 인터럽트. 대역폭 최적화설정 의존매우 낮음
/* Interrupt Coalescing 설정 (Set Features) */
/* Aggregation Time: 100μs 간격으로 모아서 통지 */
/* Aggregation Threshold: 최대 8개 CQE 모아서 통지 */
$ nvme set-feature /dev/nvme0 -f 0x08 -v 0x00080064
/*   bits 15:8 = threshold(8), bits 7:0 = time(100 = 100*100μs) */

NVMe 커맨드 구조

SQE/CQE 구조체

모든 NVMe 커맨드는 64바이트 고정 크기의 Submission Queue Entry(SQE)입니다. 완료 엔트리(CQE)는 16바이트입니다.

/* NVMe Submission Queue Entry (64 bytes) — 간략화 */
struct nvme_command {
    __u8    opcode;        /* 커맨드 opcode */
    __u8    flags;         /* FUSE, PSDT 등 */
    __u16   command_id;    /* blk-mq tag과 1:1 매핑 */
    __le32  nsid;          /* 네임스페이스 ID */
    __le64  metadata;      /* 메타데이터 포인터 */
    union nvme_data_ptr dptr;  /* PRP 또는 SGL */
    union {
        struct nvme_rw_command      rw;       /* Read/Write */
        struct nvme_identify        identify; /* Identify */
        struct nvme_features       features; /* Set/Get Features */
        struct nvme_create_cq      create_cq;
        struct nvme_create_sq      create_sq;
        struct nvme_dsm_cmd        dsm;      /* Dataset Management (TRIM) */
        struct nvme_write_zeroes_cmd write_zeroes;
        struct nvme_zone_mgmt_send zms;      /* ZNS 존 관리 */
        /* ... */
    };
};

/* NVMe Completion Queue Entry (16 bytes) */
struct nvme_completion {
    __le32  result;        /* 커맨드별 결과값 */
    __le32  rsvd;
    __le16  sq_head;       /* SQ Head 포인터 (flow control) */
    __le16  sq_id;         /* 이 CQE가 속한 SQ ID */
    __u16   command_id;    /* 완료된 커맨드의 ID */
    __le16  status;        /* 상태 코드 + Phase Tag */
};

Admin 커맨드 세트

Admin 커맨드Opcode설명
Identify0x06컨트롤러/네임스페이스 정보 조회
Create I/O CQ0x05I/O Completion Queue 생성
Create I/O SQ0x01I/O Submission Queue 생성
Delete I/O SQ0x00I/O Submission Queue 삭제
Delete I/O CQ0x04I/O Completion Queue 삭제
Set Features0x09컨트롤러 기능 설정 (큐 수, 인터럽트 합산 등)
Get Features0x0A현재 기능 설정 값 조회
Get Log Page0x02SMART/Health, Error, FW Slot 정보 조회
Async Event Request0x0C비동기 이벤트 통지 등록
Format NVM0x80네임스페이스 포맷 (LBA 크기 변경 등)
Namespace Management0x0D네임스페이스 생성/삭제
Sanitize0x84데이터 완전 삭제 (Block Erase/Crypto Erase/Overwrite)

I/O 커맨드 세트

I/O 커맨드Opcode설명
Read0x02LBA 범위 읽기
Write0x01LBA 범위 쓰기
Flush0x00휘발성 캐시 → 비휘발성 미디어 플러시
Write Zeroes0x08LBA 범위를 0으로 초기화 (데이터 전송 없이)
Dataset Management0x09TRIM/Deallocate — 사용하지 않는 LBA 알림
Compare0x05LBA 데이터와 호스트 데이터 비교
Copy0x19컨트롤러 내부 데이터 복사 (Simple Copy, NVMe 1.4+)
Zone Append0x7DZNS: 존의 WP(Write Pointer)에 데이터 추가

커맨드 실행 흐름

NVMe 커맨드의 전체 실행 사이클:

Host (CPU) NVMe Controller 1. SQE 작성 2. SQ Tail Doorbell 쓰기 (MMIO) 3. SQE DMA Fetch 4. 커맨드 처리 5. 데이터 DMA 6. CQE 기록 + MSI-X 인터럽트 7. CQE 읽기 8. CQ Head Doorbell 쓰기

Linux NVMe 드라이버 아키텍처

드라이버 소스 구조

Linux NVMe 드라이버는 drivers/nvme/ 하위에 세 가지 전송 계층으로 나뉩니다.

디렉토리전송설명
drivers/nvme/host/core.c공통NVMe 프로토콜 로직, 네임스페이스 관리, 에러 처리
drivers/nvme/host/pci.cPCIe로컬 PCIe NVMe 디바이스 드라이버
drivers/nvme/host/rdma.cRDMANVMe-oF RDMA 전송 (InfiniBand, RoCE)
drivers/nvme/host/tcp.cTCPNVMe-oF TCP 전송
drivers/nvme/host/fc.cFCNVMe-oF Fibre Channel 전송
drivers/nvme/host/multipath.c공통다중 경로(multipath) 지원
drivers/nvme/host/hwmon.c공통온도 센서 hwmon 인터페이스
drivers/nvme/host/auth.c공통DH-HMAC-CHAP 인증 (NVMe-oF)
drivers/nvme/target/타겟NVMe-oF 타겟 서브시스템 (스토리지 서버 측)

핵심 구조체

/* drivers/nvme/host/pci.c — PCIe NVMe 드라이버 핵심 구조체 */
struct nvme_dev {
    struct nvme_ctrl      ctrl;        /* 공통 컨트롤러 추상화 */
    struct pci_dev       *pci_dev;     /* PCI 디바이스 */
    void __iomem         *bar;         /* BAR0 MMIO 매핑 (Doorbell 포함) */
    struct nvme_queue    *queues;      /* 큐 배열 [0]=admin, [1..n]=I/O */
    unsigned int          num_vecs;    /* MSI-X 인터럽트 벡터 수 */
    u32                   db_stride;   /* Doorbell 레지스터 간격 */
    struct dma_pool      *prp_page_pool;   /* PRP 리스트 DMA 풀 */
    struct dma_pool      *prp_small_pool;  /* 소규모 PRP DMA 풀 */
};

/* 개별 큐 (Admin 또는 I/O) */
struct nvme_queue {
    struct nvme_dev      *dev;
    struct nvme_command  *sq_cmds;     /* SQ 커맨드 링 버퍼 (DMA) */
    struct nvme_completion *cqes;      /* CQ 엔트리 링 버퍼 (DMA) */
    dma_addr_t            sq_dma_addr; /* SQ의 DMA 주소 */
    dma_addr_t            cq_dma_addr; /* CQ의 DMA 주소 */
    u32 __iomem          *q_db;        /* Doorbell 레지스터 포인터 */
    u32                   q_depth;     /* 큐 깊이 */
    u16                   sq_tail;     /* SQ Tail (호스트 관리) */
    u16                   cq_head;     /* CQ Head (호스트 관리) */
    u16                   qid;         /* 큐 ID (0=admin) */
    u8                    cq_phase;    /* Phase Tag (새 CQE 감지) */
};

PCIe NVMe 초기화 흐름

nvme_probe()에서 시작하는 PCIe NVMe 디바이스 초기화 과정:

  1. PCI 디바이스 활성화pci_enable_device_mem(), 버스 마스터 설정, BAR0 MMIO 매핑
  2. CAP 레지스터 읽기 — 최대 큐 엔트리(MQES), Doorbell 간격(DSTRD), 메모리 페이지 크기 범위
  3. Admin Queue 생성 — DMA로 Admin SQ/CQ 할당, AQA/ASQ/ACQ 레지스터에 주소 기록
  4. CC.EN=1 설정 — 컨트롤러 활성화, CSTS.RDY=1 대기 (타임아웃 = CAP.TO × 500ms)
  5. Identify Controller — Admin Queue로 Identify 커맨드 발행, 컨트롤러 정보(MQES, MDTS, NN 등) 수집
  6. Set Features — Number of Queues(0x07)로 I/O 큐 수 요청, 인터럽트 합산 설정
  7. MSI-X 벡터 할당pci_alloc_irq_vectors()로 per-queue 인터럽트 설정
  8. I/O Queue 생성 — Create I/O CQ → Create I/O SQ (각 큐에 MSI-X 벡터 할당)
  9. 네임스페이스 스캔 — Identify Namespace List로 네임스페이스 검색, gendisk 등록
/* drivers/nvme/host/pci.c — 초기화 핵심 경로 */
static int nvme_probe(struct pci_dev *pdev,
                      const struct pci_device_id *id)
{
    nvme_dev_map(dev);              /* BAR0 MMIO 매핑 */
    nvme_configure_admin_queue(dev); /* Admin Queue 생성 + CC.EN=1 */
    nvme_init_ctrl_finish(&dev->ctrl); /* Identify Controller */
    nvme_setup_io_queues(dev);      /* I/O Queue 생성 */
    nvme_start_ctrl(&dev->ctrl);    /* 네임스페이스 스캔 시작 */
}

컨트롤러 상태 머신

NVMe 컨트롤러는 커널 내부에서 상태 머신으로 관리됩니다:

/* include/linux/nvme.h */
enum nvme_ctrl_state {
    NVME_CTRL_NEW,        /* 초기 상태, 프로브 중 */
    NVME_CTRL_LIVE,       /* 정상 동작 중, I/O 가능 */
    NVME_CTRL_RESETTING,  /* 컨트롤러 리셋 진행 중 */
    NVME_CTRL_CONNECTING, /* Fabrics: 재연결 진행 중 */
    NVME_CTRL_DELETING,   /* 디바이스 제거 진행 중 */
    NVME_CTRL_DEAD,       /* 복구 불가능, 모든 I/O 실패 */
};
NEW LIVE RESETTING CONNECTING DELETING DEAD remove

NVMe와 blk-mq 매핑

Linux NVMe 드라이버는 blk-mq(Multi-Queue Block Layer)의 가장 직접적인 사용자입니다. NVMe 하드웨어 큐(SQ/CQ)가 blk-mq의 하드웨어 디스패치 큐에 1:1로 매핑되어, CPU별 독립 I/O 경로를 구성합니다.

blk_mq_ops 콜백

NVMe 드라이버는 blk_mq_ops 구조체를 통해 블록 계층과 인터페이스합니다:

static const struct blk_mq_ops nvme_mq_ops = {
    .queue_rq       = nvme_queue_rq,      /* I/O 제출 */
    .complete       = nvme_pci_complete_rq, /* 완료 처리 */
    .commit_rqs     = nvme_commit_rqs,    /* 배치 커밋 */
    .init_hctx      = nvme_init_hctx,     /* HW 큐 초기화 */
    .init_request   = nvme_pci_init_request,
    .map_queues     = nvme_pci_map_queues, /* 큐 매핑 */
    .timeout        = nvme_timeout,       /* 타임아웃 처리 */
    .poll           = nvme_poll,          /* 폴링 I/O */
};

핵심 함수 nvme_queue_rq()의 처리 흐름:

  1. blk_mq_rq_to_pdu()로 request에서 NVMe 커맨드 구조체 추출
  2. nvme_setup_cmd()로 블록 요청을 NVMe SQE로 변환
  3. PRP/SGL 매핑: nvme_map_data()로 scatter-gather 리스트 구성
  4. Doorbell 레지스터에 SQ Tail 기록하여 커맨드 제출
blk-mq ↔ NVMe 큐 매핑 blk-mq (소프트웨어) SW Queue (CPU 0) SW Queue (CPU 1) SW Queue (CPU 2) SW Queue (CPU 3) HW Dispatch Queue hctx[0] (default) hctx[1] (default) hctx[2] (read) hctx[3] (poll) NVMe 하드웨어 큐 SQ/CQ Pair 1 SQ/CQ Pair 2 SQ/CQ Pair 3 (read) SQ/CQ Pair 4 (poll) map_queues() 1:1 매핑

큐 타입 분리 (default / read / poll)

커널 5.12+에서 NVMe 드라이버는 I/O 특성에 따라 큐를 분리합니다:

큐 타입blk-mq 매핑용도인터럽트
defaultHCTX_TYPE_DEFAULT일반 쓰기 + 혼합 I/OMSI-X
readHCTX_TYPE_READ읽기 전용 I/O (선택적)MSI-X
pollHCTX_TYPE_POLLio_uring 폴링 I/O인터럽트 없음
/* drivers/nvme/host/pci.c — 큐 매핑 */
static void nvme_pci_map_queues(struct blk_mq_tag_set *set)
{
    struct nvme_dev *dev = set->driver_data;

    /* default 큐: 모든 CPU에 매핑 */
    blk_mq_pci_map_queues(&set->map[HCTX_TYPE_DEFAULT],
                          to_pci_dev(dev->dev), 0);

    /* read 큐: 별도 큐 세트 (옵션) */
    if (dev->io_queues[HCTX_TYPE_READ])
        blk_mq_pci_map_queues(&set->map[HCTX_TYPE_READ],
                              to_pci_dev(dev->dev),
                              dev->io_queues[HCTX_TYPE_DEFAULT]);

    /* poll 큐: 인터럽트 없이 직접 폴링 */
    if (dev->io_queues[HCTX_TYPE_POLL])
        blk_mq_map_queues(&set->map[HCTX_TYPE_POLL]);
}
💡

poll 큐: io_uring에서 IORING_SETUP_IOPOLL 플래그로 서브미션하면, 인터럽트 없이 CQ를 직접 폴링하여 μs 단위 지연을 달성합니다. 고성능 워크로드에서 인터럽트 오버헤드를 제거합니다.

배치 제출과 Doorbell 최적화

NVMe 드라이버는 여러 커맨드를 모아서 한 번의 Doorbell 기록으로 제출하는 배치 최적화를 수행합니다:

/* commit_rqs: 배치된 커맨드를 한 번에 제출 */
static void nvme_commit_rqs(struct blk_mq_hw_ctx *hctx)
{
    struct nvme_queue *nvmeq = hctx->driver_data;

    spin_lock(&nvmeq->sq_lock);
    if (nvmeq->sq_tail != nvmeq->last_sq_tail) {
        nvme_write_sq_db(nvmeq, true);  /* Doorbell 1회 기록 */
    }
    spin_unlock(&nvmeq->sq_lock);
}

Shadow Doorbell: NVMe 1.3+의 Shadow Doorbell Buffer 기능을 사용하면, 호스트가 MMIO 대신 메모리에 Tail 포인터를 기록하고 컨트롤러가 이를 읽어갑니다. 이는 MMIO 기록 비용(수백 ns)을 절약합니다:

/* Shadow Doorbell: MMIO 대신 메모리 기록 */
static void nvme_write_sq_db(struct nvme_queue *nvmeq, bool write_sq)
{
    if (!nvmeq->sq_doorbell_addr) {  /* Shadow Doorbell 미지원 */
        writel(nvmeq->sq_tail, nvmeq->q_db);
    } else {
        /* Shadow Doorbell: 메모리 기록 후 필요시에만 MMIO */
        WRITE_ONCE(*nvmeq->sq_doorbell_addr, nvmeq->sq_tail);
        mb();
        if (nvme_need_event(*nvmeq->sq_eventidx_addr, nvmeq->sq_tail,
                           nvmeq->last_sq_tail))
            writel(nvmeq->sq_tail, nvmeq->q_db);
    }
    nvmeq->last_sq_tail = nvmeq->sq_tail;
}

NVMe 네임스페이스

NVMe 네임스페이스(Namespace)는 논리적 블록 주소(LBA) 공간을 분할하여 독립적인 저장 단위를 구성합니다. 하나의 NVMe 컨트롤러가 여러 네임스페이스를 관리할 수 있으며, 각 네임스페이스는 별도의 블록 디바이스(/dev/nvmeXnY)로 노출됩니다.

네임스페이스 개념과 디바이스 노드

/* include/linux/nvme.h — 네임스페이스 식별 */
struct nvme_ns {
    struct list_head    list;          /* 컨트롤러의 ns 리스트 */
    struct nvme_ctrl    *ctrl;
    struct request_queue *queue;
    struct gendisk     *disk;
    struct nvme_ns_head *head;         /* multipath head */
    unsigned            ns_id;         /* NSID: 1-based */
    u8                  lba_shift;     /* log2(LBA 크기) */
    u16                 ms;            /* 메타데이터 크기 */
    bool                ext;           /* 확장 LBA 모드 */
};
디바이스 노드설명예시
/dev/nvme0컨트롤러 캐릭터 디바이스Admin 커맨드 passthrough
/dev/nvme0n1네임스페이스 1 블록 디바이스파일시스템 마운트
/dev/nvme0n1p1네임스페이스 1의 파티션 1GPT/MBR 파티션
/dev/ng0n1네임스페이스 1 캐릭터 디바이스I/O passthrough (커널 5.13+)

네임스페이스 관리 (Create / Delete / Attach)

NVMe 1.2+에서 네임스페이스를 동적으로 생성·삭제·연결할 수 있습니다:

# 네임스페이스 생성 (10GB, 4K 블록)
$ nvme create-ns /dev/nvme0 --nsze=2621440 --ncap=2621440 --block-size=4096

# 컨트롤러에 네임스페이스 연결 (attach)
$ nvme attach-ns /dev/nvme0 --namespace-id=2 --controllers=0x41

# 네임스페이스 분리 (detach)
$ nvme detach-ns /dev/nvme0 --namespace-id=2 --controllers=0x41

# 네임스페이스 삭제
$ nvme delete-ns /dev/nvme0 --namespace-id=2

# 현재 네임스페이스 목록 확인
$ nvme list-ns /dev/nvme0
$ nvme id-ns /dev/nvme0n1
⚠️

주의: 네임스페이스 관리 기능은 모든 NVMe 드라이브에서 지원되지 않습니다. nvme id-ctrl /dev/nvme0 | grep oacs로 OACS(Optional Admin Command Support) 비트를 확인하세요. 비트 3이 설정되어야 NS 관리를 지원합니다.

PRP과 SGL

NVMe는 호스트 메모리와 컨트롤러 간의 데이터 전송 주소를 지정하는 두 가지 메커니즘을 제공합니다:

특성PRP (Physical Region Page)SGL (Scatter Gather List)
주소 지정페이지 단위 (4KB 정렬)임의 오프셋 + 길이
최소 단위메모리 페이지1바이트
연쇄PRP List (물리 페이지 배열)SGL Segment (다음 SGL 포인터)
지원NVMe 1.0+ (필수)NVMe 1.1+ (선택, 점차 필수화)
NVMe-oF미사용SGL 필수
커널 사용PCIe 기본NVMe-oF, CMB 전송
/* PRP 구조: SQE의 dptr 필드 */
struct nvme_common_command {
    ...
    union nvme_data_ptr {
        struct {
            __le64 prp1;     /* 첫 번째 PRP 엔트리 */
            __le64 prp2;     /* 두 번째 또는 PRP List 주소 */
        };
        struct nvme_sgl_desc sgl;  /* SGL 디스크립터 */
    } dptr;
};

/* PRP 매핑 로직 */
/*  데이터 ≤ 1 페이지: prp1만 사용
 *  데이터 ≤ 2 페이지: prp1 + prp2
 *  데이터 > 2 페이지: prp1 + prp2(→PRP List)
 */
/* SGL 디스크립터 (16바이트) */
struct nvme_sgl_desc {
    __le64 addr;       /* 데이터/세그먼트 주소 */
    __le32 length;     /* 바이트 길이 */
    __u8   rsvd[3];
    __u8   type;       /* SGL 타입 + 서브타입 */
};

/* SGL 타입:
 *  0x00 — Data Block (데이터가 여기에)
 *  0x01 — Bit Bucket (데이터 버림)
 *  0x02 — Segment (다음 SGL 디스크립터 체인)
 *  0x03 — Last Segment (마지막 세그먼트)
 *  0x04 — Keyed Data Block (NVMe-oF RDMA용)
 */

CMB, PMR, HMB

NVMe 스펙은 호스트와 컨트롤러 간 메모리 공유 메커니즘을 정의하여 데이터 경로를 최적화합니다.

CMB (Controller Memory Buffer)

CMB는 컨트롤러의 PCIe BAR 공간에 위치한 메모리로, 호스트가 직접 접근할 수 있습니다. SQ를 CMB에 배치하면 컨트롤러가 DMA로 SQE를 가져올 필요 없이 직접 읽을 수 있어 지연이 감소합니다.

/* drivers/nvme/host/pci.c — CMB 매핑 */
static void nvme_map_cmb(struct nvme_dev *dev)
{
    u64 szu, size, offset;
    resource_size_t bar_size;
    struct pci_dev *pdev = to_pci_dev(dev->dev);

    /* CMBSZ 레지스터에서 CMB 크기 확인 */
    dev->cmbsz = readl(dev->bar + NVME_REG_CMBSZ);
    if (!dev->cmbsz)
        return;

    /* CMB가 SQ 배치를 지원하는지 확인 (SQS 비트) */
    if (!(dev->cmbsz & NVME_CMBSZ_SQS))
        return;

    /* PCIe BAR에 CMB 매핑 */
    dev->cmb = pci_iomap_wc(pdev, bar, size);
}
CMB 용도지원 비트 (CMBSZ)설명
SQ 배치SQSSubmission Queue를 CMB에 할당
CQ 배치CQSCompletion Queue를 CMB에 할당
PRP ListLISTSPRP/SGL 리스트를 CMB에 배치
읽기 데이터RDS읽기 데이터 버퍼
쓰기 데이터WDS쓰기 데이터 버퍼

PMR (Persistent Memory Region)

PMR은 NVMe 1.4에서 도입된 비휘발성 메모리 영역입니다. CMB와 달리 전원이 꺼져도 데이터가 유지되어, 저널링이나 메타데이터 캐싱에 활용할 수 있습니다:

/* PMR 초기화 (drivers/nvme/host/pci.c) */
static void nvme_map_pmr(struct nvme_dev *dev)
{
    u32 pmrcap = readl(dev->bar + NVME_REG_PMRCAP);

    /* PMR 크기와 속성 확인 */
    dev->pmr_size = nvme_pmr_size(dev);
    dev->pmr = pci_iomap_wc(pdev, pmr_bar, dev->pmr_size);

    /* DAX (Direct Access) 지원: 파일시스템이 직접 접근 */
    dev->pmr_dax = dax_alloc(dev->pmr, dev->pmr_size);
}

HMB (Host Memory Buffer)

HMB는 CMB가 없는 저가형 NVMe 장치에서 호스트 DRAM의 일부를 컨트롤러가 캐시처럼 사용하는 메커니즘입니다. 주로 M.2 NVMe SSD에서 DRAM 없이 성능을 유지하기 위해 사용됩니다:

/* HMB 활성화 (drivers/nvme/host/core.c) */
static int nvme_setup_host_mem(struct nvme_dev *dev)
{
    u64 preferred = le32_to_cpu(id->hmpre) * 4096ULL;
    u64 min       = le32_to_cpu(id->hmmin) * 4096ULL;

    /* 호스트 메모리 할당 (Scatter 방식) */
    dev->host_mem_descs = nvme_alloc_host_mem(dev, preferred);

    /* Set Features로 HMB 활성화 */
    nvme_set_host_mem(dev, 1);  /* enable=1 */
}
ℹ️

HMB 크기: 일반적으로 64MB~256MB의 호스트 메모리를 사용합니다. nvme id-ctrl /dev/nvme0 | grep -E "hmpre|hmmin"으로 선호/최소 크기를 확인할 수 있습니다. dmesg | grep hmb로 실제 할당 크기를 확인합니다.

NVMe 전원 관리

NVMe 장치는 다중 전원 상태(Power State)를 지원하며, 호스트와 컨트롤러가 협력하여 성능과 전력 소비 사이의 균형을 조절합니다.

전원 상태 (PS0 ~ PS5)

NVMe 컨트롤러는 최대 32개의 전원 상태를 정의할 수 있습니다. 각 상태는 최대 전력 소비, 진입/탈출 지연 시간을 명시합니다:

상태분류전력 (일반적)진입 지연탈출 지연설명
PS0Operational~10W최대 성능
PS1Operational~5W~5μs~10μs약간 낮은 성능
PS2Operational~3W~50μs~50μs중간 성능
PS3Non-Operational~50mW~5ms~10ms유휴 절전
PS4Non-Operational~5mW~50ms~200ms깊은 절전
PS5Non-Operational~2mW~500ms~1s최저 전력
# 전원 상태 확인
$ nvme id-ctrl /dev/nvme0 -H | grep -A 5 "ps "
$ nvme get-feature /dev/nvme0 -f 0x02 -H   # Power Management 기능

# 수동 전원 상태 전환
$ nvme set-feature /dev/nvme0 -f 0x02 -v 3  # PS3으로 전환

APST (Autonomous Power State Transition)

APST는 컨트롤러가 유휴 시간을 감지하여 자동으로 낮은 전원 상태로 전환하는 메커니즘입니다. 리눅스 커널은 기본적으로 APST를 활성화합니다:

/* drivers/nvme/host/core.c — APST 설정 */
static void nvme_configure_apst(struct nvme_ctrl *ctrl)
{
    struct nvme_feat_auto_pst *table;
    u64 target = ctrl->ps_max_latency_us;

    /* 각 전원 상태에 대해 유휴 전환 시간 설정 */
    for (int state = ctrl->npss; state >= 0; state--) {
        if (total_latency_us > target)
            continue;
        table->entries[state] = cpu_to_le64(
            (idle_time_ms << 3) | 1  /* ITPT | ITPS */
        );
    }

    /* Set Features (0x0C) 커맨드로 APST 테이블 전송 */
    nvme_set_features(ctrl, NVME_FEAT_AUTO_PST, 1, table, ...);
}
# APST 설정 확인
$ nvme get-feature /dev/nvme0 -f 0x0c -H

# APST 비활성화 (디버깅/벤치마킹용)
$ echo 0 | tee /sys/class/nvme/nvme0/power/ps_max_latency_us
# 또는 커널 파라미터: nvme_core.default_ps_max_latency_us=0

Suspend / Resume 통합

시스템 Suspend 시 NVMe 컨트롤러는 올바른 종료 절차를 수행합니다:

/* 시스템 Suspend 시퀀스 */
nvme_dev_disable()       /* 1. I/O 큐 중지 */nvme_quiesce_io_queues() /* 2. 진행 중인 I/O 대기 */nvme_wait_freeze()   /* 3. 큐 동결 */nvme_disable_ctrl()  /* 4. CC.EN=0 → 컨트롤러 비활성화 */

/* 시스템 Resume 시퀀스 */
nvme_reset_ctrl()        /* 1. 컨트롤러 리셋 */nvme_pci_enable()    /* 2. PCIe 재활성화 */nvme_pci_configure_admin_queue() /* 3. Admin 큐 복원 */nvme_init_ctrl_finish()  /* 4. Identify 재실행 */nvme_create_io_queues()  /* 5. I/O 큐 재생성 */
💡

Simple Suspend: 커널 5.14+에서 nvme_core.noacpi=1 대신 NVMe 자체의 Simple Suspend를 사용합니다. 이는 ACPI StorageD3Enable 속성을 확인하여, 지원되는 경우 완전한 전원 차단 없이 빠른 Suspend/Resume을 수행합니다.

NVMe 열 관리

NVMe 컨트롤러는 내장 온도 센서와 열 관리 메커니즘을 통해 과열로 인한 데이터 손실이나 하드웨어 손상을 방지합니다.

온도 임계값 (WCTEMP, CCTEMP, TMT1/TMT2)

임계값설명동작
WCTEMPWarning Composite Temperature비동기 이벤트(AER) 알림 발생
CCTEMPCritical Composite Temperature강제 스로틀링 또는 셧다운
TMT1Thermal Management Temp 1가벼운 스로틀링 시작
TMT2Thermal Management Temp 2강한 스로틀링 시작
# 현재 온도 및 임계값 확인
$ nvme smart-log /dev/nvme0 | grep -i temp
temperature                         : 42°C
warning_temp_time                   : 0
critical_comp_time                  : 0

$ nvme id-ctrl /dev/nvme0 | grep -i temp
wctemp  : 358       # Warning: 85°C (켈빈 → 섭씨: 358-273=85)
cctemp  : 368       # Critical: 95°C

HCTMA (Host Controlled Thermal Management)

NVMe 1.3+에서 호스트가 TMT1/TMT2 임계값을 설정하여 컨트롤러의 스로틀링 시점을 제어할 수 있습니다:

/* drivers/nvme/host/hwmon.c — 열 관리 통합 */
static int nvme_hwmon_write(struct device *dev, u32 attr,
                            int channel, long val)
{
    /* 온도 임계값 설정 (밀리켈빈 → 켈빈) */
    temp = millikelvin_to_kelvin(val);

    /* Set Features: Thermal Management */
    nvme_set_features(ctrl, NVME_FEAT_TEMP_THRESH,
                      temp | (threshold_type << 20),
                      NULL, ...);
}

스로틀링 모니터링

리눅스 커널은 NVMe 온도를 hwmon 서브시스템에 통합하여 표준 도구로 모니터링할 수 있습니다:

# hwmon 인터페이스로 온도 모니터링
$ cat /sys/class/nvme/nvme0/hwmon*/temp1_input    # 현재 온도 (밀리섭씨)
42000
$ cat /sys/class/nvme/nvme0/hwmon*/temp1_max      # WCTEMP
85000
$ cat /sys/class/nvme/nvme0/hwmon*/temp1_crit     # CCTEMP
95000

# 센서별 온도 (NVMe 1.4+: 복합 + 개별 센서 최대 8개)
$ sensors nvme-pci-*
nvme-pci-0100
Adapter: PCI adapter
Composite:    +42.0°C  (high = +85.0°C, crit = +95.0°C)
Sensor 1:     +42.0°C  (low  =  -5.0°C, high = +80.0°C)
Sensor 2:     +38.0°C  (low  =  -5.0°C, high = +80.0°C)

# SMART 로그의 열 관리 통계
$ nvme smart-log /dev/nvme0 | grep -E "thm_temp|thermal"
thm_temp1_trans_count               : 5     # TMT1 전환 횟수
thm_temp2_trans_count               : 0     # TMT2 전환 횟수
thm_temp1_total_time                : 120   # TMT1 총 시간 (초)
thm_temp2_total_time                : 0

NVMe Multipath

NVMe Multipath는 하나의 네임스페이스에 여러 경로(path)를 제공하여 고가용성과 부하 분산을 구현합니다. 주로 NVMe-oF 환경이나 듀얼 포트 NVMe SSD에서 활용됩니다.

경로 상태와 I/O 정책

커널의 네이티브 NVMe Multipath는 nvme_ns_head 구조체를 통해 여러 경로의 nvme_ns를 하나의 가상 디바이스(/dev/nvmeXcYnZ)로 통합합니다:

/* NVMe Multipath I/O 정책 */
enum nvme_io_policy {
    NVME_IOPOLICY_NUMA,    /* NUMA 노드 기반 (기본값) */
    NVME_IOPOLICY_RR,      /* 라운드 로빈 */
    NVME_IOPOLICY_QD,      /* Queue Depth 기반 (커널 6.3+) */
};
# Multipath 활성화 확인
$ cat /sys/module/nvme_core/parameters/multipath
Y

# I/O 정책 변경
$ echo numa > /sys/module/nvme_core/parameters/iopolicy
$ echo round-robin > /sys/module/nvme_core/parameters/iopolicy
$ echo queue-depth > /sys/module/nvme_core/parameters/iopolicy

# 경로 상태 확인
$ nvme list-subsys /dev/nvme0n1
nvme-subsys0 - NQN=nqn.2024-01.com.example:nvme
\
 +- nvme0 tcp traddr=192.168.1.10,trsvcid=4420 live optimized
 +- nvme1 tcp traddr=192.168.1.11,trsvcid=4420 live non-optimized

ANA (Asymmetric Namespace Access)

ANA는 NVMe 1.4에서 도입된 비대칭 접근 메커니즘으로, SCSI ALUA의 NVMe 대응물입니다. 각 컨트롤러-네임스페이스 쌍에 대해 접근 상태를 정의합니다:

ANA 상태설명I/O 허용
Optimized최적 경로 (가장 낮은 지연)읽기/쓰기
Non-optimized동작하지만 비최적 경로읽기/쓰기
Inaccessible접근 불가 (장애 대기)불가
Persistent Loss영구적 경로 손실불가
Change상태 전환 중재시도
/* drivers/nvme/host/multipath.c — ANA 경로 선택 */
static struct nvme_ns *nvme_find_path(struct nvme_ns_head *head)
{
    /* NUMA 정책: 같은 NUMA 노드의 optimized 경로 우선 */
    test_and_clear_bit(NVME_NSHEAD_DISK_LIVE, &head->flags);
    list_for_each_entry_rcu(ns, &head->list, siblings) {
        if (nvme_path_is_optimized(ns)) {
            if (ns->ctrl->numa_node == numa_node)
                return ns;  /* 최적 경로 */
            fallback = ns;
        }
    }
    return fallback;
}
ℹ️

dm-multipath vs 네이티브: 리눅스는 NVMe 전용 네이티브 멀티패스(nvme_core.multipath=Y)와 범용 dm-multipath를 모두 지원합니다. 네이티브 방식이 오버헤드가 낮고 ANA를 직접 지원하므로 권장됩니다.

NVMe over Fabrics (NVMe-oF)

NVMe-oF는 NVMe 프로토콜을 네트워크 패브릭(RDMA, TCP, FC)을 통해 확장하여, 원격 NVMe 장치를 로컬처럼 사용할 수 있게 합니다. 전통적인 iSCSI/FC보다 낮은 지연과 높은 대역폭을 제공합니다.

NVMe-oF 아키텍처 개요

NVMe-oF 아키텍처 Host (Initiator) Application / Filesystem Block Layer (blk-mq) nvme-fabrics TCP RDMA FC Network Stack / HCA / FC HBA Target (Subsystem) NVMe Devices (Physical/Virtual) Block Layer / bdev nvmet (Target Core) TCP RDMA FC Network Stack / HCA / FC HBA NVMe Commands Completions

타겟 구성 (nvmet)

리눅스 커널의 nvmet 모듈은 ConfigFS를 통해 NVMe-oF 타겟을 구성합니다:

# 커널 모듈 로드
$ modprobe nvmet
$ modprobe nvmet-tcp   # TCP 전송용

# 서브시스템 생성
$ mkdir -p /sys/kernel/config/nvmet/subsystems/nqn.2024-01.com.example:nvme-target
$ cd /sys/kernel/config/nvmet/subsystems/nqn.2024-01.com.example:nvme-target
$ echo 1 > attr_allow_any_host

# 네임스페이스 추가 (기존 블록 디바이스 노출)
$ mkdir namespaces/1
$ echo /dev/nvme0n1 > namespaces/1/device_path
$ echo 1 > namespaces/1/enable

# TCP 포트 생성 및 바인딩
$ mkdir -p /sys/kernel/config/nvmet/ports/1
$ echo ipv4 > /sys/kernel/config/nvmet/ports/1/addr_adrfam
$ echo 0.0.0.0 > /sys/kernel/config/nvmet/ports/1/addr_traddr
$ echo 4420 > /sys/kernel/config/nvmet/ports/1/addr_trsvcid
$ echo tcp > /sys/kernel/config/nvmet/ports/1/addr_trtype

# 서브시스템을 포트에 연결
$ ln -s /sys/kernel/config/nvmet/subsystems/nqn.2024-01.com.example:nvme-target \
        /sys/kernel/config/nvmet/ports/1/subsystems/

호스트 연결

# 호스트 모듈 로드
$ modprobe nvme-tcp

# Discovery Controller를 통한 서브시스템 탐색
$ nvme discover -t tcp -a 192.168.1.100 -s 8009

# 직접 연결
$ nvme connect -t tcp -n nqn.2024-01.com.example:nvme-target \
    -a 192.168.1.100 -s 4420

# 연결 확인
$ nvme list-subsys
$ lsblk

# 연결 해제
$ nvme disconnect -n nqn.2024-01.com.example:nvme-target

Discovery Controller와 Subsystem 모델

NVMe-oF는 Discovery Controller를 통해 사용 가능한 서브시스템을 동적으로 탐색합니다:

# Discovery Controller 설정 (타겟 측)
$ mkdir -p /sys/kernel/config/nvmet/ports/2
$ echo tcp > /sys/kernel/config/nvmet/ports/2/addr_trtype
$ echo 0.0.0.0 > /sys/kernel/config/nvmet/ports/2/addr_traddr
$ echo 8009 > /sys/kernel/config/nvmet/ports/2/addr_trsvcid
$ echo ipv4 > /sys/kernel/config/nvmet/ports/2/addr_adrfam

# 호스트에서 자동 연결 (udev + systemd)
$ nvme connect-all -t tcp -a 192.168.1.100 -s 8009

TCP PDU 구조와 TLS 지원

NVMe/TCP는 자체 PDU(Protocol Data Unit) 형식을 정의하여 TCP 스트림 위에서 NVMe 커맨드/데이터를 캡슐화합니다:

PDU 타입방향설명
ICReqHost → Target초기화 연결 요청
ICRespTarget → Host초기화 연결 응답
CapsuleCmdHost → TargetNVMe 커맨드 + 인라인 데이터
CapsuleRespTarget → HostNVMe 완료 응답
H2CDataHost → Target호스트→타겟 데이터 전송
C2HDataTarget → Host타겟→호스트 데이터 전송
R2TTarget → Host데이터 전송 요청
/* include/linux/nvme-tcp.h — TCP PDU 헤더 */
struct nvme_tcp_hdr {
    __u8    type;       /* PDU 타입 */
    __u8    flags;      /* HDGSTF, DDGSTF */
    __u8    hlen;       /* PDU 헤더 길이 */
    __u8    pdo;        /* 데이터 오프셋 (정렬) */
    __le32  plen;       /* 전체 PDU 길이 */
};

TLS 1.3 지원 (커널 6.7+): NVMe/TCP에 커널 TLS를 적용하여 전송 암호화를 제공합니다. nvme connect--tls 옵션으로 활성화합니다.

RDMA 전송 심화

NVMe/RDMA는 커널 바이패스를 통해 최저 지연을 달성합니다. RDMA 전송의 특징:

# RDMA 전송으로 NVMe-oF 연결
$ modprobe nvme-rdma

# 타겟 설정
$ echo rdma > /sys/kernel/config/nvmet/ports/1/addr_trtype
$ echo 192.168.1.100 > /sys/kernel/config/nvmet/ports/1/addr_traddr
$ echo 4420 > /sys/kernel/config/nvmet/ports/1/addr_trsvcid

# 호스트 연결
$ nvme connect -t rdma -n nqn.2024-01.com.example:nvme-target \
    -a 192.168.1.100 -s 4420

ZNS (Zoned Namespaces)

ZNS는 NVMe 2.0에서 정의된 존(Zone) 기반 스토리지 인터페이스입니다. SSD 내부의 순차 쓰기 특성을 호스트에 노출하여, FTL(Flash Translation Layer) 복잡성을 줄이고 쓰기 증폭(WAF)을 최소화합니다.

존 상태 모델

상태설명허용 명령
Empty빈 존, 쓰기 포인터 = 시작Write, Zone Append
Implicitly Open쓰기 시 자동 오픈Write, Zone Append, Close
Explicitly Open호스트가 명시적 오픈Write, Zone Append, Close
Closed닫힘, 쓰기 포인터 유지Open, Write
Full존이 가득 참Reset, Finish
Read Only읽기만 가능Read
Offline접근 불가없음
# ZNS 디바이스 확인
$ cat /sys/block/nvme0n1/queue/zoned
host-managed

# 존 정보 조회
$ nvme zns report-zones /dev/nvme0n1 --descs=16
SLBA: 0x000000  WP: 0x000000  Cap: 0x040000  State: EMPTY   Type: SWR
SLBA: 0x040000  WP: 0x041000  Cap: 0x040000  State: IMP_OPEN Type: SWR
SLBA: 0x080000  WP: 0x0c0000  Cap: 0x040000  State: FULL    Type: SWR

# 존 관리
$ nvme zns open-zone /dev/nvme0n1 -s 0         # 존 열기
$ nvme zns close-zone /dev/nvme0n1 -s 0        # 존 닫기
$ nvme zns reset-zone /dev/nvme0n1 -s 0x40000  # 존 리셋
$ nvme zns finish-zone /dev/nvme0n1 -a 1       # 모든 존 완료

블록 계층 연동

리눅스 블록 계층은 ZNS 디바이스를 위한 전용 인터페이스를 제공합니다:

/* ZNS 지원 파일시스템 */
/* Btrfs: 커널 5.12+에서 ZNS 직접 지원 */
$ mkfs.btrfs -m single -d single /dev/nvme0n1
$ mount -o zoned /dev/nvme0n1 /mnt/zns

/* F2FS: ZNS 네이티브 지원 */
$ mkfs.f2fs -m /dev/nvme0n1

/* dm-zoned: 일반 파일시스템 호환 계층 */
$ dmzadm --format /dev/nvme0n1 /dev/sda  # ZNS + 일반 디스크 조합

NVMe 고급 커맨드 세트

NVMe 스펙은 기본 Block I/O 외에 다양한 고급 커맨드 세트를 정의하여, 스토리지 활용의 유연성을 극대화합니다.

Key Value (KV) 커맨드 세트

NVMe KV 커맨드 세트는 전통적인 블록 주소(LBA) 대신 키-값(Key-Value) 쌍으로 데이터를 저장·검색합니다. 데이터베이스나 오브젝트 스토리지에서 FTL과 파일시스템 오버헤드를 제거합니다:

커맨드Opcode설명
Store0x01키에 값 저장
Retrieve0x02키로 값 조회
Delete0x10키-값 쌍 삭제
Exist0x14키 존재 여부 확인
List0x06키 목록 나열
/* KV SQE 구조 (간략화) */
struct nvme_kv_command {
    __u8    opcode;        /* KV 명령 코드 */
    __u8    flags;
    __u16   command_id;
    __le32  nsid;
    __le32  key_length;    /* 키 길이 (1~16 바이트) */
    __le32  value_size;    /* 값 크기 */
    __u8    key[16];       /* 인라인 키 */
    union nvme_data_ptr dptr;  /* 값 데이터 포인터 */
};

Computational Storage

NVMe Computational Storage는 스토리지 장치 내에서 연산을 수행하는 TP 4091 스펙입니다. 호스트-디바이스 간 데이터 이동을 줄여 대규모 데이터 처리 효율을 높입니다:

Copy Offload (Simple Copy)

NVMe 1.4의 Simple Copy 커맨드는 호스트를 거치지 않고 컨트롤러 내부에서 데이터를 복사합니다:

/* Simple Copy 커맨드 (Opcode 0x19) */
struct nvme_copy_command {
    __u8    opcode;        /* 0x19 */
    __u8    flags;
    __u16   command_id;
    __le32  nsid;
    __le64  sdlba;         /* 대상 시작 LBA */
    __u8    nr;            /* 소스 범위 수 - 1 */
    /* 소스 범위 디스크립터 리스트 (PRP/SGL) */
};

/* 소스 범위 디스크립터 */
struct nvme_copy_range {
    __le64  slba;          /* 소스 시작 LBA */
    __le16  nlb;           /* 소스 블록 수 - 1 */
};
# Simple Copy 사용 (nvme-cli)
$ nvme copy /dev/nvme0n1 --sdlba=0x1000 --blocks=255 --slbs=0x0

# 커널에서의 사용: REQ_OP_COPY_OFFLOAD (개발 중)

Streams Directive

Streams Directive(NVMe 1.3)는 호스트가 데이터의 수명(lifetime) 특성을 컨트롤러에 알려주어, FTL이 데이터를 효율적으로 배치하도록 합니다:

/* 스트림 힌트 (include/uapi/linux/fcntl.h) */
enum rw_hint {
    WRITE_LIFE_NOT_SET  = 0,  /* 힌트 없음 */
    WRITE_LIFE_NONE     = 1,  /* 수명 힌트 없음 */
    WRITE_LIFE_SHORT    = 2,  /* 짧은 수명 (hot data) */
    WRITE_LIFE_MEDIUM   = 3,  /* 중간 수명 */
    WRITE_LIFE_LONG     = 4,  /* 긴 수명 (warm data) */
    WRITE_LIFE_EXTREME  = 5,  /* 매우 긴 수명 (cold data) */
};

NVMe 보안

NVMe는 데이터 보호와 접근 제어를 위한 다양한 보안 메커니즘을 제공합니다.

TCG Opal SED

TCG(Trusted Computing Group) Opal은 자체 암호화 드라이브(SED)의 표준입니다. NVMe 디바이스에서 하드웨어 기반 전체 디스크 암호화를 제공합니다:

# sedutil-cli로 TCG Opal 관리
$ sedutil-cli --scan              # Opal 지원 디바이스 검색
$ sedutil-cli --initialSetup <password> /dev/nvme0n1
$ sedutil-cli --enableLockingRange 0 <password> /dev/nvme0n1
$ sedutil-cli --setLockingRange 0 LK <password> /dev/nvme0n1  # 잠금
$ sedutil-cli --setLockingRange 0 RW <password> /dev/nvme0n1  # 해제

Sanitize

NVMe Sanitize 커맨드는 디바이스의 모든 사용자 데이터를 안전하게 삭제합니다. Format NVM보다 더 철저한 데이터 소거를 보장합니다:

Sanitize 동작방법속도보안 수준
Block EraseFlash 블록 단위 삭제빠름 (수 초~분)중간
Crypto Erase암호화 키 교체매우 빠름 (즉시)높음 (SED 필요)
Overwrite패턴으로 전체 덮어쓰기매우 느림 (수 시간)매우 높음
# Sanitize 실행
$ nvme sanitize /dev/nvme0 --sanact=2  # Block Erase
$ nvme sanitize /dev/nvme0 --sanact=4  # Crypto Erase

# Sanitize 진행 상태 확인
$ nvme sanitize-log /dev/nvme0

인증 (DH-HMAC-CHAP)

NVMe 2.0에서 도입된 DH-HMAC-CHAP 인증은 호스트와 컨트롤러 간 상호 인증을 제공합니다. 주로 NVMe-oF 환경에서 사용됩니다:

/* drivers/nvme/host/auth.c — DH-HMAC-CHAP */
/* 인증 흐름:
 * 1. 호스트 → 컨트롤러: DH 공개값 + 챌린지
 * 2. 컨트롤러 → 호스트: DH 공개값 + 응답 + 챌린지
 * 3. 호스트: 응답 검증 + 컨트롤러 챌린지에 응답
 * 4. 상호 인증 완료
 */

# 호스트 인증 키 설정
$ nvme gen-dhchap-key -n nqn.2024-01.com.example:nvme-target
DHHC-1:00:YWJjZGVmZw==:

# 타겟에 호스트 키 등록
$ echo "DHHC-1:00:YWJjZGVmZw==:" > \
    /sys/kernel/config/nvmet/hosts/nqn.host/dhchap_key

# 양방향 인증 (상호 인증)
$ echo "DHHC-1:00:eHl6MTIz:" > \
    /sys/kernel/config/nvmet/hosts/nqn.host/dhchap_ctrl_key

Secure Erase

NVMe Format NVM 커맨드의 Secure Erase 옵션:

# User Data Erase (사용자 데이터만)
$ nvme format /dev/nvme0n1 --ses=1

# Cryptographic Erase (암호화 키 폐기)
$ nvme format /dev/nvme0n1 --ses=2
☠️

데이터 파괴: Sanitize와 Format의 Secure Erase는 되돌릴 수 없습니다. 중요 데이터가 없음을 반드시 확인한 후 실행하세요.

NVMe 가상화

NVMe 디바이스를 가상 환경에서 활용하는 방법은 에뮬레이션, 패스스루, SR-IOV의 세 가지가 있습니다.

SR-IOV

NVMe 1.1+에서 SR-IOV(Single Root I/O Virtualization)를 지원하여, 하나의 물리 NVMe 컨트롤러를 여러 가상 함수(VF)로 분할합니다:

# SR-IOV VF 생성
$ echo 4 > /sys/bus/pci/devices/0000:03:00.0/sriov_numvfs

# VF 확인
$ lspci | grep NVMe
03:00.0 Non-Volatile memory controller: ...  # PF
03:00.1 Non-Volatile memory controller: ...  # VF 1
03:00.2 Non-Volatile memory controller: ...  # VF 2
03:00.3 Non-Volatile memory controller: ...  # VF 3
03:00.4 Non-Volatile memory controller: ...  # VF 4

# 각 VF에 네임스페이스 할당 (Secondary Controller)
$ nvme virt-mgmt /dev/nvme0 --act=1 --cntlid=0x1 --rt=0 --nr=2

VFIO Passthrough

VFIO를 사용하면 NVMe 디바이스를 VM에 직접 할당(passthrough)하여 네이티브에 가까운 성능을 제공합니다:

# VFIO에 NVMe 디바이스 바인딩
$ echo "0000:03:00.0" > /sys/bus/pci/devices/0000:03:00.0/driver/unbind
$ echo "vfio-pci" > /sys/bus/pci/devices/0000:03:00.0/driver_override
$ echo "0000:03:00.0" > /sys/bus/pci/drivers/vfio-pci/bind

# QEMU에서 NVMe passthrough
$ qemu-system-x86_64 \
    -device vfio-pci,host=0000:03:00.0 \
    ...

QEMU 에뮬레이션

QEMU는 소프트웨어로 NVMe 컨트롤러를 에뮬레이션하여, 물리 NVMe 디바이스 없이도 NVMe 기능을 테스트할 수 있습니다:

# QEMU NVMe 에뮬레이션 (기본)
$ qemu-system-x86_64 \
    -drive file=nvme.img,format=qcow2,if=none,id=nvm \
    -device nvme,serial=deadbeef,drive=nvm

# QEMU NVMe 에뮬레이션 (고급: ZNS + CMB)
$ qemu-system-x86_64 \
    -drive file=zns.img,format=raw,if=none,id=zns-drv \
    -device nvme,serial=zns001,drive=zns-drv,\
zoned=true,zone_size=64M,zone_capacity=62M,\
max_open=16,max_active=32,\
cmb_size_mb=128

# Multipath 테스트 (다중 컨트롤러, 공유 네임스페이스)
$ qemu-system-x86_64 \
    -device nvme-subsys,id=subsys0,nqn=nqn.test \
    -device nvme,serial=ctrl0,subsys=subsys0 \
    -device nvme,serial=ctrl1,subsys=subsys0 \
    -device nvme-ns,drive=nvm,nsid=1,shared=on,subsys=subsys0

컨테이너 활용

NVMe 디바이스를 컨테이너에서 사용하는 방법:

# Docker: NVMe 디바이스를 컨테이너에 마운트
$ docker run --device=/dev/nvme0n1 -it ubuntu

# Kubernetes: NVMe를 PersistentVolume으로 사용
# (hostPath 또는 CSI 드라이버 통해)
apiVersion: v1
kind: PersistentVolume
metadata:
  name: nvme-pv
spec:
  capacity:
    storage: 100Gi
  accessModes: [ReadWriteOnce]
  local:
    path: /dev/nvme0n1p1
  nodeAffinity: ...

NVMe 에러 처리와 복구

NVMe 드라이버는 다단계 에러 처리 메커니즘으로 하드웨어 오류부터 일시적 장애까지 포괄적으로 대응합니다.

상태 코드 체계

NVMe CQE의 상태 필드는 3개 유형으로 분류됩니다:

상태 코드 타입 (SCT)설명예시
Generic Command0x0일반적인 커맨드 오류Invalid Opcode, Invalid Field
Command Specific0x1커맨드별 특수 오류Conflicting Attributes
Media and Data Integrity0x2미디어/데이터 무결성 오류Unrecovered Read, Write Fault
Path Related0x3경로 관련 오류 (NVMe-oF)Host Path Error
Vendor Specific0x7벤더 정의 오류벤더별 상이
/* include/linux/nvme.h — 상태 코드 매크로 */
#define NVME_SC_SUCCESS              0x0
#define NVME_SC_INVALID_OPCODE       0x1
#define NVME_SC_INVALID_FIELD        0x2
#define NVME_SC_NS_NOT_READY         0x82
#define NVME_SC_WRITE_FAULT          0x280
#define NVME_SC_READ_ERROR           0x281
#define NVME_SC_DNR                  0x4000  /* Do Not Retry 비트 */

다단계 복구 메커니즘

리눅스 NVMe 드라이버의 에러 복구 레벨:

  1. 커맨드 재시도: DNR 비트가 없으면 blk-mq가 자동 재시도 (최대 nvme_core.max_retries, 기본 5회)
  2. I/O 타임아웃: nvme_timeout()에서 30초(기본) 후 처리
    • 먼저 Abort 커맨드로 개별 커맨드 취소 시도
    • Abort 실패 시 컨트롤러 리셋
  3. 컨트롤러 리셋: nvme_reset_ctrl()로 전체 컨트롤러 초기화
    • 모든 I/O 큐 삭제 후 재생성
    • 진행 중인 I/O는 실패 처리 후 상위 계층이 재시도
  4. 컨트롤러 제거: 복구 불가능 시 컨트롤러를 DEAD 상태로 전환, 모든 I/O에 에러 반환
/* drivers/nvme/host/core.c — 타임아웃 처리 */
static enum blk_eh_timer_return nvme_timeout(
    struct request *req)
{
    /* 1단계: 커맨드 Abort 시도 */
    if (nvme_abort_req(req) == 0)
        return BLK_EH_RESET_TIMER;

    /* 2단계: 컨트롤러 리셋 */
    nvme_reset_ctrl(ctrl);
    return BLK_EH_DONE;
}

AER (Asynchronous Event Request)

AER은 컨트롤러가 비동기적으로 호스트에 알리는 이벤트 메커니즘입니다. 드라이버 초기화 시 Admin SQ에 AER 커맨드를 미리 제출하고, 이벤트 발생 시 CQ에 완료가 반환됩니다:

AER 타입설명예시
Error Status오류 상태 변경Persistent Internal Error
SMART / Health건강 상태 변경온도 임계값 초과, Reliability 저하
Notice운영 관련 알림네임스페이스 변경, 펌웨어 업데이트
I/O Command Set SpecificI/O 커맨드 세트 이벤트Zone 상태 변경 (ZNS)
Vendor Specific벤더 정의 이벤트벤더별 상이
/* drivers/nvme/host/core.c — AER 처리 */
static void nvme_async_event_work(struct work_struct *work)
{
    struct nvme_ctrl *ctrl = container_of(work, ...);

    switch (aer_type) {
    case NVME_AER_NOTICE_NS_CHANGED:
        nvme_scan_work(&ctrl->scan_work);  /* NS 재스캔 */
        break;
    case NVME_AER_NOTICE_ANA:
        nvme_mpath_update(ctrl);  /* ANA 상태 업데이트 */
        break;
    case NVME_AER_NOTICE_FW_ACT_STARTING:
        nvme_fw_act_work(ctrl);  /* 펌웨어 활성화 대기 */
        break;
    }

    /* AER 커맨드 재제출 (다음 이벤트 대기) */
    nvme_submit_async_event(ctrl);
}

Character Device와 ioctl

NVMe 디바이스는 블록 디바이스 외에 캐릭터 디바이스 인터페이스를 제공하여, 애플리케이션이 NVMe 커맨드를 직접 전송할 수 있습니다.

/dev/nvmeX passthrough ioctl

컨트롤러 캐릭터 디바이스(/dev/nvme0)를 통해 Admin 커맨드를 직접 전송합니다:

/* include/uapi/linux/nvme_ioctl.h */
struct nvme_passthru_cmd {
    __u8    opcode;        /* NVMe 명령 코드 */
    __u8    flags;
    __u16   rsvd1;
    __u32   nsid;
    __u32   cdw2, cdw3;
    __u64   metadata;
    __u64   addr;          /* 데이터 버퍼 주소 */
    __u32   metadata_len;
    __u32   data_len;      /* 데이터 크기 */
    __u32   cdw10, cdw11, cdw12, cdw13, cdw14, cdw15;
    __u32   timeout_ms;
    __u32   result;       /* CQE DW0 결과 */
};

/* ioctl 번호 */
#define NVME_IOCTL_ADMIN_CMD   _IOWR('N', 0x41, struct nvme_passthru_cmd)
#define NVME_IOCTL_IO_CMD      _IOWR('N', 0x43, struct nvme_passthru_cmd)
#define NVME_IOCTL_ADMIN64_CMD _IOWR('N', 0x47, struct nvme_passthru_cmd64)
/* 사용 예: Identify Controller (C 코드) */
struct nvme_passthru_cmd cmd = {
    .opcode   = 0x06,              /* Identify */
    .nsid     = 0,
    .addr     = (__u64)(uintptr_t)buf,
    .data_len = 4096,
    .cdw10    = 1,                 /* CNS=1: Controller */
};
ioctl(fd, NVME_IOCTL_ADMIN_CMD, &cmd);

io_uring NVMe passthrough (io_uring_cmd)

커널 5.19+에서 io_uring을 통한 NVMe passthrough가 가능합니다. ioctl 기반보다 높은 IOPS를 달성합니다:

/* io_uring NVMe passthrough (사용자 공간) */
struct io_uring_sqe *sqe = io_uring_get_sqe(&ring);
sqe->opcode  = IORING_OP_URING_CMD;
sqe->fd      = nvme_fd;          /* /dev/ng0n1 fd */
sqe->cmd_op  = NVME_URING_CMD_IO;

/* nvme_uring_cmd 구조체를 sqe->cmd에 인코딩 */
struct nvme_uring_cmd *cmd = (void *)&sqe->cmd;
cmd->opcode   = 0x02;            /* Read */
cmd->addr     = (__u64)buf;
cmd->data_len = 4096;
cmd->cdw10    = slba & 0xFFFFFFFF;
cmd->cdw11    = slba >> 32;
cmd->cdw12    = nlb - 1;
💡

성능: io_uring NVMe passthrough는 블록 계층을 완전히 우회하여, fio 기준 단일 코어에서 170만+ IOPS를 달성할 수 있습니다. /dev/ng0n1 제네릭 캐릭터 디바이스를 사용하세요.

/dev/ngXnY 제네릭 캐릭터 디바이스

커널 5.13+에서 도입된 /dev/ngXnY는 네임스페이스별 캐릭터 디바이스로, I/O 커맨드를 직접 전송할 수 있습니다:

NVMe sysfs 인터페이스

리눅스 NVMe 드라이버는 sysfs를 통해 컨트롤러, 서브시스템, 네임스페이스 정보를 노출합니다.

/sys/class/nvme/

컨트롤러별 속성:

경로설명예시 값
nvme0/model모델명Samsung SSD 990 PRO
nvme0/serial시리얼 번호S6Z2NF0TA12345
nvme0/firmware_rev펌웨어 버전4B2QJXM7
nvme0/transport전송 타입pcie / tcp / rdma / fc
nvme0/state컨트롤러 상태live / resetting / dead
nvme0/address전송 주소0000:03:00.0 (PCIe)
nvme0/numa_nodeNUMA 노드0
nvme0/queue_countI/O 큐 수9 (8 I/O + 1 Admin)
nvme0/sqsizeSQ 엔트리 수1023
nvme0/cntrltype컨트롤러 타입io / admin / discovery
# 컨트롤러 정보 한눈에 보기
$ for f in /sys/class/nvme/nvme0/{model,serial,firmware_rev,transport,state}; do
    echo "$(basename $f): $(cat $f)"
  done

# 전원 관리 속성
$ cat /sys/class/nvme/nvme0/power/ps_max_latency_us  # APST 최대 지연
$ cat /sys/class/nvme/nvme0/power/nuse                # 사용 중인 블록 수

/sys/class/nvme-subsystem/

NVMe 서브시스템은 여러 컨트롤러와 네임스페이스를 그룹화합니다:

# 서브시스템 정보
$ cat /sys/class/nvme-subsystem/nvme-subsys0/nqn
nqn.2024-01.com.samsung:990PRO:S6Z2NF0TA12345

$ cat /sys/class/nvme-subsystem/nvme-subsys0/model
Samsung SSD 990 PRO 2TB

# 서브시스템 내 컨트롤러 목록
$ ls /sys/class/nvme-subsystem/nvme-subsys0/nvme*
nvme0  nvme1  # Multipath 환경

# I/O 정책 (Multipath)
$ cat /sys/class/nvme-subsystem/nvme-subsys0/iopolicy
numa

/sys/block/nvmeXnY/queue/

블록 디바이스 큐 속성으로 I/O 동작을 튜닝합니다:

속성설명기본값
schedulerI/O 스케줄러none (NVMe 기본)
nr_requests큐당 최대 요청 수1023
read_ahead_kb읽기 선행 크기128
max_sectors_kb최대 I/O 크기512~2048
io_pollI/O 폴링 활성화0
io_poll_delay폴링 전 대기 시간-1 (자동)
nomergesI/O 병합 비활성화0
wbt_lat_usec쓰기 백프레셔 지연2000
zone_append_max_bytesZone Append 최대 크기 (ZNS)디바이스 의존

NVMe 내구성과 수명 관리

NAND 플래시 기반 NVMe SSD는 유한한 쓰기 수명을 가집니다. SMART 로그와 모니터링을 통해 수명을 추적하고 관리합니다.

SMART / Health 로그 해석

# SMART 로그 전체 출력
$ nvme smart-log /dev/nvme0
Smart Log for NVME device:nvme0 namespace-id:ffffffff
critical_warning                    : 0        # 0=정상
temperature                         : 42°C
available_spare                     : 100%     # 여유 블록 비율
available_spare_threshold           : 10%      # 경고 임계값
percentage_used                     : 1%       # 수명 소모율 (100%=TBW 도달)
data_units_read                     : 15,234,567
data_units_written                  : 8,456,789
host_read_commands                  : 245,678,901
host_write_commands                 : 123,456,789
controller_busy_time                : 1,234
power_cycles                        : 156
power_on_hours                      : 8,760
unsafe_shutdowns                    : 3
media_errors                        : 0        # 미디어 오류 누적
num_err_log_entries                  : 0
지표의미주의 수준
percentage_usedTBW 대비 사용률>90%: 교체 계획 수립
available_spare여유 블록 비율<threshold: 경고
media_errors복구 불가 미디어 에러>0: 즉시 조사
critical_warning비트 플래그비트별 의미 확인
unsafe_shutdowns비정상 종료 횟수잦으면 전원 환경 점검

WAF와 Over-Provisioning

# WAF 추정 (SMART 로그 기반)
# 벤더별 NAND 쓰기량 로그 위치가 다름
$ nvme intel smart-log-add /dev/nvme0     # Intel
$ nvme samsung vs-smart-add-log /dev/nvme0 # Samsung

# Over-Provisioning: 네임스페이스 축소로 OP 확보
$ nvme delete-ns /dev/nvme0 --namespace-id=1
$ nvme create-ns /dev/nvme0 \
    --nsze=1953525168 \        # 전체의 90%만 사용
    --ncap=1953525168 \
    --block-size=512
$ nvme attach-ns /dev/nvme0 --namespace-id=1 --controllers=0x41

네임스페이스 수준 모니터링

# 네임스페이스별 사용량 (NVMe 1.4+)
$ nvme id-ns /dev/nvme0n1 | grep -E "nsze|ncap|nuse"
nsze    : 1953525168  # 네임스페이스 크기 (블록 수)
ncap    : 1953525168  # 네임스페이스 용량
nuse    : 976762584   # 실제 사용 중인 블록 (Thin Provisioning)

# Endurance Group 로그 (NVMe 1.4+)
$ nvme endurance-log /dev/nvme0 --group-id=1

nvme-cli 관리 도구

nvme-cli는 리눅스 공식 NVMe 관리 유틸리티로, NVMe 스펙의 거의 모든 Admin/I/O 커맨드를 사용자 공간에서 실행할 수 있습니다.

정보 조회

# 설치
$ apt install nvme-cli    # Debian/Ubuntu
$ dnf install nvme-cli    # Fedora/RHEL

# NVMe 디바이스 목록
$ nvme list
Node          SN              Model               Namespace Usage          Format  FW Rev
/dev/nvme0n1  S6Z2NF0TA12345  Samsung SSD 990 PRO  1         953.9 GB / 2 TB 512B+0B 4B2QJXM7

# 컨트롤러 식별
$ nvme id-ctrl /dev/nvme0 -H  # Human-readable
$ nvme id-ctrl /dev/nvme0 -o json  # JSON 출력

# 네임스페이스 식별
$ nvme id-ns /dev/nvme0n1 -H

# 서브시스템 목록 (Multipath 포함)
$ nvme list-subsys

# 에러 로그
$ nvme error-log /dev/nvme0 --log-entries=16

# 기능 조회
$ nvme get-feature /dev/nvme0 -f 0x01 -H  # Arbitration
$ nvme get-feature /dev/nvme0 -f 0x07 -H  # Number of Queues
$ nvme get-feature /dev/nvme0 -f 0x09 -H  # Interrupt Coalescing

관리 명령

# 펌웨어 관리
$ nvme fw-download /dev/nvme0 --fw=firmware.bin
$ nvme fw-commit /dev/nvme0 --slot=1 --action=1   # 다음 리셋 시 활성화
$ nvme fw-commit /dev/nvme0 --slot=1 --action=3   # 즉시 활성화

# 포맷 (데이터 파괴!)
$ nvme format /dev/nvme0n1 --lbaf=0 --ses=0

# Self-Test
$ nvme device-self-test /dev/nvme0 --stc=1   # Short test
$ nvme device-self-test /dev/nvme0 --stc=2   # Extended test
$ nvme self-test-log /dev/nvme0              # 결과 확인

# 로그 페이지 조회 (Raw)
$ nvme get-log /dev/nvme0 --log-id=2 --log-len=512  # SMART
$ nvme get-log /dev/nvme0 --log-id=5 --log-len=512  # Commands Supported

플러그인과 자동화

nvme-cli는 벤더별 플러그인으로 확장됩니다:

# 벤더 플러그인 목록
$ nvme help | grep -A1 "vendor"
  intel           Intel vendor specific extensions
  samsung         Samsung vendor specific extensions
  wdc             Western Digital vendor specific extensions
  micron          Micron vendor specific extensions

# Samsung 벤더 로그
$ nvme samsung vs-smart-add-log /dev/nvme0

# JSON 출력 + jq 활용
$ nvme smart-log /dev/nvme0 -o json | jq '.temperature'
42

# 자동 모니터링 스크립트
$ nvme smart-log /dev/nvme0 -o json | jq '{
    temp: .temperature,
    spare: .avail_spare,
    used: .percent_used,
    media_errors: .media_errors,
    unsafe_shutdowns: .unsafe_shutdowns
  }'

NVMe 성능 튜닝

NVMe의 잠재 성능을 최대로 끌어내기 위한 커널/시스템 수준 튜닝 방법을 다룹니다.

커널 파라미터

파라미터기본값설명튜닝 지침
nvme_core.io_timeout30I/O 타임아웃 (초)NVMe-oF: 60~120초로 증가
nvme_core.max_retries5최대 재시도 횟수성능 우선: 2~3으로 감소
nvme_core.multipathY네이티브 멀티패스불필요 시 N으로 비활성화
nvme_core.default_ps_max_latency_us100000APST 최대 지연 (μs)벤치마킹: 0 (APST 비활성화)
nvme.poll_queues0폴링 큐 수저지연: CPU 수의 1/4~1/2
nvme.write_queues0쓰기 전용 큐 수혼합 워크로드: 2~4
# I/O 스케줄러: NVMe는 기본 none (직접 디스패치)
$ cat /sys/block/nvme0n1/queue/scheduler
[none] mq-deadline kyber bfq

# IRQ affinity 최적화
$ cat /proc/interrupts | grep nvme
  45: ... IR-PCI-MSI 524288-edge nvme0q0   # Admin
  46: ... IR-PCI-MSI 524289-edge nvme0q1   # CPU 0
  47: ... IR-PCI-MSI 524290-edge nvme0q2   # CPU 1
$ echo 1 > /proc/irq/46/smp_affinity     # CPU 0에 고정
$ echo 2 > /proc/irq/47/smp_affinity     # CPU 1에 고정

# NUMA-aware I/O: NVMe가 연결된 NUMA 노드에서 I/O 수행
$ cat /sys/class/nvme/nvme0/numa_node
0
$ numactl --cpunodebind=0 --membind=0 fio ...

fio 벤치마크

# 순차 읽기 (대역폭 측정)
$ fio --name=seq-read --filename=/dev/nvme0n1 \
    --rw=read --bs=128k --iodepth=64 --numjobs=4 \
    --ioengine=io_uring --direct=1 --runtime=30 --group_reporting

# 랜덤 4K 읽기 (IOPS 측정)
$ fio --name=rand-read --filename=/dev/nvme0n1 \
    --rw=randread --bs=4k --iodepth=256 --numjobs=4 \
    --ioengine=io_uring --direct=1 --runtime=30 --group_reporting

# io_uring 폴링 모드 (최저 지연)
$ fio --name=poll-read --filename=/dev/nvme0n1 \
    --rw=randread --bs=4k --iodepth=1 --numjobs=1 \
    --ioengine=io_uring --direct=1 --hipri=1 --runtime=30

# io_uring NVMe passthrough (최대 IOPS)
$ fio --name=pt-read --filename=/dev/ng0n1 \
    --rw=randread --bs=4k --iodepth=128 --numjobs=4 \
    --ioengine=io_uring_cmd --cmd_type=nvme --direct=1 \
    --fixedbufs=1 --runtime=30 --group_reporting

perf / BPF 프로파일링

# NVMe I/O 지연 분포 (BCC/bpftrace)
$ biolatency-bpfcc -D nvme0n1 10
     usec           : count    distribution
       0 -> 1       : 0       |                    |
       2 -> 3       : 0       |                    |
       4 -> 7       : 12      |*                   |
       8 -> 15      : 8234    |********************|
      16 -> 31      : 4521    |***********         |
      32 -> 63      : 234     |*                   |

# NVMe 이벤트 트레이싱 (ftrace)
$ echo 1 > /sys/kernel/debug/tracing/events/nvme/enable
$ cat /sys/kernel/debug/tracing/trace_pipe

# perf로 NVMe 드라이버 오버헤드 분석
$ perf record -g -a -e block:block_rq_issue -e block:block_rq_complete \
    -- fio ... --runtime=10
$ perf report --sort=symbol

# bpftrace: NVMe 커맨드별 지연
$ bpftrace -e '
kprobe:nvme_queue_rq { @start[tid] = nsecs; }
kretprobe:nvme_queue_rq /@start[tid]/ {
    @latency = hist(nsecs - @start[tid]);
    delete(@start[tid]);
}'

커널 빌드 설정 (Kconfig)

NVMe 관련 커널 설정 옵션:

CONFIG 옵션설명기본값의존성
CONFIG_BLK_DEV_NVMENVMe PCIe 드라이버mPCI, BLOCK
CONFIG_NVME_MULTIPATH네이티브 멀티패스yBLK_DEV_NVME
CONFIG_NVME_HWMONhwmon 온도 모니터링yBLK_DEV_NVME, HWMON
CONFIG_NVME_FABRICSNVMe-oF 공통 코드mBLK_DEV_NVME
CONFIG_NVME_RDMANVMe/RDMA 호스트mNVME_FABRICS, INFINIBAND
CONFIG_NVME_TCPNVMe/TCP 호스트mNVME_FABRICS, INET
CONFIG_NVME_FCNVMe/FC 호스트mNVME_FABRICS
CONFIG_NVME_AUTHDH-HMAC-CHAP 인증yNVME_FABRICS, CRYPTO
CONFIG_NVME_TARGETNVMe-oF 타겟 코어mBLOCK, CONFIGFS
CONFIG_NVME_TARGET_TCPNVMe-oF TCP 타겟mNVME_TARGET, INET
CONFIG_NVME_TARGET_RDMANVMe-oF RDMA 타겟mNVME_TARGET, INFINIBAND
CONFIG_NVME_TARGET_FCNVMe-oF FC 타겟mNVME_TARGET
CONFIG_NVME_TARGET_LOOPNVMe-oF 루프백 타겟mNVME_TARGET
CONFIG_NVME_TARGET_AUTH타겟 인증yNVME_TARGET, CRYPTO

NVMe 드라이버 소스 트리 구조:

drivers/nvme/ 소스 트리 구조 drivers/nvme/ host/ 호스트(Initiator) 측 core.c 핵심 NVMe 로직 pci.c PCIe 전송 드라이버 fabrics.c NVMe-oF 공통 tcp.c NVMe/TCP 호스트 rdma.c NVMe/RDMA 호스트 fc.c NVMe/FC 호스트 multipath.c 네이티브 멀티패스 hwmon.c hwmon 통합 auth.c DH-HMAC-CHAP zns.c / ioctl.c ZNS / passthru sysfs.c / trace.h sysfs / ftrace target/ 타겟(Target) 측 core.c nvmet 코어 configfs.c ConfigFS 인터페이스 tcp.c NVMe/TCP 타겟 rdma.c NVMe/RDMA 타겟 fc.c NVMe/FC 타겟 loop.c 루프백 타겟 discovery.c Discovery Controller io-cmd-bdev.c 블록 디바이스 백엔드 io-cmd-file.c 파일 백엔드 passthru.c Passthrough 타겟 zns.c / auth.c ZNS / 인증 common/ 공통 코드 auth.c 인증 공통 로직 host/auth.c target/auth.c 모두 common/auth.c 공통 함수 사용 범례 핵심 파일 일반 파일 공유 코드 host/: NVMe 스토리지를 사용하는 서버/PC 측 드라이버 (pci.c가 메인) target/: NVMe 스토리지를 제공하는 타겟 서버 측 드라이버 (configfs로 설정)

일반적인 실수와 올바른 패턴

NVMe를 처음 다루거나 최적화할 때 자주 발생하는 실수와 올바른 해결 방법을 정리합니다.

❌ 실수 1: Queue Depth를 무조건 최대로 설정

/* ❌ 잘못된 예: 모든 워크로드에 최대 큐 깊이 사용 */
# /sys/block/nvme0n1/queue/nr_requests를 최대값으로 설정
echo 1024 > /sys/block/nvme0n1/queue/nr_requests
# 문제: 랜덤 I/O에서는 지연 시간만 증가하고 처리량은 정체
/* ✅ 올바른 예: 워크로드 특성에 따라 큐 깊이 조정 */
# 순차 I/O (대용량 스트리밍): 큰 큐 깊이
echo 256 > /sys/block/nvme0n1/queue/nr_requests

# 랜덤 I/O (데이터베이스): 작은 큐 깊이로 지연 최소화
echo 32 > /sys/block/nvme0n1/queue/nr_requests

# fio로 최적값 찾기
fio --name=test --filename=/dev/nvme0n1 --ioengine=libaio \
    --iodepth=1,4,8,16,32,64 --rw=randread --bs=4k --numjobs=1
핵심: Queue Depth가 크다고 항상 좋은 것은 아닙니다. 큐가 깊을수록 요청 대기 시간이 길어져 지연이 증가합니다. 랜덤 I/O는 낮은 큐 깊이(8-32)에서, 순차 I/O는 높은 큐 깊이(64-256)에서 최적 성능을 보입니다.

❌ 실수 2: 잘못된 인터럽트 모드 선택

/* ❌ 잘못된 예: 고성능 워크로드에서 인터럽트 모드 사용 */
# polling 비활성화 상태에서 초저지연 요구
echo 0 > /sys/module/nvme/parameters/poll_queues
# 문제: 인터럽트 오버헤드로 지연 시간 증가 (수십 마이크로초)
/* ✅ 올바른 예: 워크로드에 따라 polling vs interrupt 선택 */
# 초저지연 요구 (금융 거래, 실시간 분석): polling 모드
echo 4 > /sys/module/nvme/parameters/poll_queues
# CPU 일부를 polling에 전담 할당

# 일반 워크로드: interrupt 모드 (CPU 효율적)
echo 0 > /sys/module/nvme/parameters/poll_queues

# 혼합 모드: 일부 큐는 polling, 나머지는 interrupt
modprobe nvme poll_queues=2 nr_io_queues=16
# → 16개 I/O 큐 중 2개는 polling, 14개는 interrupt

❌ 실수 3: PRP 페이지 정렬 무시

/* ❌ 잘못된 예: 정렬되지 않은 버퍼로 DMA */
void *buf = malloc(8192);  /* 4KB 정렬 보장 안 됨 */
struct nvme_command cmd;
cmd.rw.prp1 = virt_to_phys(buf);  /* ❌ 정렬 위반 시 에러 */
/* ✅ 올바른 예: 4KB 정렬된 버퍼 할당 */
void *buf;
posix_memalign(&buf, 4096, 8192);  /* 4KB 정렬 */

/* 또는 커널에서 */
void *buf = kmalloc(8192, GFP_KERNEL);  /* 자동 정렬 보장 */

/* SGL 사용 시 불연속 메모리 가능 */
struct nvme_sgl_desc sgl[2];
sgl[0].addr = virt_to_phys(buf1);
sgl[1].addr = virt_to_phys(buf2);  /* 불연속 OK */

❌ 실수 4: Multipath 설정 없이 이중화 기대

/* ❌ 잘못된 예: 두 컨트롤러에 연결했지만 자동 장애조치 안 됨 */
# NVMe-oF로 두 경로 연결
nvme connect -t tcp -a 192.168.1.10 -s 4420 -n nqn.subsys
nvme connect -t tcp -a 192.168.1.11 -s 4420 -n nqn.subsys
# → /dev/nvme0n1, /dev/nvme1n1 두 개 생성되지만 독립적
# 문제: nvme0 경로 장애 시 수동으로 nvme1 사용해야 함
/* ✅ 올바른 예: Native Multipath 활성화 */
# 커널 파라미터로 multipath 활성화
modprobe nvme-core multipath=Y

# 두 경로 연결 시 자동으로 단일 네임스페이스로 통합
nvme connect -t tcp -a 192.168.1.10 -s 4420 -n nqn.subsys
nvme connect -t tcp -a 192.168.1.11 -s 4420 -n nqn.subsys
# → /dev/nvme0n1 단일 디바이스로 통합, 자동 장애조치

# 상태 확인
nvme list-subsys
# nvme-subsys0 - NQN=nqn.subsys
#  \
#   +- nvme0 tcp 192.168.1.10:4420 live
#   +- nvme1 tcp 192.168.1.11:4420 live

❌ 실수 5: 네임스페이스 공유 시 동기화 누락

/* ❌ 잘못된 예: 여러 호스트에서 동일 네임스페이스 동시 쓰기 */
# 호스트 A와 B가 동일한 NVMe-oF 네임스페이스에 파일시스템 마운트
# 호스트 A:
mkfs.ext4 /dev/nvme0n1
mount /dev/nvme0n1 /mnt

# 호스트 B:
mount /dev/nvme0n1 /mnt
# ❌ 문제: 파일시스템 메타데이터 충돌, 데이터 손상
/* ✅ 올바른 예: 클러스터 파일시스템 사용 또는 네임스페이스 분리 */
# 방법 1: 클러스터 파일시스템 사용 (GFS2, OCFS2)
mkfs.gfs2 -p lock_dlm -t mycluster:myfs /dev/nvme0n1
# 호스트 A, B 모두 마운트 가능 (DLM으로 동기화)

# 방법 2: 네임스페이스 분리
# 타겟에서 네임스페이스 2개 생성
nvmetcli
> cd subsystems/nqn.subsys/namespaces
> create 1  # 호스트 A 전용
> create 2  # 호스트 B 전용

# 방법 3: Read-Only 공유
mount -o ro /dev/nvme0n1 /mnt  # 읽기 전용으로 안전 공유

베스트 프랙티스 체크리스트

항목권장 사항확인 방법
Queue Depth 워크로드 특성에 맞게 조정 (랜덤: 8-32, 순차: 64-256) cat /sys/block/nvme0n1/queue/nr_requests
인터럽트 모드 저지연 필요 시 polling, 일반 워크로드는 interrupt cat /sys/module/nvme/parameters/poll_queues
CPU Affinity I/O 큐를 특정 CPU에 고정하여 캐시 효율 향상 cat /proc/irq/*/smp_affinity_list
Multipath 이중화 환경에서는 반드시 Native Multipath 활성화 nvme list-subsys
메모리 정렬 DMA 버퍼는 4KB 정렬 필수 posix_memalign() 사용
스케줄러 NVMe는 'none' 스케줄러 권장 (blk-mq 자체 처리) cat /sys/block/nvme0n1/queue/scheduler
Write Cache 데이터 보호 중요 시 FUA 사용, 성능 우선 시 활성화 nvme get-feature -f 0x06 /dev/nvme0
APST 전력 절약 필요 시 활성화, 지연 민감 시 비활성화 cat /sys/module/nvme_core/parameters/default_ps_max_latency_us

성능 최적화 실전 가이드

NVMe 디바이스의 최대 성능을 끌어내기 위한 실전 튜닝 가이드입니다.

I/O 스케줄러 최적화

# NVMe는 하드웨어 큐가 충분하므로 스케줄러 오버헤드 제거
echo none > /sys/block/nvme0n1/queue/scheduler

# 전체 NVMe 디바이스에 일괄 적용
for dev in /sys/block/nvme*; do
  echo none > $dev/queue/scheduler
done

# 성능 비교 (mq-deadline vs none)
fio --name=test --filename=/dev/nvme0n1 --ioengine=libaio \
    --iodepth=32 --rw=randread --bs=4k --numjobs=4 --runtime=30
# none 스케줄러: ~500K IOPS
# mq-deadline: ~450K IOPS (병합/정렬 오버헤드)

CPU Affinity 및 IRQ 밸런싱

# NVMe 인터럽트를 특정 CPU에 고정하여 캐시 효율 향상
# 1. NVMe 디바이스의 IRQ 번호 찾기
grep nvme /proc/interrupts | awk '{print $1}' | sed 's/://'

# 2. IRQ를 CPU에 할당 (예: CPU 0-3에 분산)
for irq in $(cat /proc/interrupts | grep nvme0q | awk '{print $1}' | sed 's/://'); do
  cpu=$((irq % 4))
  echo $cpu > /proc/irq/$irq/smp_affinity_list
done

# 3. NUMA 인식 최적화 (로컬 메모리 액세스)
# NVMe가 NUMA 노드 0에 있다면
numactl --cpunodebind=0 --membind=0 fio --name=test ...

Polling 모드 성능 극대화

# Polling 큐 전용 CPU 할당으로 지연 최소화
# 1. Polling 큐 설정 (4개 큐를 polling으로)
modprobe nvme poll_queues=4

# 2. Polling 큐 전용 CPU 격리 (부팅 파라미터)
# /etc/default/grub에 추가:
# GRUB_CMDLINE_LINUX="isolcpus=4,5,6,7"

# 3. io_uring로 polling 모드 I/O
fio --name=poll_test --filename=/dev/nvme0n1 \
    --ioengine=io_uring --hipri --iodepth=8 \
    --rw=randread --bs=4k --runtime=30

# 지연 시간 비교:
# Interrupt 모드: ~30μs
# Polling 모드: ~10μs (3배 개선)

Write Cache 및 FUA 튜닝

# Write Cache 상태 확인 (Feature ID 0x06)
nvme get-feature -f 0x06 /dev/nvme0
# 결과: 0x00000001 → Volatile Write Cache 활성화

# Write Cache 활성화 (성능 우선)
nvme set-feature -f 0x06 -v 1 /dev/nvme0
# 장점: 쓰기 처리량 2배 향상
# 단점: 전원 장애 시 데이터 손실 위험

# FUA (Force Unit Access) 사용 (안정성 우선)
fio --name=fua_test --filename=/dev/nvme0n1 \
    --ioengine=libaio --iodepth=32 --rw=randwrite \
    --bs=4k --direct=1 --end_fsync=1
# FUA 플래그로 캐시 우회, 안전하지만 느림

네임스페이스 최적화

# 워크로드별로 네임스페이스 분리하여 간섭 최소화
# 1. 고성능 네임스페이스 생성 (낮은 지연, 높은 IOPS)
nvme create-ns /dev/nvme0 --nsze=209715200 --ncap=209715200 \
    --flbas=0 --dps=0 --nmic=0
nvme attach-ns /dev/nvme0 --namespace-id=1 --controllers=0

# 2. 대용량 네임스페이스 생성 (순차 I/O 최적화)
nvme create-ns /dev/nvme0 --nsze=419430400 --ncap=419430400 \
    --flbas=0 --dps=0 --nmic=0
nvme attach-ns /dev/nvme0 --namespace-id=2 --controllers=0

# 3. ZNS 네임스페이스 (로그/시계열 데이터)
# ZNS는 순차 쓰기 전용으로 쓰기 증폭 최소화

성능 측정 도구

도구용도사용 예시
fio 벤치마크 (IOPS, 처리량, 지연) fio --name=test --ioengine=libaio --iodepth=32 --rw=randread --bs=4k
iostat 실시간 I/O 통계 iostat -xz 1 nvme0n1
blktrace I/O 요청 추적 blktrace -d /dev/nvme0n1 -o trace
perf CPU 사이클, 캐시 미스 분석 perf stat -e cycles,cache-misses fio ...
nvme smart-log 디바이스 건강 상태 nvme smart-log /dev/nvme0
bpftrace 커널 내부 지연 추적 bpftrace -e 'kprobe:nvme_queue_rq { @start[tid] = nsecs; }'
성능 튜닝 우선순위:
  1. I/O 스케줄러를 'none'으로 설정 (즉각 적용, 큰 효과)
  2. 워크로드에 맞는 Queue Depth 조정 (fio로 측정)
  3. 초저지연 필요 시 Polling 모드 활성화
  4. CPU Affinity 설정으로 캐시 효율 향상
  5. NUMA 인식 메모리 할당으로 원격 메모리 접근 최소화

실전 케이스 스터디

실제 프로덕션 환경에서 NVMe를 활용한 사례와 최적화 경험을 공유합니다.

케이스 1: 고성능 데이터베이스 스토리지

문제 상황: PostgreSQL 데이터베이스에서 초당 100만 건의 트랜잭션 처리 필요. 기존 SATA SSD는 IOPS 한계로 병목 발생.

해결 방법:

# 1. NVMe SSD로 마이그레이션 (Intel Optane P5800X)
# 2. I/O 스케줄러 비활성화
echo none > /sys/block/nvme0n1/queue/scheduler

# 3. Queue Depth 최적화 (랜덤 읽기 중심)
echo 16 > /sys/block/nvme0n1/queue/nr_requests

# 4. PostgreSQL WAL을 별도 NVMe 네임스페이스로 분리
# /dev/nvme0n1 → 데이터 파일 (랜덤 I/O)
# /dev/nvme0n2 → WAL (순차 쓰기)
echo 128 > /sys/block/nvme0n2/queue/nr_requests  # WAL은 큰 큐

# 5. Direct I/O 활성화 (캐시 중복 제거)
# postgresql.conf:
wal_sync_method = fdatasync
fsync = on

결과:

케이스 2: 분산 스토리지 시스템 (Ceph)

문제 상황: Ceph 클러스터의 OSD 노드에서 복제 쓰기 지연이 누적되어 클라이언트 성능 저하.

해결 방법:

# 1. BlueStore 백엔드로 NVMe 직접 사용 (FileStore 대체)
ceph-volume lvm create --bluestore --data /dev/nvme0n1

# 2. WAL/DB를 고속 NVMe에 배치
ceph-volume lvm create --bluestore \
  --data /dev/nvme0n1 \
  --block.wal /dev/nvme1n1p1 \
  --block.db /dev/nvme1n1p2

# 3. NVMe Multipath로 이중화 (두 NVMe-oF 경로)
modprobe nvme-core multipath=Y
nvme connect -t rdma -a 192.168.1.10 -s 4420 -n nqn.ceph.osd1
nvme connect -t rdma -a 192.168.1.11 -s 4420 -n nqn.ceph.osd1

# 4. CPU Affinity로 OSD 프로세스와 NVMe IRQ 동일 NUMA 노드 배치
numactl --cpunodebind=0 --membind=0 ceph-osd -i 0

결과:

케이스 3: ZNS를 활용한 로그 저장 시스템

문제 상황: 대규모 로그 수집 시스템에서 높은 쓰기 증폭으로 SSD 수명 단축 및 성능 저하.

해결 방법:

# 1. ZNS 네임스페이스 생성 (순차 쓰기 전용)
nvme zns id-ns /dev/nvme0n1
# Zone Size: 1GB, Total Zones: 256

# 2. F2FS 파일시스템을 ZNS 모드로 마운트
mkfs.f2fs -m /dev/nvme0n1
mount -t f2fs -o mode=lfs /dev/nvme0n1 /mnt/logs

# 3. 애플리케이션에서 Zone Append 사용
#include <linux/blkzoned.h>

int append_log(int fd, void *data, size_t len) {
    struct blk_zone_range range = { .sector = 0, .nr_sectors = 2097152 };
    ioctl(fd, BLKRESETZONE, &range);  /* Zone 재설정 */

    /* Zone Append 커맨드로 순차 쓰기 */
    return write(fd, data, len);
}

결과:

케이스 4: NVMe-oF 기반 클라우드 스토리지

문제 상황: 가상 머신에 로컬 NVMe 성능을 제공하면서도 스토리지 풀 공유 필요.

해결 방법:

# 타겟 서버: NVMe-oF TCP 타겟 설정
modprobe nvmet nvmet-tcp

# 1. 서브시스템 생성
mkdir /sys/kernel/config/nvmet/subsystems/nqn.storage.pool1
echo 1 > /sys/kernel/config/nvmet/subsystems/nqn.storage.pool1/attr_allow_any_host

# 2. 네임스페이스 생성 (실제 NVMe 백엔드)
mkdir /sys/kernel/config/nvmet/subsystems/nqn.storage.pool1/namespaces/1
echo /dev/nvme0n1 > /sys/kernel/config/nvmet/subsystems/nqn.storage.pool1/namespaces/1/device_path
echo 1 > /sys/kernel/config/nvmet/subsystems/nqn.storage.pool1/namespaces/1/enable

# 3. TCP 포트 바인딩
mkdir /sys/kernel/config/nvmet/ports/1
echo 0.0.0.0 > /sys/kernel/config/nvmet/ports/1/addr_traddr
echo tcp > /sys/kernel/config/nvmet/ports/1/addr_trtype
echo 4420 > /sys/kernel/config/nvmet/ports/1/addr_trsvcid

# 호스트 (VM): NVMe-oF 연결
nvme connect -t tcp -a 192.168.1.10 -s 4420 -n nqn.storage.pool1
# → /dev/nvme1n1로 로컬 NVMe처럼 사용

결과:

문제 해결 FAQ

NVMe를 사용하면서 자주 발생하는 문제와 해결 방법을 정리합니다.

Q1. NVMe 디스크가 인식되지 않습니다

증상: lsblknvme list에서 디스크가 보이지 않음.

원인 및 해결:

원인확인 방법해결
PCIe 링크 미연결 lspci | grep -i nvme
아무것도 안 나옴
물리적 연결 확인, M.2 슬롯 재장착
드라이버 미로드 lsmod | grep nvme
nvme 모듈 없음
modprobe nvme
BAR 크기 초과 dmesg | grep BAR
"can't allocate BAR"
BIOS에서 "Above 4G Decoding" 활성화
컨트롤러 비활성화 nvme list 시 "Controller not ready" nvme reset /dev/nvme0
네임스페이스 미생성 nvme id-ctrl /dev/nvme0
NN (Number of Namespaces) = 0
nvme create-ns /dev/nvme0 --nsze=...

Q2. 성능이 예상보다 훨씬 낮습니다

증상: 제조사 스펙은 7GB/s인데 실제 측정 시 1GB/s만 나옴.

체크리스트:

# 1. PCIe 레인 수 확인
lspci -vvv -s $(lspci | grep NVM | awk '{print $1}') | grep LnkSta
# Width x4 → 정상, x1 → 병목

# 2. I/O 스케줄러 확인
cat /sys/block/nvme0n1/queue/scheduler
# [none]이 아니면 변경

# 3. Queue Depth 확인
cat /sys/block/nvme0n1/queue/nr_requests
# 32 미만이면 증가

# 4. fio로 정확한 측정 (버퍼 캐시 우회)
fio --name=test --filename=/dev/nvme0n1 --direct=1 \
    --ioengine=libaio --iodepth=128 --rw=read --bs=128k --numjobs=4

# 5. Thermal Throttling 확인
nvme smart-log /dev/nvme0 | grep temperature
# 80°C 이상이면 냉각 개선 필요

Q3. I/O 에러가 반복적으로 발생합니다

증상: dmesg에 "I/O error" 메시지가 지속적으로 출력.

진단 절차:

# 1. SMART 로그로 하드웨어 상태 확인
nvme smart-log /dev/nvme0
# critical_warning: 0x00 (정상)
# media_errors: 0 (미디어 에러 없음)
# num_err_log_entries: 0 (에러 로그 없음)

# 2. 에러 로그 상세 확인
nvme error-log /dev/nvme0
# Status Code Field (SCT/SC)로 에러 타입 분석

# 3. 컨트롤러 레지스터 확인
nvme show-regs /dev/nvme0
# CSTS (Controller Status) 확인

# 4. 펌웨어 업데이트 확인
nvme fw-log /dev/nvme0
# 제조사 사이트에서 최신 펌웨어 확인

흔한 에러 코드:

Q4. Multipath 장애조치가 작동하지 않습니다

증상: 한 경로가 끊겨도 자동으로 다른 경로로 전환되지 않음.

해결:

# 1. Multipath 모듈 활성화 확인
cat /sys/module/nvme_core/parameters/multipath
# N이면 활성화 필요
echo 1 > /sys/module/nvme_core/parameters/multipath

# 2. 서브시스템 구조 확인
nvme list-subsys
# 두 컨트롤러가 동일 NQN으로 묶여야 함
# nvme-subsys0 - NQN=nqn.example
#  \
#   +- nvme0 tcp 192.168.1.10 live
#   +- nvme1 tcp 192.168.1.11 live

# 3. ANA (Asymmetric Namespace Access) 상태 확인
nvme list-subsys -v
# ANA State: optimized (활성 경로)
# ANA State: non-optimized (대기 경로)

# 4. 장애조치 테스트
# 한 경로의 네트워크 끊기
ip link set eth0 down
# I/O가 중단 없이 계속되는지 확인
dd if=/dev/nvme0n1 of=/dev/null bs=1M count=1000

Q5. 지연 시간이 갑자기 증가합니다

증상: 평소 100μs였던 지연이 간헐적으로 10ms까지 증가.

원인 및 해결:

원인확인해결
GC (Garbage Collection) iostat -x 1에서 주기적 스파이크 Over-provisioning 증가, TRIM 활성화
APST 전원 절약 nvme get-feature -f 0x0c /dev/nvme0 echo 0 > /sys/module/nvme_core/parameters/default_ps_max_latency_us
CPU C-State cpupower idle-info cpupower idle-set -d 2 (깊은 C-State 비활성화)
Thermal Throttling nvme smart-log /dev/nvme0 | grep temperature 냉각 개선 (히트싱크, 팬)
IRQ 밸런싱 cat /proc/interrupts | grep nvme IRQ를 고정 CPU에 할당

Q6. 네임스페이스 크기를 변경할 수 있나요?

답변: NVMe 스펙상 네임스페이스 크기 변경은 지원하지 않습니다. 삭제 후 재생성만 가능합니다.

# 1. 기존 네임스페이스 삭제 (⚠️ 데이터 손실)
nvme detach-ns /dev/nvme0 --namespace-id=1 --controllers=0
nvme delete-ns /dev/nvme0 --namespace-id=1

# 2. 새 크기로 네임스페이스 생성
nvme create-ns /dev/nvme0 --nsze=419430400 --ncap=419430400 --flbas=0

# 3. 네임스페이스 연결
nvme attach-ns /dev/nvme0 --namespace-id=1 --controllers=0

# 대안: LVM을 사용하여 유연한 크기 조정
pvcreate /dev/nvme0n1
vgcreate vg_nvme /dev/nvme0n1
lvcreate -L 100G -n lv_data vg_nvme
# 나중에 lvextend로 확장 가능
추가 문제 해결 리소스:
  • nvme-cli GitHub 저장소의 Issue 섹션
  • 커널 메일링 리스트 (linux-nvme@lists.infradead.org)
  • dmesg/var/log/kern.log의 상세 로그
  • strace로 ioctl 호출 추적

NVMe와 관련된 다른 주제를 더 깊이 이해하고 싶다면 다음 문서를 참고하세요.