CastingONE Tech Blog Publicationへの投稿 😊 とある事象からMySQL5.7→8に上げた話 CastingONE Tech Blog 2023/05/19 に公開 MySQL MySQL 8 tech はじめまして、CastingONEでバックエンドエンジニアをやっておりますJakeと申します。 最近犬を飼い始めて、原神の時間が無くなりました。ありがとうございます。 CastingONEでは当初からMySQL5.7を使用していたのですが、とある事情からMySQL8にアップデートしたのでそちらについてお話しようと思っております。 なぜMySQL8にアップデートしたのか MySQL5系までのバグのせいで、データの復旧が上手くいかない事象が発生していた MySQL8にアップデートすれば解消される事象 MySQL8の新機能(WITH句、WINDOW関数などなど)が使える 大きく2つの理由があったのですが、今回は1つ目の理由について、何があったか・どう対応したかを中心にお話したいと思います。 MySQL8に関しての具体的な機能などについてはこの記事では割愛させていただきます。 なぜデータの復旧が上手くいかない事象が発生していたのか 当社のサービスには、一部のデータに対してデータを削除する際復旧テーブルに削除したデータを退避させて、顧客の要望があれば一定期間復旧できるような仕組みがあります。 データを削除する際、データのIDをそのまま復旧テーブルのIDにして退避しているのですが、そこで開発環境で削除が失敗する事象が発生しました。 調べてみると、退避テーブルに既に存在するIDでINSERTしようとしていて、失敗しているようでした。 なぜこの問題がおこるのかというと、MySQL5.7まではAUTO_INCREMENTがサーバー再起動でリセットされてしまう仕様が存在していて、最後にデータの削除を行った状態でインスタンスの再起動などが起こると発生していました。 具体例を説明すると、 staffs.id:100を(物理)削除 staffsテーブルのAUTO_INCREMENTが101にカウントアップ recoveryテーブルにstaff_id:100のデータが生成される インスタンスの再起動 staffsテーブルのAUTO_INCREMENTが、テーブル内のidの最大値+1にセットされる 99+1=100で、staffs.AUTO_INCREMENT=100となる staffsテーブルのデータを削除する recoveryテーブルにINSERT時に Duplicate entry ‘100’ for key ‘PRIMARY’ が発生 このような手順を踏むと発生していた事象になります。 MySQL8からは、AUTO_INCREMENTのカウンター値はREDOログに書き込まれるようになったため、再起動してもAUTO_INCREMENTの値が変わることは無くなったので、今回のような事象は起こらなくなり、こちらが今回MySQL8にあげた一番大きなモチベーションになります。 </span><a href="https://dev.mysql.com/doc/refman/8.0/en/mysql-nutshell.html" style="" target="_blank" rel="nofollow noopener noreferrer">https://dev.mysql.com/doc/refman/8.0/en/mysql-nutshell.html</a></p> <a class="header-anchor-link" href="#%E4%BA%8B%E8%B1%A1%E3%81%8C%E5%88%A4%E6%98%8E%E3%81%97%E3%81%A6%E3%81%8B%E3%82%89%E8%A1%8C%E3%81%A3%E3%81%9F%E3%81%93%E3%81%A8" aria-hidden="true"/> 事象が判明してから行ったこと</h1> <p>事象が判明した時点で、次のリリースまで時間があったので、以下の対応をまず行いました</p> <li>既におかしくなっているデータが無いか <li>originalのテーブルと、recoveryのテーブルで同じIDができていないか確認</li> <li>リカバリにIDが重複している場合は削除をできないようにhotfixで修正 <li>テーブルにINSERTしているところで、リカバリに同じIDが存在した場合は削除失敗させる</li> <li>削除失敗を検知したらAUTO_INCREMENTを更新</li> <p>3に関しては幸い本番環境では発生しなかったので良かったです。</p> <p>一旦傷口を広げない対応を行った後、MySQL8にアップデートする検証を始めました</p> <a class="header-anchor-link" href="#mysql8%E3%81%B8%E3%81%AE%E3%82%A2%E3%83%83%E3%83%97%E3%83%87%E3%83%BC%E3%83%88%E5%89%8D%E3%81%AB%E8%A1%8C%E3%81%A3%E3%81%9F%E3%81%93%E3%81%A8" aria-hidden="true"/> MySQL8へのアップデート前に行ったこと</h1> <li>公式ドキュメントを読む <li>破壊的な変更などないか一通り確認する</li> <li>アップデートの手順をまとめる</li> <li>blogなど他の方の知見を参考にする</li> <li>開発環境にMySQL8のインスタンスを追加して検証 <li>インスタンスを追加したのち、開発環境のデータをコピー</li> <li>全機能を探索的に触る</li> <li>qaテスト(システムを全体的に触るテスト)に混ぜてテスト</li> <a class="header-anchor-link" href="#%E3%82%A2%E3%83%83%E3%83%97%E3%83%87%E3%83%BC%E3%83%88%E6%A4%9C%E8%A8%BC%E4%B8%AD%E3%81%AB%E5%88%A4%E6%98%8E%E3%81%97%E3%81%9F%E3%81%93%E3%81%A8%E3%81%A8%E5%AF%BE%E5%BF%9C" aria-hidden="true"/> アップデート検証中に判明したことと対応</h1> <p>事前にドキュメントなど読んだ感じだと問題なさそうだと思っていたのですが、いざ検証を始めると以下2点の問題点が出てきました。</p> <a class="header-anchor-link" href="#local%E3%81%AEmysql%E3%81%AE%E3%83%86%E3%82%B9%E3%83%88%E3%81%8C%E7%95%B0%E6%A7%98%E3%81%AB%E9%81%85%E3%81%8F%E3%81%AA%E3%81%A3%E3%81%9F" aria-hidden="true"/> localのmysqlのテストが異様に遅くなった</h3> <p>M1Macの環境で、当初5分くらいで終わっていたのが、10分以上かかるようになってしまった。</p> <p>mysqlを自前でビルドでしていたのが原因で時間がかかっていたのですが、公式イメージ(mysql:latest)をそのまま使用するようにし、事前にschemaを作成してそれをコンテナのエントリポイントにコピーして起動するようにして解消しました。<br/> 結果3分くらいでテストが終わるようになったので良かったです。</p> <a class="header-anchor-link" href="#collation%E3%81%AE%E8%A9%B1" aria-hidden="true"/> collationの話</h3> <p>MySQL5.7以前のutf8mb4のデフォルトのcollationは<code>utf8mb4_0900_general_ci</code>ですが、MySQL8になるとデフォルトが<code>utf8mb4_0900_ai_ci</code>になり、それが原因で一部タイムアウトが発生するAPIが出てきてしまったのですが、 以下の理由から、CREATE TABLE時に<code>COLLATE=utf8mb4_general_ci</code>を指定して回避するようにしました</p> <li>utf8mb4_0900_ai_ciだと、カタカナや小文字などが同一と判定される</li> <li>テーブル間でcollationが異なる場合、JOINのときに結合キーでインデックスが効かないためクエリが遅くなる</li> <a class="header-anchor-link" href="#%E6%9C%AC%E7%95%AA%E3%82%A2%E3%83%83%E3%83%97%E3%83%87%E3%83%BC%E3%83%88%E6%99%82%E3%81%AB%E8%A1%8C%E3%81%A3%E3%81%9F%E3%81%93%E3%81%A8" aria-hidden="true"/> 本番アップデート時に行ったこと</h1> <li>改めて事前にAUTO_INCREMENTがおかしくなっていないかを確認 <li>originalとrecoveryテーブルのAUTO_INCREMENTの値を比較し、recoveryのほうが大きくなっていたら修正する</li> <li>AUTO_INCREMENTの対応が終わったら、MySQLを8に上げる</li> <li>本番環境で一通り触ってみて問題ないか確認</li>