레플리카 샤드
고친 사람 github-actions[bot]
레플리카 샤드는 데이터 조각 하나를 다른 서버에 한 벌 더 둡니다. 원본을 든 서버가 죽어도 그 조각의 데이터를 잃지 않게 합니다. 평소에는 검색 요청을 나눠 받아 읽는 부담도 덜어 줍니다. 원본 조각이 받은 쓰기를 뒤따라 받아서 원본과 같은 내용을 지닙니다.
쉽고 빠른 이해
레플리카 샤드는 원본 조각의 복사본을 다른 서버에 두는 일을 합니다. 데이터를 조각 셋으로 나눈다고 해 봅시다. 조각마다 복사본을 하나씩 두면 서버들 위에 원본 셋과 복사본 셋이 흩어져 놓입니다.
복사본이 없으면 서버 한 대가 죽을 때 그 서버가 들고 있던 조각의 문서를 찾을 수 없습니다.
어떻게 도나:
- 문서를 쓰는 요청은 원본 조각이 먼저 받습니다
- 원본이 같은 쓰기를 복사본들에 보냅니다. 복사본까지 적은 뒤에 성공을 돌려줍니다
- 원본이 든 서버가 죽으면 복사본 하나가 새 원본이 되어 일을 이어 갑니다
대가는 디스크와 쓰기 일이 복사본 수만큼 늘어난다는 것입니다. 복사본은 원본과 다른 서버에 둬야 합니다. 서버 수가 모자라면 복사본은 놓일 서버가 없어 놓이지 못한 채 남습니다.
상세
중요한 서류는 복사해서 다른 건물에 한 부 더 둡니다. 한 건물에 불이 나도 다른 건물의 서류로 일을 이어 갑니다. 평소에는 두 건물에서 같은 서류를 따로 열람할 수 있습니다.
레플리카 샤드를 알려면 먼저 무엇의 복사본인지를 가려야 합니다. 아래에서는 샤드와 프라이머리 샤드부터 세웁니다. 이어서 복사본을 원본과 다른 노드에 두는 규칙을 봅니다. 그다음 복사본 수에 따라 노드가 몇 대 있어야 하는지 셉니다. 쓰기와 읽기가 어떤 순서로 도는지는 「동작」 절이, 설정 이름과 기본값은 「운영」 절이 받습니다.
샤드와 프라이머리 샤드
데이터가 서버 한 대에 다 안 들어가면 문서를 건수로 나눠 여러 서버에 흩어 둡니다. 이렇게 나눈 조각 하나가 샤드입니다.
노드는 샤드를 들고 있는 서버 하나입니다. 노드 한 대가 여러 샤드를 함께 들기도 합니다.
노드들을 한데 묶은 무리는 클러스터입니다. 어느 샤드를 어느 노드에 둘지는 클러스터가 정합니다.
검색 엔진에서 인덱스는 문서를 담는 묶음 하나입니다. 관계형 데이터베이스의 테이블에 가까운 단위입니다. 인덱스 하나가 샤드 여럿으로 나뉩니다.
조각마다 원본이 하나씩 있습니다. 이 원본 조각이 프라이머리 샤드입니다. 주 샤드라고도 부릅니다.
레플리카 샤드는 프라이머리 샤드 하나를 빠짐없이 베낀 복사본입니다. 원본이 담은 문서를 전부 담습니다. 원본과 똑같이 검색할 수도 있습니다.
이 글에서 원본이라고 하면 프라이머리 샤드를, 복사본이라고 하면 레플리카 샤드를 가리킵니다. 조각 하나의 원본과 복사본을 한데 셀 때는 몇 벌이라고 셉니다.
같은 데이터를 여러 곳에 베껴 두는 일이 복제입니다. 레플리카(replica)라는 이름도 복제본이라는 뜻입니다. 샤드가 데이터를 나누는 축이라면, 레플리카 샤드는 나눈 조각을 한 벌씩 더 두는 축입니다.
원본과 다른 노드에 두는 까닭
레플리카 샤드는 자기 프라이머리 샤드와 같은 노드에 놓이지 않습니다. 둘이 한 노드에 같이 있으면 그 노드가 죽을 때 원본과 복사본이 함께 사라집니다. 복사본을 둔 까닭이 없어집니다.
아래 그림은 프라이머리 샤드 셋에 복사본을 하나씩 두고 노드 셋에 나눠 놓은 모습입니다. 노드마다 어느 조각의 원본과 다른 조각의 복사본을 섞어 듭니다.
flowchart TD
subgraph N1["노드 1"]
P0["프라이머리 샤드 0"]
R2["레플리카 샤드 2"]
end
subgraph N2["노드 2"]
P1["프라이머리 샤드 1"]
R0["레플리카 샤드 0"]
end
subgraph N3["노드 3"]
P2["프라이머리 샤드 2"]
R1["레플리카 샤드 1"]
end
P0 -.베낀다.-> R0
P1 -.베낀다.-> R1
P2 -.베낀다.-> R2
어느 노드 하나를 지워 봐도 조각 0, 1, 2 가 다른 노드에 한 벌씩 남습니다. 노드 1 이 죽으면 조각 0 은 노드 2 의 복사본으로, 조각 2 는 노드 3 의 원본으로 남습니다.
복사본 수와 노드 수
레플리카 수는 프라이머리 샤드 하나마다 두는 복사본의 수입니다. 레플리카 수가 1 이면 조각마다 원본과 복사본, 모두 두 벌이 생깁니다.
아래 표는 프라이머리 샤드가 셋인 인덱스에서 레플리카 수만 바꿔 본 것입니다.
| 레플리카 수 | 조각마다 벌 수 | 전체 샤드 수 | 벌을 다 놓는 데 필요한 노드 |
|---|---|---|---|
| 0 | 1 | 3 | 1 |
| 1 | 2 | 6 | 2 |
| 2 | 3 | 9 | 3 |
같은 조각의 벌들은 서로 다른 노드에 놓여야 하므로, 벌 수만큼 노드가 있어야 복사본이 전부 놓입니다. 노드가 모자라면 남는 복사본은 놓일 노드가 없어 배정되지 않은 채 남습니다. 이런 샤드가 미할당 샤드입니다.
벌이 모두 놓였다면 벌 수보다 하나 적은 수의 노드를 잃어도 조각마다 한 벌이 남습니다. 레플리카 수가 1 이면 노드 한 대, 2 면 두 대까지입니다.
이 셈은 데이터가 남는지만 따진 것입니다. 클러스터가 요청을 계속 받는지는 어느 노드가 클러스터를 지휘할지 뽑는 일에도 걸립니다. 그 조건은 「운영」 절에서 봅니다.
쓰기는 원본이 먼저 받는다
문서를 넣거나 고치거나 지우는 요청은 프라이머리 샤드가 먼저 받습니다. 원본은 자기 쪽에 먼저 적습니다. 그다음 같은 쓰기를 복사본들에 보냅니다.
검색 엔진은 문서를 넣을 때 나중에 찾을 수 있게 정리해서 적습니다. 이 처리가 색인(indexing)입니다. 이름이 비슷하지만 인덱스는 문서를 담는 묶음입니다. 색인은 그 묶음에 문서를 적어 넣는 일입니다.
쓰기가 늘 원본 한 곳을 거쳐 가므로 쓰기의 순서가 하나로 정해집니다. 원본과 복사본이 쓰기를 따로 받는다면 같은 문서를 고치는 두 요청이 서로 다른 순서로 적힐 수 있습니다. 그러면 원본과 복사본의 내용이 갈라집니다.
쓰기는 원본 하나만 받습니다. 나머지는 원본을 따라 적습니다. 이 방식이 주-백업 복제(primary-backup replication)입니다.
복사본이 맡는 두 가지 일
복사본은 원본과 같은 문서를 담으므로 두 가지 일을 맡습니다.
첫째는 데이터를 지키는 일입니다. 원본이 든 노드가 죽으면 복사본 하나가 원본 역할을 넘겨받습니다. 복사본이 원본으로 올라서는 이 일이 승격입니다.
둘째는 읽기를 나눠 받는 일입니다. 검색이나 문서 한 건 조회는 원본과 복사본 가운데 어느 쪽이 받아도 됩니다. 복사본이 늘면 같은 조각의 읽기를 받아 줄 벌도 그만큼 늘어납니다.
배경
데이터를 샤드로 나눠 여러 노드에 흩어 두면 노드 한 대가 감당하지 못할 크기도 담을 수 있습니다. 대신 노드 한 대가 죽으면 그 노드가 든 샤드의 문서가 한꺼번에 사라집니다. 노드가 많을수록 그중 한 대가 죽는 일도 잦아집니다.
샤드 하나가 빠지면 검색 결과에서 그 몫의 문서가 빠집니다. 그 샤드로 가야 할 쓰기도 받을 곳이 없습니다. 디스크를 백업해 두었더라도 다시 올릴 때까지는 그 몫을 못 씁니다.
샤드마다 복사본을 다른 노드에 미리 띄워 둘 필요가 여기서 생겼습니다. 복사본은 원본이 받는 쓰기를 늘 같이 받습니다. 그러면 원본이 사라져도 복사본이 이어받습니다. 같은 문서를 든 복사본은 읽기 요청도 나눠 받습니다. 샤드 단위로 둔 이 복사본이 레플리카 샤드입니다.
예시
아래는 레플리카 샤드가 나타나는 제품 넷입니다. 레플리카 샤드라는 이름은 Elasticsearch 와 OpenSearch 가 씁니다. SolrCloud 는 같은 구조를 다른 낱말로 부릅니다. MongoDB 는 조각마다 복사본을 두지만 서버를 묶는 단위가 다릅니다.
Elasticsearch
Elasticsearch 는 인덱스 설정 index.number_of_replicas 로 프라이머리 샤드 하나마다 둘 복사본 수를
정합니다. 기본값은 1 입니다. 프라이머리 샤드 수는 index.number_of_shards 로 정합니다. 이 값의
기본값도 1 입니다.
이미 있는 인덱스의 복사본 수는 인덱스 설정을 바꾸는 요청으로 고칩니다.
PUT /my-index-000001/_settings
{
"index" : {
"number_of_replicas" : 2
}
}
my-index-000001 인덱스의 프라이머리 샤드마다 복사본이 두 벌씩 생깁니다. 원본까지 치면 조각마다
세 벌입니다. index.number_of_replicas 는 인덱스가 도는 중에 바꿀 수 있는 동적 설정입니다.
복사본이 배정됐는지는 클러스터 헬스가 색으로 보여 줍니다. 인덱스의 색은 그 인덱스의 샤드 가운데 가장 심각한 색을 따릅니다.
| 색 | 뜻 |
|---|---|
| green | 모든 샤드가 배정됐다 |
| yellow | 프라이머리 샤드는 전부 배정됐다. 레플리카 샤드 가운데 배정 안 된 것이 있다. 노드 하나가 죽으면 그 노드를 고칠 때까지 일부 데이터를 못 쓸 수 있다 |
| red | 배정 안 된 프라이머리 샤드가 있다. 일부 데이터를 쓸 수 없다. 클러스터가 막 시작해 프라이머리를 배정하는 동안 잠깐 뜨기도 한다 |
Elasticsearch 는 복사본을 자기 원본과 같은 노드에 배정하지 않습니다. 이 규칙 때문에 노드 하나로 띄운 클러스터는 기본값 그대로면 늘 yellow 입니다. 모든 인덱스의 레플리카 수를 0 으로 내리면 green 이 됩니다. 대신 복사본 없이 도니 장애를 견디는 힘과 복구할 수단이 줄어듭니다.
OpenSearch
OpenSearch 도 index.number_of_replicas 와 index.number_of_shards 를 씁니다. 레플리카 수를 적지
않으면 클러스터 설정 cluster.default_number_of_replicas 를 따릅니다. 그 기본값은 1 입니다.
프라이머리 샤드가 넷인 인덱스에 레플리카 수를 3 으로 두면 레플리카 샤드는 12 개가 됩니다.
다른 점은 복사본이 쓰기를 받아 적는 방식을 인덱스마다 고른다는 것입니다. 인덱스 설정
index.replication.type 이 그 방식을 정합니다.
| 값 | 복사본이 하는 일 |
|---|---|
DOCUMENT (기본) |
문서 하나하나를 복사본마다 다시 색인한다 |
SEGMENT |
원본이 만든 세그먼트 파일을 복사본이 받아 온다 |
세그먼트는 색인을 마친 문서들이 디스크에 적힌 파일 묶음입니다. SEGMENT 로 두면 복사본마다 같은
색인 일을 되풀이하지 않습니다.
그 결과 색인 처리량이 늘어납니다. 자원 사용은 줄어듭니다. 대신 네트워크 사용이 늘어납니다. 원본이 노드 사이 통신으로 세그먼트 파일을 모든 복사본에 직접 밀어 보내기 때문입니다.
PUT /my-index
{
"settings": {
"index": {
"replication.type": "SEGMENT"
}
}
}
replication.type 은 인덱스를 만들 때만 정할 수 있습니다. 이미 있는 인덱스를 세그먼트 복제로
바꾸려면 다시 색인해야 합니다.
SolrCloud
SolrCloud 는 문서를 담는 묶음을 컬렉션이라고 부릅니다. 컬렉션이 한 노드에 다 안 들어가면 샤드 여럿으로 나눕니다. 컬렉션의 문서는 저마다 샤드 하나에만 속합니다.
갈리는 것은 낱말이 가리키는 범위입니다. SolrCloud 에서 샤드는 문서를 나눈 논리 단위입니다. 샤드마다 물리 복사본이 하나 이상 있습니다. 이 복사본을 전부 replica 라고 부릅니다. 그 가운데 하나만 리더(leader)입니다.
리더는 원본 노릇을 합니다. 색인할 문서가 들어오면 어느 샤드 몫인지 정한 뒤 그 샤드의 리더를 든 노드로 넘깁니다. 리더는 그 문서를 색인합니다. 같은 갱신을 나머지 replica 에도 보냅니다.
리더가 죽으면 다른 replica 하나가 새 리더로 자동 선출됩니다.
replica 는 종류가 셋입니다. 셋은 트랜잭션 로그를 두는지, 자기 쪽에서 색인하는지로 갈립니다. 트랜잭션 로그는 받은 갱신을 차례로 적어 두는 기록입니다.
| 종류 | 하는 일 | 리더가 될 수 있나 |
|---|---|---|
| NRT(NearRealTime) · 기본 | 트랜잭션 로그를 둔다. 새 문서를 자기 쪽에서 직접 색인한다 | ✓ |
| TLOG(transaction log) | 트랜잭션 로그는 두지만 자기 쪽에서 색인하지 않는다. 리더가 색인해 둔 결과를 복제해 온다 | ✓ |
| PULL(pull, 당겨 오기) | 트랜잭션 로그를 두지 않는다. 색인도 안 한다. 리더가 색인해 둔 결과만 복제해 온다 | ✗ 리더 선출에 끼지 않는다 |
MongoDB
데이터베이스 가운데에는 조각마다 서버 묶음을 따로 두는 것도 있습니다. 복제는 그 묶음 안에서 합니다. MongoDB 의 샤드 클러스터에서 샤드 하나는 레플리카 셋으로 띄워야 합니다. 레플리카 셋은 같은 데이터를 복제해 드는 서버 묶음입니다. 권장 구성은 멤버 셋입니다.
이때는 복사본 하나하나를 샤드라고 부르지 않습니다. 묶음 전체가 샤드 하나입니다.
레플리카 샤드 쪽은 노드 한 대가 여러 조각의 원본과 복사본을 섞어 듭니다. 레플리카 셋 쪽은 한 묶음의 서버들이 한 조각만 맡습니다. 조각과 서버를 짝짓는 방식이 다릅니다. 조각마다 복사본을 둔다는 생각은 같습니다.
동작
레플리카 샤드가 움직이는 순서는 Elasticsearch 의 데이터 복제 모델로 따라가 봅니다. 등장하는 것은 조각 하나의 원본과 그 복사본들입니다. 아래에서는 성공 응답이 언제 나가는지 봅니다. 복사본이나 원본이 실패할 때 무엇이 갈리는지도 봅니다.
이 모델은 상세에서 본 주-백업 복제에 기댑니다. Microsoft Research 의 PacificA 논문이 정리한 방식입니다.
복제 그룹과 in-sync 목록
복제 그룹(replication group)은 조각 하나의 원본과 복사본들을 한데 묶은 것입니다. 복제 그룹의 벌들은 문서가 들어오고 지워질 때마다 같은 내용을 유지해야 합니다. 어긋나면 어느 벌을 읽느냐에 따라 결과가 달라집니다.
복사본은 꺼져 있을 수도 있으므로 원본은 모든 복사본을 기다리지 않습니다. 쓰기를 꼭 받아야 하는 복사본의 목록을 따로 둡니다. 이 목록이 in-sync 목록(in-sync copies)입니다.
in-sync 목록의 벌은 사용자에게 성공을 알린 색인과 삭제를 전부 처리했다고 보장됩니다. 이 목록은 마스터 노드가 관리합니다. 마스터 노드는 클러스터의 상태를 관리하는 노드입니다. 샤드를 어느 노드에 둘지도 이 노드가 정합니다.
쓰기 한 번이 건너가는 순서
문서를 넣거나 지우는 요청은 먼저 라우팅, 곧 갈 곳을 정하는 단계를 거칩니다. 대개 문서 식별자를 보고 어느 복제 그룹 몫인지 정합니다. 그다음 그 그룹의 현재 원본으로 요청을 넘깁니다.
원본은 요청을 이렇게 처리합니다.
- 요청의 구조를 검사합니다. 숫자가 와야 할 필드에 객체가 오면 거절합니다
- 자기 쪽에서 문서를 색인하거나 지웁니다. 필드 내용도 이때 검사합니다
- 같은 요청을 in-sync 목록의 복사본마다 보냅니다. 복사본이 여럿이면 동시에 보냅니다
- in-sync 복사본이 전부 처리하고 답하면 요청한 쪽에 성공을 알립니다
sequenceDiagram
participant 요청 as 요청한 쪽
participant 원본 as 프라이머리 샤드
participant 복1 as 레플리카 샤드 1
participant 복2 as 레플리카 샤드 2
요청->>원본: 문서를 쓴다
Note over 원본: 검사하고 자기 쪽에 먼저 적는다
원본->>복1: 같은 쓰기를 보낸다
원본->>복2: 같은 쓰기를 보낸다
복1-->>원본: 처리했다
복2-->>원본: 처리했다
원본-->>요청: 성공
복사본은 받은 쓰기를 자기 쪽에서 한 번 더 색인합니다. 성공을 받은 쓰기는 in-sync 복사본에도 이미 적혀 있습니다.
복사본이 답하지 못할 때
원본에서 처리한 쓰기가 복사본에서 실패할 수 있습니다. 복사본 자체가 고장 났을 수도 있습니다. 네트워크가 막혀 요청이나 응답이 못 건너갔을 수도 있습니다. 어느 쪽이든 in-sync 목록의 복사본 하나가 곧 성공을 알릴 쓰기를 빠뜨린 상태가 됩니다.
원본은 이 복사본을 in-sync 목록에서 빼 달라고 마스터에 요청합니다. 마스터가 뺐다고 답한 뒤에야 성공을 알립니다. 그래야 「in-sync 목록의 벌은 성공한 쓰기를 전부 가진다」는 약속이 깨지지 않습니다. 마스터는 다른 노드에 새 복사본을 만들게 합니다.
flowchart TD
A["원본이 in-sync 복사본마다 쓰기를 보낸다"] --> B{"전부 답했나"}
B -->|예| E["요청한 쪽에 성공을 알린다"]
B -->|아니오| C["원본이 마스터에 그 복사본을 목록에서 빼 달라고 한다"]
C --> D["마스터가 뺐다고 답한다"]
D --> E
D --> F["마스터가 다른 노드에 새 복사본을 만들게 한다"]
복사본이 하나도 없는 채로 돌 수도 있습니다. 레플리카 수를 0 으로 뒀거나 복사본이 전부 실패한 경우입니다. 이때 원본은 확인해 줄 복사본 없이 혼자 쓰기를 처리합니다.
그래도 원본에 색인한 쓰기는 잃지 않습니다. 앞에서 본 대로 실패한 복사본을 빼는 일은 원본이 혼자 못 합니다. 늘 마스터에 요청해야 합니다. 복사본이 다 빠지는 동안 마스터는 그 과정을 전부 거치므로, 원본이 유일하게 온전한 벌이라는 것을 압니다.
마스터는 목록에서 빠진 낡은 복사본을 새 원본으로 올리지 않습니다. 다만 데이터가 한 벌뿐이라 하드웨어가 고장 나면 데이터를 잃을 수 있습니다.
원본이 실패할 때
원본 쪽이 실패하면 이렇게 넘어갑니다.
- 원본을 든 노드가 마스터에 실패를 알립니다
- 원본으로 가던 쓰기는 마스터가 복사본 하나를 새 원본으로 승격할 때까지 기다립니다. 기본으로 최대 1분입니다
- 승격이 끝나면 기다리던 쓰기를 새 원본으로 넘겨 처리합니다
승격이 끝난 그룹은 복사본이 한 벌 모자랍니다. 샤드 복구는 샤드 복사본 하나를 새로 띄워 쓸 수 있게 만드는 일입니다. 원본을 베껴 새 복사본을 만드는 것도 그 가운데 하나입니다. 복구가 끝난 벌은 검색과 색인을 받습니다.
아래 그림은 상세의 노드 셋 배치에서 노드 1 이 죽은 뒤를 세 단계로 따라갑니다. 노드 2 의 복사본이 조각 0 의 원본이 됩니다. 새로 만드는 복사본도 자기 원본과 다른 노드에 놓입니다.
flowchart TD
subgraph S1["1 · 노드 1 이 죽는다"]
A1["사라짐<br/>프라이머리 샤드 0 · 레플리카 샤드 2"]
A2["노드 2<br/>프라이머리 샤드 1 · 레플리카 샤드 0"]
A3["노드 3<br/>프라이머리 샤드 2 · 레플리카 샤드 1"]
end
subgraph S2["2 · 승격"]
B2["노드 2<br/>프라이머리 샤드 1 · 프라이머리 샤드 0(승격)"]
B3["노드 3<br/>프라이머리 샤드 2 · 레플리카 샤드 1"]
end
subgraph S3["3 · 샤드 복구"]
C2["노드 2<br/>프라이머리 샤드 1 · 프라이머리 샤드 0<br/>레플리카 샤드 2(새로 만듦)"]
C3["노드 3<br/>프라이머리 샤드 2 · 레플리카 샤드 1<br/>레플리카 샤드 0(새로 만듦)"]
end
S1 --> S2 --> S3
원본이 죽지 않고 네트워크에서 떨어져 나가기만 할 수도 있습니다. 마스터는 노드 상태를 지켜보다가 이런 원본을 미리 강등하기도 합니다. 강등은 원본 자격을 거두는 일입니다. 떨어진 원본은 강등된 줄을 모르는 채로 쓰기를 계속 받을 수 있습니다.
원본은 쓰기를 복사본에 보낼 때 자기가 아직 원본인지도 복사본을 통해 확인합니다. 떨어진 원본이 보낸 쓰기는 복사본들이 거절합니다. 거절을 받은 원본은 마스터에 물어 자기가 바뀌었다는 것을 알게 됩니다. 쓰기는 새 원본으로 다시 갑니다.
읽기 한 번이 고르는 벌
주-백업 복제는 진행 중인 쓰기를 빼면 복제 그룹의 벌들을 똑같이 유지하므로, 읽기는 in-sync 벌 하나만 읽어도 됩니다.
- 요청이 어느 샤드들을 봐야 하는지 정합니다. 검색은 대개 여러 샤드를 읽습니다
- 샤드마다 복제 그룹에서 살아 있는 벌 하나를 고릅니다. 원본일 수도 복사본일 수도 있습니다
- 고른 벌들에 샤드 단위 읽기를 보냅니다
- 결과를 합쳐 답합니다. 문서 식별자로 한 건을 찾을 때는 샤드가 하나라 이 단계를 건너뜁니다
고른 벌이 답하지 않으면 같은 그룹의 다른 벌로 다시 보냅니다. 실패가 거듭되면 쓸 수 있는 벌이 하나도 안 남을 수 있습니다.
이 순서가 낳는 성질
앞의 쓰기·읽기 순서에서 성질 셋이 따라 나옵니다. 아래 표는 성질마다 그것이 생기는 까닭을 짝지어 둡니다. 원본이 떨어져 나갔을 때 생기는 넷째 성질은 표 아래에서 따로 풉니다.
| 성질 | 왜 생기나 |
|---|---|
| 성공 전에 보이는 쓰기 | 원본이 자기 쪽에 먼저 적은 뒤 복사본에 보낸다. 그 사이 다른 읽기가 아직 성공을 안 알린 변경을 볼 수 있다 |
| 벌 하나가 쓰기를 늦춘다 | 원본이 in-sync 복사본을 전부 기다린다. 한 벌이 늦게 답하면 그룹 전체의 쓰기가 늦어진다. 그 벌로 간 검색도 늦어진다 |
| 두 벌로 장애를 견딘다 | 원본과 복사본 두 벌만 있어도 장애를 견딜 수 있다. 과반 동의로 정하는 쿼럼(quorum) 방식은 장애를 견디려면 최소 세 벌이 든다 |
원본이 네트워크에서 떨어져 나가면 성공 전에 보이던 쓰기가 끝내 성공하지 못할 수 있습니다. 떨어진 원본은 복사본이나 마스터에 요청을 보낼 때에야 자기가 떨어진 줄 압니다. 그 전에 받은 쓰기는 이미 원본에 색인돼 있어 다른 읽기가 볼 수 있습니다.
끝내 성공하지 못할 쓰기를 이렇게 읽는 일이 더티 리드(dirty read)입니다. 원본은 기본으로 1초마다 마스터에 신호를 보내 확인합니다. 이 확인에서 마스터를 하나도 찾지 못하면 쓰기를 거절합니다. 더티 리드의 위험은 이렇게 줄어듭니다.
운영
Elasticsearch 를 굴릴 때 복사본과 관련해 만지는 것은 인덱스 설정 셋입니다. 보는 것은 클러스터 헬스입니다. 아래에서는 설정마다 기본값을 봅니다. 헬스에서 무엇이 보이면 복사본이 모자란 것인지도 봅니다.
복사본 수를 정하는 설정
복사본 수는 아래 인덱스 설정 셋으로 정합니다. 표에 나오는 데이터 노드는 샤드를 드는 노드입니다. 색인과 검색도 이 노드가 맡습니다.
| 설정 | 정하는 것 | 기본값 | 바꿀 수 있는 때 |
|---|---|---|---|
index.number_of_replicas |
프라이머리 샤드 하나마다 둘 복사본 수 | 1 | 언제든. 색인과 검색을 멈추지 않는다 |
index.auto_expand_replicas |
복사본 수를 데이터 노드 수에 맞춘다. 0-5 처럼 아래·위 한도를 주거나 위 한도에 all 을 준다 |
false (꺼짐) |
언제든 |
index.number_of_shards |
프라이머리 샤드 수 | 1 | 인덱스를 만들 때만 |
index.number_of_replicas 를 0 으로 두면 노드를 다시 시작하는 동안 일시적으로 데이터를 못 쓸 수
있습니다. 데이터가 깨지면 영영 잃을 수도 있습니다.
index.auto_expand_replicas 는 샤드 배정 규칙 가운데 할당 필터(allocation filtering)만 따집니다.
할당 필터는 이름 그대로 샤드를 놓을 수 있는 노드를 걸러 내는 규칙입니다. 노드당 샤드 총수 같은 다른
배정 규칙은 안 봅니다. 그 규칙들 때문에 복사본을 다 못 놓으면 클러스터 헬스가 yellow 가 될 수 있습니다.
프라이머리 샤드 수는 이미 만든 인덱스에서는 못 바꿉니다. 수를 바꾸려면 split 이나 shrink 로 새 인덱스를 만듭니다. split 은 프라이머리 샤드가 더 많은 새 인덱스를, shrink 는 더 적은 새 인덱스를 만듭니다.
둘 다 원래 인덱스가 읽기 전용이어야 합니다. 헬스 조건은 둘이 다릅니다. split 은 클러스터 헬스가 green 이어야 합니다. shrink 는 원래 인덱스의 헬스가 green 이어야 합니다.
복사본이 배정되지 못할 때
Elasticsearch 는 하드웨어 고장에 대비해 복사본을 자기 원본과 같은 노드에 배정하지 않습니다. 복사본을 받을 다른 데이터 노드가 없으면 그 복사본은 미할당으로 남습니다. 레플리카 수가 노드 수와 같거나 크면 일부 샤드는 배정되지 않습니다.
이때 클러스터 헬스는 yellow 입니다. 미할당 프라이머리는 없지만 미할당 복사본이 있다는 뜻입니다. 데이터를 잃을 위험이 커집니다. 클러스터 성능이 떨어질 수도 있습니다.
푸는 방법은 둘입니다. 데이터 노드를 하나 더 두어 복사본을 받게 합니다. 아니면
index.number_of_replicas 를 줄입니다. 고가용성을 위해서는 프라이머리마다 복사본을 적어도 하나
두기를 권합니다.
원본을 든 노드를 잃어도 대개 다른 노드의 복사본으로 대신합니다. 그 노드를 살릴 수 없을 때도
있습니다. 복사본까지 없거나 못 쓰게 되면 배정 사유를 설명하는 Allocation Explain 이
no_valid_shard_copy 를 보고합니다.
스냅샷은 인덱스를 어느 시점 그대로 떠 둔 백업입니다. no_valid_shard_copy 가 뜨면 스냅샷에서
데이터를 복원하거나, 원래 데이터 원천에서 다시 색인해야 합니다.
작은 클러스터에서 잃어도 되는 노드
상세에서 센 노드 손실 한도는 데이터가 남는지만 따진 것입니다. 클러스터가 계속 돌려면 마스터 노드도 뽑을 수 있어야 합니다. master-eligible 노드는 마스터가 될 자격이 있는 노드입니다. 마스터 선출은 이 노드들의 과반으로 정합니다.
| 노드 수 | 권장 구성 | 견디는 손실 |
|---|---|---|
| 1 | green 을 보려면 모든 인덱스의 레플리카 수를 0 으로 둔다 | 못 견딘다. 노드가 죽으면 클러스터가 멈춘다 |
| 2 | 둘 다 데이터 노드. 레플리카 수 1. master-eligible 은 하나만 | master-eligible 이 아닌 쪽을 잃는 것만 견딘다. 둘 다 master-eligible 이면 선출에 두 노드가 다 있어야 한다. 그러면 어느 쪽을 잃어도 견딘다고 장담할 수 없다 |
| 3 | 셋 다 데이터 노드이자 master-eligible. 레플리카 수 1 이상 | 아무 노드 하나 |
노드 셋에 레플리카 수를 2 로 두면 노드마다 모든 조각이 한 벌씩 있습니다. 그래도 노드 둘을 잃으면 선출에 필요한 master-eligible 노드 둘이 안 남습니다.
어느 노드 하나를 잃어도 견디려면 아래 조건이 함께 걸립니다. 목록에 낱말 둘이 새로 나옵니다. 검색 가능 스냅샷 인덱스는 이름 그대로 스냅샷에 든 데이터를 검색하는 인덱스입니다. 투표만 하는 노드는 마스터 선출에서 표를 던지기만 하는 master-eligible 노드입니다.
- 클러스터 헬스가 green 이다
- 데이터 노드가 둘 이상이다
- 모든 인덱스가 샤드마다 복사본을 하나 이상 둔다. 검색 가능 스냅샷 인덱스는 이 조건에서 빠진다
- master-eligible 노드가 셋 이상이다. 그 가운데 둘 이상은 투표만 하는 노드가 아니다
- 클라이언트가 요청을 노드 여럿에 보내거나, 노드 여럿에 나눠 주는 로드 밸런서를 거친다
처음 대량으로 넣을 때
많은 데이터를 한 번에 넣을 때는 index.number_of_replicas 를 0 으로 두면 색인이 빨라질 수 있습니다.
복사본이 없으면 노드 하나만 잃어도 데이터를 잃을 수 있습니다. 문제가 나면 적재를 다시 할 수 있도록
그 데이터가 다른 곳에도 있어야 합니다. 적재가 끝나면 레플리카 수를 원래 값으로 되돌릴 수 있습니다.
복사본이 치르는 값
복사본은 원본이 담은 문서를 전부 담으므로, 레플리카 수가 1 이면 인덱스가 차지하는 디스크가 대략 두 배, 2 면 대략 세 배가 됩니다.
문서 단위로 복제하면 쓰기 한 번을 in-sync 복사본마다 따로 색인합니다. 복사본이 늘수록 클러스터 전체의 색인 일이 늘어납니다. 성공 응답도 가장 늦게 답한 복사본을 기다렸다가 나갑니다.
관련 항목
레플리카 샤드가 베끼는 원본과 그 안의 구조
프라이머리 샤드 · 샤드 · 샤딩 · 인덱스 · 수평 분할 · 라우팅 · Lucene · 세그먼트 · 트랜잭션 로그 · 복제 그룹
레플리카 샤드를 들고 있는 서버 단위
노드 · 데이터 노드 · 마스터 노드 · 클러스터 · 분산 시스템
레플리카 샤드가 기대는 복제 방식
복제 · 프라이머리-백업 복제 · PacificA · 동기 복제 · 비동기 복제 · 세그먼트 복제 · 복제 지연
레플리카 샤드가 지키는 성질
고가용성 · 내결함성 · 내구성 · 수평 확장 · 단일 장애점
원본을 잃은 뒤 거치는 처리 단계
장애 조치 · 승격 · 리더 선출 · 마스터 선출 · 샤드 복구 · 리밸런싱 · 샤드 할당
레플리카 샤드의 배정을 보여 주는 상태
클러스터 헬스 · 미할당 샤드 · 클러스터 상태 · Allocation Explain
복제 그룹에서 터지는 장애
네트워크 분할 · 스플릿 브레인 · 더티 리드 · 데이터 손실
레플리카 샤드 대신 데이터를 되살리는 수단
샤드마다 복사본을 두는 제품
Elasticsearch · OpenSearch · Apache Solr · SolrCloud · MongoDB
레플리카 샤드와 맞세워지는 복제 단위
레플리카 · 레플리카 셋 · 리드 레플리카 · 쿼럼
다른 이름: replica shard