Skip to content

하둡프로그래밍2 #
Find similar titles

개요 #

[하둡프로그래밍]에서는 하둡 설치와 설치한 하둡을 통해 기본적인 실습을 해보았다. 우리는 간략하게나마 HDFS를 통해 데이터가 분산 저장된다는 것을 알고 있다. 또한 HDFS가 장애 허용하고 있으며 복구할 수 있다는 것도 알고 있다. 그렇다면 HDFS는 어떻게 장애를 인지하고 대처하는 것일까? HDFS는 데이터를 복제한다고 하는데 속도 부하를 어떻게 해결하고 있을까? 이러한 질문에 대한 답을 찾기 위해 이번 글에서는 하둡을 지탱하는 주요 아키텍처 중 하나인 HDFS가 어떻게 작동하는지를 알아보고자 한다.

HDFS 주요 특징 #

  • 대용량 데이터 저장
    • HDFS는 수십 테라바이트 또는 페타바이트 이상의 대용량 파일을 분산된 서버에 저장할 수 있다. 또한 인스턴스에서는 수백만 개 이상의 파일을 지원한다.
  • 데이터 무결성
    • 데이터 무결성은 데이터의 일관성을 의미한다. HDFS는 데이터를 한 번 쓰고 여러 번 읽는 액세스 모델을 통해 데이터의 안정성을 저해하는 요소를 막는다. 데이터 수정은 불가능하지만 이동,수정,복사할 수 있는 인터페이스를 제공한다.
    • 하둡 2.0 알파 버전부터는 HDFS에 저장된 파일에 append가 가능하다.
  • 장애 허용
    • HDFS는 데이터를 블록 단위로 저장하는데 기본적으로 3개의 다른 노드에 복제하여 저장한다. 따라서 특정 네트워크 스위치나 랙이 작동하지 않아도 데이터 유실을 방지한다.
    • 분산 서버 간에는 주기적으로 데이터 노드 상태를 체크해 빠른 시간에 장애를 인지하고, 장애를 복구할 수 있게 도와준다.
  • 데이터 스트리밍
    • HDFS는 데이터에 빠르게 접근하는 것보다 배치 프로세싱을 위해 디자인되었다. 동일한 시간 내에 더 많은 데이터를 처리하기 위해 스트리밍 방식으로 데이터에 접근한다.

HDFS 아키텍처 #

블록 #

일반적으로 물리적으로 데이터를 저장하는 디스크는 한 번에 읽고 쓸 수 있는 최대량으로 나누어 데이터를 관리한다. 보통 디스크 블록의 크기는 보통 512바이트이다. HDFS는 64메가바이트~256메가바이트(버전에 따라 상이)로 블록을 관리한다. HDFS의 블록 단위가 보통 디스크의 블록 단위보다 큰 이유는 HDFS 블록 단위가 크기 때문에 탐색비용(디스크 시크 타임)을 최소화할 수 있기 때문이다. 또한 데이터 전송에 더 많은 시간을 할당할 수 있다. 그리고 블록의 크기가 크면 메타데이터의 사이즈를 줄일 수 있다. 블록의 크기가 작으면 많은 블록이 생기고 그에 따라 각각 메타정보를 유지해야 한다. HDFS는 기본 블록 크기가 크기 때문에 메타데이터 크기가 감소한다.

HDFS의 fsck 명령어로 블록을 관리 할 수 있다. 아래 명령어의 /user/root/input/ 경로를 추가하면 해당 경로 아래에 있는 블록의 상황을 순차적으로 볼 수 있다.

$hdfs fsck /user/root/input/ -files -blocks
Connecting to namenode via http://master:50070/fsck?ugi=root&files=1&blocks=1&path=%2Fuser%2Froot%2Finput
FSCK started by root (auth:SIMPLE) from /192.168.10.1 for path /user/root/input at Thu Jul 18 08:31:09 KST 2019
/user/root/input <dir>
/user/root/input/airline <dir>
/user/root/input/airline/2006.csv 672068096 bytes, 6 block(s):  OK
0. BP-1712187933-192.168.10.1-1553:blk_1073741867_1043 len=134217728 Live_repl=2
1. BP-1712187933-192.168.10.1-1553:blk_1073741868_1044 len=134217728 Live_repl=2
2. BP-1712187933-192.168.10.1-1553:blk_1073741869_1045 len=134217728 Live_repl=2
3. BP-1712187933-192.168.10.1-1553:blk_1073741870_1046 len=134217728 Live_repl=2
4. BP-1712187933-192.168.10.1-1553:blk_1073741871_1047 len=134217728 Live_repl=2
5. BP-1712187933-192.168.10.1-1553:blk_1073741872_1048 len=979456 Live_repl=2

/user/root/input/airline/2007.csv 702878193 bytes, 6 block(s):  OK
0. BP-1712187933-192.168.10.1-1553:blk_1073741873_1049 len=134217728 Live_repl=2
1. BP-1712187933-192.168.10.1-1553:blk_1073741874_1050 len=134217728 Live_repl=2
2. BP-1712187933-192.168.10.1-1553:blk_1073741875_1051 len=134217728 Live_repl=2
3. BP-1712187933-192.168.10.1-1553:blk_1073741876_1052 len=134217728 Live_repl=2
4. BP-1712187933-192.168.10.1-1553:blk_1073741877_1053 len=134217728 Live_repl=2
5. BP-1712187933-192.168.10.1-1553:blk_1073741878_1054 len=31789553 Live_repl=2

/user/root/input/airline/2008.csv 689413344 bytes, 6 block(s):  OK
0. BP-1712187933-192.168.10.1-1553:blk_1073741879_1055 len=134217728 Live_repl=2
1. BP-1712187933-192.168.10.1-1553:blk_1073741880_1056 len=134217728 Live_repl=2
2. BP-1712187933-192.168.10.1-1553:blk_1073741881_1057 len=134217728 Live_repl=2
3. BP-1712187933-192.168.10.1-1553:blk_1073741882_1058 len=134217728 Live_repl=2
4. BP-1712187933-192.168.10.1-1553:blk_1073741883_1059 len=134217728 Live_repl=2
5. BP-1712187933-192.168.10.1-1553:blk_1073741884_1060 len=18324704 Live_repl=2

Status: HEALTHY #상태 (예시 : HEALTHY, CORRUPT 등)
 #현재 사용 중인 byte
 Total size:    2064359633 B 
 Total dirs:    2
 Total files:   3
 Total symlinks:        0
 Total blocks (validated):  18 (avg. block size 114686646 B)
 #최소 개수로 복제된 블록 수
 Minimally replicated blocks:   18 (100.0 %) 
 #기본 복제 수 보다 더 복제된 블록 수
 Over-replicated blocks:    0 (0.0 %) 
 #기본 복제 수 보다 덜 복제된 블록 수
 Under-replicated blocks:   0 (0.0 %) 
 #규정을 위반한 블록 수 (예시 : 같은 랙에 복제되면 안 됨)
 Mis-replicated blocks:     0 (0.0 %) 
 #기본 복제 수
 Default replication factor:    2 
 #복제 평균
 Average block replication: 2.0 
 #오류 블록 수
 Corrupt blocks:        0 
 #복제 블록이 없는 블록 수
 Missing replicas:      0 (0.0 %) 
 #데이터 노드 수
 Number of data-nodes:      4 
 #랙 수
 Number of racks:       1 
FSCK ended at Thu Jul 18 08:31:09 KST 2019 in 2 milliseconds

위의 내용처럼 fsck 명령어를 통하여 Corrupt blocks, Missing replicas 등 오류 현상을 확인할 수 있다. 기본적으로 블록에 문제가 생기면 NameNode가 다른 데이터 노드에 복제된 데이터를 가져와 자동으로 복구를 수행하지만 정상적인 블록이 없는 경우 등 예외 상황에서는 오류를 발견하고 수정하지는 않는다. 이때 사용자가 커럽트된 파일을 삭제하고 원본 파일을 다시 HDFS에 올려주어야 한다.

#커럽트 상태 파일을 /lost+found 위치로 이동
$hdfs fsck -move
#커럽트 상태 파일을 삭제 
$hdfs fsck -delete

데이터 노드의 개수보다 복제 개수를 많이 지정하면 Under-replicated 상태가 발생할 수 있다. 이때는 기본 복제 수 보다 덜 복제된 경우이다.

#해당 경로의 복제 개수를 5개로 조정
$hadoop fs -setrep 5 /user/root/input/ 
#또는 해당 경로 하위의 모든 파일의 복제 개수를 5개로 조정
$hadoop fs -setrep 5 -R /user/root/input/

자주 읽는 블록은 블록 캐싱을 통하여 데이터 노드의 메모리에 캐싱할 수 있다. 블록을 포함하고 있는 파일을 단위로 캐싱할 수도 있으므로 조인에 사용되는 데이터들을 등록하여 읽기 성능을 높일 수 있다.

# testpool1 이름의 pool 등록
$hdfs cacheadmin -addPool testpool1 
Successfully added cache pool testpool1.
# 등록된 pool에 path 등록
$hdfs cacheadmin -addDirective -path /user/root/input -pool testpool1 
Added cache directive 2
$hdfs cacheadmin -listDirectives
Found 1 entry
 ID POOL    REPL    EXPIRY  Path
 2  testpool1   1   never   /user/root/input

네임노드 #

네임노드는 HDFS의 마스터 서버이다. 메타데이터 관리, 데이터 노드 관리, 블록 관리, 클라이언트 요청 접수 등의 기능을 수행한다.

  • 메타데이터 관리

    • 파일 시스템 이미지(파일 이름, 파일 크기, 파일생성시간, 파일접근권한 등)와 파일이 위치한 블록의 정보로 구성된다.
    • 네임노드는 실행될 때 Fsimage를 읽어 메모리에 보관한다.
    • Edits 파일을 읽어와서 변경내용을 반영한다.
    • 현재의 메모리 상태를 스냅 샷으로 생성하여 Fsimage 파일 생성한다.
    • 따라서 Fsimage와 Edits 파일의 크기가 크면 구동 시작시각이 오래 걸릴 수 있다.

      home/opt/hadoop/namenode/ #dfs.name.dir (data/dfs/name/)
      ├── current
      │ ├── VERSION
      │ ├── edits_0000000000000000001-0000000000000000007 
      │ ├── edits_0000000000000000008-0000000000000000015
      │ ├── edits_0000000000000000016-0000000000000000022
      │ ├── edits_0000000000000000023-0000000000000000029
      │ ├── edits_0000000000000000030-0000000000000000030
      │ ├── edits_0000000000000000031-0000000000000000031
      │ ├── edits_inprogress_0000000000000000032
      │ ├── fsimage_0000000000000000030 
      │ ├── fsimage_0000000000000000030.md5
      │ ├── fsimage_0000000000000000031
      │ ├── fsimage_0000000000000000031.md5
      │ └── seen_txid 
      └── in_use.lock
      
    • Edits 파일 : 파일 생성, 삭제에 대한 트랜잭션 로그(가장 최근의 Fsimage 이후에 작성된 로그로서 주기적으로 생성됨)

    • Fsimage 파일 : 네임스페이스와 블록 정보(특정 시점의 파일 시스템의 전체 상태를 포함)
    • seen_txid : 마지막 체크포이느의 마지막 트랜잭션 ID 또는 현재 edits_inprogress의 마지막
    • in_use.lock : 디렉토리 수정을 방지
  • 데이터 노드 관리

    • 데이터 노드가 네임노드에게 보내는 하트비트(데이터 노드의 상태 정보와 데이터 노드에 저장된 블록 목록인 블록리포트로 구성)를 이용하여 데이터 노드를 모니터링한다.
    • 일정 시간 동안 하트비트가 도착하지 않으면 네임노드는 데이터 노드가 동작하지 않는 것으로 간주하고 서버가 장애가 발생했다고 판단한다.
    • 하트비트 : default 3초, dfs.heartbeat.interval로 설정한다.
    • 블록리포트 : default 6시간, dfs.bloackreport.intervalMsec으로 설정한다.
  • 블록 관리

    • 장애가 발생한 데이터 노드의 블록을 새로운 데이터 노드로 복제한다.
    • 용량이 부족한 데이터 노드가 있다면 여유가 있는 다른 데이터 노드로 블록을 이동시킨다.
    • 블록의 기본 복제 수를 기준으로 블록을 복제하거나 삭제한다.
  • 클라이언트 요청 접수

    • 클라이언트가 HDFS에 접근하려면 반드시 네임노드에 먼저 접속해야 한다. HDFS에 파일을 저장하면 네임노드는 파일 존재 여부 및 권한 확인 절차를 거친다. HDFS의 파일을 조회할 경우에는 블록의 위치정보를 반환한다.

데이터 노드 #

데이터 노드는 파일을 블록 단위로 저장하는 역할을 한다. 아래 그림에서와같이 저장되는 파일은 'blk_임의정수'와 'blk_임의정수_증가하는 수.meta'으로 두 종류로 구성된다. 첫 번째 파일은 실제 데이터가 저장된 로우 데이터이고 두 번째 파일은 파일의 생성 일자와 같은 메타데이터가 설정된 파일이다. 데이터 노드는 주기적으로 네임노드에 하트비트와 블록리포트를 전달한다.

home/opt/hadoop/datanode/ #dfs.data.dir (data/dfs/data/)
├── current
│ ├── BP-1079595417-192.168.2.45-1412613236271 
│ │ ├── current
│ │ │ ├── VERSION
│ │ │ ├── finalized 
│ │ │ │ └── subdir0
│ │ │ │ └── subdir1
│ │ │ │ ├── blk_1073741825 
│ │ │ │ └── blk_1073741825_1001.meta 
│ │ │ │── lazyPersist 
│ │ │ └── rbw #복제본 작성
│ │ └── tmp
│ └── VERSION
└── in_use.lock
  • 데이터 노드
    • BP-1079595417-192.168.2.45-1412613236271 : 블록 풀 ID, 네임노드의 아이피와 생성시간으로 ID를 형성한다.
    • finalized : 블록 저장을 위한 디렉토리 구조를 포함한다. 로우데이터와 메타데이터를 포함한다.
    • blk_1073741825 : 로우 데이터
    • blk_1073741825_1001.meta : 메타데이터
    • lazyPersist : Hadoop 2.6 이상 부터 데이터노드의 오프 힙 메모리를 사용할 때 하위 디렉토리가 생성된다. 디스크 I/O 및 체크섬 계산을 없애 비동기적으로 쓰기기능을 수행한다.
    • dncp_block_verification.log.curr : 현재 각 블록이 마지막으로 확인 된 시간을 추적한다.
    • dncp_block_verification.log.prev : 이전 각 블록이 마지막으로 확인 된 시간을 추적한다.
    • in_use.lock : 디렉토리 수정을 방지

보조네임노드 #

앞서 네임노드는 구동할 때 메타데이터를 메모리에 적재한다고 언급한 바 있다. 만약 서버가 리부트된다면 메모리에 적재된 메타데이터는 사라질 수 있다. HDFS는 이러한 문제를 해결하기 위해 editlog와 fsimage라는 두 개의 파일을 생성한다. 문제는 editlog는 별도의 크기 제한이 없기 때문에 무한대로 커질 수 있다. 커질대로 커진 editlog에 기록된 변경이력을 메모리에 로딩된 fsimage에 적용하는 단계에 많은 시간을 소비할 수 있다. 보조네임노드는 주기적으로 fsimage를 갱신하는 역할을 하여 이러한 문제를 해결해 준다. 보조네임노드는 네임노드의 editlog를 롤링할 것을 요청하고(현재 로그는 새로운 이름으로 변경하고 원래 이름으로 새 로그파일을 만드는 것, 예시 : editlogs, editlogs.new) 롤링된 editlog와 fsimage를 다운로드하여 fsimage.ckpt를 생성한다. fsimage를 줄여주는 역할 통해 fsimage가 너무 커서 네임노드가 메모리에 로딩되지 못하는 경우를 예방한다.

HDFS 파일 저장 #

클라이언트가 네임노드에게 파일 저장을 요청하는 것으로 프로세스가 시작된다. 네임노드의 유효성 검사 이후 클라이언트가 데이터노드에게 패킷을 전송하고 마지막으로 파일 저장 후에 스트림을 닫으면 HDFS에 파일을 저장할 수 있다.

파일 저장 요청 #

1. FileSystem이라는 추상 클래스를 상속 받은 DistributedFileSystem의 create 메서드를 호출해 FSDataOutputStream 객체를 생성한다.
public class DistributedFileSystem extends FileSystem {
    ...
    public FSDataOutputStream create(Path f, FsPermission permission, boolean overwrite, int bufferSize, short replication, long blockSize, Progressable progress) throws IOException {
        return create(f, permission, overwrite, bufferSize, replication, blockSize, getConf().getInt("io.bytes.per.checksum", FSConstants.DEFAULT_BYTES_PER_CHECKSUM), progress);
    }
    ...
}
2. DistributedFileSystem은 DFSOutputStream을 생성하기 위해 DFSClient의 create 메서드를 호출한다.
3. DFSClient가 DFSOutputStream을 생성할 때 DFSOutputStream은 RPC 통신으로 네임노드의 create 메서드를 호출한다. 클라이언트의 요청이 유효한지 검사를 진행한다. 이미 존재하는 파일이거나 파일 시스템의 용량을 초과한다면 오류가 발생한다. 정상일 경우 fsimage에 해당 파일 엔트리를 추가하고 DFSClient에서 DFSOutputStream이 정상적으로 생성된다. DistributedFileSystem은 DFSOutputStream을 래핑한 FSDataOutputStream을 반환한다.
public class DistributedFileSystem extends FileSystem {
    ...
    DFSClient dfs;
    ...
    public void initialize(URI uri, Configuration conf) throws IOException {
        this.dfs = new DFSClient(namenode, null, conf, statistics, getUniqueId(), this);
    }
    ...
    public FSDataOutputStream create(Path f, FsPermission permission, boolean overwrite, int bufferSize, short replication, long blockSize, int bytesPerChecksum, Progressable progress) throws     IOException {
        return new FSDataOutputStream(dfs.create(getPathName(f), permission, overwrite, true, replication, blockSize, progress, bufferSize, bytesPerChecksum), statistics);
    }
    ...
}

패킷 전송 #

클라이언트가 네임노드에게서 파일 승인을 받으면 클라이언트는 각 데이터노드에 파일을 패킷 단위로 나누어 전송한다.

1. 스트림 객체의 write 메서드를 호출해 파일 저장을 시작한다. 파일은 64K 크기의 패킷으로 분할 된다.
2. DFSOutputStream은 dataQueue에 전송할 패킷을 등록하고 내장 클래스인 DataStreamer는 네임노드의 addBlock 메서드를 호출한다.
3. addBlock 메서드를 통해 네임노드는 DataStreamer에게 블록을 저장할 데이터노드 목록을 반환한다.
4. DataStreamer는 복제본 수 만큼 파이프라인을 형성하고 첫 번째 데이터노드부터 패킷 전송을 시작한다.
5. 패킷을 저장할 때 ackQueue에 패킷 전송이 완료됐다는 패킷을 등록하는데 모든 데이터노드로부터 응답을 받았을 때만 해당 패킷이 제거된다.
6. 패킷 전송을 실패할 경우 ackQueue에 있는 모든 패킷이 다시 dataQueue로 이동되어 장애가 발생한 데이터노드를 제거한 새로운 데이터노드 목록으로 다시 패킷 전송 작업을 시작한다.

파일 저장 완료 #

1. DistributedFileSystem의 close 메서드를 호출해 파일을 닫는다.
2. DFSOutputStream은 네임노드의 complete 메서드를 호출해 패킷이 잘 저장됐는지 확인한다.

HDFS 파일 읽기 #

파일 조회 요청 #

1. DistributedFileSystem의 open 메서드를 호출해 스트림 객체 생성을 요청한다.
2. FSDataInputStream은 DFSDataInputStream과 DFSInputStream을 차례대로 래핑한다.
3. DFSInputStream을 생성하기 위해 DFSClient의 open 메서드를 호출한다.
public class DistributedFileSystem extends FileSystem {
    ...
    public FSDataInputStream open(Path f, int bufferSize) throws IOException {
        return open(f, bufferSize, new ReadOptions());
    }
    public FSDataInputStream open(Path f, int bufferSize, ReadOptions options) throws IOException {
        return new DFSClient.DFSDataInputStream(dfs.open(getPathName(f), bufferSize, verifyChecksum, statistics, clearOsBuffer, options));
    }
    ...
}
4. DFSInputStream은 네임노드의 getBlockLocation 메서드를 호출하여 블록 위치 정보를 조회한다.

블록 조회 #

1. 클라이언트는 입력 스트림 객체의 read 메서드를 호출하여 스트림 조회를 요청한다.
2. DFSInputStream은 첫 번째 블록과 가장 가까운 데이터노드를 조회한 후 리더기를 생성하여 리더기의 read 메서드를 호출한다.
3. DFSInputStream은 파일을 모두 읽을 때까지 블록을 조회한다.
4. 네임노드는 DFSInputStream에게 클라이언트에게 가까운 순으로 정렬된 블록 위치 목록을 반환한다.

입력 스트림 닫기 #

1. DistributedFileSystem의 close 메서드를 호출해 파일을 닫는다.
2. DFSInputStream은 데이터노드와 연결돼 있는 커넥션을 종료한다.
0.0.1_20140628_0