AWS 기술 블로그

AWS DMS를 활용하여 MySQL 트랜잭셔널 데이터를 Amazon OpenSearch Service로 복제하기

배경

우리는 고객의 검색 경험을 개선하기 위해 새로운 기술을 도입하고자 합니다. 현재 MySQL 데이터베이스에 저장된 상품 상세 설명에 시멘틱 검색 기능을 적용하는 것이 목표입니다. 시멘틱 검색은 단순한 키워드 매칭을 넘어 문맥과 의미를 이해하는 고급 검색 방식입니다. 이를 구현하기 위해서 Amazon OpenSearch Service(AOS)의 Neural search 플러그인을 활용할 계획입니다. 이 플러그인은 시멘틱 검색 기능을 쉽게 적용할 수 있게 해줍니다. 하지만. 이 계획을 실현하기 위해서는 한 가지 중요한 과제가 있습니다. MySQL에 있는 데이터를 실시간으로 AOS로 복제해야 합니다. 이 과정에서 필요한 시스템 구조와 구체적인 구현 방법에 대해 논의하고자 합니다.

아키텍처

AWS Database Migration Service(AWS DMS)는 관계형 데이터베이스, 데이터 웨어하우스, NoSQL 데이터베이스 및 기타 유형의 데이터 스토어를 마이그레이션할 수 있는 클라우드 서비스입니다. DMS를 사용하여 데이터를 AWS 클라우드로 마이그레이션하거나, 클라우드와 온프레미스 설정 조합 간에 마이그레이션할 수 있습니다. DMS의 주요 구성 요소로는 Replication instance, Replication task, Endpoint가 있습니다.

  • Replication instance는 하나 이상의 Replication task를 호스팅하는 관리형 Amazon Elastic Compute Cloud (Amazon EC2) 인스턴스입니다.
  • Replication task는 실제 데이터 복제 작업을 정의하고 관리합니다
  • Endpoint는 소스 및 대상 데이터 스토어의 연결 정보, 데이터 스토어 유형, 위치 정보 등을 제공합니다

AWS DMS에서 소스로 MySQL를 타겟으로 AOS로 복제할 경우 고려할 사항과 제약 사항들은 다음과 같습니다.

MySQL에서 AOS로 복제하기 위해서는 복제할 테이블에 PK(Primary key) 또는 UK(Unique key)가 선언되어 있어야 합니다. MySQL 테이블의 로우는 OpenSearch 인덱스의 도큐먼트로 대응되며, CDC(Change Data Capture) 모드에서는 테이블의 키 값을 SHA256 해시 변환해서 도큐먼트의 _id로 사용합니다.
MySQL 테이블에 수정 또는 삭제가 발생될 경우, 해당 로우에 해당하는 AOS 인덱스내의 도큐먼트에 수정 또는 삭제가 이루어집니다.

Amazon OpenSearch Service(AOS)를 DMS 엔드포인트로 등록하기 위해서는 Fine-grained access control(FGAC)를 비활성화 해야 합니다. 비활성화 시 AOS와 AOS 대시보드에 인증(사용자명과 암호)과 권한 관리를 설정할 수 없지만, 보안을 강화하는 방법으로 Access policy에서 허가된 IAM과 소스 IP 주소만 접근할 수 있도록 설정할 수 있습니다. 또한 FGAC 활성화된 AOS와 FGAC 비활성화된 AOS간에 Cross-cluster search를 이용하여 클러스터간에 검색할 수 있습니다.

데모

단계1: 사전 작업

사전 작업 단계에서는 소스로 사용할 Amazon Relational Database Service(RDS) MySQL를 생성하고 CDC 복제를 위하여 Binlog를 설정하고 Full load와 CDC 테스트에 사용할 샘플 데이터베이스를 구성합니다. 그리고 타겟으로 사용할 FGAC 비활성화 된 AOS를 생성합니다.

A-1.RDS MySQL에서 실시간 복제(CDC)를 위해 바이너리 로그 형식을 변경해야 합니다. Amazon RDS 콘솔에서 Parameter groups > Create parameter group을 클릭하여 파라미터 그룹을 생성합니다.

    • Parameter group name: “src-rds-mysql-cluster-parameters”
    • Description: “Cluster parameters group for src-rds-mysql”
    • Engine type: “Aurora MySQL”
    • Parameter group family: “aurora-mysql8.0”
    • Type: “DB Cluster Parameter Group”

생성된 “src-rds-mysql-cluster-parameters” 파라미터 그룹에서 binlog_format 파라미터의 값을 OFF에서 ROW로 변경합니다.

A-2. Aurora RDS 콘솔에서 Databases > Create database를 클릭하여 MySQL를 생성합니다.

    • Engine type: “Aurora (MySQL Compatible)”
    • Templates: “Dev/Testr”
    • DB instance identifier: “src-rds-mysql”
    • Credentials management: “Self managed”
    • Master password: “********”
    • VPC security group (firewall): “Create new”
    • New VPC security group name: “src-rds-mysql-sg”
    • Additional configuration > DB cluster parameter group: “src-rds-mysql-cluster-parameters”

A-3. Amazon EC2 콘솔에서 Instances > Lunch Instances를 클릭하여 MySQL 클라이언트를 설치할 EC2를 생성합니다.

    • Name: “mysql-client”
    • Key pair: “Proceed without a key pair”

EC2를 생성한 다음 MySQL과 연결하기 위하여 Next Steps > Connect an RDS database를 클릭합니다.

    • RDS database: “src-rds-mysql”

생성한 EC2에 Connect > Session Manager > Connect를 클릭하여 접속 후 최신 버전의 MySQL 클라이언트를 설치합니다.

$ sudo wget https://dev.mysql.com/get/mysql80-community-release-el9-1.noarch.rpm
$ sudo dnf install mysql80-community-release-el9-1.noarch.rpm -y
$ sudo rpm --import https://repo.mysql.com/RPM-GPG-KEY-mysql-2023
$ sudo dnf install mysql-community-client -y

테스트를 위하여 sakila 샘플 데이터베이스를 구성합니다. A-2에서 생성한 MySQL의 엔드포인트는 Amazon RDS 콘솔에서 Databases > src-rds-mysql를 클릭하여 조회할 수 있습니다.

$ wget https://downloads.mysql.com/docs/sakila-db.zip
$ unzip sakila-db.zip && cd sakila-db
$ mysql -h src-rds-mysql.cluster-cfkiwuihxzny.ap-northeast-2.rds.amazonaws.com -P 3306 -u admin -p
Enter password:
mysql> source sakila-schema.sql
...
mysql> source sakila-data.sql
...
mysql> use sakila
Database changed
mysql> select count(*) from customer;
+----------+
| count(*) |
+----------+
|      599 |
+----------+
1 row in set (0.00 sec)

mysql>

B-1. Amazon OpenSearch Service(AOS) 콘솔에서 Domains > Create domain 클릭하여 OpenSearch 도메인을 생성한다. DMS 엔드포인트로 등록하기 위해서는 Fine-grained access control(FGAC)를 비활성화로 해야 합니다.

    • Domain name: “dst-aos”
    • Domain creation method: “Standard create”
    • Templates: “Dev/test”
    • Deployment option(s): “Domain without standby”
    • Availability Zone(s): “1-AZ”
    • Number of nodes: “1”
    • Network: “Public access”
    • IP address type: “IPv4 only”
    • Fine-grained access control: “disable”

B-2. 생성된 AOS 도메인에 접속하기 위해서 AOS 콘솔에서 Domains > dst-aos > Security configuration tab > Edit를 클릭합니다. Access policy에 AOS 대시보드에 접속할 IP 주소를 등록합니다.

AOS 콘솔에서 Domains > dst-aos > OpenSearch Dashboards URL (IPv4)를 클릭하여 AOS 대시보드에 접속합니다.

단계2: DMS 구성 및 태스크 시작

MySQL에서 AOS로 복제하기 위하여 Replication instances, 소스/타겟 엔드포인트 그리고 태스크를 구성합니다.

A-1. AWS DMS 콘솔에서 Migrate data > Replication instances > Create replication instance를 클릭하여 복제 인스턴스를 생성합니다.

    • Name: “cdc-mysql2aos”
    • High Availability: “Dev or test workload (Single-AZ)”

A-2. replication instance가 MySQL에 접근할 수 있도록 Security group을 수정해야 합니다. Aurora VPC 콘솔에서 Security > Security groups > src-rds-mysql-sg (Security group name) > Edit inbound rules 클릭하여 Inbound rules를 추가 합니다.

    • Type: “MySQL/Aurora”
    • Source: “172.31.0.0/16”
      • DMS replication instance를 생성할 VPC의 IPv4 대역

A-3. DMS에게 AOS 요청을 위한 권한을 부여해야 합니다. IAM 콘솔에서 Roles > Create role를 클릭합니다.

    • Select trusted entity > Use case: “DMS”
    • Add permissions: “AmazonOpenSearchServiceFullAccess”
    • Role name: “dms-aos-fullaccess”

B-1. AWS DMS 콘솔에서 Endpoints > Create endpoint를 클릭하여 MySQL 소스 엔드포인트와 AOS 타겟 엔드포인트를 생성합니다.

    • MySQL 소스 엔드포인트
      • Endpoint type: “Source endpoint”
      • Endpoint identifier: “src-rds-mysql”
      • Source engine: “Amazon Aurora Mysql”
      • Access to endpoint database: “Provide access information manually”
      • Server name: “src-rds-mysql.cluster-cfkiwuihxzny.ap-northeast-2.rds.amazonaws.com”
        • 소스로 사용할 Amazon RDS (src-rds-mysql)의 엔드포인트
      • Port: “3306”
      • User name: “admin”
      • Password: “********”
    • AOS 타겟 엔드포인트
      • Endpoint type: “Target endpoint”
      • Endpoint identifier: “dst-aos”
      • Target engine: “Amazon OpenSearch Service (successor to Amazon Elasticsearch Service)”
      • Amazon Resource Name (ARN) for service access role: “arn:aws:iam::410073890645:role/dms-aos-fullaccess”
        • dms-aos-fullaccess 권한의 ARN
      • Endpoint URI: “https://search-dst-aos-yahi4pvel6hulhzj7ujwtcbwqu.ap-northeast-2.es.amazonaws.com”
        • 타겟으로 사용할 AOS ()의 Domain endpoint
      • Endpoint settings: “UseNewMappingType” 추가
        • AOS와 Elasticsearch 7.x 이상인 경우에는 doc 문서 유형을 지원하지 않고 _doc 문서 유형만 지원하기 때문에, “UseNewMappingType” 옵션을 true로 설정하여 DMS 타켓 엔드포인트에서 AOS로 RestAPI 요청시 _doc을 사용하도록 지정

B-2. AWS DMS 콘솔에서 Endpoints > [src-rds-mysql|dst-aos] > Connections > Test connections를 클릭하여 replication instance에서 엔드포인트로 연결이 되는지 테스트합니다.

C-1. AWS DMS 콘솔에서 Database migration tasks > Create task를 클릭하여 MySQL의 customer와 rental 테이블을 복제할 태스크를 생성합니다.

    • Task identifier: “cdc-mysql2aos-task1”
    • Replication instance: “cdc-mysql2aos”
    • Source database endpoint: “src-rds-mysql”
    • Target database endpoint: “dst-aos”
    • Migration type: “Migrate existing data and replicate ongoing changes”
    • Task logs: check
    • Table mappings > Add new selection rule

    • Premigration assessment: 체크 해제
      • 본 블로그에서는 해제하여 진행하였지만, 개발/프로덕션 환경에서는 체크하여 진행하는 것을 권장합니다.

태스크가 정상적으로 생성되면 상태가 “created”로 표시됩니다.

C-2. 생성한 태스크를 선택하고 Actions > Restart/Resume을 클릭하면 Full load를 진행하고 CDC를 시작하면서 상태는 “Load complete, replication ongoing”로 표시됩니다.

단계3: 복제 검증 (Full load)

AWS DMS 콘솔에서 Database migration tasks > cdc-mysql2aos-task1 > Table statistics를 클릭하여 테이블별로 Full load한 시간과 건수를 조회할 수 있습니다.

복제를 검증하기 위하여 우선 MySQL에서 테이블 별 데이터 건수와 특정 로우 데이터를 조회합니다.

mysql> select count(*) from customer;
+----------+
| count(*) |
+----------+
|      599 |
+----------+
1 row in set (0.00 sec)

mysql> select * from customer limit 1;
+-------------+----------+------------+-----------+-------------------------------+------------+--------+---------------------+---------------------+
| customer_id | store_id | first_name | last_name | email                         | address_id | active | create_date         | last_update         |
+-------------+----------+------------+-----------+-------------------------------+------------+--------+---------------------+---------------------+
|           1 |        1 | MARY       | SMITH     | MARY.SMITH@sakilacustomer.org |          5 |      1 | 2006-02-14 22:04:36 | 2006-02-15 04:57:20 |
+-------------+----------+------------+-----------+-------------------------------+------------+--------+---------------------+---------------------+
1 row in set (0.01 sec)

mysql> select count(*) from rental;
+----------+
| count(*) |
+----------+
|    16044 |
+----------+
1 row in set (0.00 sec)

mysql> select * from rental limit 1;
+-----------+---------------------+--------------+-------------+---------------------+----------+---------------------+
| rental_id | rental_date         | inventory_id | customer_id | return_date         | staff_id | last_update         |
+-----------+---------------------+--------------+-------------+---------------------+----------+---------------------+
|         1 | 2005-05-24 22:53:30 |          367 |         130 | 2005-05-26 22:04:30 |        1 | 2006-02-15 21:30:53 |
+-----------+---------------------+--------------+-------------+---------------------+----------+---------------------+
1 row in set (0.00 sec)

mysql>

AOS 대시보드의 Dev tools에서 복제된 인덱스의 건수와 특정 도큐먼트를 조회하여 MySQL과 일치하는지 확인합니다.

GET /customer/_count
{
  "count": 599,
  "_shards": {
    "total": 5,
    "successful": 5,
    "skipped": 0,
    "failed": 0
  }
}

GET /customer/_search
{
  "query": {
    "term": {
      "customer_id": 1
    }
  }
}
{
  "took": 4,
  "timed_out": false,
  "_shards": {
    "total": 5,
    "successful": 5,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total": {
      "value": 1,
      "relation": "eq"
    },
    "max_score": 1,
    "hits": [
      {
        "_index": "customer",
        "_id": "6B86B273FF34FCE19D6B804EFF5A3F5747ADA4EAA22F1D49C01E52DDB7875B4B",
        "_score": 1,
        "_source": {
          "customer_id": 1,
          "store_id": 1,
          "first_name": "MARY",
          "last_name": "SMITH",
          "email": "MARY.SMITH@sakilacustomer.org",
          "address_id": 5,
          "active": 1,
          "create_date": "2006-02-14T22:04:36Z",
          "last_update": "2006-02-15T04:57:20Z"
        }
      }
    ]
  }
}

GET /rental/_count
{
  "count": 16044,
  "_shards": {
    "total": 5,
    "successful": 5,
    "skipped": 0,
    "failed": 0
  }
}

GET /rental/_search
{
  "query": {
    "term": {
      "rental_id": 1
    }
  }
}
{
  "took": 3,
  "timed_out": false,
  "_shards": {
    "total": 5,
    "successful": 5,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total": {
      "value": 1,
      "relation": "eq"
    },
    "max_score": 1,
    "hits": [
      {
        "_index": "rental",
        "_id": "6B86B273FF34FCE19D6B804EFF5A3F5747ADA4EAA22F1D49C01E52DDB7875B4B",
        "_score": 1,
        "_source": {
          "rental_id": 1,
          "rental_date": "2005-05-24T22:53:30Z",
          "inventory_id": 367,
          "customer_id": 130,
          "return_date": "2005-05-26T22:04:30Z",
          "staff_id": 1,
          "last_update": "2006-02-15T21:30:53Z"
        }
      }
    ]
  }
}

단계4: 테스트 (CDC)

cdc-mysql2aos-task1 태스크가 실행 중인 상태에서 MySQL customer 테이블에 새로운 데이터를 입력했을 때 AOS customer 인덱스에 복제되는지 확인해 보겠습니다.

  • MySQL
    Mysql> insert into customer values (600, 1, "Yoosung", "jeon", "yoosung@amazon.com", 5, 1, curdate(), curdate());
    Query OK, 1 row affected (0.00 sec)
    
    mysql> select * from customer where customer_id = 600;
    +-------------+----------+------------+-----------+--------------------+------------+--------+---------------------+---------------------+
    | customer_id | store_id | first_name | last_name | email              | address_id | active | create_date         | last_update         |
    +-------------+----------+------------+-----------+--------------------+------------+--------+---------------------+---------------------+
    |         600 |        1 | Yoosung    | jeon      | yoosung@amazon.com |          5 |      1 | 2024-10-06 10:08:36 | 2024-10-06 00:00:00 |
    +-------------+----------+------------+-----------+--------------------+------------+--------+---------------------+---------------------+
    1 row in set (0.00 sec)
    
    mysql>
  • AOS
  • DMS 콘솔

MySQL에서 customer 테이블에 입력한 customer_id가 600인 고객의 active 컬럼 값을 2로 변경시, AOS에는 기존에 있던 도큐먼트의 active 필드 값만 변경됩니다.

  • MySQL
    mysql> update customer set active = 2 where customer_id = 600;
    Query OK, 1 row affected (0.01 sec)
    Rows matched: 1  Changed: 1  Warnings: 0
    
    mysql>
  • AOS

MySQL에서 customer 테이블의 customer_id가 600인 고객 데이터를 삭제하면 AOS에서도 해당 도큐먼트가 삭제됩니다.

  • MySQL
    mysql> delete from customer where customer_id = 600;
    Query OK, 1 row affected (0.00 sec)
    
    mysql>
  • AOS

결론

AWS DMS를 활용하여 MySQL 데이터베이스에서 AOS로 쉽고 간편하게 실시간 데이터 복제를 성공적으로 구현했습니다. 이 과정에서 다음과 같은 주요 사항들을 고려하고 해결했습니다:

  • 실시간 데이터 동기화: DMS를 사용하여 MySQL의 데이터 변경사항을 실시간으로 AOS에 반영할 수 있었습니다. Full load와 CDC(Change Data Capture) 모두 효과적으로 작동하는 것을 확인했습니다.
  • 아키텍처 고려사항: MySQL 테이블에 PK 또는 UK 선언, AOS의 Fine-grained access control 비활성화 등 구현 시 주의해야 할 기술적 요구사항들을 파악하고 적용했습니다.
  • 보안 설정: FGAC 비활성화 된 AOS의 접근 제어를 위해 IP 기반 접근 제어를 활용하여 보안을 강화하였습니다..

이러한 구현을 통해 MySQL 데이터베이스의 데이터를 AOS로 실시간 복제하는 안정적이고 효율적인 시스템을 구축할 수 있었습니다. 이는 향후 시멘틱 검색 기능 도입 등 고객 경험 개선을 위한 중요한 첫 단계가 될 것입니다. 또한, 이 아키텍처는 다양한 데이터 분석 및 검색 기능 구현을 위한 견고한 기반을 제공할 것으로 기대됩니다.

YooSung Jeon

YooSung Jeon

전유성 솔루션즈 아키텍트는 통신/공공 산업군에서 데이터 분석과 다양한 오픈소스 활용 경험을 바탕으로 DNB(Digital Native Business) 고객을 대상으로 고객의 비즈니스 성과를 달성하도록 최적의 아키텍처를 구성하는 역할을 수행하고 있습니다.

Jiyun Park

Jiyun Park

박지윤 솔루션즈 아키텍트는 소프트웨어&인터넷 기반으로 비지니스를 하는 다양한 고객들의 워크로드를 마이그레이션하는데 기술 지원을 드리고, 고객의 비지니스 성과 달성을 위해 최적의 클라우드 솔루션을 제공하고 있습니다.