힌트

힌트 (Hints)

힌팅(hinting)은 쓰기 연산 중에 적용되는 데이터 리페어 기법이에요. 리플리카 노드가 실패로 인해 또는 더 흔히는 일상적인 유지보수로 인해 변경(mutation)을 받아들이지 못할 때, 그 리플리카에 쓰려는 조정자(coordinator)는 나중에 사용할 수 없었던 리플리카에 적용하기 위해 임시 힌트를 로컬 파일시스템에 저장해요. 힌트는 데이터 불일치 기간을 줄이는 데 중요한 방법입니다. 조정자는 사용할 수 없던 리플리카 노드가 링으로 돌아온 직후 힌트를 빠르게 재생합니다. 다만 힌트는 best-effort로, anti-entropy 리페어가 하는 것처럼 최종 일관성을 보장하지는 못해요.

힌트가 유용한 이유는 Apache Cassandra가 내결함성, 고가용성, 지속성을 제공하기 위해 데이터를 복제하는 방식 때문입니다. Cassandra는 consistent hashing을 사용해 데이터를 클러스터 전반에 파티셔닝하고, 해시 링을 따라 키를 여러 노드에 복제합니다. 가용성을 보장하기 위해 키의 모든 리플리카는 합의(consensus) 없이도 변경을 받아들일 수 있지만, 이는 일부 리플리카는 변경을 받아들이고 다른 리플리카는 받아들이지 않을 가능성이 있다는 뜻입니다. 이럴 때 불일치가 발생해요.

힌트는 read-repair 및 full/incremental anti-entropy 리페어와 함께, 모든 업데이트가 결국 모든 리플리카에 도달한다는 최종 일관성 보장을 Cassandra가 구현하는 세 가지 방법 중 하나입니다. 힌트는 read-repair처럼 best-effort이며 전체 리페어를 수행하는 대안은 아니지만, 실제로 리플리카 간의 불일치 기간을 줄이는 데 도움이 됩니다.

출처: 문서

본문

Hinted Handoff

Hinted handoff는 Cassandra가 사용할 수 없는 노드에 힌트를 적용하는 과정이에요.

예를 들어 복제 계수(Replication Factor)가 3인 키스페이스에 대해 Consistency Level LOCAL_QUORUM으로 변경을 만든다고 가정해 봅시다. 보통 클라이언트는 변경을 단일 조정자에게 보내고, 조정자는 그 변경을 세 리플리카 모두에게 보냅니다. 세 리플리카 중 두 개가 변경을 승인하면 조정자는 클라이언트에게 성공으로 응답해요. 하지만 리플리카 노드를 사용할 수 없다면 조정자는 나중에 적용하기 위해 로컬 파일시스템에 힌트를 저장합니다. 새 힌트는 max_hint_windowin_ms 동안의 다운타임(기본 3시간)까지 보관됩니다. 사용할 수 없던 리플리카가 기간 만료 전에 클러스터로 돌아오면 조정자는 그 리플리카에 대해 보류 중인 힌트 변경을 적용해 최종 일관성을 유지합니다.

  • (t0): 쓰기가 클라이언트에 의해 전송되고, 조정자가 세 리플리카에 전송합니다. 안타깝게도 replica_2가 재시작 중이라 변경을 받을 수 없어요.
  • (t1): 클라이언트가 조정자로부터 쿼럼 승인을 받습니다. 이 시점에 클라이언트는 쓰기가 (실제로 그렇듯) 영구적이고 읽기에 보인다고 믿어요.
  • (t2): 쓰기 타임아웃(기본 2초) 후 조정자는 replica_2를 사용할 수 없다고 판단하고 로컬 디스크에 힌트를 저장합니다.
  • (t3): 나중에 replica_2가 다시 시작되면 조정자를 포함한 모든 노드에 gossip 메시지를 보냅니다.
  • (t4): 조정자가 놓친 변경을 포함한 힌트를 replica_2에 대해 재생합니다.

노드가 제때 돌아오지 않으면 read-repair나 full/incremental anti-entropy 리페어가 변경을 전파할 때까지 대상 리플리카는 영구적으로 동기화되지 않은 상태로 남습니다.

힌트 적용

힌트는 세그먼트 단위로 대량으로 대상 리플리카 노드에 스트리밍되고, 대상 노드가 로컬에서 재생합니다. 대상 노드가 세그먼트를 재생한 후에는 세그먼트를 삭제하고 다음 세그먼트를 받습니다. 이 과정은 모든 힌트가 소진될 때까지 계속됩니다.

디스크의 힌트 저장

힌트는 조정자 노드의 $CASSANDRA_HOME/data/hints 디렉터리의 플랫 파일에 저장됩니다. 힌트는 힌트 id, 변경이 저장되어야 할 대상 리플리카 노드, 리플리카 노드에 전달할 수 없었던 직렬화된 변경(blob으로 저장), 변경 타임스탬프, 변경을 직렬화하는 데 사용된 Cassandra 버전을 포함해요. 기본적으로 힌트는 LZ4Compressor로 압축됩니다. 여러 힌트가 같은 힌트 파일에 추가됩니다.

힌트는 원래 수정되지 않은 변경 타임스탬프를 포함하므로 힌트 적용은 idempotent하며 미래의 변경을 덮어쓸 수 없어요.

타임아웃된 쓰기 요청에 대한 힌트

타임아웃된 쓰기 요청에 대해서도 힌트가 저장됩니다. cassandra.yamlwrite_request_timeout 설정이 쓰기 요청의 타임아웃을 구성합니다.

write_request_timeout: 2000ms

조정자는 쓰기 요청이 완료되도록 구성된 시간만큼 기다리고, 그 시점에 타임아웃되어 타임아웃된 요청에 대한 힌트를 생성합니다. write_request_timeout의 허용되는 최소 값은 10ms예요.

힌트 구성

힌트는 데이터 일관성에 중요하므로 기본적으로 활성화되어 있습니다. cassandra.yaml 구성 파일은 힌트를 구성하기 위한 여러 설정을 제공해요.

표 1. 힌트 설정

설정 설명 기본값
hinted_handoff_enabled hinted handoff 활성화/비활성화 true
hinted_handoff_disabled_datacenters handoff가 활성화되어 있더라도 hinted handoff를 수행하지 않는 데이터센터 목록. 예: hinted_handoff_disabled_datacenters: - DC1 - DC2 unset
max_hint_window 노드가 실패한 후 힌트가 생성될 최대 시간을 정의 3h
hinted_handoff_throttle 전달 스레드당 초당 최대 제한(KiB). 클러스터의 노드 수에 비례해 줄어듦. (클러스터에 노드가 두 개면 각 전달 스레드가 최대 속도를 사용하고, 세 개면 각각 절반으로 줄어듦. 두 노드가 동시에 힌트를 전달할 것으로 예상되기 때문) 1024KiB
max_hints_delivery_threads 힌트를 전달할 스레드 수. 멀티-DC 배포에서는 크로스-DC handoff가 더 느리기 때문에 이 숫자를 늘리는 것을 고려 2
hints_directory Cassandra가 힌트를 저장하는 디렉터리 $CASSANDRA_HOME/data/hints
hints_flush_period 힌트를 내부 버퍼에서 디스크로 플러시할 빈도. fsync를 트리거하지 않음 10000ms
max_hints_file_size 단일 힌트 파일의 최대 크기(MB) 128MiB
hints_compression 힌트 파일에 적용할 압축. 생략하면 압축 없이 기록. LZ4, Snappy, Deflate 압축기 지원 LZ4Compressor

nodetool로 런타임에 힌트 구성

nodetool은 힌트를 구성하거나 힌트 관련 정보를 얻기 위한 여러 명령을 제공합니다. nodetool 명령은 명령을 실행하는 노드의 cassandra.yaml에 있는 해당 설정을 덮어씁니다.

표 2. 힌트용 Nodetool 명령

명령 설명
nodetool disablehandoff 힌트 저장 및 전달 비활성화
nodetool disablehintsfordc 데이터센터로의 힌트 저장 및 전달 비활성화
nodetool enablehandoff 현재 노드에서 향후 힌트 저장 및 전달 다시 활성화
nodetool enablehintsfordc 이전에 비활성화된 데이터센터에 힌트 활성화
nodetool getmaxhintwindow 최대 힌트 윈도우를 ms로 출력. Cassandra 4.0의 새 기능
nodetool handoffwindow 현재 hinted handoff 윈도우 출력
nodetool pausehandoff 힌트 전달 프로세스 일시 중지
nodetool resumehandoff 힌트 전달 프로세스 재개
nodetool sethintedhandoffthrottlekb 전달 스레드당 초당 hinted handoff 제한 설정(kb)
nodetool setmaxhintwindow 지정된 최대 힌트 윈도우 설정(ms)
nodetool statushandoff 현재 노드에 향후 힌트 저장 상태
nodetool truncatehints 로컬 노드의 모든 힌트를 잘라내거나, 지정된 엔드포인트의 힌트를 잘라냄

런타임에 힌트 재생을 더 빠르게

기본 1024kbps handoff 제한은 대부분의 현대 네트워크에 보수적이며, 단순한 노드 재시작에서도 재생에 몇 시간이 걸릴 수 있는 수 기가바이트의 힌트가 쌓일 수 있어요. 예를 들어 노드당 100Mbps 데이터를 수집한다면 10분짜리 재시작 하나는 10분 * (100Mbps) ≈ 7GiB의 데이터를 만들고, (1024KiB/초) 속도로 재생하려면 7.5GiB / (1024KiB/초) = 2.03시간이 걸립니다. 정확한 계산은 부하 분산 전략(라운드 로빈이 token-aware보다 나음), 노드당 토큰 수(토큰이 많을수록 나음), 그리고 당연히 클러스터의 쓰기 속도에 따라 달라지지만, 어쨌든 런타임에 이 제한을 늘리고 싶을 수 있어요.

이런 상황이라면 nodetool sethintedhandoffthrottlekb 명령으로 hinted_handoff_throttle을 동적으로 올리는 것을 고려해 보세요.

런타임에 노드가 더 오래 다운되도록 허용

때때로 노드가 정상적인 max_hint_window(기본 3시간)보다 오래 다운될 수 있지만 하드웨어와 데이터 자체는 여전히 접근 가능할 수 있어요. 이런 경우 Cassandra 4.0에서 추가된 nodetool setmaxhintwindow 명령(CASSANDRA-11720)으로 max_hint_window를 동적으로 올리는 것을 고려할 수 있습니다. 이는 Cassandra가 다운된 엔드포인트의 힌트를 더 오래 계속 보관하도록 지시해요.

이 명령은 힌트를 보유하고 있을 수 있는 클러스터의 모든 노드에 적용해야 합니다. 필요한 경우 cassandra.yamlmax_hint_window 설정을 설정하고 롤링 재시작을 수행해 이 설정을 영구적으로 적용할 수 있어요.

힌트 전달 모니터링

Cassandra 4.0은 힌트를 전달하는 데 걸리는 시간을 이해할 수 있는 히스토그램을 추가했으며, 운영자가 문제를 더 잘 식별하는 데 유용합니다(CASSANDRA-13234).

Hinted Handoff 및 Hints Service 메트릭을 추적하기 위한 메트릭도 있습니다.

더 알아보기 (Learn more)