본문 바로가기

백엔드

[Primary-Secondary Replication 4] WAL Timeline과 pg_rewind를 이용한 Failover Recovery

서론

이전 글에서는 Primary 장애가 발생했을 때 Secondary를 New Primary로 Promotion하는 과정과 Split-Brain을 방지하기 위한 Fencing을 살펴보았습니다.

 

안전한 Failover를 위해서는 Old Primary가 더 이상 쓰기를 처리하지 못하도록 차단한 뒤, Primary의 변경을 가장 많이 반영한 Secondary 하나를 New Primary로 Promotion해야 합니다.

Primary 장애 판단
        ↓
Old Primary Fencing
        ↓
Secondary 상태 비교
        ↓
New Primary Promotion
        ↓
애플리케이션 연결 전환

 

이 과정을 완료하면 애플리케이션은 New Primary를 통해 다시 쓰기 요청을 처리할 수 있습니다.

 

하지만 쓰기 서비스가 재개되었다고 해서 전체 Primary-Secondary 구조가 복구된 것은 아닙니다.

장애가 발생한 Old Primary를 다시 Secondary로 사용하려면 Old Primary의 데이터 상태를 New Primary에 맞춰야 합니다.

 

특히 Fencing이 완료되기 전에 Old Primary와 New Primary가 각각 쓰기를 처리했다면 두 데이터베이스의 WAL 이력이 서로 다른 방향으로 진행되었을 수 있습니다.

공통 WAL 이력

W1 → W2 → W3
           │
           ├─ Old Primary: W4-old → W5-old
           │
           └─ New Primary: W4-new → W5-new

 

이 상태에서 Old Primary를 단순히 다시 실행해 New Primary의 Standby로 연결해서는 안 됩니다.

Old Primary와 New Primary의 WAL 이력이 마지막으로 일치했던 지점을 확인하고, Old Primary의 데이터 상태를 New Primary의 유효한 이력에 맞춰야 합니다.

 

이번 글에서는 PostgreSQL의 WAL Timeline과 pg_rewind, WAL Replay를 이용해 Old Primary를 New Primary의 Standby로 다시 구성하는 과정을 살펴보겠습니다.


1. Fencing 이후에도 Recovery가 필요하다

Fencing은 Old Primary가 새로운 쓰기를 처리하지 못하도록 차단하는 절차입니다.

Fencing
→ Old Primary의 새로운 쓰기 차단

 

Fencing이 성공하면 Old Primary와 New Primary에서 서로 다른 변경 이력이 추가로 생성되는 것을 막을 수 있습니다.

그러나 Fencing은 Old Primary의 데이터 상태를 New Primary와 같게 만들어 주는 작업은 아닙니다.

 

Old Primary는 장애가 발생한 시점에 따라 다음과 같은 상태일 수 있습니다.

New Primary보다 단순히 뒤처진 상태

Old Primary가 장애 이후 새로운 쓰기를 처리하지 않았다면 New Primary보다 일부 변경이 부족한 상태일 수 있습니다.

New Primary : W1 → W2 → W3 → W4 → W5
Old Primary : W1 → W2 → W3

 

이 경우 Old Primary에는 New Primary에서 발생한 W4와 W5가 반영되지 않았습니다.

New Primary와 다른 변경을 처리한 상태

Fencing이 완료되기 전에 Old Primary가 일부 쓰기를 계속 처리했다면 두 노드의 WAL 이력이 갈라질 수 있습니다.

W1 → W2 → W3
           │
           ├─ Old Primary: W4-old → W5-old
           │
           └─ New Primary: W4-new → W5-new

 

이 경우 Old Primary는 단순히 뒤처진 상태가 아닙니다.

공통 지점 이후 New Primary와 다른 변경을 처리한 상태입니다.

 

따라서 Old Primary를 다시 사용하기 전에 두 노드의 WAL 이력이 어떤 관계인지 확인해야 합니다.


2. WAL이란

PostgreSQL은 데이터 파일을 직접 변경하기 전에 변경 내용을 WAL에 먼저 기록합니다.

(WAL은 Write-Ahead Logging의 약자입니다.)

 

트랜잭션에서 발생한 변경을 WAL에 먼저 기록한 뒤 데이터 파일에 반영함으로써, 장애가 발생해도 WAL을 이용해 데이터베이스를 일관된 상태로 Recovery할 수 있습니다.

 

개념적인 흐름은 다음과 같습니다.

트랜잭션 변경 발생
        ↓
WAL 기록
        ↓
Commit
        ↓
데이터 파일 반영

 

Primary-Secondary Replication에서도 Primary의 WAL이 Secondary로 전달됩니다.

Secondary는 전달받은 WAL을 저장하고 Replay하여 Primary의 변경을 자신의 데이터에 반영합니다.

Primary
    │
    │ WAL 전송
    ▼
Secondary
    │
    │ WAL 저장
    ▼
WAL Replay
    │
    ▼
데이터에 변경 반영

 

WAL은 다음과 같은 역할을 담당합니다.

  • Primary에서 발생한 변경 기록
  • 장애 이후 데이터베이스 Recovery
  • Secondary로 변경 사항 전달
  • 데이터 파일의 일관된 상태 복구
  • Old Primary와 New Primary의 변경 이력 비교

하지만 WAL이 존재한다고 해서 서로 다른 두 Primary의 변경을 자동으로 합칠 수 있는 것은 아닙니다.

WAL은 데이터베이스 변경을 기록하고 재현하는 자료이지, 서로 충돌하는 업무 결과 중 무엇이 올바른지를 판단하는 자료는 아니기 때문입니다.


3. WAL Timeline이란

PostgreSQL에서 Standby가 New Primary로 Promotion되면 새로운 Timeline이 만들어집니다.

Timeline은 Promotion이나 Recovery 이후 생성된 WAL 이력을 기존 이력과 구분하기 위한 개념입니다.

 

다음과 같은 상태를 가정하겠습니다.

Timeline 1

W1 → W2 → W3

 

W3까지 Primary를 따라가던 Standby가 Promotion되면 새로운 Timeline에서 WAL을 생성하기 시작합니다.

Timeline 1

W1 → W2 → W3
           │
           └─ Standby Promotion
                    ↓
               Timeline 2

               W4 → W5 → W6

 

Timeline 2는 Timeline 1의 W3 지점에서 시작된 새로운 WAL 이력입니다.

Promotion 이전까지는 Primary와 Standby가 같은 이력을 공유했습니다.

Promotion 이후에는 New Primary가 새로운 Timeline에서 변경을 처리합니다.

 

Timeline을 이용하면 다음 내용을 구분할 수 있습니다.

  • 어떤 WAL 이력이 기존 Primary의 이력인가?
  • Promotion은 어느 WAL 지점에서 발생했는가?
  • New Primary의 이력은 어디에서 시작되었는가?
  • Old Primary와 New Primary의 이력이 어디까지 일치하는가?
  • 두 이력이 어느 지점에서 갈라졌는가?

즉, Timeline은 Promotion 이후 어떤 WAL 이력을 유효한 데이터베이스 이력으로 사용할 것인지를 구분하는 기준입니다.


4. Fencing이 정상적으로 수행된 경우

Fencing이 정상적으로 수행되었다면 Promotion 이후 Old Primary는 새로운 쓰기를 처리하지 않습니다.

Promotion 이전

W1 → W2 → W3
Promotion 이후

Old Primary
W1 → W2 → W3
             X 추가 쓰기 없음

New Primary
W1 → W2 → W3 → W4-new → W5-new

 

이 경우 Old Primary는 New Primary보다 뒤처져 있지만, Promotion 이후 별도의 변경을 만들지는 않았습니다.

Old Primary의 데이터 상태를 New Primary의 Timeline에 맞춘 뒤 필요한 WAL을 Replay하면 Standby로 다시 구성할 수 있습니다.

 

다만 다음 조건을 확인해야 합니다.

  • Old Primary가 완전히 중지되어 있는가?
  • Old Primary에서 Promotion 이후 새로운 쓰기가 발생하지 않았는가?
  • New Primary의 WAL 이력을 따라가는 데 필요한 WAL이 남아 있는가?
  • Old Primary와 New Primary의 데이터 상태가 재동기화 가능한가?
  • Old Primary가 New Primary의 Timeline을 따라갈 수 있는가?

Fencing이 성공했다고 해서 Old Primary를 아무 확인 없이 바로 실행해도 된다는 의미는 아닙니다.

Old Primary의 데이터 상태와 WAL 이력을 먼저 확인해야 합니다.


5. Fencing 이전에 Old Primary가 쓰기를 처리한 경우

Fencing이 완료되기 전에 Old Primary와 New Primary가 모두 쓰기를 처리했다면 공통 WAL 지점 이후 서로 다른 변경 이력이 생성됩니다.

W1 → W2 → W3
           │
           ├─ Old Primary: W4-old → W5-old
           │
           └─ New Primary: W4-new → W5-new

 

Old Primary에는 New Primary가 알지 못하는 변경이 존재합니다.

New Primary에도 Old Primary가 알지 못하는 변경이 존재합니다.

 

예를 들어 다음과 같은 상황이 발생할 수 있습니다.

Old Primary

Order O-100
PAID
New Primary

Order O-100
CANCELLED

 

두 데이터베이스에서 각각 트랜잭션이 정상적으로 Commit되었더라도 최종 주문 상태를 자동으로 결정할 수는 없습니다.

 

또한, 다음과 같은 충돌도 발생할 수 있습니다.

  • 같은 Row가 서로 다른 값으로 변경됩니다.
  • 양쪽에서 동일한 Unique Key가 생성됩니다.
  • 한쪽에서는 데이터가 삭제되고 다른 쪽에서는 수정됩니다.
  • 서로 다른 Row의 변경을 합치면 업무 규칙이 깨집니다.
  • 재고나 잔액과 같은 업무 불변식이 위반됩니다.

따라서 WAL 이력이 갈라진 Old Primary를 그대로 New Primary의 Standby로 연결해서는 안 됩니다.

먼저 두 이력이 마지막으로 일치했던 지점을 찾고, Old Primary의 데이터 파일을 New Primary의 유효한 이력에 맞게 조정해야 합니다.


6. 유효한 WAL 이력을 선택해야 한다

Primary-Secondary Failover에서는 일반적으로 Promotion된 New Primary의 Timeline을 유효한 데이터베이스 이력으로 선택합니다.

Old Primary Timeline
→ 더 이상 유효한 쓰기 이력으로 사용하지 않음

New Primary Timeline
→ Failover 이후의 유효한 쓰기 이력으로 선택

 

애플리케이션은 Failover 이후 New Primary를 통해 새로운 쓰기를 계속 처리합니다.

따라서 클러스터를 다시 구성하려면 Old Primary의 데이터 상태를 New Primary의 Timeline에 맞춰야 합니다.

New Primary
→ 기준이 되는 Source

Old Primary
→ New Primary에 맞춰 조정할 Target

 

Old Primary에서 분기 이후 발생한 변경은 New Primary에 자동으로 추가되지 않습니다.

해당 변경은 다음 중 하나로 처리해야 합니다.

  • 유효하지 않은 이력으로 판단해 폐기합니다.
  • 별도로 추출해 업무적으로 분석합니다.
  • 외부 시스템 기록과 비교합니다.
  • 안전하게 재처리합니다.
  • 보상 트랜잭션을 수행합니다.
  • 운영자가 수동으로 상태를 조정합니다.

데이터베이스의 유효한 이력을 하나로 정하는 작업과 Old Primary에서만 성공한 업무를 처리하는 작업은 서로 구분해야 합니다.


7. pg_rewind란

PostgreSQL에서는 필요한 조건을 충족하는 경우 pg_rewind를 사용해 Old Primary를 New Primary의 Standby로 다시 구성할 수 있습니다.

pg_rewind는 Old Primary의 데이터 디렉터리를 New Primary의 유효한 이력에 맞게 조정하는 도구입니다.

 

일반적인 Failover Recovery에서는 다음과 같이 구성합니다.

Source
→ New Primary

Target
→ Old Primary

 

New Primary는 Failover 이후 유효한 WAL 이력을 가진 데이터베이스입니다.

Old Primary는 New Primary의 이력에 맞춰 다시 구성해야 하는 대상입니다.

 

pg_rewind는 두 클러스터의 Timeline History를 비교하여 WAL 이력이 갈라진 지점을 찾습니다.

그 후 Target인 Old Primary에서 분기 이후 변경된 데이터 블록을 Source인 New Primary의 상태에 맞게 조정합니다.

공통 지점 확인
        ↓
분기 이후 변경된 블록 확인
        ↓
Old Primary의 블록 조정
        ↓
New Primary의 이력에 맞춤

 

전체 데이터 디렉터리를 처음부터 복사하는 Base Backup과 달리, pg_rewind는 분기 이후 달라진 부분을 중심으로 조정할 수 있습니다.

 

다만 pg_rewind를 항상 사용할 수 있는 것은 아니며 운영 환경이 pg_rewind의 사용 조건을 충족하는지 사전에 확인해야 하며, 필요한 WAL이 확보되어 있어야 합니다.


8. pg_rewind를 이용한 재동기화 흐름

pg_rewind를 이용한 일반적인 재동기화 흐름은 다음과 같습니다.

1. Old Primary를 완전히 중지한다.
2. New Primary와 Old Primary의 Timeline을 비교한다.
3. WAL 이력이 갈라진 지점을 찾는다.
4. Old Primary의 데이터 파일을 New Primary 기준으로 조정한다.
5. 필요한 WAL을 확보한다.
6. WAL을 Replay한다.
7. Old Primary를 New Primary의 Standby로 시작한다.

 

각 단계를 살펴보겠습니다.

1단계: Old Primary를 완전히 중지한다

pg_rewind를 수행하는 동안 Old Primary에서 새로운 변경이 발생해서는 안 됩니다.

따라서 데이터베이스 프로세스를 완전히 중지하고 쓰기 요청이 전달되지 않도록 해야 합니다.

Old Primary
    │
    X 프로세스 중지
    X 네트워크 또는 쓰기 경로 차단

 

이 단계는 Fencing 상태가 계속 유지되고 있는지 확인하는 과정이기도 합니다.

2단계: Timeline History를 비교한다

New Primary와 Old Primary의 Timeline History를 비교하여 두 WAL 이력이 어디까지 일치하는지 확인합니다.

공통 이력

W1 → W2 → W3

 

W3가 마지막 공통 지점이라면 W3 이후의 변경 이력이 서로 다를 수 있습니다.

3단계: WAL 이력이 갈라진 지점을 찾는다

Old Primary와 New Primary가 마지막으로 같은 WAL 이력을 공유했던 지점을 찾습니다.

마지막 공통 지점
        ↓
       W3

 

이 지점 이후 Old Primary에서 변경된 데이터 블록은 New Primary의 상태와 다를 수 있습니다.

4단계: Old Primary의 데이터 파일을 조정한다

pg_rewind는 분기 이후 Old Primary에서 변경된 데이터 블록을 New Primary의 상태에 맞게 조정합니다.

Old Primary의 분기 이후 변경
        ↓
New Primary 상태를 기준으로 교체

 

이 과정에서 Old Primary의 분기 이후 변경은 유효한 데이터베이스 이력에서 제외됩니다.

5단계: 필요한 WAL을 확보한다

데이터 파일을 조정한 뒤에도 Old Primary가 New Primary와 같은 데이터 반영 수준에 도달하려면 필요한 WAL이 있어야 합니다.

필요한 WAL은 New Primary나 WAL Archive 등에서 확보할 수 있어야 합니다.

6단계: WAL을 Replay한다

확보한 WAL을 Replay하여 New Primary에서 발생한 변경을 Old Primary에 반영합니다.

필요한 WAL 확보
        ↓
WAL Replay
        ↓
New Primary의 변경 반영

7단계: Standby로 시작한다

데이터 파일 조정과 WAL Replay가 완료되면 Old Primary를 New Primary의 Standby로 시작합니다.

New Primary
    │
    │ WAL 전송
    ▼
Old Primary
→ New Standby

9. pg_rewind 이후에도 WAL Replay가 필요하다

pg_rewind가 완료되었다고 해서 Old Primary가 즉시 New Primary와 같은 데이터 반영 수준에 도달하는 것은 아닙니다.

 

pg_rewind는 Old Primary의 데이터 파일을 New Primary의 유효한 이력에 맞게 조정합니다.

이후 Old Primary가 일관된 상태에 도달하려면 분기 지점 이후 필요한 WAL을 확보하고 Replay해야 합니다.

pg_rewind 완료
        ↓
필요한 WAL 확보
        ↓
WAL Replay
        ↓
New Primary의 변경 반영
        ↓
Standby로 합류

 

예를 들어 W3 지점에서 WAL 이력이 갈라졌고, New Primary가 W8까지 진행되었다고 가정하겠습니다.

마지막 공통 지점
W3

New Primary
W4-new → W5-new → W6-new → W7-new → W8-new

 

Old Primary의 데이터 파일을 New Primary 기준으로 조정한 뒤 W4부터 W8까지 필요한 WAL을 Replay해야 New Primary의 이력을 따라갈 수 있습니다.

 

따라서 pg_rewind와 WAL Replay의 역할을 구분해야 합니다.

pg_rewind
→ 분기된 데이터 파일을 New Primary 기준으로 조정

WAL Replay
→ New Primary에서 발생한 변경을 적용

10. 필요한 WAL이 없다면 어떻게 되는가

Old Primary를 New Primary의 Standby로 구성하려면 분기 지점 이후 필요한 WAL이 있어야 합니다.

하지만 필요한 WAL이 이미 제거되었을 수 있습니다.

필요한 WAL
W4 → W5 → W6

현재 보관된 WAL
W7 → W8 → W9

 

W4부터 W6까지의 WAL이 제거되었다면 Old Primary는 필요한 변경을 Replay할 수 없습니다.

 

이러한 상황을 방지하려면 운영 환경에서 필요한 WAL을 충분한 기간 동안 보존해야 합니다.

 

다음과 같은 구성을 검토할 수 있습니다.

  • WAL Archive 운영
  • 충분한 WAL 보존 용량 설정
  • Replication Slot 사용
  • Failover와 재동기화 시간을 고려한 보존 기간 설정
  • WAL 보존 상태 모니터링

필요한 WAL이 제거되었거나 pg_rewind를 사용할 수 없는 경우에는 New Primary의 새로운 Base Backup을 이용해 Old Primary를 다시 구축해야 합니다.


11. Base Backup으로 Standby를 다시 구축한다

Base Backup은 New Primary의 데이터 상태를 기준으로 새로운 Standby를 처음부터 구성하는 방식입니다.

New Primary
    │
    │ Base Backup
    ▼
Old Primary의 기존 데이터 제거
    │
    ▼
새 Standby 구성

 

일반적인 흐름은 다음과 같습니다.

1. Old Primary를 완전히 중지한다.
2. Old Primary의 기존 데이터 디렉터리를 제거하거나 별도로 보관한다.
3. New Primary에서 Base Backup을 생성한다.
4. Base Backup을 Old Primary 서버에 배치한다.
5. New Primary를 Replication 원본으로 설정한다.
6. 필요한 WAL을 Replay한다.
7. Standby로 시작한다.

 

Base Backup은 전체 데이터셋을 다시 전송해야 하므로 데이터 크기가 크면 많은 시간이 걸릴 수 있습니다.

네트워크와 저장 장치 사용량도 증가할 수 있습니다.

 

하지만 Old Primary의 상태를 안전하게 재사용하기 어렵거나 필요한 WAL이 없는 경우에는 Base Backup이 더 확실한 재구축 방법이 될 수 있습니다.

 

pg_rewind와 Base Backup은 다음과 같이 구분할 수 있습니다.

pg_rewind
→ 기존 Old Primary의 데이터 파일을 활용
→ 분기 이후 달라진 부분을 중심으로 조정

Base Backup
→ New Primary의 전체 데이터 상태를 다시 복사
→ Standby를 처음부터 재구축

 

어떤 방식을 사용할지는 Old Primary의 상태, 필요한 WAL의 존재 여부, 데이터 크기와 Recovery 시간 요구사항을 기준으로 결정해야 합니다.


12. WAL은 두 Primary의 업무 결과를 자동으로 합치지 않는다

WAL은 데이터베이스에 발생한 변경을 기록하고 데이터 파일을 Recovery하는 데 사용하는 핵심 자료입니다.

하지만 Old Primary와 New Primary에서 각각 성공한 업무 트랜잭션을 의미에 맞게 자동으로 병합하지는 않습니다.

 

다음과 같은 상황을 가정하겠습니다.

Old Primary

Order O-100
PAID
New Primary

Order O-100
CANCELLED

 

Old Primary의 WAL에는 주문을 PAID로 변경한 기록이 존재합니다.

New Primary의 WAL에는 같은 주문을 CANCELLED로 변경한 기록이 존재합니다.

 

두 WAL을 모두 적용한다고 해서 올바른 주문 상태를 결정할 수 있는 것은 아닙니다.

 

다음과 같은 정보가 추가로 필요합니다.

  • 실제 결제가 승인되었는가?
  • 결제 시스템에서 취소가 완료되었는가?
  • 사용자가 어떤 요청을 먼저 보냈는가?
  • 외부 결제 시스템의 최종 상태는 무엇인가?
  • 재고 차감이나 환불이 수행되었는가?
  • 업무 규칙상 어떤 상태 전이가 유효한가?

WAL은 데이터베이스에 어떤 변경이 발생했는지는 보여줄 수 있지만, 업무적으로 어떤 결과를 유지해야 하는지를 판단하지는 않습니다.

 

Primary-Secondary Failover에서는 일반적으로 New Primary의 Timeline을 유효한 데이터베이스 이력으로 선택합니다.

따라서 Old Primary에서 분기 이후 발생한 변경은 다음과 같이 처리됩니다.

Old Primary의 분기 이후 변경
        ↓
New Primary에 자동 병합하지 않음
        ↓
폐기 또는 별도 업무 분석

13. 기술적 Recovery와 업무 Recovery를 구분해야 한다

Old Primary를 New Primary의 Standby로 다시 구성하는 것은 데이터베이스 수준의 Recovery입니다.

하지만 데이터베이스 Replication 상태가 정상화되었다고 해서 주문, 결제, 재고 같은 업무 상태까지 모두 정상이라는 의미는 아닙니다.

 

따라서 Recovery를 다음 두 가지로 구분해야 합니다.

기술적 Recovery

기술적 Recovery는 데이터베이스와 Replication 구조를 정상화하는 작업입니다.

다음과 같은 작업이 포함됩니다.

  • Old Primary Fencing
  • New Primary Timeline 선택
  • WAL Timeline 비교
  • pg_rewind
  • Base Backup
  • WAL Replay
  • Old Primary의 Standby 재구성
  • Replication 연결 상태 확인
  • WAL 수신 및 Replay 위치 확인

기술적 Recovery의 목표는 다음과 같습니다.

유효한 데이터베이스 이력을 New Primary 하나로 정하고, 나머지 노드가 해당 이력을 따라가도록 구성한다.

업무 Recovery

업무 Recovery는 Failover 과정에서 누락되거나 중복되거나 결과가 불확실해진 업무를 확인하는 작업입니다.

다음과 같은 작업이 포함될 수 있습니다.

  • 클라이언트 요청 로그와 데이터베이스 기록 비교
  • Old Primary에서만 성공한 트랜잭션 확인
  • New Primary에 존재하지 않는 요청 식별
  • 응답이 유실된 요청 확인
  • 중복 처리된 주문이나 결제 식별
  • 외부 결제 시스템과 처리 상태 대조
  • 재고와 주문 상태 비교
  • 누락된 요청 재처리
  • 보상 트랜잭션 수행
  • 수동 상태 조정
  • Recovery 결과와 감사 기록 작성

업무 Recovery의 목표는 다음과 같습니다.

데이터베이스의 물리적 상태뿐 아니라 실제 업무 결과를 정상적인 상태로 복구한다.

14. Old Primary에서만 성공한 쓰기를 확인해야 한다

Split-Brain이나 응답 유실이 발생했다면 Old Primary에서만 성공한 쓰기가 존재할 수 있습니다.

 

예를 들어 다음과 같은 상황을 가정하겠습니다.

1. Client가 Old Primary에 결제 완료 요청을 보낸다.
2. Old Primary에서 결제 상태가 PAID로 Commit된다.
3. 응답이 Client에 도착하지 않는다.
4. Secondary가 New Primary로 Promotion된다.
5. New Primary에는 해당 결제 변경이 존재하지 않는다.

 

이 경우 데이터베이스의 유효한 이력은 New Primary를 기준으로 구성되지만, 외부 결제 시스템에서는 실제 결제가 완료되었을 수 있습니다.

New Primary
결제 상태: PENDING

외부 결제 시스템
결제 상태: PAID

 

데이터베이스 Replication만으로는 이 불일치를 자동으로 해결할 수 없습니다.

다음과 같은 대조가 필요합니다.

  • 클라이언트의 요청 ID
  • 애플리케이션 처리 로그
  • Old Primary의 트랜잭션 기록
  • New Primary의 업무 데이터
  • 외부 결제 시스템의 거래 기록
  • 메시지 또는 이벤트 처리 기록
  • 재시도 및 멱등성 기록

확인 결과에 따라 다음 작업을 수행할 수 있습니다.

실제 결제는 완료됐지만 DB에 반영되지 않음
→ 안전한 재처리

결제가 중복 처리됨
→ 취소 또는 환불

업무 상태를 자동 판단할 수 없음
→ 운영자 수동 확인

 

따라서 Failover 이후에는 데이터베이스 상태뿐 아니라 장애 시간대에 처리된 주요 업무 요청도 함께 검증해야 합니다.


15. Recovery 완료 조건

Old Primary의 데이터베이스 프로세스가 다시 실행되었다는 사실만으로 Recovery가 완료된 것은 아닙니다.

최소한 다음 조건을 확인해야 합니다.

쓰기 권한

  • Old Primary가 더 이상 쓰기 권한을 가지고 있지 않습니다.
  • New Primary 하나만 쓰기 요청을 처리합니다.
  • 애플리케이션이 New Primary만 쓰기 대상으로 사용합니다.
  • Old Primary로 연결된 기존 Connection이 남아 있지 않습니다.

WAL 이력

  • New Primary의 Timeline이 유효한 데이터베이스 이력으로 선택되었습니다.
  • Old Primary와 New Primary의 WAL 분기 지점을 확인했습니다.
  • Old Primary의 데이터 파일이 New Primary 기준으로 조정되었습니다.
  • 필요한 WAL을 모두 확보했습니다.
  • 필요한 WAL Replay가 완료되었습니다.

Replication 상태

  • Old Primary가 New Primary의 Standby로 정상 연결되었습니다.
  • WAL 수신 위치가 지속적으로 전진합니다.
  • WAL Replay 위치가 지속적으로 전진합니다.
  • WAL 수신 또는 Replay 오류가 발생하지 않습니다.
  • New Primary와 Standby 사이의 Replication Lag이 허용 범위 안에 있습니다.

업무 데이터

  • 분기 과정에서 폐기될 수 있는 트랜잭션을 확인했습니다.
  • Failover 당시 결과가 불확실한 요청을 확인했습니다.
  • 중복되거나 누락된 주문과 결제를 확인했습니다.
  • 외부 시스템의 처리 상태와 대조했습니다.
  • 필요한 재처리와 보상 트랜잭션을 완료했습니다.
  • 수동으로 처리한 Recovery 내용을 기록했습니다.

이러한 조건을 만족해야 데이터베이스 구조와 업무 데이터가 모두 Recovery되었다고 판단할 수 있습니다.


16. Failover와 Recovery 단계의 역할

Failover와 Recovery에서 수행하는 각 작업은 다음과 같이 구분할 수 있습니다.

Fencing
→ Old Primary가 새로운 쓰기를 만들지 못하게 한다.

Promotion
→ Secondary 하나를 새로운 쓰기 주체로 정한다.

WAL Timeline 비교
→ Old Primary와 New Primary의 이력이 어디에서 갈라졌는지 확인한다.

pg_rewind
→ Old Primary의 데이터 파일을 New Primary 기준으로 조정한다.

WAL Replay
→ New Primary에서 발생한 변경을 Old Primary에 적용한다.

Base Backup
→ 기존 데이터를 활용하기 어렵다면 Standby를 처음부터 재구축한다.

업무 Recovery
→ WAL만으로 판단할 수 없는 주문·결제 등의 업무 상태를 확인한다.

 

전체 흐름은 다음과 같습니다.

Old Primary Fencing
        ↓
New Primary Promotion
        ↓
New Primary Timeline 선택
        ↓
Old Primary WAL 이력 확인
        ↓
pg_rewind 또는 Base Backup
        ↓
필요한 WAL Replay
        ↓
Old Primary를 Standby로 시작
        ↓
Replication 상태 확인
        ↓
업무 데이터 검증

마무리

Primary 장애 이후 Secondary를 New Primary로 Promotion하면 쓰기 서비스를 다시 제공할 수 있습니다.

하지만 Promotion은 Failover의 시작점일 뿐, 전체 Primary-Secondary 구조의 Recovery가 완료되었다는 의미는 아닙니다.

 

Old Primary를 다시 Standby로 사용하려면 Old Primary와 New Primary의 WAL 이력을 비교해야 합니다.

Fencing이 정상적으로 수행되어 Old Primary가 추가 쓰기를 처리하지 않았다면 Old Primary는 New Primary보다 단순히 뒤처진 상태일 수 있습니다.

 

반면 Fencing이 완료되기 전에 Old Primary와 New Primary가 각각 쓰기를 처리했다면 공통 지점 이후 WAL 이력이 서로 다른 방향으로 진행되었을 수 있습니다.

W1 → W2 → W3
           │
           ├─ Old Primary: W4-old → W5-old
           │
           └─ New Primary: W4-new → W5-new

 

이 상태의 Old Primary를 그대로 New Primary의 Standby로 연결해서는 안 됩니다.

New Primary의 Timeline을 유효한 데이터베이스 이력으로 선택하고, pg_rewind 또는 새로운 Base Backup을 이용해 Old Primary의 데이터 상태를 New Primary에 맞춰야 합니다.

 

pg_rewind와 WAL Replay의 역할도 구분해야 합니다.

pg_rewind
→ 분기된 데이터 파일을 New Primary 기준으로 조정한다.

WAL Replay
→ New Primary에서 발생한 변경을 적용한다.

 

또한 WAL은 Old Primary와 New Primary에서 각각 성공한 업무 결과를 자동으로 병합하지 않습니다.

 

따라서 데이터베이스 구조를 정상화하는 기술적 Recovery와 주문, 결제, 재고 등의 상태를 확인하는 업무 Recovery를 별도로 수행해야 합니다.

 

결국 Failover Recovery의 목표는 다음과 같습니다.

유효한 데이터베이스 이력을 New Primary 하나로 정하고, 모든 Secondary가 해당 이력을 따라가도록 구성한다.

하지만 Recovery 절차가 문서로 작성되어 있다는 사실만으로 실제 장애 상황에서 정상적으로 동작한다고 볼 수는 없습니다.

 

다음 글에서는 Primary와 Secondary의 Replication 상태를 관찰하기 위한 지표, 반드시 수행해야 하는 Failover 테스트, Primary-Secondary가 해결하는 문제와 해결하지 못하는 문제를 살펴보겠습니다.