RegionServer 로그에 다음과 같은 줄이 주기적으로 찍힌다.
PeriodicMemStoreFlusher: MemStoreFlusherChore requesting flush of prod,,1690000000000.abc123.
because i has an old edit so flush to free WALs after random delay 213000 ms
읽는 순서는 이렇다.
PeriodicMemStoreFlusher — RegionServer 안에서 일정 간격으로 도는 내부 작업이다. 사람이 건 것이 아니다.prod,,...abc123. — 대상은 테이블 prod 의 특정 리전이다. 테이블 전체가 아니다.has an old edit — 이 리전의 MemStore 에 오래된 변경이 남아 있다. 그 변경이 기록된 WAL 파일을 아직 버릴 수 없는 상태다.flush to free WALs — 플러시해서 HFile 로 내려보내면 그 WAL 을 버릴 수 있다. 목적은 성능이 아니라 WAL 정리다.after random delay 213000 ms — 바로 하지 않고 무작위로 지연시킨다. 리전 수백 개가 동시에 플러시를 시작하면 디스크가 순간적으로 몰리기 때문에 일부러 흩는 것이다.즉 이 메시지는 오류가 아니다. 쓰기가 드문 리전이 WAL 을 붙들고 있는 것을 HBase 가 스스로 풀고 있다는 뜻이다.
RegionServer 는 쓰기를 먼저 WAL 에 적고 MemStore 에 담는다. MemStore 가 HFile 로 내려가야 그 WAL 구간이 필요 없어진다. 문제는 쓰기가 거의 없는 리전이다. MemStore 가 플러시 임계치(hbase.hregion.memstore.flush.size, 기본 128MB)에 닿지 않으니 영원히 내려가지 않고, 그 리전의 변경이 들어 있는 오래된 WAL 이 계속 살아남는다.
WAL 파일 수가 hbase.regionserver.maxlogs 를 넘으면 RegionServer 는 가장 오래된 WAL 에 걸린 리전을 강제로 플러시한다. 주기적 플러셔는 그 상황에 이르기 전에 미리 털어 내는 장치다.
관련 설정은 셋이다.
| 설정 | 기본값 | 뜻 |
|---|---|---|
hbase.regionserver.optionalcacheflushinterval |
3600000 (1시간) | 이보다 오래된 변경이 MemStore 에 있으면 플러시 대상으로 삼는다. 0 이면 끈다 |
hbase.hregion.memstore.flush.size |
134217728 (128MB) | MemStore 가 이 크기를 넘으면 플러시 |
hbase.regionserver.maxlogs |
32 (버전에 따라 다름) | WAL 파일 개수 상한 |
의도된 동작은 아니다. 플러시 자체는 온라인으로 도는 작업이라 리전을 막지 않는 것이 원칙이다. 수 초씩 응답이 멎는다면 다른 요인이 겹쳐 있다.
플러시가 몰렸다. 무작위 지연은 리전 단위로 흩는 것이지 총량을 줄이지 않는다. 리전이 많으면 지연을 줘도 디스크 IO 가 계속 높은 상태가 된다. RegionServer 지표에서 flushQueueLength 와 flushTime 을 본다.
컴팩션이 뒤따라 왔다. 작은 HFile 이 자주 생기면 minor compaction 이 이어진다. 플러시보다 컴팩션 쪽이 IO 를 훨씬 많이 쓴다. compactionQueueLength 를 같이 본다.
GC 가 멈춰 세웠다. MemStore 는 힙을 쓴다. 플러시 직후 대량의 객체가 회수되면서 stop-the-world 가 길어지면 그동안 응답이 없다. GC 로그에 그 시각의 정지 시간이 남아 있는지 확인하는 것이 가장 확실하다.
grep -E "Pause (Young|Full)" /var/log/hbase/gc.log | tail -50
WAL 을 쓰는 디스크가 느리다. WAL 디렉터리가 데이터 디렉터리와 같은 스핀들에 있으면 플러시 쓰기와 WAL 쓰기가 서로 부딪힌다.
우선 이 메시지 자체를 없애려 하지 않는다. 없애는 방향은 WAL 이 쌓이는 방향이다.
쓰기가 드문 리전이 많은 것이 원인이면 리전을 줄인다. 쓰이지 않는 테이블을 비활성화하거나, 과하게 분할된 테이블을 병합한다.
echo "merge_region 'ENCODED_1', 'ENCODED_2'" | hbase shell
주기 자체를 늘리면 플러시 횟수는 줄지만 WAL 이 더 오래 살아 복구 시간이 길어진다. 늘릴 때는 maxlogs 와 같이 본다.
<property>
<name>hbase.regionserver.optionalcacheflushinterval</name>
<value>7200000</value>
</property>
수동으로 털어야 할 때는 HBase 셸에서 건다. 테이블 전체 또는 리전 하나를 지정할 수 있다.
echo "flush 'prod'" | hbase shell
echo "flush 'prod,,1690000000000.abc123.'" | hbase shell
이것을 cron 으로 주기 실행하는 구성은 권하지 않는다. HBase 가 이미 같은 일을 지연까지 넣어서 하고 있으므로, 밖에서 한 번 더 걸면 플러시가 겹쳐 오히려 IO 가 몰린다. 수동 플러시는 스냅샷 직전처럼 시점을 맞춰야 할 때만 쓴다.