SK텔레콤 Hadoop 3.1 트러블슈팅 사례공유

수많은 회사들이 인공지능 모형개발과 서비스를 만들기 위해 다양한 노력을 기울이고 있습니다. 바야흐로 데이터와 인공지능의 시대가 열렸는데요,

필자가 Popit 에 SK텔레콤, Hadoop DW 와 데이터 분석환경 구축사례를 기고 했던 것도 벌써 수 년이 흘렀고, 현재는 하루 수백명의 사내 구성원이 사용하는 분석환경이 되었습니다.

이 분석환경을 구성하는 기술 플랫폼들은 그동안 여러가지 신규 기술이 도입되고 변화되어 왔지만, 에코시스템의 가장 근간이 되는 하둡 플랫폼의 버전은 2.7 버전(HDP)을 꽤 오랫동안 유지해 왔습니다.

얼마 전, 오랫동안 업그레이드 하지 못했던 하둡의 버전을 2.7 에서 3.1로 올리는 작업을 진행했는데, 이후 운영과정에서 발생했던 문제와 그 해결과정을 공유해 보고자 합니다.

하둡 업그레이드 필요성

하둡 1 혹은 2 버전을 사용하고 계시다면, 하둡 3 로의 업그레이드를 고민하실 것 입니다. 하둡 3 의 주요 특성과 장점은 아래 글을 참고하세요.

업그레이드를 부르는 Hadoop 3.0 신규 기능 살펴보기

하둡 3 로의 업그레이드는 위 글에서와 같이 여러가지 장점이 있습니다.

필자의 경우에는, 늘어나는 다양한 데이터를 저장할 공간이 부족한 가운데, 두배의 저장공간 확보가 가능하다는 것이 가장 큰 메리트로 다가왔으며, 언젠가 해야할 숙원사업 처럼 생각하고 있던 것을 드디어 실행에 옮기게 되었습니다.

하둡 버전에 대한 고민과 업그레이드 전략

얼마전, 클라우데라와 호튼웍스가 합병되면서, 하둡 플랫폼의 버전과 라이센스에도 큰 변화가 있었습니다. 클라우데라의 CDH 와 HDP 를 통합한 CDP 가 탄생했으며, CDP 는 더이상 무료로 다운로드 받을 수 없게 되었죠. 변화된 라이센스 정책을 간단하게 요약하면 아래와 같습니다.

  • CDH 와 HDP 는 CDP 로 통합되고, CDH 와 HDP 는 2021년까지만 유지한다.
  • CDP 는 서크스크립션 계약을 한 경우에만 다운로드 가능하다.
  • 기존에 On-presmise 로 운영중이던 하둡 플랫폼(CDH, HDP)은 변경되는 라이센스 정책에 영향받지 않는다.

저희는 어차피 On-premise 로 서브스크립션 계약 없이 운영을 할 예정 이었기에, 기 사용 중이던 HDP 버전 중 유료화 버전으로 전환되기 바로 전 버전을 사용하기로 결정했습니다.

문제는 업그레이드가 2 에서 3 로 넘어가는 Major 버전 업그레이드 이기 때문에, "롤링 업그레이드를 할 것인가?" 에 대한 선택을 해야 했는데, 5G 장비 통계 등 새로운 데이터의 유입으로 장비의 증설이 필요한 상황을 고려하여, 롤링 업그레이드를 하지 않고 별도의 클러스터를 하나 더 구축하는 방향으로 가닥을 잡았습니다.

기 운영 중인 하둡 2 클러스터를 그대로 두고, 하둡 3 기반의 신규 하둡 클러스터를 구성한 후 유입되는 데이터를 양쪽으로 저장(약 2달)한 후 기존 클러스터를 바라보던 서비스를 신규 클러스터를 바라보도록 변경하는 방식입니다.

이 방식은 신규 클러스터를 구축해야 하는 비용이 발생하지만, 안정적으로 Job 이 수행되는지 등을 사전에 다 체크해보고, 안전하다고 판단되는 시점에 서비스를 전환할 수 있기 때문에 업그레이드 실패의 확률이 거의 없는 방식입니다. 신규 클러스터로 서비스를 넘긴 후에는 구 클러스터의 데이터노드를 점진적으로 신규 클러스터에 추가하여 확장을 진행합니다.

하둡 클러스터 현황

필자가 운영중인 하둡 클러스터에서는 하루에 2만 5천개에서 3만개의 YARN Application 이 구동되고 있으며, 전체 저장 공간의 70% 정도를 사용하고 있습니다. 수년간 운영을 해왔기 때문에 전체 클러스터 중 약 3개의 서로 다른 스펙을 가지고 있는 서버군 들이 있습니다. 서버 도입 시기에 따라 스펙이 달라진 사유에 기인하는데, 업그레이드를 위해 신규로 구성한 하둡 3 클러스터의 서버들도 기 운영중인 하둡 2 클러스터 서버들과는 스펙이 많이 다른 서버들 이었습니다. (이 점이 오늘 공유 드리고자 하는 트러블 슈팅 사례와 연관성이 있습니다.)

하둡을 On-premise 로 운영중이신 분들은 도입 시기에 따라 달라진, 서로 다른 스펙의 서버들을 하나의 하둡 클러스터로 운영하며 발생하는 여러가지 문제점들을 경험 하셨으리라 생각합니다. 특히나 디스크 크기가 다른 서버의 경우 아래와 같은 문제가 있을 수 있습니다.

예를들어, 가장 처음 도입한 클러스터의 서버 들에는 노드 당 4TB 디스크 6개가 있고, 이 경우 노드 당 총 디스크 크기는 24 TB 입니다. 두번째 도입한 클러스터의 서버 들에는 2TB 디스크 12개가 있으며, 이 경우 노드 하나 당 디스크 크기는 총 48TB (두배) 입니다.

이러한 경우, 전체 크기가 절반밖에 안되는 첫번째 서버군의 디스크는 저장할 공간이 없지만, 두번째 도입한 클러스터의 서버들에는 디스크 크기가 절반밖에 차지않는현상이 발생하게 됩니다.

필자는 이러한 경우를 방지하기 위해 Rack wareness 설정을 활용 했습니다. 예를들어, Rack 1번에는 24TB 노드 20 대를, Rack 2번에는 48TB 노드 10대를 설정하는 것 입니다. 이렇게 설정을 하면 전체 Rack 당 데이터를 저장하는 데이터의 밸런싱을 얼추 맞출 수 있습니다. 물론 물리적인 Rack awareness 설정의 장점은 포기합니다. (랙 전원 장애나 스위치 장애)

문제 발생 및 디버깅

앞서 설명했듯이, 신규 클러스터 구성 후 전환의 방식 이었기에, 업그레이드 자체를 실패할 확률은 없었습니다. 준비한대로 신규 하둡 클러스터를 3.1 (HDP) 버전으로 구성 후 2달 정도 데이터를 적재하고 기 수행중인 ETL Job 등도 사전에 테스트 하여 문제가 없다는 것을 확인했습니다. 서비스를 신규 클러스터로 저장하고 1주일 정도 사용자 Ad-hoc 쿼리등도 수용하면서도 전혀 문제 없음을 확인하였으며, 이제 하둡 2 기반으로 운영 중이던 구 하둡 클러스터의 데이터노드를 신규 클러스터에 붙이는 일만 남겨 놓고 있었습니다.

데이터노드를 신규 클러스터로 붙일 때, 한꺼번에 몇대의 노드를 붙일 것인가 정도의 고민이 있었지만, 그다지 심각한 고민은 아니라고 생각했으며, 한꺼번에 많은 노드를 붙여서 빨리 전체 클러스터 구성을 마치는 방향으로 진행을 했습니다.

구 클러스터의 데이터 노드들을 신규 클러스터로 붙이고, 수천만개의 Under replicated block 이 정상화 되기까지 꽤 시간이 걸렸지만 성공적으로 클러스터 구성이 마무리 되는 듯 싶었습니다. (예상은 하루 정도 걸릴것으로 생각했는데, 2틀 정도 시간이 소요 되었음)

그런데, 시간이 흐르면서 문제가 발생하기 시작했습니다. 증상은 HDFS 에 파일 쓰기가 간헐적으로 실패하는 것 이었습니다. Job 에 대한 실패를 모니터링 하기 때문에 실패한 Job 을 재실행하면 성공하기도 하고 또다시 실패하기도 했습니다. 원래 100% 재현이 잘 안되는 증상이 디버깅 하기 어려운 것이죠 ^^;

발생하는 에러 메시지는 아래와 같습니다. (HDFS 경로, 파일명, IP, 포트 등은 ??? 처리)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
2019-12-24 18:11:08,517 WARN  blockmanagement.BlockPlacementPolicy (BlockPlacementPolicyDefault.java:chooseTarget(432)) - Failed to place enough replicas, still in need of 2 to reach 3 (unavailableStorages=[], storagePolicy=BlockStoragePolicy{HOT:7, storageTypes=[DISK], creationFallbacks=[], replicationFallbacks=[ARCHIVE]}, newBlock=true) For more information, please enable DEBUG log level on org.apache.hadoop.hdfs.server.blockmanagement.BlockPlacementPolicy and org.apache.hadoop.net.NetworkTopology
2019-12-24 18:11:08,517 WARN  blockmanagement.BlockPlacementPolicy (BlockPlacementPolicyDefault.java:chooseTarget(432)) - Failed to place enough replicas, still in need of 2 to reach 3 (unavailableStorages=[], storagePolicy=BlockStoragePolicy{HOT:7, storageTypes=[DISK], creationFallbacks=[], replicationFallbacks=[ARCHIVE]}, newBlock=true) For more information, please enable DEBUG log level on org.apache.hadoop.hdfs.server.blockmanagement.BlockPlacementPolicy and org.apache.hadoop.net.NetworkTopology
2019-12-24 18:11:08,517 WARN  blockmanagement.BlockPlacementPolicy (BlockPlacementPolicyDefault.java:chooseTarget(432)) - Failed to place enough replicas, still in need of 9 to reach 9 (unavailableStorages=[], storagePolicy=BlockStoragePolicy{HOT:7, storageTypes=[DISK], creationFallbacks=[], replicationFallbacks=[ARCHIVE]}, newBlock=true) For more information, please enable DEBUG log level on org.apache.hadoop.hdfs.server.blockmanagement.BlockPlacementPolicy and org.apache.hadoop.net.NetworkTopology
2019-12-24 18:11:08,517 WARN  blockmanagement.BlockPlacementPolicy (BlockPlacementPolicyDefault.java:chooseTarget(432)) - Failed to place enough replicas, still in need of 9 to reach 9 (unavailableStorages=[], storagePolicy=BlockStoragePolicy{HOT:7, storageTypes=[DISK], creationFallbacks=[], replicationFallbacks=[ARCHIVE]}, newBlock=true) For more information, please enable DEBUG log level on org.apache.hadoop.hdfs.server.blockmanagement.BlockPlacementPolicy and org.apache.hadoop.net.NetworkTopology
2019-12-24 18:11:08,517 WARN  hdfs.StateChange (FSNamesystem.java:internalReleaseLease(3390)) - DIR* NameSystem.internalReleaseLease: Failed to release lease for file /???/dt=20191223/hh=21/PI004010007_RAN-EMS0024_201912232100.csv. Committed blocks are waiting to be minimally replicated. Try again later.
...
2019-12-24 18:11:08,518 INFO  ipc.Server (Server.java:logException(2726)) - IPC Server handler 29 on ????, call Call#109824 Retry#0 org.apache.hadoop.hdfs.protocol.ClientProtocol.addBlock from ???.???.???.???:???
...
java.io.IOException: File /???/dt=20191224/hh=17/???_201912241700.csv could only be written to 0 of the 6 required nodes for RS-6-3-1024k. There are ??? datanode(s) running and no node(s) are excluded in this operation.
        at org.apache.hadoop.hdfs.server.blockmanagement.BlockManager.chooseTarget4NewBlock(BlockManager.java:2128)
        at org.apache.hadoop.hdfs.server.namenode.FSDirWriteFileOp.chooseTargetForNewBlock(FSDirWriteFileOp.java:286)
        at org.apache.hadoop.hdfs.server.namenode.FSNamesystem.getAdditionalBlock(FSNamesystem.java:2706)
        at org.apache.hadoop.hdfs.server.namenode.NameNodeRpcServer.addBlock(NameNodeRpcServer.java:875)
        at org.apache.hadoop.hdfs.protocolPB.ClientNamenodeProtocolServerSideTranslatorPB.addBlock(ClientNamenodeProtocolServerSideTranslatorPB.java:561)
        at org.apache.hadoop.hdfs.protocol.proto.ClientNamenodeProtocolProtos$ClientNamenodeProtocol$2.callBlockingMethod(ClientNamenodeProtocolProtos.java)
        at org.apache.hadoop.ipc.ProtobufRpcEngine$Server$ProtoBufRpcInvoker.call(ProtobufRpcEngine.java:524)
        at org.apache.hadoop.ipc.RPC$Server.call(RPC.java:1025)
        at org.apache.hadoop.ipc.Server$RpcCall.run(Server.java:876)
        at org.apache.hadoop.ipc.Server$RpcCall.run(Server.java:822)
        at java.security.AccessController.doPrivileged(Native Method)
        at javax.security.auth.Subject.doAs(Subject.java:422)
        at org.apache.hadoop.security.UserGroupInformation.doAs(UserGroupInformation.java:1730)
        at org.apache.hadoop.ipc.Server$Handler.run(Server.java:2682)
2019-12-24 18:11:08,518 WARN  namenode.LeaseManager (LeaseManager.java:checkLeases(589)) - Cannot release the path /???/dt=20191223/hh=21/RAN-EMS???_201912232100.csv in the lease [Lease.  Holder: DFSClient_NONMAPREDUCE_-1718731533_1, pending creates: 475]. It will be retried.
org.apache.hadoop.hdfs.protocol.AlreadyBeingCreatedException: DIR* NameSystem.internalReleaseLease: Failed to release lease for file /???/dt=20191223/hh=21/RAN-EMS???_201912232100.csv. Committed blocks are waiting to be minimally replicated. Try again later.
        at org.apache.hadoop.hdfs.server.namenode.FSNamesystem.internalReleaseLease(FSNamesystem.java:3391)
        at org.apache.hadoop.hdfs.server.namenode.LeaseManager.checkLeases(LeaseManager.java:586)
        at org.apache.hadoop.hdfs.server.namenode.LeaseManager$Monitor.run(LeaseManager.java:524)
        at java.lang.Thread.run(Thread.java:748)
...
1
2
3
4
5
...
WARN blockmanagement.BlockPlacementPolicy (BlockPlacementPolicyDefault.java:chooseTarget()) - 
Failed to place enough replicas, still in need of 3 to reach 3 (...) For more information, 
please enable DEBUG log level on org.apache.hadoop.hdfs.server.blockmanagement.BlockPlacementPolicy and org.apache.hadoop.net.NetworkTopology
...

에러 메시지의 주요 내용은, "replica 를 쓸만한 저장소가 충분하지 못해서 쓰기를 실패했다. (RS-6-3-1024k 라서 6개를 써야하는데 0개 썼다)" 였고, "BlockPlacementPolicy 를 DEBUG 레벨로 로깅 설정 하는 것을 제안한다." 였습니다.

시키는 대로 아래 명령어를 통해, 로깅 레벨을 설정 했습니다. (라이브 상태에서 설정 가능)

1
hadoop daemonlog -setlevel <namenode>:<port> org.apache.hadoop.hdfs.server.blockmanagement.BlockPlacementPolicy DEBUG

이후, 네임노드의 로그를 보면 아래와 같은 로그들을 발견하게 됩니다.

1
2
3
4
5
...
Datanode <ip1>:<port> is not chosen since the node is too busy (load: node_load > load_threshold)
Datanode <ip2>:<port> is not chosen since the node is too busy (load: node_load > load_threshold)
Datanode <ip3>:<port> is not chosen since the node is too busy (load: node_load > load_threshold)
...

여러대의 데이터노드 들에서 "너무 바쁜 관계로 선택되지 못했다." 라는 메시지 입니다. 여기서 바쁘다는 기준은 해당 데이터노드의 node_load 가 load_threshold 보다 크다 인데, 이 부분의 의미를 정확히 이해하기 위해서 코드를 살펴보면 아래와 같습니다.

1
2
3
4
5
6
7
8
9
10
11
12
// check the communication traffic of the target machine
if (considerLoad) {
  // 필자주 : considerLoadFactor 의 Default 값이 2 입니다.
  final double maxLoad = considerLoadFactor *
      stats.getInServiceXceiverAverage(); 
  final int nodeLoad = node.getXceiverCount();
  if (nodeLoad > maxLoad) {
    logNodeIsNotChosen(node, NodeNotChosenReason.NODE_TOO_BUSY,
        "(load: " + nodeLoad + " > " + maxLoad + ")");
    return false;
  }
}

데이터노드의 noadLoad 가 maxLoad 보다 큰 경우, 위 에러로그를 출력하는데, maxLoad 는 getInServiceXceiverAverage() 함수의 리턴값에 2(기본값) 를 곱하여 얻고, noadLoad 의 값은 해당 데이터 노드의 Xceiver 갯수를 가져와서 판단합니다.

DataNodeManager.java 의 getInServiceXceiverAverage() 함수의 코드를 살펴보면 아래와 같습니다.

1
2
3
4
5
6
7
8
9
public double getInServiceXceiverAverage() {
    double avgLoad = 0;
    final int nodes = getNumDatanodesInService();
    if (nodes != 0) {
        final int xceivers = heartbeatManager.getInServiceXceiverCount();
        avgLoad = (double)xceivers/nodes;
    }
    return avgLoad;
}

코드를 보면 avgLoad 값은 네임노드에게 heartbeat 을 보내는 전체 Xceiver 의 갯수를 서비스 중인 전체 데이터노드의 수로 나눈 값으로, 전체 노드의 Xceiver 의 평균 갯수라는 것을 알 수 있습니다.

Xceiver 라는 놈은 하둡이 데이터를 송/수신할 때 띄우는 스레드 입니다. 특정 데이터노드에 Xceiver 갯수가 많이 떠 있다는 것은 해당 노드가 블록 읽기와 쓰기가 활발히 진행되고 있다는 것을 의미하며, 위 코드에서는 전체 노드에 평균적으로 떠있는 것보다 해당 데이터노드가 2배 이상의 Xceiver 갯수가 떠 있는 경우에 "Load 가 심해서 바쁘다" 라고 판단하고 있는 것 입니다.

잘못된 계산이 되는 예제

위와 같은 부하 판단 코드는 하나의 클러스터에 서로 다른 디스크 유형을 운영하기 위해 존재합니다.

AchivalStorage 문서보기

더 쉬운 이해를 위해, 잘못된 계산이 발생하는 예를 살펴보겠습니다.

클러스터에 100개의 노드가 있다고 가정하고, 90개의 노드는 DISK 스토리지 타입, 10개의 노드는 ARCHIVE 스토리지 타입이 설정되어 있습니다.

모든 노드의 평균로드는 10 입니다. (Xceiver 의 갯수가 평균 10개 라는 의미)

이 상황에서 ARCHIVE 스토리지 타입 노드들의 사용량이 높아져 Xceiver 의 갯수가 평균 23개로 늘어났다고 가정하면 계산식은 아래와 같이 됩니다.

1
(90*10 + 10*23)/100 = 11.3 (average xceivers count)

전체 서버의 평균 로드는 11.3 이고, ARCHIVE 스토리지 타입의 평균 로드는 23 이므로, 11.3 의 두배인 22.6보드 크게 되므로,  ARCHIVE 타입으로 설정된 노드에는 데이터 블록 쓰기를 할 수 없게 됩니다.

문제 해결

이 현상은 아래 링크에 하둡 버그로 이슈 등록 되어 있으며, 이 글을 작성하는 시점 기준으로 Unresolved 상태 입니다.

https://issues.apache.org/jira/browse/HDFS-14383

스크린샷 2020-01-13 오전 9.32.25

일단 데이터노드의 Load 상태를 BUSY 로 판단하지 않게 만들기 위하여, dfs.namenode.redundancy.considerLoad 의 설정값을 false 로 세팅하고, NameNode 를 재기동 혹은 Active NameNode 를 절체 시키게 되면, 더 이상 DataNode 의 부하상태를 판단하여 Block 쓰기 대상에서 제외하는 판단을 하지 않게 됩니다.

해결책 : dfs.namenode.redundancy.considerLoad=false;

문제발생에 대한 고찰

필자의  경우에는, 일단 부하를 판단하는 부분의 설정을 false 로 하여 문제가 발생하지 않게 만들긴 하였으나, Xceiver 의 갯수가 전체 평균보다 2배 이상인 노드가 매우 많다는 것을 알 수 있습니다.

또한, 전체 클러스터의 스토리지 타입은 전부 DISK 타입이기 때문에, 문제가 발생하는 이유를 고찰해 보면 아래와 같습니다.

하나의 클러스터에 서로 다른 스펙의 서버들이 존재함으로 인해 Rack awareness 설정으로 Rack 단위의 디스크 총량의 균형을 맞췄습니다. 하지만, 하나의 데이터노드 관점에서 보면 특정 노드에 더 많은 Block 을 저장하게 되고, Xceiver 의 경우에는 HDFS 에 read/write 가 발생하는 모든 job 에 대해 스레드로 포크되기 때문에, 많은 Block 을 가진 데이터노드에 많은 갯수의 Xceiver 가 뜨게될 수 밖에 없습니다.

또한 저희의 경우에는 하둡 3.1 업그레이드 이후 Erasure Coding 설정을 통해, 총 6개의 Rack 에 Block 을 하나씩 쓰도록 구성되어 있습니다. (6개 중 0개를 썼다는 에러 메시지)

1
could only be written to 0 of the 6 required nodes for RS-6-3-1024k. There are ??? datanode(s) running and no node(s) are excluded in this operation.

결론적으로, 저희와 같이 데이터노드 간 디스크 크기가 2배 가까이 차이가 있는 경우, 저장소의 유형이 전체 DISK 타입이라 할지라도, 위의 부하 판단 코드에 의해 많은 수의 데이터 노드가 부하상태로 판단될 수 있습니다.

문제 해결은 했지만, 향후 더 안정적인 운영을 위해 Xceiver 의 갯수는 별도로 모니터링을 하여, 하나의 데이터 노드에 포크되는 Xceiver 의 수는 적정한 수준인지, 혹은 분산시킬 수 있는 방법이 없는지를 고민해야 할 것 입니다.

마치며...

문제발생 후  해결책을 찾기 위해 구글링을 하는 과정에서, 김형준님께서 저희의 경우와 99% 일치하는 링크를 보내주셨습니다. (문제해결에 결정적 도움이 됨)

이상한 점은, 글을 작성하는 현재 시점에 해당 링크의 내용이 Page Not Found 인 것 인데, 좋은 내용의 기술 컨텐츠 내용을 왜 갑자기 내렸을까...하는 의문이 남습니다. (사이트 접속은 잘 되는데 컨텐츠만 보이지 않음)

시대의 흐름 처럼 기술 플랫폼도 스스로 판단(부하상태 등)을 하는 코드들이 들어가고 있습니다. 저희의 경우에는 이러한 부분이 역효과로 다가왔는데, 우리가 추구해 나아가는 인공지능도 이러한 내용의 역효과가  없을지 찬찬히 들여봐야 하지 않을까 싶습니다.

긴 글 읽어주셔서 감사합니다.


Popit은 페이스북 댓글만 사용하고 있습니다. 페이스북 로그인 후 글을 보시면 댓글이 나타납니다.