LoginSignup
5
4

More than 1 year has passed since last update.

Java のレガシーなサービスで使ってきた Aurora MySQL v1 を v3 にバージョンアップした話

Last updated at Posted at 2022-10-20

2022 年 2 月に Amazon Aurora MySQL 互換版のバージョン 1(以降「Aurora MySQL v1」と表記)の EoL 発表を受けて、Java のレガシーな Web サービスで使ってきた Aurora MySQL v1 を v3 にバージョンアップした記録です。

後日 Zenn で本にまとめる予定なので、 この記事は要約版となります。

2022/10/28 追記:
Zenn で本にまとめました。

対象サービスの概要

  • Java の 15 年 over モノのレガシーなシステム
    • その上に Web アプリケーションが複数
  • 5 年前 AWS に Lift & Shift したのを機に DB を MySQL 5.5 から Aurora MySQL v1 に移行
  • DB の接続には MySQL Connector/J 5.1 または 8.0(一部)を使用

なぜ Aurora MySQL v2 ではなく v3 に移行したのか?

いくつか理由はありますが、主に以下の 2 点が大きいです。

  • 本家 MySQL 5.7 の EoL が迫る中、一旦 v2 を挟むと最悪の場合 2 年で 2 回バージョンアップが必要になる
    • Web フレームワークやミドルウェアなどの EoL 対応も予想される中それは厳しい
  • 私個人が MySQL 8.0 の薄い本 の製作・頒布を MySQL 8.0.24 の頃まで続けていたのでバージョン間差分の情報をそこそこ把握していた
    • Aurora MySQL v3 のバージョン 3.01.x・3.02.x は MySQL 8.0.23 + α ベースでリリースされたのでほぼカバーできる

バージョンアップの流れ

ざっくり箇条書きするとこんな流れでした。

  • MySQL 5.6 / Aurora MySQL v1 と MySQL 8.0 / Aurora MySQL v3 の差分洗い出し
  • Aurora MySQL のバージョンアップ手順に関する情報収集
  • Zenn に記事を書き連ねていき、本に集約・整理

  • アプリケーションコードの修正点を調査し仮修正
  • 仮修正したアプリケーションの動作確認
    • 小規模なチームで実施
    • 問題が見つかったら追加で修正
  • 修正・確認のポイントをまとめて開発チーム全体に展開
    • 仮修正した部分の確認と、より詳細な動作確認を実施
  • バージョンアップ方法について計画
    • v1 → v3 レプリケーション併用でシステムの停止時間を短縮
    • プレリハーサルを実施して手順を確認
  • リハーサルを実施
  • 性能試験を実施
    • 移行後のインスタンスタイプ(ファミリー・サイズ)調整を計画
  • 修正したアプリケーションを事前デプロイ
  • バージョンアップ実施

困ったこと

出力結果のソート順変化

MySQL 8.0(の途中のマイナーバージョン)でGROUP BY ... ASC/DESCが廃止されましたが、

  • ORDER BYのないGROUP BY
    • 従来バージョンではGROUP BY ... ASC相当
  • ORDER BYの列指定が非ユニーク
    • 従来バージョンも仕様上は「非ユニークな部分の順序は不定」
      • でも実際には一定の法則性があったのでそれに合わせてコードを書いていた

などの出力結果のソート順がことごとく変わり

  • すぐに修正すべきものとそうでないものの振り分け(判断)
  • すぐに修正すべきと判断したものの修正

がかなりのボリュームになり、予定より作業期間が延びてしまいました。

Aurora MySQL v3(MySQL 8.0)での挙動変化

こちら↓の仕様変更が原因で、それまでエラーなく処理されていた SQL 文(日付比較)でエラーが発生しました。

MySQL Connector/J 5.1 → 8.0、あるいは 8.0 のマイナーバージョン間の挙動変化

仮修正の段階で動作確認したときには↓の 1 つ目の項目が見つかっただけでしたが、最終的にはかなり出てきました。

  • NOT NULLかつDEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMPTIMESTAMPDATETIME列に対してnullUPDATEするとエラーになる
    • パラメータグループでデフォルトどおりexplicit_defaults_for_timestamp1(有効)を指定した状態で実行
    • MySQL Connector/J 5.1 の頃はUPDATE時のタイムスタンプで更新
    • MySQL Connector/J 8.0 では「NOT NULL列にnullUPDATEすることはできない」旨のエラーが発生
  • INSERT直後にROW_COUNT()を取得すると常に0が返る
    • MySQL Connector/J 8.0.29 のバグ(?)
      • MySQL Connector/J 8.0.28 にバージョンダウンして回避
  • java.sql.ResultSet.getObject()で一旦取得してからjava.sql.Timestamp型に値を変換するコードがException
    • フォーマット無指定の状態(デフォルト)で渡ってくる日付のフォーマットが変わったため(秒以下が無くなった)
    • java.sql.ResultSet.getTimestamp()で取得するよう変更
  • FLOAT(M,D)DOUBLE(M,D)で定義された列をjava.math.BigDecimal型に取り込むとスケールがずれることがある
    • MySQL Connector/J 5.1 の頃はDで定義された値が自動的にスケール指定されていた
    • MySQL Connector/J 8.0 では明示的にsetScale(D)する必要がある
  • Collation(照合順序)が文字セットと不一致でエラーになることがある
    • MySQL Connector/J 8.0.25 以前は接続パラメータconnectionCollationが無指定の場合、システム変数character_set_serverで指定された文字セットのデフォルト照合順序が採用されていたが、8.0.26 以降で文字セットと照合順序の決定ルールが変わった結果、当該アプリケーションでは文字セットに合わない照合順序が指定されてしまった

これら以外にも、MySQL Connector/J 8.0.20 以降では特に

  • 日付(日時)データの処理方法の変更
    • TIMESTAMP型の 2038 年問題に関連する仕様変更も影響?
  • TLS 1.0/1.1 廃止

などマイナーバージョン間の差異がかなり出ており、迂闊にバージョンを上げられない印象です。

DMS レプリケーション使用不可

この問題に当たってしまいました。

無保証(自己責任)になりますが、やむなく Aurora MySQL v1 → v2 → v3 の binlog(ROW 形式)レプリケーションに切り替えて乗り切りました。

意外と困らなかったこと

調査の中でいくつか見つけていた「問題になりそうな点」のうち、対象サービスではあまり問題にならなかった点もいくつかありました。

大量IN()の失速

MySQL 5.6 → 5.7(Aurora MySQL v1 → v2)のバージョンアップでよく話題になる、IN()で数千・数万の値を列挙するとフルスキャンになる」 問題は生じませんでした。

ただしこれは「v3 にバージョンアップしたから問題にならなかった」のではなく、アプリケーションで処理するIN()の列挙数がそれほど多くなかったのが理由です。

全件COUNT(*)の失速

これはデータの新旧比較用 SQL 文を用意している中で見つけた問題でした。

アプリケーションではテーブル(またはパーティション)全件のCOUNT(*)をしていなかったので、バージョンアップ作業時のデータ新旧比較が若干遅くなる以外の影響はありませんでした。

実行計画の変化(その他)

わずかに生じた程度でしたが、データ量などの状況を見ながらオプティマイザヒントで対応しました。

全体を通しての感想

概ね事前予想どおりではあったものの、やはり大変でした。

まず、収集した情報をまとめて開発チーム全体に展開したまでは良かったのですが(そこまでは順調)、DB の知識が少ないメンバーにもタスクが割り振られた結果、

  • 作業の手が止まる
  • 間違った作業をする(例:一時テーブルと内部一時テーブルを混同して動作確認)

などの無駄が発生しました。

このあたりは、開発チーム全体に展開する前に考慮しておくべきでした。

また、対象システム上にあるアプリケーションは複数に分割されているものの、(歴史的経緯により)「開発するチームの境界とアプリケーションの境界が異なる」(それぞれが複雑に入り組んでいる)状態で、チーム間で作業がバッティングして効率的に進められない問題も発生しました。

その結果、当初は「修正が終わったものから(DB のバージョンアップ前に)順次デプロイ・リリースしていく」予定を立てていたのにそれができず、DB のバージョンアップ直前に全ての修正をまとめてデプロイすることになってしまいました。

そして侮れなかったのが MySQL Connector/J の挙動変化です。

後から考えると、一旦 MySQL Connector/J のバージョンアップ対応だけ先に実施しておいたほうが良かったかもしれません。

今回は Java で MySQL Connector/J 使っていたからこそ当たってしまった問題かもしれず、他言語向けのライブラリでは必ずしも同様の状況になるとは限りませんが、ある意味それが安全にバージョンアップする上で大きなポイントだったと思います。

関連資料(発表スライド)

2022/10/28 追記:

5
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
4