hdfs의 file write protocol에 대한 이해

의외로 자료가 별로 없어서.. 직접 소스코드와 로그를 뒤지면서 정리를 해 보았다.

출처 : Hadoop: The Definitive Guide, Second Edition

일단 책에 나와 있는 내용은 저 정도이고, 실제 소스코드를 찾아보면, 책에는 생략된 block management에 대한 내용도 있다. (사실 요 부분이 궁금해서..)
테스트 환경은 hadoop 0.20.5 / pseudo-distribute 모드이며, protocol 자체는 최근에 릴리즈된 hadoop 1.0 버전과 크게 차이는 없을 것이라고 생각된다. 아래는 local에 있는 102 MB (정확히는 106,168,320 byte) 샘플 파일을 hdfs에 upload하는 과정에서 발생한 log (debug log 포함)들을 모아서 정리한 것이다.

node

time

message

comment

Client

21:50:56

hdfs.DFSClient: /test.txt: masked=rwxr-xr-x

Client hdfs://test.txt 라는 파일을 생성하고자 함

Client

21:50:56

hdfs.DFSClient: Allocating new block

Client NameNode에게 block 요청

NameNode

21:50:56,717

*DIR* NameNode.create: file /test.txt for DFSClient_439896788 at 127.0.0.1

Client file을 요청하면, NameNode namespace (메모리)에서 빈 file entry 생성

NameNode

21:50:56,717

DIR* NameSystem.startFile: src=/test.txt, holder=DFSClient_439896788, ClientMachine=127.0.0.1, replication=1, overwrite=false, append=false

FSNameSystem.startFileInternal()

NameNode

21:50:56,718

DIR* FSDirectory.addFile: /test.txt is added to the file system

NameNode

21:50:56,718

DIR* NameSystem.startFile: add /test.txt to namespace for DFSClient_439896788

NameNode

21:50:56,724

ugi=chaehyun    ip=/127.0.0.1   cmd=create  src=/test.txt  dst=null    perm=chaehyun:supergroup:rw-r--r--

FSEditLog.logSync()가 완료 된 뒤 출력된 메시지.
메모리에 추가된 file entry가 정상적으로 EditLog (file)로 기록됨

NameNode

21:50:56,729

*BLOCK* NameNode.addBlock: file /test.txt for DFSClient_439896788

/test.txt 파일을 위한 block을 요청

NameNode

21:50:56,729

BLOCK*NameSystem.getAdditionalBlock: file /test.txt for DFSClient_439896788

해당 file의 내용을 기록할 block 이 block을 저장할 machine list를 반환
이 시점에서 block id와 DataNode list가 결정되며, Client는 DataNode list 중 첫 번째 machine에게 data를 전송

NameNode

21:50:56,729

DIR* FSDirectory.addFile: /test.txt with blk_-1208142459293778377_1012block is added to the in-memory file system

메모리에 저장된 file entry에 할당받은 block 정보들을 추가

NameNode

21:50:56,729

BLOCK* NameSystem.allocateBlock: /test.txt. blk_-1208142459293778377_1012

FSNameSystem.allocateBlock()

Client

21:50:56

hdfs.DFSClient: pipeline = 127.0.0.1:50010

 

Client

21:50:56

hdfs.DFSClient: Connecting to 127.0.0.1:50010

Client DataNode로 접속

Client

21:50:56

hdfs.DFSClient: Send buf size 131072

Client 해당 DataNode data를 packet 단위로 쪼개서 전송 시작

DataNode

21:50:56,750

Receiving block blk_-1208142459293778377_1012 src: /127.0.0.1:6047 dest: /127.0.0.1:50010

DataNode Client로 부터 첫 번째 block을 받기 시작

DataNode

21:50:57,606

PacketResponder 0 for block blk_-1208142459293778377_1012Closing down.

DataNode Client로 부터 첫 번째 block 받기를 끝냄

Nadenode

21:50:57,609

*BLOCK* NameNode.blockReceived: from 127.0.0.1:50010 1 blocks.

DataNode NameNode에게 보고.
"형님 block 잘 받았습니다!"

Nadenode

21:50:57,609

BLOCK* NameSystem.blockReceived: blk_-1208142459293778377_1012 is received from 127.0.0.1:50010

해당 block들을 정상적으로 수신했음을 알림. 
"오~ 니가 이 block들을 받았구나~"

Nadenode

21:50:57,609

BLOCK* NameSystem.addStoredBlock: blockMap updated: 127.0.0.1:50010 is added to blk_-1208142459293778377_1012 size 67108864

NameNode block들을 실제로 저장한 DataNode들의 정보를 저장
"나중에 딴 소리 못 하게 적어놔야지.."

Client

21:50:57

Allocating new block

Client가 두 번째 block을 요청
"아직 데이터가 많이 남았습니다! block 하나 더 주세요!"

Nadenode

21:50:57,610

*BLOCK* NameNode.addBlock: file /test.txt for DFSClient_439896788

Nadenode

21:50:57,610

BLOCK* NameSystem.getAdditionalBlock: file /test.txt for DFSClient_439896788

NameNode에서 추가 block 할당

Nadenode

21:50:57,610

DIR* FSDirectory.addFile: /test.txt with blk_5548041259556473375_1012 block is added to the in-memory file system

Nadenode

21:50:57,610

BLOCK* NameSystem.allocateBlock: /test.txt. blk_5548041259556473375_1012

Client

21:50:57

pipeline = 127.0.0.1:50010


Client

21:50:57

Connecting to 127.0.0.1:50010

Client DataNode packet 단위로 쪼개진 data 전송 시작

DataNode

21:50:57,611

Receiving block blk_5548041259556473375_1012 src: /127.0.0.1:6050 dest: /127.0.0.1:50010

DataNode Client로 부터 두 번째block을 받기 시작

DataNode

21:50:58,034

PacketResponder 0 for block blk_5548041259556473375_1012 Closing down.

DataNode Client로 부터 두 번 째 block 받기를 끝냄

Nadenode

21:50:58,037

*BLOCK* NameNode.blockReceived: from 127.0.0.1:50010 1 blocks.

DataNode NameNode에게 보고

Nadenode

21:50:58,037

BLOCK* NameSystem.blockReceived: blk_5548041259556473375_1012 is received from 127.0.0.1:50010

해당 block들을 정상적으로 수신했음을 알림

Nadenode

21:50:58,037

BLOCK* NameSystem.addStoredBlock: blockMap updated: 127.0.0.1:50010 is added to blk_5548041259556473375_1012 size 39059456

NameNode block들을 실제로 저장한 DataNode들을 저장

Nadenode

21:50:58,038

*DIR* NameNode.complete: /test.txt for DFSClient_439896788

Client가 파일을 다 썼음

Nadenode

21:50:58,038

DIR* NameSystem.completeFile: /test.txt for DFSClient_439896788

file을 구성하는 모든 block들이 최소 replication 이상 복사 되었음을 확인

Nadenode

21:50:58,038

Removing lease on  file /test.txt from Client DFSClient_439896788

Nadenode

21:50:58,038

DIR* FSDirectory.closeFile: /test.txt with 2 blocks is persisted to the file system

Nadenode

21:50:58,038

DIR* NameSystem.completeFile: file /test.txt is closed by DFSClient_439896788

파일 쓰기 완료

  
복잡해 보이니 간단히 요약해보자.

  1. Client에서 파일을 쓰기 위해 NameNode에게 file 생성을 요청을 하면, NameNode는 먼저 메모리에 해당 path에 이미 파일이 존재하는지, Client가 적절한 권한을 가지고 있는지 확인한 다음, 문제가 없으면, namespace 상에서 file entry를 생성한다.
  2. 그런 다음, 실제 file을 저장할 block을 할당하고, Client의 위치와 DataNode들의 저장 상황, 위치 등을 고려하려, 해당 block을 저장할 DataNode 들을 선정한다.
  3. Client는 NameNode로 부터 받은 block 정보를 이용하여, 첫 번째 DataNode에 data를 전송하고, 전송이 정상적으로 완료되면, NameNode에게 다음 block을 요청한다.
  4. NameNode가 다시 block을 할당하면, Client가 file의 다음 내용을 DataNode에게 전송하고, 이 과정을 반복한다.
  5. Client에 있는 file의 모든 data가 DataNode들에게 정상적으로 전송이 끝나고, NameNode가 해당 파일에 속하는 모든 block들이 최소한의 복제 계수만큼 복사가 완료되었다고 판단하면 file write가 종료된다.

그 외 내가 궁금해서 찾아본 내 맘대로 FAQ)

  • Q) NameNode는 block을 어떻게 관리하나?
    A) block은 long으로 구분되며, 이론 상 2^64 개의 block을 생성할 수 있다. block은 계속 생성되고 소멸되므로, block id (long)을 관리하는 일이 중요한데, NameNode에서는 현재 굉장히 simple하게-_- 관리를 한다. 랜덤하게 block id값을 고른 다음, 아직 사용하지 않은 숫자가 나올 때 까지 계속한다. (허무할 정도로 간단하게..)
    참고로, 사용 중인 block들에 대한 정보는 BlocksMap 이라는 class에서 관리하며, 내부에서는 LightWeightGSet 이라는 자체 set class를 만들어서 저장한다.

  • Q) block을 저장할 DataNode들은 어떻게 선발하나요?
    A) hadoop 0.20.5 버전에서는 ReplicationTargetChooser 라는 class가 해당 역할을 수행한다. 현재 버전에서의 복사 전략은 Client가 DataNode일 경우에는 먼저 해당 DataNode가 첫 번째 노드로 선정된다. (이럴 경우, 첫 번째 복사는 network을 타지 않기 때문에 빠르다) 만약 Client가 DataNode가 아니면, 그냥 랜덤하게 고른다.
    그리고 두 번째 node는 첫 번째 DataNode와 다른 rack에 위치한 DataNode들을 고르고, 세 번째 DataNode는 첫 번째 node와 같은 rack에 있는 node를 고른다. 그리고 네 번째 부터는 그냥 랜덤하게 고른다.
    이렇게 함으로써, data 전송을 위한 network traffic을 줄이고, data 안정성을 최대한으로 높일 수 있다. (복제계수가 2이상이면, 최소 하나 이상의 block은 다른 rack에 저장되므로, 한 rack 전체가 날아가더라도, file은 살아 있을 것이다)
    그리고 ReplicationTargetChooser에게 DataNode를 요청할 때는 exclude list를 함께 줘서, 장애가 발생한 DataNode나, 이미 해당 block을 저장하고 있는 DataNode, decommission 중인 DataNode들은 제외되도록 설계되어 있다.


댓글

Designed by JB FACTORY