리페어

리페어 (Repair)

Cassandra는 노드 중 하나가 다운되거나 접근 불가능해도 계속 사용 가능하도록 설계됐어요. 하지만 노드가 다운되거나 접근 불가능할 때, 그 노드는 결국 놓친 쓰기를 발견해야 합니다. 힌트(hints)는 노드에 놓친 쓰기를 알려주려 시도하지만 best-effort이며, 노드가 놓친 쓰기의 100%를 알려주는 것은 보장되지 않아요. 이러한 불일치는 노드가 교체되거나 툼스톤이 만료됨에 따라 결국 데이터 손실로 이어질 수 있습니다.

이러한 불일치는 리페어(repair) 프로세스로 해결됩니다. 리페어는 공통 토큰 범위에 대한 각자의 데이터셋을 비교하고, 노드 간 동기화되지 않은 섹션에 대한 차이를 스트리밍함으로써 노드 간 데이터를 동기화해요. 데이터는 해시 계층 구조인 Merkle tree로 비교합니다.

출처: 문서

본문

증분 및 전체 리페어

리페어에는 전체 리페어(full repair)와 증분 리페어(incremental repair) 두 가지 유형이 있어요. 전체 리페어는 리페어되는 토큰 범위의 모든 데이터에 대해 동작합니다. 증분 리페어는 이전 증분 리페어 이후에 쓰인 데이터만 리페어해요.

증분 리페어가 기본 리페어 유형이며, 정기적으로 실행하면 리페어의 시간과 I/O 비용을 크게 줄일 수 있어요. 하지만 증분 리페어가 데이터를 '리페어됨'으로 표시하면 다시 리페어하려 하지 않는다는 점을 이해하는 것이 중요합니다. 이는 놓친 쓰기를 동기화하는 데는 좋지만, 디스크 손상, 운영자 오류로 인한 데이터 손실, Cassandra의 버그 같은 것은 보호하지 못해요. 이런 이유로 전체 리페어도 가끔 실행해야 합니다.

사용법과 모범 사례

리페어는 디스크와 네트워크 I/O가 많이 발생할 수 있으므로 Cassandra가 자동으로 실행하지 않아요. 운영자가 nodetool로 실행합니다.

증분 리페어가 기본이며 다음 명령으로 실행됩니다:

nodetool repair

전체 리페어는 다음 명령으로 실행할 수 있어요:

nodetool repair --full

추가로 단일 키스페이스에 리페어를 실행할 수 있습니다:

nodetool repair [options] <keyspace_name>

또는 특정 테이블에만:

nodetool repair [options] <keyspace_name> <table1> <table2>

리페어 명령은 리페어되는 노드의 토큰 범위만 리페어하며, 전체 클러스터를 리페어하지는 않아요. 기본적으로 리페어는 리페어가 실행되는 노드가 복제하는 모든 토큰 범위에 대해 동작하므로, 매 노드에서 실행하면 중복 작업이 발생합니다. -pr 플래그를 사용해 노드의 "기본(primary)" 범위만 리페어함으로써 중복 작업을 피하세요. 클러스터의 모든 노드와 데이터센터가 리페어될 때까지 각 데이터센터의 각 노드에서 nodetool repair -pr 명령을 실행해 전체 클러스터 리페어를 수행하세요.

클러스터에 적합한 구체적인 리페어 빈도는 당연히 여러 요인에 따라 달라져요. 하지만 시작하는 단계이고 어디서부터 시작할지 찾고 있다면, 1-3일마다 증분 리페어를, 1-3주마다 전체 리페어를 실행하는 것이 합리적일 것입니다. 증분 리페어를 원하지 않는다면 5일마다 전체 리페어가 좋은 시작점입니다.

최소한 리페어는 gc grace period가 리페어되지 않은 데이터에서 만료되지 않을 만큼 자주 실행해야 해요. 그렇지 않으면 삭제된 데이터가 다시 나타날 수 있습니다. 기본 gc grace period가 10일이므로, 클러스터의 모든 노드를 최소 7일마다 리페어하면 지연을 허용할 충분한 여유를 제공하면서 이를 방지합니다.

기타 옵션

-pr, --partitioner-range 리페어되는 노드의 '기본' 토큰 범위로 리페어를 제한합니다. 기본 범위는 노드가 링의 첫 번째 리플리카인 토큰 범위에요.

-prv, --preview 주어진 리페어 명령에서 발생할 스트리밍 양을 추정합니다. Merkle tree를 만들고 예상 스트리밍 활동을 출력하지만 실제 스트리밍은 하지 않아요. 기본적으로 증분 리페어가 추정되며, 전체 리페어를 추정하려면 --full 플래그를 추가하세요.

-vd, --validate 리페어된 데이터가 모든 노드에서 동일한지 검증합니다. --preview와 유사하게 리페어된 데이터의 Merkle tree를 만들고 비교하지만 스트리밍은 하지 않아요. 문제 해결에 유용합니다. 이 검사가 리페어된 데이터가 동기화되지 않았음을 보여주면 전체 리페어를 실행해야 합니다.

nodetool repair 문서 참고.

전체 리페어 예제

전체 리페어는 보통 키스페이스의 복제 계수를 늘리거나 클러스터에 노드를 추가한 뒤 데이터를 재분배하기 위해 필요합니다. 전체 리페어는 SSTable 스트리밍을 포함해요. 전체 리페어를 시연하기 위해 세 노드 클러스터로 시작합니다.

[ec2-user@ip-10-0-2-238 ~]$ nodetool status
Datacenter: us-east-1
=====================
Status=Up/Down
|/ State=Normal/Leaving/Joining/Moving
--  Address   Load        Tokens  Owns  Host ID                              Rack
UN  10.0.1.115  547 KiB     256    ?  b64cb32a-b32a-46b4-9eeb-e123fa8fc287  us-east-1b
UN  10.0.3.206  617.91 KiB  256    ?  74863177-684b-45f4-99f7-d1006625dc9e  us-east-1d
UN  10.0.2.238  670.26 KiB  256    ?  4dcdadd2-41f9-4f34-9892-1f20868b27c7  us-east-1c

복제 계수 3으로 키스페이스를 만듭니다:

cqlsh> DROP KEYSPACE cqlkeyspace;
cqlsh> CREATE KEYSPACE CQLKeyspace
  ... WITH replication = {'class': 'SimpleStrategy', 'replication_factor' : 3};

키스페이스에 테이블을 추가합니다:

cqlsh> use cqlkeyspace;
cqlsh:cqlkeyspace> CREATE TABLE t (
           ...   id int,
           ...   k int,
           ...   v text,
           ...   PRIMARY KEY (id)
           ... );

테이블 데이터를 추가합니다:

cqlsh:cqlkeyspace> INSERT INTO t (id, k, v) VALUES (0, 0, 'val0');
cqlsh:cqlkeyspace> INSERT INTO t (id, k, v) VALUES (1, 1, 'val1');
cqlsh:cqlkeyspace> INSERT INTO t (id, k, v) VALUES (2, 2, 'val2');

쿼리가 추가된 데이터를 나열합니다:

cqlsh:cqlkeyspace> SELECT * FROM t;

id | k | v
----+---+------
 1 | 1 | val1
 0 | 0 | val0
 2 | 2 | val2
(3 rows)

세 노드 클러스터에 다음 변경을 합니다:

  • 복제 계수를 3에서 4로 증가
  • 4번째 노드를 클러스터에 추가

복제 계수가 증가하면 (CASSANDRA-13079)에 따라 전체 리페어가 필요하다는 다음 메시지가 출력됩니다:

cqlsh:cqlkeyspace> ALTER KEYSPACE CQLKeyspace
           ... WITH replication = {'class': 'SimpleStrategy', 'replication_factor' : 4};
Warnings :
When increasing replication factor you need to run a full (-full) repair to distribute the
data.

다음 명령으로 키스페이스 cqlkeyspace 테이블 t에 전체 리페어를 수행합니다:

nodetool repair -full cqlkeyspace t

전체 리페어는 출력이 나타내듯 약 1초 만에 완료됩니다:

[ec2-user@ip-10-0-2-238 ~]$ nodetool repair -full cqlkeyspace t
[2019-08-17 03:06:21,445] Starting repair command #1 (fd576da0-c09b-11e9-b00c-1520e8c38f00), repairing keyspace cqlkeyspace with repair options (parallelism: parallel, primary range: false, incremental: false, job threads: 1, ColumnFamilies: [t], dataCenters: [], hosts: [], previewKind: NONE, # of ranges: 1024, pull repair: false, force repair: false, optimise streams: false)
[2019-08-17 03:06:23,059] Repair session fd8e5c20-c09b-11e9-b00c-1520e8c38f00 for range [(-8792657144775336505,-8786320730900698730], (-5454146041421260303,-5439402053041523135], (4288357893651763201,4324309707046452322], ... , (4350676211955643098,4351706629422088296]] finished (progress: 0%)
[2019-08-17 03:06:23,077] Repair completed successfully
[2019-08-17 03:06:23,077] Repair command #1 finished in 1 second
[ec2-user@ip-10-0-2-238 ~]$

nodetool tpstats 명령은 Repair-Task > Completed 컬럼 값이 1인 완료된 리페어를 나열해야 합니다:

[ec2-user@ip-10-0-2-238 ~]$ nodetool tpstats
Pool Name Active   Pending Completed   Blocked  All time blocked
ReadStage  0           0           99       0              0
…
Repair-Task 0       0           1        0              0
RequestResponseStage                  0        0        2078        0               0

더 알아보기 (Learn more)