Aurora v3 移行と Locust を活用したクエリテスト

## はじめに

こんにちは、Platformチームの大野です。本記事では、私が所属するPlatformチームが実施したRDS Aurora MySQL v3（MySQL 8.0互換）への移行に伴うクエリテストの概要についてご紹介します。当社のPlatformチームについては、昨日の栗山さんの記事「[マルチプロダクトの成長を支えるPlatformチームの紹介](https://zenn.dev/akira_kuriyama/articles/f2e39f71102d35)」で触れられていますので、そちらもぜひご参照ください。

## 話の背景

我々Platformチームは、各プロダクトが利用するAWS・Kubernetesインフラに加え、各プロダクトから共通して利用される機能（認証、通知、共通マスタ）をマイクロサービスとして提供しています。

![TechBlog_全体概要.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3952720/75f7613d-4026-ec0a-4f2a-242bdaa382d4.png)

これらのマイクロサービスはデータストアとしてRDS Aurora MySQLを使用しており、長らくAurora v2で稼働していましたが、AWSの標準サポート終了を機に、重い腰を上げてAurora v3への移行を行うことにしました。MySQL 8系へのアップデートに際しては、先行している他社の事例を参考にしつつ、リリースノートを詳しく読み込みました。その上で、実際にワークロードを流して、クエリの劣化などが発生していないかを確認することにしました。

## どのようにワークロードを流すか

実際のワークロードでテストする方法はいくつもあると思います。例えば、「①負荷テストの要領でHTTP通信を用いてシナリオを流す方法」や「②クエリログを取得し、DBに対してクエリをリプレイする方法」などです。Platformチームが提供する共通機能のマイクロサービスは、他のプロダクトからgRPC経由で内部的に呼び出されるため、外部向けのAPI層が存在しません。そのため、①の方法は採用できず、②の方法についても、後述する最終的に採用した方法と比較して実装に時間がかかると判断し、見送ることにしました。

## 採用した構成

最終的には以下の構成と方針でテストを行うこととしました。

![TechBlog_テスト環境.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3952720/ea6e4840-fe1b-5d08-52a8-b4a555d5106d.png)

### 本番DBをクローンして専用テスト環境用のDBを作成

本番相当のデータ量でテストを行うために、テスト環境のDBは本番DBをクローンして作成しました。本番環境とテスト環境ではAWSアカウントが分かれているのですが、AWS RAM（Resource Access Manager）を使用してテスト環境のサブネットを本番AWSアカウントに共有することで、共有されたサブネットに本番DBをクローンしています。スナップショットを別アカウントに共有する方法もありますが、AWSアカウントのデフォルトKMSキーで暗号化されていると共有できない制約があります。この方法であれば、その制約も問題ありません。

### Locustマスター・ワーカーのPodからgRPCアクセス

Platformチームが提供するマイクロサービスは外部向けのAPI層を持たないため、EKSクラスター内にLocustのマスター・ワーカーを立てて、直接gRPCでアクセスすることにしました。当社の環境では、ほぼ全てのマイクロサービスにDatadog APMが導入されており、発行されたgRPCメソッドや、そのgRPCメソッドで発行されたクエリおよびレイテンシーをDatadog上で確認できます。本番環境で発行数の多いgRPCメソッドを抽出し、それらを実行するシナリオを流すことで、実際にORMが発行するクエリに劣化などがないか確認することにしました。

## 環境の準備と実行

### シナリオの準備

[Locust](https://locust.io/) は通常Pythonでシナリオを記述しますが、今回はGoでシナリオを記述できるlaunceというライブラリを見つけたので、これを利用しました。

https://zenn.dev/qitoi/articles/locust-worker-library-launce

当社ではバックエンドの言語がGoで統一されているため、このライブラリを使うことでプロダクション環境で使用しているgRPCクライアントをそのまま利用してシナリオを記述することができました。

### Kubernetesマニフェスト

Kubernetesマニフェストは以下のようになります（一部の記載は省略）。マスター用のイメージは、Locustの公式イメージにマスターとして起動する locustfile.py をコピーしただけのものです。ワーカー用のイメージは、引数にシナリオ名を取るGoのシングルバイナリです。なお、Locustの管理UIには、シナリオ実行時に `kubectl port-forward` を使用して接続するようにしています。

```yaml
---
apiVersion: v1
kind: Service
metadata:
  name: locust-master
spec:
  type: ClusterIP
  ports:
    - name: webui
      port: 8089
      protocol: TCP
      targetPort: 8089
    - name: worker
      port: 5557
      protocol: TCP
      targetPort: 5557
  selector:
    app: locust-master
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: locust-master
spec:
  selector:
    matchLabels:
      app: locust-master
  replicas: 1
  strategy:
    type: Recreate
  template:
    metadata:
      labels:
        app: locust-master
    spec:
      serviceAccountName: locust-master
      nodeSelector:
        nodegroup: platform
      containers:
        - name: locust-master
          image: locustmaster:v0.0.1
          imagePullPolicy: IfNotPresent
          command: ["locust", "-f", "/locustfile/locustfile.py", "-H", "http://localhost/", "--master"]
          ports:
            - containerPort: 8089
            - containerPort: 5557
          env:
            - name: ENV
              value: "test"
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: locust-worker
spec:
  selector:
    matchLabels:
      app: locust-worker
  replicas: 2
  strategy:
    type: Recreate
  template:
    metadata:
      labels:
        app: locust-worker
    spec:
      serviceAccountName: locust-worker
      nodeSelector:
        nodegroup: platform
      containers:
        - name: locust-worker
          image: locustworker:v0.0.1
          imagePullPolicy: IfNotPresent
          command: ["/app/worker", "scenario1"]
          env:
            - name: ENV
              value: "test"
            - name: MASTER_HOST
              value: "locust-master"
            - name: MASTER_PORT
              value: "5557"
          envFrom:
            - secretRef:
                name: locust-worker
```

以上で負荷をかける準備が整いました。Locustマスターに接続して負荷をかけると、期待通りにDatadog APMにリソースごとの詳細が記録されています。あとは各リソースをドリルダウンし、実際にORMから発行された各クエリの詳細を確認することで、一通りの確認ができます。

![SCR-20241209-ufqb-2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3952720/92b9ad4f-610e-d1eb-3149-e00ce50ae4bb.png)

## おわりに

Aurora v3への移行に際し、Platformチームで実施したクエリテストの概要をご紹介しました。今回の方法により、実際に発行されるクエリをざっと調査できたため、事前の検証が少し楽になったと感じています。今後も、ツールを上手く組み合わせて、バージョンアップの検証など、プラットフォームの維持・改善を継続していきたいと思います。