Region & 용량 관리
Region & 용량 관리 (Region & Capacity Management)
HBase 클러스터의 region 수와 크기를 어떻게 정하고 용량을 계획하는지, 그리고 RegionServer 그룹핑(rsgroup)과 Region Normalizer 같은 고급 기능을 다루는 페이지예요. region 관리를 제대로 해야만 성능이 안정적이고 원활하게 돌아가요.
출처: 문서
본문
Region 관리 (Region Management)
Major Compaction
Major compactions은 HBase 셸이나 Admin.majorCompact를 통해 요청할 수 있어요.
참고: major compaction은 region 병합(merge)을 수행하지 않아요. 컴팩션에 대한 자세한 내용은 compaction을 참고하세요.
Merge
Merge는 같은 테이블의 인접한 region들을 병합하는 유틸리티예요(org.apache.hadoop.hbase.util.Merge 참고).
$ bin/hbase org.apache.hadoop.hbase.util.Merge <tablename> <region1> <region2>
region이 너무 많다고 느껴서 정리하고 싶다면 Merge가 바로 필요한 유틸리티예요. Merge는 클러스터가 다운된 상태에서 실행해야 해요. 사용 예는 O'Reilly HBase Book에서 볼 수 있어요.
이 애플리케이션에는 3개의 파라미터를 전달해야 해요. 첫 번째는 테이블 이름이에요. 두 번째는 병합할 첫 번째 region의 완전한 이름으로, "table_name,\x0A,1342956111995.7cef47f192318ba7ccc75b1bbf27a82b." 같은 형식이에요. 세 번째는 병합할 두 번째 region의 완전한 이름이에요.
추가로 region 병합을 위한 Ruby 스크립트가 HBASE-1621에 첨부되어 있어요.
용량 계획과 Region 크기 결정 (Capacity Planning and Region Sizing)
HBase 클러스터의 용량을 계획하고 초기 설정을 수행할 때 고려해야 할 사항이 몇 가지 있어요. HBase가 내부적으로 데이터를 어떻게 처리하는지에 대한 확실한 이해부터 시작해야 해요.
노드 수와 하드웨어/VM 구성 (Node count and hardware/VM configuration)
물리적 데이터 크기 (Physical data size)
디스크의 물리적 데이터 크기는 데이터의 논리적 크기와는 별개이며 다음 항목의 영향을 받아요:
- HBase 오버헤드로 증가
- keyvalue와 keysize 참고. 키-값(셀)당 최소 24바이트이며 더 커질 수 있어요. 키/값이 작을수록 상대적 오버헤드가 커져요.
- KeyValue 인스턴스는 인덱스가 있는 블록으로 집계돼요. 인덱스도 저장해야 해요. 블록 크기는 ColumnFamily 단위로 설정 가능해요. regions.arch 참고.
- 데이터에 따라 압축과 데이터 블록 인코딩으로 감소. 어떤 압축과 인코딩(있다면)이 여러분 데이터에 맞는지 테스트해 볼 필요가 있어요.
- region server wal의 크기로 증가(보통 고정적이고 무시할 만하며, RS당 RS 메모리 크기의 절반 미만)
- HDFS 복제로 증가 - 보통 x3.
데이터 저장에 필요한 디스크 공간 외에도, region 수와 크기에 대한 몇 가지 실질적 한계 때문에 하나의 RS가 임의로 큰 데이터 양을 서비스하지 못할 수 있어요(아래 region 수와 크기 결정 참고).
읽기/쓰기 처리량 (Read/Write throughput)
노드 수는 읽기/쓰기에 필요한 처리량에 의해서도 결정될 수 있어요. 노드당 얻을 수 있는 처리량은 데이터(특히 키/값 크기)와 요청 패턴, 그리고 노드·시스템 구성에 크게 의존해요. 로드가 노드 수 증가의 주요 동인일 가능성이 있다면 최대 부하(peak load)에 맞춰 계획을 세워야 해요. PerformanceEvaluation과 ycsb 도구로 단일 노드나 테스트 클러스터를 테스트할 수 있어요.
쓰기의 경우, 모든 region server에는 활성 WAL이 하나뿐이므로 보통 RS당 5-15Mb/s를 기대할 수 있어요. 읽기는 데이터·요청·캐시 히트율에 크게 의존하므로 좋은 추정치가 없어요. perf.casestudy가 도움이 될 수 있어요.
JVM GC 한계 (JVM GC limitations)
RS는 GC 비용 때문에 현재 매우 큰 힙을 활용하지 못해요. 또한 서버당 여러 RS를 실행할 좋은 방법도 없어요(머신당 여러 VM을 실행하는 것 외에는). 따라서 RS 하나에 약 20-24Gb 이하의 메모리를 전용하는 것이 권장돼요. 큰 힙 크기에는 GC 튜닝이 필요해요. gcpause, trouble.log.gc 및 기타 문서를 참고하세요(TODO: 어디?).
region 수와 크기 결정 (Determining region count and size)
일반적으로 region이 적을수록 클러스터가 더 원활하게 돌아가요(나중에 필요한 경우 큰 region을 수동으로 분할해서 데이터나 요청 부하를 클러스터에 분산시킬 수 있으니까요). RS당 20-200개 region이 합리적인 범위예요. region 수는 직접 설정할 수 없어요(완전히 disable.splitting을 하지 않는 한) 그래서 테이블 크기가 주어졌을 때 목표 region 크기를 달성하도록 region 크기를 조정해요.
여러 테이블의 region을 구성할 때, 대부분의 region 설정은 TableDescriptorBuilder나 셸 명령으로 테이블별로 설정할 수 있다는 점에 주목하세요. 이 설정은 hbase-site.xml의 것을 덮어써요. 테이블마다 워크로드/사용 사례가 다르다면 유용하죠.
또한 여기서 말하는 region 크기 논의에서는 HDFS 복제 계수는 (그리고 그래야만 하는 것도) 고려하지 않으며, 다른 요소 ops.capacity.nodes.datasize는 고려해야 해요. 즉 데이터가 압축되어 HDFS로 3중 복제된다면 "9 Gb region"은 9 Gb의 압축된 데이터를 의미해요. HDFS 복제 계수는 디스크 사용량에만 영향을 주고 대부분의 HBase 코드에는 보이지 않아요.
현재 region 수 보기 (Viewing the Current Number of Regions)
주어진 테이블의 현재 region 수는 HMaster UI에서 볼 수 있어요. Tables 섹션에서 각 테이블의 온라인 region 수가 Online Regions 열에 나열돼요. 이 합계는 인메모리 상태만 포함하며 비활성화되거나 오프라인인 region은 포함하지 않아요.
RS당 region 수 - 상한 (Number of regions per RS - upper bound)
데이터가 많은 프로덕션 환경에서는 보통 서버당 가질 수 있는 최대 region 수에 관심을 가지게 돼요. too many regions에서 이 주제에 대한 기술적 논의가 있어요. 기본적으로 최대 region 수는 대부분 memstore 메모리 사용량에 의해 결정돼요. 각 region은 자체 memstore를 가지며 설정 가능한 크기(보통 128-256 MB 범위)까지 자라요. hbase.hregion.memstore.flush.size를 참고하세요. memstore는 컬럼 패밀리마다 하나씩 존재해요(테이블에 CF가 하나면 region에도 하나만 있어요). RS는 총 메모리의 일부를 memstore에 할당해요(hbase.regionserver.global.memstore.size 참고). 이 메모리를 초과하면(memstore 사용량이 너무 많아지면) 응답 없는 서버나 컴팩션 스톰 같은 바람직하지 않은 결과가 생길 수 있어요. RS당 region 수의 좋은 시작점은(한 테이블을 가정할 때):
((RS memory) * (total memstore fraction)) / ((memstore size)*(# column families))
이 공식은 의사 코드예요. 실제 튜닝 파라미터를 사용하는 두 가지 공식이 있는데, 첫 번째는 HBase 0.98+용이고 두 번째는 HBase 0.94.x용이에요.
HBase 0.98.x
((RS Xmx) * hbase.regionserver.global.memstore.size) / (hbase.hregion.memstore.flush.size * (# column families))
HBase 0.94.x
((RS Xmx) * hbase.regionserver.global.memstore.upperLimit) / (hbase.hregion.memstore.flush.size * (# column families))+
주어진 RegionServer가 16 GB RAM을 가지고 기본 설정이라면, 공식상 16384*0.4/128 ~ 즉 RS당 약 51개 region이 시작점이 돼요. 이 공식은 여러 테이블로 확장할 수 있어요. 모두 같은 설정이라면 총 패밀리 수를 사용하면 돼요.
이 숫자는 조정될 수 있어요. 위 공식은 모든 region이 거의 같은 비율로 채워진다고 가정해요. region 중 일부만 활발히 쓰인다면 그 비율로 나눠서 더 많은 region 수를 얻을 수 있어요. 그러면 모든 region에 쓰여도 모든 region memstore가 균등하게 채워지지 않고, 균등하게 채워져도(제한된 동시 flush 수 때문에) 결국 지터(jitter)가 나타나요. 따라서 시작점보다 2-3배 많은 region도 가질 수 있지만, 숫자가 늘어날수록 위험도 커져요.
쓰기 집약 워크로드에서는 블록 캐시를 희생해서 memstore 비율을 설정에서 늘릴 수 있어요. 이렇게 하면 더 많은 region을 가질 수 있어요.
RS당 region 수 - 하한 (Number of regions per RS - lower bound)
HBase는 region을 많은 서버에 분산시켜 확장돼요. 따라서 16GB 데이터에 2개 region만 있고 20노드 머신이면 데이터가 몇 개 머신에 집중될 거예요 — 거의 전체 클러스터가 유휴 상태이죠. 이 점은 아무리 강조해도 지나치지 않아요. 200MB 데이터를 HBase에 로드해놓고 왜 멋진 10노드 클러스터가 아무것도 안 하는지 궁금해하는 건 아주 흔한 문제거든요.
반면 데이터가 매우 많다면 너무 큰 region을 피하기 위해 더 많은 수의 region을 택하는 것도 좋아요.
최대 region 크기 (Maximum region size)
프로덕션 환경의 큰 테이블에서 최대 region 크기는 대부분 컴팩션에 의해 제한돼요. 특히 major 컴팩션이 아주 크면 클러스터 성능이 저하될 수 있죠. 현재 권장 최대 region 크기는 10-20Gb이고, 5-10Gb가 최적이에요. 오래된 0.90.x 코드베이스에서는 region 크기 상한이 약 4Gb이고 기본값이 256Mb였어요.
region이 둘로 분할되는 크기는 일반적으로 hbase.hregion.max.filesize로 설정해요. 자세한 내용은 arch.region.splits를 참고하세요.
테이블 크기를 잘 추정할 수 없다면, 시작할 때는 기본 region 크기를 유지하는 게 아마 최선이에요. 핫 테이블은 더 작게 하거나(핫 region을 수동 분할해서 부하를 분산) 셀 크기가 큰 편(100k 이상)이라면 더 큰 region 크기를 택하세요.
HBase 0.98에서는 특히 로그 데이터를 위해 더 큰 region을 허용하는 실험적 stripe 컴팩션 기능이 추가됐어요. ops.stripe 참고.
region server당 총 데이터 크기 (Total data size per region server)
위의 region 크기와 region server당 region 수에 따르면, 낙관적 추정으로 10 GB x 100 region/RS로 region server당 최대 1TB를 서비스할 수 있어요. 이것은 보고된 일부 멀티-PB 사용 사례와 일치해요. 하지만 RS 수준에서 데이터 대 캐시 크기 비율에 대해 생각하는 것이 중요해요. 서버당 1TB 데이터에 10GB 블록 캐시라면 데이터의 1%만 캐시되어 모든 블록 인덱스를 겨우 덮을 수 있을 뿐이에요.
초기 구성과 튜닝 (Initial configuration and tuning)
먼저 important configurations를 참고하세요. 일부 설정은 다른 것보다 특정 시나리오에 더 의존적이에요. 특히 다음에 주의하세요:
- hbase.regionserver.handler.count - 요청 핸들러 스레드 수로, 고처리량 워크로드에 필수적이에요.
- config.wals - WAL 파일의 차단 수는 memstore 설정에 의존하며, 많은 쓰기 볼륨에서 잠재적 차단을 막도록 그에 맞게 설정해야 해요.
그 다음에는 클러스터와 테이블을 설정할 때 몇 가지 고려 사항이 있어요.
컴팩션 (Compactions)
읽기/쓰기 볼륨과 대기 시간 요구 사항에 따라 최적 컴팩션 설정이 달라질 수 있어요. 자세한 내용은 compaction을 참고하세요.
큰 데이터 크기를 준비할 때는 컴팩션이 쓰기 처리량에 영향을 줄 수 있다는 점을 기억해야 해요. 따라서 쓰기 집약 워크로드에서는 덜 빈번한 컴팩션과 region당 더 많은 store file을 선택할 수 있어요. 컴팩션의 최소 파일 수(hbase.hstore.compaction.min)를 더 높게 설정할 수 있고, 그 경우 hbase.hstore.blockingStoreFiles도 늘려야 해요. 파일이 더 많이 쌓일 수 있으니까요. 컴팩션을 수동으로 관리하는 것도 고려할 수 있어요: managed.compactions
테이블 사전 분할 (Pre-splitting the table)
RS당 목표 region 수(위 ops.capacity.regions.count 참고)와 RS 수를 기준으로 테이블 생성 시점에 사전 분할을 할 수 있어요. 이렇게 하면 테이블이 차기 시작할 때 일부 값비싼 분할을 피하고, 테이블이 처음부터 많은 서버에 분산되어 시작되도록 보장해요.
테이블이 그럴 만큼 커질 것으로 예상된다면 RS당 최소 1개 region을 만들어야 해요. 즉시 목표 region 수 전체(예: 50 * RS 수)로 분할하는 것은 권장하지 않아요. 낮은 중간 값을 선택하는 것이 좋아요. 여러 테이블의 경우 사전 분할에 보수적이 되는 것이 좋아요(예: RS당 최대 1개 region 사전 분할). 특히 각 테이블이 얼마나 커질지 모를 때요. 너무 많이 분할하면 너무 많은 region이 생겨 일부 테이블에 너무 많은 작은 region이 생길 수 있어요.
사전 분할 방법은 manual region splitting decisions과 precreate.regions를 참고하세요.
RegionServer 그룹핑 (RegionServer Grouping)
RegionServer Grouping(일명 rsgroup)은 엄격한 격리를 위해 regionserver를 구분된 그룹으로 나누는 고급 기능이에요. 전체 영향을 충분히 이해하고 HBase 클러스터 관리에 대한 충분한 배경 지식을 가진 사용자만 사용해야 해요. Yahoo!가 개발했고 대규모 grid 클러스터에서 그 규모로 운영하고 있어요. HBase at Yahoo! Scale 참고.
RSGroups는 admin 메서드와 셸 명령으로 정의·관리할 수 있어요. 호스트 이름과 포트 쌍으로 서버를 그룹에 추가할 수 있고, 테이블을 이 그룹으로 이동해서 같은 rsgroup 안의 regionserver만 그 테이블의 region을 호스팅할 수 있게 해요. 테이블의 그룹은 해당 TableDescriptor에 저장되는데 속성 이름은 hbase.rsgroup.name이에요. 이 속성을 네임스페이스에 설정하면 그 네임스페이스 아래의 모든 테이블이 이 그룹에 배치돼요. RegionServer와 테이블은 한 번에 하나의 rsgroup에만 속할 수 있어요. 기본적으로 모든 테이블과 regionserver는 default rsgroup에 속해요. 시스템 테이블도 일반 API로 rsgroup에 넣을 수 있어요. 커스텀 밸런서 구현은 rsgroup별 할당을 추적하고 region을 해당 rsgroup의 관련 regionserver로 이동하도록 보장해요. rsgroup 정보는 일반 HBase 테이블에 저장되고, 클러스터 부트스트랩 시 zookeeper 기반 읽기 전용 캐시가 사용돼요.
활성화하려면 다음을 hbase-site.xml에 추가하고 Master를 재시작하세요:
<property>
<name>hbase.balancer.rsgroup.enabled</name>
<value>true</value>
</property>
그런 다음 admin/셸의 rsgroup 메서드/명령으로 RegionServer 그룹을 만들고 조작하면 돼요: 예를 들어 rsgroup을 추가하고 서버를 추가하는 식이죠. hbase 셸에서 사용 가능한 rsgroup 명령 목록을 보려면:
hbase(main):008:0> help 'rsgroup'
Took 0.5610 seconds
높은 수준에서 보면, default 그룹이 아닌 rsgroup을 add_rsgroup 명령으로 만들어요. 그 다음 move_servers_rsgroup과 move_tables_rsgroup 명령으로 서버와 테이블을 이 그룹에 추가해요. 필요하다면 테이블이 그룹 전용 서버로 천천히 마이그레이션될 때 balance_rsgroup 명령으로 그룹에 대해 밸런스를 실행해요(보통 필요 없어요). 명령의 효과를 모니터링하려면 Master UI 홈페이지 끝쪽의 Tables 탭을 확인하세요. 테이블을 클릭하면 어떤 서버에 배포되어 있는지 볼 수 있어요. 여기서 셸 명령으로 한 그룹핑이 반영된 것을 볼 수 있어야 해요. 문제가 있으면 master 로그를 확인하세요.
다음은 몇 가지 rsgroup 명령을 사용하는 예시예요. 그룹을 추가하려면 이렇게 해요:
hbase(main):008:0> add_rsgroup 'my_group'
Took 0.5610 seconds
RegionServer Groups must be Enabledrsgroup 기능을 활성화하지 않았는데 rsgroup admin 메서드나 셸
명령을 호출하면 rsgroup 기능이 비활성화되어 있다는 상세 메시지와 함께
DoNotRetryIOException으로 실패해요.
방금 만든 그룹에 서버(호스트 이름 + 포트로 지정)를 move_servers_rsgroup 명령으로 추가해요:
hbase(main):010:0> move_servers_rsgroup 'my_group',['k.att.net:51129']
Hostname and Port vs ServerNamersgroup 기능은 클러스터의 서버를 호스트 이름과 포트로만 가리켜요. RegionServer 인스턴스를 구분하는 HBase ServerName 타입(즉 hostname + port + starttime)을 사용하지 않아요. rsgroup 기능은 RegionServer 재시작을 걸쳐 계속 동작하므로 ServerName의 starttime — 따라서 ServerName 타입 — 은 적절하지 않아요.
관리 (Administration)
클러스터의 수명 동안 서버는 오고 가요. 현재 rsgroup에 참조된 서버를 실행 중인 클러스터의 실제 노드 상태와 수동으로 정렬해야 해요. 즉, 서버를 디커미션하면 서버 디커미션 프로세스의 일부로 rsgroup에서 참조를 제거하도록 rsgroup을 업데이트해야 해요. 참고로 clearDeadServers를 수동으로 호출하면 죽은 서버를 어떤 rsgroup에서도 제거하지만, 문제는 master가 재시작된 후 죽은 서버 추적을 잃게 되어 여전히 rsgroup을 직접 업데이트해야 한다는 점이에요.
디커미션 서버를 rsgroup에서 제거하려면 Admin.removeServersFromRSGroup이나 셸 명령 remove_servers_rsgroup을 사용하세요.
default 그룹은 다른 rsgroup과 달리 동적이에요. 그 서버 목록이 클러스터의 현재 상태를 반영하죠. 즉 default rsgroup에 있던 서버를 종료하고 셸에서 get_rsgroup default로 내용을 나열하면 그 서버는 더 이상 나열되지 않아요. 비-default 그룹에서는 모드가 오프라인이어도 비-default 그룹의 서버 목록에 유지돼요. 하지만 오프라인 서버를 비-default rsgroup에서 default로 옮기면 default 목록에 나타나지 않아요. 그냥 버려지는 거예요.
모범 사례 (Best Practice)
rsgroup 기능의 저자인 Yahoo! HBase Engineering 팀은 그리드에서 오랫동안 실행하면서 경험에 기반한 몇 가지 모범 사례를 정리했어요.
시스템 테이블 격리 (Isolate System Tables)
모든 시스템 테이블이 들어있는 시스템 rsgroup을 갖거나, 시스템 테이블을 default rsgroup에 두고 모든 사용자 공간 테이블을 비-default rsgroup에 두세요.
죽은 노드 (Dead Nodes)
Yahoo!는 그 규모에서 죽었거나 의심스러운 노드의 특별한 rsgroup을 유지하는 것이 유용하다는 것을 발견했어요. 수리 전까지 실행에서 제외시키는 한 가지 방법이죠.
rsgroup에서 죽은 노드를 교체할 때 주의하세요. 죽은 노드를 옮기기 전에 충분한 살아있는 노드가 있는지 확인하세요. 필요하다면 좋은 살아있는 노드를 먼저 들여오세요.
문제 해결 (Troubleshooting)
Master 로그를 보면 rsgroup 동작에 대한 통찰을 얻을 수 있어요.
멈춘 것 같으면 Master 프로세스를 재시작하세요.
RegionServer 그룹핑 제거 (Remove RegionServer Grouping)
RegionServer Grouping 기능을 비활성화하는 것은 쉽다. hbase-site.xml에서 'hbase.balancer.rsgroup.enabled'를 제거하거나 명시적으로 false로 설정하면 돼요.
<property>
<name>hbase.balancer.rsgroup.enabled</name>
<value>false</value>
</property>
하지만 'hbase.balancer.rsgroup.enabled'를 true로 바꾸면 옛 rsgroup 설정이 다시 적용돼요. 그래서 클러스터에서 RegionServer Grouping 기능을 완전히 제거해서, 미래에 기능이 다시 활성화되어도 옛 메타데이터가 클러스터 동작에 영향을 주지 않게 하려면 더 많은 단계가 필요해요.
- 비-default rsgroup의 모든 테이블을
defaultregionserver 그룹으로 이동
#Reassigning table t1 from non default group - hbase shell
hbase(main):005:0> move_tables_rsgroup 'default',['t1']
- 비-default rsgroup의 모든 regionserver를
defaultregionserver 그룹으로 이동
#Reassigning all the servers in the non-default rsgroup to default - hbase shell
hbase(main):008:0> move_servers_rsgroup 'default',['rs1.xxx.com:16206','rs2.xxx.com:16202','rs3.xxx.com:16204']
- 비-default rsgroup을 모두 제거.
defaultrsgroup은 암시적으로 생성되므로 제거할 필요가 없어요
#removing non default rsgroup - hbase shell
hbase(main):009:0> remove_rsgroup 'group2'
-
hbase-site.xml에서 변경 사항을 제거하고 클러스터를 재시작 -
hbase에서 테이블hbase:rsgroup을 드롭
#Through hbase shell drop table hbase:rsgroup
hbase(main):001:0> disable 'hbase:rsgroup'
0 row(s) in 2.6270 seconds
hbase(main):002:0> drop 'hbase:rsgroup'
0 row(s) in 1.2730 seconds
- zkCli.sh를 사용해 클러스터 ZooKeeper에서 znode
rsgroup제거
#From ZK remove the node /hbase/rsgroup through zkCli.sh
rmr /hbase/rsgroup
ACL
ACL을 활성화하려면 hbase-site.xml에 다음을 추가하고 Master를 재시작하세요:
<property>
<name>hbase.security.authorization</name>
<value>true</value>
<property>
옛 구현에서 마이그레이션 (Migrating From Old Implementation)
코프로세서 org.apache.hadoop.hbase.rsgroup.RSGroupAdminEndpoint는 deprecated지만, 호환성을 위해 3.0.0 이전 hbase client/shell이 새 hbase 클러스터와 통신하게 하려면 여전히 이 코프로세서를 master에 추가해야 해요.
hbase.rsgroup.grouploadbalancer.class 설정은 deprecated됐어요. 이제 최상위 로드 밸런서는 항상 RSGroupBasedLoadBalaner가 되고, hbase.master.loadbalancer.class 설정은 그룹 내 밸런서를 설정하는 용도이기 때문이에요. 이는 rsgroup 기능이 활성화되어 있어도 hbase.master.loadbalancer.class를 RSGroupBasedLoadBalaner로 설정해서는 안 된다는 뜻이기도 해요.
그리고 호환성을 위한 몇 가지 특별한 변경도 했어요. 첫째, 코프로세서 org.apache.hadoop.hbase.rsgroup.RSGroupAdminEndpoint가 지정되면 rs group 기능을 활성화하도록 hbase.balancer.rsgroup.enabled 플래그가 자동으로 true로 설정돼요. 둘째, hbase.master.loadbalancer.class보다 hbase.rsgroup.grouploadbalancer.class를 먼저 로드해요. 마지막으로, hbase.rsgroup.grouploadbalancer.class를 설정하지 않고 hbase.master.loadbalancer.class만 RSGroupBasedLoadBalancer로 설정했다면, 무한 중첩을 피하기 위해 기본 로드 밸런서를 로드해요. 즉 이미 rs group 기능을 활성화했다면 업그레이드할 때 아무것도 바꿀 필요가 없어요.
옛 구현과의 주요 차이는 이제 테이블의 rsgroup이 RSGroupInfo 대신 TableDescriptor에 저장된다는 점이라서 RSGroupInfo의 getTables 메서드가 deprecated됐어요. Admin 메서드로 RSGroupInfo를 얻는다면 그 getTables 메서드는 항상 빈 값을 반환해요. 이는 옛 구현에서 이 메서드가 다소 깨져 있었기 때문이에요. rsgroup을 네임스페이스에 설정해서 그 네임스페이스 아래 모든 테이블을 이 그룹에 넣을 수는 있지만 RSGroupInfo.getTables로 이 테이블들을 얻을 수 없었죠. 이제 Admin의 새 메서드 두 개 listTablesInRSGroup과 getConfiguredNamespacesAndTablesInRSGroup을 사용해서 rsgroup의 테이블과 네임스페이스를 얻어야 해요.
물론 옛 RSGroupAdminEndpoint의 동작은 바뀌지 않았어요. 옛 hbase client/shell과 호환되도록 반환하기 전에 RSGroupInfo의 tables 필드를 채워요.
업그레이드할 때 RSGroupInfo와 TableDescriptor 사이의 마이그레이션은 자동으로 수행돼요. 시간이 좀 걸리지만, 중간에 master를 재시작하는 것은 괜찮아요. 재시작 후에도 마이그레이션이 계속되니까요. 마이그레이션 중에도 rs group 기능은 계속 동작하고 대부분의 경우 region이 잘못 배치되지 않아요(이는 일회성 작업이라 오래 지속되지 않기 때문에 region이 항상 잘못 배치되지 않을 것이라고 매우 심각하게 테스트하지는 않아서 '대부분의 경우'라는 표현을 써요). 구현이 다소 까다로운데 관심이 있다면 RSGroupInfoManagerImpl.migrate의 코드를 볼 수 있어요.
Region Normalizer
Region Normalizer는 테이블의 모든 Region을 크기가 거의 같아지도록 만드려고 해요. 먼저 총 테이블 크기와 region당 평균 크기를 계산해서 이러죠. 그 크기의 두 배보다 큰 region을 분할해요. 훨씬 작은 region은 인접한 region과 병합돼요. Normalizer는 설정 가능한 정기 일정으로 실행돼요. 런타임 "스위치"로 완전히 비활성화할 수도 있어요. 셸이나 Admin API 호출로 수동 실행할 수도 있어요. 보통 비활성화되어 있더라도 클러스터가 한동안 실행된 후나 큰 delete 같은 활동 폭발 후에는 수동 실행하는 것이 좋아요.
Normalizer는 테이블을 사전 분할한 초기 노력 후에 region 경계를 데이터 분포의 현실과 정렬하는 데 잘 작동해요. 그리고 스키마가 로우키에 timestamp를 포함할 때 데이터 TTL 기능과 잘 어울리는데, 내용이 만료된 region을 자동으로 병합해 버리거든요.
(아래 세부 사항의 대부분은 Romil Choksi가 HBase Region Normalizer에서 쓴 블로그에서 그대로 가져왔어요.)
Region Normalizer는 HBase-1.2부터 사용 가능한 기능이에요. 주어진 테이블의 평균 region 크기와 비교해 너무 크거나 작은 region의 크기를 조정하기 위해 사전 계산된 병합/분할 작업 집합을 실행해요. Region Normalizer가 호출되면 HBase의 모든 테이블에 대한 정규화 '계획'을 계산해요. 시스템 테이블(hbase:meta, hbase:namespace, Phoenix 시스템 테이블 등)과 정규화가 비활성화된 사용자 테이블은 계획 계산 시 무시돼요. 정규화가 활성화된 테이블의 경우 정규화 계획이 여러 테이블에 걸쳐 병렬로 수행돼요.
Normalizer는 HBase 셸의 'normalizer_switch' 명령으로 전체 클러스터에 대해 전역적으로 활성화/비활성화할 수 있어요. 정규화는 테이블별로도 제어할 수 있는데, 테이블이 생성될 때 기본적으로 비활성화돼요. 테이블의 정규화는 NORMALIZATION_ENABLED 테이블 속성을 true/false로 설정해서 활성화/비활성화할 수 있어요.
normalizer 상태를 확인하고 활성화/비활성화하려면
hbase(main):001:0> normalizer_enabled
true
0 row(s) in 0.4870 seconds
hbase(main):002:0> normalizer_switch false
true
0 row(s) in 0.0640 seconds
hbase(main):003:0> normalizer_enabled
false
0 row(s) in 0.0120 seconds
hbase(main):004:0> normalizer_switch true
false
0 row(s) in 0.0200 seconds
hbase(main):005:0> normalizer_enabled
true
0 row(s) in 0.0090 seconds
활성화되면 Normalizer는 기본적으로 5분마다 백그라운드에서 호출되는데, hbase-site.xml의 hbase.normalization.period로 설정할 수 있어요. Normalizer는 HBase 셸의 normalize 명령으로 원할 때 수동/프로그래밍 방식으로 호출할 수도 있어요. HBase는 기본적으로 SimpleRegionNormalizer를 사용하지만, 사용자는 RegionNormalizer 인터페이스를 구현하기만 하면 자신만의 normalizer를 설계할 수 있어요. SimpleRegionNormalizer가 정규화 계획을 계산할 때 사용하는 로직에 대한 자세한 내용은 여기에서 확인할 수 있어요.
아래 예는 사용자 테이블에 대해 정규화 계획이 계산되고, 그 결과 SimpleRegionNormalizer가 계산한 정규화 계획에 따라 병합 작업이 이루어지는 것을 보여줘요.
약 100K 행의 동일하게 큰 region 3개와 상대적으로 작은 region 1개(약 25K 행)를 가진 사전 분할된 region이 있는 사용자 테이블을 생각해 보세요. 다음은 사용자 테이블의 각 사전 분할 region을 보여주는 hbase meta 테이블 스캔 스니펫이에요.
table_p8ddpd6q5z,,1469494305548.68b9892220865cb6048 column=info:regioninfo, timestamp=1469494306375, value={ENCODED => 68b9892220865cb604809c950d1adf48, NAME => 'table_p8ddpd6q5z,,1469494305548.68b989222 09c950d1adf48. 0865cb604809c950d1adf48.', STARTKEY => '', ENDKEY => '1'}
....
table_p8ddpd6q5z,1,1469494317178.867b77333bdc75a028 column=info:regioninfo, timestamp=1469494317848, value={ENCODED => 867b77333bdc75a028bb4c5e4b235f48, NAME => 'table_p8ddpd6q5z,1,1469494317178.867b7733 bb4c5e4b235f48. 3bdc75a028bb4c5e4b235f48.', STARTKEY => '1', ENDKEY => '3'}
....
table_p8ddpd6q5z,3,1469494328323.98f019a753425e7977 column=info:regioninfo, timestamp=1469494328486, value={ENCODED => 98f019a753425e7977ab8636e32deeeb, NAME => 'table_p8ddpd6q5z,3,1469494328323.98f019a7 ab8636e32deeeb. 53425e7977ab8636e32deeeb.', STARTKEY => '3', ENDKEY => '7'}
....
table_p8ddpd6q5z,7,1469494339662.94c64e748979ecbb16 column=info:regioninfo, timestamp=1469494339859, value={ENCODED => 94c64e748979ecbb166f6cc6550e25c6, NAME => 'table_p8ddpd6q5z,7,1469494339662.94c64e74 6f6cc6550e25c6. 8979ecbb166f6cc6550e25c6.', STARTKEY => '7', ENDKEY => '8'}
....
table_p8ddpd6q5z,8,1469494339662.6d2b3f5fd1595ab8e7 column=info:regioninfo, timestamp=1469494339859, value={ENCODED => 6d2b3f5fd1595ab8e7c031876057b1ee, NAME => 'table_p8ddpd6q5z,8,1469494339662.6d2b3f5f c031876057b1ee. d1595ab8e7c031876057b1ee.', STARTKEY => '8', ENDKEY => ''}
HBase 셸에서 'normalize'로 normalizer를 호출하면 아래 HMaster 로그 스니펫이 SimpleRegionNormalizer에 정의된 로직대로 계산된 정규화 계획을 보여줘요. 테이블에서 인접한 가장 작은 region들의 총 region 크기(MB)가 평균 region 크기보다 작기 때문에 normalizer는 이 두 region을 병합하는 계획을 계산해요.
2016-07-26 07:08:26,928 DEBUG [B.fifo.QRpcServer.handler=20,queue=2,port=20000] master.HMaster: Skipping normalization for table: hbase:namespace, as it's either system table or doesn't have auto
normalization turned on
2016-07-26 07:08:26,928 DEBUG [B.fifo.QRpcServer.handler=20,queue=2,port=20000] master.HMaster: Skipping normalization for table: hbase:backup, as it's either system table or doesn't have auto normalization turned on
2016-07-26 07:08:26,928 DEBUG [B.fifo.QRpcServer.handler=20,queue=2,port=20000] master.HMaster: Skipping normalization for table: hbase:meta, as it's either system table or doesn't have auto normalization turned on
2016-07-26 07:08:26,928 DEBUG [B.fifo.QRpcServer.handler=20,queue=2,port=20000] master.HMaster: Skipping normalization for table: table_h2osxu3wat, as it's either system table or doesn't have autonormalization turned on
2016-07-26 07:08:26,928 DEBUG [B.fifo.QRpcServer.handler=20,queue=2,port=20000] normalizer.SimpleRegionNormalizer: Computing normalization plan for table: table_p8ddpd6q5z, number of regions: 5
2016-07-26 07:08:26,929 DEBUG [B.fifo.QRpcServer.handler=20,queue=2,port=20000] normalizer.SimpleRegionNormalizer: Table table_p8ddpd6q5z, total aggregated regions size: 12
2016-07-26 07:08:26,929 DEBUG [B.fifo.QRpcServer.handler=20,queue=2,port=20000] normalizer.SimpleRegionNormalizer: Table table_p8ddpd6q5z, average region size: 2.4
2016-07-26 07:08:26,929 INFO [B.fifo.QRpcServer.handler=20,queue=2,port=20000] normalizer.SimpleRegionNormalizer: Table table_p8ddpd6q5z, small region size: 0 plus its neighbor size: 0, less thanthe avg size 2.4, merging them
2016-07-26 07:08:26,971 INFO [B.fifo.QRpcServer.handler=20,queue=2,port=20000] normalizer.MergeNormalizationPlan: Executing merging normalization plan: MergeNormalizationPlan{firstRegion={ENCODED=> d51df2c58e9b525206b1325fd925a971, NAME => 'table_p8ddpd6q5z,,1469514755237.d51df2c58e9b525206b1325fd925a971.', STARTKEY => '', ENDKEY => '1'}, secondRegion={ENCODED => e69c6b25c7b9562d078d9ad3994f5330, NAME => 'table_p8ddpd6q5z,1,1469514767669.e69c6b25c7b9562d078d9ad3994f5330.',
STARTKEY => '1', ENDKEY => '3'}}
Region normalizer는 계산된 계획에 따라 시작 키가 ''이고 끝 키가 '1'인 region을 시작 키 '1'이고 끝 키가 '3'인 다른 region과 병합했어요. 이제 이 region들이 병합되어 시작 키 ''와 끝 키 '3'인 단일 새 region이 생겼어요.
table_p8ddpd6q5z,,1469516907210.e06c9b83c4a252b130e column=info:mergeA, timestamp=1469516907431,
value=PBUF\x08\xA5\xD9\x9E\xAF\xE2*\x12\x1B\x0A\x07default\x12\x10table_p8ddpd6q5z\x1A\x00"\x011(\x000\x00 ea74d246741ba. 8\x00
table_p8ddpd6q5z,,1469516907210.e06c9b83c4a252b130e column=info:mergeB, timestamp=1469516907431,
value=PBUF\x08\xB5\xBA\x9F\xAF\xE2*\x12\x1B\x0A\x07default\x12\x10table_p8ddpd6q5z\x1A\x011"\x013(\x000\x0 ea74d246741ba. 08\x00
table_p8ddpd6q5z,,1469516907210.e06c9b83c4a252b130e column=info:regioninfo, timestamp=1469516907431, value={ENCODED => e06c9b83c4a252b130eea74d246741ba, NAME => 'table_p8ddpd6q5z,,1469516907210.e06c9b83c ea74d246741ba. 4a252b130eea74d246741ba.', STARTKEY => '', ENDKEY => '3'}
....
table_p8ddpd6q5z,3,1469514778736.bf024670a847c0adff column=info:regioninfo, timestamp=1469514779417, value={ENCODED => bf024670a847c0adffb74b2e13408b32, NAME => 'table_p8ddpd6q5z,3,1469514778736.bf024670 b74b2e13408b32. a847c0adffb74b2e13408b32.' STARTKEY => '3', ENDKEY => '7'}
....
table_p8ddpd6q5z,7,1469514790152.7c5a67bc755e649db2 column=info:regioninfo, timestamp=1469514790312, value={ENCODED => 7c5a67bc755e649db22f49af6270f1e1, NAME => 'table_p8ddpd6q5z,7,1469514790152.7c5a67bc 2f49af6270f1e1. 755e649db22f49af6270f1e1.', STARTKEY => '7', ENDKEY => '8'}
....
table_p8ddpd6q5z,8,1469514790152.58e7503cda69f98f47 column=info:regioninfo, timestamp=1469514790312, value={ENCODED => 58e7503cda69f98f4755178e74288c3a, NAME => 'table_p8ddpd6q5z,8,1469514790152.58e7503c 55178e74288c3a. da69f98f4755178e74288c3a.', STARTKEY => '8', ENDKEY => ''}
3개의 작은 region과 1개의 상대적으로 큰 region을 가진 사용자 테이블에서도 비슷한 예를 볼 수 있어요. 이 예에서는 100K 행의 큰 region 1개와 각각 약 33K 행의 상대적으로 작은 region 3개가 있는 사용자 테이블이 있어요. 정규화 계획에서 보듯이, 큰 region이 평균 region 크기의 두 배를 넘기 때문에 시작 키 '1'과 끝 키 '154717'인 region과 시작 키 '154717'과 끝 키 '3'인 다른 region, 두 개로 분할되게 돼요.
2016-07-26 07:39:45,636 DEBUG [B.fifo.QRpcServer.handler=7,queue=1,port=20000] master.HMaster: Skipping normalization for table: hbase:backup, as it's either system table or doesn't have auto normalization turned on
2016-07-26 07:39:45,636 DEBUG [B.fifo.QRpcServer.handler=7,queue=1,port=20000] normalizer.SimpleRegionNormalizer: Computing normalization plan for table: table_p8ddpd6q5z, number of regions: 4
2016-07-26 07:39:45,636 DEBUG [B.fifo.QRpcServer.handler=7,queue=1,port=20000] normalizer.SimpleRegionNormalizer: Table table_p8ddpd6q5z, total aggregated regions size: 12
2016-07-26 07:39:45,636 DEBUG [B.fifo.QRpcServer.handler=7,queue=1,port=20000] normalizer.SimpleRegionNormalizer: Table table_p8ddpd6q5z, average region size: 3.0
2016-07-26 07:39:45,636 DEBUG [B.fifo.QRpcServer.handler=7,queue=1,port=20000] normalizer.SimpleRegionNormalizer: No normalization needed, regions look good for table: table_p8ddpd6q5z
2016-07-26 07:39:45,636 DEBUG [B.fifo.QRpcServer.handler=7,queue=1,port=20000] normalizer.SimpleRegionNormalizer: Computing normalization plan for table: table_h2osxu3wat, number of regions: 5
2016-07-26 07:39:45,636 DEBUG [B.fifo.QRpcServer.handler=7,queue=1,port=20000] normalizer.SimpleRegionNormalizer: Table table_h2osxu3wat, total aggregated regions size: 7
2016-07-26 07:39:45,636 DEBUG [B.fifo.QRpcServer.handler=7,queue=1,port=20000] normalizer.SimpleRegionNormalizer: Table table_h2osxu3wat, average region size: 1.4
2016-07-26 07:39:45,636 INFO [B.fifo.QRpcServer.handler=7,queue=1,port=20000] normalizer.SimpleRegionNormalizer: Table table_h2osxu3wat, large region table_h2osxu3wat,1,1469515926544.27f2fdbb2b6612ea163eb6b40753c3db. has size 4, more than twice avg size, splitting
2016-07-26 07:39:45,640 INFO [B.fifo.QRpcServer.handler=7,queue=1,port=20000] normalizer.SplitNormalizationPlan: Executing splitting normalization plan: SplitNormalizationPlan{regionInfo={ENCODED => 27f2fdbb2b6612ea163eb6b40753c3db, NAME => 'table_h2osxu3wat,1,1469515926544.27f2fdbb2b6612ea163eb6b40753c3db.', STARTKEY => '1', ENDKEY => '3'}, splitPoint=null}
2016-07-26 07:39:45,656 DEBUG [B.fifo.QRpcServer.handler=7,queue=1,port=20000] master.HMaster: Skipping normalization for table: hbase:namespace, as it's either system table or doesn't have auto normalization turned on
2016-07-26 07:39:45,656 DEBUG [B.fifo.QRpcServer.handler=7,queue=1,port=20000] master.HMaster: Skipping normalization for table: hbase:meta, as it's either system table or doesn't
have auto normalization turned on ..............
2016-07-26 07:39:46,246 DEBUG [AM.ZK.Worker-pool2-t278] master.RegionStates: Onlined 54de97dae764b864504704c1c8d3674a on hbase-test-rc-5.openstacklocal,16020,1469419333913 {ENCODED => 54de97dae764b864504704c1c8d3674a, NAME => 'table_h2osxu3wat,1,1469518785661.54de97dae764b864504704c1c8d3674a.', STARTKEY => '1', ENDKEY => '154717'}
2016-07-26 07:39:46,246 INFO [AM.ZK.Worker-pool2-t278] master.RegionStates: Transition {d6b5625df331cfec84dce4f1122c567f state=SPLITTING_NEW, ts=1469518786246, server=hbase-test-rc-5.openstacklocal,16020,1469419333913} to {d6b5625df331cfec84dce4f1122c567f state=OPEN, ts=1469518786246,
server=hbase-test-rc-5.openstacklocal,16020,1469419333913}
2016-07-26 07:39:46,246 DEBUG [AM.ZK.Worker-pool2-t278] master.RegionStates: Onlined d6b5625df331cfec84dce4f1122c567f on hbase-test-rc-5.openstacklocal,16020,1469419333913 {ENCODED => d6b5625df331cfec84dce4f1122c567f, NAME => 'table_h2osxu3wat,154717,1469518785661.d6b5625df331cfec84dce4f1122c567f.', STARTKEY => '154717', ENDKEY => '3'}
Auto Region Reopen
코프로세서나 핵심 함수가 어떤 식으로든 스캐너를 열거나 감싼 다음 스캐너 또는 감싼 인스턴스에서 close를 호출하지 않으면 store reader 참조를 누출할 수 있어요. 누출된 store file은 컴팩션으로 무효화된 후에도 제거되지 못할 수 있어요. reader 참조 누출에 대한 합리적인 완화책은 같은 서버에서 region을 빠르게 다시 여는 것이에요. 이렇게 하면 refcount, leases 등 모든 리소스가 해제돼요. 클라이언트는 다른 전환 중 region처럼 이 상황을 우아하게 넘겨야 해요. 기본적으로 이 auto reopen of region 기능은 비활성화되어 있어요. 활성화하려면 hbase.regions.recovery.store.file.ref.count 설정에 높은 ref count 값을 제공하세요.
설정 설명은 hbase.master.regions.recovery.check.interval과 hbase.regions.recovery.store.file.ref.count를 참고하세요.