Amazon Web Services 한국 블로그

Amazon Aurora Multi-Master를 통한 고가용성 MySQL 애플리케이션 만들기

Amazon Aurora는 고성능 상용 데이터베이스의 성능과 가용성에 오픈 소스 데이터베이스의 간편성과 비용 효율성을 결합하였으며 클라우드를 위해 구축된 MySQL 및 PostgreSQL 호환 관계형 데이터베이스입니다. MySQL 호환 버전의 Aurora는 동일한 하드웨어에서 실행되는 표준 MySQL의 처리량을 최대 5 배까지 제공합니다. 또한 하나 이상의 가용 영역(AZ) 및 리전(Region)에서 단일 쓰기 마스터와 최대 15 개의 읽기 전용 복제본을 지원합니다.

이번에 새로 출시된 Aurora 다중 마스터 기능은  여러 가용 영역에 걸쳐 다중 쓰기 인스턴스를 생성 할 수 있습니다. 이를 통해 지속적인 쓰기 가용성을 달성 할 수 있습니다. 인스턴스 또는 가용 영역 장애가 발생하는 경우, Aurora Multi-Master를 사용하면 다운 타임 없이 읽기 및 쓰기 가용성을 유지할 수 있으며, 별도의 DB 장애 조치가 필요하지 않습니다.

우선 미국 동부 (버지니아 북부), 미국 동부 (오하이오), 미국 서부 (오레곤) 및 EU (아일랜드) 리전의 Aurora MySQL 5.6에서 사용할 수 있습니다. RDS 관리 콘솔에서 몇 번의 클릭만으로 Aurora 다중 마스터 클러스터를 생성해 볼 수 있습니다. 자세한 내용은 설명서를 참조하십시오.

이 글에서는 MySQL 용 Aurora Multi-Master 기능을 최대한 활용하기 위해 알아 두시면 좋을 사항을 알려드립니다.

멀티 마스터 노드의 아키텍쳐

Aurora Multi-Master는 DB 쓰기 및 읽기에 대한 일관성을 제공하는 데이터베이스 노드 클러스터를 통해 고가용성 및 ACID 트랜잭션을 함께 달성하도록 설계되었습니다.

기본적으로 Aurora 클러스터는 컴퓨팅(데이터베이스) 노드 세트와 공유 스토리지 볼륨으로 구성됩니다. 스토리지 볼륨은 사용자 데이터의 고가용성 및 내구성을 위해 3개의 가용 영역에 배치 된 6개의 스토리지 노드로 구성됩니다. 클러스터의 모든 데이터베이스 노드는 읽기 및 쓰기 질의를 실행할 수 있습니다.

아래는 하나의 멀티 마스터 세트를 만들었을 때, 2개의 쓰기 노드가 만들어지는 것을 보실 수 있습니다.

아래 그림은 이러한 2개 노드의 Aurora 다중 마스터 클러스터의 내부 구조를 보여줍니다.

위의 그림에서 클러스터에 단일 실패 지점(single point of failure, SPOF)이 없음을 알 수 있습니다.  각 애플리케이션은 쓰기 노드를 사용하여 읽기/쓰기 및 DDL 요구를 충족 할 수 있습니다. 쓰기 노드가 수행한 데이터베이스 변경 사항은 3 개의 가용 영역에서 6 개의 스토리지 노드에 기록되어 스토리지 노드 및 가용 영역 장애에 대한 데이터 내구성과 복원력을 제공합니다. 쓰기 노드는 모두 기능적으로 동일하며 한 노드의 장애는 클러스터의 다른 노드의 가용성에 영향을 미치지 않습니다.

스토리지 내 쿼럼을 통한 데이터 복제 원리

DB 트랜잭션이 시작되어 데이터베이스에 변경이 필요하면, 쓰기 노드는 6 개의 저장소 노드 모두에 변경을 제안하여 클러스터 간 일관성을 유지합니다. 그런 다음 각 스토리지 노드는 제안 된 변경이 진행 중인 변경 혹은 이전에 완료된 변경과 충돌하는지 확인하여 진행하거나 거부합니다.

변경을 제안한 쓰기 노드가 쿼럼의 스토리지 노드로부터 긍정적 확인을 받으면 두 가지 작업을 수행합니다. 먼저 스토리지 계층에서 각 스토리지 노드가 변경 사항을 커밋합니다. 그런 다음 지연, 시간이 짧은 피어 투 피어(P2P) 복제 프로토콜을 사용하여, 변경 레코드를 클러스터의 다른 모든 쓰기 노드에 복제합니다. 각 쓰기 노드는 변경을 수신하면, 변경 내용을 메모리 내 캐시 (버퍼 풀)에 적용합니다.

만약 변경을 제안한 쓰기 노드가 스토리지 노드로부터 긍정적 인 확인을 받지 못했다면 전체 트랜잭션을 취소하고 오류를 발생시킵니다. 애플리케이션은 필요에 따라 트랜잭션을 재 시도 할 수 있습니다.

각 단계별 구현 관점에서 쓰기 노드는 페이지 변경을 제안합니다. 페이지는 행 집합(Row group)을 포함하고 크기가 16KB 인 메모리 블록입니다. 각 페이지에는 ID와 LSN (로그 시퀀스 번호)이 있습니다. 쓰기 노드는 재실행 변경 레코드를 스토리지 계층으로 전송하여 스토리지 노드의 페이지를 업데이트합니다.

스토리지 계층에 대한 변경 사항을 성공적으로 커밋하면, 쓰기 노드는 재실행 변경 레코드를 다른 쓰기 노드에 복제하여 버퍼 풀을 새로 고칩니다. 버퍼 풀, 페이지 및 InnoDB 스토리지 엔진에 대한 일반적인 이해에 대한 자세한 내용은 InnoDB 스토리지 엔진 설명서를 참조하십시오.

고 가용성 확보 방법

다중 마스터 클러스터의 모든 노드가 읽기/쓰기 노드이므로 Aurora 다중 마스터는 단일 쓰기 마스터를 가진 Aurora 보다 더 높은 가용성을 제공합니다. 아래 그림은 애플리케이션이 2개의 쓰기 노드를 선택적으로 사용할 수 있음을 보여줍니다.

단일 마스터 Aurora를 사용하면 단일 쓰기 노드가 실패하면 읽기 전용 복제본을 새 쓰기 마스터로 승격시켜야하고, 이 시간 동안 가용성이 확보되지 않습니다. 대신 Aurora 다중 마스터의 경우, 특정 쓰기 노드가 실패하면 다른 쓰기 노드에 대한 연결을 열면 됩니다.

개발자에게 적절하게 할당한 쓰기 노드의 오류가 감지되면, 정상적인 쓰기 노드에 다시 연결하여 가동 시간을 늘릴 수 있습니다. 따라서, 고 가용성을 설계 할 때 특정 쓰기 노드에 과부하가 걸리지 않도록 주의할 필요가 있습니다. 애플리케이션 내에 쓰기 노드의 상태를 점검하는 코드를 구현하고, 연결이 실패할 경우 정상적인 노드로 연결하도록 할 필요가 있습니다.

아래 그림은 하나의 쓰기 노드가 실패할 경우, 다른 노드를 사용할 수 있음을 보여줍니다.

Java 같은 프로그램 언어로 구현할 경우, 데이터베이스 연결을 관리하는 단일 클래스를 구현하면 좋습니다. 이 클래스는 상태 확인을 캡슐화 할 수 있습니다. 클래스는 쓰기 노드 목록을 맵핑하는 쓰기 할당 맵을 만들어 유지할 수 있습니다. 이를 통해 다중 마스터가 중복 업데이트를 처리하지 않게 하여 데이터 충돌을 방지 할 수 있습니다. (충돌을 일으킬 수있는 시나리오는 다음 섹션을 참조하세요).

위의 그림은 쓰기 노드가 문제를 해결한 경우, 원래 상태로 돌아오는 것을 보여줌으로서 쓰기 고가용성을 확보하는 방법을 알려줍니다.

쓰기 노드 충돌 방지 방법

서로 다른 쓰기 노드에 동시에 트랜잭션을 일으켜 동일한 페이지 세트를 수정하려고 할 때 충돌이 발생합니다. 분산 시스템에서 이러한 충돌을 해결하려면, 시스템 전체에서 발생하는 인과 관계와 이벤트 순서를 설정하는 완벽한 메커니즘이 필요합니다.

이제까지 많은 시스템이 쓰기 기능을 중앙 집중화하여 성능을 희생하면서 충돌을 감지하고 방지합니다. 이러한 시스템에서 쓰기를 승인하는 모든 데이터베이스 노드는 모든 변경 요청을 중앙 마스터로 전달하고 승인을 기다려야 합니다. 쓰기 요청에 대한 병렬 처리를 달성할 수 있지만, 중앙 마스터가 성능 병목 현상이 발생하므로 여전히 문제가 발생합니다.

Aurora Multi-Master의 충돌 감지 기능은 분산되어 있으며, 중앙 에이전트가 데이터베이스에 대한 쓰기 프록시 작업을 담당하는 방식을 운영하기 때문에 기존과 같은 성능 문제로 인해 어려움을 겪지 않습니다. 쓰기 노드는 스토리지 노드에 대한 페이지 변경을 제안하고, 각 스토리지 노드는 쓰기 노드가 제출 한 페이지의 LSN (페이지 버전)을 노드 페이지의 LSN과 비교합니다. 스토리지 노드에 최신 버전의 페이지가 포함 된 경우, 만일 변경 사항이 동일하면 데이터 충돌로 판단하고 변경 사항을 거부합니다.

다중 쓰기 노드가 동시에 페이지 변경을 제안 할 수 있습니다. 이 때, 쿼럼 승인을 받은 최초의 쓰기 노드가 트랜잭션을 계속할 수 있습니다. 그외 쓰기 노드는 변경을 중단하고 트랜잭션을 롤백합니다. 최초 쓰기 트랜잭션은 페이지의 행을 수정하고 스토리지 노드는 전체 페이지를 비교하여 충돌을 감지합니다. 물론 행이 같은 페이지가 있으면 테이블에서 두 개의 다른 행을 수정하는 두 개의 쓰기 노드가 여전히 충돌 할 수 있습니다. 따라서, 충돌 방지를 위해 애플리케이션을 디자인 할 때, 아래와 같은 몇 가지 모범 사례를 따르는 것이 좋습니다.

  • 애플리케이션에서 멀티 마스터에서 겹치는 페이지 업데이트를 수행하지 마세요.
  • 애플리케이션 계층으로 충돌이 발생하면 트랜잭션을 다시 시도하세요.
  • 애플리케이션 디자인과 요구에 따라 적절한 쓰기 비율에 따라 질의를 쓰기 노드에 라우팅하세요.

충돌을 일으키는 시나리오의 몇 가지 예는 다음과 같습니다.

스토리지 계층이 충돌을 감지하고 변경을 거부하면, Aurora Multi-Master가 MySQL 오류 1213을 반환합니다. 다음 시나리오는 MySQL CLI 및 2 노드 Aurora 다중 마스터 클러스터를 사용하여 시뮬레이션할 수 있습니다.

시나리오 1 : 인접 행 동시 업데이트의 경우

두 개의 쓰기 노드가 테이블 t1에서 인접한 행을 동시에 업데이트합니다. 이 행들은 InnoDB에 의해 동일한 페이지에 저장됩니다. 두 개의 동시 트랜잭션이 스토리지 계층에서 물리적 충돌을 일으켜 세션 중 하나가 교착 상태 오류(1213)로 중단됩니다.

###### SESSION 1 from Writer 1 

mysql> select @@aurora_server_id;
+-------------------------+
| @@aurora_server_id      |
+-------------------------+
| multimaster-test-mm-1-1 |
+-------------------------+
1 row in set (0.00 sec)

mysql> select * from t1;
+----+------+
| id | s1   |
+----+------+
|  1 | a    |
|  2 | b    |
|  3 | c    |
|  4 | d    |
|  5 | e    |
+----+------+
5 rows in set (0.00 sec)

mysql> select now(3); update t1 set s1 = 'x' where id = 1;
+-------------------------+
| now(3)                  |
+-------------------------+
| 2019-05-17 17:25:24.904 |
+-------------------------+
1 row in set (0.00 sec)

Query OK, 1 row affected (0.01 sec)
Rows matched: 1  Changed: 1  Warnings: 0

###### SESSION 2 from Writer 2 

mysql> select @@aurora_server_id;
+-------------------------+
| @@aurora_server_id      |
+-------------------------+
| multimaster-test-mm-1-2 |
+-------------------------+
1 row in set (0.00 sec)

mysql> select * from t1;
+----+------+
| id | s1   |
+----+------+
|  1 | a    |
|  2 | b    |
|  3 | c    |
|  4 | d    |
|  5 | e    |
+----+------+
5 rows in set (0.01 sec)

mysql> select now(3); update t1 set s1 = 'y' where id = 2;
+-------------------------+
| now(3)                  |
+-------------------------+
| 2019-05-17 17:25:24.905 |
+-------------------------+
1 row in set (0.00 sec)

ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction

시나리오 2 : 같은 행 동시 업데이트의 경우

두 개의 쓰기 노드가 테이블 t1에서 동일한 행을 동시에 업데이트합니다. 두 세션이 동일한 쓰기 노드에서 실행되면, 세션 중 하나에서 논리적 충돌, 잠금 대기 조건 및 결국 잠금 대기 시간 초과가 발생합니다.

Aurora 다중 마스터에서 두 세션이 두 개의 다른 쓰기 노드에서 동시에 실행되면, 세션 중 하나가 교착 상태 오류로 중단됩니다. 아래 시나리오에서 쓰기 노드 1은 트랜잭션을 시작하여 테이블 t1에서 행을 업데이트하고 있는데, 쓰기 노드 2가 트랜잭션을 커밋하기 전에 테이블 t1에서 동일한 행을 업데이트하려고 시도하여 교착 상태 오류로 실패합니다.

###### SESSION 1 from Writer 1

mysql> begin;
Query OK, 0 rows affected (0.00 sec)

mysql> select now(); update t1 set s1 = 'z' where id = 1;
+---------------------+
| now()               |
+---------------------+
| 2019-05-17 17:28:04 |
+---------------------+
1 row in set (0.00 sec)

Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

###### SESSION 2 from Writer 2

mysql> begin;
Query OK, 0 rows affected (0.00 sec)

mysql> select now(); update t1 set s1 = 'v' where id = 1;
+---------------------+
| now()               |
+---------------------+
| 2019-05-17 17:28:14 |
+---------------------+
1 row in set (0.00 sec)

ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction

시나리오 3 : 같은 행 지연 업데이트의 경우

두 개의 쓰기 노드가 서로 몇 초 간격으로 동일한 행을 업데이트합니다. 이 시나리오에서 세션 1은 테이블 t1에서 id = 1 인 행을 변경하고 내재적으로 변경을 자동 커미트하는 업데이트 명령문을 발행합니다. 몇 초 후, 쓰기 노드 2는 테이블 t1에서 동일한 행을 갱신하려고 시도합니다. 클러스터에서 복제가 쓰기 사이를 따라 잡을 시간이 충분하기 때문에 두 트랜잭션 모두 충돌 없이 성공합니다.

###### SESSION 1 from Writer 1

mysql> select now(); update t1 set s1 = 'z' where id = 1;
+---------------------+
| now()               |
+---------------------+
| 2019-05-17 17:29:03 |
+---------------------+
1 row in set (0.00 sec)

Query OK, 1 row affected (0.01 sec)
Rows matched: 1  Changed: 1  Warnings: 0

###### SESSION 2 from Writer 2

mysql> select now(); update t1 set s1 = 'v' where id = 1;
+---------------------+
| now()               |
+---------------------+
| 2019-05-17 17:29:07 |
+---------------------+
1 row in set (0.00 sec)

Query OK, 1 row affected (0.01 sec)
Rows matched: 1  Changed: 1  Warnings: 0

전역 쓰기 후 읽기(Read-After-Write) 일관성

전역 읽기 일관성은 쓰기 및 읽기에 사용되는 DB 노드에 관계없이 가장 최근의 커밋 된 변경 상태를 반영하기 위해 요구 사항입니다. 이를 구현하는 방법은 주로 동일한 노드에서 읽기가 발생하면, 쓰기 후 읽기 일관성을 제공하는 것입니다. 쓰기 작업 후 즉시 다른 노드에서 발생하는 읽기는 복제 지연에 따라 몇 밀리 초 동안 변경 사항을 볼 수 없습니다.

Aurora Multi-Master는 기본 노드 수준 일관성 모드 또는 전역 쓰기 후 읽기(GRAW)라는 클러스터 전체 일관성 모드 중에서 선택할 수 있는 유연성을 제공합니다. GRAW를 사용하면 애플리케이션이 최신 데이터를 기반으로 클러스터 전체의 일관된 읽기 작업을 수행 할 수 있습니다. 읽기 성능 측면에서는 약간의 불이익이 있지만, 일관된 읽기가 필요한 경우 적절한 해법입니다.

기본 모드에서는 쓰기 노드에 동일한 읽기 작업이 수행되면,  전역 쓰기 후 읽기 일관성이 보장됩니다. 다른 노드는 (복제 지연으로) 몇 밀리 초 내에 쓰기 상태를 반영합니다.

마무리

MySQL 호환 Amazon Aurora Multi-Master는 여러 쓰기 노드각 필요한 고성능 DB 애플리케이션에 적합한 솔루션이 될 수 있습니다. 즉, Active-Active 배포와 같은 가동 시간이 높은 애플리케이션 시나리오 및 테넌트 당 하나의 데이터베이스가 필요한 다중 테넌트 데이터베이스를 실행해야 하는 경우와 같은 SaaS 공급자 사용에도 활용할 수 있습니다.

현재 Aurora Multi-Master는 단일 리전에서 두 개의 노드 클러스터를 지원합니다. 더 많은 쓰기 노드에 대한 지원과 향후 서비스 제공 리전은 게속 확대될 예정입니다.

– Mukund Sundararajan, AWS Solutions Architect – Channy Yun(윤석찬) 편집

이 글은 AWS Database 블로그의 Build highly available MySQL applications using Amazon Aurora Multi-Master의 한국어 번역 편집본입니다.