1. HDFS 기초
HDFS는 수십 테라바이트 또는 페타바이트 이상의 대용량 파일을 분산된 서버에 저장하고, 많은 클라이언트가 저장된 데이터를 빠르게 처리하 ㄹ수 있게 설계된 파일 시스템이다. 기존 대용량 파일 시스템과 가장 큰 차이점은 저사양 서버를 이요해 스토리지를 구성할 수 있다는 것이다. 그러나 완전 대체 가능한 것은 아니다.
다음은 HDFS의 설계에 있어 4가지 목표이다.
- 장애복구: 분산 서버에는 다양한 장애가 발생할 수 있는데, 이때 데이터가 유실되는 심각한 상황이 발생할 수 있다. HDFS는 이러한 장애를 빠르게 감지하고, 대처할 수 있게 설계되어있다. HDFS에 데이터를 저장하면, 복제 데이터도 함께 저장되어 데이터 유실을 방지한다. 또한, 분산 서버 간에는 주기적으로 상태를 체크해 빠른 시간에 장애를 인지하고, 대처할 수 있게 도와준다.
- 스트리밍 방식의 데이터 접근: HDFS는 클라이언트의 요청을 빠른 시간 내에 처리하는 것보다 동일한 시간 내에 더 많은 데이터를 처리하는 것을 목표로 한다. 따라서 랜덤 방식의 데이터를 고려하지 않아 인터넷 뱅킹이나 쇼핑몰과 같은 서비스에서 기존 파일 시스템 대신 사용하는 것은 적합하지 않다. HDFS는 랜덤 방식 대신 스트리밍 방식으로 데이터를 접근한다. 그래서 클라이언트는 끊김없이 연속된 흐름으로 데이터에 접근할 수 있다.
- 대용량 데이터 저장: HDFS는 하나의 파일이 기가바이트에서 테라바이트 이상의 크기로 저장될 수 있게 설계되었다. 그래서 높은 데이터 전송 대역폭과 하나의 클러스터에서 수백 대의 노드를 지원할 수 있다. 또한, 하나의 인스턴스에서는 수백만 개 이상의 파일을 지원하다.
- 데이터 무결성: 데이터베이스에서 데이터 무결성이란 데이터베이스에 저장되는 데이터의 일관성을 의미한다. HDFS에서는 한번 저장한 데이터는 더는 수정할 수 없고, 읽기만 가능하게 해서 데이터 무결성을 유지한다. 그러나, 파일의 이동, 삭제, 복사는 가능하다.
2. HDFS 아키텍처
2.1 블록 구조 파일 시스템
HDFS는 블록 구조 파일 시스템이다. HDFS에 저장하는 파일은 특정 크기의 블록으로 나눠져 분산된 서버에 저장된다. 블록 크기는 기본적으로 64MB(Hadoop2에서는 128MB)로 설정되어있지만, 이는 언제든 변경 가능하다.
왜 블록 크기가 64MB나 되는 건가? 그 이유는 다음과 같다.
- 디스크 seek time의 감소: 디스크 탐색 시간은 데이터의 위치를 찾는데 걸리는 시간인 seek time과 원하는 데이터의 섹터에 도달하는데 걸리는 시간인 search time의 합이다. HDFS는 seek time이 디스크 전송 대역폭의 1%만 사용하는데 중점을 뒀다. 따라서, 개발 당시에 평균적인 디스크 전송 대역폭은 100MB/s였기 때문에 이에 근접한 64MB를 사용한 것이다.
- 네임노드가 유지하는 메타데이터의 크기 감소: 네임노드는 블록 위치, 파일명, 디렉터리 구조, 권한 정보와 같은 메타데이터 정보를 메모리에 저장하고 관리한다. 예를 들어 100MB 크기의 파일을 저장할 경우 HDFS는 두 블록에 대한 메타데이터만 저장하면 되는데, 일반적인 파일 시스템은 블록 크기가 4k~8k 이기 때문에 같은 크기 대비 더 많은 메타데이터를 저장해야 한다. 이는 블록 크기가 크기 때문에 이득을 얻을 수 있다는 것을 의미한다.
- 클라이언트와 네임노드의 통신 감소: 클라이언트가 HDFS에 저장된 파일을 접근할 때 네임노드에서 해당 파일을 구성하는 블록의 위치를 조회한다. 클라이언트는 스트리밍 방식으로 데이터를 쓰기 때문에 특별한 경우를 제외하고는 네임노드와 통신할 필요가 없어진다.
HDFS는 블록을 아래의 그림과 같이 저장할 때 기본적으로 3개씩(설정 파일 수정으로 개수를 조절 할 수 있다) 블록의 복제본을 각자 다른 노드에 저장한다. 블록이 복제돼 있기 때문에 오류가 발생하더라도 복제된 블록을 이용해 데이터를 조회할 수 있다.

2.2 네임노드와 데이터노드
HDFS는 Master-Slave 아키텍처이다. Master 노드는 네임노드(NameNode)이며, 슬레이브 노드는 데이터노드(DataNode)이다. 아래의 그림은 HDFS의 아키텍처를 나타낸 것이다.

네임노드
HDFS의 네임노드는 다음과 같은 기능을 수행한다.
- 메타데이터 관리: 메타데이터는 파일 시스템 이미지(파일명, 디렉터리, 크기, 권한)와 파일에 대한 매핑 정보로 구성된다. 네임노드는 클라이언트에게 빠르게 응답할 수 있게 메모리에 전체 메타데이터를 로딩해서 관리한다.
- 데이터노드 모니터링: 데이터노드는 네임노드에게 3초마다 하트비트를 전송한다. 이는 데이터노드 상태 정보와 데이터노드에 저장돼 있는 블록의 목록으로 구성된다. 네임노드는 이를 이용해 데이터노드의 실행 상태와 용량을 모니터링한다. 이때 일정 기간 동안 하트비트를 수신받지 못하면 해당 데이터노드에 장애가 발생한 것으로 판단한다.
- 블록 관리: 네임노드는 장애가 발생한 데이터 노드를 발견하면 해당 데이터노드의 블록을 새로운 데이터노드로 복제한다. 또한, 용량이 부족한 데이터노드가 있다면 용량에 여유가 있는 데이터노드로 블록을 이동시킨다. 마지막으로 네임노드는 블록의 복제본 수도 관리한다. 복제본 수가 일치하지 않는 블록이 발견되면 삭제하거나 추가한다.
- 클라이언트 요청 접수: 클라이언트가 HDFS에 접근하려면 반드시 네임노드에 먼저 접속해야 한다. HDFS에 파일을 저장하는 경우 기존 파일의 저장 여부와 권한 확인의 절차를 거쳐서 저장을 승인한다. 또한, HDFS에 저장된 파일을 조회하는 경우 블록의 위치 정보를 반환한다.
데이터노드
HDFS의 데이터노드는 저장하는 파일을 로컬 디스크에 유지한다. 이때 로컬 디스크에 저장되는 파일은 두 종류로 구성된다. 첫 번째 파일은 실제 데이터가 저장돼 있는 로우 데이터이며, 두 번째 파일은 체크섬이나 파일 생성 일자와 같은 메타데이터가 설정돼 있는 파일이다.
2.3 HDFS의 파일 저장
HDFS의 파일 저장은 클라이언트가 네임노드에게 파일을 저장을 요청하는 단계, 클라이언트가 데이터노드에게 패킷을 전송하는 단계, 클라이언트가 파일 저장을 완료하는 단계로 구성된다.
파일 저장 요청
클라이언트가 HDFS에 파일을 저장하는 경우 파일을 저장하기 위한 스트림을 생성해야 한다. 다음은 파일 저장용 스트림 객체를 생성하는 과정이다.

- Hadoop은 FileSystem이라는 추상 클래스에 일반적인 파일 시스템을 관리하기 위한 메서드를 정의하게 된다. 그리고 이 추상 클래스를 상속받아 각 파일 시스템에 맞게 구현된 다양한 파일 시스템 클래스를 제공 하고 있다. HDFS 에 파일을 저장하는 경우 DistributedFileSystem을 사용하며 클라이언트는 DistributedFileSystem의 create 메서드를 호출해 스크림 객체를 생성하게 된다.
- DistributedFileSystem은 클라이언트에게 반환할 스트림 객체로, FSDataOutputStream을 생성한다. FSDataOutputStream은 데이터노드와 네임노드의 통신을 관리하는 DFSOutputStream을 래핑하는 클래스 이며 DistributedFieSystem은 DFSOutputStream을 생성하기 위해서 DFSClient의 create 메서드를 호출하게 된다.
- DFSClient는 DFSOutputStream을 생성하게 되며 이때 DFSOutputStream은 RPC 통신으로 네임노드의 create 메서드를 호출하게 된다. 네임노드는 클라이언트의 요청이 유효한지 검사를 진행하게 되며 이미 생성된 파일이거나 권한에 문제가 있거나, 현재 파일 시스템의 용량을 초과한다면 오류를 발생시키게 된다. 네임노드는 파일 유효성 검사 결과가 정상 일 경우 파일 시스템 이미지에 해당 파일의 엔트리를 추가 한다. 마지막으로 네임노드는 클라이언트에게 해당 파일을 저장할 수 있는 제어권을 부여 하게 된다.
- 네임노드의 유효성 검사를 통과했다면 DFSOutputSteram 객체가 정상적으로 생성 된다. DistributedFileSystem은 DFSOutputStream을 래핑한 FSDataOutputStream 을 클라이언트에게 반환한다.
패킷전송
클라이언트가 네임노드에게서 파일 제어권을 얻게 되면 파일 저장을 진행한다. 이때 클라이언트는 파일을 네임노드에게 전송하지 않고 각 데이터노드에 전송한다. 그리고 저장할 파일은 패킷 단위로 나눠서 전송한다. 다음은 클라이언트의 패킷 전송 과정을 나타낸 것이다.

- 클라이언트는 스트림 객체의 write 메서드를 호출해 파일 저장을 시작하게 되며 DFSOutputStream은 클라이언트가 저장하는 파일을 64k 크기의 패킷으로 분할한다.
- DFSOutputStream은 전송할 패킷을 내부 큐인 데이터큐(dataQueue)에 등록한다. DFSOutputStream의 내부스레드가 데이터큐에 패킷이 등록된 것을 확인하면 DFSOutputSream 의 내장 클래스인 DataStreamer는 네임노드의 addBlock 메서드를 호출 하게 된다.
- 네임노드는 DataStreamer에게 블록을 저장할 데이터노드 목록을 반환한다. 이 목록은 복제본 수와 동일한 수의 데이터노드를 연결한 파이프라인을 형성한다. 예를 들어 HDFS의 복제본 수가 3으로 설정돼 있다면 데이터노드 3개가 파이프라인을 형성하게 된다.
- DataStreamer 는 파이프라인의 첫 번째 데이터노드부터 패킷 전송을 시작하게 되며, 데이터노드는 클라이언트와 다른 데이터노드로 부터 패킷을 주고 받기 위해 DataXceiverServer 데몬을 실행한다. DataXceiverServer는 클라이언트 및 다른 데이터노드와 패킷 교환 기능 제공한다. 첫 번째 데이터노드는 패킷을 저장하면서, 두 번째 데이터노드에게 패킷 저장을 요청한다. 두 번째 데이터노드도 패킷을 저장하면서, 세 번째 데이터노드에게 패킷 저장을 요청한다. 마지막으로 세 번째 데이터노드가 패킷을 저장한다. 또한 첫 번째 데이터노드에 패킷을 저장할 때 DFSOutputStream 은 내부 큐인 승인큐(askQueue) 에 패킷을 등록 하고 승인큐는 패킷 전송이 완료됐다는 응답을 기다리는 패킷이 등록돼 있으며, 모든 데이터노드로 부터 응답을 받았을 때만 해당 패킷이 제거된다.
- 각 데이터노드는 패킷이 정상적으로 저장되면 자신에게 패킷을 전송한 데이터노드에게 ACK 메세지를 전송한다. ACK 메세지는 패킷 수신이 정상적으로 완료됐다는 승인 메세지다. 승인 메세지는 파이프라인을 통해 DFSOutputStream 에게까지 전달된다.
- 각 데이터노드는 패킷 저장이 완료되면 네임노드의 blockReceived 메서드를 호출한다. 이를 통해 네임노드는 해당 블록이 정상적으로 저장됐다는 것을 인지한다.
- DFSOutputStream의 내부 스레드인 ResponseProcessor는 파이프라인에 있는 모든 데이터노드로부터 승인 메시지를 받게 되면 해당 패킷을 승인큐에서 제거한다. 만약 패킷을 전송하는 중에 장애가 발생하면 승인 큐에 있는 모든 패킷을 데이터큐로 이동한다. 그리고 네임노드에게서 장애가 발생한 데이터노드가 제거된 새로운 데이터노드 목록을 내려 받게 되고 마지막으로 새로운 파이프라인을 생성 한 후에 다시 패킷 전송 작업을 시작한다.
파일 닫기
이제 스트림을 닫고 파일 저장을 완료할 차례다. 다음은 클라이언트가 파일을 닫는 과정을 나타낸다.

- 클라이언트는 DistributedFileSystem의 Close 메서드를 호출해 파일 닫기를 요청한다.
- DistribuedFileSystem은 DFSOutputStream의 Close 메서드를 호출 합니다 이 메서드는 DFSOutputStream에 남아 있는 모든 패킷을 파이프라인으로 플러쉬(flush)한다.
- DFSOutputStream은 네임노드의 complete 메서드를 호출해 패킷이 정상적으로 저장됐는지 확인한다. 네임노드의 최소 블록 복제본 수만 저장됐다면 complete 메서드는 true 를 반환한다. DFSOutputStream은 true를 반환 받으면 파일 저장이 완료 된 것으로 설정한다.
2.4 HDFS의 파일 읽기
HDFS는 파일 조회 요청, 블록 조회, 입력 스트림 닫기로 구성된다.
파일 조회 요청
클라이언트는 입력 스트림 객체를 이용해 HDFS에 저장된 파일을 조회할 수 있다. 다음은 클라이언트가 입력 스트림 객체를 생성하는 과정을 나타낸다.

- 클라이언트는 DistriubutedFileSystem의 open 메서드를 호출해 스트림 객체 생성을 요청한다.
- DistributedFileSystem은 FSDataInputStream 객체를 생성한다. 이때 FSDataInputStream은 DFSDataInputStream과 DFSInputStream을 차례대로 래핑한다. DistributedFileSystem은 마지막 래핑이 되는 DFSInputStream을 생성하기 위해 DFSClient 의 open 메서드를 호출한다.
- DFSClient는 DFSInputStream을 생성 하고 이때 DFSInputStream은 네임노드의 GetBlockLocations 메서드를 호출해 조회 대상 파일의 블록 위치 정보를 조회 하게 된다.
- 네임노드는 조회 대상 파일의 블록 위치 목록을 생성한 후 목록을 클라이언트에 가까운 순으로 정렬 한다. 정렬이 완료 되면 DFSInputStream에 정렬된 블록 위치 목록을 반환한다. DistributedSystem은 DFSClient로 부터 전달 받은 DFSInputStream 을 이용해 FSDataInputStream 으로 생성해서 클라이언트에게 반환한다.
블럭 조회
다음은 클라이언트가 로컬 서버와 원격 서버에 있는 블록을 조회하는 과정이다.

- 클라이언트는 입력 스트림 개체의 read 메서드를 호출해 스트림 조회를 요청한다.
- DFSInputStream은 첫 번째 블록과 가장 가까운 데이터노드를 조회한 후, 해당 블록을 조회하기 위한 리더기를 생성 하고, 클라이언트와 블록이 저장된 데이터노드가 같은 서버에 있다면 로컬 블록 리더기 인 "BlockReadLocal"을 성생하게 되고, 데이터노드가 원격에 있을 경우 "RemoteBlockReader" 를 생성한다.
- DFSInputStream은 리더기의 read 메서드를 호출해 블록을 조회하게 되고, BlockReaderLocal 은 로컬 파일 시스템에 저장된 블록을 DFSInputStream 에게 반환하게 된다. 그리고 RemoteBlockReader는 원격에 있는 데이터노드에게 블록을 요청하며, 데이터노드의 DataXceiverServer가 블록을 DFSInputStream 에게 반환한다.
- DFSInputStream은 파일을 모두 읽을 때까지 계속해서 블록을 조회하며, 만약 DFSInputStream이 저장하고 있던 블록을 모두 읽었는데도 파일을 모두 읽지 못했다면 네임노드의 getBlockLocations 메서드를 호출해 불필요한 블록 위치 정보를 다시 요청한다. 이와 같이 파일을 끊김 없이 연속적으로 읽기 때문에 클라이언트는 스트리밍 데이터를 읽는 것처럼 처리 할 수 있다.
- 네임노드는 DFSInputStream에 클라이언트에게 가까운 순으로 정렬된 블록 위치 목록을 반환한다.
입력 스트림 닫기
클라이언트가 모든 블록을 읽고 나면 입력 스트림 객체를 닫아야 한다. 다음은 입력 스트림을 닫는 과정을 나타낸다.

- 클라이언트는 입력 스트림 객체의 close 메서드를 요청 해 스트림 닫기를 요청한다.
- DFSInputStream은 데이터노드와 연결돼 있는 커넥션을 종료 하며 블록 조회용으로 사용하였던 리더기도 닫아 준다.
2.5 보조네임노드
네임노드는 메모리에만 데이터를 유지하면 서버가 재부팅될 때 모든 메타데이터가 유실될 수 있다. 이를 극복하기 위해 HDFS는 editslog와 fsimage라는 두 개의 파일을 생성한다. editlog는 HDFS의 모든 변경 이력을 저장한다. HDFS는 클라이언트가 파일을 저장하거나, 삭제하거나, 혹은 파일을 이동하는 경우 editlog와 메모리에 로딩돼 있는 메타데이터에 기록한다. 그리고 fsimage는 메모리에 저장된 메타데이터의 파일 시스템 이미지를 저장한 파일이다. 네임노드가 구동될 경우, 다음과 같은 단계로 위 두 개의 파일을 사용한다.
- 네임노드가 구동되면 로컬에 저장된 fsimage와 editlog를 조회한다.
- 메모리에 fsimage를 로딩해 파일 시스템 이미지를 생성한다.
- 메모리에 로딩된 파일 시스템 이미지에 editslog에 기록된 변경 이력을 적용한다.
- 메모리에 로딩된 파일 시스템 이미지를 이용해 fsimage 파일을 갱신한다.
- editslog를 초기화한다.
- 데이터노드가 전송한 블록리포트를 메모리에 로딩된 파일 시스템 이미지에 적용한다.
위 단계는 평상시에는 아무런 문제가 없지만 editlog의 크기가 클 경우 문제가 될 수 있다. 이러한 문제점을 해결하기 위해 보조네임노드는 주기적으로 네임노드의 fsimage를 갱신하는 역할을 하며, 이러한 작업을 checkpoint라고 한다.
checkpointing이 완료되면 네임노드의 fsimage는 최신 내역으로 갱신되며, editlog의 크기도 축소된다. 참고로 checkpointing은 1시간마다 한 번씩 일어나며, 하둡 환경설정 파일에 fs.checkpoint.period 속성값을 수정해서 제어할 수 있다.
보조네임노드는 해당 데몬이 다운되어도 문제없이 Hadoop이 동작하지만 네임노드가 재구동하는 상황이 발생할 경우 editlog의 크기가 너무 커서 네임노드의 메모리에 로딩되지 못하는 상황이 발생할 수 있기 때문에 주기적으로 보조네임노드의 구동을 확인해야 한다.
'Data engineering > Apache Hadoop' 카테고리의 다른 글
| MapReduce (0) | 2023.03.31 |
|---|---|
| Apache Hadoop1 설치 (0) | 2023.03.24 |
| Apache Hadoop 개요 (0) | 2023.03.20 |