Rails: PostgreSQLのマイグレーション速度を改善する(翻訳)

概要

原著者の許諾を得て翻訳・公開いたします。

Rails: PostgreSQLのマイグレーション速度を改善する(翻訳)

データベースが成長してスケールするに連れ、気を遣わなければならない点が当初とは変わってきます。アプリをdev環境で動かしているうちは気にもかけていなかった操作コストを、production環境でがっつりと支払うはめになったりします。私たちの多くがやらかしてしまうのが、たとえばマイグレーションです。production環境でマイグレーションの起動に5分、それから15分間動き続けたまではよかったのですが、突然トラフィックに問題が生じたりします。

こういう事態を引き起こしがちな操作は2つありますが、どちらについてもダウンタイムを許容範囲内に収めるまっとうなアプローチが存在します。それぞれの操作とその仕組、安全な操作方法について見ていくことにしましょう。

1. 新しいカラムの追加

PostgreSQLのカラム追加は、実際にはきわめてコストの低い操作です。カラムを追加するには、存在するカラムのトラッキングをバックグラウンドで更新しますが、この操作はほぼ一瞬で終わります。しかし、カラムに何らかの制約を追加すると、途端にカラム追加のコストが上昇します。制約としては、主キーや外部キー制約、または何らかの一意性制約があります。PostgreSQLはテーブル内の全レコードをスキャンして、制約違反がないかどうかをチェックします。not nullなどの制約ではある程度スキャンが発生しますが、この制約は最大の原因ではありません。

カラム追加の速度が低下する最大の原因は、多くのフレームワークで新しいカラムのデフォルト値が非常にシンプルな方法で設定されていることです。デフォルト値設定は新しいレコードすべてについて行うものですが、既存のテーブルにデフォルト値を設定するとデータベースが全レコードを読み取ってから書き換えなければならなくなります。レコードが数100件程度であれば大したことはありませんが、数百万件のレコードともなるとランチとおやつとフルコースのディナーを余裕で平らげられるくらいの間待たされるでしょう。

要するに、新しいカラムにnot null制約や(作成時の)デフォルト値を設定すると痛い目にあうということです。解決方法としては、とにかくこうした操作を行わないことです。しかしデフォルト値とnot null制約をどうしても追加したい場合はどうすればよいでしょうか。本質的には、1つのマイグレーションを4つのマイグレーションに分割することでできるようになります。

  1. nullを許容する新しいカラムを追加する
  2. 新しいレコードすべてにデフォルト値を書き込んで更新する
  3. 書き込んだデフォルト値を段階的に元に戻す
  4. 制約を適用する

少々手間はかかりますが、この方法ならproduction環境にほぼインパクトを与えずに済みます。

2. インデックス

多くのDDL(Data Definition Language: データ定義言語)操作と同様、インデックスの(新規)作成中にはロックが発生します。つまり新しいデータに対するインデックス作成待ちが生じ、新しい書き込みフロー全体でも同様に待ちが発生します。繰り返しになりますが、小さなテーブルであればまったく大したことはありません。しかし大規模なデータベースでは数分から数時間の待ちが生じる可能性があります。スピードアップのためのインデックス追加がこのような速度低下をもたらすのは少々皮肉ではあります。

もちろん、PostgreSQLにはCONCURRENTインデックス作成というソリューションがあります。これはインデックスを段階的にバックグラウンドで作成するためのものであり、CREATE INDEX CONCURRENTLYを指定することでインデックス作成を並列実行できます。インデックス作成が完了して利用可能になった瞬間から、期待どおりPostgreSQLのインデックスが入れ替えられてクエリで利用できるようになります。

便利なツール: strong_migration

マイグレーション実行中に何が起きているか、それがパフォーマンスにどんなインパクトを与えるかを理解するのはよいことです。こうした要素すべてを自分で制御しなければならないわけではありません。少なくともRailsには、こうした操作などをdev環境で察知して開発者に修正を促してくれるツールがあります。Strong Migration gemは、こうしたコスト高な操作の多くを背後で検出するためのツールなので、Rails開発者ならぜひ一度チェックしてみてください。

PostgreSQLでのデータベースマイグレーションに役立つツールやテクニックを他にもご存知でしたら、ぜひ@craigkerstiensまでお知らせください。私の方でリストに追加いたします。

関連記事(PostgreSQL)

Rails開発者のためのPostgreSQLの便利技(翻訳)

PostgreSQLの機能と便利技トップ10(2016年版)(翻訳)

Rein: RailsのActiveRecordでDB制約やデータベースビューを使えるgem(README翻訳)

ハンズオン: PostgreSQLシャーディング(翻訳)

PostgreSQL拡張: 外部データラッパー(FDW)の概要(翻訳)

pgloader 3.4.1でMySQLからPostgreSQLへスマートに移行しよう(翻訳)

PostgreSQLを使う理由(更新5年目)(翻訳)

Ruby on RailsによるWEBシステム開発、Android/iPhoneアプリ開発、電子書籍配信のことならお任せください この記事を書いた人と働こう! Ruby on Rails の開発なら実績豊富なBPS

この記事の著者

hachi8833

Twitter: @hachi8833、GitHub: @hachi8833

コボラー、ITコンサル、ローカライズ業界、Rails開発を経てTechRachoの編集・記事作成を担当。
これまでにRuby on Rails チュートリアル第2版の半分ほど、Railsガイドの初期翻訳ではほぼすべてを翻訳。その後も折に触れてそれぞれ一部を翻訳。
かと思うと、正規表現の粋を尽くした日本語エラーチェックサービス enno.jpを運営。
実は最近Go言語が好き。
仕事に関係ないすっとこブログ「あけてくれ」は2000年頃から多少の中断をはさんで継続、現在はnote.muに移転。

hachi8833の書いた記事

BPSアドベントカレンダー

週刊Railsウォッチ

インフラ

BigBinary記事より

ActiveSupport探訪シリーズ