IT_Server/etc.(related Server)

[펌] LINE 스토리지, 한달에 수십억 건의 데이터를 Redis와 HBase에 저장하다.

JJun ™ 2013. 7. 3. 07:05

 


 

 출처: http://charsyam.wordpress.com/2012/04/29/%EB%B0%9C-%EB%B2%88%EC%97%AD-line-%EC%8A%A4%ED%86%A0%EB%A6%AC%EC%A7%80-%ED%95%9C%EB%8B%AC%EC%97%90-%EC%88%98%EC%8B%AD%EC%96%B5-%EA%B1%B4%EC%9D%98-%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%A5%BC-redis%EC%99%80/


 

 

 

해당글은 Naver Japan의 기술 블로그에 있는 다음 글(http://tech.naver.jp/blog/?p=1420 ) 발번역 하였습니다.  

개인적으로 Line의 이런 구조에 대해서 미리 얘기를 들었긴 했지만, 이렇게 멋진 글로 나올줄은 몰랐네요.

그냥 처음부터 한글로도 적어주시지. 글 쓰신분이 일본분이시라, 영어로만 적으셨나 봅니다. T.T

그리고, Appendix 부분은 대부분 설정내용이라 번역을 하지않고 삭제하였습니다.

원본을 참고하시기 바랍니다.

 


 

 

 

안녕하세요. 저는 Shunsuke Nakamura(@sunsuk7tp) 입니다.

불과 반년 전에 저는 도쿄텍에서 컴퓨터 사이언스 석사학위를 따고 NHN Japan의 Line 서버 팀으로 입사하였습니다. 저의 목표는 분산 처리와 스토리지 시스템에 대해서 잘 파악하고, 차세대 아키텍처를 개발하는 것이었습니다.

 

Line 서버 팀에서, Line의 주소록, 그룹, 메시지를 저장하는 스토리지 시스템을 개발하고 관리하는 역할을

맡았습니다. 이제, LINE의 스토리지 스택을 간단히 소개할려고 합니다.

 

 

LINE Beginning with Redis [2011.6 ~]

최초에는 LINE의 메인 스토리지에 Redis를 적용했습니다.

LINE은 총 2011년에 백만명의 유저가 사용할 정도로 스케일 되어야 하고, 빠르게 메시지를 주고 받는 메신저를 목표로 했습니다. Redis는 인메모리 데이터 스토어로 그 작업에 적합했습니다.

 

Redis는 정기적으로 디스크에 스냅샷을 남기는 것도 가능했습니다. 그리고 sync/async 한 master/slave replication도 제공했습니다. 우리는 Redis가 인메모리 데이터 스토어기 때문에, 확장성과 이용성에 문제가 있을 수도 있지만, 최고의 선택이라고 생각했습니다.

 

그래서 클라이언트 쪽에서 샤딩하기로 하고,  3대의 노드로 구성된 하나의 Redis 클러스터를  LINE의 스토리지 시스템으로 사용하기 시작했습니다.(역자주: 클러스터가 하나면 뭐 -_- 샤딩이고 뭐시기고 없습니다만…)

 

서비스의 규모가 커지면서, 더 많은 노드가 필요하게 되고,  클라이언트 쪽 샤딩은 효과적인 서비스 확장을

방해했습니다.  원래 Redis는 서버에서의 샤딩을 지원하지 않기 때문에, 우리는 redis 클러스터를 관리할 수

있는 클러스 터 매니저를 개발했습니다. 우리의 샤드된 Redis 클러스터는 클러스터 매니저 데몬과

Zookeeper Quorum 서버로 관리됩니다.

 

클러스터 매니저는 다음과 같은 특성이 있습니다.

  • Zookeeper에 의해서 샤딩을 관리(Consistent Hashing이나 이와 비슷한 다른 알고리즘)
  • 마스터/슬레이브 간의 장애 발견과 자동/수동의 FailOver
  • 최소의 장애시간안에 확장되어야 한다.(10초 이내)

현재, 샤딩된 여러개의 Redis 클러스터는 수백대의 서버로 구성되어 있다.

샤딩된 Reids 클러스터와 관리 툴

 

 

 

 

Tolerance Unpredictable Scaling [2011.10 ~]

그러나, 2011년 10월 근처 부터 크게 상황이 바뀌면서 LINE은  세계 여러군데서 엄청난 성장을 경험하게

되었습니다. 그리고 운영비용 역시 증가했습니다. 증가하는 비용의 주된 원인은 Redis 클러스터의 용량

때문이었습니다. 예측 불가능한 정도로 확장이 커지면서, Redis 클러스터를 안정적으로 운영하는 것은

더욱 더 힘들어졌습니다.

 

인메모리 데이터 저장소의 특성상 다른 퍼시스턴스 스토리지보다 더 많은 서버가 필요하기 때문입니다.

(역자 주: 디스크를 여러 개 추가하면 하드 디스크 용량을 늘리기 싶지만, 메모리를 많이 설치하기는 힘듭니다. 몇백 GB정도도 설치할 수 있지만 이러면 비용이 장난 아닙니다.)

 

안정적으로 가용성을 가지기 위해서,  스냅샷과 전체에 대한 리플리케이션 기능이 적절히 메모리를 관리하기 위해서 필요합니다. Redis VM(Virtual Memory) 기능이 도움이 될 수도 있지만,  VM의 사용은 성능을 엄청나게 떨어뜨립니다. (역자주: VM을 쓰면 넘는 부분을 디스크에 저장하므로 결국 Swap 쓰는 것과 별반 차이가 없습니다. 이렇게 스왑을 쓸거면 차라리 MMAP을 사용해서 OS에게 적절히 맡기는 것이 최근에는 더 좋은 성능을 내는 방법입니다.)

 

이런 이유로,  우리는 종종 Scale out 해야할 시기를 잘못 판단하고,

성능이상의 과부하와 맞닥트리게 됩니다.

 

결국 더 큰 확장성과 높은 가용성을 가지는 시스템으로 이전하는 것이 큰 문제가 되었습니다.

결국, LINE의 목표는 수천만명에서 수억 명의 유저 규모의 스케일을 가지는 것으로 변경되었습니다.

 

이것은 어떻게 해당 문제와 씨름했는지를 보여줍니다.

 

 

Data Scalability

처음에 우리는 각 데이터베이스의 규모를 분석했습니다.

(n: # of Users)

(t: Lifetime of LINE System)

  • O(1)
    • 전송 큐의 메시지
    • 잡 큐의 비동기 작업
  • O(n)
    • 유저 프로필
    • 주소록 / 그룹
      • 해당데이터는  O(n^2) 로 증가한다. 그러나 유저간 연결된 수에 따라 제한된다.(= O (n * CONSTANT_SIZE))
  • O(n*t)
    • Inbox의 메시지들
    • 유저 프로필 / 그룹 / 주소록의 데이터 변경

 

LINE 스토리지에 저장되는 레코드 수는 기하급수적으로 증가했었습니다.

가까운 미래에는 한달에 수백억개의 레코드를 다루게 될것 같습니다.

 


 

 

Data Requirement

두번째로, 각각의 사용 시나리오에 따른 데이터 요구사항을 정리했습니다.

  • O(1)
    • 가용성
    • Workload: 빠른 읽기와 쓰기 작업
  • O(n)
    • 가용성, 확장성
    • Workload: 빠른 랜덤 읽기
  • O(n*t)
    • 확장성, 대용량 볼륨 (하루에 수억 개의 작은 로우가 생기지만 대부분은 자주 접근하는 데이터가 아님)
    • Workload: 빠른 연속적인 쓰기(append-only) 와 최근 데이터에 대한 빠른 읽기

 

 

Choosing Storage

각 스토리지에 대한 위의 요구 사항에 따라, 결국, 적합한 스토리지를 결정했습니다.  

기준중의 하나로 각각의 스토리지의 특성을 설정하고, 어떤 스토리작 LINE app의 워크로드에 가장

적합한가를 결정했습니다. 몇개의 후보군들에 대해서 YCSB 나 직접 벤치마크를 수행했습니다.

그 결과, 메시지 타임 라인처럼 급격하게 증가하는 패턴을 보이는 데이터를 저장하기 위해서 hbase를

메인 스토리지로 사용하기로 결정했습니다. hbase의 특성은 메시지 타임라인에 적합합니다.

메시지 타임라인은 최신 데이터에 집중되고, 최근에 삽입된 데이터가 상위가 배치됩니다.

  • O(1)
    • Redis가 최선의 선택입니다.
  • O(n), O(n*t)
    • 몇가지 대안이 있습니다.
    • HBase
      • 장점:
        • 요구사항에 가장 부합함
        • 관리가 쉬움 ( 분산 파일 시스템위에 스토리지가 구축되어 있고, 서버마다 데이터가 ad hoc 하게 파티션되어있음)
      • 단점:
        • 랜덤 읽기와 삭제가 느림.
        • 좀 낮은 가용성 (SPOF 도 약간 존재.)
    • Cassandra (내가 가장 좋아하는 NoSQL)
      • 장점:
        • 최근 데이터를 다루는데 적합함
        • 높은 가용성 (비중앙집중적 아키텍처(P2P), rack/DC 를 고려한 리플리케이션)
      • 단점:
        • 낮은 일관성으로 인한 운영 비용 증가
        • 카운터 증가가 좀 느림.
    • MongoDB
      • 장점:
        • Auto sharding, auto failover
        • 많은 명령어 (그러나 LINE 스토리지에는 이런게 필요없음)
      • 단점:
        • 타임라인의 워크로드에 부적합함 (B-tree indexing)
        • 디스크와 네트웍 사용이 비효과적임

요약하면, LINE 스토리지 레이어는 현재 다음과 같다.

  1. Standalone Redis: 비동기 작업과 메시지 큐잉용도
    • Redis 큐와 큐 디스패처는 같은 서버에서 함께 동작합니다.(역자 주: 아마도 이미지에서 처럼 이런 구조가 된 것은, 이 단계에서 실패하면 그냥 전달 실패가 되는 그 영역일꺼 같습니다. 그리고 큐에 일단 넣어놓고 비동기적으로 메인 저장소로 전달되지 않을까 하는 추측을 해봅니다.)
  2. 샤딩된 Redis: 데이터의 캐시를 위한 앞단으로 사용됨 O(n*t)  그리고 메인 저장소로 사용 O(n)
  3. 백업용 MySQL: 2차 스토리지 (백업과 통계용)
  4. HBase: 메인 스토리지 O(n*t)
    • 각 클러스터 마다 수백 테라 규모의 데이터가 핸들링 되고 각각의 클러스터는 100대 규모에서 1000대 규모로 구성됩니다.

LINE의 메인 스토리지는 600대 정도로 구축되어 있으며, 매달 계속 늘어나고 있습니다.


LINE Storage Stack

 

 

 

 

Data Migration from Redis to HBase

 

우리는 점진적으로 수십테라의 데이터를 Redis 클러스터로 부터 Hbase 클러스터로 옮겼습니다.

3단계로 데이터를 옮겼는데,

  1. Redis 와 HBase 에 모두 써서 Redis에서만 읽음( 역자 주: 이것은 대부분 대용량 데이터를 옮기거나, 이전할 때, 필수적으로 하는 방식입니다. 처음부터 바로 바꾸는 건 거의 불가능 하시다고 보시면 됩니다. 최초에는 데이터를 비교해서 동일한 상태가 계속 유지되면, 읽기를 Hbase로 옮기고, 같이 운영하다가 그것도 안정적이다 싶으면 Redis를 제거할 수 있을껍니다.)
  2. 백엔드에서 이전 스크립트 실행 (순차적으로  Redis로 부터 HBase로 데이터를 이전합니다.)
  3. Redis (w/ TTL) 와 HBase (w/o TTL) 에 모두 쓰고,  양쪽에서 모두 읽음 (Redis 가 캐시서버를 대체)

최근 데이터를 옛날 데이터가 덮어씌우는 것을 피하기 위해서 이전되는 데이터는 append-only로만 저장되고, 다른 데이터의 일관성은 Hbase column의 timestamp를 이용해서 유지합니다.

 

 

HBase and HDFS

많은 HBase 클러스터가 HDFS 위에서 잘 돌아가고 있습니다.

우리는 메시지, 주소록등의 각각의 데이터베이스를 위해서 HBase 클러스터를 만들었고, 각각의 클러스터는

각 데이터베이스의 워크로드에 따라서 튜닝되었습니다. 각 HBase클러스터는 100대의 서버로 구성된

하나의 HDFS 클러스터를  공유하고 있고,  각각의 서버는 32GB의 메모리와 1.5TB의 하드 디스크를

가지고 있습니다. (역자 주: 아마도 Redis로 있던걸 HBase로 가서 디스크를 그렇게 많이 사용하지 않는듯 합니다.)  

 

각 RegionServer는 10GB보다 큰 50개의 작은 Region을 가지고 있습니다.

Big-Table 형태의 아키텍처에서 Read 성능은 major compaction에 영향을 받습니다.

그래서 각각의 region의 크기를 너무 크지 않게 유지하는 것이, 특히 피크 타임에 더더욱, 지속적인 major compaction을 막을 수 있습니다.  피크 타임이 아닌동안에, 운영자들이 수동으로 로드밸런싱을 수행하는 동안에, 큰 region들을 주기적인 cron 잡을 통해서 자동적으로 작은 region으로 나누어줍니다.

 

물론, HBase는 자동으로 데이터를 나눠주는 기능과 로드 밸런싱 기능이 있습니다만, 서비스 요구사항면에서 수동으로 하는 것이 더 났다고 판단했습니다. 이런 서비스의 성장때문에, 확장성은 가장 중요한 이슈중의 하나입니다. 우리는 클러스터 마다 수백대의 서버가 있고, 각 메시지는 TTL 을 가지고 있으며, TTL 단위로 멀티 클러스터로 파티션 되어집니다.  그렇게 진행되는 동안에,  메시지의 TTL이 경과된 구 서버에서는 모든 데이터가 사라지고 새로운 클러스터로 사용되어집니다. (역자 주: 멀티 클러스터로 파티션 되니, 이렇게 특정 클러스터의 내용을 전부 지우더라도 문제가 되지 않을듯 합니다.)

 

 

Current and future challenges [2012]

HBase로 이동한 이래로, LINE 스토리지는 더더욱 안정적이 되었습니다.

아직도 스토리지 때문에 가끔씩 장애가 나긴 하지만, 각각의 HBase 클러스터는 현재 새해 피크 타임 정도의

리퀘스트를 여러 번 처리했습니다.  우리는 가용성을 위해 다음과 같은 HBase와 Redis 클러스터 이슈를

남겨두고 있습니다.

  • 안정적인 설정과 SPOF를 가지지 않고 랙/데이터센터에 적합한 FailOver
    • 우리는 Full Replication, HDFS 클러스터 간의 SSTable 이나 Block level Replication 등의 다양한 리플리케이션 레이어를 시험했습니다.
  • 클러스터 간 장애에 대한 대응 (Redis cluster, HBase, and multi-HBase cluster)

 

 

HA-NameNode

다들 알고 있듯이 HDFS에서는 NameNode가 SPOF 입니다. NameNode 프로세스가 좀처럼 죽지 않는다고

하지만,(Experience at Yahoo!) 디스크나 네트워크 장애로 인해서 발생하는 하다른 소프트웨어나 하드웨어 장애가 문제를 일으킬 수 있습니다. 즉, 높은 가용성을 확보하기 위해서는 NameNode Failover 방안이 필요합니다. 몇개의 HA-NameNode 설정이 있습니다.

  • HDFS NameNode를 위한 고 가용성 프레임워크(HDFS-1623)
  • Backup NameNode (0.21)
  • Avatar NameNode (Facebook)
  • HA NameNode using Linux HA
  • Active/passive 설정을 2대의 NameNode에 배포하는 방법 (cloudera)

우리는 Linux HA를 이용해서 NameNode의 HA를 설정하는 방법을 선택했고,

Linux HA를 위한 각각의 컴포넌트 들은 다음과 같습니다.

  • DRBD: Disk mirroring
  • Heartbeat / (Corosync): Network fail-detector
  • Pacemaker: Failover definition
  • service: NameNode, Secondary NameNode, VIP

HA NameNode using Linux HA

 

 

DRBD (Distributed Replicated Block Device) 는 block level replication 을 제공합니다.

특별히 네트웍으로 연결된 RAID 드라이브라고 보면됩니다.  

 

Heartbeat monitors가 서버 간의 네트웍 연결 상태를  모니터링 하고,

만약 Heartbeat가 하드웨어나 서비스의 이상을 감지하면, primary/secondary 를 바꿔줍니다.

 

그리고 각 서비스의 데몬은  pacemaker  에 정의되어 있습니다.

 

 

Conclusion

우리는 LINE의 성장에 따라 다양한 확장성과 가용성 문제를 경험했습니다.

앞으로 LINE 스토리지와 전략은 계속 변경될 것이고, 더 큰 확장과 다양한 장애를 경험하게 될 것입니다.  

LINE의 미래 서장과 함께 우리 역시 성장하기를 바랍니다.

 

더보기

Appendix: How to setup HA-NameNode using Linux-HA

In the rest of this entry, I will introduce how to build HA-NameNode using two CentOS 5.4 servers and Linux-HA. These servers are to assume the following environment.

  • Hosts:
    1. NAMENODE01: 192.168.32.1 (bonding)
    2. NAMENODE02: 192.168.32.2 (bonding)
  • OS: CentOS 5.4
  • DRBD (v8.0.16):
    • conf file: ${DEPLOY_HOME}/ha/drbd.conf
    • resource name: drbd01
    • mount disk: /dev/sda3
    • mount device: /dev/drbd0
    • mount directory: /data/namenode
  • Heartbeat (v3.0.3):
    • conf file: ${DEPLOY_HOME}/ha/haresources, authkeys
  • Pacemaker (v1.0.12)
  • service daemons
    • VIP: 192.168.32.3
    • Hadoop NameNode, SecondaryNameNode (v1.0.2, the latest edition now)

Configuration

Configure drbd and heartbeat settings in your deploy home direcoty, ${DEPLOY_HOME}.

  • drbd.conf
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
global { usage-count no; }
 
resource drbd01 {
  protocol  C;
  syncer { rate 100M; }
  startup { wfc-timeout 0; degr-wfc-timeout 120; }
 
  on NAMENODE01 {
    device /dev/drbd0;
    disk    /dev/sda3;
    address 192.168.32.1:7791;
    meta-disk   internal;
  }
  on NAMENODE02 {
    device /dev/drbd0;
    disk    /dev/sda3;
    address 192.168.32.2:7791;
    meta-disk   internal;
  }
}
  • ha.conf
1
2
3
4
5
6
7
8
9
10
debugfile ${HOME}/logs/ha/ha-debug
logfile ${HOME}/logs/ha/ha-log
logfacility local0
pacemaker on
keepalive 1
deadtime 5
initdead 60
udpport 694
auto_failback off
node    NAMENODE01 NAMENODE02
  • haresources (Can skip this step when using pacemaker)
1
2
3
# <primary hostname> <vip> <drbd> <local fs path> <running daemon name>
NAMENODE01 IPaddr::192.168.32.3 drbddisk::drbd0 Filesystem::/dev/drbd0::/data/namenode::ext3::defaults hadoop-1.0.2-namenode
{code}
  • authkeys
1
2
auth 1
1 sha1 hadoop-namenode-cluster

 

Installation of Linux-HA

Pacemaker and Heartbeat3.0 packages are not included in the default base and updates repositories in CetOS5. Before installation, you first need to add the Cluster Labs repo:

 

1
wget -O /etc/yum.repos.d/clusterlabs.repo http://clusterlabs.org/rpm/epel-5/clusterlabs.repo

Then run the following script:

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
yum install -y drbd kmod-drbd heartbeat pacemaker
 
# logs
mkdir -p ${HOME}/logs/ha
mkdir -p ${HOME}/data/pids/hadoop
 
# drbd
cd ${DRBD_HOME}
ln -sf ${DEPLOY_HOME}/drbd/drbd.conf drbd.conf
echo "/dev/drbd0 /data/namenode ext3 defaults,noauto 0 0" >> /etc/fstab
yes | drbdadm create-md drbd01
 
# heartbeat
cd ${HA_HOME}
ln -sf ${DEPLOY_HOME}/ha/ha.cf ha.cf
ln -sf ${DEPLOY_HOME}/ha/haresources haresources
cp ${DEPLOY_HOME}/ha/authkeys authkeys
chmod 600 authkeys
 
chown -R www.www ${HOME}/logs
chown -R www.www ${HOME}/data
chown -R www.www /data/namenode
 
chkconfig -add heartbeat
chkconfig hearbeat on

 

DRBD Initialization and Running heartbeat

  1. Run drbd service @ primary and secondary
  2. 1
    # service drbd start
  3. Initialize drbd and format NameNode@primary
  4. 1
    2
    3
    4
    5
    6
    # drbdadm -- --overwrite-data-of-peer primary drbd01
    # mkfs.ext3 /dev/drbd0
    # mount /dev/drbd0
    $ hadoop namenode -format
    # umount /dev/drbd0
    # service drbd stop
  5. Run heartbeat @ primary and secondary
  6. 1
    # service heartbeat start

 

Daemonize hadoop processes (Apache Hadoop)

When using Apache Hadoop, you need to daemonize each node such as NameNode, SecondaryNameNode in order for heartbeat process to kick them. The follow script, “hadoop-1.0.2-namenode” is an example for NameNode daemon.

  • /usr/lib/ocf/resource.d/nhnjp/hadoop-1.0.2-namenode
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
31
#!/bin/sh
 
BASENAME=$(basename $0)
HADOOP_RELEASE=$(echo $BASENAME | awk '{n = split($0, a, "-"); s=a[1]; s = a[1]; for(i = 2; i < n; ++i) s = s "-" a[i]; print s}')
SVNAME=$(echo $BASENAME | awk '{n = split($0, a, "-"); print a[n]}')
 
DAEMON_CMD=/usr/local/${HADOOP_RELEASE}/bin/hadoop-daemon.sh
[ -f $DAEMON_CMD ] || exit -1
 
RETVAL=0
case "$1" in
    start)
        start
        ;;
 
    stop)
        stop
        ;;
 
    restart)
        stop
        sleep 2
        start
        ;;
 
    *)
        echo "Usage: ${HADOOP_RELEASE}-${SVNAME} {start|stop|restart}"
        exit 1
    ;;
esac
exit $RETVAL

Second, place a script for pacemaker to kick this daemon services. There are pacemaker scripts under /usr/lib/ocf/resource.d/ .

  • /usr/lib/ocf/resource.d/nhnjp/Hadoop
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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
#!/bin/bash
#
# Resource script for Hadoop service
#
# Description:  Manages Hadoop service as an OCF resource in
#               an High Availability setup.
#
#
#   usage: $0 {start|stop|status|monitor|validate-all|meta-data}
#
#   The "start" arg starts Hadoop service.
#
#   The "stop" arg stops it.
#
# OCF parameters:
# OCF_RESKEY_hadoopversion
# OCF_RESKEY_hadoopsvname
#
# Note:This RA uses 'jps' command to identify Hadoop process
##########################################################################
# Initialization:
 
: ${OCF_FUNCTIONS_DIR=${OCF_ROOT}/lib/heartbeat}
. ${OCF_FUNCTIONS_DIR}/ocf-shellfuncs
 
USAGE="Usage: $0 {start|stop|status|monitor|validate-all|meta-data}";
 
##########################################################################
 
usage()
{
    echo $USAGE >&2
}
 
meta_data()
{
cat <<END
<?xml version="1.0"?>
<!DOCTYPE resource-agent SYSTEM "ra-api-1.dtd">
<resource-agent name="Hadoop">
<version>1.0</version>
<longdesc lang="en">
This script manages Hadoop service.
</longdesc>
<shortdesc lang="en">Manages an Hadoop service.</shortdesc>
 
<parameters>
 
<parameter name="hadoopversion">
<longdesc lang="en">
Hadoop version identifier: hadoop-[version]
For example, "1.0.2" or "0.20.2-cdh3u3"
</longdesc>
<shortdesc lang="en">hadoop version string</shortdesc>
<content type="string" default="1.0.2"/>
</parameter>
 
<parameter name="hadoopsvname">
<longdesc lang="en">
Hadoop service name.
One of namenode|secondarynamenode|datanode|jobtracker|tasktracker
</longdesc>
<shortdesc lang="en">hadoop service name</shortdesc>
<content type="string" default="none"/>
</parameter>
 
</parameters>
 
<actions>
<action name="start" timeout="20s"/>
<action name="stop" timeout="20s"/>
<action name="monitor" depth="0" timeout="10s" interval="5s" />
<action name="validate-all" timeout="5s"/>
<action name="meta-data"  timeout="5s"/>
</actions>
</resource-agent>
END
exit $OCF_SUCCESS
}
 
HADOOP_VERSION="hadoop-${OCF_RESKEY_hadoopversion}"
HADOOP_HOME="/usr/local/${HADOOP_VERSION}"
[ -f "${HADOOP_HOME}/conf/hadoop-env.sh" ] && . "${HADOOP_HOME}/conf/hadoop-env.sh"
 
HADOOP_SERVICE_NAME="${OCF_RESKEY_hadoopsvname}"
HADOOP_PID_FILE="${HADOOP_PID_DIR}/hadoop-www-${HADOOP_SERVICE_NAME}.pid"
 
trace()
{
    ocf_log $@
    timestamp=$(date "+%Y-%m-%d %H:%M:%S")
    echo "$timestamp ${HADOOP_VERSION}-${HADOOP_SERVICE_NAME} $@" >> /dev/null
}
 
Hadoop_status()
{
    trace "Hadoop_status()"
    if [ -n "${HADOOP_PID_FILE}" -a -f "${HADOOP_PID_FILE}" ]; then
        # Hadoop is probably running
        HADOOP_PID=`cat "${HADOOP_PID_FILE}"`
        if [ -n "$HADOOP_PID" ]; then
            if ps f -p $HADOOP_PID | grep -qwi "${HADOOP_SERVICE_NAME}" ; then
                trace info "Hadoop ${HADOOP_SERVICE_NAME} running"
                return $OCF_SUCCESS
            else
                trace info "Hadoop ${HADOOP_SERVICE_NAME} is not running but pid file exists"
                return $OCF_NOT_RUNNING
            fi
        else
            trace err "PID file empty!"
            return $OCF_ERR_GENERIC
        fi
    fi
 
    # Hadoop is not running
    trace info "Hadoop ${HADOOP_SERVICE_NAME} is not running"
    return $OCF_NOT_RUNNING
}
 
Hadoop_start()
{
    trace "Hadoop_start()"
    # if Hadoop is running return success
    Hadoop_status
    retVal=$?
    if [ $retVal -eq $OCF_SUCCESS ]; then
        exit $OCF_SUCCESS
    elif [ $retVal -ne $OCF_NOT_RUNNING ]; then
        trace err "Error. Unknown status."
        exit $OCF_ERR_GENERIC
    fi
 
    service ${HADOOP_VERSION}-${HADOOP_SERVICE_NAME} start
    if [ $? -ne 0 ]; then
        trace err "Error. Hadoop ${HADOOP_SERVICE_NAME} returned error $?."
        exit $OCF_ERR_GENERIC
    fi
 
    trace info "Started Hadoop ${HADOOP_SERVICE_NAME}."
    exit $OCF_SUCCESS
}
 
Hadoop_stop()
{
    trace "Hadoop_stop()"
    if Hadoop_status ; then
        HADOOP_PID=`cat "${HADOOP_PID_FILE}"`
        if [ -n "$HADOOP_PID" ] ; then
            kill $HADOOP_PID
            if [ $? -ne 0 ]; then
                kill -s KILL $HADOOP_PID
                if [ $? -ne 0 ]; then
                    trace err "Error. Could not stop Hadoop ${HADOOP_SERVICE_NAME}."
                    return $OCF_ERR_GENERIC
                fi
            fi
            rm -f "${HADOOP_PID_FILE}" 2>/dev/null
        fi
    fi
    trace info "Stopped Hadoop ${HADOOP_SERVICE_NAME}."
    exit $OCF_SUCCESS
}
 
Hadoop_monitor()
{
    trace "Hadoop_monitor()"
    Hadoop_status
}
 
Hadoop_validate_all()
{
    trace "Hadoop_validate_all()"
    if [ ! -n ${OCF_RESKEY_hadoopversion} ] || [ "${OCF_RESKEY_hadoopversion}" == "none" ]; then
        trace err "Invalid hadoop version: ${OCF_RESKEY_hadoopversion}"
        exit $OCF_ERR_ARGS
    fi
 
    if [ ! -n ${OCF_RESKEY_hadoopsvname} ] || [ "${OCF_RESKEY_hadoopsvname}" == "none" ]; then
        trace err "Invalid hadoop service name: ${OCF_RESKEY_hadoopsvname}"
        exit $OCF_ERR_ARGS
    fi
 
    HADOOP_INIT_SCRIPT=/etc/init.d/${HADOOP_VERSION}-${HADOOP_SERVICE_NAME}
    if [ ! -d "${HADOOP_HOME}" ] || [ ! -x ${HADOOP_INIT_SCRIPT} ]; then
        trace err "Cannot find ${HADOOP_VERSION}-${HADOOP_SERVICE_NAME}"
        exit $OCF_ERR_ARGS
    fi
 
    if [ ! -L ${HADOOP_HOME}/conf ] || [ ! -f "$(readlink ${HADOOP_HOME}/conf)/hadoop-env.sh" ]; then
        trace err "${HADOOP_VERSION} isn't configured yet"
        exit $OCF_ERR_ARGS
    fi
 
    # TODO: do more strict checking
 
    return $OCF_SUCCESS
}
 
if [ $# -ne 1 ]; then
    usage
    exit $OCF_ERR_ARGS
fi
 
case $1 in
    start)
        Hadoop_start
        ;;
 
    stop)
        Hadoop_stop
        ;;
 
    status)
        Hadoop_status
        ;;
 
    monitor)
        Hadoop_monitor
        ;;
 
    validate-all)
        Hadoop_validate_all
        ;;
 
    meta-data)
        meta_data
        ;;
 
    usage)
        usage
        exit $OCF_SUCCESS
        ;;
 
    *)
        usage
        exit $OCF_ERR_UNIMPLEMENTED
        ;;
esac

Pacemaker settings

First, using the crm_mon command, verify whether the heartbeat process is running.

1
2
3
4
5
6
7
8
9
10
# crm_mon
Last updated: Thu Mar 29 17:32:36 2012
Stack: Heartbeat
Current DC: NAMENODE01 (bc16bea6-bed0-4b22-be37-d1d9d4c4c213)-partition with quorum
Version: 1.0.12
2 Nodes configured, unknown expected votes
0 Resources configured.
============
 
Online: [ NAMENODE01 NAMENODE02 ]

 

After verifying the process is running, connect to pacemaker using the crm command and configure its resource settings. (This step is needed instead of haresource setting)

 

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
31
32
33
34
35
36
crm(live)# configure
INFO: building help index
crm(live)configure# show
node $id="bc16bea6-bed0-4b22-be37-d1d9d4c4c213" NAMENODE01
node $id="25884ee1-3ce4-40c1-bdc9-c2ddc9185771" NAMENODE02
property $id="cib-bootstrap-options" \
        dc-version="1.0.12" \
        cluster-infrastructure="Heartbeat"
 
# if this cluster is composed of two NameNode, the following setting is need.
crm(live)configure# property $id="cib-bootstrap-options" no-quorum-policy="ignore"
 
# vip setting
crm(live)configure# primitive ip_namenode ocf:heartbeat:IPaddr \
params ip="192.168.32.3"
 
# drbd setting
crm(live)configure# primitive drbd_namenode ocf:heartbeat:drbd \
        params drbd_resource="drbd01" \
        op start interval="0s" timeout="10s" on-fail="restart" \
        op stop interval="0s" timeout="60s" on-fail="block"
# drbd master/slave setting
crm(live)configure# ms ms_drbd_namenode drbd_namenode meta master-max="1" \
master-node-max="1" clone-max="2" clone-node-max="1" notify="true"
 
# fs mount setting
crm(live)configure# primitive fs_namenode ocf:heartbeat:Filesystem \
params device="/dev/drbd0" directory="/data/namenode" fstype="ext3"
 
# service daemon setting
primitive namenode ocf:nhnjp:Hadoop \
        params hadoopversion="1.0.2" hadoopsvname="namenode" \
        op monitor interval="5s" timeout="60s" on-fail="standby"
primitive secondarynamenode ocf:nhnjp:Hadoop \
        params hadoopversion="1.0.2" hadoopsvname="secondarynamenode" \
        op monitor interval="30s" timeout="60s" on-fail="restart"

Here, ocf:${GROUP}/${SERVICE} path corresponds with /usr/lib/ocf/resource.d/${GROUP}/${SERVICE}. So you should place your original service script there. Also lsb:${SERVICE} path corresponds with /etc/init.d/${SERVICE}.

Finnaly, you can confirm pacemaker’s settings using the show command.

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
crm(live)configure# show
node $id="bc16bea6-bed0-4b22-be37-d1d9d4c4c213" NAMENODE01
node $id="25884ee1-3ce4-40c1-bdc9-c2ddc9185771" NAMENODE02
primitive drbd_namenode ocf:heartbeat:drbd \
        params drbd_resource="drbd01" \
        op start interval="0s" timeout="10s" on-fail="restart" \
        op stop interval="0s" timeout="60s" on-fail="block"
primitive fs_namenode ocf:heartbeat:Filesystem \
        params device="/dev/drbd0" directory="/data/namenode" fstype="ext3"
primitive ip_namenode ocf:heartbeat:IPaddr \
        params ip="192.168.32.3"
primitive namenode ocf:nhnjp:Hadoop \
        params hadoopversion="1.0.2" hadoopsvname="namenode" \
        meta target-role="Started" \
        op monitor interval="5s" timeout="60s" on-fail="standby"
primitive secondarynamenode ocf:nhnjp:Hadoop \
        params hadoopversion="1.0.2" hadoopsvname="secondarynamenode" \
        meta target-role="Started" \
        op monitor interval="30s" timeout="60s" on-fail="restart"
group namenode-group fs_namenode ip_namenode namenode secondarynamenode
ms ms_drbd_namenode drbd_namenode \
        meta master-max="1" master-node-max="1" clone-max="2" \
        clone-node-max="1" notify="true" globally-unique="false"
colocation namenode-group_on_drbd inf: namenode-group ms_drbd_namenode:Master
order namenode_after_drbd inf: ms_drbd_namenode:promote namenode-group:start
property $id="cib-bootstrap-options" \
        dc-version="1.0.12" \
        cluster-infrastructure="Heartbeat" \
        no-quorum-policy="ignore" \
        stonith-enabled="false"

 

Once you’ve confirmed the configuration is correct, commit it using the commit command.

 

1
crm(live)configure# commit

 

Once you’ve run the commit command, heartbeat kicks each service following pacemaker’s rules.
You can monitor dead or alive using the crm_mon command.

 

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
$crm_mon -A
 
============
Last updated: Tue Apr 10 12:40:11 2012
Stack: Heartbeat
Current DC: NAMENODE01 (bc16bea6-bed0-4b22-be37-d1d9d4c4c213)-partition with quorum
Version: 1.0.12
2 Nodes configured, unknown expected votes
2 Resources configured.
============
 
Online: [ NAMENODE01 NAMENODE02 ]
 
 Master/Slave Set: ms_drbd_namenode
     Masters: [ NAMENODE01 ]
     Slaves: [ NAMENODE02 ]
 Resource Group: namenode-group
     fs_namenode        (ocf::heartbeat:Filesystem):    Started NAMENODE01
     ip_namenode        (ocf::heartbeat:IPaddr):        Started NAMENODE01
     namenode   (ocf::nhnjp:Hadoop):    Started NAMENODE01
     secondarynamenode  (ocf::nhnjp:Hadoop):    Started NAMENODE01
 
Node Attributes:
* Node NAMENODE01:
    + master-drbd_namenode:0            : 75
* Node NAMENODE02:
    + master-drbd_namenode:1            : 75

 

Finally, you should test the various failover tests.
For example, kill each service daemon and cause pseudo-network failures using iptables.  

 

 

 

Reference documents