HDFS 중앙 캐시 관리

HDFS 중앙 캐시 관리 (Centralized Cache Management in HDFS)

HDFS의 중앙 캐시 관리는 HDFS가 캐시할 경로를 사용자가 지정할 수 있게 하는 명시적 캐싱 메커니즘입니다. NameNode는 원하는 블록을 디스크에 가진 DataNode와 통신해, 블록을 off-heap 캐시에 캐시하도록 지시합니다.

출처: Centralized Cache Management in HDFS

HDFS의 중앙 캐시 관리는 많은 중요한 이점이 있습니다.

  • **명시적 고정(pinning)**은 자주 사용하는 데이터가 메모리에서 내려가는 것을 방지합니다. 이는 작업 집합(working set)의 크기가 주 메모리 크기를 초과할 때 특히 중요하며, 이는 많은 HDFS 워크로드에서 흔합니다.
  • DataNode 캐시는 NameNode가 관리하므로 애플리케이션은 태스크 배치 결정을 내릴 때 캐시된 블록 위치 집합을 조회할 수 있습니다. 태스크를 캐시된 블록 복제본과 동일 위치에 배치하면 읽기 성능이 향상됩니다.
  • 블록이 DataNode에 캐시되었으면 클라이언트는 새롭고 더 효율적인 제로 카피(zero-copy) 읽기 API를 사용할 수 있습니다. 캐시된 데이터의 체크섬 검증은 DataNode가 한 번 수행하므로, 클라이언트는 이 새 API를 사용할 때 사실상 오버헤드가 없습니다.
  • 중앙 캐싱은 전반적인 클러스터 메모리 활용을 향상시킬 수 있습니다. 각 DataNode의 OS 버퍼 캐시에 의존할 때, 블록에 대한 반복 읽기는 블록의 n개 복제본이 모두 버퍼 캐시로 로드됩니다. 중앙 캐시 관리를 사용하면 사용자는 n개 중 m개 복제본만 명시적으로 고정하여 n-m 메모리를 절약할 수 있습니다.
  • HDFS는 Linux 플랫폼에서 비휘발성 스토리지 클래스 메모리(SCM, persistent memory라고도 함) 캐시를 지원합니다. 사용자는 DataNode에 대해 DRAM 캐시 또는 SCM 캐시를 활성화할 수 있습니다. DRAM 캐시와 SCM 캐시는 DataNode 사이에 공존할 수 있습니다. 또한 SCM 캐시는 **캐시 영속성(persistence)**을 지원합니다. dfs.datanode.pmem.cache.recovery를 true로 설정하면 DataNode 시작 시 SCM에 영속화된 캐시 상태가 복구됩니다. 그렇지 않으면 이전에 영속화된 캐시는 버려지고 데이터를 다시 캐시해야 합니다.

사용 사례 (Use Cases)

중앙 캐시 관리는 반복적으로 접근하는 파일에 유용합니다. 예를 들어 조인에 자주 사용되는 Hive의 작은 팩트 테이블(fact table)이 좋은 캐싱 후보입니다. 반면 1년 치 보고 쿼리의 입력을 캐시하는 것은 덜 유용할 수 있습니다. 기록 데이터는 한 번만 읽힐 수 있기 때문입니다.

중앙 캐시 관리는 성능 SLA가 있는 혼합 워크로드에도 유용합니다. 우선순위가 높은 워크로드의 작업 집합을 캐시하면 우선순위가 낮은 워크로드와 디스크 I/O를 두고 경쟁하지 않도록 보장합니다.

아키텍처

이 아키텍처에서 NameNode는 클러스터의 모든 DataNode off-heap 캐시를 조정하는 책임이 있습니다. NameNode는 주기적으로 각 DataNode로부터 해당 DN에 캐시된 모든 블록을 설명하는 캐시 리포트를 받습니다. NameNode는 DataNode 하트비트에 캐시 및 언캐시 명령을 실어 보내 DataNode 캐시를 관리합니다.

NameNode는 캐시 지시문(cache directive) 집합을 조회해 어떤 경로를 캐시할지 결정합니다. 캐시 지시문은 fsimage와 edit log에 영속적으로 저장되며 Java 및 명령줄 API를 통해 추가, 제거, 수정할 수 있습니다. NameNode는 또한 일련의 **캐시 풀(cache pool)**을 저장합니다. 캐시 풀은 리소스 관리와 권한 시행을 위해 캐시 지시문을 함께 그룹화하는 데 사용되는 관리 엔티티입니다.

NameNode는 주기적으로 네임스페이스와 활성 캐시 지시문을 재스캔해 어떤 블록을 캐시하거나 언캐시해야 하는지 결정하고 DataNode에 캐싱 작업을 할당합니다. 재스캔은 캐시 지시문 추가/제거나 캐시 풀 제거 같은 사용자 작업에 의해서도 트리거될 수 있습니다.

현재 우리는 구성 중이거나, 손상되었거나, 불완전한 블록은 캐시하지 않습니다. 캐시 지시문이 심볼릭 링크를 덮으면 심볼릭 링크 대상은 캐시되지 않습니다.

캐싱은 현재 파일 또는 디렉터리 레벨에서 수행됩니다. 블록 및 하위 블록 캐싱은 향후 작업 항목입니다.

개념 (Concepts)

캐시 지시문 (Cache directive)

캐시 지시문은 캐시되어야 할 경로를 정의합니다. 경로는 디렉터리 또는 파일일 수 있습니다. 디렉터리는 비재귀적으로 캐시됩니다. 즉 디렉터리의 1차 수준 목록에 있는 파일만 캐시됩니다.

지시문은 또한 캐시 복제 계수(cache replication factor)와 만료 시간과 같은 추가 파라미터를 지정합니다. 복제 계수는 캐시할 블록 복제본 수를 지정합니다. 여러 캐시 지시문이 같은 파일을 가리키면 최대 캐시 복제 계수가 적용됩니다.

만료 시간은 명령줄에서 TTL(time-to-live)로, 미래의 상대 만료 시간으로 지정됩니다. 캐시 지시문이 만료되면 NameNode는 캐싱 결정을 내릴 때 더 이상 고려하지 않습니다.

캐시 풀 (Cache pool)

캐시 풀은 캐시 지시문 그룹을 관리하는 데 사용되는 관리 엔티티입니다. 캐시 풀은 UNIX 유사 권한을 가지며, 이는 어떤 사용자와 그룹이 풀에 접근할 수 있는지 제한합니다. 쓰기 권한은 사용자가 풀에 캐시 지시문을 추가하고 제거할 수 있게 합니다. 읽기 권한은 사용자가 풀의 캐시 지시문과 추가 메타데이터를 나열할 수 있게 합니다. 실행 권한은 사용되지 않습니다.

캐시 풀은 리소스 관리에도 사용됩니다. 풀은 최대 한도(maximum limit)를 시행할 수 있으며, 이는 풀의 지시문이 총합으로 캐시할 수 있는 바이트 수를 제한합니다. 일반적으로 풀 한도의 합은 클러스터에서 HDFS 캐싱을 위해 예약된 총 메모리 양과 대략 같습니다. 캐시 풀은 또한 클러스터 사용자가 무엇이 캐시되고 무엇을 캐시해야 하는지 결정하는 데 도움이 되는 다양한 통계를 추적합니다.

풀은 또한 최대 TTL을 시행할 수 있습니다. 이는 풀에 추가되는 지시문의 최대 만료 시간을 제한합니다.

cacheadmin 명령줄 인터페이스

명령줄에서 관리자와 사용자는 hdfs cacheadmin 하위 명령을 통해 캐시 풀과 지시문과 상호작용할 수 있습니다.

캐시 지시문은 고유하고 반복되지 않는 64비트 정수 ID로 식별됩니다. 캐시 지시문이 나중에 제거되어도 ID는 재사용되지 않습니다.

캐시 풀은 고유한 문자열 이름으로 식별됩니다.

캐시 지시문 명령

addDirective

사용법: hdfs cacheadmin -addDirective -path <path> -pool <pool-name> [-force] [-replication <replication>] [-ttl <time-to-live>]

새 캐시 지시문을 추가합니다.

인자 설명
<path> 캐시할 경로. 디렉터리 또는 파일일 수 있습니다.
<pool-name> 지시문이 추가될 풀. 새 지시문을 추가하려면 캐시 풀에 쓰기 권한이 있어야 합니다.
-force 캐시 풀 리소스 한도 확인을 건너뜁니다.
<replication> 사용할 캐시 복제 계수. 기본값은 1.
<time-to-live> 지시문이 유효한 기간. 분, 시간, 일로 지정할 수 있습니다. 예: 30m, 4h, 2d. 유효한 단위는 [smhd]. "never"는 만료되지 않는 지시문을 나타냅니다. 지정하지 않으면 지시문은 만료되지 않습니다.

removeDirective

사용법: hdfs cacheadmin -removeDirective <id>

캐시 지시문을 제거합니다.

인자 설명
<id> 제거할 캐시 지시문의 id. 제거하려면 지시문의 풀에 쓰기 권한이 있어야 합니다. cachedirective ID 목록을 보려면 -listDirectives 명령을 사용하세요.

removeDirectives

사용법: hdfs cacheadmin -removeDirectives <path>

지정된 경로를 가진 모든 캐시 지시문을 제거합니다.

인자 설명
<path> 제거할 캐시 지시문의 경로. 제거하려면 지시문의 풀에 쓰기 권한이 있어야 합니다. 캐시 지시문 목록을 보려면 -listDirectives 명령을 사용하세요.

listDirectives

사용법: hdfs cacheadmin -listDirectives [-stats] [-path <path>] [-pool <pool>]

캐시 지시문을 나열합니다.

인자 설명
<path> 이 경로를 가진 캐시 지시문만 나열. 읽기 접근 권한이 없는 캐시 풀에 경로에 대한 캐시 지시문이 있어도 나열되지 않습니다.
<pool> 그 풀의 경로 캐시 지시문만 나열.
-stats 경로 기반 캐시 지시문 통계를 나열.

캐시 풀 명령

addPool

사용법: hdfs cacheadmin -addPool <name> [-owner <owner>] [-group <group>] [-mode <mode>] [-limit <limit>] [-maxTtl <maxTtl>]

새 캐시 풀을 추가합니다.

인자 설명
<name> 새 풀의 이름.
<owner> 풀 소유자의 사용자 이름. 기본값은 현재 사용자.
<group> 풀의 그룹. 기본값은 현재 사용자의 기본 그룹 이름.
<mode> 풀의 UNIX 스타일 권한. 권한은 8진수로 지정됩니다(예: 0755). 기본적으로 0755로 설정됩니다.
<limit> 이 풀의 지시문이 총합으로 캐시할 수 있는 최대 바이트 수. 기본적으로 한도가 설정되지 않습니다.
<maxTtl> 풀에 추가되는 지시문의 최대 허용 TTL. 초, 분, 시간, 일로 지정할 수 있습니다(예: 120s, 30m, 4h, 2d). 유효한 단위는 [smhd]. 기본적으로 최대값이 설정되지 않습니다. "never" 값은 한도가 없음을 지정합니다.

modifyPool

사용법: hdfs cacheadmin -modifyPool <name> [-owner <owner>] [-group <group>] [-mode <mode>] [-limit <limit>] [-maxTtl <maxTtl>]

기존 캐시 풀의 메타데이터를 수정합니다.

인자 설명
<name> 수정할 풀의 이름.
<owner> 풀 소유자의 사용자 이름.
<group> 풀 그룹의 그룹 이름.
<mode> 8진수로 된 풀의 Unix 스타일 권한.
<limit> 이 풀이 캐시할 수 있는 최대 바이트 수.
<maxTtl> 풀에 추가되는 지시문의 최대 허용 TTL.

removePool

사용법: hdfs cacheadmin -removePool <name>

캐시 풀을 제거합니다. 이는 풀과 연결된 경로도 언캐시합니다.

인자 설명
<name> 제거할 캐시 풀의 이름.

listPools

사용법: hdfs cacheadmin -listPools [-stats] [<name>]

하나 이상의 캐시 풀에 대한 정보(이름, 소유자, 그룹, 권한 등)를 표시합니다.

인자 설명
-stats 추가 캐시 풀 통계를 표시.
<name> 지정하면 명명된 캐시 풀만 나열.

help

사용법: hdfs cacheadmin -help <command-name>

명령에 대한 상세 도움말을 얻습니다.

인자 설명
<command-name> 상세 도움말을 얻을 명령. 명령이 지정되지 않으면 모든 명령에 대한 상세 도움말을 출력.

설정 (Configuration)

네이티브 라이브러리 (Native Libraries)

블록 파일을 메모리에 잠그기 위해 DataNode는 libhadoop.so 또는 Windows의 hadoop.dll에서 찾을 수 있는 네이티브 JNI 코드에 의존합니다. HDFS 중앙 캐시 관리를 사용한다면 JNI를 활성화해야 합니다.

현재 영속 메모리 캐시에는 두 가지 구현이 있습니다. 기본은 순수 Java 기반 구현이고, 다른 하나는 PMDK 라이브러리를 활용해 캐시 쓰기와 캐시 읽기 성능을 향상시키는 네이티브 구현입니다.

PMDK 기반 구현을 활성화하려면 다음 단계를 따르세요.

  1. PMDK 라이브러리를 설치합니다. 자세한 정보는 공식 사이트 http://pmem.io/ 를 참고하세요.
  2. PMDK 지원으로 Hadoop을 빌드합니다. 소스 코드의 BUILDING.txt에 있는 "PMDK library build options" 절을 참고하세요.
  3. Hadoop이 PMDK를 올바르게 감지하는지 확인하려면 hadoop checknative 명령을 실행하세요.

설정 속성

필수 (Required)

DRAM 캐시 또는 영속 메모리 캐시에 대해 다음 속성 중 하나를 구성해야 합니다. DRAM 캐시와 영속 캐시는 DataNode에서 공존할 수 없습니다.

dfs.datanode.max.locked.memory

DataNode가 캐싱에 사용할 최대 메모리 양을 결정합니다. Unix 계열 시스템에서는 DataNode 사용자의 "locked-in-memory size" ulimit(ulimit -l)도 이 파라미터와 일치하도록 늘려야 합니다(아래 OS Limits 절 참고). 이 값을 설정할 때 DataNode와 애플리케이션 JVM 힙, 운영체제 페이지 캐시 같은 다른 것들을 위한 메모리 공간도 필요함을 기억하세요.

이 설정은 Lazy Persist Writes 기능과 공유됩니다. Data Node는 Lazy Persist Writes와 Centralized Cache Management가 함께 사용하는 메모리가 dfs.datanode.max.locked.memory에 구성된 양을 초과하지 않도록 보장합니다.

dfs.datanode.pmem.cache.dirs

이 속성은 영속 메모리의 캐시 볼륨을 지정합니다. 여러 볼륨이면 ","로 구분해야 합니다(예: /mnt/pmem0, /mnt/pmem1). 기본값은 비어 있습니다. 이 속성이 구성되면 볼륨 용량이 감지됩니다. 그리고 dfs.datanode.max.locked.memory를 구성할 필요가 없습니다.

선택 (Optional)

다음 속성은 필수는 아니지만 튜닝을 위해 지정할 수 있습니다.

dfs.namenode.path.based.cache.refresh.interval.ms

NameNode가 이 값을 후속 경로 캐시 재스캔 사이의 밀리초 양으로 사용합니다. 이는 캐시할 블록과, 블록의 복제본을 포함하는 각 DataNode를 계산합니다.

기본적으로 이 파라미터는 30000(30초)으로 설정됩니다.

dfs.datanode.fsdatasetcache.max.threads.per.volume

DataNode가 새 데이터 캐싱에 사용할 볼륨당 최대 스레드 수로 사용합니다.

기본적으로 이 파라미터는 4로 설정됩니다.

dfs.cachereport.intervalMsec

DataNode가 캐시 상태의 전체 리포트를 NameNode에 보내는 사이의 밀리초 양으로 사용합니다.

기본적으로 이 파라미터는 10000(10초)으로 설정됩니다.

dfs.namenode.path.based.cache.block.map.allocation.percent

캐시된 블록 맵에 할당할 Java 힙의 백분율. 캐시된 블록 맵은 체이닝 해싱을 사용하는 해시 맵입니다. 캐시된 블록 수가 많으면 더 작은 맵이 더 느리게 접근될 수 있습니다. 더 큰 맵은 더 많은 메모리를 소비합니다. 기본값은 0.25퍼센트입니다.

dfs.namenode.caching.enabled

이 파라미터는 NameNode에서 중앙 캐싱을 활성화/비활성화하는 데 사용할 수 있습니다. 중앙 캐싱이 비활성화되면 NameNode는 캐시 리포트를 처리하거나 클러스터의 블록 캐시 위치에 대한 정보를 저장하지 않습니다. 참고로 캐싱이 활성화될 때까지 이 정보에 대해 행동하지 않더라도 NameNode는 파일시스템 메타데이터에 경로 기반 캐시 위치를 계속 저장합니다. 이 파라미터의 기본값은 true(즉 중앙 캐싱이 활성화)입니다. 현재 구현에서 중앙 캐싱은 캐시할 경로가 지정되지 않은 경우에도 추가 쓰기 잠금 오버헤드를 도입합니다(CacheReplicationMonitor#rescan 참고). 따라서 사용하지 않을 때는 이 기능을 비활성화하는 것을 권장합니다. 이후 버전에서는 중앙 캐싱을 기본적으로 비활성화할 것입니다.

dfs.datanode.pmem.cache.recovery

이 파라미터는 DataNode 시작 시 영속 메모리의 이전 캐시 상태를 복구할지 결정하는 데 사용됩니다. 활성화되면 DataNode가 영속 메모리에 이전에 캐시된 데이터의 상태를 복구합니다. 따라서 재캐싱을 피할 수 있습니다. 이 속성이 활성화되지 않으면 DataNode는 영속 메모리의 캐시(있으면)를 버립니다. 이 속성은 영속 메모리 캐시가 활성화되어 있을 때, 즉 dfs.datanode.pmem.cache.dirs가 구성되어 있을 때만 작동합니다.

OS 한도 (OS Limits)

"Cannot start datanode because the configured max locked memory size… is more than the datanode's available RLIMIT_MEMLOCK ulimit," 오류가 발생하면, 운영체제가 구성한 것보다 낮은 잠글 수 있는 메모리 한도를 부과하고 있다는 뜻입니다. 이를 해결하려면 DataNode가 실행되는 ulimit -l 값을 조정해야 합니다. 보통 이 값은 /etc/security/limits.conf에서 구성합니다. 다만 사용 중인 운영체제와 배포판에 따라 달라집니다.

셸에서 ulimit -l을 실행해 dfs.datanode.max.locked.memory로 구성한 값보다 높은 값이나, 한도가 없음을 나타내는 "unlimited" 문자열을 얻으면 값을 올바르게 구성한 것입니다. ulimit -l은 보통 메모리 잠금 한도를 KB로 출력하지만, dfs.datanode.max.locked.memory는 바이트로 지정해야 합니다.

이 정보는 Windows 배포에는 적용되지 않습니다. Windows에는 ulimit -l의 직접적인 동등물이 없습니다.

더 알아보기 (Learn more)