LabHub
배우기 러닝패스 코스

Apache Hadoop — HDFS 와 YARN 을 한 파드에 세우고 운영한다 · 작은 파일과 HAR · 이론

작은 파일은 디스크가 아니라 NameNode 의 메모리를 먹는다

LabHub 에서 이어서 보기

한 줄 요약

HDFS 에서 파일 하나의 비용은 바이트가 아니라 NameNode 메모리 속 객체 수로 매겨진다. 1KB 파일 2,000개는 디스크로는 2MB 지만 NameNode 에는 파일 2,000개와 블록 2,000개의 객체다. 하둡 아카이브(HAR)는 그 객체를 몇 개로 접어 이름공간을 가볍게 하고, 원본을 지우는 것은 사람의 몫이다.

개념 지도: NameNode 메모리 속 객체 수 · 이름공간 전체와 블록 대응표를 메모리에 · 최대 크기 · 하둡 아카이브(HAR)

왜 이게 문제가 되나

[HDFS 설계 문서](https://hadoop.apache.org/docs/r3.5.0/hadoop-project-dist/hadoop-hdfs/HdfsDesign.html)는 NameNode 가 이름공간 전체와 블록 대응표를 메모리에 들고 있다고 적는다. 디스크는 DataNode 를 늘리면 얼마든지 늘지만, 이름공간은 NameNode 한 프로세스의 힙 안에 산다. 파일 하나, 디렉터리 하나, 블록 하나가 모두 그 힙 안의 객체다. 그래서 HDFS 의 용량에는 두 축이 있다. 바이트로 재는 디스크 용량과, 객체 수로 재는 이름공간 용량이다.

같은 문서는 HDFS 가 큰 파일에 맞춰져 있다고 분명히 한다. 전형적인 파일은 기가바이트에서 테라바이트이고, 한 인스턴스가 수천만 개의 파일을 지원해야 한다고 적는다. 수천만이라는 규모가 설계가 염두에 둔 크기다. 같은 수천만 개라도 파일이 평균 1GB 면 수십 페타바이트를 담지만, 평균 10KB 면 수백 기가바이트를 담는 사이에 이름공간이 먼저 찬다. [Federation 문서](https://hadoop.apache.org/docs/r3.5.0/hadoop-project-dist/hadoop-hdfs/Federation.html)가 NameNode 를 여럿 두는 이유로 "작은 파일이 많은 배포" 를 직접 드는 것도 이 때문이다.

작은 파일은 계산 쪽에서도 손해다. [MapReduce 튜토리얼](https://hadoop.apache.org/docs/r3.5.0/hadoop-mapreduce-client/hadoop-mapreduce-client-core/MapReduceTutorial.html)에 따르면 맵 수는 입력 블록 수가 좌우하고, 태스크를 준비하는 데 시간이 들어 맵 하나가 적어도 1분은 도는 것이 좋다. 1KB 파일 2,000개는 블록 2,000개이고, 준비에 몇 초씩 드는 맵 태스크가 1초도 안 걸릴 일을 하러 수천 번 뜬다.

어떻게 동작하나

객체 수는 숫자로 볼 수 있다. [지표 문서](https://hadoop.apache.org/docs/r3.5.0/hadoop-project-dist/hadoop-common/Metrics.html)의 FSNamesystem 항목에서 FilesTotal 은 파일과 디렉터리 수, BlocksTotal 은 할당된 블록 수다. NameNode 웹 주소(기본 9870 포트)의 /jmx 로 바로 읽힌다. 빈 디렉터리 하나에 작은 파일 2,000개를 올리면 두 값은 이런 식으로 움직인다(앞의 숫자는 예시다).

put 전     FilesTotal=  41   BlocksTotal=  12put 2,000  FilesTotal=2042   BlocksTotal=2012   <- 파일 2,000 + 디렉터리 1, 블록 2,000

작은 파일 하나는 블록 하나를 만든다. [hdfs-default.xml](https://hadoop.apache.org/docs/r3.5.0/hadoop-project-dist/hadoop-hdfs/hdfs-default.xml)의 dfs.blocksize 기본은 128MB 지만, 그것은 블록의 최대 크기일 뿐이다. 1KB 파일의 블록은 DataNode 디스크에서 1KB 남짓만 차지한다. 디스크는 낭비되지 않는다. 낭비되는 것은 그 블록 하나를 기억하는 NameNode 의 객체와, 그 블록을 보고하고 관리하는 일이다. 작은 파일 문제를 "디스크가 아깝다" 로 설명하면 틀린 설명이 된다.

하둡 아카이브(HAR) 는 이 객체들을 접는다. [Hadoop Archives 안내](https://hadoop.apache.org/docs/r3.5.0/hadoop-archives/HadoopArchives.html)에 따르면 .har 는 파일 시스템 위의 디렉터리 하나이고, 안에 메타데이터 _index·_masterindex 와 자료 파일 part-* 가 들어 있다. 원본 파일들의 내용은 몇 개의 part 파일에 이어 붙여지고, _index 가 각 원본 파일의 이름과 part 파일 안의 위치를 기억한다. 2,000개의 파일이 NameNode 에게는 몇 개의 파일이 되는 것이다.

hadoop archive -archiveName logs.har -p /data/raw day1 /data/archivehdfs dfs -ls har:///data/archive/logs.har/day1

HAR 은 파일 시스템 층으로 자신을 드러낸다. har:// 주소로 ls·cat 같은 셸 명령이 그대로 되고, MapReduce 입력으로도 쓸 수 있다. 대신 몇 가지 대가가 있다.

HAR 이 줄이는 것은 이름공간의 객체 수다. HAR 을 MapReduce 입력으로 넣어도 안의 논리 파일들은 여전히 파일로 보이므로, 맵 수 문제까지 저절로 풀리지는 않는다. 계산이 문제라면 애초에 큰 파일로 합쳐 쓰는 편이 낫다.

distcp 로 옮기고 푼다

대량 복사는 [DistCp](https://hadoop.apache.org/docs/r3.5.0/hadoop-distcp/DistCp.html)로 한다. 이것도 MapReduce 잡이다. 복사할 파일 목록을 펼쳐 맵 태스크들에 나눠 주고, 맵마다 자기 몫을 복사한다. 문서에 따르면 맵마다 비슷한 바이트를 맡도록 나누지만 파일이 가장 작은 단위라서, 작은 파일이 많으면 복사 역시 파일 수에 끌려간다. -update 는 대상에 없거나 다른 파일만 복사해 두 번째 실행부터 빠르다. HAR 을 푸는 것도 복사다. har:// 주소를 원본으로 hdfs dfs -cp 하면 차례로, distcp 하면 병렬로 풀린다.

현장에서 만나는 모습

첫째, NameNode 가 GC 로 멈춘다. 디스크는 절반이 비어 있는데 NameNode 힙이 가득 차 긴 GC 가 반복된다. 원인을 추적하면 누군가 5분마다 수천 개씩 작은 파일을 떨어뜨리는 수집기다. FilesTotal 의 추세를 감시 항목에 넣어야 하는 이유다.

둘째, 디렉터리 하나에 파일이 너무 많다. dfs.namenode.fs-limits.max-directory-items 의 기본은 1,048,576 이다. 한 디렉터리에 계속 떨어뜨리는 수집기는 언젠가 이 벽에 부딪혀 쓰기가 실패한다.

셋째, 아카이브를 만들었는데 객체가 줄지 않았다. 원본을 지우지 않았다. 아카이브를 har 주소로 읽어 확인하고, 그다음에 원본을 지운다. 순서를 바꾸면 되돌릴 길이 없다.

넷째, 진짜 해결은 쓰는 쪽에 있다. HAR 은 이미 쌓인 것을 치우는 도구다. 새로 쌓이는 것은 수집 단계에서 묶어 쓰거나, 정기적으로 큰 파일로 합치는 작업을 둬야 문제가 다시 자라지 않는다.

실무에서 진짜 중요한 것

다음 실습에서 할 것

작은 파일 2,000개를 HDFS 에 올리고 count 와 fsck 로 파일·디렉터리·블록 수를 세어 NameNode 가 떠안은 객체 수를 잰다. YARN 을 켜지 않고 로컬 모드로 hadoop archive 를 돌려 그 파일들을 복제 1 짜리 HAR 하나로 묶고, har 주소로 목록을 보아 안의 파일 수를 센 뒤 파일 하나를 읽어 로컬로 꺼낸다. 원본 디렉터리와 HAR 의 객체 수를 나란히 견주어 얼마나 줄어드는지 숫자로 확인하고, 마지막으로 distcp 로 아카이브의 백업 사본을 만든다.