서론
이전 글에서는 동기 Replication과 비동기 Replication에서 쓰기 성공이 의미하는 범위를 살펴보았습니다.
비동기 Replication에서는 Primary가 Secondary의 처리를 기다리지 않고 성공을 반환할 수 있습니다.
이로 인해 쓰기 응답 시간을 비교적 짧게 유지할 수 있지만, Primary의 변경이 Secondary에 전달되기 전에 장애가 발생하면 성공한 쓰기가 Failover 이후 사라질 수 있습니다.
동기 Replication에서는 Primary가 설정된 Secondary 처리 단계까지 기다린 뒤 성공을 반환합니다.
그 대신 네트워크 전송, WAL 저장 또는 WAL Replay에 필요한 시간이 클라이언트의 쓰기 대기 시간에 포함됩니다.
하지만 어떤 Replication 방식을 선택하더라도 Primary가 더 이상 쓰기를 처리할 수 없는 상황은 발생할 수 있습니다.
이 경우 Secondary 중 하나를 새로운 Primary로 Promotion하여 쓰기 서비스를 복구해야 합니다.
장애 전
[Client] → [Primary]
│
├─→ [Secondary A]
└─→ [Secondary B]
장애 후
[Failed Primary]
[Client] → [Secondary A → New Primary]
│
└─→ [Secondary B]
구조만 보면 Secondary 하나를 새로운 Primary로 전환하면 끝나는 것처럼 보입니다.
하지만 안전한 Failover를 위해서는 다음 문제를 함께 해결해야 합니다.
- Primary가 실제로 장애 상태인지 어떻게 판단할 것인가?
- 여러 Secondary 중 어떤 노드를 Promotion할 것인가?
- Old Primary가 다시 쓰기를 처리하지 못하도록 어떻게 차단할 것인가?
- 여러 Secondary가 동시에 Promotion되는 것을 어떻게 방지할 것인가?
- 애플리케이션 연결을 어떻게 New Primary로 전환할 것인가?
- 나머지 Secondary는 어떻게 New Primary를 따라가게 할 것인가?
이번 글에서는 Primary 장애 감지부터 Secondary Promotion, Fencing, 애플리케이션 연결 전환까지 안전한 Failover에 필요한 전체 흐름을 살펴보겠습니다.
1. Primary 장애가 발생하면 어떻게 되는가
Primary-Secondary 구조에서는 정상 상태의 쓰기 요청이 Primary 하나로 전달됩니다.
[Application]
│
│ INSERT / UPDATE / DELETE
▼
[Primary]
따라서 Primary가 쓰기를 처리할 수 없게 되면 Secondary가 정상적으로 동작하고 있더라도 새로운 쓰기 요청을 처리할 수 없습니다.
Secondary는 Primary의 변경을 따라가고 있지만 아직 쓰기 권한을 가진 노드는 아니기 때문입니다.
Primary 장애 이후에도 서비스를 계속 제공하려면 Secondary 중 하나를 새로운 쓰기 주체로 전환해야 합니다.
이 과정을 Promotion이라고 합니다.
Secondary
↓ Promotion
New Primary
Promotion 이후에는 다음과 같이 역할이 바뀝니다.
장애 전
Primary
→ 쓰기 처리
Secondary A
→ Primary의 변경 적용
Secondary B
→ Primary의 변경 적용
장애 후
Old Primary
→ 쓰기 차단
Secondary A
→ New Primary로 Promotion
→ 쓰기 처리
Secondary B
→ New Primary의 변경 적용
그러나 Secondary를 Promotion하는 작업만으로 Failover가 완료되는 것은 아닙니다.
Old Primary가 실제로 쓰기를 중단했는지 확인해야 하며, 애플리케이션이 New Primary로 연결되도록 변경해야 합니다.
또한, 나머지 Secondary도 New Primary를 새로운 Replication 원본으로 사용하도록 재구성해야 합니다.
2. Primary 장애 판단이 어려운 이유
Primary의 응답이 없다고 해서 반드시 Primary 자체가 종료된 것은 아닙니다.
다음과 같은 여러 원인으로 Primary에 접근하지 못할 수 있습니다.
- 데이터베이스 프로세스가 종료되었습니다.
- 서버 전체가 종료되었습니다.
- 저장 장치에 문제가 발생했습니다.
- Primary와 애플리케이션 사이의 네트워크가 끊겼습니다.
- Primary와 장애 관리 계층 사이의 네트워크만 끊겼습니다.
- 일시적인 과부하로 응답이 지연되고 있습니다.
- 긴 Garbage Collection이나 디스크 지연이 발생했습니다.
- Connection 수가 한계에 도달했습니다.
헬스 체크가 한두 번 실패했다는 이유만으로 Secondary를 즉시 Promotion하면 일시적인 장애를 실제 Primary 장애로 오인할 수 있습니다.
반대로 장애 판단을 지나치게 오래 미루면 쓰기 서비스의 중단 시간이 길어집니다.
따라서 Failover 정책에서는 다음 기준을 정해야 합니다.
- 몇 번의 헬스 체크 실패를 장애로 판단할 것인가?
- 어느 시간 동안 응답이 없으면 장애로 판단할 것인가?
- 데이터베이스 프로세스와 서버 상태를 각각 확인할 것인가?
- 네트워크 장애와 데이터베이스 장애를 어떻게 구분할 것인가?
- 일부 애플리케이션에서만 접근할 수 없는 경우에도 Failover할 것인가?
- 누가 최종 Failover 권한을 가질 것인가?
중요한 것은 Primary에 접근할 수 없다는 사실과 Primary가 쓰기를 처리할 수 없다는 사실을 구분하는 것입니다.
예를 들어 장애 관리 시스템에서는 Primary에 접근할 수 없지만 일부 애플리케이션에서는 여전히 Primary에 접근할 수 있을 수 있습니다.
네트워크 단절
[관리 시스템] X──────────── [Primary]
↑
[Application]
이 상태에서 Secondary를 Promotion하면 기존 Primary와 새로운 Primary가 동시에 쓰기를 처리할 수 있습니다.
따라서 Failover를 시작하려면 기존 Primary가 더 이상 유효한 쓰기 주체가 아니라는 사실을 확인할 수 있어야 합니다.
3. 어떤 Secondary를 Promotion해야 하는가
Secondary가 여러 대라고 해서 모두 같은 데이터 반영 수준을 가지고 있는 것은 아닙니다.
다음과 같은 상황을 가정하겠습니다.
Primary : W1, W2, W3, W4
Secondary A : W1, W2, W3
Secondary B : W1, W2
Secondary A와 B 모두 Primary를 정상적으로 따라가고 있을 수 있습니다.
하지만 A는 W3까지 반영했고, B는 W2까지만 반영했습니다.
이 상태에서 Secondary B를 Promotion하면 Secondary A를 Promotion할 때보다 더 많은 변경이 New Primary에서 사라질 수 있습니다.
따라서 Promotion 대상을 선택할 때는 단순히 Secondary가 정상적으로 실행되고 있는지만 확인해서는 안 됩니다.
다음과 같은 Replication 상태를 비교해야 합니다.
- 마지막으로 수신한 Replication Log 위치
- 마지막으로 영구 저장한 로그 위치
- 실제 데이터에 적용한 로그 위치
- 적용을 기다리고 있는 로그의 양
- WAL 수신 또는 Replay 오류 여부
- Primary와의 Replication 연결 상태
- Promotion 가능한 상태인지 여부
- 다른 Secondary와의 데이터 반영 수준 차이
PostgreSQL 환경이라면 WAL을 어디까지 수신하고 Replay했는지를 비교할 수 있습니다.
Primary의 마지막 WAL 위치
↓
Secondary A의 WAL 수신 위치
↓
Secondary A의 WAL Replay 위치
Promotion 대상으로는 일반적으로 Primary의 변경을 가장 많이 반영한 Secondary를 선택해야 합니다.
다만 가장 많은 WAL을 수신한 Secondary가 항상 가장 적합한 것은 아닙니다.
WAL을 수신했지만 아직 데이터에 적용하지 못한 상태일 수 있고, Replay 오류가 발생했을 수도 있습니다.
따라서 다음 위치를 구분해서 확인해야 합니다.
WAL 수신 위치
→ Secondary가 어디까지 전달받았는가
WAL 저장 위치
→ Secondary가 어디까지 저장했는가
WAL Replay 위치
→ Secondary 데이터에 어디까지 적용했는가
어떤 위치를 기준으로 Promotion 후보를 선택할지는 사용하는 데이터베이스와 Failover 도구의 동작 방식에 따라 달라질 수 있습니다.
중요한 것은 모든 Secondary가 같은 상태라고 가정하지 않는 것입니다.
4. Promotion 대상은 하나만 선택되어야 한다
여러 Secondary가 동시에 자신을 New Primary로 Promotion하면 새로운 Split-Brain이 발생할 수 있습니다.
┌─→ Secondary A → New Primary A
Primary 장애 ────┤
└─→ Secondary B → New Primary B
관리 노드가 여러 대인 경우 각 관리 노드가 서로 다른 Secondary를 선택할 수도 있습니다.
관리 노드 1
→ Secondary A Promotion
관리 노드 2
→ Secondary B Promotion
이를 방지하려면 Failover를 수행할 권한을 하나의 주체에만 부여해야 합니다.
환경에 따라 다음과 같은 방법을 사용할 수 있습니다.
- 리더 선출
- 분산 잠금
- 합의 기반 클러스터 관리
- 임대 기반 권한
- 세대 번호
- Fencing Token
- 외부 관리 시스템의 단일 승인
중요한 것은 여러 관리 주체가 동시에 서로 다른 Promotion 결정을 내리지 못하도록 하는 것입니다.
Promotion 대상이 결정되었다면 다른 Secondary는 Promotion을 시도하지 않고 선택된 New Primary를 따라가야 합니다.
5. Split-Brain은 어떻게 발생하는가
Primary에 접근할 수 없다고 판단하여 Secondary를 Promotion했지만, Old Primary가 실제로 종료된 것은 아닐 수 있습니다.
예를 들어 네트워크 장애로 인해 Primary와 다른 데이터베이스 노드 사이의 연결만 끊겼다고 가정하겠습니다.
네트워크 분리
Old Primary X────────────── New Primary
↑ ↑
Client A Client B
Old Primary는 Client A의 쓰기를 계속 처리하고, New Primary는 Client B의 쓰기를 처리할 수 있습니다.
이처럼 두 노드가 동시에 자신을 Primary로 판단하고 서로 다른 쓰기를 처리하는 상황을 Split-Brain이라고 합니다.
Split-Brain이 발생하면 Old Primary와 New Primary의 변경 이력이 서로 다른 방향으로 진행됩니다.
공통 변경 이력
W1 → W2 → W3
│
├─ Old Primary: W4-old → W5-old
│
└─ New Primary: W4-new → W5-new
공통 지점인 W3 이후에 두 Primary가 서로 다른 쓰기를 처리한 것입니다.
이 경우 다음과 같은 문제가 발생할 수 있습니다.
- Old Primary에만 존재하는 변경이 생깁니다.
- New Primary에만 존재하는 변경이 생깁니다.
- 같은 Row가 양쪽에서 서로 다른 값으로 변경될 수 있습니다.
- 동일한 Unique Key가 양쪽에서 별도로 생성될 수 있습니다.
- 어느 쪽의 변경을 유지해야 하는지 자동으로 결정하기 어렵습니다.
- 두 WAL 이력을 단순히 이어 붙일 수 없습니다.
예를 들어 Old Primary에서는 주문 상태가 PAID로 변경되고, New Primary에서는 같은 주문 상태가 CANCELLED로 변경될 수 있습니다.
Old Primary
Order O-100 → PAID
New Primary
Order O-100 → CANCELLED
두 변경이 모두 데이터베이스 관점에서는 정상적으로 Commit되었더라도 최종 업무 상태를 자동으로 결정할 수는 없습니다.
Primary-Secondary 구조가 유지해야 하는 핵심 조건은 다음과 같습니다.
어떤 장애 상황에서도 유효한 쓰기 주체는 하나여야 한다.
따라서 New Primary를 Promotion하기 전후로 Old Primary의 쓰기 권한을 확실하게 제거해야 합니다.
6. Fencing이란
Old Primary가 더 이상 쓰기를 처리하지 못하도록 차단하는 절차를 Fencing이라고 합니다.
Fencing의 목적은 단순히 Old Primary를 Replication 구조에서 제외하는 것이 아닙니다.
Old Primary가 어떤 경로를 통해서도 새로운 쓰기를 승인하지 못하도록 만드는 것입니다.
Old Primary
│
X 쓰기 차단
환경에 따라 다음과 같은 Fencing 방법을 사용할 수 있습니다.
- Old Primary의 데이터베이스 프로세스 강제 종료
- Old Primary 서버 강제 종료
- Old Primary의 네트워크 차단
- 공유 스토리지 접근 권한 제거
- 데이터베이스 Proxy에서 Old Primary 주소 제거
- Service Discovery에서 Old Primary 제거
- 클라우드 관리 계층에서 인스턴스 중지
- 리더 임대 또는 세대 번호 무효화
- Fencing Token을 이용한 쓰기 권한 검증
이 방법들은 구현 방식은 다르지만 목적은 같습니다.
New Primary가 쓰기를 시작하기 전에 Old Primary의 쓰기 권한이 제거되었다는 것을 보장해야 한다.
애플리케이션의 연결 대상에서 Old Primary를 제거하는 것만으로는 충분하지 않을 수 있습니다.
기존 Connection Pool에 Old Primary 연결이 남아 있을 수 있고, 관리되지 않는 다른 클라이언트가 직접 Old Primary에 연결할 수도 있기 때문입니다.
따라서 가능하면 데이터베이스 프로세스, 서버, 네트워크 또는 스토리지 계층에서 Old Primary의 쓰기를 차단해야 합니다.
7. Fencing과 WAL은 서로 다른 문제를 해결한다
Fencing과 WAL은 모두 Failover 과정에서 중요하지만 서로 다른 문제를 해결합니다.
Fencing
→ Old Primary가 새로운 쓰기를 만들지 못하게 차단한다.
WAL
→ 이미 발생한 변경 이력을 확인하고 데이터를 Recovery한다.
WAL이 존재한다고 해서 Old Primary와 New Primary가 동시에 쓰기를 처리해도 안전한 것은 아닙니다.
Split-Brain 상태에서 양쪽이 각각 WAL을 생성하면 공통 지점 이후의 이력이 서로 갈라집니다.
W1 → W2 → W3
│
├─ Old Primary WAL
└─ New Primary WAL
WAL은 각 데이터베이스에서 발생한 변경을 기록하지만, 서로 충돌하는 두 업무 결과를 자동으로 병합하지는 않습니다.
따라서 먼저 Fencing을 통해 더 이상의 이력 분기를 차단해야 합니다.
그 후 Old Primary와 New Primary의 WAL 이력을 비교하고 Old Primary를 New Primary 기준으로 재동기화해야 합니다.
정리하면 순서는 다음과 같습니다.
1. Old Primary의 쓰기를 차단한다.
2. Secondary 하나를 New Primary로 Promotion한다.
3. New Primary를 유효한 쓰기 주체로 사용한다.
4. Old Primary의 WAL 이력을 확인한다.
5. Old Primary를 New Primary 기준으로 재동기화한다.
이번 글에서는 1번부터 3번까지를 다룹니다.
WAL Timeline 비교와 Old Primary 재동기화는 다음 글에서 자세히 살펴보겠습니다.
8. Secondary를 New Primary로 Promotion한다
Promotion할 Secondary와 Old Primary의 Fencing이 결정되면 선택한 Secondary를 New Primary로 전환합니다.
Secondary A
↓ Promotion
New Primary
Promotion 이후에는 다음 사항을 확인해야 합니다.
- New Primary가 실제로 쓰기 가능한 상태인가?
- New Primary가 새로운 변경 로그를 생성하고 있는가?
- Promotion이 중복으로 실행되지 않았는가?
- 다른 Secondary가 동시에 Promotion되지 않았는가?
- Old Primary의 쓰기가 차단되었는가?
Promotion 명령이 성공했다는 사실만으로는 충분하지 않습니다.
New Primary에 실제 쓰기 요청을 수행할 수 있는지 확인해야 합니다.
다만 검증을 위해 임의의 업무 데이터를 삽입하는 것은 위험할 수 있으므로 별도의 Health Check용 테이블이나 안전한 검증 방식을 사용할 수 있습니다.
Promotion이 완료되면 New Primary가 유효한 쓰기 주체가 됩니다.
하지만 애플리케이션이 여전히 Old Primary를 바라보고 있다면 서비스는 복구되지 않습니다.
다음으로 애플리케이션의 연결 대상을 변경해야 합니다.
9. 애플리케이션 연결을 New Primary로 전환한다
애플리케이션이 사용하는 쓰기 연결을 Old Primary에서 New Primary로 변경해야 합니다.
환경에 따라 다음과 같은 방법을 사용할 수 있습니다.
- DNS 변경
- Virtual IP 전환
- Service Discovery 갱신
- 데이터베이스 Proxy의 Primary 대상 변경
- Load Balancer 설정 변경
- 애플리케이션의 연결 정보 갱신
- 클러스터가 제공하는 단일 Endpoint 사용
전환 전
[Application] → [Old Primary]
전환 후
[Application] → [New Primary]
연결 주소만 변경했다고 해서 모든 요청이 즉시 New Primary로 전달되는 것은 아닙니다.
애플리케이션의 Connection Pool에 Old Primary와 연결된 기존 Connection이 남아 있을 수 있기 때문입니다.
Connection Pool
Connection 1 → Old Primary
Connection 2 → Old Primary
Connection 3 → New Primary
이 상태에서는 일부 요청이 계속 Old Primary로 전달될 수 있습니다.
따라서 다음과 같은 정책이 필요합니다.
- Old Primary와 연결된 Connection 폐기
- Connection Pool 재생성
- 연결 유효성 검사
- 실패한 쓰기 요청의 재시도
- 재시도 시 멱등성 보장
- 일정 시간 이상 대기하는 Connection 종료
- New Primary 연결 성공 여부 확인
특히 Failover 중에는 다음 상황이 발생할 수 있습니다.
1. 애플리케이션이 Old Primary에 쓰기 요청을 보낸다.
2. 요청 처리 중 연결이 끊어진다.
3. 애플리케이션이 요청 결과를 확인하지 못한다.
4. New Primary로 동일한 요청을 재시도한다.
첫 번째 요청이 Old Primary에서 이미 Commit되었다면 동일한 업무가 중복 처리될 수 있습니다.
따라서 이전 글에서 설명한 멱등성 설계가 Failover 과정에서도 중요합니다.
10. 나머지 Secondary의 Replication 원본을 변경한다
New Primary가 쓰기를 처리하기 시작하면 기존에 Old Primary를 따라가던 나머지 Secondary도 New Primary를 따라가도록 변경해야 합니다.
장애 전
Old Primary
├─→ Secondary A
└─→ Secondary B
장애 후
New Primary
└─→ Secondary B
Secondary B의 Replication 원본을 Old Primary에서 New Primary로 변경해야 합니다.
이 과정에서는 다음 사항을 확인해야 합니다.
- Secondary가 New Primary에 연결할 수 있는가?
- New Primary의 변경 로그를 정상적으로 수신하는가?
- 로그 저장 위치가 계속 전진하는가?
- 로그 Replay 위치가 계속 전진하는가?
- New Primary의 이력을 그대로 따라갈 수 있는 상태인가?
- 별도의 재동기화가 필요한가?
Secondary의 WAL 이력이 New Primary와 호환되지 않는다면 단순히 연결 대상만 변경해서는 안 됩니다.
Old Primary 또는 다른 Secondary가 New Primary와 다른 WAL 이력을 가지고 있다면 별도의 Recovery가 필요합니다.
이 내용은 다음 글에서 WAL Timeline과 함께 자세히 살펴보겠습니다.
11. 안전한 Failover의 전체 흐름
지금까지 살펴본 내용을 하나의 절차로 정리하면 다음과 같습니다.
1. Primary 이상 감지
↓
2. 실제 Failover가 필요한 장애인지 판단
↓
3. Old Primary의 쓰기 차단
↓
4. Secondary별 Replication 상태 비교
↓
5. Promotion 대상 Secondary 하나 선택
↓
6. 선택한 Secondary를 New Primary로 Promotion
↓
7. 애플리케이션 쓰기 연결 전환
↓
8. 기존 Connection Pool 정리
↓
9. 나머지 Secondary의 Replication 원본 변경
↓
10. New Primary와 Secondary 상태 확인
각 단계를 조금 더 구체적으로 살펴보겠습니다.
1단계: Primary 이상을 감지한다
데이터베이스 프로세스, 서버, 네트워크, 저장 장치 및 애플리케이션 연결 상태를 확인합니다.
단순한 응답 지연과 실제 장애를 구분할 수 있어야 합니다.
2단계: 실제 Failover가 필요한지 판단한다
일시적인 네트워크 지연이나 과부하를 Primary 장애로 잘못 판단하지 않도록 여러 상태 확인 결과를 사용합니다.
3단계: Old Primary를 Fencing한다
Old Primary가 더 이상 쓰기를 처리할 수 없도록 프로세스, 서버, 네트워크, 스토리지 또는 쓰기 권한을 차단합니다.
4단계: Secondary별 Replication 상태를 비교한다
각 Secondary가 Primary의 변경을 어디까지 수신하고 저장하고 적용했는지 확인합니다.
5단계: Promotion 대상을 하나만 선택한다
Primary의 변경을 가장 많이 반영했고 정상적으로 Promotion할 수 있는 Secondary를 하나 선택합니다.
여러 관리 주체가 서로 다른 Secondary를 선택하지 못하도록 제어해야 합니다.
6단계: Secondary를 New Primary로 Promotion한다
선택된 Secondary를 쓰기 가능한 New Primary로 전환합니다.
7단계: 애플리케이션 연결을 전환한다
애플리케이션과 데이터베이스 Proxy가 New Primary를 바라보도록 변경합니다.
8단계: 기존 Connection을 정리한다
Connection Pool에 남아 있는 Old Primary 연결을 제거하고 New Primary 연결을 생성합니다.
9단계: 나머지 Secondary의 원본을 변경한다
나머지 Secondary가 New Primary의 변경을 따라가도록 Replication 구성을 변경합니다.
10단계: 전체 상태를 확인한다
New Primary가 쓰기를 처리하는지 확인하고, 나머지 Secondary의 Replication 상태가 정상적으로 전진하는지 확인합니다.
12. 단계를 생략하면 어떤 문제가 발생하는가
Failover 절차 중 일부 단계를 생략하면 서비스가 일시적으로 복구된 것처럼 보여도 데이터베이스 구조에는 문제가 남을 수 있습니다.
| 장애 판단 검증 | 정상적인 Primary를 잘못 교체할 수 있습니다. |
| Old Primary Fencing | Old Primary와 New Primary가 동시에 쓰기를 처리할 수 있습니다. |
| Secondary별 Replication 상태 비교 | Primary의 변경을 충분히 반영하지 못한 Secondary가 Promotion될 수 있습니다. |
| Promotion 대상 단일화 | 여러 Secondary가 동시에 Primary로 Promotion될 수 있습니다. |
| 애플리케이션 연결 전환 | 일부 요청이 계속 Old Primary로 전달될 수 있습니다. |
| 나머지 Secondary의 원본 변경 | Secondary가 더 이상 WAL을 받지 못할 수 있습니다. |
| WAL Timeline 확인 | 서로 충돌하는 데이터 이력을 가진 노드가 Replication에 합류할 수 있습니다. |
| Old Primary 재동기화 | 오래된 데이터 상태가 Replication 구조에 남을 수 있습니다. |
| 업무 데이터 검증 | 데이터베이스는 정상이여도 주문·결제 상태가 실제 처리 결과와 다를 수 있습니다. |
| 최종 Replication 상태 확인 | 서비스는 재개됐지만 Replication 오류가 계속 남아 있을 수 있습니다. |
13. Failover의 완료 기준을 구분해야 한다
Failover의 완료를 단순히 “New Primary가 쓰기를 받기 시작했다”로 판단해서는 안 됩니다.
New Primary가 쓰기를 처리하는 시점과 전체 Primary-Secondary 구조가 정상화되는 시점은 다를 수 있기 때문입니다.
Failover 진행 상태는 다음과 같이 구분할 수 있습니다.
쓰기 서비스 복구
다음 조건을 만족하면 애플리케이션의 쓰기 서비스가 다시 제공되는 상태라고 볼 수 있습니다.
- New Primary가 쓰기 요청을 처리합니다.
- 애플리케이션이 New Primary에 연결됩니다.
- Old Primary로 향하는 연결이 차단됩니다.
- 핵심 읽기와 쓰기 기능이 정상적으로 응답합니다.
Replication 구조 복구
다음 조건까지 만족해야 Primary-Secondary 구조가 다시 정상적으로 구성되었다고 볼 수 있습니다.
- New Primary 하나만 쓰기를 처리합니다.
- 나머지 Secondary가 New Primary를 따라갑니다.
- Secondary의 로그 수신과 Replay 위치가 정상적으로 전진합니다.
- 잘못된 Promotion 후보나 오래된 연결이 남아 있지 않습니다.
Old Primary Recovery
Old Primary를 다시 Standby로 사용하려면 추가 작업이 필요할 수 있습니다.
- Old Primary와 New Primary의 WAL 이력을 비교합니다.
- WAL Timeline이 갈라졌는지 확인합니다.
- 필요한 경우 pg_rewind 또는 새로운 Base Backup을 수행합니다.
- Old Primary를 New Primary의 Standby로 다시 구성합니다.
이 단계는 Promotion 자체와 구분해야 합니다.
New Primary가 쓰기를 처리하기 시작했다고 해서 Old Primary의 Recovery까지 완료된 것은 아닙니다.
마무리
Primary에 장애가 발생하면 Secondary 하나를 New Primary로 Promotion하여 쓰기 서비스를 복구할 수 있습니다.
하지만 Failover는 Secondary를 Promotion하는 명령 하나로 끝나지 않습니다.
안전한 Failover를 위해서는 다음 절차가 필요합니다.
Primary 장애 판단
↓
Old Primary Fencing
↓
Secondary별 Replication 상태 비교
↓
Promotion 대상 단일 선택
↓
New Primary Promotion
↓
애플리케이션 연결 전환
↓
나머지 Secondary 재구성
이 과정에서 가장 중요한 것은 유효한 쓰기 주체를 하나로 유지하는 것입니다.
Old Primary가 완전히 종료되지 않은 상태에서 New Primary가 쓰기를 시작하면 두 노드가 동시에 쓰기를 처리하는 Split-Brain이 발생할 수 있습니다.
따라서 Promotion 전후로 Old Primary의 쓰기 권한을 제거하는 Fencing이 필요합니다.
또한, Fencing과 WAL의 역할도 구분해야 합니다.
Fencing
→ Old Primary가 새로운 쓰기를 만들지 못하게 한다.
WAL
→ 이미 발생한 변경 이력을 확인하고 데이터를 Recovery한다.
Fencing을 완료했다고 해서 Old Primary의 데이터가 자동으로 New Primary와 같아지는 것은 아닙니다.
특히 Fencing이 완료되기 전에 Old Primary가 일부 쓰기를 처리했다면 Old Primary와 New Primary의 WAL 이력이 서로 갈라졌을 수 있습니다.
이 상태의 Old Primary를 그대로 New Primary의 Standby로 연결해서는 안 됩니다.
다음 글에서는 PostgreSQL의 WAL Timeline을 통해 변경 이력이 어떻게 분기되는지 살펴보고, pg_rewind와 WAL Replay를 이용해 Old Primary를 New Primary의 Standby로 다시 구성하는 방법을 살펴보겠습니다.