MySQLのutf8mb4と戦った話

皆様こんにちは、NewsPicksエンジニアの米澤です。

先日 2023/03/30は、こちらでアナウンスしていた通り、サービスの停止を伴うシステムメンテナンスを実施させて頂きました。

NewsPicksをご利用頂いている皆様には、ご迷惑おかけいたしました。

今回はこのメンテナンスの中で行われたDBテーブルのmigrationについてお話ししたいと思います。

ことの始まり

NewsPicksではバグの検知にBugSnagを利用しています。

ある時、BugSnagにこんなエラーが通知されてきました。

org.springframework.orm.hibernate4.HibernateJdbcException: JDBC exception on Hibernate data access: SQLException for SQL [n/a]; SQL state [HY000]; error code [1366]; could not execute statement; nested exception is org.hibernate.exception.GenericJDBCException: could not execute statement

詳しくログを追ってみると、「𩸽」や「𠮷」といった 4バイト の文字をユーザ管理テーブルに保存しようとした時にエラーとなっていることが原因でした。

私はこの時初めて知ったのですが、昔のMySQLのテーブルって 4バイト の文字列に対応してなかったんですね...

参考: MySQL公式によるutf8mb4の解説

このエラーに遭遇されてしまったユーザの皆様、大変申し訳ありませんでした。

やったこと

異常系でないにも関わらず特定の文字列を含む場合にユーザの情報が正しく登録できないのはシステムとして不健全でしたので、解決に乗り出しました。

方針決め

この時点で対応が必要だと分かっていたのは

  • 実際にユーザがエラーを体験してしまいうる users テーブル
  • 削除済みユーザの履歴情報を管理する backup_users テーブル

の2つです。

しかし、users テーブルも backup_users もレコード数・更新頻度が多く影響範囲も広いため、システム稼働中にmigrationを当てることは断念し、システムメンテナンス中にmigrationを実行することにしました。

utf8mb4に対応していないテーブルを調べる

NewsPicksはそこそこ歴史のあるプロダクトなので、最近追加されたテーブルは utf8mb4 に対応していたのですが、昔からあるテーブルは utf8mb4 に対応していませんでした。

元々 2023/03/30にメンテナンスすることは決まっていたので、これを機に utf8mb4 対応できるテーブルはmigrationを当ててしまおうと思っていました。

そこで utf8mb4 に対応していないテーブル一覧を調査しました。

MySQLのコマンドで以下を実行すれば、utf8mb4 に対応していないテーブル一覧を出してくれます。

show table status 
from `database` 
where COLLATION <> 'utf8mb4_general_ci';

このコマンドの結果、該当テーブルの数が 270程度 あることが分かりました。

3/30 までに全てのテーブルを utf8mb4 化対応するのは現実的ではないと考え、 users テーブルと backup_users テーブルに絞って対応することに決めました。

migrationを作成する

ALTER TABLE `backup_users`
CONVERT TO CHARACTER SET utf8mb4
COLLATE utf8mb4_general_ci;

ALTER TABLE `users`
CONVERT TO CHARACTER SET utf8mb4
COLLATE utf8mb4_general_ci;

修正自体はこれだけでした。この alter tableutf8mb4 に対応させることができます。

影響範囲を調べる

users テーブル及び backup_users テーブルが、システムのどこから参照されているかを確認したところ、ユーザのプロフィール編集画面以外にも以下の2つで連携されていることが分かりました。

  • Redshift (アクセスログ等解析用)
  • Braze

→ Braze公式に問い合わせたり、実際にRedshiftに utf8mb4 のレコードを作って問題ないか確認しました。

開発環境でリハーサルを行う

幸いなことに、開発環境で検証しやすくするためのツールがSREチームから提供されていました。

これは本番DBにあるデータを(個人情報などはマスクした上で)開発環境のDBにコピーしてくれるもので、本番に限りなく近いデータ量でリハーサルをすることができます。

→ まずはこのツールで開発環境のDBの各種テーブルを本番相当に更新しました。

次に開発環境で利用しているRDSを 一時的に本番相当のスペックにグレードアップ し、どの程度でmigrationが終わるのかを検証しました。

→幸い数分でmigrationが終わってくれました。

そして、万一に備えて users テーブルと backup_users テーブルのダンプをとり、どの程度時間がかかるか計測

→こちらも数分で終わってくれました。

※注意

一度間違って低スペックのままmigrationを実行して1時間程度かかってしまったので、リハーサルをする際は本番相当の環境で行うことが重要です。

今度は開発環境の画面で、名前などをわざと utf8mb4 の文字にしてユーザ登録をしてみます。

utf8mb4の文字を含むユーザ情報

うまく行きましたね😀

メンテナンスの日

メンテナンスの日を迎えました。

◾️1. 早めに仕事を一時中断し、寝る

作業開始は深夜に行うので、それに備えて 寝ました 🛌

◾️2. Gatherに集まる

NewsPicksのエンジニアチームでは Gatherを利用しているので、メンテ開始直前に対応メンバーで集合。

やや不謹慎ですが、ちょっとしたパーティ感覚です 🪅

◾️3. ダンプの出力

ここから対応開始。

万一の時に備え、users テーブルと bakcup_users のダンプをとっておきます。

→ リハーサル通り、数分で終了。

◾️4. migrationの実行

先にも書きましたが、以下のmigrationを実行しました。

ALTER TABLE `backup_users`
CONVERT TO CHARACTER SET utf8mb4
COLLATE utf8mb4_general_ci;

ALTER TABLE `users`
CONVERT TO CHARACTER SET utf8mb4
COLLATE utf8mb4_general_ci;

→ リハーサル通り、無事数分で終了。

◾️5. preview環境で動作確認

NewsPicskでは「DBは本番を参照しているが、外部からはアクセスできない」というpreview環境が用意されています。

そのためmigration実行後、実際に utf8mb4 の文字列を含むユーザでユーザ登録&退会して動作確認

→ ちゃんとDBに保存できていたのでOK。

◾️6. 就寝

おやすみなさい😴

◾️7. サービスイン後

本番環境で utf8mb4 の文字列を含む名前のユーザで登録し、退会

→ こちらもちゃんとDBに保存できていたのでOK。

最後に

たかが2テーブルのCHARACTER SETを修正するだけですが、影響範囲が広いだけあって事前準備に手間取りました。

今回特に以下の3点が良かったと感じています。

  • 一度に全てのテーブルを utf8mb4 対応しようとしないこと
  • 開発環境に本番とほぼ同じデータを用意したこと
  • 開発環境で利用しているDBのスペックを一時的に本番相当のものにグレードアップして検証した結果、そこまで時間がかからないことが事前に分かっていたこと

やはりAWSのRDSのようなスペックを簡単に変えられるシステムを使っていると、事前検証が格段に楽ですね。

NewsPicksにジョインしてから初めてシステムメンテナンスに関わったのですが、とても良い経験になりました。

私がこのメンテナンスで何か新しい機能をリリースした訳ではありませんが、こうした地道な改善作業がサービスの品質向上につながると信じ、今後も継続していきたいと思います。

ありがとうございました。

Page top