R&D 목적
- 다양한 커스텀 세그먼트 조합의 자유로운 분석을 위한 빅데이터 시스템 개발
- 빅데이터 기술 습득
HBase 실습 방법 및 과정
Overview
기존에 사용하는 데이터 처리 방식과 동일하게 Spark에서 데이터를 처리하고 HBase에 수억건의 데이터를 저장할 수 있는 환경을 구축하였다.
실습은 아래와 같은 순서로 진행하였다.
1. 테스트용 샘플 데이터 선정 2. Amazon EMR 환경에서 Spark를 이용해 샘플 데이터를 HBase로 적재 3. HBase에 적재된 샘플 데이터 기반으로 Phoenix에서 테이블 생성 후, 쿼리 실행 결과 확인 4. ❓Phoenix에 API를 요청하고 응답을 확인해 외부에서도 접근 가능한 환경 세팅
테스트용 샘플 데이터로는 1억 4천개의 row를 가진 **** 데이터 1일치를 선정하여 실습을 진행하였다.
Spark와 HBase를 구동할 수 있는 Amazon EMR을 생성해 Spark에서 데이터를 HBase에 적합한 데이터 스키마로 재구성(데이터 크기 확인, Row Key 설계, 사전분할, Column Family 설계)하여 HBase에 적재하였다.
또한 HBase에서 configuration 값을 변경하여 결과적으로 데이터 적재 성능을 88%, 처리속도는 9배 개선하였다. Ganglia와 HBase UI를 이용해 지속적으로 모니터링하며 실습을 진행하였다.
추후 HBase 데이터에 쿼리 작업을 진행할 수 있게 Apache Phoenix 쿼리엔진을 연결해 분석 작업이 가능한 환경도 대비하였다.
실습 환경
- 하드웨어
- Amazon EMR 버전
- emr-6.5.0
- 설치 애플리케이션
- Ganglia 3.7.2
- Hadoop 3.2.1
- HBase 2.4.4
- Hive 3.1.2
- Hue 4.9.0
- Phoenix 5.1.2
- Spark 3.1.2
- Zeppelin 0.10.0
- ZooKeeper 3.5.7
- 클러스터 구성
- Primary : r5.xlarge 1개
- Core : r4.2xlarge 5개
- HBase는 Hadoop의 HDFS 위에서 실행이 되는데 HDFS의 파일 복제 관련 기본 설정이 최소 3개 (즉, 하나의 파일이 3개의 노드에 분산 복제됨)이기 때문에 노드가 최소 5개 이상 존재할 때 성능 저하 없이 정상적으로 작동 가능
실습 과정
- Amazon EMR을 생성하여 환경 세팅
- 위의 ‘실습 환경/ 1. 하드웨어’ 설정에 맞추어 EMR을 생성
- Zeppelin에서 spark interpreter 설정
name: spark.jars.packages , value: org.apache.hbase:hbase-shaded-mapreduce:2.4.15
name: spark.jars , value: /home/hadoop/hbase-spark-1.0.1-SNAPSHOT_spark331_hbase2415.jar,/home/hadoop/hbase-spark-protocol-shaded-1.0.1-SNAPSHOT_spark331_hbase2415.jar
# spark.jars의 경우 생성하는 emr에 부트스트랩 작업으로 hbase-spark-1.0.1-SNAPSHOT_spark331_hbase2415.jar, hbase-spark-protocol-shaded-1.0.1-SNAPSHOT_spark331_hbase2415.jar 설치하게 설정
- Spark에서 샘플 데이터를 HBase로 적재
- HBase에 맞추어 샘플 데이터 스키마 구성
- HBase에 저장될 데이터 크기 확인
- 일반적으로 HBase의 cell 당 10MB 이하로 데이터를 유지하는 것 권장
- 데이터 중 keyword 컬럼의 가장 긴 값을 가지는 (가장 큰 데이터) 값의 크기는 1MB 이하로, 데이터를 적재하기에 HBase 저장소는 충분한 크기라고 판단
- Row Key 설계
- HBase에서는 RDBMS의 PK와 비슷한 고유의 Row Key를 가지는데, 이 Row Key의 사전순 정렬 특징으로 데이터를 빠르게 검색 가능
- 잘못된 Row Key 설계로 데이터가 특정 Region Server에 몰리는 HotSpot을 방지하기 위해 클러스터 전체의 여러 Region에 데이터가 기록되도록 Row Key 설계하는 것이 중요
- 가능하면 짧은 길이의 Row Key 권장
- 설계 방법 중 하나인 Hashing을 이용해 Row Key 설계
- 데이터의 컬럼 중 고유한 값으로 존재하는 컬럼 선택
- 해당 컬럼값을 해시 함수 MD5로 Hashing한 뒤 각 32bytes의 문자열을 가진 “id”컬럼을 생성
- id 컬럼값 중 첫번째 문자를 추출 후 그룹화하여 개수를 확인했을 때, 데이터 전체 row수 16진수에 맞추어 고르게 분산되는 모습 확인
- Hase shell에서 Table 생성시 16진수 범위로 사전분할 후 데이터 적재시 Write 성능 향상 기대
- Column Family 설계
- 다수의 Column Family (이하 cf)가 존재할 경우 Flush 상호 작용으로 인해 불필요한 I/O가 발생할 수 있어 Table 당 1~3개의 cf를 유지하는 것을 권장
- 단일 Table에 여러 cf가 존재할 때는 “Row 수”에 유의 필요
- 예) cf1 (100만개 행), cf2 (10억개 행). 2개의 cf가 존재할 때 cf1의 데이터 수많은 지역에 분산될 가능성이 높은데 이는 결국 cf1에 대한 대량 스캔의 효율성이 저하되는 문제로 이어짐
- 데이터의 경우 cate_code 와 keyword 컬럼값이 없는 Row가 다수 존재하여 여러개의 cf로 데이터를 적재할 때 Write/Read에서 성능이 떨어질 것으로 예상하여 1개의 cf로 구성
- Rowkey와 마찬가지로 cf명도 데이터와 함께 저장소에 같이 저장되기 때문에 짧은 길이 권장
- 테스트용으로 cf 명은 ‘t’로 지정
- HBase Shell에서 Table 생성시 사전 분할
- 외부에서 HBase로 데이터를 전송하기 전에 해당 데이터에 부합하는 스키마의 Table이 HBase에 미리 생성되어야 함
- hbase shell >
create "테이블명" , "t", SPLITS=>['0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f']
- 해당 실습에서는 사전 분할을 0부터 시작해 ‘첫 데이터~ 0 사이의 값’을 지닌 region도 따로 생성되었지만 실제로는 해당 범위에 포함되는 값이 없어 해당 region에는 어떠한 값도 저장되지 않음
- 따라서 이 경우 Table 사전 분할의 분할점은 아래와 같이 분할해야 남는 region 없이 저장 가능
['1','2','3','4','5','6','7','8','9','a','b','c','d','e','f']
- Spark → HBase 데이터 저장
- SSH로 마스터 노드에 원격 접속 후, Spark ↔ HBase 연결을 위해 Spark conf에 hbase-site.xml 파일 복사
sudo cp /lib/hbase/conf/hbase-site.xml /lib/spark/conf/
- Spark에서 HBase Table 스키마에 맞춰 데이터 적재
- HBase 튜닝으로 성능 개선
- Amazon EMR 생성시 소프트웨어 구성에서 HBase Configuration 미리 설정
- 튜닝 목표 : 처리량 ↑, 메모리 사용 ↑, 압축률 ↓
- hbase-env.sh
- hbase-site.xml
- Amazon EMR 소프트웨어 구성 설정
- 위의 ‘실습 환경/ 1. 하드웨어/소프트웨어 설정’ 참고
- Ganglia와 HBase UI로 모니터링
- S3 저장 크기 : 298.9GB
- Zeppelin 적재 시간 : 3시간 이상 (기본 설정) → 22분 (Row Key 설계 + 구성 변경 튜닝 후)
- Ganglia 모니터링
- 데이터 적재시, 기본 설정에서는 모든 노드의 CPU가 초반에만 많이 사용되다가 급격하게 줄어 긴 시간 동안 병목되 모습 확인
- 반면에 튜닝 후 모든 노드에서 CPU가 활발히 사용되고 스레드 수가 줄어듦에 따라 CPU의 사용 정도도 점진적으로 줄어드는 모습 확인
- 데이터 적재시, 기본 설정에서는 CPU 사용량이 급격하게 떨어지는 시점부터 캐시 메모리가 급증하고 낮은 사용 메모리에 비해 높은 수준의 캐시 메모리 사용량을 유지하는 모습 확인
- 반면 튜닝 후에는 처리량에 따라 메모리가 점진적으로 많이 사용되면 정점을 찍고 일반적인 사용량 정도로 회복하는 모습 확인
- 위의 지표들과 마찬가지로 기본 설정에서는 모든 노드들이 초반에만 작업을 진행하다 긴 시간 병목되는 모습을 보이는 반면 튜닝 후에는 모든 자원을 골고루 활용해 빠르게 작업을 처리하는 모습 확인
- 네트워크 트래픽에서도 기본 설정과 튜닝 후 설정 사이에 차이 존재. 기본 설정은 초반에 많은 양의 인바운드 트래픽을 아웃바운드 트래픽이 따라가지 못해 점점 인바운드와 아웃바운드 모두 줄어드는 모습 확인
- 반면 튜닝 후에는 인바운드 트래픽과 아웃바운드 트래픽이 적절한 균형을 이루며 통신하는 모습 확인
- 데이터 적재시, 튜닝 후 모든 노드들이 동시에 일하는 모습 확인
- 사전 분할로 각 region server의 region에 데이터가 고르게 분산되기 때문에 모든 region server 노드가 동시에 작업하는 것으로 추측
- 사전 분할 전 기본 설정에서는 노드가 순차적으로 작업 진행
- (보안상 사진 미첨부)
- HBase UI 모니터링
- Row Key 설계 없이, 기본 설정으로 데이터를 적재했을 때 쓰기 작업이 각 Region에 불균형하게 할당되는 모습을 보임
- 반면 튜닝 후에는 각 Region에 데이터가 고르게 분산되어 저장되는 모습을 보임
- HDFS 확인
- 각 Region Server 당 1개의 WAL 파일 생성
- HBase의 17개의 Region에 분산 저장된 데이터 확인
from pyspark.sql.functions import *
date_format = ""
raw_inte = spark.read.parquet("".format(date_param))\
.withColumn("id", md5("컬"))
적재시 모든 컬럼값의 데이터 타입을 ‘STRING’으로 주었는데, Spark의 데이터 타입과 HBase의 데이터 타입이 불일치해 적재 실패되는 경우가 생겨 임시로 모두 ‘STRING’ 형태로 적재
raw_inte.write.format("org.apahce.hadoop.hbase.spark")\
.option("hbase.columns.mapping", "컬럼 속성")\
.option("hbase.namespace", "default")\
.option("hbase.table", "테이블명")\
.option("hbase.spark.use.hbasecontext", False).save()
해당 실습에서는 emr 생성시 소프트웨어 설정값으로 HEAPSIZE를 바꾸지 못해서 ssh로 모든 노드에 접속 후 설정 파일을 수동으로 바꾸고 HBase 관련 서비스를 재시작해주었습니다.
# 각 region server에서 사용할 힙 크기
Export HBASE_HEAPSIZE=30720
# 리전서버에서 데이터 처리를 위해 사용되는 thread 수
<property>
<name>hbase.regionserver.handler.count</name>
<value>88</value>
</property>
# memstore가 이 크기 이상을 가지면 flush 진행
<property>
<name>hbase.hregion.memstore.flush.size</name>
<value>268435456</value>
</property>
# memstore크기가 multiplier*flush.size 곱한 값만큼 증가할 경우 강제로 flush
<property>
<name>hbase.hregion.memstore.block.multiplier</name>
<value>8</value>
</property>
# 리전서버의 힙영역에서의 memstore 크기 비율, 이 크기 넘기면 memstore에서 쓰기 중단 후 강제로 flush
<property>
<name>hbase.regionserver.global.memstore.upperLimit</name>
<value>0.5</value>
</property>
# memstore 크기가 lowerlimit에 도달하면 soft한 flush 발생, memstore에 쓰기도 하면서 flush도 진행
<property>
<name>hbase.regionserver.global.memstore.lowerLimit</name>
<value>0.45</value>
</property>
# 리전서버 처리 메모리, hfile에서 사용하는 블록캐시에 할당할 최대 힙의 백분율
<property>
<name>hfile.block.cache.size</name>
<value>0.3</value>
</property>
# hstorefile 수를 초과하는 store가 하나라도 있으면 압축 실행. 모든 sotrefile을 하나의 storefile로 다시 씀
<property>
<name>hbase.hstore.compactionThreshold</name>
<value>40</value>
</property>
# 한 store에 이 수보다 많은 storefile이 있을 경우 압축이 완료될 떄까지 hregion에 대한 업데이트 차단
<property>
<name>hbase.hstore.blockingStoreFiles</name>
<value>40</value>
</property>
<property>
<name>hbase.thrift.minWorkerThreads</name>
<value>256</value>
</property>













- Phoenix에서 HBase 데이터 기반으로 테이블 생성 후 쿼리 결과 확인
- Phoenix 실행
cd /lib/phoenix/bin
./sqlline.py
orpython sqlline.py localhost:2181
- Phoneix에 view 생성 후 쿼리 결과 확인
- View 생성
- 1억 4천개 row를 가진 Table로 View 생성 소요 시간 : 38초
- Table or View 이름은 HBase에 존재하는 Table명과 동일하게 생성
- 테이블명과 cf명, 컬럼명은 모두 따옴표로 묶어서 쿼리 작성 필요
- “ColumnFamily”.”Column” 형식으로 표기
- SELECT 구문으로 데이터 조회
- 1억 4천개 row 중 30개 row 모든 column 조회 소요 시간 : 8초
- 1억 4천개 row 중 30개 row 모든 column을 특정 컬럼 기준오름차순으로 정렬 조회 소요 시간 : 583초 (9분)
- Athena에서 같은 쿼리 소요 시간 : 8.9초
CREATE VIEW "테이블명" (컬럼명 컬럼 속성);
Table로 생성시 데이터 값이 제대로 안들어오는 문제 지속 발생으로 임시로 View로 생성
API 요청 응답 확인
- Amazon EMR 종료시
- HBase에 저장된 데이터 유지하기
- 모든 테이블 비활성화하기
bash /usr/lib/hbase/bin/disable_all_tables.sh
- 데이터 (테이블) 플러시하기
- hbase shell >
flush 'hbase:meta'
- emr 새로 생성시 HBase 루트 디렉토리로 이전 클러스터와 동일한 S3 위치로 지정
R&D 결과 및 고찰
HBase 운영에 적합한 EMR 환경을 세팅하고 데이터의 cell 크기 확인, Row Key 디자인, Column Family 설계, 사전분할 그리고 HBase의 구성에 변화를 주어 데이터 적재 성능을 88%, 처리속도는 9배 개선하였다.
성능 개선 = (이전 작업 시간 - 새로운 작업 시간) / 이전 작업 시간 * 100
HBase에 적재 성능 개선에 가장 큰 영향을 준 부분은 Row Key 설계로, 데이터 저장시 적절한 스키마를 구성하는 것이 필수적인 요인임을 깨달았다. 더불어 Phoenix를 연결해 HBase에 쿼리를 실행한 결과, 아테나에서의 쿼리 실행 속도보다 느린 부분을 확인하였다.
written by salmonavocado🥑
Share article