사전 결과적 일관성
개념

결과적 일관성

gabury1

결과적 일관성은 여러 곳에 흩어 둔 사본이 끝내 같은 값 하나로 모이도록 맞춰 줍니다. 값을 바꾼 직후에는 어느 사본을 읽느냐에 따라 다른 답이 돌아옵니다. 바뀐 값이 아직 안 닿은 사본이 남아 있기 때문입니다. 대신 사본 한 곳이 끊겨도 나머지가 계속 답합니다.

쉽고 빠른 이해

결과적 일관성은 데이터를 여러 곳에 복사해 두고 쓰는 시스템이 내거는 약속입니다. 게시글의 좋아요 수가 새로고침할 때마다 다르게 보이다가 조금 뒤 하나로 맞는 것이 이 약속 아래에서 벌어지는 일입니다.

모든 사본을 다 맞춘 뒤에 응답하면, 사본 하나가 느리거나 끊긴 동안 쓰기가 전부 멈춥니다. 그 멈춤을 피하려고 잠깐의 어긋남을 받아들입니다.

도는 모양은 셋입니다.

  1. 쓰기를 받은 사본 하나가 값을 바꾸고 바로 응답합니다
  2. 바뀐 값을 나머지 사본에 뒤따라 보냅니다
  3. 더 쓸 것이 없어지면 모든 사본이 같은 값이 됩니다

대가가 있습니다. 그 사이에 읽으면 예전 값이 나올 수 있습니다. 두 곳에서 같은 데이터를 동시에 고치면 어느 값을 남길지 따로 정해 줘야 합니다.

그래서 쓰는 데가 갈립니다. 좋아요 수처럼 잠깐 달라 보여도 되는 데이터에 씁니다. 잔액이나 좌석 예약처럼 되돌릴 수 없는 데이터에는 안 씁니다.

상세

선생님이 칠판에 새 줄을 계속 적는 동안 아이들은 각자 공책에 그것을 받아 적습니다. 어느 때 들여다봐도 공책들은 칠판보다 한두 줄씩 뒤처져 있습니다. 공책이 칠판과 똑같아지는 때는 선생님이 분필을 놓은 다음입니다.

결과적 일관성은 같은 데이터를 여러 사본으로 나눠 둔 시스템이 내거는 약속입니다. 내용은 한 줄입니다. 새로 쓰는 일이 멈추면 모든 사본이 마지막에 쓴 값 하나로 모입니다. 사본을 여러 곳에 두는 일 자체는 복제라고 부르고, 결과적 일관성은 그 사본들이 얼마나 엄하게 맞아야 하는지를 정하는 약속입니다.

이 약속이 말하지 않는 것이 둘입니다. 하나는 시간입니다. 모이는 데 얼마가 걸리는지는 약속에 안 들어 있습니다. 다른 하나는 모이기 전에 읽으면 무엇이 나오는지입니다. 그 답도 정해져 있지 않습니다.

약한 약속으로 보이지만 없는 것보다는 셉니다. 아무 약속이 없으면 어긋난 값이 영영 어긋난 채 남아도 규칙을 어긴 것이 아닙니다. 결과적 일관성은 적어도 「내버려 두면 스스로 맞는다」를 보장합니다.

곧바로 못 맞추는 까닭

사본을 두는 까닭부터 봅니다. 한 대가 죽어도 서비스가 이어지게 하려고 사본을 만듭니다. 멀리 있는 사용자 가까이에 데이터를 두려는 까닭도 있습니다. 그렇게 만든 사본들은 네트워크로 이어져 있습니다.

네트워크는 느려지고 때로 끊깁니다. 사본끼리 서로 못 닿게 된 상태가 네트워크 분단입니다. 아래는 사본 셋이 두 무리로 갈린 모양입니다.

flowchart TD
    APP["앱 · 아무 사본에나 붙는다"]
    subgraph G1["서로 닿는 무리"]
        A["사본A"]
        B["사본B"]
    end
    subgraph G2["갈라진 무리"]
        C["사본C"]
    end
    APP --> A
    APP --> C
    A --- B
    A -. 못 닿음 .- C

끊긴 것은 사본 사이의 연결뿐입니다. 사본C 도 앱의 요청은 그대로 받습니다. 그래서 분단이 났을 때 시스템은 둘 중 하나를 골라야 합니다.

고르는 방식 쓰기를 언제 끝냈다고 하나 한 사본이 끊기면
다 맞추고 응답 모든 사본이 새 값을 받은 뒤 쓰기가 멈춥니다
하나만 쓰고 응답 사본 하나가 새 값을 받으면 남은 사본이 계속 받습니다

결과적 일관성은 아래 줄을 고른 것입니다. 한 곳이 끊기거나 느려져도 서비스가 계속 답하는 성질을 가용성이라고 부릅니다. 결과적 일관성은 가용성 쪽으로 기울어 있습니다. 그 대가로 잠깐의 어긋남을 내놓습니다.

위 표의 위 줄을 고르면 쓰기 한 번에 드는 시간이 가장 오래 걸리는 사본에 묶입니다. 사본을 지구 반대편에 두면 그 왕복 시간이 쓰기 시간에 얹힙니다. 사용자 가까이에 사본을 두려던 이유가 여기서 지워집니다.

한 번의 쓰기가 지나가는 길

아래는 좋아요 수를 10 에서 11 로 올린 직후 벌어지는 일입니다. 사본 둘만 그렸습니다.

sequenceDiagram
    participant 앱
    participant 사본A
    participant 사본B
    앱->>사본A: 좋아요 수를 11 로 바꿔라
    사본A-->>앱: 바꿨다
    앱->>사본B: 좋아요 수를 달라
    사본B-->>앱: 10
    사본A->>사본B: 11 로 맞춰라
    앱->>사본B: 좋아요 수를 달라
    사본B-->>앱: 11

사본A 는 자기만 고치고 바로 응답했습니다. 사본B 에게 알리는 일은 응답 뒤로 미뤘습니다. 그 틈에 사본B 를 읽은 사람은 옛 값인 10 을 받습니다.

틀린 값을 준 것이 아닙니다. 사본B 로서는 10 이 자기가 아는 마지막 값입니다. 어긋남은 사본 안에 있는 것이 아니라 사본과 사본 사이에 있습니다.

「결국」의 시점

약속에 붙은 조건은 「새로 쓰는 일이 멈추면」입니다. 돌아가는 서비스에는 쓰기가 멈추는 때가 거의 없습니다. 그래서 사본들은 한 번 같아지고 끝나는 것이 아니라 언제나 조금씩 뒤처진 채 따라갑니다.

stateDiagram-v2
    같음: 모든 사본이 같음
    어긋남: 사본끼리 어긋남
    같음 --> 어긋남: 쓰기가 들어옴
    어긋남 --> 같음: 전파가 끝남
    어긋남 --> 어긋남: 다음 쓰기가 또 들어옴
    같음 --> 같음: 쓰기가 없는 동안

그림에서 볼 것은 「어긋남」이 자기 자신으로 돌아오는 화살표입니다. 쓰기가 계속 들어오는 동안은 거기에 머뭅니다. 「결국」은 시각을 정한 말이 아니라 조건을 붙인 말입니다.

뒤처진 정도는 잴 수 있습니다. 어떤 사본이 아직 못 받은 쓰기가 얼마나 밀려 있는지를 복제 지연이라고 부릅니다. 데이터가 얼마나 낡았는지를 재고 다루는 이야기는 신선도가 따로 받습니다.

그래서 이 약속을 쓸 때 보는 것은 「언젠가 맞는다」가 아닙니다. 「보통 얼마나 뒤처지고 나쁠 때는 얼마나 뒤처지나」를 재서 관리합니다. 그 수치가 업무가 버티는 범위 안이면 쓸 만한 것입니다. 넘으면 더 엄한 약속으로 옮겨야 합니다.

읽는 쪽이 만나는 어긋남

어긋남은 세 가지 모양으로 나타납니다.

어긋남 읽는 쪽에 무엇이 보이나 언제 생기나
낡은 읽기 새 값이 있는데 예전 값이 옵니다 아직 못 받은 사본을 읽었을 때
시간이 거꾸로 가는 읽기 두 번 읽었는데 두 번째가 더 예전 값입니다 두 읽기가 서로 다른 사본에 붙었을 때
내가 쓴 것이 안 보임 쓰고 바로 읽었는데 방금 쓴 값이 없습니다 쓰기를 받은 사본 말고 다른 사본에서 읽었을 때

아래 둘은 한 앱의 읽기가 사본을 옮겨 다니면 벌어집니다. 앞의 그림은 전파가 끝나면 맞는다는 것을 보였고, 이 그림은 값이 뒤로 가는 것을 보입니다.

sequenceDiagram
    participant 앱
    participant 사본A
    participant 사본B
    앱->>사본A: 11 로 바꿔라
    사본A-->>앱: 바꿨다
    앱->>사본B: 값을 달라
    사본B-->>앱: 10
    Note over 앱,사본B: 내가 쓴 것이 안 보인다
    앱->>사본A: 값을 달라
    사본A-->>앱: 11
    앱->>사본B: 값을 달라
    사본B-->>앱: 10
    Note over 앱,사본B: 방금 본 11 이 10 으로 되돌아간다

셋째가 제일 자주 말썽입니다. 사용자는 방금 한 일이 안 보이면 실패했다고 여기고 같은 일을 또 합니다. 어긋남 하나가 중복 요청을 부릅니다.

어긋남을 좁히는 약속 덧대기

전체를 엄하게 만들지 않고도 위의 어긋남을 상당 부분 없앨 수 있습니다. 한 사용자가 겪는 순서만 손보면 됩니다.

한 사용자의 요청을 늘 같은 사본으로 보내면 그 사용자 눈에 시간이 거꾸로 가지 않습니다. 앞 소절의 「시간이 거꾸로 가는 읽기」를 없애는 조치입니다. 이 약속이 단조 읽기입니다.

쓰기를 받은 사본에서 얼마 동안 읽게 하면 자기가 쓴 것은 언제나 보입니다. 「내가 쓴 것이 안 보임」을 없애는 조치입니다. 이쪽은 읽기 자기 쓰기 일관성입니다.

둘 다 다른 사용자가 보는 값까지 맞추지는 않습니다. 요청을 어느 사본으로 보낼지만 정하면 됩니다. 겉으로 느껴지는 어색함은 크게 줄어듭니다. 그래서 결과적 일관성 위에 덧대는 방식으로 흔히 씁니다.

두 곳에서 동시에 고쳤을 때의 충돌

장바구니에 담은 수량을 사본 둘에서 각자 고쳤다고 해 봅니다. 사본 둘이 서로 못 닿는 동안에는 이런 일이 안 막힙니다. 나중에 둘이 다시 닿으면 값이 두 개가 되어 있습니다.

flowchart TD
    W1["사본A · 수량을 2 로 고침"] --> M["끊겼던 사이 값이 둘로 갈림"]
    W2["사본B · 수량을 5 로 고침"] --> M

결과적 일관성은 「결국 하나로 모인다」고만 약속했습니다. 어느 값으로 모을지는 시스템이 따로 정해 줘야 합니다. 모으는 방식은 셋이고, 무엇을 남기느냐에 따라 치르는 대가가 갈립니다.

방식 무엇을 남기나 대가
늦게 쓴 쪽만 남긴다 시각이 늦은 쪽의 값 하나 진 쪽의 수정이 아무 표시 없이 사라집니다
둘 다 남겨 읽는 쪽에 넘긴다 두 값 모두 읽는 쪽 코드가 값이 둘인 경우를 다뤄야 합니다
정해 둔 규칙으로 합친다 규칙대로 합친 값 하나 쓸 수 있는 연산이 몇 가지로 줄어듭니다

첫째는 시각이 늦은 쪽만 남기는 방식입니다. 마지막 쓰기 승리라고 부릅니다. 규칙이 간단한 대신 진 쪽이 무엇을 고쳤는지는 어디에도 안 남습니다.

둘째는 두 값을 다 들고 있다가 읽는 쪽에 함께 건네는 방식입니다. 장바구니라면 두 목록을 합쳐 보여 주고 무엇을 뺄지 사람이 고르게 합니다.

셋째는 합치는 규칙을 데이터 모양에 미리 박아 두는 방식입니다. 「목록에 항목 하나 넣기」나 「숫자 더하기」처럼 어느 순서로 합쳐도 답이 같아지는 연산만 씁니다. 이렇게 만든 자료 구조를 CRDT(Conflict-free Replicated Data Type, 충돌 없는 복제 자료형)라고 부릅니다.

쓰는 데와 안 쓰는 데

가르는 물음은 하나입니다. 어긋난 값을 보고 내린 결정을 나중에 되돌릴 수 있나.

좋아요 수, 조회 수, 읽지 않은 알림 개수, 검색 결과 목록은 되돌릴 수 있습니다. 잠깐 다르게 보였다가 맞아도 업무가 깨지지 않습니다. 이런 데이터는 결과적 일관성으로 둡니다. 그러면 쓰기가 사본 하나에 닿는 즉시 끝나고, 사본 한 곳이 끊겨도 서비스가 이어집니다.

계좌 잔액, 마지막 한 개 남은 재고, 좌석 예약, 아이디 중복 검사는 되돌릴 수 없습니다. 두 사본이 각자 「아직 하나 남았다」고 답하면 같은 좌석이 두 사람에게 팔립니다. 이런 데이터에는 더 엄한 약속을 걸거나, 그 데이터만 한 곳에서 다루도록 몰아둡니다.

고르는 것이 아니라 떠안는 경우도 있습니다. 마이크로서비스처럼 서비스마다 저장소를 따로 두면 여러 저장소를 한 번에 묶는 수단이 없습니다. 주문은 끝났는데 결제 기록이 아직 없는 몇 초가 생깁니다. 그 몇 초를 업무 규칙으로 받아들여야 합니다. 이런 흐름을 단계로 쪼개 실패하면 앞 단계를 되돌리는 방식이 사가입니다.

선형화 가능성과 갈리는 대목

제일 엄한 약속은 선형화 가능성입니다. 흔히 강한 일관성이라고도 부릅니다. 쓰기가 끝난 뒤에 시작한 읽기는 그 값이나 그보다 나중 값을 반드시 본다는 약속입니다. 사본이 여럿이어도 밖에서 보기에는 한 대뿐인 것처럼 굴어야 합니다.

선형화 가능성 결과적 일관성
읽으면 무엇이 오나 언제나 최신 값 최신 값이거나 예전 값
사본 하나가 끊기면 쓰기가 멈출 수 있습니다 남은 사본이 계속 받습니다
쓰기 한 번에 드는 시간 사본들을 맞추는 만큼 사본 하나에 쓰는 만큼
짜는 쪽이 할 일 없습니다 어긋난 값을 다루는 코드를 씁니다

둘 사이가 비어 있는 것은 아닙니다. 원인과 결과의 순서만은 뒤집히지 않게 지키는 인과 일관성이 그 중간에 섭니다. 낡은 정도의 상한을 정해 두는 bounded staleness처럼 다른 중간 약속도 있습니다.

엄한 쪽으로 갈수록 사본을 더 많이 기다려야 합니다. 느슨한 쪽으로 갈수록 어긋남을 다루는 몫이 짜는 쪽으로 넘어옵니다.

관련 항목

맞세워지는 일관성 약속

선형화 가능성 · 강한 일관성 · 순차 일관성 · 인과 일관성 · 읽기 자기 쓰기 일관성 · 단조 읽기 · 단조 쓰기 · bounded staleness · 일관성

어긋남이 생겨나는 바탕 구조

복제 · 복제 지연 · 분산 시스템 · 비동기 복제 · 다중 리더 복제 · 리더 없는 복제 · 정족수 · 샤딩

어긋난 사본을 하나로 맞추는 방법

충돌 해소 · 마지막 쓰기 승리 · 버전 벡터 · 벡터 시계 · CRDT · 읽기 복구 · 안티 엔트로피 · 머클 트리 · 가십 프로토콜

읽기가 겪는 이상 현상

낡은 읽기 · 낡은 데이터 · 신선도 · 캐시 무효화 · 쓰기 후 읽기

느슨한 쪽을 고르게 만드는 제약

CAP · 네트워크 분단 · 가용성 · 지연 · PACELC · 고가용성

어긋남을 전제로 짜는 설계

마이크로서비스 · 사가 · 이벤트 소싱 · CQRS · 멱등성 · 보상 트랜잭션 · 아웃박스 패턴 · 2단계 커밋

이 약속을 채택한 저장 제품

Amazon DynamoDB · Cassandra · Riak · Amazon S3 · DNS · CDN · Elasticsearch

다른 이름: eventual consistency · eventually consistent · 최종 일관성 · 궁극적 일관성 · 결과적 정합성