DjangoのマイグレーションとMySQL 5.6~でのオンラインDDLの副作用について

まえがき

こんにちは。バックエンドエンジニアの@mobojisan と申します。

この時期は本当にカンファレンスが多いですね!

個人用GitHub上にSSGを利用したブログ基盤があるのですが、それが使いにくかったので、今流行りのsupabaseでブログ基盤をおっ立て、カンファレンスに参加した時の感想記事でも載っけようかと画策していました。

しかし、作業がカンファレンスの開催スピードにとても追いつかず、カンファレンスのメモが無情にもnotion上に溜まっていきます。

もはやメモをブログとして世に出す頃には来年のカンファレンスが開催されているんじゃないか…?と思いながら、また先週もカンファレンスに参加してメモを積み上げています。

とはいえ、直接の原因は池袋ハロウィンへ参加するにあたって、準備に時間を持って行かれたからなのですが。

閑話休題

今回はMySQLを使用している際に、オンラインDDLを用いたマイグレーションDjangoマイグレーションファイルで管理している際に起こりうる問題・その対策について書きます。

ちょっと待った。オンラインDDLって何よ?

安心してください。僕もわかりませんでしたよ。(過去形)

オンラインDDLと言われてピンとくる方は、次の章まで読み飛ばしていただけますと。

DDLについて

DDLは、Data Definition Languageの略で、データ定義言語とも略されます。

基本的には、データベース新規作成にかかわる命令がDDLにあたり、具体的にはテーブル新規作成・削除で使うCREATE・DROPや、インデックスの作成、そしてカラム名の変更のALTERなどが含まれます。

ちなみにCRUDのC以外に関わるSELECTやUPDATEなど、データベースの操作を行う命令はDMLData Manipulation Languageの略でデータ操作言語と呼ばれます。

この伏線はすぐ後で回収するので覚えておいてね。

オンラインDDL

MySQL 5.6から新たに入った機能の一つです。

Alter Table実行中にDMLの操作ができるということを意味します。

オンラインDDLがないMySQL 5.5以前では、

Alter Table操作中にはそのテーブルへの更新がブロックされ、多くのセッションが待機してしまうため

本番稼働中のMySQLに対しての実行は非常に悩ましいものでした。

といったことが実務上の課題として挙げられていたようです。

詳しくは、引用元である以下をご覧ください。

第30回 InnoDBオンラインDDLについて | gihyo.jp

具体的には、以下のような組み合わせでYesであればオンラインDDLに対応しています。

基本的な操作はだいたいカバーされていそうですね。

操作 in-placeで テーブルの再構築 並行DMLを許可 メタデータのみ変更
二次インデックスの作成または追加 Yes No Yes No
インデックスの削除 Yes No Yes Yes
インデックスの名前変更 Yes No Yes Yes
FULLTEXT インデックスの追加 Yes* No* No No
SPATIAL インデックスの追加 Yes No No No
インデックスの種類変更 Yes No Yes Yes

MySQL :: MySQL 5.7 Reference Manual :: 14.13.1 Online DDL Operations

オンラインDDL実行で起こりかけた問題

ロックを取得する

オンラインDDLによって、DMLDDLを同時に実行させることが可能になるのですが、開始時・終了時にわずかな時間ロックを必要とします。

端的に言えば、

  1. DDLを実行しようとする際に、すでに

  2. ほかのトランザクションなどが走っていると、

  3. 〜テーブルへのほかのすべてのDMLがwaitし、

つまってしまうのです。

これが頻繁にアクセスされるテーブルに発生した場合、サービスが実質的に停止することになります。

SaaSのような多数のユーザーが同時に操作する環境では、つぎつぎと後続のDMLが流れ込んでくることが容易に想像できます。

そうでなくともビジネスで使われるようなソフトウェアにおいては致命的ですね。

じゃあどうすればいいのさ

DDLを流す時にメンテを入れる

安全かつ確実なのは、テーブル、とくに頻繁にアクセスされるテーブルについてDDLを流すことがわかっている場合、メンテを入れることです。

メンテを入れられるのであれば、DMLが走る心配がないのでオンラインDDLに付随する副作用である、後続DMLのブロックを気にする必要がありません。

とはいえ、ビジネスユースのアプリケーション、まして外食産業のインフラを目指す弊社サービス、できる限りメンテは減らしたいものです。

そのため、このケースに関しては別の方法を取りました。

TO値を設定する

MySQLにはlock_wait_timeoutという設定値があり、これによりメタデータロックを行うDDLの実行のタイムアウト値を設定することができます。

デフォルトの値は31536000、なんと1年になっています。

デフォルトでは1年間テーブルを占有されるのです。そんなにいる?

これは単位がs(秒)のため、標準的なテーブルの場合、ある程度でタイムアウト(TO)によって止めてしまうことで、オンラインDDL中に後続のDMLが詰まるのを防ぐことができます。

それがlock_wait_timeoutにあたるわけです。

そのため、長すぎるDDLがほかのDMLをブロックし、サービスを止めてしまう前に、TOによってテーブルのロックを強制的に解除するような設定を入れることにしました。

この設定により、このケースに関してメンテなく進めることができました。

あとがき

いかがでしたでしょうか?

話は変わりますが、弊社クロスマートでは新卒中途問わず、ポジションはさまざまにエンジニアを募集しております。

少しでもご興味持っていただけましたら、カジュアルに記事にスターを入れ、 そしてカジュアルに面談を申し込んでいただけますと、書いているこちらにもとても励みになります。

対戦よろしくお願いします。

xorder.notion.site