Galera Cluster MySQL 고가용성, NGINX Plus로 달성

이 포스트에서는 MySQL, Galera Cluster 및 NGINX Plus R5에서 도입된 TCP 로드 밸런싱 기능을 사용하여 고가용성 데이터베이스 클러스터를 생성하고 테스트합니다. NGINX Plus로 적절한 MySQL 로드 밸런싱 구성과 데이터베이스 클러스터에서 쓰기 충돌 및 장애를 처리하는 기술을 시연합니다.

Galera Cluster NGINX Plus TCP Load Balancing

목차

1. Galera Cluster 소개
2. MySQL 로드 밸런싱을 위한 NGINX Plus 구성
3. Galera Cluster 테스트
4. 병렬 업데이트 문제 처리
5. MySQL 고가용성을 위한 더 나은 솔루션
5-1. 데이터베이스 장애에 대한 복원력

1. Galera Cluster 소개

Galera Cluster 는 MySQL 데이터베이스 서버 클러스터를 위한 동기식 복제 솔루션입니다. Galera Cluster 의 모든 노드에 데이터베이스 쓰기가 즉시 복제되며, 모든 데이터베이스 서버는 주(Primary) 노드로 작동합니다. 일부 주의 사항과 신중한 성능 테스트를 통해, 로드 밸런싱된 Galera Cluster 는 매우 높은 가용성이 필요한 중요한 비즈니스 데이터의 단일 MySQL 데이터베이스 대신 사용될 수 있습니다.

이 포스트의 예시에서는 stock MySQL을 사용하며, DigitalOcean에서 실행 중인 Ubuntu 이미지에 세 개의 데이터베이스 서버 (db1, db2, db3)를 배포하기 위해 이 설치 지침을 따릅니다. 설치 및 클러스터 부트스트래핑은 신중하게 수행되어야 하며, 각각의 데이터베이스 서버가 두 개의 피어로부터 복제되는 안정적인 클러스터를 생성하는 것이 최종 목표입니다. Galera Cluster 는 MariaDB와 Percona XtrDB Cluster도 지원합니다.

autoindex id, 값(문자열), 동일한 값이 테이블에 삽입될 때마다 증가하는 개수(정수)를 포함하는 간단한 데이터베이스 테이블(test.data)로 시작합니다.

CREATE TABLE data ( 
    id INTEGER NOT NULL AUTO_INCREMENT, 
    value CHAR(30), 
    count INTEGER, 
    PRIMARY KEY (value), 
    KEY (id)
);

하나의 데이터베이스 인스턴스에서 이 CREATE를 실행하고 테이블이 피어 인스턴스에 복제되는지 확인합니다.

2. MySQL 로드 밸런싱을 위한 NGINX Plus 구성

NGINX Plus를 구성하여 라운드 로빈 방식(기본값)으로 세 서버 간에 데이터베이스 연결의 부하를 분산합니다.

stream {
    upstream db {
        server db1:3306;
        server db2:3306;
        server db3:3306;
    }

    server {
        listen 3306;
        proxy_pass db;
        proxy_connect_timeout 1s; # detect failure quickly
    }
}

그런 다음 NGINX Plus를 통해 데이터베이스에 연결하고 SQL을 사용하여 연결할 인스턴스를 결정합니다.

# mysql -u galera -p --protocol=tcp
Enter password: ********

mysql> SHOW VARIABLES WHERE Variable_name = 'hostname';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| hostname      | db2   |
+---------------+-------+

NGINX Plus가 라운드 로빈 방식으로 3개의 서버에 로드 밸런싱을 하고 있는지 확인하기 위해 반복적으로 재연결할 수 있습니다.

3. Galera Cluster 테스트

데이터베이스 클러스터를 테스트하기 위해 테이블에 항목을 삽입하고 일부로 충돌을 유발합니다. 새 항목은 1부터 시작하며, 중복된 항목(충돌)은 카운트를 증가시킵니다.

mysql> INSERT INTO data (value, count) VALUES ( '$value', 1 ) 
           ON DUPLICATE KEY UPDATE count=count+1;

첨부된 간단한 Perl 스크립트(query1.pl)는 100개의 항목을 삽입하거나 증가시키고, 각 트랜잭션이 전송된 인스턴스의 이름을 출력합니다.

$ ./query1.pl
db3
db1
db2
db3
...

mysql> SELECT * FROM data;
+-----+-----------+-------+
| id  | value     | count |
+-----+-----------+-------+
|   3 | value-000 |     1 |
|   4 | value-001 |     1 |
...
| 101 | value-098 |     1 |
| 102 | value-099 |     1 |
+-----+-----------+-------+
100 rows in set (0.04 sec)

UPDATE가 데이터베이스 간에 라운드 로빈으로 로드 밸런싱되고, 연속적으로 더 많은 UPDATE를 실행할 때 카운트가 올바르게 업데이트되는 것을 관찰합니다.

$ ./query1.pl ; ./query1.pl ; ./query1.pl
...

mysql> SELECT * FROM data;
+-----+-----------+-------+
| id  | value     | count |
+-----+-----------+-------+
|   3 | value-000 |     4 |
|   4 | value-001 |     4 |
...
| 101 | value-098 |     4 |
| 102 | value-099 |     4 |
+-----+-----------+-------+
100 rows in set (0.04 sec)

4. 병렬 업데이트 문제 처리

새로운 데이터베이스 테이블로 다시 시작합니다.

mysql> DROP TABLE data;
mysql> CREATE TABLE data ( id INTEGER NOT NULL AUTO_INCREMENT, value CHAR(30), 
           count INTEGER, PRIMARY KEY (value), KEY (id) );

업데이트를 병렬로 수행합니다(인스턴스 20개).

$ for i in {1..20} ; do ( ./query1.pl& ) ; done

데이터베이스에서 가끔 오류가 발생합니다.

DBD::mysql::st execute failed: Deadlock found when trying to get lock; try 
restarting transaction at ./query1.pl line 42.

표를 살펴보면 20개까지 증가한 항목이 거의 없음을 알 수 있습니다.

mysql> SELECT * FROM data;
+------+-----------+-------+
| id   | value     | count |
+------+-----------+-------+
|    1 | value-000 |    14 |
|   31 | value-001 |    15 |
...
| 2566 | value-098 |    18 |
| 2601 | value-099 |    20 |
+------+-----------+-------+
100 rows in set (0.03 sec)

이는 Galera Cluster 복제 프로세스의 결과입니다. 동일한 레코드에 대한 UPDATE가 데이터베이스 간에 병렬로 실행될 때 데드락이 발생할 수 있고, 데이터베이스는 해당 트랜잭션을 거부합니다.

일부 상황에서는 이러한 동작이 허용될 수 있습니다. 만약 애플리케이션이 충돌하는 업데이트를 병렬로 제출하는 가능성이 없고, 애플리케이션 코드가 매우 드문 거부 트랜잭션을 정상적으로 처리할 경우(예: 사용자에게 오류 반환) 심각한 문제가 아닐 수 있습니다.

이 동작이 허용되지 않은 경우 가장 간단한 솔루션은 업스트림 서버 그룹에서 하나의 주 데이터베이스 인스턴스를 지정하는 것입니다. 다른 인스턴스를 백업으로 표시하고 비활성화하면 됩니다.

upstream db {
    server db1:3306;
    server db2:3306 backup;
    server db3:3306 down;
}

이 구성으로는 모든 트랜잭션이 db1로 라우팅 됩니다. db1이 실패하면 현재 연결이 끊기고 NGINX Plus 는 새로운 연결을 위해 db2로 장애 조치(failover)합니다. db3는 클러스터에서 조용한 파트너로 작동하며, db1, db2로부터만 업데이트를 받습니다.

이 구성을 테스트하면 각 데이터베이스 인스턴스에서 모든 항목이 존재하고 올바른 count 값인 20을 갖는 것을 확인할 수 있습니다. 그러나 테스트 중에 db1이 실패하면 일부 트랜잭션이 손실될 수 있습니다.

5. MySQL 고가용성을 위한 더 나은 솔루션

트랜잭션이 실패하는 경우는 다양합니다. 매우 높은 수준의 보호가 필요한 경우 애플리케이션이 실패한 트랜잭션을 적절하게 감지하고 재시도할 수 있도록 해야 합니다. NGINX Plus 와 Galera Cluster 는 나머지 처리를 담당할 수 있습니다.

테스트 클라이언트에서는 query2.pl에 표시된 것처럼 짧은 일시 중지 후 트랜잭션을 다시 시작하는 예외 처리기에서 오류를 포착하고 데이터베이스 트랜잭션을 래핑하는 것으로 충분합니다.

    my $backoff = 0.1; # exponential backoff, in seconds
TRY:
    eval {

        ... YOUR CODE HERE ...
        ... perform DB operations, and call die() on failure
 
    } or do {
        print "Failed: $@";
        select( undef, undef, undef, $backoff );
        $backoff *= 1.5;
        goto TRY;
    }

이 수정된 접근 방식으로 시스템은 병렬로 업데이트를 처리하고 데드락 오류가 발생하는 경우에는 트랜잭션을 다시 시도합니다. 이를 통해 카운트가 올바르게 증가되고 시스템이 여러 개의 활성 기본 노드로 신뢰성을 유지하는 것이 보장됩니다.

mysql> SELECT * FROM data;
+------+-----------+-------+
| id   | value     | count |
+------+-----------+-------+
|    1 | value-000 |    20 |
|   33 | value-001 |    20 |
...
| 2964 | value-098 |    20 |
| 2993 | value-099 |    20 |
+------+-----------+-------+
100 rows in set (0.04 sec)

5-1. 데이터베이스 장애에 대한 복원력

이 시스템은 데이터베이스 장애에도 견고합니다. 데이터베이스 서버를 종료한 다음 클러스터에 병렬 업데이트를 제출하여 장애를 시뮬레이션할 수 있습니다. NGINX 서버 구성에서 proxy_connect_timeout 값을 1초(1s)로 줄여서 NGINX가 연결 실패를 신속히 감지할 수 있도록 합니다.

Failed: Can't connect to DBI:mysql:test:dev: Lost connection to MySQL server at 
  'reading initial communication packet', system error: 0 at ./query2.pl line 28. 
Failed: Can't execute 'SHOW VARIABLES WHERE Variable_name = 'hostname';': Lost 
  connection to MySQL server during query at ./query2.pl line 33.
Failed: Can't execute 'INSERT INTO data (value, count) VALUES ( 'value-020', 1 ) 
  ON DUPLICATE KEY UPDATE count=count+1': Unknown command at ./query2.pl line 45.

테스트 중에 이러한 오류에도 불구하고, 어떤 트랜잭션도 삭제되거나 두 번 실행되지 않았으며, 세 개의 데이터베이스는 일관된 상태를 유지했습니다. 적절한 애플리케이션 로직, NGINX Plus 로드 밸런싱 및 Galera Cluster 의 조합은 견고하고 고성능이며 무엇보다 100% 신뢰할 수 있는 MySQL 데이터베이스 클러스터를 제공합니다.

자체 환경에서 NGINX Plus를 사용하여 Galera Cluster MySQL 로드 밸런싱을 시도해보려면 30일 무료 평가판을 시작하거나 NGINX STORE에 문의하십시오.

사용 사례에 대해 최신 소식을 더 빠르게 전달받고 싶으시면 아래 뉴스레터를 구독하세요.