AWS: Database Migration Service, часть 1 — обзор и пример миграции self-hosted MariaDB в AWS Aurora RDS

Автор: | 08/27/2020
 

В общем-то — продолжение эпопеи с миграцией приложения из Digital Ocean в Amazon Web Services.

В посте Kubernetes: нагрузочное тестирование и high-load тюнинг — проблемы и решения мы тестировали работу самого приложения в Kubernetes, следующая задача — перенести базу данных.

Сейчас база данных проекта находится в Digital Ocean, на обычном дроплете с Linux и MariaDB. Сама база порядка 80 гигабайт, некоторые таблицы — более 100 миллионов записей.

Переносить будем в AWS RDS Aurora MySQL.

Задача

Что мы хотим: максимально безболезнено перенести саму базу, и переключить приложение на работу с новой БД, при этом желательно обойтись либо без даунтаймов вообще — либо с каким-то максимально минимальным прерыванием сервиса.

Решение

Что используем: AWS Database Migration Service (AWS DMS) — предназначена для переноса баз данных в AWS, при этом исходная база остаётся полностью рабочей:

Обычно ссылки добавляю в конце, но в этот раз пусть будут тут, плюс по ходу в самом посте:

Кроме того, в исходной базе используется партиционирование, и надо проверить как будет работать приложение после миграции, т.к. партиционирование переносить не планируется. См. So, What is MySQL Partitioning? (проверили — не играет роли вообще).

Сначала немного теории, и потом приступим к практике.

Homogeneous vs Heterogeneous migration

homogeneous migration: миграция из source базы в target базу, где и source и target работают под управлением одной и той же системы управления базами данных, например — MySQL в MySQL (или MySQL в MariaDB)

heterogeneous migration: миграция из source базы в target базу, где и source и target работают под управлением различных системы управления базами данных, например — MySQL в PostgreSQL.

В нашем случае это будет homogeneous миграция.

Database Replication vs Migration

При database migration данные переносятся из одной базы данных в другую. После миграции данных — исходная база данных удаляется, а все клиенты перенаправляются на новую базу.

В некоторых случаях исходная база может оставаться в работе на случай проблем с новой базой, но после того, как приложение начинает нормально работать с новой базой — старая удаляется.

При database replication — данные постоянно трансилуются из исходной базы в новую без удаления старой. Так же, такая репликация иногда называется database streaming.

«У репликации есть начало — но нет конца» — обе базы могут оставаться рабочими и использоваться приложением, хотя в конце-концов репликация может стать миграцией.

Варианты миграции

  1. RDS MySQL в RDS Aurora: через снапшот — создаётся копия (snapshot) MySQL RDS базы, из которой потом разворачивается Aurora RDS кластер
  2. RDS MySQL в RDS Aurora: через Aurora Slave инстанс — из копии RDS MySQL создаётся Slave Aurora, который потом промоутится до Master-хоста
  3. MySQL self-hosted: с помощью стандартных инструментов типа Percona XtraBackup или mysqldump — создаём снапшот базы, загружаем в AWS S3 корзину, потом из снапшота в корзине разворачиваем Aurora RDS кластер

Или, разумеется — AWS Database Migration Service, который мы и используем.

Schema Conversion Tool vs Database Migration Service

В некоторых случаях ни один из вариантов может не работать, например при миграции MySQL 5.7 на Aurora, которая совместима с  MySQL 5.6.

В таком случае создание из RDS-снапшота или из MySQL бекапа в AWS S3 и затем создание Aurora Read Replica для MySQL может не сработать из-за несовместимости версий MySQL.

В таком случае используется AWS Schema Conversion Tool (AWS SCT) + AWS Database Migration Service (AWS DMS).

SCT

SCT может применяться во время миграций MySQL InnoDB в Amazon Aurora, для переноса и изменения схемы, процедур и функций из исходной БД в новую базу.

Учтите, что SCT не выполняет переноса пользователей — может потребоваться их ручное создание.

Совместимость и варианты миграции с использованием SCT:

Source database Target database on Amazon RDS
Microsoft SQL Server (version 2008 and later) Amazon Aurora with MySQL compatibility, Amazon Aurora with PostgreSQL compatibility, MariaDB 10.2 and 10.3, Microsoft SQL Server, MySQL, PostgreSQL
MySQL (version 5.5 and later) Aurora PostgreSQL, MySQL, PostgreSQL

You can migrate schema and data from MySQL to an Aurora MySQL DB cluster without using AWS SCT. For more information, see Migrating data to an Amazon Aurora DB cluster.

Oracle (version 10.2 and later) Aurora MySQL, Aurora PostgreSQL, MariaDB 10.2 and 10.3, MySQL, Oracle, PostgreSQL
PostgreSQL (version 9.1 and later) Aurora MySQL, MySQL, PostgreSQL
IBM Db2 LUW (versions 9.1, 9.5, 9.7, 10.5, and 11.1) Aurora MySQL, MariaDB 10.2 and 10.3, MySQL, PostgreSQL, Aurora PostgreSQL
Apache Cassandra (versions 2.0, 3.0, 3.1.1, and 3.11.2) Amazon DynamoDB
Sybase (16.0 and 15.7) Aurora MySQL, Aurora PostgreSQL, MySQL, PostgreSQL

DMS

Другой вариант миграции — с использованием AWS DMS.

DMS может выполнить единоразовую миграцию, либо выполнять постоянную репликацию двух баз данных.

В случае миграции MySQL базы в Aurora — мы сначала выполним копирование всех данных, а затем будет выполняться репликация, или Change Data Capture (CDC).

Для этого DMS требует включенного бинлога транзакций на исходной базе, см. Prerequisite и в документации.

Prerequisite

Проверим бинлоги.

В AWS RDS настраивается через parameter group, в on-demand MySQL/MariaDB — через my.cnf.

Проверяем на сервер-источнике:

Dev — отключены:

MariaDB [(none)]> show binary logs;
ERROR 1381 (HY000): You are not using binary logging

На Production всё включено, т.к. там имеется активная read-replica:

MariaDB [(none)]> show binary logs;
+------------------+------------+
| Log_name         | File_size  |
+------------------+------------+
| mysql-bin.000271 | 1073742323 |
| mysql-bin.000272 | 1073742366 |
| mysql-bin.000273 |  245820301 |
+------------------+------------+
3 rows in set (0.107 sec)

И сама база, которую мы сегодня будем мигрировать:

MariaDB [(none)]> SELECT table_name AS table_name, engine, ROUND(data_length/1024/1024,2) AS total_size_mb, table_rows FROM  information_schema.tables WHERE table_schema='new-eat';
+---------------------------+--------+---------------+------------+
| table_name                | engine | total_size_mb | table_rows |
+---------------------------+--------+---------------+------------+
| acl_permissions_acl_roles | InnoDB |          0.02 |         49 |
| orders                    | InnoDB |      27873.50 |   31894446 |
...
| order_logs                | InnoDB |      39779.89 |  111170699 |
| menus                     | InnoDB |          0.02 |          0 |
| payment_logs              | InnoDB |       4117.00 |    5463699 |
...
| order_cookies             | InnoDB |        679.00 |    3061810 |
| customers                 | InnoDB |       1275.00 |   20122134 |
...
| payments                  | InnoDB |       1695.95 |    5319672 |
+---------------------------+--------+---------------+------------+

Как будем мигрировать?

А как-что переключать будем?

Что есть:

  • приложение в DigitalOcean
  • сервер баз данных в DigitalOcean

Что надо:

  • приложение в AWS Elastic Kubernetes Service
  • база в AWS Aurora RDS MySQL

Как будем переключать:

  1. мигрируем базу данных, оставляем включенным Change Data Set — продолжаем реплицировать изменения из DigitalOcean в AWS
  2. переключаем приложение с базы в DigitalOcean на базу в AWS Aurora RDS
  3. деплоим приложение в Kubernetes, приложение настроено на AWS Aurora RDS
  4. на DNS переключим домен с адреса LoadBalancer в DigitalOcean на AWS LoadBalancer, созданный Kubernetes AWS ALB Controller

Перед таким переключением надо проверить скорость ответа приложения в DigitalOcean при работе с базой из DigitalOcean и из AWS RDS Aurora.

Создание DMS миграции

За основую использовался документ вот отсюда>>>.

Replication Instance

Документация — Working with an AWS DMS replication instance.

Replication Instance — такой себе «промежуточный» сервер баз данных, который собственно и будет ходить в наш Source Target, получать оттуда данные, и сохранять у себя, а потом передавать их на Target Instance.

Переходим в DMS => Replication instances => Create replication instance:

General settings

  • instance type — мы ожидаем 80 гиг базу, с таблицами свыше 100 миллионов записей, так что возьмём машину посерьёзнее, см. документацию тут>>>

    R4 instances are memory optimized for memory-intensive workloads. Ongoing migrations or replications of high-throughput transaction systems using AWS DMS can also consume large amounts of CPU and memory. R4 instances include more memory per vCPU than earlier generation instance types.

  • Engine version — оставляем дефолтную, последнюю
  • Allocated storage — у нас только база 80 гиг, плюс запас — пусть будет 200 гиг
  • VPC — наш Target instance живёт в своей отдельной AWS VPC, поселим реплику туда же, что бы не возиться с Security Groups
  • Multi AZ — не вижу смысла
  • Publicly accessible — тоже особо разницы нет — подключиться к нему мы всё-равно не сможем (но это не точно), но пусть будет — всё равно после миграции убьём этот сервер

Advanced security and network configuration

  • Replication subnet group — вот тут я не понял: VPC выбрана aurora-web-dev-vpc, а в Replication subnet group доступна subnet из default VPC… ну — ладно, пусть будет
  • Availability zone — без разницы
  • VPC security group(s) — тут ОК, доcтупна SecurityGroup из выбранной VPC, используем её

В Maintenance — ничего не меняем, жмём Create:

Пока создаётся инстанс — идём в Endpoints, и создадим наши Source и Target endpoints.

Endpoints

Source endpoint

В DigitalOcean сейчас запущен отдельный дроплет, на котором полная копия Production базы данных — это будет наш Source, источник данных.

Создаём Source Endpoint:

  • Endpoint identifier — имя ендпоинта, как мы будем его видеть в списке дашборды DMS
  • Source engine — у нас в DigitalOcean работает MariaDB, выбираем mariadb (хотя и с mysql типом работало вроде нормально)
  • Server name — адрес сервера (при чём тут «name»? можно было яснее поле назвать — Server address, не?)
  • port, username, password — понятно, указываем рутовые данные доступа

Endpoint-specific settings, KMS master key, Tags — пропускаем.

Test endpoint connection — имеет смысл проверить подключение.

Не очень понял, какую VPC он тут запрашивает, но выберем нашу, в которой запущен и будущий Target, и наш Replication instance (но работает и без выбора VPC вообще).

Запускаем тест, ждём минуту — Successful:

Кликаем Create Endpoint.

Target endpoint

Тут всё тоже самое, только вверху выбираем тип Target Instance, и Select RDS DB instance:

Ендпоинты готовы:

Помните про Security Groups между Replication, Source и Target инстансами — должен быть доступ по порту 3306 (в случае MySQL/MariaDB).

Migration task

Переходим в Database migration tasks, кликаем Create task:

Task configuration

Указываем имя задачи, как она будет отображаться у нас в DMS, выбираем Replication instance, Source и Target.

В Migration type выбираем Migrate existing data and replicate ongoing changes — склонируем всю базу, а затем будем подтягивать изменения, которые в ней будут появляться.

  • Migrate existing data only — Use this migration type for one-time migrations.
  • Migrate existing data and replicate ongoing changes — Use this migration type to migrate large databases to the AWS Cloud with minimal downtime.
  • Migrate ongoing replication changes — Use this migration type when you have already migrated the existing data and want to synchronize the source database with the target

Task settings

  • Target table preparation mode — выбираем Drop tables on target — пересоздадим таблицу, если она уже есть в Авроре
  • Stop task after full load completes — не останавливаем Change Data Capture (CDC), пусть тянет себе изменения дальше — в Production миграции так и будет, пока мы не переключим приложение на новый сервер баз данных
  • Include LOB columns in replication — у нас нет никаких LOB, пусть остаётся по-умолчанию, если есть — см. Setting LOB support for source databases in an AWS DMS task
  • Enable validation — давайте включим, посмотрим что там будет
  • Enable CloudWatch logs — можно включить на уровень Warning:

Table mappings

Выбираем Guided UI, кликаем Add new selection rule.

В правилах описываем что именно мы хотим клонировать — какие базы данных (схемы), и какие таблицы из них:

Transformation rules позволяет в процессе миграции изменить схему, таблицы или другие объекты. Нам сейчас не надо, пропускаем.

Premigration assessment — интересно, но сейчас не хочу тратить время на создание корзины и IAM-политики

Migration task startup configuration — выбираем ручной запуск.

Advanced task settings тоже оставляем, как есть.

Жмём Create task, ждём пару минут — Ready:

Запуск DMS Migration task

Выбираем задачу и Restart/Resume:

На Target, в Aurora, проверяем базы сейчас:

MySQL [(none)]> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| awsdms_control     |
| mysql              |
| performance_schema |
+--------------------+
4 rows in set (0.127 sec)

Вот, кстати, и база awsdms_control из настроек Database migration task.

Процессы на Source сервере:

MariaDB [new-eat]> SELECT id,User,Host,db,command,time,state,info FROM information_schema.processlist WHERE Host LIKE 'ec2-%' AND command != 'Sleep';
+------+------+------------------------------------------------------+------+-------------+------+------------------------------------------------------------------+------------------------------------------------------------------------------------------------+
| id   | User | Host                                                 | db   | command     | time | state                                                            | info                                                                                           |
+------+------+------------------------------------------------------+------+-------------+------+------------------------------------------------------------------+------------------------------------------------------------------------------------------------+
| 4055 | root | ec2-3-***-*-78.us-east-2.compute.amazonaws.com:54152 | NULL | Query       |   39 | Writing to net                                                   | SELECT `id`,`created_at`,`updated_at`,`order_id`,`action`,`data`  FROM `new-eat`.`order_logs`  |
| 4051 | root | ec2-3-***-*-78.us-east-2.compute.amazonaws.com:53936 | NULL | Query       |   42 | Writing to net                                                   | SELECT `id`,`order_id`,`order_hash`,`cookie`  FROM `new-eat`.`order_cookies`                   |
...
| 4030 | root | ec2-3-***-*-78.us-east-2.compute.amazonaws.com:53650 | NULL | Binlog Dump |   51 | Master has sent all binlog to slave; waiting for binlog to be up | NULL                                                                                           |
+------+------+------------------------------------------------------+------+-------------+------+------------------------------------------------------------------+------------------------------------------------------------------------------------------------+

Тут ec2-3-***-*-78.us-east-2.compute.amazonaws.com — это наш Replication instance, который загружает к себе данные из Source.

На Target, Aurora:

MySQL [(none)]> show processlist;
+----+----------+----------------------+----------------+---------+------+----------------------+------------------------------------------------------------------------------------------------------+
| Id | User     | Host                 | db             | Command | Time | State                | Info                                                                                                 |
+----+----------+----------------------+----------------+---------+------+----------------------+------------------------------------------------------------------------------------------------------+
| 11 | rdsadmin | localhost            | NULL           | Sleep   |    0 | delayed send ok done | NULL                                                                                                 |
...                                                                                               |
| 50 | admin    | 3.***.*.78:57872     | NULL           | Sleep   |   79 | delayed send ok done | NULL                                                                                                 |
| 51 | admin    | 3.***.*.78:57874     | awsdms_control | Query   |    5 | System lock          | load data local infile "/rdsdbdata/data/tasks/OMUZV4MRJHDFMRKRUNPSPCLZWMD7ME6VFRXOADY/data_files/13/ |
| 52 | admin    | 3.***.*.78:57876     | awsdms_control | Sleep   |   71 | cleaned up           | NULL                                                                                                 |
...                                                                                              |
| 66 | admin    | 3.***.*.78:59006     | awsdms_control | Sleep   |   62 | cleaned up           | NULL                                                                                                 |
+----+----------+----------------------+----------------+---------+------+----------------------+------------------------------------------------------------------------------------------------------+

Тут хост 3.***.*.78 — это наш Replication instance, который загружает в Аврору данные, которые он получил из Source базы.

Миграция пошла:

Load state в DMS — см. Monitoring AWS DMS tasks.

У нас уже почти все таблицы в Table completed — Full load has completed, и три самых больших ещё в Full load — The full load process is in progress.

На Авроре уже появилась база:

MySQL [(none)]> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| awsdms_control     |
| mysql              |
| new-eat            |
| performance_schema |
+--------------------+

И даже таблицы:

MySQL [new-eat]> show tables;
+---------------------------+
| Tables_in_new-eat         |
+---------------------------+
| acl_permissions           |
| acl_permissions_acl_roles |
...
| users                     |
+---------------------------+
24 rows in set (0.125 sec)

И даже данные:

MySQL [new-eat]> select * from cron;
+----+---------------------+---------------------+-------------+------+--------------------+-----------------------------------------------------------------------+------------+----------------+-------------+
| id | created_at          | updated_at          | expression  | type | command            | description                                                           | is_enabled | in_maintenance | can_overlap |
+----+---------------------+---------------------+-------------+------+--------------------+-----------------------------------------------------------------------+------------+----------------+-------------+
|  1 | 2020-01-23 10:24:48 | 2020-05-27 19:41:02 | 0 20 * * *  |    1 | cron:send-offer    | Ремаркетинг через имейлы                                              |          1 |              0 |           0 |
|  3 | 2020-01-23 10:43:12 | 2020-03-03 09:46:01 | * * * * *   |    1 | inspire            | Проверка что планировщик работает                                     |          0 |              1 |           1 |
|  5 | 2020-04-30 22:29:39 | 2020-05-27 19:41:05 | */5 * * * * |    1 | cron:send-esputnik | Отправка НЕ оплаченных заказов в esputnik                             |          1 |              0 |           0 |
+----+---------------------+---------------------+-------------+------+--------------------+-----------------------------------------------------------------------+------------+----------------+-------------+

High traffic!

Трафик в самом деле выжирает знатно — учтите перед миграцией, бывали пики до 300 мб/с:

Для Production — мы будем запускать миграцию с read-replica, что бы не забить канал основного сервера баз данных.

Результаты миграции

Миграция заняла около полутора часов на dms.r4.xlarge — порядка 80 гигабайт база с несколькими таблицами по ~110 миллионов записей:

replication ongoing — значит, DMS должен подтянуть любые изменения в текущей Source-базе.

DMS replication

Проверим процессы на Source:

MariaDB [(none)]> SELECT id,User,Host,db,command,time,state,info FROM information_schema.processlist WHERE Host LIKE 'ec2-%' AND command != 'Sleep';
+------+------+------------------------------------------------------+------+-------------+-------+------------------------------------------------------------------+------+
| id   | User | Host                                                 | db   | command     | time  | state                                                            | info |
+------+------+------------------------------------------------------+------+-------------+-------+------------------------------------------------------------------+------+
| 4030 | root | ec2-3-***-*-78.us-east-2.compute.amazonaws.com:53650 | NULL | Binlog Dump | 13400 | Master has sent all binlog to slave; waiting for binlog to be up | NULL |
+------+------+------------------------------------------------------+------+-------------+-------+------------------------------------------------------------------+------+

Чтение бинлога идёт, а изменения применятся?

Добавим руками запись в исходную базу, например — создадим новую таблицу:

MariaDB [new-eat]> CREATE TABLE dms_test (id varchar(255) PRIMARY KEY);
Query OK, 0 rows affected (0.116 sec)

Проверяем в DMS:

И в Aurora:

MySQL [new-eat]> show tables like "dms_test";
+----------------------------------+
| Tables_in_new-eat (dms_test) |
+----------------------------------+
| dms_test                         |
+----------------------------------+
1 row in set (0.123 sec)

Всё скопировалось.

Ошибки DMS

При первом запуске наткнулся на несколько ошибок — вынесу их отдельным блоком.

Errors in MySQL server binary logging configuration

Ошибка:

Last Error Failed in resolving configuration. Task error notification received from subtask 0, thread 0 [reptask/replicationtask.c:2814] [1020418] Error Code [10002] : MySQL binary Logging must use ROW format; Errors in MySQL server binary logging configuration. Follow all prerequisites for ‘MySQL as a source in DMS’ from https://docs.aws.amazon.com/dms/latest/userguide/CHAP_Source.MySQL.html or’MySQL as a target in DMS’ from https://docs.aws.amazon.com/dms/latest/userguide/CHAP_Target.MySQL.html ; Failed while preparing stream component ‘st_0_V4MHLPR4QEGCKXNIEW6EFBECZJWXY45HPAH5VDA’.; Cannot initialize subtask; Stream component ‘st_0_V4MHLPR4QEGCKXNIEW6EFBECZJWXY45HPAH5VDA’ terminated [reptask/replicationtask.c:2821] [1020418] Stop Reason FATAL_ERROR Error Level FATAL

Проверяем настройки в Source:

MariaDB [(none)]> show variables like 'binlog_format';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| binlog_format | MIXED |
+---------------+-------+

Читаем документацию — Using a self-managed MySQL-compatible database as a source for AWS DMS, меняем настройки в MariaDB:

MariaDB [(none)]> SET GLOBAL binlog_format = ROW;

Проверяем:

MariaDB [(none)]> show variables like 'binlog_format';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| binlog_format | ROW   |
+---------------+-------+

Заодно меняем binlog_checksum:

MariaDB [(none)]> show variables like 'binlog_checksum';
+-----------------+-------+
| Variable_name   | Value |
+-----------------+-------+
| binlog_checksum | CRC32 |
+-----------------+-------+

Должен быть в NONE.

CloudWatch logs && DMS

Логи? Ага, счаз:

Иногда не создаются Log Group, почему — не знаю, написал в тех. поддержку.

UPD: уже перед самой «отправкой в печать» этого поста — пришёл ответ от тех. поддержки. В двух словах — «В AWS IAM что-то пошло не так, как должно — поправьте вручную»:

I have investigated the DMS task and found that logging is enabled. This generally occurs when DMS does not have the needed role to publish logs to CloudWatch. What happens in the back-end is that every running tasks will have its logs created on the Replication Instance (In DMS). DMS will then try to push the logs to CloudWatch for so that you can view the Logs from CloudWatch. Due to permissions, DMS as a service would need a role that allows it Access to CloudWatch for it to be able to publish/push the task logs to CloudWatch. If this role does not exist, then DMS silently fails and when you check for logs in CloudWatch, you will not find any.

This role is called «dms-cloudwatch-logs-role». I checked for this role under your environment and could not find it. Usually if you create the Task using the DMS Console, this role automatically gets added to your environment. If you however create your resources using the CLI or SDK, then you need to manually add this role as explained on the link [1] under references.

Ну, окей — мелочь, в принципе, но для Production миграции надо будет включить.

UPD 2: включено, см. CloudWatch logs

После миграции нет AUTO_INCREMENT и индексов

См. продолжение — AWS: Database Migration Service, часть 2 — нет AUTO_INCREMENT и индексов. Фиксы для «foreign key constraint fails» и логов CloudWatch.