Database/High Performance MySQL

MySQL 복제 (2)

Seung-o 2023. 12. 12. 00:28

복제 Failover

 

복제는 고가용성의 초석이다. 데이터 복사본을 다른 위치에서 지속적으로 업데이트하면 백업으로 이동하는 것보다 재해로부터 복구하는 것이 훨씬 쉽다. 

 

여기에서는 레플리카를 원본 노드로 승격하는 올바른 방법에 대해 설명한다. "레플리카 승격"과 "Failover"가 동의어임에 유의하자. 두 가지 모두 쓰기를 수행하지 못하도록 소스를 강등시키고 레플리카를 소스 역할로 승격시키는 작업을 의미한다. 

 

계획된 승격

 

일반적으로 승격은 유지 관리 이벤트( 보안 패치, 커널 업데이트, 재시작을 해야하는 몇 가지 구성 옵션으로 인한 재시작 등 )로 인해 수행된다. 이러한 유형의 승격을 계획된 승격이라고 한다.

 

이 승격을 성공적으로 처리하기 위해 다음 단계를 수행한다.

 

1. 승격할 레플리카를 결정한다. 대개 모든 데이터가 있다고 확신하는 레플리카가 그 대상이 된다.

2. 몇 초 이내인지 확인하기 위해 지연을 확인한다.

3. super_read_only를 설정하여 소스에 쓰기를 중단한다.

4. 복제가 대상과 동기화될 때까지 기다린다. ( GTID를 비교하여 확인 가능하다 )

5. 레플리카 대상에서 read_only 설정을 해제한다.

6. 애플리케이션 트래픽을 대상으로 향하게 한다.

7. 강등된 레플리카를 포함하여 모든 레플리카를 새 소스로 다시 지정한다. 

 

계획되지 않은 승격

 

장기적으로 모든 시스템에는 소프트웨어 또는 하드 웨어 장애가 발생할 수 있다. 이는 계획되지 않은 승격이 필요한 경우이다. 확인할 라이브 소스가 없으므로 이미 복제된 데이터를 기준으로 레플리카를 선택하는 간소화된 승격이 이루어진다.

 

1. 승격할 레플리카를 결정한다. 

2. 대상에서 read_only 설정을 해제한다.

3. 애플리케이션 트래픽을 대상으로 향하게 한다.

4. 서비스가 재개될 때 강등된 레플리카를 포함하여 모든 레플리카를 새 소스로 지정한다.

 

승격의 트레이드 오프

 

계획되지 않은 승격이 이루어질 때, 레플리카에서 누락된 데이터의 양을 파악하기 어렵기에 때로는 Failover를 하지 않는 것이 더 나은 전략이 될 수 있다. 계획되지 않은 승격은 자주 수행되는 이벤트가 아니다. 따라서 요청을 받았을 때 단계를 놓치지 않았는지 문서를 확인해야 할 수도 있다. 또한, 다른 레플리카를 모두 검사하며 어떤 것이 적합한지 확인하는 시간도 소요된다. 결국 시간 소모의 총계를 고려하면, MySQL 프로세스가 다시 온라인 상태가 될 때까지 기다리는 것이 더 빠를 수도 있다.

 

복제 토폴로지 ( Topology )

소스 및 레플리카의 거의 모든 구성에 대해 MySQL 복제 설정이 가능하다. 복잡한 토폴로지가 가능하지만, 간단한 토폴로지도 매우 유연할 수 있다. 

 

요구 사항을 충족하면서 복제 토폴로지를 최대한 단순하게 유지하는 것이 좋다. 이와 관련하여, 거의 모든 사례에 적용이 가능한 두 가지 전략을 권장하고자 한다. 이 범위를 벗어나는 타당한 이유가 있을 수 있지만, 더 복잡해진다면 문제를 올바르게 해결하고 있는지 자문해보아야 한다.

 

액티브/패시브 ( Active/Passive )

 

액티브/패시브 토폴로지에서는 모든 읽기 및 쓰기를 단일 소스 서버로 보낸다. 또한 애플리케이션 트래픽을 능동적으로 처리하지 않는 소수의 패시브 레플리카를 유지 관리한다. 이 모델을 선택하는 주된 이유는, "복제 지연에 대해 신경 쓰고 싶지 않을 때"이다. 모든 읽기가 소스로 이동하므로 애플리케이션에서 원치 않는 쓰기 후 읽기 문제를 방지할 수 있다. 

 

소스와 여러 레플리카의 구성

 

구성

 

액티브/패시브 토폴로지에서는 소스와 레플리카의 CPU, 메모리 등이 동일하다. 업그레이드나 하드웨어 장애 등을 위해 현재 실행 중인 소스에서 레플리카 중 하나로 Failover 하고, 레플리카가 소스와 스펙이 동일하면 페일오버 전과 같이 트래픽 용량과 처리량을 유지할 수 있다.

 

중복성

 

물리적 하드웨어 환경에서는 최소 3개의 총 서버에 대해 n+2 중복이 필요하다. 이는 하드웨어 장애가 발생한 경우에 대비한 페일오버, 원본에서 백업이 어려운 경우를 고려한 산정값이다. 

 

클라우드 환경에서는 데이터가 충분히 작거나 데이터를 쉽게 복사할 수 있는 경우, 총 2대의 서버에 대해 n+1 중복을 해결할 수 있다. 이것이 어렵다면, 물리적 환경과 마찬가지로 n+2개를 구성해야 한다. 만약 n+1을 선택하면, 클라우드 공급자의 동적 프로비저닝 특성으로 인해 보다 쉽게 관리할 수 있다. 

 

두 경우 모두 레플리카 중 하나를 물리적으로 떨어진 위치에 배치하는 것이 좋다. 레플리카는 사용자가 설정한 지침에 따라 복구할 수 있어야 하며 데이터 손실이 발생할 수 있다.

 

주의 사항

 

이 모델의 단점은 읽기 확장을 단일 서버 용량에 명시적으로 바인딩한다는 것이다. 읽기 확장 제한에 도달하면 이 토폴로지 이상으로 발전하거나 샤딩을 활용하여 소스에서 읽기를 줄여야 한다.

 

액티브/리드 풀

 

액티브/리드 풀 구성에서는 모든 쓰기를 소스로 보낸다. 읽기는 애플리케이션 요구 사항에 따라 소스 서버나 리드 풀로 보낼 수 있다. 리드 풀을 활용하면 읽기 집약적인 애플리케이션는 읽기를 수평적으로 확장할 수 있다. 물론 어느 시점에서는 소스에 대한 복제 요구로 인해 수평 확장이 떨어지게 된다.

 

리드풀을 지닌 단일 소스

 

구성

 

액티브/패시브 구성과 마찬가지로, Failover 등을 대비하여 1~2개의 레플리카는 소스와 동일한 스펙을 유지하는 것이 권장된다. 

 

시간이 지남에 따라, 리드풀이 증가하는 경우 레플리카의 스펙에 따라 트래픽의 균형을 맞추는 것이 비용을 절감시킬 수 있다. 예를 들어, Failover 대상 레플리카에 32개의 코어가 있고, 일반 레플리카에는 8개의 코어가 있다면 트래픽을 Failover 대상 레플리카쪽으로 4배 더 늘려서 사용률을 높일 수 있다.

 

중복성

 

리드풀에 있는 레플리카 수는 당연히도 액티브/패시브에서 제시된 요구 사항을 충족해야 한다. 그 외에도 읽기 트래픽을 수용할 수 있는 충분한 노드와 노드 장애에 대비한 작은 버퍼가 필요하다.

 

CPU 사용률이 증가할수록 작업과 지연 시간 사이의 컨텍스트 전환에 더 많은 시간을 소요하게 된다. 따라서 읽기의 경우,  풀의 노드당 CPU 사용률을 50%~60% 사이로 유지할 수 있도록 한다. 

 

주의 사항

 

리드 풀을 사용할 때는 애플리케이션은 Stale Read를 어느 정도 허용해야 한다. 소스에서 완료한 쓰기가 이미 레플리카에 복제되었는지 보장할 수 없다. 복제에서 너무 뒤처진 노드를 풀링 해제하는 방법이 필요할 수 있다.

 

Stale Read 란?

소스의 최신 상태가 미처 반영되지 않은 데이터를 읽는 것을 의미한다.

 

복제 유지 및 관리

 

적은 양의 데이터임에도 일관된 쓰기 워크로드로 인해 복제 지연 또는 더 심각한 복제 중단이 자주 발생할 수 있다. 데이터베이스는 시간에 따라 커지는 경향이 있기에, 복제에 대한 유지 관리가 필요하다.

 

복제 모니터링

 

복제는 MySQL 모니터링의 복잡성을 높인다. 물론 복제를 위해 소스와 레플리카 모두에서 작업이 이루어지긴 하지만, 대다수의 작업은 레플리카에서 이루어지기에 레플리카를 두는 것은 관리 포인트가 늘어남을 의미한다. 

 

"모든 레플리카가 작동하나요?", "레플리카에 오류가 있나요?", "가장 느린 레플리카는 얼마나 뒤쳐져 있나요?" 등의 질문에 답하기 위해 모니터링 프로세스를 자동화하고 복제를 강력하게 만들어야 한다.

 

아래는 복제 모니터링을 설정할 때, 중요하게 고려해야할 몇 가지 사항이다.

 

1. 복제는 소스와 레플리카 모두 디스크 공간이 필요하다.

 

복제는 소스의 바이너리 로그와 레플리카의 릴레이 로그를 모두 사용하기에, 소스에 여유 디스크 공간이 없으면 트랜잭션이 완료될 수 없고 타임 아웃 에러가 발생한다. 레플리카는 디스크 공간이 없으면 복제를 중단하고 디스크 여유 공간이 생길 때까지 기다리는 방식을 취한다. 

 

2. 복제 상태 및 오류를 모니터링 해야한다.

 

복제는 오랜 기간 사용되어 매우 강력하다. 하지만 예상치 못한 네트워크 문제, 데이터 불일치, 데이터 손상과 같은 외부 요인으로 복제가 중단될 수 있다. 따라서 복제 스레드가 실행 중인지 여부를 모니터링하고 실행이 중단된 경우는 그 오류를 확인해야 한다.

 

3. 지연된 복제는 예상대로 지연되어야 한다.

 

지연된 레플리카가 실제로 정확한 시간만큼 지연되었는지 모니터링하는 것이 필요하다.

 

복제 지연 측정

 

모니터링해야하는 가장 일반적인 요소 중 하나는 레플리카가 실행 중인 소스보다 얼마나 뒤떨어져 있는지이다. 

 

SHOW REPLICA STATUS의 Seconds_behind_source 열은 이론적으로 레플리카의 지연을 보여주지만, 사실 여러 가지 이유로 항상 정확하지는 않다.

 

- 레플리카는 서버의 현재 타임스탬프와 바이너리 로그 이벤트에 기록된 타임스탬프를 비교하여 Seconds_behind_source를 계산하기에 쿼리를 처리하지 않는 한 지연을 보고할 수도 없다.

- 복제 스레드가 실행 중이 아니라면 일반적으로 NULL을 보고한다.

- 일부 오류는 복제를 중단하거나 복제 스레드를 중지할 수 있지만, Seconds_behind_source는 오류를 표시하지 않고 0을 보고한다.

- 복제 프로세스가 실행 중이더라도 레플리카가 지연을 계산하지 못하는 경우도 있다.

- 트랜잭션이 매우 길면 보고된 지연이 바뀔 수 있다. 

 

mysql> SHOW REPLICA STATUS\G
*************************** 1. row ***************************
             Replica_IO_State: Waiting for source to send event
                  Source_Host: localhost
                  Source_User: repl
                  Source_Port: 13000
                Connect_Retry: 60
              Source_Log_File: source-bin.000002
          Read_Source_Log_Pos: 1307
               Relay_Log_File: replica-relay-bin.000003
                Relay_Log_Pos: 1508
        Relay_Source_Log_File: source-bin.000002
           Replica_IO_Running: Yes
          Replica_SQL_Running: Yes
              Replicate_Do_DB:
          Replicate_Ignore_DB:
           Replicate_Do_Table:
       Replicate_Ignore_Table:
      Replicate_Wild_Do_Table:
  Replicate_Wild_Ignore_Table:
                   Last_Errno: 0
                   Last_Error:
                 Skip_Counter: 0
          Exec_Source_Log_Pos: 1307
              Relay_Log_Space: 1858
              Until_Condition: None
               Until_Log_File:
                Until_Log_Pos: 0
           Source_SSL_Allowed: No
           Source_SSL_CA_File:
           Source_SSL_CA_Path:
              Source_SSL_Cert:
            Source_SSL_Cipher:
               Source_SSL_Key:
        Seconds_Behind_Source: 0
Source_SSL_Verify_Server_Cert: No
                Last_IO_Errno: 0
                Last_IO_Error:
               Last_SQL_Errno: 0
               Last_SQL_Error:
  Replicate_Ignore_Server_Ids:
             Source_Server_Id: 1
                  Source_UUID: 3e11fa47-71ca-11e1-9e33-c80aa9429562
             Source_Info_File:
                    SQL_Delay: 0
          SQL_Remaining_Delay: NULL
    Replica_SQL_Running_State: Reading event from the relay log
           Source_Retry_Count: 10
                  Source_Bind:
      Last_IO_Error_Timestamp:
     Last_SQL_Error_Timestamp:
               Source_SSL_Crl:
           Source_SSL_Crlpath:
           Retrieved_Gtid_Set: 3e11fa47-71ca-11e1-9e33-c80aa9429562:1-5
            Executed_Gtid_Set: 3e11fa47-71ca-11e1-9e33-c80aa9429562:1-5
                Auto_Position: 1
         Replicate_Rewrite_DB:
                 Channel_name:
           Source_TLS_Version: TLSv1.2
       Source_public_key_path: public_key.pem
        Get_source_public_key: 0
            Network_Namespace:

 

이러한 문제에 대한 해결 방법은 Seconds_behind_source를 무시하고 직접 관찰하고 측정할 수 있는 것으로 레플리카 지연을 모니터링하는 것이다. 한 가지 좋은 솔루션은 초당 한 번씩 업데이트하는 타임스탬프인 하트비트 레코드이다. 지연을 계산하기 위해, 레플리카의 현재 타임스탬프에서 하트비트를 빼면 된다. Percona Toolkit에 포함된 pt-heartbeat 스크립트는 가장 널리 사용되는 복제 하트비트 구현물이다.

 

레플리카와 소스의 일치 여부 확인

 

완벽한 환경에서 ( 복제 지연을 제외하면 ) 레플리카는 항상 원본의 정확한 복사본이다. 하지만, 아래의 몇 가지 원인들로 인해 불일치가 발생할 수 있다.

 

- 레플리카에 실수로 쓰기 작업 수행

- 양쪽이 쓰기를 수행하는 이중 소스 복제 사용

- 비결정적 쿼리 및 명령문 기반 복제

 

이를 방지하기 위해 아래와 같은 규칙을 제안한다.

 

1. 항상 super_read_only가 활성화된 상태에서 레플리카 실행

 

2. 행 기반 복제 또는 결정론적 구문 사용

 

3. 적절한 복제 토폴로지 구성

 

복제 문제와 해법

 

소스의 바이너리 로그 손상

 

소스의 바이너리 로그가 손상되었다면, 레플리카를 재구성하는 것 외에는 다른 방법이 없다.

 

고유하지 않은 서버 ID

 

이는 복제에서 발생할 수 있는 가장 어려운 문제 중 하나이다. 실수로 동일한 서버 ID로 두 개의 레플리카를 구성한 경우, 겉으로 보기에는 제대로 작동하는 것처럼 보인다. 하지만 오류 로그를 보거나 innotop과 같은 도구로 소스를 보면 이상한 점을 발견할 수 있다.

 

우선 소스에서는 두 개 중 하나의 레플리카만 연결된 것으로 나타난다. 레플리카에서는 오류 로그에 계속해서 연결 끊기 및 다시 연결 오류 메세지가 표시되지만, 잘못된 서버 ID 구성에 대한 언급은 없다.

 

MySQL 버전에 따라 레플리카는 올바르게 복제되지만 느리게 복제될 수 있고, 제대로 복제가 이루어지지 않을 수도 있다. 레플리카가 바이너리 로그 이벤트를 누락할 수 있고, 두 레플리카가 서로 경합하면서 생기는 부하로 소스에 문제가 발생할 수도 있다.

 

따라서 레플리카를 설정할 때는 서버 ID 매핑이 올바르게 이루어지도록 유의해야 한다.

 

정의되지 않은 서버 ID

 

서버 ID를 정의하지 않으면, MySQL이 CHANGE REPLICATION SOURCE TO로 복제를 설정하는 것처럼 보이지만 레플리카를 시작할 수 없다. 서버 ID를 명시적으로 설정해야함에 유의하자.

 

임시 테이블 누락

 

임시 테이블은 일부 유용하지만, 안타깝게도 명령문 기반 복제와 호환되지 않는다. 레플리카가 충돌하거나 종료하면 레플리카 스레드에서 사용하던 임시 테이블이 사라지게 된다.

 

이를 해결하기 위한 가장 좋은 방법은 행 기반 복제를 사용하는 것이다. 차선책은 임시테이블의 이름을 일관되게 짓고 ( 가령, temp_%NAME% ) 복제 규칙을 통해 해당 테이블 복제를 건너뛰는 것이다.

 

과도한 복제 지연

 

복제 지연은 자주 발생하는 문제이다. 그렇기에 애플리케이션단에서부터 약간의 지연을 허용하도록 설계하는 것이 좋다.

 

아래 방법을 통해 복제 지연을 어느 정도 완화할 수 있다.

 

1. 멀티 스레드 복제

 

멀티 스레드 복제를 사용하는지 확인하자. 

 

2. 샤딩 사용

 

샤딩 기술을 사용하여 여러 소스에 쓰기를 분산하는 것은 매우 효과적인 전략이다.

 

3. 일시적인 지속성 저하

 

복제 지연이 쓰기 작업 제한으로 인한 것이라면 일시적으로 sync_binlog=0 및 innodb_flush_log_at_trx_commit=0을 설정하여 복제 속도를 높일 수 있다. 대신 이 방법은 정말 유의해야한다. 레플리카에서만 이 작업을 수행해야 하며, 레플리카가 백업을 수행하는 위치인 경우, 이 설정을 변경하면 백업에서 복구를 못할 수 있다. 

'Database > High Performance MySQL' 카테고리의 다른 글

MySQL 복제 (1)  (0) 2023.12.11
쿼리 성능 최적화 (2)  (2) 2023.11.30
쿼리 성능 최적화 (1)  (0) 2023.11.28
고성능을 위한 인덱싱 (2)  (2) 2023.11.09
고성능을 위한 인덱싱 (1)  (0) 2023.11.07