사전 스플릿 브레인
문제

스플릿 브레인

gabury1고친 사람 github-actions[bot]

스플릿 브레인은 한 무리로 돌던 서버들이 둘로 갈라진 상태입니다. 갈라진 양쪽은 저마다 자기가 쓰기를 받을 쪽이라고 믿습니다. 두 쪽은 같은 데이터를 서로 모르게 고칩니다. 끊겼던 연결이 돌아와도 어느 쪽 데이터가 맞는지 기계가 고를 수 없습니다.

쉽고 빠른 이해

서버 여러 대가 한 팀으로 도는 시스템에는 쓰기를 혼자 받는 한 대가 있습니다. 그 한 대를 리더라고 부릅니다. 스플릿 브레인은 팀이 둘로 갈라져 양쪽이 각자 리더를 세워 버린 상태입니다. 데이터베이스 세 대 가운데 한 대만 쓰기를 받기로 해 뒀습니다. 그런데 네트워크가 끊긴 뒤 두 대가 동시에 쓰기를 받습니다.

갈라진 쪽에서는 상대가 죽은 것인지 안 보이는 것뿐인지 가릴 수 없습니다. 죽었다고 보면 새 리더를 세워야 합니다. 살아 있다고 보면 기다려야 합니다. 어느 쪽으로 정해도 틀릴 수 있는 이 갈림이 스플릿 브레인의 뿌리입니다.

어떻게 터지나:

  1. 서버 사이의 연결이 끊겨 무리가 둘로 갈립니다
  2. 양쪽이 상대가 죽었다고 보고 각자 리더를 세웁니다
  3. 리더 둘이 같은 데이터를 따로 고칩니다

무엇이 나빠지나. 연결이 돌아왔을 때 두 벌로 갈린 데이터를 기계가 합칠 수 없습니다. 한쪽을 버리면 그쪽에서 성공 응답을 받은 쓰기가 사라집니다. 사람이 두 벌을 견주어 손으로 맞춰야 합니다.

상세

한 부서에 부장은 한 사람이어야 합니다. 그런데 두 사무실을 잇던 전화선이 끊깁니다. 양쪽은 저쪽 부장이 없어졌다고 보고 각자 부장 대행을 세웁니다. 두 대행이 같은 예산을 따로 씁니다.

한 무리로 도는 노드 가운데 쓰기를 혼자 받기로 정해 둔 한 대를 리더라고 부릅니다. 스플릿 브레인은 그 무리가 둘 이상으로 갈라진 뒤, 갈라진 쪽마다 자기가 리더라고 믿게 된 상태입니다. 리더가 하나뿐이라는 전제 위에 얹혀 있던 규칙이 그 순간 깨집니다.

쓰기를 받는 노드가 둘이 됩니다. 둘은 서로의 쓰기를 보지 못합니다. 같은 행에 다른 값이 남습니다.

갈라지는 과정

뿌리는 분단입니다. 분단은 네트워크 장애로 노드들이 두 무리 이상으로 갈려 서로 메시지를 주고받지 못하게 된 상태입니다. 랙 두 개를 잇던 스위치가 죽어 한쪽 랙의 서버가 다른 쪽 서버를 못 보게 되는 일이 그것입니다.

갈린 쪽에서 보면 상대가 죽은 것인지 안 보이는 것뿐인지 가릴 방법이 없습니다. 죽은 노드도 답을 안 합니다. 살아 있지만 끊긴 노드도 답을 안 합니다. 기다리는 쪽에 도착하는 신호는 둘이 똑같습니다.

그래서 시스템은 타임아웃으로 판정합니다. 정해진 시간 안에 하트비트가 안 오면 죽은 것으로 봅니다. 하트비트는 살아 있음을 알리려고 짧은 간격으로 보내는 신호입니다.

여기까지는 설계대로 도는 것입니다. 갈라진 양쪽이 같은 판정을 동시에 내릴 때 어긋납니다. 양쪽이 각자 리더 선출을 열어 자기 쪽 리더를 세웁니다.

flowchart TD
    A["노드 다섯이 한 무리로 돈다"] --> B["링크가 끊겨 셋과 둘로 갈린다"]
    B --> C["셋 쪽 · 상대가 죽었다고 판정"]
    B --> D["둘 쪽 · 상대가 죽었다고 판정"]
    C --> E["갈래마다 리더를 세운다"]
    D --> E
    E --> F["리더 둘이 같은 데이터를 따로 고친다"]

갈라진 뒤에 남는 데이터 두 벌

앱은 자기가 닿는 쪽 리더에게 쓰기를 보냅니다. 두 쪽 모두 스스로는 멀쩡해 보입니다. 그래서 둘 다 성공을 돌려줍니다.

sequenceDiagram
    participant 앱1 as 한쪽 앱
    participant 리더1 as 한쪽 리더
    participant 리더2 as 다른 쪽 리더
    participant 앱2 as 다른 쪽 앱
    앱1->>리더1: 잔액을 100 으로 고쳐라
    리더1-->>앱1: 성공
    앱2->>리더2: 잔액을 300 으로 고쳐라
    리더2-->>앱2: 성공
    Note over 리더1,리더2: 서로의 쓰기를 보지 못한다

같은 계좌의 잔액이 한쪽에서는 100 이고 다른 쪽에서는 300 입니다. 두 값 모두 앱에게는 성공으로 확인된 값입니다.

어긋남은 연결이 돌아온 다음에 드러납니다. 두 갈래 모두 자기 쪽에서는 절차를 지켜 확정한 값입니다. 기계가 한쪽을 고를 근거가 없습니다.

흔한 수습은 한쪽을 버리는 것입니다. 버린 쪽에서 성공 응답을 받은 쓰기는 그대로 사라집니다. 데이터 유실이 여기서 납니다. 남은 일은 사람이 두 벌을 견주어 손으로 맞추는 것입니다.

터지는 조건 셋

셋이 함께 서야 합니다. 하나만 빠져도 스플릿 브레인은 안 납니다.

  1. 한 역할을 한 노드만 맡기로 한 시스템입니다. 쓰기를 아무 노드나 받는 시스템에는 빼앗길 리더가 없습니다
  2. 노드 사이의 메시지가 오가지 못합니다. 끊긴 시간이 판정 타임아웃을 넘깁니다
  3. 갈라진 쪽이 저마다 스스로 리더를 세울 수 있습니다

셋째가 급소입니다. 리더가 되는 데 다른 노드 과반의 동의를 받게 해 두면, 둘로 갈린 무리에서 양쪽이 동시에 과반을 쥘 수 없습니다. 조건 하나가 사라지므로 나머지 둘이 서도 안 터집니다.

리더 둘을 막는 장치

이름 붙은 장치가 셋 있습니다. 셋 다 분단 자체는 못 막습니다. 막는 것은 갈라진 양쪽이 동시에 리더가 되는 일입니다.

장치 무엇을 막나
정족수 과반을 못 쥔 쪽이 리더를 세우는 것
펜싱 물러난 리더가 저장소에 계속 쓰는 것
리스 기한이 지난 리더가 자격을 쥐고 있는 것

셋은 겹쳐 씁니다. 과반 규칙은 새 리더가 둘 생기는 것을 막습니다. 끊긴 쪽에 남은 옛 리더가 자기 상태를 아직 모르는 동안은 막지 못합니다. 그 틈을 펜싱과 리스가 닫습니다.

Raft 같은 합의 알고리즘은 과반 규칙을 알고리즘 안에 넣어 둡니다. 그런 저장소를 쓰면 리더 자격을 앱이 직접 챙기지 않아도 됩니다.

분단과 가르는 선

분단은 끊긴 상태 자체입니다. 스플릿 브레인은 그 끊김 위에서 양쪽이 각자 리더를 세웠을 때만 생깁니다. 과반 규칙이 서 있으면 분단이 나도 소수 쪽은 리더를 못 세웁니다.

그래서 둘은 빈도가 다릅니다. 분단은 링크가 돌아오면 끝납니다. 스플릿 브레인은 링크가 돌아온 다음부터 사람 손이 갑니다.

관련 항목

이 상태보다 먼저 오는 장애

분단 · 하트비트 · 타임아웃 · GC 멈춤 · 클럭 드리프트 · 정족수 상실

리더가 둘이 되는 것을 막는 장치

정족수 · 펜싱 · STONITH · 리스 · 합의 · Raft · Paxos

갈라진 뒤에 남는 손상

데이터 유실 · 쓰기 충돌 · 충돌 해소 · 재동기화 · 일관성

이 문제가 나는 시스템 구성

분산 시스템 · 클러스터 · 노드 · 복제 · 고가용성 · 페일오버 · 단일 장애점

리더를 정하고 바꾸는 절차

리더 선출 · 승격 · 좀비 리더 · 임기 · 장애 감지

이 문제를 다루는 제품

etcd · MongoDB · Elasticsearch · Redis · Kubernetes · PostgreSQL

이 문제를 놓고 갈리는 선택 축

CAP · 가용성 · 동기 복제 · 비동기 복제

다른 이름: 스플릿브레인 · split-brain