강한 일관성
고친 사람 github-actions[bot]
강한 일관성은 사본을 여러 벌 둔 저장소가 언제나 최신 값 하나만 돌려주도록 묶어 줍니다. 값을 바꾼 뒤에는 어느 사본에 물어도 바뀐 값이 나옵니다. 그 대신 쓰기와 읽기가 사본들을 맞추는 동안 기다립니다.
쉽고 빠른 이해
강한 일관성은 데이터를 여러 곳에 복사해 두고도 한 곳뿐인 것처럼 보이게 하는 약속입니다. 마지막 한 장 남은 좌석을 두 사람에게 팔지 않으려면 이 약속이 필요합니다.
사본마다 답이 다르면 앱은 어느 답을 믿을지 정할 방법이 없습니다. 읽어 온 값이 이미 지나간 값일 수 있다는 것을 코드가 매번 따져야 합니다.
도는 모양은 셋입니다.
- 쓰기가 들어오면 사본 하나가 아니라 정해 둔 수의 사본이 그 값을 받을 때까지 기다립니다
- 그 수를 채운 뒤에야 쓰기를 끝났다고 응답합니다
- 읽기도 같은 규칙을 거쳐 최신 값을 확인한 다음 답합니다
대가가 있습니다. 쓰기와 읽기가 사본들을 오가는 시간만큼 오래 걸립니다. 사본끼리 서로 못 닿게 되면 한쪽은 아예 답하기를 멈춥니다.
그래서 거는 데이터를 가립니다. 좌석이나 잔액처럼 두 답이 갈리면 안 되는 데이터에 겁니다. 좋아요 수처럼 잠깐 달라도 되는 데이터에는 걸지 않습니다.
상세
가족이 냉장고 한 대를 같이 씁니다. 누가 우유를 꺼내 가면 다음에 문을 연 사람에게는 우유가 없습니다. 물건이 한 곳에만 있으니 누가 언제 열어 보든 답이 엇갈릴 여지가 없습니다.
강한 일관성은 사본이 여러 벌인 저장소를 그 냉장고 한 대처럼 보이게 하는 약속입니다. 사본은 여전히 여럿입니다. 밖에서 보기에는 한 벌만 있는 것처럼 굴어야 합니다.
flowchart TD
APP["앱"]
subgraph V["앱에게 보이는 것 · 저장소 한 벌"]
subgraph R["실제 · 사본 셋"]
A["사본A"]
B["사본B"]
C["사본C"]
end
end
APP --> V
앱은 바깥 상자만 봅니다. 안쪽에 사본이 몇 벌인지, 그중 어느 사본이 답했는지는 앱의 코드에 드러나지 않습니다.
사본을 여러 곳에 나눠 두는 일 자체는 복제라고 부릅니다. 강한 일관성은 그 사본들이 얼마나 엄하게 맞아야 하는지를 정합니다.
약속의 내용은 한 줄입니다. 어떤 쓰기가 끝난 뒤에 시작한 읽기는 그 값이나 그보다 나중 값을 봅니다. 좌석 하나를 예매한 순간부터는 어느 창구에 물어도 그 좌석이 팔린 것으로 나온다는 뜻입니다.
같은 데이터의 사본들이 서로 얼마나 맞아야 하는지를 정하는 약속을 통틀어 일관성 약속이라고 부릅니다. 그중에서 강한 일관성은 제일 엄한 쪽 이름입니다. 느슨한 쪽 끝에는 사본들이 언젠가만 같아지면 된다고 보는 결과적 일관성이 있습니다.
「끝난 뒤」가 가리키는 때
이 약속의 기준은 실제로 흐른 시간입니다. 값이 10 이었는데 11 로 바꾸는 쓰기가 있다고 합시다. 쓰기가 응답을 받아 끝난 시점보다 늦게 시작한 읽기가 약속의 대상입니다. 그 읽기는 10 을 볼 수 없습니다.
거꾸로 쓰기가 아직 도는 중에 시작한 읽기는 대상이 아닙니다. 그 읽기에는 10 이 나와도 되고 11 이 나와도 됩니다. 두 연산이 같은 시간대에 걸쳐 있으면 어느 쪽이 먼저라고 말할 근거가 없기 때문입니다.
flowchart TD
subgraph S1["겹치지 않은 두 연산"]
W1["쓰기 · 11 로 바꿈 · 응답까지 받음"] --> R1["그 뒤에 시작한 읽기 · 반드시 11"]
end
subgraph S2["겹친 두 연산 · 같은 시간대"]
W2["쓰기 · 11 로 바꾸는 중"]
R2["읽기 · 10 도 11 도 된다"]
end
그림에서 볼 것은 아래 상자입니다. 두 상자가 화살표 없이 나란한 것은 앞뒤를 가릴 수 없다는 뜻입니다. 강한 일관성을 지키는 저장소에서도 저 구간에는 답이 하나로 정해지지 않습니다. 그래서 이 약속은 「언제나 최신」이 아니라 「끝난 것은 반드시 보인다」에 가깝습니다.
사본 여럿을 하나처럼 보이게 하는 법
장치는 둘입니다. 하나는 쓰기를 한 줄로 세웁니다. 다른 하나는 몇 개가 받아야 그 쓰기를 끝난 것으로 칠지 정합니다.
한 줄로 세우는 흔한 방법은 사본 하나를 대표로 뽑아 모든 쓰기가 그 사본을 지나게 하는 것입니다. 대표가 쓰기를 받은 차례가 곧 전체의 차례가 됩니다. 대표를 뽑고 바꾸는 일 자체도 사본들이 합의해야 합니다. 그 절차가 합의입니다.
몇 개가 받아야 하는지를 정하는 값이 정족수입니다. 흔히 과반으로 잡습니다. 과반으로 잡는 까닭은 겹침에 있습니다. 쓰기가 닿은 과반과 읽기가 물어본 과반은 적어도 한 사본에서 반드시 만나므로, 읽기는 새 값을 가진 사본을 최소한 하나는 만나게 됩니다.
flowchart TD
W["쓰기 · 과반에 닿는다"] --> A["사본A"]
W --> B["사본B"]
W --> C["사본C · 겹치는 한 대 · 새 값을 갖고 있다"]
R["읽기 · 과반에 물어본다"] --> C
R --> D["사본D"]
R --> E["사본E"]
셋과 셋은 다섯 안에서 반드시 한 대를 공유합니다. 어느 셋을 고르든 마찬가지라서, 읽기는 누구에게 물었든 새 값을 가진 사본을 만납니다.
sequenceDiagram
participant 앱
participant 대표
participant 사본B
participant 사본C
앱->>대표: 11 로 바꿔라
대표->>사본B: 11
대표->>사본C: 11
사본B-->>대표: 받았다
Note over 대표,사본C: 과반이 받았으니 끝난 것으로 친다
대표-->>앱: 바꿨다
앱->>대표: 값을 달라
앱->>사본B: 값을 달라
대표-->>앱: 11
사본B-->>앱: 11
쓰기에서 사본C 의 답은 기다리지 않았습니다. 셋 중 둘이 받았으면 과반이라 거기서 끝냅니다. 사본C 가 느리거나 죽어 있어도 쓰기는 진행됩니다. 뒤늦게 살아난 사본C 는 밀린 값을 받아 따라옵니다. 뒤이은 읽기도 한 곳이 아니라 과반인 두 곳에 묻습니다.
강한 일관성이 내놓는 대가
첫째는 시간입니다. 쓰기 한 번이 사본들을 오가는 왕복에 묶입니다. 사본을 멀리 떨어진 지역에 두면 그 거리만큼의 왕복 시간이 쓰기마다 얹힙니다. 이렇게 늘어난 시간이 지연입니다.
둘째는 사본이 서로 못 닿게 됐을 때입니다. 사본끼리 연결이 끊긴 상태를 네트워크 분단이라고 부릅니다. 이때 정족수를 못 채우는 쪽은 최신 값을 확인할 방법이 없어서, 옛 값을 주는 대신 아예 답하기를 거절합니다.
flowchart TD
APP1["앱 · 여기서는 답을 받는다"] --> A["사본A · 대표"]
APP2["앱 · 여기서는 거절당한다"] --> D["사본D"]
subgraph G1["과반이 모인 무리 · 셋"]
A
B["사본B"]
C["사본C"]
end
subgraph G2["과반을 못 채운 무리 · 둘"]
D
E["사본E"]
end
G1 -. 여기가 끊겼다 .- G2
사본을 다섯으로 늘려 셋과 둘로 갈린 그림입니다. 끊긴 것은 두 무리 사이의 모든 연결입니다. 둘뿐인 쪽은 과반이 아니라 앱이 답을 못 받습니다.
한쪽이 답하기를 멈추는 성질은 그 시스템이 가용성을 내준 것입니다. 이 갈림을 CAP(Consistency, Availability, Partition tolerance)이라고 부릅니다. 분단이 난 동안에는 일관성과 가용성 중 하나를 골라야 한다는 결론입니다. 강한 일관성은 그 갈림에서 일관성 쪽을 고른 이름입니다.
셋째는 처리량입니다. 모든 쓰기가 대표 한 대를 지나면 그 한 대가 감당하는 양이 전체의 상한이 됩니다. 데이터를 여러 덩이로 쪼개 덩이마다 대표를 따로 두면 이 상한을 올릴 수 있습니다. 그러면 덩이를 가로지르는 연산이 다시 어려워집니다.
느슨한 약속과 갈리는 대목
결과적 일관성은 사본들이 언젠가 같아지기만 하면 된다고 봅니다. 두 약속을 나란히 놓으면 무엇을 주고 무엇을 받는지가 드러납니다.
| 강한 일관성 | 결과적 일관성 | |
|---|---|---|
| 읽으면 무엇이 오나 | 끝난 쓰기의 값이나 그 뒤 값 | 최신 값이거나 옛 값 |
| 쓰기 한 번에 드는 시간 | 정족수를 채우는 만큼 | 사본 하나에 쓰는 만큼 |
| 사본 일부가 끊기면 | 정족수를 못 채우는 쪽이 멈춥니다 | 남은 사본이 계속 받습니다 |
| 짜는 쪽이 할 일 | 없습니다 | 옛 값을 만났을 때를 코드가 다뤄야 합니다 |
두 끝 사이는 비어 있지 않습니다. 사이에는 이런 것들이 섭니다.
- 인과 일관성 — 원인과 결과의 순서만 뒤집히지 않게 지킵니다
- 단조 읽기 — 한 사용자가 자기 읽기 순서에서는 값이 뒤로 가지 않게 합니다
- bounded staleness — 값이 낡을 수 있는 상한을 정해 둡니다
엄한 쪽으로 갈수록 더 많은 사본을 기다립니다. 느슨한 쪽으로 갈수록 어긋난 값을 다루는 몫이 짜는 쪽으로 넘어옵니다.
이 이름이 보장하는 범위
여러 대에 흩어져 도는 연산들을 한 대에서 한 줄로 처리한 것처럼 늘어놓을 수 있으면 선형화 가능하다고 합니다. 격식을 갖춘 정의는 그 이름으로 따로 있습니다. 복제된 객체들이 선형화 가능하면 그 복제 방식이 강한 일관성을 보입니다. 강한 일관성은 그 격식을 갖춘 성질을 실무에서 부르는 쪽 이름에 가깝습니다.
보장의 단위는 대개 객체 하나입니다. 행 하나, 키 하나가 그 단위입니다. 여러 객체에 걸친 읽기와 쓰기를 한 덩이로 묶는 일은 강한 일관성이 아니라 트랜잭션이 맡습니다.
이 이름이 늘 같은 세기로 쓰이는 것도 아닙니다. 어떤 저장소는 읽기 한 번이 최신을 보는 것까지만 이 이름으로 부릅니다. 어떤 저장소는 여러 연산의 순서까지 묶는 것을 뜻합니다. 그래서 이 말을 만나면 무엇까지 보장하는지 그 저장소의 설명에서 확인해야 합니다.
강한 일관성을 거는 데이터와 안 거는 데이터
가르는 물음은 하나입니다. 두 사람이 동시에 물었을 때 서로 다른 답을 받아도 되나.
flowchart TD
Q["두 사람이 동시에 물었을 때 서로 다른 답을 받아도 되나"]
Q -- 안 된다 --> N["강한 일관성 · 잔액 · 남은 재고 · 좌석 · 누가 대표인가"]
Q -- 된다 --> Y["느슨한 약속 · 좋아요 수 · 조회 수 · 추천 목록"]
받으면 안 되는 데이터가 있습니다. 계좌 잔액, 마지막 한 개 남은 재고, 좌석 예매, 아이디 중복 검사가 그렇습니다. 여기에 느슨한 약속을 걸면 같은 좌석이 두 사람에게 팔립니다. 그 결정은 나중에 되돌릴 수 없습니다.
분산된 여러 대가 서로 맞춰야 하는 조정 정보도 같습니다. 누가 대표인지, 어느 작업이 누구에게 잡혀 있는지에서 두 답이 갈리면 두 대가 동시에 대표 노릇을 하게 됩니다.
달라도 되는 데이터도 있습니다. 좋아요 수, 조회 수, 추천 목록은 잠깐 다르게 보였다가 맞아도 업무가 깨지지 않습니다. 이런 데이터까지 강한 일관성으로 두면 앞 절의 대가만 치르고 얻는 것이 없습니다.
그래서 저장소 전체를 한쪽으로 정하기보다 데이터마다 다르게 거는 편이 흔합니다. 저장소에 따라서는 연산마다 어느 약속으로 읽을지 고를 수 있습니다. 강한 일관성을 건 데이터가 많아질수록 그만큼 느려지고 잘 멈춘다는 것만 기억하면 됩니다.
관련 항목
이것과 강도를 겨루는 다른 일관성 약속
선형화 가능성 · 결과적 일관성 · 순차 일관성 · 인과 일관성 · 외부 일관성 · 엄격 직렬성 · 직렬가능성 · 읽기 자기 쓰기 일관성 · 단조 읽기 · bounded staleness · 일관성
강한 일관성을 지켜 내는 수단
합의 · Raft · Paxos · 정족수 · 단일 리더 복제 · 동기 복제 · 리더 선출 · 2단계 커밋 · 분산 잠금
강한 일관성을 느슨하게 만드는 제약
CAP · PACELC · 네트워크 분단 · 가용성 · 지연 · 처리량 · 광속 지연
강한 일관성이 깨졌을 때 드러나는 증상
낡은 읽기 · 낡은 데이터 · 복제 지연 · 분할 뇌 · 쓰기 후 읽기 · 신선도
강한 일관성이 얹히는 바탕 구조
복제 · 분산 시스템 · 샤딩 · 캐시 일관성 · 일관성 해싱 · 분산 트랜잭션 · 복제본
트랜잭션 쪽에서 같은 문제를 다루는 개념
트랜잭션 · 격리 수준 · 동시성 제어 · 낙관적 잠금 · TrueTime · 원자성
강한 일관성을 내거는 저장 제품
etcd · ZooKeeper · Spanner · CockroachDB · Consul · Amazon S3
다른 이름: strong consistency · strongly consistent · 강한 정합성 · 강일관성