Huge Pages (2MB/1GB) & THP — 대형 페이지 심화

리눅스 커널의 Huge Pages는 기본 4KB 페이지 대신 2MB 또는 1GB 크기의 대형 페이지를 사용하여 TLB(Translation Lookaside Buffer) 미스를 획기적으로 줄이고 메모리 접근 성능을 극대화하는 메커니즘입니다. 정적 예약 기반의 hugetlbfs부터 커널이 자동으로 대형 페이지를 생성하는 Transparent Huge Pages(THP), khugepaged 데몬, PMD 직접 매핑, compound page 구조, NUMA 정책, 예약 시스템, 마이그레이션/compaction 연동, 그리고 데이터베이스/DPDK/VM 실전 적용까지 전 영역을 상세히 다룹니다.

전제 조건: MMU & TLB 문서와 페이지 할당자(Buddy Allocator) 문서를 먼저 읽으세요. 가상 주소 변환, 페이지 테이블 계층 구조, Buddy 시스템의 order 개념을 이해해야 Huge Pages의 동작 원리를 정확히 파악할 수 있습니다.
일상 비유: 일반 4KB 페이지는 편의점 봉투에 물건을 하나씩 담는 것과 같고, 2MB Huge Page는 대형 택배 상자에 512개의 물건을 한꺼번에 담는 것과 같습니다. 택배 상자를 사용하면 배송 추적(TLB 조회) 횟수가 512분의 1로 줄어들어 전체 배송 효율이 크게 향상됩니다. 1GB Huge Page는 아예 컨테이너 트럭으로 262,144개의 물건을 한 번에 운송하는 것에 해당합니다.

핵심 요약

  • Huge Page — 4KB보다 큰 페이지 크기(x86_64에서 2MB 또는 1GB)를 사용하여 TLB 효율을 극대화하는 메모리 관리 기법
  • hugetlbfs — 사용자가 명시적으로 대형 페이지를 예약하고 사용하는 정적 방식의 파일시스템 인터페이스
  • THP (Transparent Huge Pages) — 커널이 자동으로 4KB 페이지를 2MB 대형 페이지로 승격/분할하는 투명 메커니즘
  • khugepaged — 백그라운드에서 분산된 4KB 페이지를 스캔하여 2MB 대형 페이지로 병합하는 커널 데몬
  • PMD (Page Middle Directory) — x86_64에서 2MB Huge Page를 위한 페이지 테이블 레벨로, PTE 단계를 건너뛰어 직접 물리 프레임을 가리킴
  • compound page — 연속된 물리 페이지를 하나의 논리적 대형 페이지로 묶는 커널 자료구조

단계별 이해

  1. TLB 미스 비용 이해
    CPU가 가상 주소를 물리 주소로 변환할 때, TLB 캐시에 없으면 4단계 페이지 테이블 워크가 발생합니다. 4KB 페이지로 1GB 메모리를 매핑하면 262,144개의 TLB 엔트리가 필요하지만, 2MB 페이지로는 512개, 1GB 페이지로는 단 1개면 충분합니다.
  2. 정적 Huge Pages 예약 (hugetlbfs)
    시스템 부팅 시 또는 런타임에 /proc/sys/vm/nr_hugepages를 통해 필요한 수의 Huge Pages를 미리 예약합니다. 예약된 페이지는 hugetlbfs를 통해 사용자 공간에 매핑됩니다.
  3. 투명 대형 페이지 (THP) 활성화
    /sys/kernel/mm/transparent_hugepage/enabledalways 또는 madvise로 설정하면, 커널이 자동으로 연속된 4KB 페이지를 2MB 페이지로 승격합니다.
  4. khugepaged 백그라운드 최적화
    THP가 활성화되면 khugepaged 데몬이 주기적으로 프로세스의 메모리를 스캔하여 병합 가능한 4KB 페이지 그룹을 2MB Huge Page로 통합합니다.
  5. 모니터링과 튜닝
    /proc/meminfo, /sys/kernel/mm/hugepages/, /sys/kernel/mm/transparent_hugepage/ 경로에서 Huge Pages 사용 현황과 THP 통계를 확인하고 세부 파라미터를 조정합니다.

개요 — 왜 Huge Pages가 필요한가

TLB 미스의 비용

현대 x86_64 프로세서에서 가상 주소를 물리 주소로 변환하는 과정은 4단계 페이지 테이블 워크 (PGD → PUD → PMD → PTE)를 거칩니다. TLB에 캐시된 변환이 있으면 1~2 사이클 내에 완료되지만, TLB 미스가 발생하면 최대 4번의 메모리 접근이 필요하여 수십~수백 사이클의 지연이 발생합니다.

일반적인 x86_64 프로세서의 TLB 엔트리 수는 다음과 같습니다:

TLB 레벨4KB 엔트리 수2MB 엔트리 수1GB 엔트리 수
L1 DTLB (데이터) 64~72 32 4~8
L1 ITLB (명령어) 128 8
L2 STLB (공유) 1,536~2,048 1,536~2,048 (공유)
커버 가능 메모리 6~8 MB 3~4 GB 4~8 GB
핵심 포인트: 4KB 페이지로는 L2 STLB 2,048개 엔트리를 모두 사용해도 겨우 8MB의 메모리만 TLB로 커버할 수 있습니다. 반면 2MB Huge Pages는 동일한 TLB 엔트리로 약 4GB, 1GB Huge Pages는 8GB를 커버합니다. 메모리 집약적 워크로드에서 TLB 미스 감소 효과는 수십 퍼센트의 성능 향상으로 직결됩니다.

Huge Pages의 두 가지 방식

항목hugetlbfs (정적 Huge Pages)THP (투명 대형 페이지)
할당 방식사전 예약 (부팅 시 또는 런타임)커널 자동 (on-demand)
지원 크기2MB, 1GB2MB (기본)
사용자 인터페이스mmap(MAP_HUGETLB), hugetlbfs 마운트투명 (madvise 힌트 가능)
OOM 위험낮음 (사전 예약)있음 (compaction 실패 시 fallback)
분할(split) 지원불가가능 (필요 시 4KB로 분할)
스왑 지원불가가능 (swap-out 시 분할 후 스왑)
대표 사용처DPDK, 데이터베이스, VM일반 애플리케이션 자동 최적화

x86_64 페이지 크기 계층

x86_64 아키텍처는 3가지 페이지 크기를 하드웨어적으로 지원합니다. 각 크기는 페이지 테이블의 서로 다른 레벨에서 매핑됩니다.

/* x86_64 페이지 크기 계층 */
4KB   = 2^12  →  PTE 레벨 매핑 (기본)
2MB   = 2^21  →  PMD 레벨 매핑 (512 x 4KB)
1GB   = 2^30  →  PUD 레벨 매핑 (512 x 2MB = 262,144 x 4KB)

페이지 크기와 TLB 효과 비교

4KB vs 2MB vs 1GB TLB 커버리지

동일한 TLB 엔트리 수에서 페이지 크기에 따른 커버 가능한 메모리 양의 차이를 비교합니다. 아래 다이어그램은 64개의 DTLB 엔트리를 기준으로 각 페이지 크기별 커버리지를 보여줍니다.

64개 DTLB 엔트리 기준 — 페이지 크기별 메모리 커버리지 64 GB 48 GB 32 GB 16 GB 0 256 KB 4KB 페이지 64 x 4KB 128 MB 2MB 페이지 64 x 2MB 64 GB 1GB 페이지 64 x 1GB x512 x512 4KB (기본) 2MB (PMD) 1GB (PUD)

TLB 미스율 시뮬레이션

아래 표는 연속적으로 접근하는 메모리 영역 크기별 TLB 미스 횟수를 비교합니다. (L1 DTLB 64개, L2 STLB 2,048개 기준)

접근 메모리 크기4KB 페이지 (필요 엔트리)4KB TLB 미스2MB 페이지 (필요 엔트리)2MB TLB 미스
8 MB2,048L2 경계40 (L1 캐시)
64 MB16,384빈번320 (L1 캐시)
512 MB131,072매우 빈번2560 (L2 캐시)
4 GB1,048,576극심2,048L2 경계
32 GB8,388,608극심16,384빈번
성능 효과: 실제 벤치마크에서 대용량 메모리를 순회하는 워크로드(데이터베이스 버퍼 풀, 과학 계산, VM 메모리 등)에서 THP 또는 hugetlbfs를 활성화하면 TLB 미스가 90% 이상 감소하고, 전체 성능이 5~30% 향상되는 사례가 보고됩니다.

hugetlbfs 아키텍처 — 예약 기반 정적 Huge Pages

hugetlbfs 개요

hugetlbfs는 커널 2.6부터 도입된 특수 파일시스템으로, 정적으로 예약된 Huge Pages를 사용자 공간에 제공합니다. Buddy 할당자에서 연속된 고차(order-9 또는 order-18) 물리 페이지를 미리 확보하여 전용 풀에 보관하며, 사용자가 hugetlbfs를 마운트하고 파일을 mmap하여 사용합니다.

hugetlbfs 예약 흐름

hugetlbfs 아키텍처: 예약 → 할당 → 매핑 흐름 사용자 공간 프로세스 A (mmap) 프로세스 B (shmget) libhugetlbfs sysctl / 부팅 파라미터 nr_hugepages 설정 커널 공간 hugetlbfs VFS 레이어 예약 관리 (resv_map) subpool 관리 HugeTLB 페이지 풀 free_huge_pages / nr_huge_pages NUMA Node 0 node_huge_pages[] NUMA Node 1 node_huge_pages[] Buddy 할당자 (order-9 / order-18) 물리 메모리 (연속 페이지 프레임) 초기 할당

hugetlbfs 페이지 풀 관리 구조체

/* mm/hugetlb.c - 핵심 전역 변수 */
struct hstate hstates[HUGE_MAX_HSTATE];
unsigned int default_hstate_idx;

struct hstate {
    struct mutex resize_lock;
    int next_nid_to_alloc;
    int next_nid_to_free;
    unsigned int order;           /* 2MB: order=9, 1GB: order=18 */
    unsigned int demote_order;
    unsigned long mask;
    unsigned long max_huge_pages;
    unsigned long nr_huge_pages;    /* 현재 총 Huge Pages 수 */
    unsigned long free_huge_pages;  /* 미사용 Huge Pages 수 */
    unsigned long resv_huge_pages;  /* 예약된 Huge Pages 수 */
    unsigned long surplus_huge_pages;
    unsigned long nr_overcommit_huge_pages;
    struct list_head hugepage_activelist;
    struct list_head hugepage_freelists[MAX_NUMNODES];
    unsigned int nr_huge_pages_node[MAX_NUMNODES];
    unsigned int free_huge_pages_node[MAX_NUMNODES];
    unsigned int surplus_huge_pages_node[MAX_NUMNODES];
    char name[HSTATE_NAME_LEN];
};
코드 설명
  • 2행 hstates 배열은 시스템이 지원하는 각 Huge Page 크기별 상태를 관리합니다. x86_64에서는 일반적으로 2MB와 1GB 두 가지입니다.
  • 6행 order 필드는 Buddy 할당자에서의 주문 크기입니다. 2MB는 order-9(512개의 4KB 페이지), 1GB는 order-18(262,144개)입니다.
  • 11~13행 핵심 카운터: 전체 수(nr_huge_pages), 여유 수(free_huge_pages), 예약 수(resv_huge_pages)로 풀 상태를 추적합니다.
  • 16~17행 활성 목록과 여유 목록을 NUMA 노드별로 분리하여, NUMA 지역성을 고려한 할당이 가능합니다.

hugetlbfs 마운트와 사용

# hugetlbfs 마운트
mount -t hugetlbfs -o pagesize=2M,size=4G,min_size=1G none /mnt/hugepages

# 2MB Huge Pages 예약 (1024개 = 2GB)
echo 1024 > /proc/sys/vm/nr_hugepages

# NUMA 노드별 예약
echo 512 > /sys/devices/system/node/node0/hugepages/hugepages-2048kB/nr_hugepages
echo 512 > /sys/devices/system/node/node1/hugepages/hugepages-2048kB/nr_hugepages

# 1GB Huge Pages 예약 (부팅 파라미터로만 안정적 할당 가능)
# 커널 부팅 파라미터: hugepagesz=1G hugepages=16

# 현재 상태 확인
cat /proc/meminfo | grep -i huge
/* 사용자 공간에서 mmap으로 Huge Page 매핑 */
#include <sys/mman.h>

void *alloc_hugepage(size_t size)
{
    void *addr = mmap(NULL, size,
                       PROT_READ | PROT_WRITE,
                       MAP_PRIVATE | MAP_ANONYMOUS | MAP_HUGETLB,
                       -1, 0);
    if (addr == MAP_FAILED) {
        perror("mmap MAP_HUGETLB");
        return NULL;
    }
    return addr;
}

/* 특정 크기 지정: MAP_HUGE_2MB 또는 MAP_HUGE_1GB */
void *alloc_1gb_hugepage(size_t size)
{
    void *addr = mmap(NULL, size,
                       PROT_READ | PROT_WRITE,
                       MAP_PRIVATE | MAP_ANONYMOUS | MAP_HUGETLB |
                       MAP_HUGE_1GB,
                       -1, 0);
    return (addr == MAP_FAILED) ? NULL : addr;
}

Transparent Huge Pages (THP) 메커니즘

THP 개요

Transparent Huge Pages(THP)는 커널 2.6.38에 도입된 메커니즘으로, 사용자 공간 애플리케이션의 수정 없이 커널이 자동으로 2MB 대형 페이지를 할당하고 관리합니다. hugetlbfs와 달리 사전 예약이 필요 없고, 할당 실패 시 자동으로 4KB 페이지로 폴백합니다.

THP 동작 모드

모드설정값동작사용 사례
always always 모든 익명 메모리 매핑에 THP 시도 메모리 집약적 서버, 일반 데스크탑
madvise madvise madvise(MADV_HUGEPAGE) 힌트를 준 영역만 THP 적용 데이터베이스, 선택적 최적화
never never THP 완전 비활성화 지연 민감 실시간 시스템
THP 페이지 폴트 처리 흐름 Anonymous Page Fault THP 활성화? (enabled/madvise) 4KB 일반 할당 No Yes VMA >= 2MB? 정렬 확인 4KB 폴백 할당 No Yes compound page 할당 시도 할당 성공? PMD 직접 매핑 Yes 2MB Huge Page 성공 compaction 시도 후 4KB 폴백 할당 No khugepaged가 나중에 병합

THP 설정 인터페이스

# THP 모드 설정
echo always > /sys/kernel/mm/transparent_hugepage/enabled
echo madvise > /sys/kernel/mm/transparent_hugepage/enabled
echo never  > /sys/kernel/mm/transparent_hugepage/enabled

# defrag 정책 (할당 실패 시 compaction 수행 여부)
echo always  > /sys/kernel/mm/transparent_hugepage/defrag   # 항상 compaction
echo defer   > /sys/kernel/mm/transparent_hugepage/defrag   # kswapd에게 위임
echo madvise > /sys/kernel/mm/transparent_hugepage/defrag   # MADV_HUGEPAGE만
echo never   > /sys/kernel/mm/transparent_hugepage/defrag   # compaction 안 함

# THP 통계 확인
cat /proc/vmstat | grep thp
# thp_fault_alloc: THP 할당 성공 횟수
# thp_fault_fallback: THP 할당 실패 (4KB 폴백) 횟수
# thp_collapse_alloc: khugepaged 병합 성공 횟수
# thp_split_page: THP 분할 횟수

THP 핵심 코드 경로

/* mm/huge_memory.c - THP 페이지 폴트 핸들러 */
static vm_fault_t __do_huge_pmd_anonymous_page(
    struct vm_fault *vmf, struct page *page,
    gfp_t gfp)
{
    struct vm_area_struct *vma = vmf->vma;
    pgtable_t pgtable;
    unsigned long haddr = vmf->address & HPAGE_PMD_MASK;

    /* 2MB 정렬된 주소 확인 */
    VM_BUG_ON_PAGE(!PageCompound(page), page);
    VM_BUG_ON_PAGE(!PageHead(page), page);

    /* 페이지 초기화 (zeroing) */
    clear_huge_page(page, vmf->address, HPAGE_PMD_NR);

    /* PMD 엔트리를 직접 설정 (PTE 레벨 건너뜀) */
    __SetPageUptodate(page);

    spin_lock(vmf->ptl);
    if (unlikely(!pmd_none(*vmf->pmd))) {
        spin_unlock(vmf->ptl);
        goto out;
    }

    entry = mk_huge_pmd(page, vma->vm_page_prot);
    entry = maybe_pmd_mkwrite(pmd_mkdirty(entry), vma);
    page_add_new_anon_rmap(page, vma, haddr);
    lru_cache_add_inactive_or_unevictable(page, vma);
    pgtable_trans_huge_deposit(vma->vm_mm, vmf->pmd, pgtable);
    set_pmd_at(vma->vm_mm, haddr, vmf->pmd, entry);

    spin_unlock(vmf->ptl);
    return VM_FAULT_NOPAGE;

out:
    mem_cgroup_uncharge(page);
    put_page(page);
    return VM_FAULT_FALLBACK;
}
코드 설명
  • 8행 HPAGE_PMD_MASK로 주소를 2MB 경계에 정렬합니다. THP는 반드시 2MB 정렬된 가상 주소에 매핑됩니다.
  • 11~12행 할당된 페이지가 compound page(Head 페이지)인지 검증합니다. THP는 항상 compound page 형태입니다.
  • 15행 clear_huge_page()는 512개의 4KB 페이지를 한꺼번에 0으로 초기화합니다. HPAGE_PMD_NR은 512입니다.
  • 26행 mk_huge_pmd()가 PMD 엔트리를 생성합니다. PTE 레벨을 건너뛰고 PMD에서 직접 2MB 물리 프레임을 가리킵니다.
  • 30행 pgtable_trans_huge_deposit()는 나중에 THP가 분할될 때 사용할 PTE 페이지 테이블을 미리 보관합니다.
  • 31행 set_pmd_at()으로 PMD 엔트리를 원자적으로 설정하여 2MB 매핑을 완성합니다.

khugepaged 데몬 동작

khugepaged 개요

khugepaged는 THP 프레임워크의 백그라운드 병합 데몬입니다. 주기적으로 프로세스의 가상 메모리 영역을 스캔하여, 연속된 512개의 4KB 페이지가 동일한 VMA에 속하고 병합 조건을 만족하면 하나의 2MB THP로 통합(collapse)합니다.

khugepaged 스캔 및 병합(collapse) 흐름 Sleep scan_sleep_ms mm_struct 선택 라운드 로빈 스캔 VMA 순회 hugepage_vma_check() 2MB 정렬 확인 HPAGE_PMD_MASK 512개 PTE 스캔 __collapse_huge_page_swapin() max_ptes_none, max_ptes_swap 검사 병합 가능? 건너뜀 (다음 VMA) Yes compound page 할당 alloc_charge_hpage(order=9) 512개 페이지 복사 copy_page(hpage + i, pages[i]) PMD 엔트리 교체 set_pmd_at() + TLB flush 병합 완료 thp_collapse_alloc++ 반복

khugepaged 튜닝 파라미터

# khugepaged 스캔 간격 (밀리초, 기본 10000 = 10초)
echo 5000 > /sys/kernel/mm/transparent_hugepage/khugepaged/scan_sleep_millisecs

# 한 번의 스캔에서 처리할 최대 페이지 수 (기본 4096)
echo 8192 > /sys/kernel/mm/transparent_hugepage/khugepaged/pages_to_scan

# 비어 있는 PTE 최대 허용 수 (기본 511, 최대 511)
echo 511 > /sys/kernel/mm/transparent_hugepage/khugepaged/max_ptes_none

# 스왑된 PTE 최대 허용 수 (기본 64)
echo 64 > /sys/kernel/mm/transparent_hugepage/khugepaged/max_ptes_swap

# 공유 PTE 최대 허용 수 (기본 256)
echo 256 > /sys/kernel/mm/transparent_hugepage/khugepaged/max_ptes_shared

Huge Page 할당 경로 — alloc_hugepage, compound page

compound page 구조

Huge Page는 내부적으로 compound page로 구현됩니다. 연속된 2^order개의 물리 페이지(struct page)를 하나의 논리적 단위로 묶어, 첫 번째 페이지가 Head 페이지, 나머지가 Tail 페이지가 됩니다.

compound page 구조 (order-9, 2MB Huge Page) Head Page PG_head 플래그 _refcount compound_order=9 Tail[0] PG_tail compound_head → Head Tail[1] PG_tail compound_head → Head ... Tail[510] PG_tail compound_head → Head Tail[511] PG_tail compound_head → Head 총 512개 Tail 페이지 (각 4KB) = 512 x 4KB = 2MB compound_dtor free_compound_page() 또는 free_transhuge_page() 소멸자 compound_mapcount 전체 compound page 매핑 횟수 compound_pincount GUP(pin_user_pages) 핀 횟수 각 Tail 페이지의 compound_head 포인터는 Head 페이지를 가리켜 임의의 Tail 페이지에서 O(1)으로 Head 페이지를 찾을 수 있습니다.

Huge Page 할당 코드 경로

/* mm/hugetlb.c - Huge Page 할당 핵심 함수 */
static struct page *dequeue_huge_page_vma(
    struct hstate *h,
    struct vm_area_struct *vma,
    unsigned long address, int avoid_reserve,
    long chg)
{
    struct page *page;
    struct zonelist *zonelist;
    struct zone *zone;
    struct zoneref *z;
    nodemask_t *nodemask;
    int nid;

    /* NUMA 정책에 따른 할당 우선순위 결정 */
    nid = huge_node(vma, address, huge_page_shift(h), &nodemask);
    zonelist = node_zonelist(nid, htlb_alloc_mask(h));

    /* 여유 풀에서 페이지 꺼내기 */
    for_each_zone_zonelist_nodemask(zone, z, zonelist,
                                     MAX_NR_ZONES - 1, nodemask) {
        nid = zone_to_nid(zone);
        if (!list_empty(&h->hugepage_freelists[nid])) {
            page = list_entry(
                h->hugepage_freelists[nid].next,
                struct page, lru);
            list_move(&page->lru, &h->hugepage_activelist);
            set_page_refcounted(page);
            h->free_huge_pages--;
            h->free_huge_pages_node[nid]--;
            return page;
        }
    }
    return NULL;
}

페이지 테이블 구조 — PMD 직접 매핑

4KB vs 2MB 페이지 테이블 비교

일반 4KB 페이지는 4단계(PGD → PUD → PMD → PTE)의 페이지 테이블 워크가 필요하지만, 2MB Huge Page는 PMD에서 직접 물리 프레임을 가리키므로 PTE 레벨이 생략됩니다. 1GB Huge Page는 PUD에서 직접 매핑하여 PMD와 PTE 두 레벨을 모두 건너뜁니다.

x86_64 페이지 테이블 계층: 4KB vs 2MB vs 1GB 매핑 비교 4KB 페이지 (4단계) PGD PUD (P4D) PMD PTE 4KB 물리 페이지 2MB Huge Page (3단계) PGD PUD (P4D) PMD (PSE 비트=1) PTE (생략) 2MB 물리 프레임 1GB Huge Page (2단계) PGD PUD (PSE 비트=1) PMD (생략) PTE (생략) 1GB 물리 프레임 TLB 미스 시 메모리 접근 횟수 (페이지 테이블 워크) 4KB: 4회 PGD → PUD → PMD → PTE 2MB: 3회 PGD → PUD → PMD(직접) 1GB: 2회 PGD → PUD(직접)

PMD 엔트리 구조 (x86_64)

/* arch/x86/include/asm/pgtable_types.h */
/*
 * PMD 엔트리 비트 필드 (2MB Huge Page 매핑 시)
 *
 * [63]    NX (No Execute)
 * [62:52] 소프트웨어 사용
 * [51:21] 물리 프레임 번호 (2MB 정렬)
 * [20:13] PAT, 소프트웨어 예약
 * [12]    PAT (Page Attribute Table)
 * [11:9]  소프트웨어 사용 (linux: _PAGE_SOFT_DIRTY 등)
 * [8]     Global
 * [7]     PS (Page Size) = 1 → 2MB Huge Page 표시
 * [6]     Dirty
 * [5]     Accessed
 * [4]     PCD (Cache Disable)
 * [3]     PWT (Write Through)
 * [2]     U/S (User/Supervisor)
 * [1]     R/W (Read/Write)
 * [0]     Present
 */

#define _PAGE_BIT_PSE     7    /* Page Size Extension: 1=대형 페이지 */
#define _PAGE_PSE         (1UL << _PAGE_BIT_PSE)

/* PMD가 Huge Page인지 확인 */
static inline int pmd_large(pmd_t pmd)
{
    return pmd_flags(pmd) & _PAGE_PSE;
}

/* THP를 위한 PMD 생성 */
static inline pmd_t mk_huge_pmd(struct page *page, pgprot_t pgprot)
{
    return pfn_pmd(page_to_pfn(page),
                   __pgprot(pgprot_val(pgprot) | _PAGE_PSE));
}

Huge Page와 NUMA

NUMA 토폴로지와 Huge Page 할당

NUMA(Non-Uniform Memory Access) 시스템에서 Huge Page의 할당 위치는 성능에 큰 영향을 미칩니다. 원격 NUMA 노드에서 할당된 Huge Page는 로컬 노드 대비 30~50% 더 높은 접근 지연시간을 보입니다. 커널은 NUMA 정책(mbind, set_mempolicy)과 연계하여 Huge Page 할당 노드를 결정합니다.

NUMA 시스템의 Huge Page 풀 구조 NUMA Node 0 CPU 0~15 (소켓 0) 메모리 컨트롤러 0 2MB HugePage 풀 (Node 0) hugepage_freelists[0]: 512개 예약 1GB HugePage 풀 (Node 0) hugepage_freelists[0]: 8개 예약 일반 4KB 페이지 + Buddy 시스템 NUMA Node 1 CPU 16~31 (소켓 1) 메모리 컨트롤러 1 2MB HugePage 풀 (Node 1) hugepage_freelists[1]: 512개 예약 1GB HugePage 풀 (Node 1) hugepage_freelists[1]: 8개 예약 일반 4KB 페이지 + Buddy 시스템 QPI/UPI 인터커넥트 (원격 접근 시 지연 증가) 로컬 접근: ~70ns 원격 접근: ~130ns

NUMA 정책과 Huge Page

/* NUMA 정책을 적용한 Huge Page 할당 예제 */
#include <numaif.h>
#include <sys/mman.h>

void *alloc_numa_hugepage(size_t size, int node)
{
    void *addr;
    unsigned long nodemask = 1UL << node;

    /* NUMA 바인드 정책 설정 */
    set_mempolicy(MPOL_BIND, &nodemask, sizeof(nodemask) * 8);

    /* Huge Page 할당 */
    addr = mmap(NULL, size,
                PROT_READ | PROT_WRITE,
                MAP_PRIVATE | MAP_ANONYMOUS | MAP_HUGETLB,
                -1, 0);

    /* 기본 정책 복원 */
    set_mempolicy(MPOL_DEFAULT, NULL, 0);

    return (addr == MAP_FAILED) ? NULL : addr;
}
# NUMA 노드별 Huge Page 예약 상태 확인
cat /sys/devices/system/node/node0/hugepages/hugepages-2048kB/nr_hugepages
cat /sys/devices/system/node/node0/hugepages/hugepages-2048kB/free_hugepages
cat /sys/devices/system/node/node1/hugepages/hugepages-2048kB/nr_hugepages
cat /sys/devices/system/node/node1/hugepages/hugepages-2048kB/free_hugepages

# numactl을 사용한 Huge Page 바인딩
numactl --membind=0 --hugepage ./my_application

HugeTLB 예약 시스템 — resv_map, subpool

예약(Reservation) 메커니즘

hugetlbfs에서 mmap()을 호출하면 즉시 물리 페이지를 할당하지 않고, 예약만 수행합니다. 이는 실제 페이지 폴트가 발생할 때까지 물리 할당을 지연하되, 할당 실패가 발생하지 않도록 여유 페이지 수를 미리 확보하는 방식입니다.

/* include/linux/hugetlb.h - 예약 맵 구조체 */
struct resv_map {
    struct kref refs;
    spinlock_t lock;
    struct list_head regions;        /* 예약된 영역 리스트 */
    long adds_in_progress;            /* 진행 중인 추가 수 */
    struct list_head region_cache;    /* 영역 캐시 */
    long region_cache_count;
};

/* 예약 영역 단위 */
struct file_region {
    struct list_head link;
    long from;                        /* 시작 인덱스 (Huge Page 단위) */
    long to;                          /* 끝 인덱스 */
};

/* subpool: 마운트별 Huge Page 제한 */
struct hugepage_subpool {
    spinlock_t lock;
    long count;                       /* 현재 사용 중인 페이지 수 */
    long max_hpages;                  /* 최대 허용 페이지 수 (size= 옵션) */
    long used_hpages;
    struct hstate *hstate;
    long min_hpages;                  /* 최소 보장 페이지 수 (min_size= 옵션) */
    long rsv_hpages;                  /* 예약 중인 페이지 수 */
};
코드 설명
  • 3~8행 resv_map은 파일(inode)당 하나 생성됩니다. regions 리스트로 예약된 인덱스 범위를 추적합니다.
  • 12~15행 file_region은 연속된 예약 범위를 [from, to) 형태로 표현합니다. 범위가 겹치면 병합됩니다.
  • 19~26행 hugepage_subpool은 hugetlbfs 마운트 포인트별로 Huge Page 사용량을 제한합니다. mount -o size=4G로 설정합니다.

예약 흐름

  1. mmap(MAP_HUGETLB) 호출
  2. 커널이 hugetlb_reserve_pages()를 호출하여 필요한 Huge Page 수 계산
  3. resv_map에 예약 범위 추가
  4. 전역 풀에서 resv_huge_pages 카운터 증가
  5. subpool이 있으면 subpool 카운터도 증가
  6. 실제 페이지 폴트 시 alloc_huge_page()가 예약된 풀에서 할당
  7. munmap() 시 미사용 예약분 반환

Huge Page 마이그레이션과 compaction

THP 분할 (Split)

THP는 필요에 따라 512개의 4KB 페이지로 분할될 수 있습니다. 분할이 발생하는 주요 상황:

/* mm/huge_memory.c - THP 분할 핵심 */
int split_huge_page_to_list(struct page *page,
                            struct list_head *list)
{
    struct page *head = compound_head(page);
    struct deferred_split *ds_queue;
    int ret;

    /* Head 페이지의 참조 카운트 확인 */
    if (!PageCompound(page))
        return 0;

    /* anon_vma 잠금 (역방향 매핑 보호) */
    anon_vma_lock_write(head->mapping);

    /* PMD 엔트리를 512개 PTE로 교체 */
    ret = __split_huge_page(page, list, end);

    /* 통계 업데이트 */
    if (!ret)
        count_vm_event(THP_SPLIT_PAGE);

    return ret;
}

Memory Compaction과 Huge Page

THP 할당이 실패하면 커널은 memory compaction을 시도합니다. Compaction은 사용 중인 4KB 페이지를 한쪽으로 이동시켜 연속된 빈 영역을 만들고, 이 영역에서 2MB compound page를 할당합니다.

# compaction 관련 통계
cat /proc/vmstat | grep compact
# compact_stall: compaction 대기 횟수
# compact_success: compaction 성공 횟수
# compact_fail: compaction 실패 횟수

# 수동 compaction 트리거
echo 1 > /proc/sys/vm/compact_memory

# proactive compaction 설정 (커널 5.9+)
echo 20 > /proc/sys/vm/compaction_proactiveness
compaction 비용: Memory compaction은 페이지 이동을 수반하므로 CPU 시간과 I/O를 소비합니다. 실시간 시스템이나 지연 민감 워크로드에서는 defrag=never로 설정하여 compaction을 비활성화하고, 대신 hugetlbfs 사전 예약을 사용하는 것이 바람직합니다.

사용자 공간 인터페이스 — mmap, shmget, madvise

mmap을 통한 Huge Page 할당

/* MAP_HUGETLB를 사용한 Huge Page 할당 */
#include <sys/mman.h>
#include <stdio.h>
#include <string.h>

#define HUGEPAGE_SIZE  (2 * 1024 * 1024)  /* 2MB */
#define NUM_PAGES      64

int main(void)
{
    size_t total_size = HUGEPAGE_SIZE * NUM_PAGES;  /* 128MB */

    /* 익명 Huge Page 할당 */
    void *addr = mmap(NULL, total_size,
                      PROT_READ | PROT_WRITE,
                      MAP_PRIVATE | MAP_ANONYMOUS | MAP_HUGETLB |
                      MAP_HUGE_2MB,    /* 2MB 명시 */
                      -1, 0);
    if (addr == MAP_FAILED) {
        perror("mmap");
        return 1;
    }

    /* 메모리 사용 */
    memset(addr, 0xAB, total_size);
    printf("Huge Pages 할당 성공: %p, 크기: %zu MB\n",
           addr, total_size / (1024 * 1024));

    /* 해제 */
    munmap(addr, total_size);
    return 0;
}

shmget을 통한 공유 Huge Page

/* System V 공유 메모리로 Huge Page 사용 */
#include <sys/ipc.h>
#include <sys/shm.h>

#define SHM_SIZE  (256 * 1024 * 1024)  /* 256MB */

int shmid = shmget(IPC_PRIVATE, SHM_SIZE,
                   IPC_CREAT | SHM_HUGETLB | 0666);
if (shmid < 0) {
    perror("shmget SHM_HUGETLB");
    return 1;
}

void *addr = shmat(shmid, NULL, 0);
if (addr == (void *)-1) {
    perror("shmat");
    return 1;
}

/* 사용 후 해제 */
shmdt(addr);
shmctl(shmid, IPC_RMID, NULL);

madvise를 통한 THP 힌트

/* THP madvise 모드에서 선택적 활성화 */
#include <sys/mman.h>

void *addr = mmap(NULL, size,
                  PROT_READ | PROT_WRITE,
                  MAP_PRIVATE | MAP_ANONYMOUS,
                  -1, 0);

/* 이 영역에 THP를 적용해달라고 커널에 힌트 */
madvise(addr, size, MADV_HUGEPAGE);

/* THP를 비활성화하려면 */
madvise(addr, size, MADV_NOHUGEPAGE);

/* THP 분할을 자발적으로 요청 (커널 5.4+) */
madvise(addr, size, MADV_PAGEOUT);   /* 스왑 아웃 힌트 (분할 수반) */
madvise(addr, size, MADV_COLD);      /* 비활성 페이지로 표시 */

커널 설정과 부팅 파라미터

Kconfig 옵션

# Huge Pages 기본 지원
CONFIG_HUGETLBFS=y           # hugetlbfs 파일시스템 활성화
CONFIG_HUGETLB_PAGE=y        # Huge Page 인프라 (자동 선택됨)

# Transparent Huge Pages
CONFIG_TRANSPARENT_HUGEPAGE=y          # THP 지원 활성화
CONFIG_TRANSPARENT_HUGEPAGE_ALWAYS=y   # 기본 모드: always
CONFIG_TRANSPARENT_HUGEPAGE_MADVISE=y  # 기본 모드: madvise

# Huge Page 관련 고급 옵션
CONFIG_HUGETLB_PAGE_OPTIMIZE_VMEMMAP=y # HVO: vmemmap 최적화 (커널 6.1+)
CONFIG_ARCH_HAS_HUGEPD=y               # 아키텍처별 HugePD 지원
CONFIG_HUGETLB_PAGE_SIZE_VARIABLE=y    # 가변 Huge Page 크기 지원

# compaction 관련
CONFIG_COMPACTION=y          # 메모리 compaction (THP에 필수)
CONFIG_MIGRATION=y           # 페이지 마이그레이션 지원

부팅 파라미터

파라미터설명예제
hugepagesz= Huge Page 크기 지정 hugepagesz=2M, hugepagesz=1G
hugepages= 지정된 크기의 예약 페이지 수 hugepages=1024
default_hugepagesz= 기본 Huge Page 크기 default_hugepagesz=2M
transparent_hugepage= THP 초기 모드 transparent_hugepage=madvise
hugepages=0:512,1:512 NUMA 노드별 예약 (커널 6.1+) Node0에 512개, Node1에 512개
# GRUB 부팅 파라미터 예제 (/etc/default/grub)
GRUB_CMDLINE_LINUX="default_hugepagesz=2M hugepagesz=2M hugepages=2048 hugepagesz=1G hugepages=16 transparent_hugepage=madvise"

# 효과: 2MB x 2048 = 4GB (2MB 풀) + 1GB x 16 = 16GB (1GB 풀)

성능 벤치마크와 튜닝

TLB 미스율 비교 벤치마크

아래 차트는 대규모 메모리 접근 워크로드에서 4KB 페이지와 2MB THP, 1GB hugetlbfs의 상대적 TLB 미스율과 처리량 차이를 보여줍니다.

16GB 메모리 랜덤 접근 벤치마크: TLB 미스율 및 처리량 비교 TLB 미스율 (상대값 %) 100% 75% 50% 25% 0% 100% 4KB 기준값 8% 2MB THP 6% 2MB hugetlbfs 0.4% 1GB hugetlbfs 상대 처리량 1.0x 4KB 1.15x THP 1.20x 2MB 1.30x 1GB 4KB 일반 2MB THP 2MB hugetlbfs 1GB hugetlbfs

perf를 활용한 TLB 미스 측정

# TLB 미스 이벤트 측정
perf stat -e dTLB-load-misses,dTLB-loads,iTLB-load-misses,iTLB-loads \
    -e dTLB-store-misses,dTLB-stores \
    ./my_application

# 출력 예시:
#  12,345,678  dTLB-load-misses     # 0.45% of all dTLB loads
#  2,741,234,567  dTLB-loads
#  1,234,567  iTLB-load-misses

# THP 활성화 후 동일 측정으로 미스율 비교
echo always > /sys/kernel/mm/transparent_hugepage/enabled
perf stat -e dTLB-load-misses,dTLB-loads ./my_application

# 페이지 워크 사이클 측정 (Intel 프로세서)
perf stat -e cpu/event=0x08,umask=0x01,name=dtlb_load_misses_walk_completed/ \
    ./my_application

주요 튜닝 가이드라인

워크로드추천 설정이유
데이터베이스 (PostgreSQL, MySQL) THP=madvise, defrag=madvise DB가 공유 버퍼에만 선택적으로 THP 적용
Redis / 인메모리 캐시 THP=never (hugetlbfs 사용 권장) THP 분할/병합의 지연 스파이크 방지
DPDK 패킷 처리 hugetlbfs 1GB 전용 예약 최대 TLB 효율, 고정 할당 보장
KVM/QEMU VM THP=always 또는 hugetlbfs 백엔드 VM 메모리의 대부분이 대형 페이지 활용 가능
과학 계산 / HPC hugetlbfs 2MB/1GB 사전 예약 대규모 배열 순회 시 TLB 미스 최소화
일반 데스크탑 THP=always, defrag=defer+madvise 자동 최적화, 사용자 개입 불필요

모니터링과 디버깅

/proc/meminfo Huge Page 항목

# Huge Page 관련 /proc/meminfo 항목
cat /proc/meminfo | grep -i huge

# 출력 예시:
# AnonHugePages:    524288 kB    ← THP로 매핑된 익명 메모리
# ShmemHugePages:        0 kB    ← THP로 매핑된 공유 메모리
# FileHugePages:         0 kB    ← THP로 매핑된 파일 캐시
# HugePages_Total:    1024       ← 예약된 총 Huge Pages 수
# HugePages_Free:      512       ← 미사용 Huge Pages 수
# HugePages_Rsvd:      256       ← 예약되었지만 아직 할당 안 된 수
# HugePages_Surp:        0       ← surplus (초과 할당) 수
# Hugepagesize:       2048 kB    ← 기본 Huge Page 크기
# Hugetlb:          2097152 kB   ← HugeTLB에 사용 중인 총 메모리

/sys 파일시스템 인터페이스

# hugetlbfs 풀 상태
ls /sys/kernel/mm/hugepages/
# hugepages-1048576kB/  hugepages-2048kB/

# 2MB Huge Page 상세 정보
cat /sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages
cat /sys/kernel/mm/hugepages/hugepages-2048kB/free_hugepages
cat /sys/kernel/mm/hugepages/hugepages-2048kB/resv_hugepages
cat /sys/kernel/mm/hugepages/hugepages-2048kB/surplus_hugepages
cat /sys/kernel/mm/hugepages/hugepages-2048kB/nr_overcommit_hugepages

# THP 상태 및 설정
cat /sys/kernel/mm/transparent_hugepage/enabled
# [always] madvise never

cat /sys/kernel/mm/transparent_hugepage/defrag
cat /sys/kernel/mm/transparent_hugepage/use_zero_page
cat /sys/kernel/mm/transparent_hugepage/hpage_pmd_size
# 2097152 (= 2MB)

# khugepaged 통계
cat /sys/kernel/mm/transparent_hugepage/khugepaged/pages_collapsed
cat /sys/kernel/mm/transparent_hugepage/khugepaged/full_scans

프로세스별 Huge Page 사용 현황

# 특정 프로세스의 smaps에서 Huge Page 매핑 확인
cat /proc/<pid>/smaps | grep -E "(AnonHugePages|ShmemPmd|FilePmd)"

# 간략한 요약
cat /proc/<pid>/smaps_rollup | grep -i huge
# AnonHugePages:    262144 kB

# 프로세스의 THP 사용 비율 계산
# AnonHugePages / (Anonymous 총 메모리) x 100 = THP 활용률

# 모든 프로세스의 Huge Page 사용량 정렬
for pid in /proc/[0-9]*; do
    hp=$(grep AnonHugePages "$pid/smaps_rollup" 2>/dev/null | awk '{print $2}')
    [ -n "$hp" ] && [ "$hp" -gt 0 ] && \
        echo "$hp kB  $(cat $pid/comm 2>/dev/null)  (PID: $(basename $pid))"
done | sort -rn | head -20

/proc/vmstat THP 통계

# THP 관련 vmstat 카운터
grep thp /proc/vmstat

# 주요 카운터 의미:
# thp_fault_alloc         - 페이지 폴트 시 THP 할당 성공
# thp_fault_fallback      - 페이지 폴트 시 THP 할당 실패 (4KB 폴백)
# thp_collapse_alloc      - khugepaged 병합 성공
# thp_collapse_alloc_failed - khugepaged 병합 실패
# thp_split_page          - THP 분할 (page 단위)
# thp_split_pmd           - PMD 분할 (매핑 단위)
# thp_zero_page_alloc     - 제로 THP 할당
# thp_deferred_split_page - 분할 지연 대기 중인 THP
# thp_swpout             - 스왑 아웃된 THP 수
# thp_swpout_fallback    - THP 스왑 아웃 실패 (분할 후 스왑)

실전 사용 사례

데이터베이스 (PostgreSQL)

PostgreSQL의 공유 버퍼(shared_buffers)는 대규모 메모리를 사용하므로 Huge Pages 적용 시 상당한 성능 향상을 얻을 수 있습니다.

# PostgreSQL Huge Pages 설정

# 1. 필요한 Huge Pages 수 계산
# shared_buffers = 8GB일 때: 8GB / 2MB = 4096개
# 여유분 포함: 4096 + 100 = 4196개
echo 4196 > /proc/sys/vm/nr_hugepages

# 2. PostgreSQL 설정 (postgresql.conf)
# huge_pages = try   # 또는 on (실패 시 시작 불가)
# shared_buffers = 8GB

# 3. postgres 사용자에게 huge page 권한 부여
# /etc/sysctl.conf:
# vm.hugetlb_shm_group = <postgres GID>

# 4. 확인
grep -i huge /proc/meminfo

DPDK 고성능 패킷 처리

DPDK(Data Plane Development Kit)는 커널을 우회하는 사용자 공간 패킷 처리 프레임워크로, 1GB Huge Pages를 사용하여 TLB 미스를 최소화하고 패킷 버퍼 접근 속도를 극대화합니다.

# DPDK용 1GB Huge Pages 설정

# 1. 부팅 파라미터 설정
# hugepagesz=1G hugepages=16 default_hugepagesz=1G

# 2. 또는 런타임에 2MB Huge Pages 예약
echo 8192 > /sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages

# 3. hugetlbfs 마운트
mkdir -p /dev/hugepages-1G
mount -t hugetlbfs -o pagesize=1G none /dev/hugepages-1G

mkdir -p /dev/hugepages-2M
mount -t hugetlbfs -o pagesize=2M none /dev/hugepages-2M

# 4. DPDK EAL 파라미터
./dpdk-app --socket-mem 4096,4096 --huge-dir /dev/hugepages-1G

# 5. DPDK 메모리 정보 확인
dpdk-proc-info -- --stats

KVM/QEMU 가상 머신

KVM 가상 머신의 메모리를 Huge Pages로 백엔드하면 VM 내부의 메모리 접근 성능이 크게 향상됩니다. 특히 중첩 페이지 테이블(EPT/NPT)을 사용하는 환경에서 Huge Pages는 2단계 변환의 TLB 미스를 함께 줄여줍니다.

# QEMU에서 hugetlbfs 백엔드 사용
qemu-system-x86_64 \
    -m 16G \
    -mem-path /dev/hugepages-2M \
    -mem-prealloc \
    -smp 8 \
    -enable-kvm \
    ...

# libvirt XML 설정 (hugepages 사용)
# <memoryBacking>
#   <hugepages>
#     <page size="2048" unit="KiB"/>
#   </hugepages>
# </memoryBacking>

# NUMA별 Huge Page 크기 지정 (libvirt)
# <memoryBacking>
#   <hugepages>
#     <page size="1048576" unit="KiB" nodeset="0"/>
#     <page size="2048" unit="KiB" nodeset="1"/>
#   </hugepages>
# </memoryBacking>
워크로드별 Huge Page 추천 구성 Huge Pages 2MB / 1GB 대형 페이지 데이터베이스 hugetlbfs + THP=madvise DPDK / SmartNIC hugetlbfs 1GB 전용 KVM / QEMU hugetlbfs + THP=always HPC / 과학 계산 hugetlbfs 2MB/1GB Redis / 캐시 THP=never, hugetlbfs 일반 서버/데스크탑 THP=always, defrag=defer THP의 분할/병합 지연이 문제되는 지연 민감 워크로드는 hugetlbfs 정적 예약을 우선 고려
Redis와 THP 경고: Redis는 공식적으로 THP 비활성화를 권장합니다. THP의 분할/병합 과정에서 발생하는 지연 스파이크가 Redis의 밀리초 단위 응답 시간에 악영향을 미칩니다. Redis 서버에서는 반드시 echo never > /sys/kernel/mm/transparent_hugepage/enabled를 실행하세요. Redis 시작 시 이 설정을 경고하는 로그도 출력됩니다.

HVO — HugeTLB Vmemmap Optimization

HugeTLB 페이지는 실제 데이터 페이지 외에도 메타데이터(struct page) 배열이 필요합니다. 1GB Huge Page 하나는 4KB 페이지 262,144개로 구성되므로, 기본 방식에서는 struct page도 같은 수만큼 필요합니다. 커널 6.x의 HVO(HugeTLB Vmemmap Optimization)는 Tail 페이지의 vmemmap을 공유/재활용하여 메타데이터 메모리 오버헤드를 크게 줄입니다.

HugeTLB vmemmap 오버헤드와 HVO 최적화 기본 방식 (최적화 없음) 1GB Huge Page = 262,144개의 struct page Head + 모든 Tail 페이지 각각 vmemmap 엔트리 유지 메타데이터 메모리 소비: 262,144 x sizeof(struct page) (대략 수 MB) Huge Page를 많이 쓰는 시스템에서 vmemmap 자체가 의미 있는 메모리 압박 요인 HVO 활성화 Tail 페이지 vmemmap 중복분 제거/재활용 필수 엔트리만 남기고 나머지는 재매핑/해제 효과: HugeTLB 메타데이터 오버헤드 감소 대규모 1GB Huge Page 풀에서 체감 큼 조건: CONFIG_HUGETLB_PAGE_OPTIMIZE_VMEMMAP=y
# HVO 지원 여부 점검
grep HUGETLB_PAGE_OPTIMIZE_VMEMMAP /boot/config-$(uname -r)

# 커널 로그에서 HugeTLB/VMEMMAP 관련 메시지 확인
dmesg | grep -Ei 'hugetlb|vmemmap|hvo'

cgroup v2와 컨테이너 Huge Pages

컨테이너 환경에서는 Huge Pages를 일반 메모리와 별도로 제한해야 합니다. cgroup v2는 페이지 크기별 hugepage 한도를 제공하며, overcommit 정책과 결합해 특정 워크로드가 HugeTLB 풀을 독점하지 않도록 제어합니다.

cgroup v2 HugeTLB 제어 계층 시스템 HugeTLB 풀 2MB / 1GB 전역 페이지 cgroup A (DB) hugetlb.2MB.max=8G hugetlb.1GB.max=4G cgroup B (DPDK) hugetlb.2MB.max=2G hugetlb.1GB.max=8G 핵심 포인트 메모리 cgroup(limit_in_bytes)와 HugeTLB cgroup은 별도 회계 한도 초과 시 MAP_HUGETLB 할당이 즉시 실패 (OOM killer와 경로 다름)
# cgroup v2 HugeTLB 한도 예시
CG=/sys/fs/cgroup/mydb
mkdir -p $CG
echo $((8*1024*1024*1024))  > $CG/hugetlb.2MB.max
echo $((4*1024*1024*1024))  > $CG/hugetlb.1GB.max

# 사용량/실패 통계
cat $CG/hugetlb.2MB.current
cat $CG/hugetlb.2MB.events

THP 분할과 NUMA demotion 경로

THP는 항상 유지되는 것이 아니라, 메모리 압박·NUMA 재배치·mprotect/munmap 이벤트로 분할될 수 있습니다. 특히 자동 NUMA balancing이 켜진 시스템에서는 원격 노드 접근 패턴에 따라 페이지 이동이 발생하고, 이 과정에서 PMD 단위 매핑이 PTE 단위로 강등될 수 있습니다.

THP 생애주기: 할당 → 유지 → 분할/재병합 PMD THP 생성 thp_fault_alloc++ 정상 실행 TLB 효율 최대 분할 이벤트 split_huge_page PTE 512개 4KB 모드 khugepaged 조건 충족 시 재병합 분할 유발 주요 원인 mprotect/munmap 부분 범위, swap, GUP pin, NUMA migration, 메모리 회수 분할이 잦으면 thp_split_* 카운터가 빠르게 증가
# THP 분할/병합 상태 추적
grep -E 'thp_split|thp_collapse|thp_fault' /proc/vmstat

# numa balancing과 함께 확인
grep -E 'numa_hint_faults|numa_pages_migrated' /proc/vmstat

Huge Pages 장애 대응 플레이북

Huge Pages 문제는 대부분 "할당 실패", "예상보다 낮은 THP 활용률", "지연 스파이크"로 나타납니다. 아래 절차로 원인을 분류하면 대응이 빨라집니다.

Huge Pages 문제 분석 절차 1) 증상 확인 mmap 실패/지연 스파이크 2) 풀 상태 점검 HugePages_Free/Rsvd 3) THP 통계 확인 thp_fault_fallback 4) 정책 조정 예약/defrag/모드 판정 기준 예시 MAP_HUGETLB 실패 + HugePages_Free=0: 예약 부족 또는 경쟁 사용 thp_fault_fallback 급증: compaction 실패/분산 메모리 단편화 thp_split_page 급증: workload 패턴상 분할 유발 (권한 변경/부분 unmap) 가능성
# 1) HugeTLB/THP 상태 요약
grep -i huge /proc/meminfo
grep -E 'thp_fault|thp_split|thp_collapse' /proc/vmstat

# 2) THP 모드/defrag 확인
cat /sys/kernel/mm/transparent_hugepage/enabled
cat /sys/kernel/mm/transparent_hugepage/defrag

# 3) compaction 압력 확인
grep compact /proc/vmstat

# 4) 프로세스별 HugePages 사용 확인
cat /proc/<pid>/smaps_rollup | grep -i huge
운영 권장: 지연 민감 서비스는 THP를 전역 madvise로 두고, 필요한 프로세스에만 MADV_HUGEPAGE를 적용하는 방식이 가장 예측 가능성이 높습니다.

khugepaged collapse 실패 분석

THP 성능을 기대했는데 실제로는 일반 페이지로 남는 경우, 핵심은 khugepaged의 collapse 실패 원인을 분리하는 것입니다. 연속 가상 주소가 있어도 물리 단편화, 참조 상태, 페이지 핀 고정 상태에 따라 collapse가 반복 실패할 수 있습니다.

khugepaged collapse 실패 분기 후보 VMA 스캔 khugepaged PMD 범위 512개 PTE 점검 mapcount/refcount/dirty 상태 collapse 성공/실패 다음 스캔 주기 주요 실패 원인 1) compaction 실패로 연속 물리 메모리 확보 불가 2) 장기 pin(FOLL_PIN/RDMA) 페이지 존재 3) 자주 split되는 접근 패턴 (부분 mprotect/munmap) 4) 정책 미스매치: madvise/always/never 설정 부적합

HugeTLB 풀 운영 정책 (예약/버스트/격리)

HugeTLB는 사전 예약 방식이라 서비스별 용량 계획이 중요합니다. 버스트 트래픽이 있는 서비스는 최소 예약과 버스트 여유를 분리하고, 컨테이너 환경에서는 cgroup 제한과 함께 운영해야 안정적입니다.

운영 시나리오권장 정책실패 시 신호
고정 워크로드 DB정적 hugepages 예약 + 재부팅 시 일관 적용MAP_HUGETLB 실패 없음, 지연 안정
버스트 분석 작업기본 예약 + 버스트 한도 별도HugePages_Free 급락 후 복구 지연
컨테이너 다중 테넌트cgroup huge limit로 테넌트 격리일부 테넌트 과점유/기아
# HugeTLB 풀 상태 확인
grep -i huge /proc/meminfo

# THP collapse/split 동향
grep -E 'thp_fault|thp_collapse|thp_split' /proc/vmstat

# 서비스별 smaps_rollup에서 huge 사용량 확인
cat /proc/<pid>/smaps_rollup | grep -Ei 'AnonHuge|FileHuge|Hugetlb'

File THP와 워크로드 적합성 판단

최근 커널에서는 익명 메모리뿐 아니라 파일 기반 매핑에서도 THP 효과를 노릴 수 있는 경로가 확대되고 있습니다. 하지만 모든 파일 I/O 패턴에 이득이 있는 것은 아니며, 재사용 지역성과 fault 패턴을 기준으로 판단해야 합니다.

File THP 적용 판단 흐름 파일 매핑 워크로드 read-mostly / random fault/reclaim 패턴 계측 file fault locality 평가 THP 정책 결정 적용/제외 분리 적합/비적합 예시 적합: 대형 순차 스캔 + 높은 재사용 지역성 + fault 집중 비적합: 랜덤 접근 + 잦은 split 유발 + reclaim 압력 높은 환경 결론: 전역 강제보다 워크로드별 선택 적용이 안정적 성능/메모리 효율/지연을 함께 측정해 의사결정
패턴기대 효과주의점
순차 읽기 중심 분석TLB miss 완화, fault 수 감소reclaim 시 큰 단위 회수 비용
랜덤 조회 캐시효과 제한적split/compaction 오버헤드 가능
혼합 워크로드프로세스/영역별 선택 적용 필요정책 일괄 적용 시 회귀 위험

참고자료

다음 학습: