LabHub
学习 学习路径 课程

Apache Hadoop — HDFS 와 YARN 을 한 파드에 세우고 운영한다 · HDFS 의 두 역할 · 讲解

NameNode 는 이름을, DataNode 는 바이트를 가진다

在 LabHub 中继续学习

한 줄 요약

HDFS 는 이름을 아는 쪽(NameNode)바이트를 가진 쪽(DataNode) 을 처음부터 갈라 놓은 파일 시스템이다. 셸로 읽든 HTTP 로 읽든 "NameNode 에게 어디 있는지 묻고, 바이트는 DataNode 에서 받는다" 는 순서는 같다.

概念图: 이름을 아는 쪽(NameNode) · 바이트를 가진 쪽(DataNode) · NameNode · DataNode

왜 역할을 둘로 나눴나

노트북의 파일 시스템은 한 디스크 위에서 이름과 내용을 함께 관리한다. 디렉터리 항목을 찾으면 그 옆에 내용이 어디 있는지가 적혀 있고, 둘은 같은 기계에 있다. HDFS 가 풀려던 문제는 이것과 규모가 달랐다.

[HDFS 설계 문서](https://hadoop.apache.org/docs/r3.5.0/hadoop-project-dist/hadoop-hdfs/HdfsDesign.html)는 첫머리에서 전제를 둘 깐다. 하드웨어 고장은 예외가 아니라 일상이고, HDFS 위의 전형적인 파일은 기가바이트에서 테라바이트 크기라는 것이다. 수백 대에 흩어진 디스크에 큰 파일을 나눠 담으려면 누군가는 "이 파일의 몇 번째 조각이 어느 기계에 있는가" 를 한곳에서 알아야 한다. 동시에 실제 바이트는 여러 기계가 한꺼번에 내보내야 처리량이 나온다.

그래서 역할을 쪼갰다. NameNode 는 네임스페이스 — 디렉터리 트리, 소유자와 권한, 파일마다의 복제 계수 — 와 함께 "어느 블록이 어느 DataNode 에 있는가" 라는 지도를 쥔다. DataNode 는 자기 기계에 붙은 디스크의 블록을 읽고 쓴다. 설계 문서는 이 구조의 무게 중심을 한 문장으로 적는다. 사용자 데이터는 결코 NameNode 를 거쳐 흐르지 않도록 설계되었다는 것이다.

이 한 문장이 왜 중요한지 반대로 생각해 보면 분명해진다. NameNode 가 바이트까지 중계했다면 클러스터 전체의 처리량이 NameNode 한 대의 네트워크 카드와 디스크에 묶였을 것이다. 메타데이터만 다루기 때문에 한 대가 수백 대를 지휘할 수 있다. 대신 대가가 있다. 모든 이름 연산이 그 한 대를 지나므로, NameNode 는 클러스터에서 가장 중요한 단일 지점이 된다.

어떻게 동작하나

읽기. 클라이언트가 파일을 열면 먼저 NameNode 에게 그 파일의 블록 목록과 블록마다 복제본을 가진 DataNode 들을 묻는다. 그다음 바이트는 DataNode 에 직접 연결해 받는다. 설계 문서의 복제본 선택 절은 읽는 쪽에서 가장 가까운 복제본을 고른다고 설명한다 — 같은 랙에 있으면 그것을 먼저 쓴다.

쓰기. 쓰기도 같은 모양이다. 클라이언트는 NameNode 에게 새 블록을 달라고 하고, NameNode 는 블록 ID 와 복제본을 받을 DataNode 목록을 돌려준다. 바이트는 목록의 첫 번째 DataNode 로만 보낸다. 설계 문서가 복제 파이프라인이라고 부르는 부분이다. 첫 번째 DataNode 는 조각을 받아 자기 디스크에 쓰면서 동시에 두 번째로 넘기고, 두 번째는 세 번째로 넘긴다.

HDFS 쓰기 과정. 클라이언트는 NameNode 에게 새 블록을 요청하고 블록 ID 와 DataNode 세 대의 목록만 받는다. 바이트는 첫 번째 DataNode 로만 보내고, 각 DataNode 는 받은 조각을 쓰면서 다음 DataNode 로 넘긴다. DataNode 들은 따로 NameNode 에 하트비트와 블록 리포트를 보낸다

DataNode 는 파일을 모른다. 설계 문서의 표현을 그대로 옮기면, DataNode 는 HDFS 파일에 대해 아무것도 모르고 블록 하나하나를 로컬 파일 시스템의 별도 파일로 저장한다. 기동할 때 로컬 디스크를 훑어 가진 블록의 목록을 만들어 NameNode 에 보내는데, 이것이 블록 리포트다. 평소에는 주기적으로 하트비트를 보낸다. 간격은 hdfs-default.xmldfs.heartbeat.interval 로 기본 3초이고, 하트비트가 끊긴 DataNode 를 죽었다고 판정하기까지는 기본값으로 10분이 넘는 보수적인 시간을 둔다. 잠깐 흔들린 노드 때문에 복제 폭풍이 일어나지 않게 하려는 것이다(실습 파드는 기다림을 줄이려고 이 판정을 1분 30초로 당겨 두었다).

여기서 중요한 성질이 하나 나온다. 설계 문서는 NameNode 가 먼저 RPC 를 거는 일이 없고 DataNode 와 클라이언트의 요청에 답하기만 한다고 적는다. NameNode 가 DataNode 에게 "이 블록을 지워라, 복제해라" 라고 시킬 때도 하트비트의 응답에 실어 보낸다.

mv 는 바이트를 옮기지 않는다. 이름 바꾸기와 디렉터리 옮기기는 설계 문서가 NameNode 의 일로 분류한 네임스페이스 연산이다. 파일이 새 경로로 옮겨 가도 블록은 그대로이고 블록 ID 도 바뀌지 않는다. 수십 GB 짜리 디렉터리를 mv 해도 순식간에 끝나는 이유다. 반대로 cp 는 바이트를 새 블록으로 다시 쓴다.

같은 파일을 두 길로 본다

HDFS 에 닿는 길은 여럿이다. 가장 익숙한 것은 hdfs dfs 셸로, NameNode 의 RPC 포트로 말한다. 다른 하나는 HTTP 다. [WebHDFS 문서](https://hadoop.apache.org/docs/r3.5.0/hadoop-project-dist/hadoop-hdfs/WebHDFS.html)는 경로 앞에 /webhdfs/v1 을 붙이고 뒤에 op= 질의를 다는 주소 모양을 정한다. NameNode 의 HTTP 주소는 [hdfs-default.xml](https://hadoop.apache.org/docs/r3.5.0/hadoop-project-dist/hadoop-hdfs/hdfs-default.xml) 에서 dfs.namenode.http-address 로 기본 0.0.0.0:9870 이다.

# 목록은 NameNode 혼자 답한다 — 메타데이터뿐이다curl -s "http://localhost:9870/webhdfs/v1/user/root?op=LISTSTATUS"# 내용은 DataNode 로 돌려보낸다 — 307 을 따라가야 바이트가 온다curl -i "http://localhost:9870/webhdfs/v1/user/root/app.log?op=OPEN"# HTTP/1.1 307 TEMPORARY_REDIRECT# Location: http://<DataNode>:<포트>/webhdfs/v1/user/root/app.log?op=OPEN...

HTTP 로 바꿔도 역할 분담은 그대로 드러난다. LISTSTATUS 는 NameNode 가 JSON 으로 바로 답한다. OPENCREATE 는 WebHDFS 문서에 적힌 대로 보통 DataNode 로 가는 307 리다이렉트를 돌려준다. curl -L 로 따라가야 바이트가 온다. 셸이 안에서 하던 "묻고, 받으러 간다" 가 HTTP 에서는 눈에 보이는 두 번의 요청이 된 것이다.

한 가지 더. 보안이 꺼진 클러스터에서 WebHDFS 는 user.name 질의 값을 그대로 인증된 사용자로 받는다고 문서가 적고 있다. 누구인지를 말하는 대로 믿는다는 뜻이고, 권한 모듈에서 다시 다룬다.

현장에서 만나는 모습

첫째, 파일은 보이는데 읽으면 실패한다. 이름은 NameNode 에 멀쩡히 있지만 그 블록을 가진 DataNode 가 모두 내려가 있으면 ls 는 되고 cat 은 실패한다. 두 역할이 갈라져 있어서 생기는 전형적인 증상이다. 이럴 때는 [HDFS 명령 문서](https://hadoop.apache.org/docs/r3.5.0/hadoop-project-dist/hadoop-hdfs/HDFSCommands.html)의 hdfs dfsadmin -report 로 살아 있는 DataNode 와 용량부터 본다.

둘째, WebHDFS 로 목록은 되는데 읽기만 안 된다. 방화벽 밖에서 9870 만 열어 두면 흔히 겪는다. 목록은 NameNode 가 답하지만 읽기는 DataNode 주소로 리다이렉트되기 때문에, 그 주소가 클라이언트에서 닿지 않으면 두 번째 요청이 실패한다. 같은 구조가 다른 모습으로 나타난 것이다.

셋째, NameNode 가 느리면 전부 느리다. 바이트는 DataNode 가 나르지만 ls 한 번, 작은 파일 열기 하나도 NameNode 왕복을 한다. 작은 파일이 수백만 개인 클러스터가 NameNode 부터 힘들어하는 이유가 여기 있다.

넷째, 파일은 한 번 쓰고 여러 번 읽는다. 설계 문서는 파일을 만들고 닫으면 뒤에 붙이기와 자르기 말고는 바꾸지 않는 모델을 택했고, 한 파일에는 언제나 쓰는 사람이 하나뿐이다. 중간을 고치는 일은 없다. 로그처럼 쌓는 자료에 맞고, 행 단위로 고치는 자료에는 맞지 않는다.

실무에서 진짜 중요한 것

다음 실습에서 할 것

먼저 hdfs getconf 로 기본 파일 시스템 주소·복제 계수·블록 크기가 어떤 값으로 잡혀 있는지 묻고, 관리 보고서로 DataNode 가 하나 붙어 있는 모습을 본다. 접근 로그 하나를 HDFS 에 올린 뒤 셸로 줄 수를 세고, 같은 디렉터리를 WebHDFS 의 LISTSTATUS 로 HTTP 요청을 보내 JSON 목록으로 받아 본다. 마지막으로 파일을 다른 디렉터리로 옮긴 뒤 fsck 로 블록을 확인해, 옮기기 전후로 fileId 와 블록 ID 가 그대로라는 것 — 이름 연산이 바이트를 건드리지 않는다는 것을 눈으로 본다.