---
title: "Orchestrator 기반 장애조치 설계: 토폴로지 탐색과 승격 규칙"
description: "Orchestrator의 토폴로지 탐색, 장애 판정, 승격 규칙과 fencing·접속 경로 전환을 분리하여 안전한 MySQL 장애조치 설계를 정리한다."
tags: [ MySQL, 복제, 고가용성, 운영, DBA ]
image: "mysql-report-bg.png"
published: "2026-11-02"
updated: "2026-11-02"
author: "MySQL 기술 노트"
source_url: ""
---

# Orchestrator 기반 장애조치 설계: 토폴로지 탐색과 승격 규칙

MySQL 비동기 복제에서 장애조치는 단순히 replica의 `read_only`를 해제하는 작업이 아니다. 어느 서버에 어떤 트랜잭션이 남았는지 판정하고, 기존 writer의 쓰기를 차단한 다음, 승격된 서버 아래로 복제를 재구성하고 애플리케이션을 다시 연결해야 한다. 이 가운데 하나라도 빠지면 데이터가 서로 다른 두 writer가 생기거나, 데이터베이스 승격은 성공했지만 서비스는 계속 중단될 수 있다.

Orchestrator는 복제 관계를 탐색하고 장애를 분석하며 토폴로지를 복구하는 도구다. 그러나 업무 트랜잭션의 손실 허용 범위, 서버 격리 수단, 접속 경로의 최종 권한까지 자동으로 정의해 주지는 않는다. 이 글은 **탐색 → 판정 → 후보 선정 → 격리 → 승격 → 접속 전환**을 하나의 운영 계약으로 설계하는 방법을 다룬다.

> 적용 범위: Community MySQL의 일반적인 source–replica 복제를 대상으로 한다. 2026-11-02 확인 기준 `openark/orchestrator` 저장소는 보관 처리되어 있으며 README는 유지보수 협업 대상으로 `percona/orchestrator`를 안내한다. 아래 제품 동작은 공개된 upstream 문서를 기준으로 설명한다. 실제 도입에서는 선택한 fork와 릴리스의 MySQL 지원 범위·옵션·권한을 다시 검증해야 한다. 본문의 SQL은 폐기용 MySQL 8.0에서 실행했지만, Orchestrator 설치·Raft 구성·실제 자동 승격·프록시 전환은 실험하지 않았다.

## 1. 토폴로지 탐색은 서버 목록이 아니라 관계와 시점의 수집이다

### 1.1 탐색 결과에 포함되어야 할 것

Orchestrator는 MySQL에 접속하여 복제 상태와 서버 설정을 읽고 source와 replica의 관계를 추적한다. 운영자가 처음 알려 준 서버에서 출발하더라도, 인접 서버를 발견하고 접속할 수 있어야 전체 그래프가 완성된다. 목록에 없다는 이유만으로 서버가 존재하지 않는다고 판단하면 안 된다.

실무에서는 다음 정보를 함께 해석한다.

- **식별 정보**: 접속 주소와 포트, `server_uuid`, `server_id`, 이름 해석 결과.
- **복제 관계**: 어떤 source를 따르는지, 채널 구성, 수신·적용 상태, GTID 또는 binlog 좌표.
- **승격 적합성**: binlog 기록, replica에서 적용한 변경의 binlog 재기록, 버전 호환성, 복제 필터, 읽기 전용 상태.
- **운영 정책**: 데이터센터·리전·장비 용량, 의도적 지연 replica, 백업 전용 서버, 승격 선호도.
- **관측 신선도**: 마지막 성공 수집 시각, 마지막 실패, DNS 변경 여부, 권한 부족으로 누락된 항목.

특히 동일 서버가 여러 이름으로 발견되거나, 재구축 과정에서 UUID가 중복되거나, 오래된 DNS가 다른 서버를 가리키면 토폴로지 그림은 그럴듯해도 제어 대상이 잘못될 수 있다. 호스트 이름의 정규화와 실제 접속 대상의 일치 검사는 장애조치 이전에 해결해야 한다.

### 1.2 탐색 주기가 곧 복구 시간은 아니다

`InstancePollSeconds`는 주기적 관측 간격을 정하는 설정이다. 이 값을 줄여도 MySQL 접속 시간 제한, replica의 source 단절 인지, 복구 차단 정책, fencing, relay log 적용, 연결 풀 갱신 시간까지 함께 줄어들지는 않는다. 오히려 대규모 토폴로지에서는 관측 부하가 늘 수 있다.

Raft 배포의 upstream 설계에서는 각 Orchestrator 노드가 독립적으로 MySQL 서버를 탐색하고 자신의 backend에 기록한다. 따라서 제어 노드 수가 증가하면 MySQL 측 모니터링 접속량도 증가한다. 관리용 연결 여유와 장애 시 수집 지연을 함께 설계해야 한다.

```mermaid
flowchart TD
    A[MySQL 상태와 복제 관계 수집] --> B[관측 신선도와 다중 관점 비교]
    B --> C{복구 대상 장애인가}
    C -->|판정 보류| D[추가 관측과 운영자 확인]
    C -->|대상| E[자동 복구 허용과 후보 적합성 확인]
    E --> F{기존 writer 격리 증거 확보}
    F -->|실패 또는 불명| D
    F -->|확인| G[후보 데이터 경계 확인과 승격]
    G --> H[replica 재연결과 쓰기 접속 경로 전환]
    H --> I[업무 검증과 기존 writer 격리 유지]
```

이 그림은 안전한 운영 절차의 설계도다. 모든 단계를 Orchestrator가 단독으로 수행한다는 뜻이 아니다. 특히 격리 확인과 애플리케이션 검증에는 외부 시스템이 필요하다.

## 2. 접속 실패와 writer 장애를 구분한다

Orchestrator의 장애 판정은 writer 한 대의 연결 성공 여부만 보지 않는다. upstream 문서의 `DeadMaster` 예에서는 writer에 접근할 수 없고 그 replica들도 복제 실패를 보이는 상황을 함께 판단한다. 반면 관리 노드에서는 writer에 접근하지 못하지만 replica가 정상적으로 복제 중인 `UnreachableMaster`는 같은 의미가 아니다.

예를 들어 관리 네트워크만 단절되었고 애플리케이션은 계속 writer에 쓰고 있다면, 별도 writer를 세우는 것은 가용성 개선이 아니라 데이터 분기의 시작이다. replica의 상태도 마지막 조회값이거나 단절 인지 전일 수 있으므로 관측 시각과 재확인을 함께 봐야 한다.

**장애 탐지와 자동 복구 허용도 서로 다른 상태다.** 자동 복구는 명시적으로 허용한 클러스터에 적용하는 방식이다. `RecoverMasterClusterFilters`, downtime, 전역 복구 활성화 상태, 최근 복구에 대한 차단 기간 등에 따라 장애가 탐지되어도 실행되지 않을 수 있다.

`RecoveryPeriodBlockSeconds`는 동일 클러스터에서 짧은 간격으로 반복 승격하는 현상을 줄이는 장치다. 이를 단순히 낮추면 반복 장애가 해결되는 것이 아니라 데이터 경계를 확인할 기회가 줄어들 수 있다. 수동 복구는 일부 자동 차단 조건을 우회하므로, “수동 명령이 실행된다”는 사실을 안전성 증거로 사용하면 안 된다.

## 3. 승격 규칙은 적합성 검사를 통과한 후보 사이의 정책이다

### 3.1 네 가지 규칙과 만료

upstream 문서에서 `register-candidate`로 등록하는 대표적인 promotion rule은 다음과 같다.

- `prefer`: 조건을 충족하는 후보 중 우선적으로 고려한다.
- `neutral`: 특별한 선호를 부여하지 않는다.
- `prefer_not`: 가능하면 다른 후보를 선택하지만 절대 금지는 아니다.
- `must_not`: 해당 서버를 승격 대상으로 사용하지 않도록 지정한다.

`prefer`는 “무조건 승격”이 아니다. 복제 필터 때문에 업무 데이터가 없거나, 필요한 변경을 아직 적용하지 않았거나, 다른 replica를 연결할 수 없는 구성이라면 선호도만으로 적격 후보가 되지 않는다. 또한 가장 최신인 replica가 전체 replica를 유지하기 좋은 후보와 항상 같지는 않다. upstream 복구 설명은 토폴로지 보존 가능성을 고려하고 필요하면 후속 승격 단계에서 선호 후보를 선택할 수 있다고 설명한다.

중요한 함정은 **규칙 등록이 영구 설정이라고 가정하는 것**이다. upstream 문서는 promotion rule이 한 시간 후 만료되므로 주기적으로 재등록하도록 안내한다. 사용하는 fork·릴리스의 만료 설정과 실제 조회 상태를 확인하고, 정책 갱신 작업이 중단되었을 때 경보를 발생시켜야 한다. 백업 전용 서버에 `must_not`를 한 번 등록한 것만으로 영구 제외를 보장할 수 없다.

### 3.2 후보 적합성을 먼저 정의한다

GTID 기반 복구 설계에서는 적어도 다음을 분리해서 점검한다.

1. **업무 데이터의 범위**: 복제 필터, 의도적 지연, 일부 스키마만 복제하는 서버인지 확인한다.
2. **실행 이력과 남은 적용량**: 수신된 GTID와 실행된 GTID를 구분하고, 다른 후보와의 누락·분기 이력을 비교한다.
3. **재연결 가능성**: 승격 후보의 `log_bin`과 `log_replica_updates`, binlog 보존 범위, GTID auto-position 구성을 확인한다. GTID가 기록되어 있어도 필요한 binlog가 이미 삭제되었으면 다른 replica가 증분으로 따라오지 못할 수 있다.
4. **업무 수용 능력**: 메모리, CPU, 연결 한도, 스토리지 지연, 장애 도메인과 네트워크 접근성을 확인한다.
5. **정책과 권한**: promotion rule, 허용 리전, 실제 승격에 필요한 권한을 확인한다.

문서에 나오는 과거 명칭 `log_slave_updates`, `MASTER_AUTO_POSITION`은 현재 MySQL의 `log_replica_updates`, `SOURCE_AUTO_POSITION`과 대응하지만, 이를 근거로 오래된 Orchestrator 바이너리가 최신 MySQL에서 자동으로 호환된다고 판단해서는 안 된다. 배포할 바이너리의 명령 호환성과 동적 권한을 별도 시험한다. 오래된 예제의 광범위한 `SUPER` 부여도 그대로 복사하지 않는다.

### 3.3 수신 완료와 적용 완료를 구별한다

장애 시 가장 앞선 replica에도 아직 적용하지 않은 relay log가 남을 수 있다. 이를 확인하기 전에 복제 정보를 초기화하면 복구에 필요한 변경을 잃을 위험이 있다. upstream에는 `FailMasterPromotionIfSQLThreadNotUpToDate`와 `DelayMasterPromotionIfSQLThreadNotUpToDate`처럼 적용 미완료 시 실패시키거나 기다리는 정책이 있고, 문서상 두 설정은 상호 배타적이다.

기다리는 정책을 택했다면 무제한 대기가 아니라 관측·중단·승인 기준을 함께 정의한다. 반대로 지연을 무시하고 승격하는 정책이라면 업무에서 허용한 RPO와 실제 누락 가능성을 기록해야 한다. `Seconds_Behind_Source = 0` 하나로 모든 source 커밋을 받았고 적용했다고 증명할 수는 없다.

## 4. 실행 가능한 진단과 GTID 정책의 축소 검증

다음 예제는 읽기 중심의 사전 점검이다. 운영 서버에서는 해당 Performance Schema 객체를 읽을 권한이 필요하다. 실행 결과의 설정값은 테스트 인스턴스 값이며, 권장 운영값이 아니다.

### 4.1 서버가 승격 후보의 전제 조건을 갖추었는가

```sql
SELECT VERSION() AS mysql_version,
       @@GLOBAL.gtid_mode AS gtid_mode,
       @@GLOBAL.enforce_gtid_consistency AS enforce_gtid_consistency,
       @@GLOBAL.log_bin AS log_bin,
       @@GLOBAL.log_replica_updates AS log_replica_updates,
       @@GLOBAL.read_only AS read_only,
       @@GLOBAL.super_read_only AS super_read_only;
```

실행 결과(MySQL 8.0.x):

```text
mysql> SELECT VERSION() AS mysql_version,
    ->        @@GLOBAL.gtid_mode AS gtid_mode,
    ->        @@GLOBAL.enforce_gtid_consistency AS enforce_gtid_consistency,
    ->        @@GLOBAL.log_bin AS log_bin,
    ->        @@GLOBAL.log_replica_updates AS log_replica_updates,
    ->        @@GLOBAL.read_only AS read_only,
    ->        @@GLOBAL.super_read_only AS super_read_only;

+---------------+-----------+--------------------------+---------+---------------------+-----------+-----------------+
| mysql_version | gtid_mode | enforce_gtid_consistency | log_bin | log_replica_updates | read_only | super_read_only |
+---------------+-----------+--------------------------+---------+---------------------+-----------+-----------------+
| 8.0.46        | OFF       | OFF                      |       0 |                   0 |         0 |               0 |
+---------------+-----------+--------------------------+---------+---------------------+-----------+-----------------+
1 row in set (0.00 sec)
```

검증용 서버는 binlog와 GTID 복제를 구성하지 않은 단독 서버다. `log_bin = 0`과 `gtid_mode = OFF`인 결과는 “이 진단 SQL이 실행되었다”는 증거이지, GTID 장애조치 후보로 준비되었다는 증거가 아니다. `read_only = 0` 역시 현재 역할의 안전성이나 접속 경로의 유일성을 증명하지 않는다.

### 4.2 수신 측과 적용 측을 따로 확인한다

```sql
SELECT CHANNEL_NAME, SERVICE_STATE,
       LAST_ERROR_NUMBER,
       RECEIVED_TRANSACTION_SET
FROM performance_schema.replication_connection_status
ORDER BY CHANNEL_NAME;

SELECT CHANNEL_NAME, WORKER_ID, SERVICE_STATE,
       LAST_ERROR_NUMBER
FROM performance_schema.replication_applier_status_by_worker
ORDER BY CHANNEL_NAME, WORKER_ID;
```

실행 결과(MySQL 8.0.x):

```text
mysql> SELECT CHANNEL_NAME, SERVICE_STATE,
    ->        LAST_ERROR_NUMBER,
    ->        RECEIVED_TRANSACTION_SET
    -> FROM performance_schema.replication_connection_status
    -> ORDER BY CHANNEL_NAME;

Empty set (0.00 sec)

mysql> SELECT CHANNEL_NAME, WORKER_ID, SERVICE_STATE,
    ->        LAST_ERROR_NUMBER
    -> FROM performance_schema.replication_applier_status_by_worker
    -> ORDER BY CHANNEL_NAME, WORKER_ID;

Empty set (0.00 sec)
```

복제를 구성하지 않은 테스트 서버에서는 두 조회 모두 `Empty set`이다. 이는 지연이 0이거나 복제 장애가 없다는 뜻이 아니라, 확인할 채널·worker 행이 없다는 뜻이다. 운영에서는 기대한 채널이 실제로 존재하는지 먼저 확인한다. 단일 스레드 적용 등 구성에 따라 worker 조회만으로 충분하지 않을 수 있으므로 coordinator와 채널 적용 상태, `SHOW REPLICA STATUS`도 함께 점검한다.

`RECEIVED_TRANSACTION_SET`은 수신 이력을 관찰하는 항목이지 적용 완료 집합이 아니다. 또한 서버 전체의 `gtid_executed`는 여러 채널이나 로컬 쓰기의 이력을 포함할 수 있다. 다중 source 구성에서 이를 특정 채널의 업무 범위와 무조건 동일시하면 안 된다.

### 4.3 선호 후보보다 데이터 경계가 우선임을 확인한다

다음 SQL은 **가정한 GTID 문자열로 운영 승인 조건을 검사하는 축소 예제**다. Orchestrator 내부 후보 선정 알고리즘을 구현한 것이 아니며, 실제 복제 서버를 조회하거나 승격하지 않는다. UUID도 설명용 값이다.

기준 이력은 동일 source의 1번부터 100번까지로 가정한다. 지연된 선호 후보, 기준과 일치하는 중립 후보, 승격 금지 후보, 추가 이력을 가진 후보를 비교한다. 여기서는 사전에 확정된 기준과 정확히 일치해야 한다는 보수적인 정책을 사용한다.

```sql
WITH baseline AS (
  SELECT 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa:1-100' AS required_set
), candidates AS (
  SELECT 'replica_a' AS candidate, 'prefer' AS promotion_rule,
         'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa:1-98' AS executed_set
  UNION ALL
  SELECT 'replica_b', 'neutral',
         'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa:1-100'
  UNION ALL
  SELECT 'replica_c', 'must_not',
         'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa:1-100'
  UNION ALL
  SELECT 'replica_d', 'prefer',
         'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa:1-100,bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb:1'
), checks AS (
  SELECT candidate, promotion_rule,
         GTID_SUBSET(required_set, executed_set) AS covers_required,
         GTID_SUBTRACT(required_set, executed_set) AS missing_gtids,
         GTID_SUBTRACT(executed_set, required_set) AS extra_gtids
  FROM candidates CROSS JOIN baseline
)
SELECT candidate, promotion_rule, covers_required,
       missing_gtids <> '' AS has_missing,
       extra_gtids <> '' AS has_extra,
       CASE
         WHEN promotion_rule = 'must_not' THEN 'BLOCK_RULE'
         WHEN missing_gtids <> '' THEN 'BLOCK_MISSING'
         WHEN extra_gtids <> '' THEN 'REVIEW_EXTRA'
         ELSE 'PASS_DATA_GATE'
       END AS data_gate
FROM checks
ORDER BY candidate;
```

실행 결과(MySQL 8.0.x):

위 SQL의 결과 표를 그대로 싣고, 긴 입력 문장만 반복하지 않았다.

```text
+-----------+----------------+-----------------+-------------+-----------+----------------+
| candidate | promotion_rule | covers_required | has_missing | has_extra | data_gate      |
+-----------+----------------+-----------------+-------------+-----------+----------------+
| replica_a | prefer         |               0 |           1 |         0 | BLOCK_MISSING  |
| replica_b | neutral        |               1 |           0 |         0 | PASS_DATA_GATE |
| replica_c | must_not       |               1 |           0 |         0 | BLOCK_RULE     |
| replica_d | prefer         |               1 |           0 |         1 | REVIEW_EXTRA   |
+-----------+----------------+-----------------+-------------+-----------+----------------+
4 rows in set (0.01 sec)
```

`replica_a`는 `prefer`여도 필요한 이력이 빠져 있다. `replica_c`는 기준 이력을 모두 가지고 있어도 금지 규칙으로 제외한다. `replica_d`의 추가 GTID는 자동으로 삭제하거나 승인할 대상이 아니라 출처를 조사할 대상이다. 실제 운영의 추가 GTID는 정당한 별도 source 이력일 수도 있고, replica의 임의 쓰기인 errant transaction일 수도 있다.

`replica_b`의 `PASS_DATA_GATE`는 이 문자열 비교 조건만 통과했다는 뜻이다. fencing, 업무 행 정합성, 용량, binlog 보존, 정책 유효기간을 검증하지 않았으므로 **승격 실행 허가가 아니다**. GTID가 같아도 복제 필터나 빈 GTID 처리 때문에 실제 데이터가 다를 수 있다.

더 근본적인 문제는 기준 집합을 어디서 얻느냐이다. writer가 완전히 소실되었을 때 남은 replica들의 합집합만으로 writer가 승인한 모든 커밋을 알 수는 없다. 비동기 복제에서 어떤 replica에도 전달되지 않은 커밋은 관측되지 않을 수 있다. 사전 기록한 기준, 복구 가능한 binlog, 업무 로그 및 승인된 손실 정책을 함께 사용해야 한다.

## 5. Raft와 fencing, 접속 경로 전환은 서로 다른 안전장치다

### 5.1 제어 노드의 합의가 데이터 커밋의 합의는 아니다

Orchestrator/Raft는 제어 계층의 leader와 제어 작업의 일관성을 관리한다. upstream은 과반수에 속한 leader만 변경을 수행하는 구조를 설명한다. 그러나 이 Raft 합의에 MySQL의 모든 업무 커밋이 들어가는 것은 아니다. 따라서 Orchestrator의 제어 계층이 정상이어도 비동기 복제의 RPO가 자동으로 0이 되지 않는다.

또한 소수 측 Orchestrator가 제어권을 잃었다는 사실과 기존 MySQL writer가 쓰기를 멈췄다는 사실은 다르다. 기존 writer에 애플리케이션이 계속 접속할 수 있으면 두 쓰기 경로가 공존할 수 있다.

### 5.2 격리는 명령 실행이 아니라 차단 효과로 확인한다

fencing은 기존 writer가 더 이상 업무 쓰기를 받아들이지 못하도록 만드는 조치다. 전원·인스턴스 제어, 네트워크 차단, 프록시 경로 제거 등 환경에 맞는 방법을 조합한다. 프록시에서 제거하더라도 직접 연결이나 기존 연결이 남아 있다면 충분하지 않다.

`read_only`와 `super_read_only`는 유용하지만, 도달할 수 없는 서버에는 적용할 수 없다. 이미 적용된 값이 재시작 후에도 유지되는지, 자동화나 높은 권한의 계정이 해제할 수 있는지도 별도 문제다. 그러므로 **접속 실패를 쓰기 불가능의 증거로 대체하지 않는다.**

안전성을 우선하는 설계에서는 `PreFailoverProcesses` 같은 사전 훅에서 격리의 성공 증거를 확인하고, 확인하지 못하면 복구를 중단한다. upstream 문서는 동기 사전 훅의 0이 아닌 종료 코드가 복구를 중단한다고 설명한다. 반면 명령 끝에 `&`를 붙인 비동기 훅의 실패는 무시되므로 핵심 안전 검사에 사용하면 안 된다. 실제 훅의 시간 제한·재시도·외부 제어 API 장애 동작은 선택한 배포판에서 검증해야 한다.

### 5.3 승격 후 실패는 단순 롤백 문제가 아니다

Orchestrator는 토폴로지 복구 이후 훅을 통해 DNS, 프록시 또는 서비스 검색 체계와 연동할 수 있다. 업무 접속 경로 전환은 대체로 운영자가 구현하는 책임 영역이다.

후보 승격은 끝났지만 프록시 갱신에 실패한 경우, 데이터베이스 역할 변경과 서비스 노출 상태가 불일치한다. 이때 기존 writer를 다시 열어 “원상복구”하면 이미 새 writer에서 승인된 쓰기와 충돌할 수 있다. 기존 writer는 격리한 채 새 writer의 신원을 확인하고 접속 경로 수렴을 재시도하는 절차가 필요하다.

훅은 중복 실행에도 같은 결과로 수렴하도록 작성한다. 클러스터와 복구 작업의 식별자, 대상 writer, fencing 결과, 경로 갱신 결과를 기록한다. DNS TTL이 지났다고 기존 TCP 연결이 이동하지는 않으므로 연결 풀 폐기·재연결 정책과 트랜잭션 재시도를 함께 시험한다. 커밋 응답을 받지 못한 요청은 실행 여부가 불명확할 수 있으므로 업무 키 기반의 멱등 처리도 필요하다.

## 6. Aurora에는 같은 제어 모델을 그대로 이식하지 않는다

Aurora의 클러스터 내부 reader는 Community MySQL의 독립된 datadir와 binlog 복제 토폴로지를 그대로 옮긴 구조가 아니다. writer 승격은 관리형 제어 계층이 담당한다. 내부 장애조치를 Orchestrator가 직접 재배선하도록 설계하지 않는다.

Aurora에서는 replica의 promotion tier와 가용 영역 배치, 인스턴스 크기, cluster endpoint, 애플리케이션 재연결을 중심으로 설계한다. AWS 문서의 tier는 숫자가 작을수록 우선하며, 같은 tier 안에서는 인스턴스 크기 등 추가 조건이 적용된다. 문서는 반복 승격 실패 후 tier가 더 이상 고려되지 않는 경우도 명시한다. 따라서 tier를 Orchestrator의 `must_not` 같은 절대 제외 규칙으로 해석하면 안 된다.

cluster endpoint가 현재 writer를 나타내더라도 기존 연결이 자동으로 옮겨 가는 것은 아니다. 외부 MySQL과 Aurora 사이에 별도 binlog 복제가 있다면 그것은 클러스터 내부 HA와 구분하여 설계한다. 관리형 권한 제약, 지원되는 구성 명령, 외부 토폴로지 재연결 범위를 따로 확인해야 한다. 본문의 로컬 SQL 실행은 Aurora 장애조치 검증을 대신하지 않는다.

## 7. 자동 복구를 켜기 전의 승인 체크리스트

- [ ] 사용할 Orchestrator fork와 릴리스, MySQL 버전, 지원 명령·권한을 고정했다.
- [ ] 모든 예상 서버가 탐색되며 주소·포트·UUID·DNS가 실제 인스턴스와 일치한다.
- [ ] 마지막 수집 성공 시각을 감시하고 수집 불가를 정상 상태로 취급하지 않는다.
- [ ] 탐지 상태와 자동 복구 허용 상태를 구분하고 최초에는 제한된 클러스터만 허용한다.
- [ ] 백업·지연·필터 적용 replica의 승격 정책과 등록 만료·갱신 실패를 감시한다.
- [ ] 후보의 GTID 누락·추가 이력, 미적용 relay log, binlog 보존 및 재연결 가능성을 확인한다.
- [ ] 기존 writer 격리 실패 시 승격을 중단하는 기준과 외부 차단 수단이 있다.
- [ ] 사전 안전 훅은 동기로 실행하며 실패·시간 초과·중복 실행을 시험했다.
- [ ] 승격 이후 접속 경로 전환 실패를 별도 상태로 추적하며 기존 writer를 임의로 재개하지 않는다.
- [ ] 연결 풀 재연결, 쓰기 경로 유일성, 커밋 불명확 요청의 멱등 재시도를 확인한다.
- [ ] 복귀한 이전 writer는 이력 차이를 조사하고 필요하면 재구축한 후 replica로 편입한다.
- [ ] 관리 네트워크 단절, writer만 단절, 일부 replica 손실, 훅 실패, 정책 만료를 장애 리허설에 포함한다.

## 맺음말

Orchestrator의 가치는 복제 그래프를 지속적으로 관측하고, 현재 토폴로지에서 가능한 복구를 판단하는 데 있다. 안전한 장애조치를 완성하려면 그 위에 후보의 데이터 경계, 기존 writer의 fencing, 서비스 검색과 연결 재수립의 책임을 명확히 올려야 한다.

핵심은 “어느 replica를 선호하는가”보다 “어떤 증거가 없으면 승격하지 않을 것인가”를 먼저 정하는 것이다. 다음 고가용성 설계에서도 과반수와 split-brain, 데이터 손실 허용 범위, 실제 쓰기 경로의 유일성을 이 기준으로 함께 검토해야 한다.

## 참고 자료

- [Orchestrator upstream README와 보관 안내](https://github.com/openark/orchestrator)
- [Percona Orchestrator 저장소](https://github.com/percona/orchestrator)
- [기본 탐색 구성](https://github.com/openark/orchestrator/blob/master/docs/configuration-discovery-basic.md)
- [장애 판정](https://github.com/openark/orchestrator/blob/master/docs/failure-detection.md)
- [토폴로지 복구와 promotion rule](https://github.com/openark/orchestrator/blob/master/docs/topology-recovery.md)
- [복구 정책과 훅 구성](https://github.com/openark/orchestrator/blob/master/docs/configuration-recovery.md)
- [Orchestrator/Raft의 역할](https://github.com/openark/orchestrator/blob/master/docs/raft.md)
- [Aurora 고가용성과 승격 우선순위](https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/Concepts.AuroraHighAvailability.html)
