사전 복제 지연
개념

복제 지연

gabury1고친 사람 github-actions[bot]

복제 지연은 사본이 원본보다 얼마나 뒤처져 있는지를 잽니다. 원본에 쓴 값이 사본에 닿기까지는 시간이 걸립니다. 그동안 사본은 예전 값을 들고 답합니다. 이 값이 커질수록 사본을 읽는 쪽은 더 오래된 화면을 봅니다.

쉽고 빠른 이해

복제 지연은 사본이 원본보다 몇 걸음 뒤처졌는지를 재는 값입니다. 글을 올린 직후 새로고침했더니 그 글이 없다가 잠시 뒤 나타나는 일이 이 뒤처짐 때문에 벌어집니다.

사본은 틀린 값을 주지 않습니다. 자기가 아는 마지막 값을 정상 응답으로 줍니다. 그래서 뒤처짐은 오류 화면으로 드러나지 않고, 재 두지 않으면 아무도 모르는 채 커집니다.

도는 모양은 셋입니다.

  1. 원본이 바뀐 내용을 기록으로 남기고 사본에 보냅니다
  2. 사본이 받은 기록을 자기 데이터에 하나씩 반영합니다
  3. 아직 못 반영한 양이 남습니다. 그 양이 복제 지연입니다

대가가 있습니다. 뒤처짐을 없애려면 원본이 사본의 대답을 기다렸다가 응답해야 합니다. 그만큼 쓰기 한 번에 드는 시간이 길어집니다. 사본 한 곳이 굼떠지면 쓰기 전체가 거기에 묶입니다.

상세

마트에서 아침에 값이 바뀌면 계산대는 곧바로 새 값을 찍습니다. 진열대 가격표는 직원이 새 표로 갈아 끼울 때까지 어제 값을 달고 있습니다. 손님은 여느 때처럼 붙어 있는 그 표를 믿고 물건을 집습니다.

복제 지연은 같은 데이터를 여러 곳에 두는 복제 구조에서 사본이 원본의 변경을 아직 못 따라잡은 정도입니다. 원본 노릇을 하는 쪽이 리더입니다. 리더의 변경을 받아 적는 사본은 팔로워입니다.

리더는 자기 데이터를 고친 뒤 곧바로 응답합니다. 팔로워에게 알리는 일은 그 뒤로 미룹니다. 이렇게 뒤로 미루는 방식이 비동기 복제입니다. 복제 지연은 그 미룸이 얼마나 쌓였는지를 나타냅니다.

사본을 여러 벌 두는 까닭은 한 대가 죽어도 서비스가 이어지게 하려는 것입니다. 읽기 요청을 나눠 받게 하려는 것이기도 합니다.

읽기를 사본으로 보내는 순간부터 복제 지연은 사용자가 보는 화면의 나이가 됩니다. 그래서 이 수치는 안에서만 들여다보는 것으로 끝나지 않고 사용자 눈에 곧바로 드러납니다.

지연을 재는 두 잣대

시간으로 재는 쪽과 밀린 양으로 재는 쪽입니다.

잣대 무엇을 재나 무엇에 쓰나
시간 리더가 그 변경을 끝낸 때와 팔로워가 같은 변경을 반영한 때의 차이 사용자가 보는 값이 얼마나 낡았는지
밀린 양 팔로워가 아직 반영하지 못한 변경 기록의 분량 따라잡을 여력이 남았는지

시간 잣대는 읽는 쪽 이야기에 가깝습니다. 밀린 양 잣대는 굴리는 쪽 이야기에 가깝습니다.

둘이 같이 움직이지 않을 때도 있습니다. 쓰기가 한동안 없으면 밀린 양은 0인데도 마지막 변경 이후 흐른 시간은 계속 늘어납니다. 이때 재는 것은 한 변경의 지연이 아닙니다. 팔로워가 마지막으로 반영한 변경의 시각과 지금 사이의 거리입니다.

같은 이름으로 서로 다른 두 계산을 부르는 셈입니다. 표의 시간 잣대는 변경 하나를 두 시각으로 견줍니다. 이 계산은 마지막 변경 하나만 두고 지금 시각까지를 잽니다. 쓰기가 멎은 동안에는 뒤의 값만 늘어납니다.

마지막 쓰기 뒤 세 시점을 늘어놓으면 두 값이 갈라지는 모양이 보입니다.

flowchart TD
    subgraph S1["마지막 쓰기 1초 뒤"]
        A1["밀린 양 0건"]
        B1["시간 잣대 1초"]
    end
    subgraph S2["마지막 쓰기 10초 뒤"]
        A2["밀린 양 0건"]
        B2["시간 잣대 10초"]
    end
    subgraph S3["마지막 쓰기 60초 뒤"]
        A3["밀린 양 0건"]
        B3["시간 잣대 60초"]
    end
    S1 --> S2 --> S3

팔로워는 받은 것을 다 반영했습니다. 그런데도 시간 잣대만 보면 점점 더 뒤처진 것으로 읽힙니다.

쓰기가 사본에 닿는 경로

앱이 보내는 쓰기 한 번이 어디를 지나는지 보면 지연이 어느 구간에서 생기는지 잡힙니다. 여기서 앱은 사용자의 요청을 받아 데이터베이스에 말을 거는 쪽입니다.

sequenceDiagram
    participant 앱
    participant 리더
    participant 팔로워
    앱->>리더: 값을 바꿔라
    리더-->>앱: 바꿨다
    리더->>팔로워: 바뀐 내용 기록을 보낸다
    Note over 팔로워: 받은 기록을 순서대로 반영한다
    앱->>팔로워: 값을 달라
    팔로워-->>앱: 아직 반영 안 된 예전 값

리더는 자기 데이터만 고치고 곧바로 응답했습니다. 팔로워에게 보내는 일과 팔로워가 반영하는 일은 그 응답 뒤에 이어집니다. 앱이 그 틈에 팔로워를 읽으면 예전 값을 받습니다.

구간은 셋입니다. 리더가 변경을 기록으로 만드는 구간, 그 기록이 네트워크를 건너는 구간, 팔로워가 기록을 자기 데이터에 반영하는 구간입니다. 세 구간에서 걸린 시간을 더한 것이 복제 지연입니다.

기록이라고 부른 것은 WAL(Write-Ahead Log, 미리 쓰기 로그)처럼 변경을 일어난 순서대로 적어 둔 목록입니다. 팔로워는 이 목록을 순서대로 다시 실행해 리더와 같은 상태에 닿습니다. 순서를 지켜야 하므로 중간의 하나가 늦으면 그 뒤가 전부 뒤처집니다.

지연이 쌓이는 까닭

지연은 대개 어느 한 단계가 갑자기 커져서 생기지 않습니다. 들어오는 속도가 반영하는 속도를 넘어설 때 쌓입니다.

초당 100건이 들어오는데 팔로워가 초당 40건만 반영한다고 해 봅니다. 남는 60건이 초마다 쌓입니다.

flowchart TD
    subgraph T1["1초 시점"]
        I1["들어온 변경 100건"] --> D1["반영한 변경 40건"] --> L1["밀린 양 60건"]
    end
    subgraph T2["2초 시점"]
        I2["들어온 변경 200건"] --> D2["반영한 변경 80건"] --> L2["밀린 양 120건"]
    end
    subgraph T3["3초 시점"]
        I3["들어온 변경 300건"] --> D3["반영한 변경 120건"] --> L3["밀린 양 180건"]
    end
    T1 --> T2 --> T3

한 층 내려갈 때마다 밀린 양이 60건씩 불어납니다. 어느 단계가 갑자기 느려져서가 아닙니다. 두 속도의 차이가 그대로 쌓인 것입니다.

두 속도가 어긋나는 까닭은 여럿입니다.

까닭 어떻게 뒤처지나
쓰기가 몰린다 리더가 받는 변경이 팔로워가 반영하는 양보다 많아 목록이 길어집니다
한 덩어리 변경이 크다 대량 갱신 하나가 팔로워에서 오래 도는 동안 뒤의 변경이 기다립니다
팔로워가 읽기까지 받는다 조회 부하와 반영 작업이 같은 장비를 나눠 써서 반영이 뒤처집니다
네트워크가 멀거나 끊긴다 기록이 건너가는 구간이 길어지거나 한동안 멈춥니다

한 번 뒤처지기 시작하면 스스로 풀리지 않는 까닭도 같습니다. 밀린 양을 따라잡는 동안에도 새 쓰기는 계속 들어옵니다. 들어오는 속도가 반영하는 속도를 계속 웃돌면 지연은 줄지 않습니다. 쓰기가 잦아들 때까지 늘어납니다.

지연을 없애는 방식과 그 대가

지연을 거의 없애는 방법이 있습니다. 리더가 팔로워의 확인을 받은 뒤에 응답하는 것입니다. 이 방식이 동기 복제입니다.

sequenceDiagram
    participant 앱
    participant 리더
    participant 팔로워
    앱->>리더: 값을 바꿔라
    리더->>팔로워: 바뀐 내용 기록을 보낸다
    팔로워-->>리더: 반영했다
    리더-->>앱: 바꿨다

앞 그림과 견주면 화살표 하나의 자리만 다릅니다. 앱이 받는 응답이 팔로워의 확인 뒤로 물러났습니다. 그 물러난 만큼이 대가입니다.

대가는 쓰기 쪽이 집니다. 쓰기 한 번에 드는 시간이 가장 굼뜬 팔로워에 묶입니다. 팔로워 한 곳이 끊기면 그 확인이 오지 않아 쓰기가 멈춥니다.

한 곳이 멈춰도 서비스가 계속 답하는 성질을 가용성이라고 부릅니다. 동기 복제는 그 가용성을 깎아 지연을 산 것입니다.

그래서 대개는 비동기 쪽을 고릅니다. 지연을 없애는 대신 재서 관리합니다. 사본들이 언젠가 같은 값으로 모인다는 느슨한 약속만 두는 방식이 결과적 일관성입니다. 복제 지연은 그 「언젠가」가 지금 얼마나 되는지를 보여 주는 값입니다.

읽는 쪽이 겪는 어긋남

지연 자체는 수치입니다. 사용자가 만나는 것은 그 수치가 만든 화면입니다.

어긋남 무엇이 보이나
방금 쓴 것이 안 보인다 글을 올리고 목록을 열었는데 그 글이 없습니다
시간이 거꾸로 간다 두 번 읽었는데 두 번째가 더 예전 값입니다
화면끼리 안 맞는다 목록에는 있는 항목이 상세 화면에서는 없다고 나옵니다

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

둘째는 두 번의 읽기가 서로 다른 팔로워에 붙었을 때 생깁니다. 먼저 읽은 팔로워보다 나중에 읽은 팔로워가 더 뒤처져 있으면 시간이 되돌아간 것처럼 보입니다.

sequenceDiagram
    participant 앱
    participant 팔로워A
    participant 팔로워B
    앱->>팔로워A: 값을 달라
    팔로워A-->>앱: 5번까지 반영된 값
    앱->>팔로워B: 한 번 더 읽는다
    팔로워B-->>앱: 3번까지만 반영된 값
    Note over 팔로워B: 두 번째 응답이 더 예전 값이다

이렇게 예전 값이 돌아오는 응답을 낡은 읽기라고 부릅니다. 사본이 들고 있는 예전 값 자체는 낡은 데이터입니다.

지연을 안고 굴리는 법

복제 지연을 없앨 수 없다면 남는 일은 어디까지 허용할지를 정하는 것입니다. 정할 것은 넷입니다.

첫째, 한도를 정하고 넘은 사본은 읽기에서 뺍니다. 지연이 한도를 넘은 팔로워에게 읽기를 보내지 않으면 사용자가 보는 값의 나이에 상한이 생깁니다.

둘째, 방금 쓴 사용자만 리더로 보냅니다. 쓰기 직후 얼마 동안 그 사용자의 읽기를 리더가 받게 하면 자기가 쓴 것은 언제나 보입니다. 이 약속이 읽기 자기 쓰기 일관성입니다.

셋째, 한 사용자의 읽기를 늘 같은 사본에 붙입니다. 읽을 때마다 다른 팔로워에 붙으면 시간이 거꾸로 가는 화면이 나옵니다. 같은 사본에 붙여 그것을 막는 약속이 단조 읽기입니다.

넷째, 어긋나도 되는 화면과 안 되는 화면을 갈라 둡니다. 조회 수나 추천 목록은 잠깐 뒤처져도 업무가 깨지지 않습니다. 잔액이나 남은 재고는 그렇지 않습니다. 뒤처짐을 못 받아들이는 데이터만 리더에서 읽게 하면 나머지 읽기는 사본들이 나눠 받을 수 있습니다.

넷이 따로 선 규칙처럼 보이지만 읽기 한 건이 차례로 지나는 갈림길 하나입니다.

flowchart TD
    A["읽기 도착"] --> B{"방금 쓴 그 사용자인가"}
    B -->|예| L["리더에서 읽는다"]
    B -->|아니오| C{"뒤처지면 안 되는 데이터인가"}
    C -->|예| L
    C -->|아니오| D{"지연이 한도 안인 사본이 있나"}
    D -->|예| F["늘 붙던 그 팔로워에서 읽는다"]
    D -->|아니오| L

관련 항목

복제 지연이 생겨나는 복제 구조

복제 · 비동기 복제 · 동기 복제 · 리더 · 팔로워 · 읽기 복제본 · 핫 스탠바이 · 다중 리더 복제 · 리더 없는 복제 · WAL · 리플리케이션 슬롯 · 복제 토폴로지

복제 지연이 흔드는 일관성 약속

결과적 일관성 · 읽기 자기 쓰기 일관성 · 단조 읽기 · 선형화 가능성 · 강한 일관성 · 인과 일관성 · bounded staleness · 일관성 · 정족수

복제 지연 때문에 읽는 쪽이 만나는 이상 현상

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

복제 지연을 재고 지켜보는 수단

지연 · 처리량 · 모니터링 · 하트비트 · 체크포인트

복제 지연이 커질 때 위태로워지는 운영 동작

장애 조치 · 고가용성 · 복구 목표 시점 · 백업과 복구 · 스플릿 브레인

복제 지연을 안고 짜는 설계

샤딩 · 읽기 쓰기 분리 · 캐싱 · CQRS · 마이크로서비스

복제 지연을 두고 고르게 만드는 제약

CAP · PACELC · 네트워크 분단 · 가용성 · 분산 시스템

다른 이름: replication lag · replica lag · 리플리케이션 랙 · 레플리카 지연 · 복제 랙