사전 펜싱
개념

펜싱

gabury1고친 사람 github-actions[bot]

펜싱은 장애가 의심되는 서버가 공유 데이터에 더는 손대지 못하게 막는 일입니다. 새 서버가 일을 넘겨받기 전에 옛 서버의 손부터 묶어 둡니다. 죽은 줄 알았던 서버가 살아 있어도 데이터가 두 갈래로 갈리지 않습니다. 그래픽 드라이버에서는 같은 이름이 그래픽 카드의 작업 완료를 기다리는 일을 가리킵니다.

쉽고 빠른 이해

쓰기를 받던 서버가 바뀔 때 옛 서버가 더는 쓰지 못하게 막는 일입니다. 데이터베이스 주 서버가 응답을 멈추면, 대기 서버를 올리기 전에 옛 서버의 전원부터 끊어 둡니다.

응답이 없는 서버가 죽은 것인지 잠깐 멈춘 것인지는 밖에서 가릴 수 없습니다. 멈췄던 서버는 깨어나면 자기가 아직 주 서버인 줄 압니다. 그 서버가 계속 쓰면 두 서버가 같은 데이터를 따로 고칩니다. 펜싱은 옛 서버가 그렇게 믿어도 쓰기가 닿지 않게 합니다.

어떻게 도나:

  1. 쓰기를 받던 서버의 신호가 끊깁니다
  2. 그 서버를 끄거나 데이터로 가는 길을 막습니다. 막혔다는 확인을 받습니다
  3. 확인이 온 뒤에야 대기 서버를 새 주 서버로 올립니다

무엇이 나빠지나. 잠깐 느렸을 뿐인 멀쩡한 서버도 끊깁니다. 막는 장치가 고장 나면 넘겨받기가 멈춥니다.

쓰기를 서버 한 대만 받는 구성에서만 필요합니다. 여러 서버가 함께 쓰기를 받는 구성에는 막을 옛 서버가 없습니다.

상세

건물 경비를 교대하는데 옛 경비원과 연락이 끊겼습니다. 관리실은 새 경비원에게 열쇠를 주기 전에 옛 경비원의 출입증부터 막습니다. 옛 경비원이 그저 전화를 못 받은 것이어도 이제 건물에 들어오지 못합니다.

클러스터는 여러 서버를 한 시스템처럼 묶어 돌리는 구성입니다. 클러스터를 이루는 서버 한 대를 노드라고 부릅니다. 여러 대를 묶어 두면 한 대가 멈춰도 다른 노드가 일을 이어받을 수 있습니다.

여러 노드가 한 데이터베이스나 디스크를 함께 바라보는 일이 흔합니다. 이렇게 함께 쓰는 대상을 공유 자원이라고 합니다. 두 노드가 공유 자원에 동시에 쓰면 서로의 쓰기를 덮습니다.

이를 피하려고 공유 자원에 쓰기를 한 노드만 하도록 정해 두는 구성이 많습니다. 이 한 노드를 리더라고 부릅니다. 펜싱은 리더 자격을 잃은 노드가 공유 자원에 다시 손대지 못하게 떼어 내는 일입니다.

주 서버 한 대가 쓰기를 받고 대기 서버가 뒤에서 기다리는 데이터베이스가 그 예입니다. 이 구성에서 주 서버가 곧 리더입니다. 대기 서버는 리더의 뒤를 이을 대기 노드입니다.

리더가 응답을 멈추면 대기 노드가 쓰기를 넘겨받습니다. 이 넘겨받기 전체를 페일오버라고 합니다. 펜싱은 페일오버 안에서 옛 리더를 막는 단계입니다.

죽었는지 멈췄는지 가릴 수 없는 문제

노드가 살아 있는지는 하트비트로 봅니다. 하트비트는 노드가 일정한 간격으로 보내는 「살아 있다」는 신호입니다. 신호가 정해 둔 시간, 곧 타임아웃을 넘겨 끊기면 그 노드를 죽었다고 봅니다.

신호가 끊기는 까닭은 죽음만이 아닙니다. 네트워크가 끊기면 노드들이 서로 연락이 닿지 않는 두 무리로 갈립니다. 이것이 분단입니다. 노드는 멀쩡해도 반대편 무리에는 신호가 가지 않습니다.

프로그램이 잠깐 멈춰도 신호가 끊깁니다. 가비지 컬렉션(GC, Garbage Collection)은 안 쓰는 메모리를 거둬 가는 일입니다. 이 일이 프로그램을 몇 초씩 세우는 것을 GC 멈춤이라고 합니다. 밖에서 보면 죽음과 분단과 GC 멈춤이 똑같이 조용합니다.

죽은 줄 알았던 옛 리더가 깨어나도 그사이 새 리더가 섰다는 것을 모릅니다. 옛 리더는 자기가 아직 리더라고 믿습니다. 두 노드가 같은 데이터를 따로 고칩니다. 데이터가 두 벌로 갈린 이 상태가 스플릿 브레인입니다.

펜싱은 옛 리더의 믿음을 고치려 들지 않습니다. 옛 리더가 무엇을 믿든 그 쓰기가 공유 자원에 닿지 않게 합니다. 그래서 상대가 죽었는지 몰라도 안전하게 넘겨받을 수 있습니다.

승격보다 먼저 끝나는 펜싱

페일오버 가운데 대기 노드를 새 리더로 올리는 단계를 승격이라고 합니다. 펜싱은 승격보다 먼저 끝나야 합니다. 옛 리더를 막았다는 확인 없이 올리면 리더가 둘이 될 수 있습니다.

펜싱이 실패하면 승격하지 않고 다시 시도합니다. 그동안 쓰기는 멈춥니다. 서비스가 잠깐 멈추는 것은 기다리면 풀립니다. 두 벌로 갈린 데이터는 사람이 하나씩 맞춰야 합니다.

flowchart TD
    A["리더의 신호가 끊긴다"] --> B["옛 리더를 막는다"]
    B --> C{"막혔다는 확인이 왔나"}
    C -->|예| D["대기 노드를 승격한다"]
    C -->|아니오| E["승격하지 않고 다시 시도한다"]
    E --> B

막는 방법 셋

옛 리더를 어디서 막느냐에 따라 방법이 셋으로 갈립니다. 노드 자체를 끄는 방법, 공유 자원의 입구를 닫는 방법, 공유 자원이 요청에 붙은 번호(토큰)를 보고 가려 받는 방법입니다.

방법 어디서 막나 필요한 것
노드 펜싱 옛 리더 노드 자체 노드 전원을 바깥에서 끊는 장치
자원 펜싱 공유 자원의 입구 특정 노드의 접근을 끊을 수 있는 저장 장치나 스위치
펜싱 토큰 공유 자원 안 요청에 붙은 번호를 검사하는 저장소

표를 아래로 내려갈수록 막는 지점이 데이터에 가까워집니다. 데이터에 가까울수록 옛 리더를 건드릴 일이 줄어듭니다. 대신 공유 자원이 그만큼 일을 더 맡습니다.

노드 펜싱과 STONITH

노드 펜싱은 옛 리더를 강제로 끄거나 다시 켜는 방법입니다. 꺼진 노드는 아무것도 쓸 수 없습니다. 이 방법을 흔히 STONITH(Shoot The Other Node In The Head, 다른 노드의 머리를 쏜다)라고 부릅니다.

끄라는 요청을 옛 리더에게 보내서는 안 됩니다. 응답이 없는 노드라 요청을 받을 수 없기 때문입니다. 그래서 서버 바깥의 장치를 씁니다. 전원 콘센트를 원격으로 끊는 전원 장치나, 서버 본체와 따로 도는 원격 관리 보드가 그런 장치입니다.

자원 펜싱

자원 펜싱은 노드는 살려 두고 공유 자원으로 가는 길만 끊습니다. 저장 장치가 옛 리더의 쓰기를 거절하게 하거나, 저장 장치로 가는 스위치 포트를 닫습니다. 입출력(I/O, Input/Output)을 끊는다고 해서 I/O 펜싱이라고도 부릅니다.

노드가 살아 있으니 나중에 로그를 보고 원인을 찾기 쉽습니다. 대신 옛 리더가 쓰던 공유 자원이 여럿이면 모두 막아야 합니다. 하나라도 빠지면 그 자원으로는 옛 리더의 쓰기가 들어갑니다.

펜싱 토큰

펜싱 토큰은 리더 자격을 줄 때마다 하나씩 커지는 번호입니다. 리더는 공유 자원에 쓸 때마다 이 번호를 붙입니다. 공유 자원은 지금까지 본 가장 큰 번호를 기억합니다. 그보다 작은 번호가 붙은 요청은 거절합니다.

리더 자격은 대개 분산 락으로 줍니다. 분산 락은 여러 노드 가운데 한 대만 어떤 일을 하게 하는 잠금입니다. 이 락을 나눠 주는 쪽이 락 서비스입니다.

락에는 리스가 붙습니다. 리스는 만료 시각이 붙은 권리입니다. 락을 쥔 노드가 멈추면 시계가 만료 시각을 넘기는 순간 락이 저절로 풀립니다. 그러면 다른 노드가 락을 받습니다.

아래 그림은 멈췄던 노드가 깨어나 쓰려는 장면입니다. 노드 1 이 번호 33 과 함께 락을 받은 뒤 멈춥니다. 그사이 리스가 끝나 락은 번호 34 와 함께 노드 2 에게 넘어갑니다.

sequenceDiagram
    participant 락 as 락 서비스
    participant N1 as 노드 1
    participant N2 as 노드 2
    participant 저장소
    락->>N1: 락 · 번호 33
    Note over N1: 긴 멈춤에 빠진다
    Note over 락: 리스가 끝나 락을 거둔다
    락->>N2: 락 · 번호 34
    N2->>저장소: 쓰기 · 번호 34
    저장소-->>N2: 받는다 · 34 를 기억한다
    N1->>저장소: 쓰기 · 번호 33
    저장소-->>N1: 거절한다 · 34 보다 작다

깨어난 노드 1 은 자기가 아직 락을 쥐었다고 믿습니다. 번호 33 을 붙여 쓰기를 보냅니다. 저장소는 이미 34 를 봤으므로 이 쓰기를 거절합니다.

리스만으로는 이 틈을 못 막습니다. 노드 1 이 쓰기 직전에 리스가 남았는지 확인했을 수 있습니다. 확인과 쓰기 사이에 멈추면 그 확인은 낡은 것이 됩니다. 번호 검사는 쓰기가 도착한 순간 저장소에서 하므로 이 틈이 없습니다.

저장소가 관계형 데이터베이스라면 이 검사를 조건이 붙은 UPDATE 한 줄로 걸 수 있습니다. 행마다 마지막으로 쓴 번호를 함께 적어 둡니다. 아래는 번호 34 가 이미 적힌 행에 노드 1 이 번호 33 으로 쓰려는 장면입니다.

SQL
UPDATE orders
   SET status = 'PAID', token = 33
 WHERE id = 7
   AND token <= 33;  -- 저장된 34 → 0행

WHERE 조건이 저장된 번호보다 작은 번호의 쓰기를 걸러 냅니다. 옛 리더의 쓰기는 한 행도 바꾸지 못하고 끝납니다. 버전 번호를 비교해 늦게 온 쓰기를 막는 낙관적 잠금과 같은 꼴입니다.

펜싱 토큰은 리스처럼 만료 시각을 재지 않습니다. 번호의 크기만 봅니다. 대신 공유 자원이 번호 검사에 참여해야 합니다. 메일 발송이나 외부 결제 호출처럼 번호를 검사하지 않는 바깥 시스템은 이 방법으로 막을 수 없습니다.

정족수 · 리스와의 역할 분담

스플릿 브레인을 막는 장치는 펜싱 말고도 둘이 더 있습니다. 정족수는 결정에 필요한 최소 동의 수입니다. 대개 과반으로 잡습니다. 과반의 동의를 받은 쪽만 새 리더를 세우게 하면 새 리더가 둘 생기는 일은 막힙니다.

그래도 끊긴 쪽에 남은 옛 리더는 자기가 밀려난 줄 모릅니다. 리스는 옛 리더가 스스로 물러날 시각을 정해 줍니다. 펜싱은 그 시각을 못 지킨 옛 리더의 쓰기를 막습니다. 셋은 서로 다른 틈을 맡으므로 함께 씁니다.

대가

펜싱은 상대가 정말 죽었는지 모르는 채로 움직입니다. 그래서 틀린 판단의 비용을 떠안습니다.

대가 언제 드러나나
멀쩡한 노드가 꺼진다 잠깐 느렸을 뿐인 노드를 죽었다고 판정했을 때
두 노드가 서로를 끈다 두 대짜리 클러스터에서 둘 사이 연결만 끊겼을 때
페일오버가 멈춘다 펜싱 장치가 고장 나 막았다는 확인이 안 올 때
막히지 않는 자원이 남는다 펜싱 토큰을 검사하지 않는 자원에 쓸 때

두 대짜리 클러스터에서는 연결이 끊기면 양쪽 모두 상대가 죽었다고 봅니다. 두 노드가 동시에 서로의 전원을 끊으려 들면 둘 다 꺼질 수 있습니다. 이를 막으려고 투표만 하는 셋째 노드를 둡니다. 과반을 쥔 쪽만 펜싱할 수 있게 하는 구성입니다.

펜싱 장치가 고장 나면 펜싱이 끝나지 않습니다. 앞 절의 흐름대로라면 승격도 일어나지 않습니다. 장치 하나의 고장으로 페일오버 전체가 멈추는 것입니다.

이렇게 하나만 고장 나도 전체가 멈추는 부품을 단일 장애점이라고 합니다. 펜싱 장치가 단일 장애점이 되지 않게 장치를 둘 두기도 합니다. 하나가 실패하면 다른 하나로 시도합니다.

펜싱이 필요 없는 구성

펜싱은 쓰기를 한 노드만 받기로 한 구성에서만 필요합니다. 빼앗길 리더 자격이 있어야 막을 옛 리더도 생깁니다.

노드 여럿이 동시에 쓰기를 받는 구성에는 리더 자격이 없습니다. 이런 구성은 갈린 쓰기를 나중에 합치는 쪽으로 풉니다. 리더 없는 복제가 그런 구성입니다.

합의 알고리즘은 여러 노드가 과반의 동의를 모아 한 가지 결정에 함께 이르게 하는 방법입니다. 누가 리더인지도 이 방법으로 정합니다. Raft 가 널리 쓰이는 합의 알고리즘입니다.

합의 알고리즘은 펜싱과 같은 일을 알고리즘 안에서 합니다. 리더가 바뀔 때마다 커지는 번호를 모든 메시지에 싣습니다. Raft 에서는 이 번호를 텀이라고 부릅니다. 낡은 텀이 붙은 메시지를 받은 노드는 그 메시지를 따르지 않습니다.

펜싱을 따로 챙겨야 하는 것은 알고리즘이 관리하지 않는 자원에 쓸 때입니다. Raft 로 뽑은 리더가 바깥 데이터베이스에 쓰는 경우가 그렇습니다. 그 데이터베이스는 텀을 모르니 옛 리더의 쓰기도 받습니다. 이때는 앞의 펜싱 토큰 같은 장치를 따로 둡니다.

다른 분야의 펜스

펜싱이라는 이름은 다른 두 분야에서도 쓰입니다. 앞의 일이 끝나기 전에는 뒤의 일이 넘어오지 못하게 막는다는 뼈대는 같습니다. 앞 소절들은 분산 시스템에서 쓰는 뜻입니다.

그래픽 드라이버에서 펜스는 GPU(Graphics Processing Unit, 그래픽 처리 장치)에 맡긴 작업이 끝났는지 알려 주는 표시입니다. CPU(Central Processing Unit, 중앙 처리 장치)는 GPU 에 그리기 명령을 넘긴 뒤 기다리지 않고 다음 일을 합니다. 그래서 그 결과를 쓰기 전에 펜스를 보고 작업이 끝났는지 확인합니다.

화면에 프레임을 내보내기 전이나, 쓰던 버퍼를 다시 채우기 전에 이 확인을 거칩니다. 드라이버 쪽에서는 펜스를 주고받으며 작업 순서를 맞추는 일을 펜싱이라고 부릅니다.

CPU 쪽에서 펜스는 메모리 배리어의 다른 이름입니다. CPU 와 컴파일러는 빨리 돌려고 메모리 읽기와 쓰기의 순서를 바꾸기도 합니다. 메모리 배리어는 그 명령 앞의 읽기와 쓰기가 뒤의 것보다 먼저 끝나게 순서를 묶어 둡니다.

관련 항목

이것이 막으려는 장애

스플릿 브레인 · 분단 · GC 멈춤 · 클럭 드리프트

이것을 구현하는 방식

STONITH · 펜싱 토큰 · SCSI 영구 예약 · 워치독 타이머 · 셀프 펜싱

노드를 끌 때 쓰는 장치

IPMI · BMC · PDU · 아웃오브밴드 관리

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

정족수 · 리스 · 분산 락 · 합의 · Raft · Paxos · 텀 · 에포크

이것이 끼어드는 장애 조치 절차

페일오버 · 승격 · 리더 선출 · 하트비트 · 타임아웃 · 장애 감지기 · 고가용성 · 클러스터 · 단일 장애점

번호로 늦은 쓰기를 막는 이웃 기법

낙관적 잠금 · 버전 번호 · 조건부 쓰기 · compare-and-set

이것이 쓰이는 공유 자원

공유 디스크 · 관계형 데이터베이스 · 분산 파일 시스템 · 객체 스토리지

이것을 채택한 클러스터 소프트웨어

Pacemaker · Corosync · ZooKeeper · etcd

같은 이름을 쓰는 다른 분야의 동기화 장치

메모리 배리어 · GPU · 그래픽 드라이버 · 커맨드 서브미션 · vblank · 버퍼

다른 이름: fencing · 노드 펜싱 · 자원 펜싱 · I/O 펜싱