사전 힌티드 핸드오프
패턴

힌티드 핸드오프

gabury1

데이터를 맡아야 할 노드가 잠깐 죽었을 때 다른 노드가 대신 받아 두는 방식입니다. 받아 둔 쪽은 원래 주인이 누구였는지를 쪽지처럼 함께 적어 둡니다. 주인이 살아나면 그 쪽지를 보고 데이터를 넘깁니다. 넘기고 나면 대신 들고 있던 것은 지워도 됩니다.

쉽고 빠른 이해

담당 노드가 잠깐 죽어도 쓰기를 실패로 돌려보내지 않는 방식입니다. 노드 A 에 저장됐어야 할 복제본을 노드 D 가 대신 받아 둡니다.

이게 없으면 노드 한 대가 잠깐 안 보이는 동안 그 몫의 쓰기가 전부 실패로 돌아갑니다.

  1. 담당 노드가 응답하지 않으면 다른 노드가 그 복제본을 대신 받습니다.
  2. 받아 둔 쪽은 원래 주인이 누구였는지를 함께 적어 두고, 자기 데이터와 섞지 않습니다.
  3. 원래 주인이 살아난 것을 알아채면 넘겨주고, 넘기고 나면 들고 있던 것을 지웁니다.

대가는 넘겨줄 때까지 데이터가 원래 있어야 할 자리에 없다는 것입니다. 노드가 자주 들고 나거나 장애가 길게 가면 이 방식만으로는 부족합니다.

상세

힌티드 핸드오프(hinted handoff)는 담당 노드가 잠깐 없다고 해서 쓰기를 실패로 돌려보내지 않기로 한 결정입니다. Dynamo 논문은 이 결정이 서는 자리를 먼저 깔아 둡니다. 논문은 Dynamo 가 전통적인 정족수 방식을 썼다면 서버 장애와 네트워크 분단 동안 쓸 수 없는 상태가 됐을 거라고 적습니다. 그래서 Dynamo 는 엄격한 정족수 멤버십을 강제하지 않고 느슨한 정족수(sloppy quorum)를 씁니다. 모든 읽기와 쓰기는 preference list 의 첫 N 개 정상 노드에서 수행됩니다. 논문은 그 N 개가 일관성 해싱 링을 걸으며 처음 만나는 첫 N 개 노드가 아닐 수도 있다고 적습니다.

쓰기 한 번을 따라가면 이렇게 됩니다. 노드 A 가 쓰기 도중 일시적으로 다운됐거나 도달 불가능하면, 원래 A 에 살았을 복제본이 이제 노드 D 로 보내집니다. D 로 간 복제본은 메타데이터에 힌트를 달고 갑니다. 그 힌트는 이 복제본을 원래 받기로 되어 있던 노드가 누구인지를 가리킵니다. 이 경우에는 A 입니다.

받아 둔 쪽은 그것을 자기 데이터와 섞지 않습니다. 논문은 힌트가 붙은 복제본을 받은 노드가 그것을 별도의 로컬 데이터베이스에 보관하고 주기적으로 훑는다고 적습니다. A 가 회복된 것을 감지하면 D 는 복제본을 A 에게 전달하려 시도합니다. 전달이 성공하면 D 는 시스템 전체의 복제본 수를 줄이지 않고 자기 로컬 저장소에서 그 객체를 지워도 됩니다. 논문은 이 방식으로 일시적인 노드 장애나 네트워크 장애 때문에 읽기와 쓰기가 실패하지 않도록 한다고 적습니다. 여기까지가 Dynamo 논문이 적은 왕복입니다.

sequenceDiagram
    participant CL as 클라이언트
    participant CO as 코디네이터
    participant D as 대신 받는 노드
    participant A as 담당 노드
    CL->>CO: 쓰기
    CO->>A: 복제본 전달 시도
    Note over A: 응답 없음
    CO->>D: 복제본 + 힌트
    CO-->>CL: 쓰기 성공
    Note over D: 별도 저장소에 보관
    D->>A: 회복을 감지하면 전달
    Note over D: 전달이 성공하면 지워도 됨

힌트를 누가 들고 있는지는 제품마다 다릅니다. Apache Cassandra 공식 문서는 힌팅을 쓰기 동작 중에 적용되는 데이터 복구 기법이라고 적습니다. 복제본 노드가 변경(mutation)을 받을 수 없으면, 그 노드에 쓰려던 코디네이터가 나중에 적용할 임시 힌트를 자기 로컬 파일시스템에 저장합니다. 사용 불가였던 복제본 노드가 링으로 돌아오면 코디네이터가 곧바로 힌트를 재생합니다. Dynamo 는 대신 받은 노드가 힌트를 들고 있고, Cassandra 는 코디네이터가 들고 있습니다. Cassandra 쪽 왕복은 이렇게 됩니다.

sequenceDiagram
    participant CL as 클라이언트
    participant CO as 코디네이터
    participant A as 담당 노드
    CL->>CO: 쓰기
    CO->>A: 변경 전달 시도
    Note over A: 응답 없음
    Note over CO: 자기 로컬 파일시스템에 힌트 저장
    CO-->>CL: 쓰기 성공
    Note over A: 링으로 복귀
    CO->>A: 밀린 힌트 재생

대가

쓰기를 받아들이는 대신 데이터가 원래 있어야 할 자리에 없습니다. A 에 살았어야 할 복제본이 D 에 있는 상태가 전달이 끝날 때까지 이어집니다.

이 방식이 잘 도는 조건이 원 논문에 못 박혀 있습니다. 논문은 힌티드 핸드오프가 시스템 멤버십 변동이 적고 노드 장애가 일시적일 때 가장 잘 동작한다고 적습니다. 조건을 뒤집으면 노드가 자주 들고 나는 클러스터나 장애가 길게 가는 클러스터에서는 이 결정만으로 부족하다는 뜻이 됩니다.

메워지는 정도에도 한정이 붙습니다. Cassandra 공식 문서는 힌트가 최선 노력(best effort)이며 anti-entropy 복구처럼 최종 일관성을 보장하지는 않는다고 적습니다. 같은 문서는 힌트가 읽기 복구와 마찬가지로 최선 노력이고 전체 복구를 대신하는 수단이 아니라고 적습니다. 다만 복제본 사이가 어긋나 있는 기간을 줄이는 데는 도움이 된다고 적습니다.

쓰기를 받아 준 값은 힌트를 저장하고 재생하는 일입니다. 그 일을 누가 떠맡는지는 제품마다 다릅니다. Cassandra 공식 문서는 받을 수 없는 복제본을 대신해 코디네이터가 힌트를 자기 로컬 파일시스템에 저장한다고 적습니다. 그 노드가 링으로 돌아오면 코디네이터가 곧바로 밀린 힌트를 재생합니다. Dynamo 논문 쪽은 대신 받은 노드가 힌트가 붙은 복제본을 별도의 로컬 데이터베이스에 두고 주기적으로 훑는다고 적습니다.

예시

Apache Cassandra

max_hint_window_in_ms: 10800000   # 3 hours
hints_directory: $CASSANDRA_HOME/data/hints

max_hint_window_in_ms 는 노드가 죽은 뒤 그 노드 몫의 힌트를 만들어 둘 최대 시간을 밀리초로 정합니다. 기본값은 10800000, 곧 3시간입니다. 공식 문서는 새 힌트가 다운타임 max_hint_window_in_ms 까지 보관된다고 적습니다. 그 창이 지나기 전에 노드가 클러스터로 돌아오면 코디네이터는 밀려 있던 힌트 변경을 그 복제본에 적용합니다. hints_directory 는 힌트를 쌓아 두는 디렉토리이고 기본값은 $CASSANDRA_HOME/data/hints 입니다.

굴리는 쪽 손잡이는 nodetool 에 있습니다.

nodetool statushandoff      # 이 노드가 앞으로 힌트를 저장할지의 상태
nodetool disablehandoff     # 힌트 저장과 전달을 끈다
nodetool truncatehints      # 로컬 노드의 힌트를 전부, 또는 지정한 엔드포인트의 힌트를 자른다
nodetool getmaxhintwindow   # 최대 힌트 창을 밀리초로 출력한다

nodetool getmaxhintwindow 는 Cassandra 4.0 에서 새로 생긴 명령입니다.

ScyllaDB

hinted_handoff_enabled: DC1,DC2

ScyllaDB 공식 문서는 이 설정이 힌티드 핸드오프를 켜고 끄는 자리라고 적습니다. 데이터센터 단위로 켜려면 값에 데이터센터 목록을 적습니다. 위 줄이 문서가 든 예입니다. 기본값은 전체 데이터센터에 대해 켜진 상태입니다. 같은 문서는 힌트가 사용 불가능한 노드로 쓰기를 재생해야 한다는 표시라고 적습니다.

Riak KV

Riak KV(Key Value, 키-값) 공식 문서는 클러스터 안에서 일어나는 핸드오프가 대개 두 형태 중 하나를 띤다고 적습니다. 하나가 힌티드 핸드오프이고 다른 하나가 소유권 이전입니다. 문서는 힌티드 핸드오프를 가상 노드(vnode)가 일시적으로 어떤 데이터의 책임을 넘겨받았다가 그 데이터를 원래 주인에게 돌려주는 일이라고 적습니다. 문서가 든 예는 노드 A, B, C 세 대짜리 클러스터입니다. 네트워크 분단 같은 이유로 노드 C 가 오프라인이 되면 노드 A 와 B 가 그 몫을 받습니다. 받는 단위가 노드가 아니라 가상 노드라는 점이 앞의 둘과 다릅니다.

실패

힌트가 끝내 안 닿는 경우가 있습니다. 조건이 붙어 있습니다.

조건 그때 벌어지는 일
Cassandra 에서 노드가 힌트 창 안에 돌아오지 않는다 대상 복제본은 읽기 복구나 전체 또는 증분 anti-entropy 복구가 그 변경을 전파할 때까지 영구히 어긋난 상태로 남습니다
다운타임이 max_hint_window_in_ms 를 넘긴다 새 힌트는 그 시간까지만 보관됩니다. 그 뒤로 쌓인 쓰기는 힌트로 메워지지 않습니다
힌트가 붙은 복제본이 원 주인에게 돌아가기 전에 사용 불가가 된다 Dynamo 논문은 그런 시나리오가 있다고 적고, 그 자리를 머클 트리 기반 anti-entropy 동기화로 넘깁니다

앞의 두 줄은 창이 닫히는 경우입니다. 세 번째 줄은 창 안에 있는데도 못 닿는 경우입니다. 논문이 4.7절에서 영구 장애를 따로 다루는 이유가 여기 있습니다. 힌티드 핸드오프는 일시적 장애까지를 맡고, 그 밖은 다른 장치가 받습니다.

여기서 갈리는 것은 쓰기의 성공 여부가 아닙니다. 쓰기는 이미 성공으로 돌아갔습니다. 갈리는 것은 그 값이 담당 노드에 언제 도착하느냐, 또는 끝내 도착하지 못하느냐입니다.

관련 항목

이것이 얹히는 복제 인프라

느슨한 정족수 · 정족수 · 복제 · 일관성 해싱 · preference list

이것에 관여하는 역할·참여자

노드 · 코디네이터 · 가상 노드

이것이 지키는 성질

가용성 · 최종 일관성 · 내구성(durability)

이것을 대신할 수 있는 다른 수단

anti-entropy 복구 · 읽기 복구 · 머클 트리

이 이름을 쓰는 저장소

Dynamo · Apache Cassandra · Riak KV · ScyllaDB

다른 이름: hinted handoff · 임시 위탁 · 힌티드핸드오프