HDFS
고친 사람 github-actions[bot]
HDFS 는 서버 여러 대의 디스크를 하나로 묶어서 한 대에 안 들어가는 큰 파일을 나눠 담아 주는 파일 시스템입니다. 파일을 큰 조각으로 자르고 조각마다 여러 서버에 복사해 둡니다. 그래서 서버 몇 대가 고장 나도 파일을 잃지 않습니다. 그 대신 한번 쓴 파일은 고쳐 쓰지 못하고 끝에 덧붙이기만 합니다.
쉽고 빠른 이해
HDFS 는 파일 하나를 서버 여러 대에 나눠 담습니다. 쓰는 쪽에는 파일 하나로 보여 줍니다. 몇 테라바이트짜리 로그 파일도 경로 하나로 올리고 읽습니다.
이게 없으면 데이터가 서버 한 대의 디스크에 묶입니다. 디스크가 차면 더 못 담습니다. 그 서버가 죽으면 데이터도 같이 사라집니다.
- 파일을 큰 조각으로 자릅니다
- 조각마다 서로 다른 서버 세 대에 복사해 둡니다
- 관리 서버 한 대가 어느 조각이 어느 서버에 있는지 적어 둡니다
읽는 쪽은 관리 서버에 조각의 위치를 묻습니다. 데이터는 조각을 가진 서버에서 바로 받습니다.
대가도 있습니다. 파일 중간을 고쳐 쓸 수 없습니다. 작은 파일이 수백만 개 쌓이면 관리 서버의 메모리가 먼저 찹니다. 한 번 읽을 때마다 기다리는 시간도 로컬 디스크보다 깁니다.
상세
HDFS(Hadoop Distributed File System, 하둡 분산 파일 시스템)는 Hadoop 의 저장 계층입니다. Hadoop 은 서버 수백 대에 데이터를 나눠 두고 그 위에서 계산까지 돌리는 오픈 소스 프로젝트 묶음입니다. HDFS 는 그중 데이터를 담는 일을 맡습니다.
이 절은 HDFS 가 파일을 자르고 두는 법, 읽기와 쓰기의 순서, 관리 서버가 멈출 때, 그리고 HDFS 가 내려놓은 것을 봅니다.
한 대에 안 들어가는 데이터
웹 서비스의 접속 로그를 몇 년 치 모으면 수백 테라바이트가 됩니다. 이런 데이터는 서버 한 대의 디스크에 안 들어갑니다. 들어가더라도 디스크 하나로 처음부터 끝까지 읽으면 몇 시간이 걸립니다.
분산 파일 시스템은 이 문제를 서버 여러 대로 푸는 파일 시스템입니다. 파일을 여러 서버의 디스크에 나눠 담습니다. 그래도 쓰는 쪽은 경로 하나로 파일 하나를 다룹니다. HDFS 가 그런 파일 시스템입니다.
HDFS 는 값싼 서버를 많이 묶는 쪽을 골랐습니다. 서버가 수백 대면 디스크든 전원이든 어딘가는 거의 날마다 고장 납니다. 그래서 HDFS 는 고장을 드문 사고가 아니라 늘 있는 일로 보고 설계했습니다.
HDFS 는 구글이 논문으로 공개한 구글 파일 시스템(GFS, Google File System)의 설계를 본떠 만들었습니다. 파일을 큰 조각으로 자릅니다. 조각은 여러 서버에 복사합니다. 목록은 관리 서버 한 대가 쥡니다. 이 얼개가 그 논문에서 왔습니다.
블록
HDFS 는 파일을 블록으로 잘라 담습니다. 블록은 파일을 일정한 크기로 자른 조각 하나입니다. 1기가바이트짜리 파일은 128메가바이트 블록 여덟 개가 됩니다.
HDFS 의 블록은 보통 128메가바이트입니다. 로컬 디스크 파일 시스템의 블록보다 수만 배 큽니다. 블록이 크면 관리 서버가 적어 둘 조각 수가 줄어듭니다.
큰 블록은 읽기도 빠르게 합니다. 디스크는 데이터를 읽기 전에 읽을 곳을 찾아가는 데 시간을 씁니다. 블록이 크면 한 번 찾아간 뒤 오래 이어 읽으므로 찾아가는 데 쓰는 몫이 줄어듭니다.
마지막 블록은 남은 만큼만 차지합니다. 130메가바이트 파일은 128메가바이트 블록 하나와 2메가바이트 블록 하나가 됩니다. 두 번째 블록이 디스크를 128메가바이트만큼 잡아먹지는 않습니다.
네임노드와 데이터노드
HDFS 클러스터에는 두 종류의 서버가 있습니다. 클러스터는 한 가지 일을 함께 하도록 묶은 서버 무리입니다.
네임노드(NameNode)는 관리 서버입니다. 디렉터리 구조와 파일마다 어떤 블록으로 이루어졌는지를 쥡니다. 이런 정보가 메타데이터입니다. 메타데이터는 데이터 자체가 아니라 데이터를 설명하는 데이터입니다.
네임노드는 메타데이터를 전부 메모리에 올려 둡니다. 파일을 찾을 때마다 디스크를 뒤지지 않으려는 것입니다. 그래서 담을 수 있는 파일 수가 네임노드의 메모리 크기에 묶입니다.
데이터노드(DataNode)는 블록을 담는 서버입니다. 블록 하나를 자기 디스크에 평범한 파일 하나로 저장합니다. 클러스터를 키울 때는 데이터노드를 더 붙입니다.
아래 그림은 두 블록으로 된 파일 하나가 데이터노드 네 대에 흩어진 모습입니다. 블록마다 복사본이 세 벌씩 있습니다.
flowchart TD
subgraph NN["네임노드"]
M["/logs/app.log = 블록 1 + 블록 2"]
end
subgraph D1["데이터노드 1"]
A1["블록 1"]
B1["블록 2"]
end
subgraph D2["데이터노드 2"]
A2["블록 1"]
end
subgraph D3["데이터노드 3"]
A3["블록 1"]
B3["블록 2"]
end
subgraph D4["데이터노드 4"]
B4["블록 2"]
end
NN -.->|블록 위치를 앎| D1
NN -.->|블록 위치를 앎| D2
NN -.->|블록 위치를 앎| D3
NN -.->|블록 위치를 앎| D4
데이터노드 한 대가 죽어도 두 블록 모두 다른 데이터노드에 남아 있습니다. 네임노드는 블록 목록만 쥐고 블록의 내용은 하나도 담지 않습니다.
복제
HDFS 는 블록을 복제해서 복사본을 여러 벌 둡니다. 흔히 세 벌을 둡니다. 몇 벌을 둘지는 파일마다 정할 수 있습니다.
데이터노드는 몇 초마다 네임노드에 살아 있다는 신호를 보냅니다. 이 신호가 하트비트입니다. 신호가 한참 끊기면 네임노드는 그 데이터노드가 죽었다고 봅니다.
데이터노드가 죽으면 그 안의 블록은 복사본이 한 벌씩 줄어듭니다. 네임노드는 남은 복사본을 다른 데이터노드로 다시 복사하게 시킵니다. 사람이 손대지 않아도 복사본 수가 원래대로 돌아옵니다.
서버는 랙에 꽂힙니다. 랙은 서버 여러 대를 층층이 꽂는 선반입니다. 같은 랙의 서버는 네트워크 스위치와 전원을 함께 씁니다. 그래서 스위치 하나가 고장 나면 랙 하나가 한꺼번에 끊깁니다.
HDFS 는 블록의 복사본을 두 개 이상의 랙에 나눠 둡니다. 서버가 어느 랙에 있는지 알고 복사본을 놓는 이 방식을 랙 인식이라고 부릅니다. 랙 하나가 통째 끊겨도 다른 랙의 복사본으로 읽습니다.
블록은 디스크 안에서 조용히 깨지기도 합니다. HDFS 는 블록을 쓸 때 체크섬을 함께 적습니다. 체크섬은 데이터에서 계산한 짧은 검산 값입니다. 읽을 때 다시 계산해 값이 다르면 그 복사본을 버리고 다른 복사본을 읽습니다.
쓰는 모습
프로그램이나 명령줄에서 HDFS 를 다루는 모습은 로컬 파일 시스템과 비슷합니다. 경로가 있고 디렉터리가 있습니다. 아래는 명령줄 도구로 파일을 올리고 읽는 모습입니다.
hdfs dfs -mkdir /logs # 디렉터리
hdfs dfs -put app.log /logs/ # 올리기
hdfs dfs -cat /logs/app.log # 읽기
hdfs dfs -rm /logs/app.log # 지우기
파일 안의 한 줄을 고치는 명령은 없습니다. 고치려면 파일을 새로 써서 옛 파일과 바꿉니다.
읽기
클라이언트는 HDFS 를 쓰는 프로그램입니다. 클라이언트가 파일을 읽을 때 데이터는 네임노드를 거치지 않습니다. 아래는 블록 하나를 읽는 순서입니다.
sequenceDiagram
participant 클라이언트
participant 네임노드
participant 데이터노드
클라이언트->>네임노드: /logs/app.log 의 블록은 어디 있나
네임노드-->>클라이언트: 블록마다 복사본을 가진 데이터노드 목록
클라이언트->>데이터노드: 블록 1 을 달라
데이터노드-->>클라이언트: 블록 1 의 내용
Note over 클라이언트,데이터노드: 다음 블록도 같은 방식으로 읽는다
네임노드는 위치만 알려 주고 빠집니다. 블록 내용은 데이터노드에서 클라이언트로 바로 갑니다. 클라이언트 수백 개가 한꺼번에 읽어도 네임노드 한 대가 데이터를 나르는 병목이 되지 않습니다.
복사본이 여럿이면 클라이언트는 가까운 데이터노드부터 읽습니다. 같은 서버나 같은 랙에 복사본이 있으면 그쪽이 먼저입니다. 네트워크를 덜 타기 때문입니다.
쓰기
HDFS 의 파일은 한 번 쓰고 닫으면 내용을 바꿀 수 없습니다. 끝에 덧붙이는 것만 됩니다. 이 방식을 흔히 한 번 쓰고 여러 번 읽는 방식(write-once, read-many)이라고 부릅니다.
쓰기도 네임노드에게 먼저 묻습니다. 네임노드는 새 블록을 담을 데이터노드 세 대를 골라 줍니다. 클라이언트는 첫째 데이터노드에게만 데이터를 보냅니다. 첫째가 받은 것을 둘째에게, 둘째가 셋째에게 넘깁니다.
이렇게 줄지어 넘기는 방식을 파이프라인이라고 부릅니다. 클라이언트가 세 대에 따로 보내면 클라이언트 쪽 네트워크가 세 배로 바빠집니다. 줄지어 넘기면 클라이언트는 한 벌만 보냅니다.
sequenceDiagram
participant 클라이언트
participant 네임노드
participant 첫째 노드
participant 둘째 노드
클라이언트->>네임노드: 새 블록을 담을 곳을 달라
네임노드-->>클라이언트: 데이터노드 세 대
클라이언트->>첫째 노드: 데이터
첫째 노드->>둘째 노드: 받은 데이터를 넘긴다
Note over 둘째 노드: 셋째 노드에게 넘기고 받았다는 답을 기다린다
둘째 노드-->>첫째 노드: 받았다
첫째 노드-->>클라이언트: 받았다
받았다는 답은 넘어간 길을 거꾸로 돌아옵니다. 클라이언트가 답을 받으면 세 대 모두 그 데이터를 가진 것입니다.
고쳐 쓰기를 막은 까닭이 여기 있습니다. 파일 중간을 고치려면 세 복사본을 같은 순간에 같은 내용으로 바꿔야 합니다. 그 사이 어느 한 대가 죽으면 복사본끼리 내용이 갈립니다. HDFS 는 이 어려운 일을 떠안지 않고 덧붙이기만 허용했습니다.
네임노드가 멈추면
네임노드는 클러스터에 하나뿐인 관리 서버입니다. 네임노드가 멈추면 블록이 데이터노드에 멀쩡히 있어도 어느 블록이 어느 파일인지 알 길이 없습니다. 이렇게 하나가 멈추면 전체가 멈추는 부품이 단일 장애점입니다.
메타데이터를 잃지 않으려고 네임노드는 바뀐 것을 디스크에 두 가지로 남깁니다. 파일을 만들거나 지울 때마다 그 변경을 편집 로그(EditLog)에 먼저 적습니다. 변경을 먼저 로그에 적는 이 방식은 선행 기록 로그(WAL, Write-Ahead Logging)와 같은 생각입니다.
편집 로그만 있으면 다시 켤 때 로그를 처음부터 다 되풀이해야 합니다. 그래서 네임노드는 메타데이터 전체 모습을 이미지 파일(FsImage)로 떠 둡니다. 다시 켤 때는 이미지를 읽고 그 뒤의 로그만 되풀이합니다.
블록이 어느 데이터노드에 있는지는 디스크에 남기지 않고 메모리에만 쥡니다. 데이터노드는 켜질 때와 그 뒤로 주기적으로 자기가 가진 블록 목록을 네임노드에 보고합니다. 네임노드는 다시 켤 때 이 보고로 위치를 다시 채웁니다.
이름이 헷갈리는 서버가 하나 있습니다. 보조 네임노드(Secondary NameNode)는 네임노드가 죽었을 때 대신 서는 서버가 아닙니다. 편집 로그를 이미지 파일에 합쳐 로그가 끝없이 길어지지 않게 하는 일만 합니다.
네임노드가 죽어도 클러스터가 멈추지 않게 하려면 네임노드를 두 대 둡니다. 평소 일하는 활성 네임노드가 죽으면 기다리던 대기 네임노드가 넘겨받습니다. 한 대가 멈춰도 서비스가 이어지게 하는 이런 구성이 고가용성입니다.
넘겨받으려면 대기 쪽도 같은 메타데이터를 쥐고 있어야 합니다. 그래서 활성 네임노드는 편집 로그를 저널노드(JournalNode)라는 작은 서버 무리에 씁니다. 대기 네임노드는 그 로그를 읽어 같은 메타데이터를 쥐고 있습니다.
작은 파일이 많으면
네임노드는 파일과 블록 하나하나를 메모리에 적습니다. 1킬로바이트짜리 파일도 128메가바이트짜리 파일과 같은 칸을 차지합니다. 작은 파일이 수억 개가 되면 디스크는 남아도 네임노드의 메모리가 먼저 찹니다.
이 현상이 작은 파일 문제입니다. 읽기도 느려집니다. 파일마다 네임노드에 위치를 묻고 데이터노드와 새로 연결해야 하기 때문입니다.
그래서 HDFS 를 쓰는 쪽은 작은 파일을 모아 큰 파일로 합쳐 넣습니다. 몇 초마다 한 줄씩 들어오는 로그도 한동안 모았다가 큰 파일 하나로 씁니다.
HDFS 가 내려놓은 것
HDFS 는 큰 파일을 처음부터 끝까지 읽는 일에 맞춰 여러 가지를 포기했습니다. 아래 표가 그 목록입니다.
| 내려놓은 것 | 얻은 것 |
|---|---|
| 파일 중간 고쳐 쓰기 | 복사본끼리 내용이 갈리지 않는다 |
| 한 번의 짧은 읽기가 빨리 끝나는 것 | 많은 데이터를 한꺼번에 읽는 처리량 |
| 작은 파일 수백만 개 | 네임노드 한 대로 목록 전체를 메모리에 쥔다 |
| 로컬 파일 시스템과 똑같이 다루는 것 | 파일 시스템 규칙을 줄여 설계가 단순해진다 |
마지막 줄은 POSIX(Portable Operating System Interface) 이야기입니다. POSIX 는 유닉스 계열 운영체제가 파일을 다루는 방식을 맞춘 표준입니다. HDFS 는 이 표준을 다 따르지 않습니다. 그래서 로컬 디스크에서 파일 중간을 열어 고치던 프로그램은 HDFS 에서 그대로 돌지 않습니다.
NFS(Network File System)와 견주면 차이가 잘 보입니다. NFS 는 서버 한 대의 디스크를 네트워크 너머에서 로컬 디스크처럼 쓰게 합니다. HDFS 는 서버 수백 대의 디스크를 합친 대신 로컬 디스크처럼 쓰는 편함을 내려놓았습니다.
HDFS 를 고르는 경우
한 번 쌓고 여러 번 통째로 훑는 데이터에 맞습니다. 몇 년 치 로그를 모아 밤마다 집계하는 배치 처리가 그런 일입니다. 계산을 데이터가 있는 서버로 보내는 Hadoop 의 계산 도구와 함께 쓸 때 가장 잘 맞습니다.
주문 한 건을 곧바로 읽고 고치는 서비스 데이터베이스 노릇은 못 합니다. 파일을 고쳐 쓸 수 없습니다. 한 번 읽을 때 기다리는 시간도 깁니다. 그런 일은 관계형 데이터베이스가 맡습니다.
클라우드에서는 오브젝트 스토리지가 HDFS 의 역할을 맡는 일이 많습니다. 오브젝트 스토리지는 파일을 디렉터리 트리 대신 키 하나로 찾는 객체로 담는 저장소입니다. Amazon S3(Simple Storage Service)가 그런 저장소입니다.
HDFS 는 데이터를 담는 서버에서 계산도 함께 돌리므로 저장과 계산이 같이 늘어납니다. 저장소를 따로 두면 데이터를 담는 서버와 계산하는 서버를 따로 늘리고 줄일 수 있습니다.
맞물림
HDFS 는 Hadoop 을 이루는 한 부품입니다. 같은 Apache Software Foundation 에서 나온 도구들이 HDFS 위에 서거나 HDFS 를 부릅니다. 이 절은 그중 넷이 HDFS 와 어느 방향으로 붙는지 봅니다.
YARN 과 MapReduce 가 묻는 블록 위치
MapReduce 는 큰 데이터를 조각마다 따로 계산한 뒤 결과를 모으는 계산 방식입니다. 입력 파일의 블록 하나가 계산 조각 하나가 되는 것이 보통입니다.
YARN(Yet Another Resource Negotiator)은 Hadoop 에서 계산 작업을 어느 서버에서 돌릴지 정해 주는 관리자입니다. MapReduce 작업은 YARN 이 정해 준 서버에서 돕니다.
작업을 나눌 때 MapReduce 가 네임노드를 부릅니다. 입력 파일의 블록이 어느 데이터노드에 있는지 묻습니다. 그리고 그 블록을 가진 서버에서 계산을 띄워 달라고 YARN 에 청합니다.
데이터 수백 테라바이트를 네트워크로 옮기는 대신 계산 코드를 데이터 쪽으로 보내는 것입니다. 이것을 데이터 지역성이라고 부릅니다. HDFS 의 데이터노드와 YARN 의 계산 서버를 같은 서버에 함께 띄우는 까닭이 이것입니다.
이렇게 하면 저장과 계산이 한 서버에 묶입니다. 디스크만 더 필요해도 계산 서버까지 함께 늘리게 됩니다.
HBase 가 HDFS 에 쌓는 파일
HBase 는 키로 행 하나를 곧바로 읽고 쓰는 데이터베이스입니다. HBase 는 자기 데이터 파일과 로그를 HDFS 에 씁니다. 복사본을 여러 벌 두는 일은 HDFS 에 맡깁니다.
HDFS 는 파일을 고쳐 쓰지 못하므로 HBase 는 들어온 쓰기를 먼저 메모리에 모읍니다. 모인 것이 커지면 HDFS 에 새 파일로 한꺼번에 씁니다. 행 하나를 고친 것도 옛 파일을 고치지 않고 새 파일에 새 값으로 남습니다.
그래서 HBase 아래의 HDFS 에는 파일이 계속 늘어납니다. HBase 는 작은 파일 여럿을 큰 파일로 합쳐 다시 쓰는 컴팩션을 주기적으로 돌립니다. 이 작업이 도는 동안 디스크와 네트워크가 바빠집니다.
Hive 테이블이 가리키는 디렉터리
Apache Hive 는 HDFS 의 파일을 SQL(Structured Query Language)로 조회하게 해 주는 도구입니다. Hive 에서 테이블 하나는 HDFS 의 디렉터리 하나입니다. 그 디렉터리 안의 파일이 테이블의 행이 됩니다.
테이블 이름과 디렉터리 경로의 짝은 Hive 메타스토어가 쥡니다. Hive 는 조회를 받으면 메타스토어에서 경로를 찾습니다. 그리고 그 디렉터리의 파일을 읽는 계산 작업을 띄웁니다.
행을 조금씩 자주 넣으면 넣을 때마다 디렉터리에 작은 파일이 하나씩 생깁니다. 그래서 앞에서 본 작은 파일 문제가 Hive 테이블에서 자주 터집니다.
ZooKeeper 가 정하는 활성 네임노드
ZooKeeper 는 여러 서버가 한 가지 값에 합의하도록 돕는 조정 서비스입니다. HDFS 는 네임노드 두 대 중 누가 활성인지를 ZooKeeper 로 정합니다.
장애 조치(페일오버)는 죽은 서버의 일을 다른 서버가 넘겨받는 것입니다. 네임노드마다 곁에 장애 조치를 맡는 감시 프로세스가 붙습니다. 감시 프로세스는 자기 네임노드가 살아 있는지 살핍니다.
활성 네임노드의 감시 프로세스는 ZooKeeper 에 활성 자격을 잡아 둡니다. 자격은 한 번에 한 쪽만 쥘 수 있는 표식(잠금)입니다. 활성 네임노드가 죽으면 그 자격이 풀립니다. 그러면 대기 쪽 감시 프로세스가 자격을 잡아 대기 네임노드를 활성으로 올립니다.
위험은 옛 활성 네임노드가 아주 죽지 않고 잠시 멈췄다 돌아오는 경우입니다. 두 대가 저마다 활성이라고 믿고 메타데이터를 쓰면 스플릿 브레인이 됩니다. 그래서 대기 쪽을 올리기 전에 옛 활성 쪽이 더는 쓰지 못하게 막는 절차를 함께 둡니다.
관련 항목
HDFS 가 속하는 상위 분류
분산 파일 시스템 · 파일 시스템 · 분산 시스템 · Hadoop · 오픈 소스 · Apache Software Foundation
HDFS 를 이루는 구성 요소
네임노드 · 데이터노드 · 보조 네임노드 · 저널노드 · 블록 · 메타데이터 · 편집 로그
HDFS 가 데이터를 지키는 방식
복제 · 랙 인식 · 하트비트 · 체크섬 · 이레이저 코딩 · WAL · 고가용성 · 페일오버
HDFS 위에서 도는 Hadoop 도구
YARN · MapReduce · HBase · Apache Hive · ZooKeeper · Apache Spark
HDFS 와 같은 역할을 두고 겨루는 저장소
오브젝트 스토리지 · Amazon S3 · GFS · Ceph · NFS · 데이터 레이크
HDFS 가 맞춘 작업 방식
배치 처리 · 처리량 · 데이터 지역성 · 파이프라인 · 클러스터 · 랙
HDFS 에서 자주 나는 운영 문제
다른 이름: Hadoop Distributed File System · 하둡 분산 파일 시스템