Akatsuki Hackers Lab | 株式会社アカツキ(Akatsuki Inc.)

Akatsuki Hackers Labは株式会社アカツキが運営しています。

S3 ExportされたAuroraのデータをAWS Glueを使ってDBに書き戻す

Amazon AuroraのS3エクスポート機能

AuroraのS3エクスポート機能は、DBクラスターの現在のデータやスナップショットのデータをS3にApache Parquet形式で出力する機能です。
Apache Parquet形式は、スキーマ情報を内包している・列志向で分析用途にも適している・高効率な圧縮が可能・複雑なデータ構造にも対応しているといった特徴を備えたデータ形式です。AthenaやRedshift、あるいはBigQueryへの取り込みに利用することができ、これらを使ったDB内のデータ分析が可能になります。

高効率さについての実験として、試しに手元の64GBのクラスタースナップショットをS3エクスポートしてみたところ、出力されたParquetファイルの合計サイズは約4GBと、なんと1/16にも圧縮されました。特に毎日何TBもの大容量データをリージョン外に転送するようなケースではネットワーク転送費が月百万円オーダーになるため、圧縮効率も重要になってきます。

そんなS3エクスポートにも欠点はあります。Aurora自身はParquet形式のデータインポート機能を備えておらず、エクスポートしたデータを読み込めません。*1
とはいえ書き出したデータをAuroraに復元できると有効活用できるシーンもあるため、これをAWS Glueを使うことで簡単に実現できないか検討してみました。

構成

前提

(1) Aurora MySQL 3.0 (MySQL 8.0互換) で検証
(2) テーブル構造はAurora内にあるものとする
Parquet 形式はスキーマを保持していますが、MySQLのスキーマが完全に維持されるわけではないため、この前提を置いています。これはインポート前にマイグレーションを走らせるなどしてテーブルを作っておけば満たせます。

AWS Glueのセットアップ

Glue がエクスポート先のS3および復元先のAurora DBクラスターにアクセスできるようにします。基本的には下記Glueのドキュメント通りなのですが、注意が必要な点について触れておきます。
AWS Glue 用 JDBC データストアに接続するための VPC の設定 - AWS Glue

IAM Roleの設定

最初にGlueが利用するIAM Roleの設定が必要です。

AWS Glue Studio のセットアップ - AWS Glue Studio
Glue Studioのドキュメントを参考にセットアップを行います。コンソール上で設定を進めていくと、IAMロールが作成されます。S3バケットへのアクセス許可をGlueのGUIで行うこともできますが、RDSからエクスポートされたデータはKMS鍵で暗号化されているため、この鍵を使った復号も許可する必要があります。このため、Glue用のマネージドポリシーに加えて、以下のようなポリシーを追加する必要があります。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "KMSDecrypt",
            "Effect": "Allow",
            "Action": "kms:Decrypt",
            "Resource": "arn:aws:kms:<REGION>:<アカウントID>:key/<キーID>"
        },
        {
            "Sid": "S3Access",
            "Effect": "Allow",
            "Action": ["s3:Get*","s3:List*"],
            "Resource": [
                "arn:aws:s3:::<バケット名>",
                "arn:aws:s3:::<バケット名>/*"
            ]
        }
    ]
}

Connectionsの設定

Auroraにアクセスするためには、GlueがVPC内にアクセスするための設定が必要です。

AWS GlueのサイドバーからData connectionsを開き、Connections セクションの Create connection ボタンを押します。Connection typeを開くとAuroraなど色々なDBの選択肢が出てきますが、後述する手順ではあくまでVPCアクセスのためだけにConnectionsを使用するため、ここではNetworkを選びます。そして、S3バケットにアクセス可能かつAuroraクラスターの書き込みエンドポイントに接続可能なVPC・サブネット・セキュリティグループを選択します。ここで選択したENI(ネットワークインターフェイス)を介してGlueのワーカーが通信をすることになります。

(S3については、具体的にはVPCエンドポイントかNAT Gatewayを介してS3にアクセスできる必要があります。)

Glueジョブの作成と実行を行うスクリプト

インポート処理は、データ変換の必要もないため、読み取ったParquetのデータをそのままJDBCコネクタに流すだけのシンプルなものです。この場合、複数のテーブルをインポートする場合は複数のジョブを作る必要があるため、ジョブ生成をスクリプト化しました。設定項目に必要な値を書き込んでスクリプトを実行すると、指定テーブル用のPythonスクリプトをS3上に配置して、それを実行するGlueジョブを作成し、起動します。これを必要なテーブル数分だけ繰り返します。
実行中にGlueのコンソールで作成されたジョブを選んでRunsタブを開くと、ログなどをリアルタイムに追うことができます。

#!/bin/bash -eu
# S3 に Export されたAuroraのテーブルのParquetファイルをGlueを使用してRDSにロードする

# 設定項目
aws_account_id=$(aws sts get-caller-identity --query Account --output text)
region="ap-northeast-1"

S3_SCRIPT_BASE=s3://aws-glue-assets-"$aws_account_id"-"$region"/scripts/
S3_TEMP_PATH=s3://aws-glue-assets-"$aws_account_id"-"$region"/temporary/
S3_UILOG_PATH=s3://aws-glue-assets-"$aws_account_id"-"$region"/sparkHistoryLogs/
GLUE_ROLE=AWSGlueServiceRole  # Glue用のIAM Role名
GLUE_CONNECTION=VPCNetwork  # Glue上で設定したVPC接続設定名

S3_DATA_PATH=s3://rds-export/export_name  # Aurora Export先S3
S3_DB_NAME=database_name  # エクスポート時のデータベース名

DB_ENDPOINT=database.cluster-xxxx."$region".rds.amazonaws.com
DB_NAME=tmp  # インポート先のデータベース名
DB_USER=write_user
DB_PASSWORD=write_password

TABLE=target_table_name  # インポート対象のテーブル名
###

script_path="$S3_SCRIPT_BASE$DB_NAME-$TABLE.py"

template=$(cat <<EOF
import sys
from awsglue.transforms import *
from awsglue.utils import getResolvedOptions
from pyspark.context import SparkContext
from awsglue.context import GlueContext
from awsglue.job import Job

args = getResolvedOptions(sys.argv, ["JOB_NAME"])
sc = SparkContext()
glueContext = GlueContext(sc)
spark = glueContext.spark_session
job = Job(glueContext)
job.init(args["JOB_NAME"], args)

S3bucket_node1 = glueContext.create_dynamic_frame.from_options(
    format_options={},
    connection_type="s3",
    format="parquet",
    connection_options={
        "paths": [
            "$S3_DATA_PATH/$S3_DB_NAME/$S3_DB_NAME.$TABLE/"
        ],
        "recurse": True,
    },
    transformation_ctx="S3bucket_node1",
)

MySQLtable_node2 = glueContext.write_dynamic_frame.from_options(
    frame=S3bucket_node1,
    connection_type="mysql",
    connection_options={
        "url": "jdbc:mysql://$DB_ENDPOINT:3306/$DB_NAME",
        "dbtable": "$TABLE",
        "user": "$DB_USER",
        "password": "$DB_PASSWORD",
        "bulkSize": 100
    },
    transformation_ctx="MySQLtable_node2",
)

job.commit()
EOF
)

echo "Creating script on S3: $script_path" >&2
aws s3 cp - "$script_path" <<< "$template"

echo "Creating job: $DB_NAME-$TABLE" >&2
aws glue create-job \
    --name "$DB_NAME-$TABLE" \
    --role "$GLUE_ROLE" \
    --command "Name=glueetl,ScriptLocation=$script_path,PythonVersion=3" \
    --glue-version "4.0" \
    --connections Connections="$GLUE_CONNECTION" \
    --default-arguments '{
        "--TempDir": "'"$S3_TEMP_PATH"'",
        "--enable-metrics": "true",
        "--enable-continuous-cloudwatch-log": "true",
        "--enable-continuous-log-filter": "true",
        "--enable-spark-ui": "true",
        "--spark-event-logs-path": "'"$S3_UILOG_PATH"'",
        "--enable-glue-datacatalog": "true"
    }' \
    --number-of-workers 4

echo "Starting job: $DB_NAME-$TABLE" >&2
aws glue start-job-run --job-name "$DB_NAME-$TABLE"

スクリプトなどはAWS Glue StudioのGUIで使用されるものと同一にしています。

--number-of-workers 4 は 4つのワーカーを起動するという意味です。大規模なデータ・Auroraインスタンスを使用する場合はこれを大きくして並列度を上げた方が良いでしょう。

また、MySQLのconnection_optionsの "bulkSize": 100 は、1回のINSERT文で100行分のデータを挿入するという意味で、所要時間に結構効いてきます。ちなみにJDBC接続のオプションのドキュメントは以下にありますが、本記事の執筆時点では英語版含めて bulkSize の大文字小文字が間違っており、そのまま指定しても無効になるという罠があってハマりました。。
AWS Glue での ETL の接続タイプとオプション - AWS Glue
実際、Glue Studio のGUIから生成したスクリプトでは bulkSizeは 1 相当(デフォルト)となっており、これをそのまま使うと上記スクリプトと比べて所要時間が2〜3倍くらいかかってしまいました。いかにbulkSize指定に効果があるかが見て取れますね。

試しにdb.r6g.4xlargeのインスタンスに64GBのデータを復元してみたところ、かかった時間は40分程度*2で、十分実用的かなと感じました。

まとめ

AuroraからS3エクスボートで書き出したParquet形式のデータを、再びAuroraデータベースにAWS Glueを使って書き戻す方法について紹介しました。
BIGINT, TEXT, JSON, BINARY, DATETIME など色々なデータ型で試してみましたが、今のところいずれもきちんと元通りに復元できていることを確認することができました。*3

Parquet形式は高効率でデータのやり取りが可能です。またRDSエクスポート機能も、スナップショットからでも既存クラスターからでも性能に影響を与えずに利用できて使い勝手が良いので、RDSへ再インポートできることでさらに活用の機会を広げられるのではないかと考えています。

*1:「Aurora S3エクスポート 復元」などのキーワードで検索すると無慈悲にも「復元はできない」と書かれた記事がヒットします。

*2:複数のテーブルを順次転送しているので、間に待ち時間も挟まっており、正確な時間ではありませんが目安ということで。

*3:とはいえエンコーディングなど含めあらゆるパターンで確実に復元できる保証はないので、事前に自身のデータで相違が発生しないか確認する必要はあります。