읽기 리페어
읽기 리페어 (Read Repair)
Read Repair는 읽기 요청 중에 데이터 리플리카를 수리하는 과정이에요. 주어진 읽기 일관성 수준에서 읽기 요청에 관여하는 모든 리플리카가 일관적이면 데이터가 클라이언트에 반환되고 읽기 리페어가 필요 없습니다. 하지만 주어진 일관성 수준에서 읽기 요청에 관여하는 리플리카가 일관적이지 않다면, 읽기 요청에 관여하는 리플리카를 일관되게 만들기 위해 읽기 리페어가 수행됩니다. 가장 최신 데이터가 클라이언트에 반환됩니다. 읽기 리페어는 포그라운드에서 실행되며, 읽기 리페어가 완료되고 최신 데이터가 구성될 때까지 클라이언트에 응답이 반환되지 않는다는 점에서 차단(blocking) 방식입니다.
출처: 문서
본문
단조 쿼럼 읽기(Monotonic Quorum Reads)의 기대
Cassandra는 차단 읽기 리페어를 사용해 "단조 쿼럼 읽기"의 기대를 보장합니다. 즉 2번의 연속 쿼럼 읽기에서 두 번째 읽기가 첫 번째 것보다 더 오래된 것을 반환하지 않는다는 것을 보장해요. 이는 실패한 쿼럼 쓰기가 최신 값을 리플리카의 소수에만 썼더라도 마찬가지입니다. "쿼럼(Quorum)"은 리플리카 중 과반수 노드를 의미해요.
단조 읽기의 테이블 수준 구성
Cassandra 4.0은 단조 읽기의 테이블 수준 구성을 지원합니다(CASSANDRA-14635). read_repair 테이블 옵션이 테이블 스키마에 추가되었고, blocking(기본값)과 none 옵션이 있습니다.
read_repair 옵션은 다양한 성능과 일관성 동작을 조정할 수 있도록 읽기 리페어 동작을 구성합니다. 읽기 리페어 동작에 영향을 받는 두 가지 일관성 속성이 있어요.
- Monotonic Quorum Reads:
BLOCKING이 제공합니다. 단조 쿼럼 읽기는 어떤 상황에서 읽기가 시간상 뒤로 이동하는 것처럼 보이는 것을 막아요. 단조 쿼럼 읽기가 제공되지 않고 쓰기가 리플리카 쿼럼에 도달하지 못하면, 그 쓰기가 한 읽기에서는 보이다가 이후 읽기에서는 사라질 수 있습니다. - Write Atomicity(쓰기 원자성):
NONE이 제공합니다. 쓰기 원자성은 읽기가 부분적으로 적용된 쓰기를 반환하는 것을 막아요. Cassandra는 파티션 수준 쓰기 원자성을 제공하려 시도하지만, SELECT 문에 포함된 데이터만 읽기 리페어로 수리되므로, 데이터를 쓰는 것보다 더 세분화된 수준으로 읽으면 읽기 리페어가 쓰기 원자성을 깨뜨릴 수 있어요. 예를 들어 클러스터된 파티션에 여러 행을 배치로 썼는데 SELECT 문에서 클러스터링 컬럼을 지정해 단일 행을 선택한다면 읽기 리페어가 쓰기 원자성을 깨뜨릴 수 있습니다.
사용 가능한 read repair 설정은 다음과 같아요:
Blocking
기본 설정이에요. read_repair를 BLOCKING으로 설정하면 읽기 리페어가 시작될 때, 쓰기가 CL에 도달할 때까지 읽기가 다른 리플리카로 보내는 쓰기에 대해 차단됩니다. 단조 쿼럼 읽기를 제공하지만 파티션 수준 쓰기 원자성은 제공하지 않아요.
None
read_repair를 NONE으로 설정하면 조정자가 리플리카 간의 차이를 조정(reconcile)하지만, 수리하려 시도하지는 않아요. 파티션 수준 쓰기 원자성을 제공하지만 단조 쿼럼 읽기는 제공하지 않습니다.
read_repair 옵션의 NONE 설정을 사용하는 예는 다음과 같아요:
CREATE TABLE ks.tbl (k INT, c INT, v INT, PRIMARY KEY (k,c)) with read_repair='NONE');
읽기 리페어 예제
예제로 읽기 리페어를 설명하면, 클라이언트가 읽기 일관성 수준 TWO로 5노드 클러스터에 읽기 요청을 보낸다고 가정해 봅시다(그림 1 참고). 읽기 일관성 수준은 읽기 요청이 성공으로 간주되기 전에 몇 개의 리플리카 노드가 응답해야 하는지를 결정합니다.
그림 1. 클라이언트가 5노드 클러스터로 읽기 요청을 보냄
세 노드가 요청된 데이터의 리플리카를 호스팅합니다(그림 2 참고). 읽기 일관성 수준 TWO로는 읽기 요청이 성공으로 간주되기 위해 두 리플리카 노드가 응답해야 해요. 클라이언트가 요청을 보내는 노드가 요청된 데이터의 리플리카를 호스팅하면, 다른 리플리카 노드 하나에만 읽기 요청을 보내면 됩니다. 하지만 수신 노드가 요청된 데이터의 리플리카를 호스팅하지 않으면 그 노드는 조정자(coordinator)가 되어 리플리카를 호스팅하는 노드로 읽기 요청을 전달해요. 다이나믹 스니치가 결정하는 가장 빠른 노드에 직접 읽기(direct read) 요청이 전달됩니다(그림 2 참고). 직접 읽기 요청은 전체 읽기(full read)이며 요청된 데이터를 반환합니다.
그림 2. 가장 빠른 리플리카 노드로 보내는 직접 읽기 요청
다음으로 조정자 노드는 일관성 수준(TWO)을 충족하기 위해 필요한 수의 추가 요청을 보냅니다. 조정자 노드는 총 두 개가 되도록 읽기 요청 하나를 더 보내야 해요. 첫 번째 직접 읽기 요청 이후의 모든 추가 읽기 요청은 다이제스트 읽기(digest read) 요청입니다. 다이제스트 읽기 요청은 전체 읽기가 아니고 데이터의 해시 값만 반환합니다. 네트워크 데이터 트래픽을 줄이기 위해 해시 값만 반환돼요. 논의 중인 예제에서 조정자 노드는 리플리카를 호스팅하는 노드 하나에 다이제스트 읽기 요청 하나를 보냅니다(그림 3 참고).
그림 3. 조정자가 다이제스트 읽기 요청을 보냄
조정자 노드는 한 노드에서 데이터 전체 사본을, 다른 노드에서 데이터의 해시 값을 받았습니다. 반환된 데이터를 비교하기 위해 전체 데이터 사본에 대한 해시 값이 계산됩니다. 두 해시 값이 비교됩니다. 해시 값이 같으면 읽기 리페어가 필요 없고 요청된 데이터의 전체 사본이 클라이언트에 반환됩니다. 예제의 읽기 일관성 수준이 TWO이므로 조정자 노드는 총 두 개의 리플리카 읽기 요청만 수행했어요. 일관성 수준이 THREE처럼 더 높았다면 세 리플리카 노드가 읽기 요청에 응답해야 하고, 모든 다이제스트/해시 값이 데이터 전체 사본의 해시 값과 일치해야만 읽기 요청이 성공으로 간주되고 데이터가 클라이언트에 반환될 것입니다.
하지만 다이제스트 읽기 요청의 해시 값이 첫 번째 리플리카 노드의 전체 읽기 요청 데이터 해시 값과 같지 않다면 리플리카에 불일치가 존재한다는 뜻입니다. 불일치를 해결하기 위해 읽기 리페어가 수행됩니다.
예를 들어 다이제스트 요청이 직접 전체 읽기 요청의 데이터 해시 값과 같지 않은 해시 값을 반환한다고 가정해 봅시다. 리플리카를 일관되게 만들어야 하므로 조정자 노드는 앞서 다이제스트 읽기 요청을 보냈던 리플리카 노드에 직접(전체) 읽기 요청을 보냅니다(그림 4 참고).
그림 4. 조정자가 다이제스트 읽기 요청을 보냈던 리플리카 노드로 직접 읽기 요청을 보냄
두 번째 리플리카 노드에서 데이터를 받은 후 조정자는 두 리플리카 노드의 데이터를 갖게 됩니다. 예제의 읽기 일관성 수준이 TWO이므로 두 리플리카만 필요해요. 두 리플리카의 데이터를 비교하고 타임스탬프에 기반해 가장 최근 리플리카를 선택합니다. 한 리플리카가 일부 컬럼의 데이터만 갖고 있다면 최신 데이터 사본을 구성하기 위해 데이터를 병합해야 할 수 있어요. 예에서 첫 번째 직접 읽기 요청의 데이터가 오래된 것으로, 두 번째 전체 읽기 요청의 데이터가 최신으로 판명되면 Replica 2에 리페어를 수행해야 합니다. 두 리플리카를 병합해 새 최신 데이터를 구성한다면 관여한 두 리플리카 모두에 읽기 리페어가 필요할 수 있어요. 예를 들어 그림 5처럼 Replica 2에 읽기 리페어가 수행됩니다.
그림 5. 조정자가 읽기 리페어 수행
그림 6처럼 가장 최신 데이터가 클라이언트에 반환됩니다. 세 리플리카 중 Replica 1은 아예 읽히지 않아 수리되지 않아요. Replica 2는 수리됩니다. Replica 3이 가장 최신이며 클라이언트에 반환됩니다.
그림 6. 클라이언트에 반환되는 가장 최신 데이터
읽기 일관성 수준과 읽기 리페어
읽기 리페어를 수행해야 하는지 결정하는 데 읽기 일관성이 가장 중요해요. 표 1에서 논의한 대로 모든 일관성 수준에 읽기 리페어가 필요한 것은 아닙니다.
표 1. 읽기 일관성 수준에 따른 읽기 리페어
| 읽기 일관성 수준 | 설명 |
|---|---|
| ONE | 읽기 리페어가 수행되지 않음. 첫 번째 직접 읽기 요청의 데이터가 일관성 수준 ONE을 충족하기 때문. 데이터 불일치를 찾기 위한 다이제스트 읽기 요청이 관여하지 않음 |
| TWO | 직접 및 다이제스트 읽기 요청으로 확인된 데이터 불일치가 발견되면 읽기 리페어 수행 |
| THREE | 직접 및 다이제스트 읽기 요청으로 확인된 데이터 불일치가 발견되면 읽기 리페어 수행 |
| LOCAL_ONE | 읽기 리페어가 수행되지 않음. 가장 가까운 리플리카의 직접 읽기 요청 데이터가 일관성 수준 LOCAL_ONE을 충족하기 때문. 데이터 불일치를 찾기 위한 다이제스트 읽기 요청이 관여하지 않음 |
| LOCAL_QUORUM | 직접 및 다이제스트 읽기 요청으로 확인된 데이터 불일치가 발견되면 읽기 리페어 수행 |
| QUORUM | 직접 및 다이제스트 읽기 요청으로 확인된 데이터 불일치가 발견되면 읽기 리페어 수행 |
읽기 리페어를 수행한다면 그것은 최신 상태가 아니면서 읽기 요청에 관여한 리플리카에 대해서만 이루어집니다. 읽기 요청에 관여하는 리플리카 수는 읽기 일관성 수준에 기반하며, 예제에서는 두 개입니다.
Cassandra 4.0의 개선된 읽기 리페어 차단 동작
Cassandra 4.0은 읽기 리페어 차단 동작에 두 가지 개선을 합니다(CASSANDRA-10726).
- 전체 데이터 읽기 요청의 추측 재시도(Speculative Retry). Cassandra 4.0은 전체 데이터 응답을 받지 못하면(초기 전체 읽기 요청이든 읽기 리페어 중 전체 데이터 읽기 요청이든) 리플리카에 읽기 요청(full, not digest)을 보낼 때 추측 재시도를 사용합니다. 추측 재시도로 일관성 수준을 충족하기 위해 Cassandra가 메시지를 보냈던 초기 리플리카 집합에서 응답을 받지 못할 것 같으면, 접촉하지 않은 리플리카에 추가 읽기 요청을 추측적으로 보냅니다. Cassandra 4.0은 또한 누군가 응답하지 않을 것 같으면, 승인되지 않은 모든 변경의 결합된 내용으로 읽기 리페어 데이터 읽기/쓰기 주기에 관여하지 않은 소수 노드에 수리 변경(repair mutation)을 추측적으로 보냅니다. Cassandra는 전송한 수리 변경 수와 동일한 수의 승인을 받는 한, 초기에 보낸 변경 대신 그들로부터 승인을 받아들입니다.
- 일관성 수준을 충족하기 위해 전체 데이터 응답에만 차단. Cassandra 4.0은 다이제스트 불일치 해결을 위해 필요한 것에 대해서만 차단하고, 추측 재시도든 읽기 리페어 기회든 일관성 수준을 충족하기 위해 충분한 전체 데이터 응답을 기다립니다. 예를 들어 Cassandra가 모든 사람에게서 전체 데이터 요청을 제때 받지 못할 것 같으면 초기 전체 데이터 읽기에서 접촉하지 않은 추가 리플리카에 추가 요청을 보냅니다. 결국 제때 응답하는 노드 집합이 데이터에 대해 동의한다면, 읽기 리페어를 시작한 불일치 리플리카의 응답은 고려되지 않고 클라이언트 응답에도 포함되지 않아 단조 쿼럼 읽기의 기대를 보존합니다.
읽기 리페어의 진단 이벤트
Cassandra 4.0은 읽기 리페어에 대한 진단 이벤트(CASSANDRA-14668)를 추가하며, 다음과 같은 정보를 노출하는 데 사용할 수 있어요:
- 접촉한 엔드포인트
- 엔드포인트별 다이제스트 응답
- 영향을 받은 파티션 키
- 추측된 읽기/쓰기
- oversized 업데이트
백그라운드 읽기 리페어
cassandra.yaml의 read_repair_chance와 dclocal_read_repair_chance 설정으로 구성되던 백그라운드 읽기 리페어는 Cassandra 4.0에서 제거되었습니다(CASSANDRA-13910).
읽기 리페어는 전체 리페어(full repair)나 계속 실패하는 노드 교체 같은 다른 종류의 리페어의 대안이 아니에요. 읽기 리페어가 수행된 후에도 반환되는 데이터는, 일관성 수준이 모든 리플리카의 응답을 요구하는 것이 아니라면 가장 최신 데이터가 아닐 수 있습니다.
더 알아보기 (Learn more)
- 리페어 — 전체/증분 anti-entropy 리페어
- 데이터 정의(DDL) — read_repair 테이블 옵션