본문으로 바로가기

25

최종적 일관성

방금 저장했는데 왜 안 보이는가 — 그리고 그게 왜 대개는 괜찮은가

먼저 보면 좋은 강의: 데이터베이스 복제, ACID 트랜잭션

단계별로 보기

스페이스바로 재생/정지, 좌우 화살표로 단계 이동, R로 처음으로 돌아갑니다.

대기 중1 / 4 단계 — 저장은 즉시 끝난다

사용자가 사진을 바꿉니다. 원본에 기록되고 곧바로 완료 응답이 갑니다. 여기서 원본은 사본들이 그 내용을 받았는지 확인하지 않습니다. 확인을 기다렸다면 훨씬 느렸을 것입니다. 이 선택이 다음 장면의 문제를 만듭니다.

이 강의를 마치면

  • 복제 지연이 존재하는 한 사본을 읽으면 옛날 값이 나올 수 있음을 설명한다.
  • '최종적으로 일치한다'는 약속이 정확히 무엇을 보장하고 무엇을 보장하지 않는지 구분한다.
  • 어떤 화면이 최신 값을 요구하고 어떤 화면이 조금 늦어도 되는지 판단한다.
  • 자기가 방금 쓴 것을 자기는 봐야 한다는 요구를 어떻게 만족시키는지 설명한다.

"저장했는데 안 보여요"

사용자가 프로필 사진을 바꿉니다. "저장 완료"가 뜹니다. 그런데 새로고침하니 옛날 사진이 그대로 있습니다. 다시 새로고침하니 이번엔 새 사진이 나옵니다.

사용자는 확신합니다. "이 서비스 고장 났네."

버그가 아닙니다. 복제가 정상 작동한 결과입니다. 그리고 이 현상을 이해하지 못하면 절대 고칠 수 없습니다.

무슨 일이 일어났나

0ms   사진 저장 요청 → 원본에 기록 → "완료!" 응답
10ms  사용자가 새로고침 → 사본을 읽음 → 아직 옛날 사진 ❌
80ms  원본의 변경이 사본에 도착
200ms 사용자가 다시 새로고침 → 사본을 읽음 → 새 사진 ✓

원본에 적힌 내용이 사본에 도착하기까지 이 예에서는 80밀리초가 걸렸습니다. 사용자는 하필 그 사이에 읽었습니다. 이걸 오래된 읽기(stale read)라고 합니다.

왜 시간이 걸릴까요. 사본이 다른 기계에 있기 때문입니다. 전기 신호가 케이블을 타고 가는 데에도 시간이 듭니다. 지연을 0으로 만드는 건 물리적으로 불가능합니다.

"최종적으로는 일치한다"

이 상태를 최종적 일관성(eventual consistency)이라고 부릅니다. 이름이 하는 약속을 정확히 읽어야 합니다.

약속하는 것: 더 이상 새로운 변경이 없다면, 언젠가는 모든 사본이 같은 값을 갖게 된다. 약속하지 않는 것: 그 '언젠가'가 언제인지. 그리고 그때까지 여러분이 무엇을 볼지.

두 번째 줄이 핵심입니다. 이 약속은 놀랍도록 약합니다. "결국엔 맞춰집니다"는 "지금 화면에 뭐가 보일지는 모릅니다"와 같은 말입니다.

더 이상한 일도 생깁니다. 사본이 여러 개면 어느 사본을 읽느냐에 따라 답이 다릅니다. 새로고침을 누를 때마다 새 사진과 옛 사진이 번갈아 나올 수 있습니다. 이걸 시간이 거꾸로 가는 읽기라고 부릅니다. 사용자에게는 유령이 나오는 것처럼 보입니다.

그런데 왜 이걸 견디는가

대안이 있긴 합니다. 원본이 모든 사본이 다 받았다고 확인할 때까지 응답을 미루는 것입니다. 그러면 지연이 사라집니다. 대신

  • 쓰기가 가장 느린 사본만큼 느려집니다.
  • 사본 하나가 죽으면 쓰기 전체가 멈춥니다.

즉 안전을 얻는 대가로 속도와 가용성을 냅니다. 그리고 대부분의 데이터에서는 이 거래가 손해입니다. 프로필 사진이 0.1초 늦게 보이는 것과, 사본 하나가 죽어서 서비스 전체가 멈추는 것 중 무엇이 더 나쁜가요.

그래서 화면마다 다르게 판단한다

"전부 최신"도 "전부 사본"도 답이 아닙니다. 화면마다 묻습니다.

화면0.1초 늦으면?읽을 곳
다른 사람 게시글 목록아무도 모른다사본
상품 카탈로그아무도 모른다사본
조회수·좋아요 수아무도 모른다사본
내가 방금 쓴 글고장으로 인식원본
결제 직후 잔액고객센터 전화원본
재고 확인 후 주문없는 물건을 판다원본

아래 세 줄에는 공통점이 있습니다. 본인이 방금 바꾼 것을 본인이 확인하는 상황입니다. 남의 변경이 늦게 보이는 건 알아채지 못하지만, 내 변경이 안 보이면 즉시 알아챕니다.

이 요구에는 이름이 있습니다. "자기가 쓴 것 읽기(read-your-writes)"입니다. 가장 흔한 해법은 단순합니다.

무언가를 쓴 직후 몇 초 동안, 그 사용자의 읽기는 원본으로 보낸다.

전체 트래픽에서 이런 요청은 극소수라 원본에 부담이 거의 없습니다. 나머지 압도적 다수의 조회는 계속 사본이 처리합니다. 좁게 예외를 두는 것이지, 포기하는 게 아닙니다.

정리

복제 지연은 없앨 수 있는 버그가 아니라 관리해야 할 성질입니다. 설계자가 할 일은 지연을 0으로 만드는 게 아니라, 어느 화면이 지연을 견디지 못하는지 골라내는 것입니다. 그 화면만 원본으로 보내면 됩니다.

아래 과제에서 원본만 바라보다 무너지는 시스템을 사본으로 살리되, 최신이어야 하는 화면은 남겨 두는 판단을 직접 해보세요.

이 강의가 단순화한 것

교육용 모형은 언제나 무언가를 생략합니다. 무엇을 생략했는지 아는 것도 학습의 일부입니다.

전제한 것

  • 복제 지연을 고정된 하나의 숫자로 둔다. 실제로는 쓰기 부하, 네트워크 상태, 사본의 처리 여력에 따라 밀리초에서 수 분까지 요동친다. 지연이 갑자기 튀는 순간이 사고의 대부분이다.
  • 이 강의의 시뮬레이터는 '오래된 읽기'가 몇 건 발생했는지를 세어 보여주지만, 실제로 옛날 값을 돌려주지는 않는다. 값의 내용이 아니라 그런 읽기가 일어난다는 사실만 모델링한다.
  • '자기가 쓴 것 읽기'를 위해 사용자별로 어느 요청이 최근에 쓰기를 했는지 추적하는 장치는 이 캔버스에 없다. 과제에서는 최신이 필요한 화면을 아예 별도의 경로로 나눠 표현한다.
  • 복제는 비동기이며 사본이 밀리는 일은 없다고 본다. 현실에서는 쓰기가 몰릴 때 사본이 따라잡지 못해 지연이 계속 벌어지기도 한다.

다루지 않은 것

  • 일관성 모델의 계층(선형화 가능성, 순차 일관성, 인과 일관성)
  • CAP 정리와 PACELC
  • 정족수(quorum) 읽기·쓰기와 R + W > N 조건
  • 벡터 시계와 충돌 해결, CRDT

더 읽을거리: Designing Data-Intensive Applications — Chapter 5: Replication · Werner Vogels — Eventually Consistent

이제 직접 만들어 보세요

읽어서 아는 것과 만들어서 아는 것은 다릅니다. 컴포넌트를 배치하고 트래픽을 흘려서 실제로 동작하는지 확인해 보세요.

실습 과제 열기

이 강의에 물어보기

하루 5번

이 강의 본문만 근거로 답합니다. 강의에 없는 내용은 지어내지 않습니다.