PostgreSQL環境における、DB定義変更を伴う無停止リリース「概要と計画」編 (original) (raw)

こんにちは。
株式会社ラクスで先行技術検証をしたり、ビジネス部門向けに技術情報を提供する取り組みを行っている「技術推進課」という部署に所属している鈴木(@moomooya)です。

ラクスの開発部ではこれまで社内で利用していなかった技術要素を自社の開発に適合するか検証し、ビジネス要求に対して迅速に応えられるようにそなえる 「技術推進プロジェクト」というプロジェクトがあります。

このプロジェクトで「PostgreSQL環境における、DB定義変更を伴う無停止リリース」にまつわる検証を進めているので、その中間報告を共有しようかと思います。

※本記事はタイトルに「概要と計画」編とあるように、通年で行う調査の前半時点の中間報告となります。
実際の検証結果については3月末に予定している後編をお待ち下さい。

課題の経緯、前提条件

課題の経緯

ラクスではこれまでも特定条件下のリリースにおいては無停止でのリリースを実現していましたが、一部のパターン、特にDB定義に変更が入る場合に無停止でのリリースを実現できずにいました。

これに対して、2020年に新規サービスを想定して(≒ミドルウェアもゼロベースで選定して)の無停止について検証を行いました(参考記事1, 参考記事2, 参考記事3)が、それほど新規サービスが多くなかったことと、既存サービスへの転用コストが高すぎるためにノウハウを流用できないことから、さらなる検証を必要としていました。

今回の検証では既存サービスで主に採用されているミドルウェアやシステム構成をベースとして、無停止リリースを実現するために検証を進めています。

無停止リリース実現のモチベーション

そもそもなぜ無停止リリースを行いたいかというと、主としてはリリースの自由度を高めたい、逆に言えばリリース時の制約を減らしたいというのが最大のモチベーションです。

近年注目されるDORA, Four Keysといった開発生産性の観点でもリリース単位を小さくすることを良しとされていますが、1日に何度もリリースすることが必ずしも正しいとは言えない1ものの、無停止リリースが出来ないがためにリリース規模を大きくしなければならないという状態は良い状態ではありません。

無停止リリースを実現することで、意図に反するリリース規模肥大が抑制できることが期待する状態です。

前提条件

前述の通り、今回の検証では既存サービスで主に利用しているPostgreSQLをベースとした無停止リリースの実現となります。

ここでいうリリースには、テーブル定義の変更を伴うものも含みます。PostgreSQLにはオンラインDDLが用意されていませんが、サービス全体としてオンラインでのDDL適用を実現することが目標となります。

なお、「サービス全体で」としているように、無停止の定義も「リリース中のエンドユーザーの操作を欠落、およびエラーにしないこと」として取り組みます。

実現手法

本記事はタイトルにもあるように通年で行う調査の前半中間報告記事となります。
課題を解決するための候補となった手法と、それらを用いてどういった検証を行う予定なのかを記述したいと思います。

候補に上がった手法

調査の結果、候補に上がった手法は以下の6つです。それぞれに対して概要をまとめます。

DDL最適化によるロック時間短縮

PostgreSQLDDL実行において、例えばカラムの追加と制約の付与を1クエリで実行すると、すべての処理が終わるまで高いロックレベルでロックされるという現象があります。
これを「カラムの追加」と「カラムへの制約付与」の2クエリにすることで、高いロックレベルと低いロックレベルの2段階になり、ロックされる操作が緩和される状態を狙います。
ただPostgreSQLもバージョンアップに伴い、上記のようなクエリを内部で最適化して実行してくれるケースも増えているため、改めて効果のあるもの無いものを整理して、必要なものだけにDDLの最適化を適用するようなガイドラインを導ければと考えています。

リトライ機構によるロック時間回避

DDLによるロックは一時的なものなのでアプリケーション側にリトライ機構を用意することで、DDLによるロックをアプリケーション側からみて「なかったこと」に出来ないかという試みです。
今回の検証ではJava(かつSpringを利用したアプリケーション)に限定されてしまいますが、Spring Retryを用いたリトライ機構を組み込むことで、DDLを実行してもアプリケーション側からは意識することなく処理を継続できることを期待します。

github.com

pg-oscを利用してのロック時間短縮

DDL適用によるロック時間の短縮テクニックとして、テーブルのコピー(これをシャドウテーブルと呼ぶ)を作成し、シャドウテーブルに対して定義変更やデータマイグレーションを行ってから、元のテーブルと差し替えることでロック時間を短くするというテクニックがあります。
作業中はトリガーを利用してデータの同期も行います。

ただしこれは、手作業で行うには手順が煩雑でミスのもとになりそうな危険性を感じます。
これを補助してくれるツールがpg-oscとなります。

github.com

pglogicalを利用してのロック時間短縮

PostgreSQLにはスキーマやテーブルの単位で複製を行う「論理レプリケーション」という機能があります。
論理レプリケーションではWALではなく、WALをデコードした操作内容で同期を取るため、大雑把にいえば同じSQLを適用できるのであれば余計なカラムが増えていたりテーブル定義が厳密に同一ではなくても同期を取ることができるようです。

pglogicalは論理レプリケーションを補助するツールで、論理レプリケーションを上述のシャドウテーブルのように使ってロック時間を短縮することができるのではないかと考えました。

github.com

Patroni + etcd

PatroniはPostgreSQLノード内に設置し、外部のetcdなどと連携してPostgreSQLノードの状態管理を行ってくれるツールです。
これによってPostgreSQLがロックしている間の状態管理と振り分けを行えないかと考えましたが……あくまでPatroniは高可用性ソリューションでPostgreSQLの死活監視を行ってくれるもので、テーブル単位のロックを監視するものではなさそうでした。

github.com

pgpool-II

こちらもpgpool-IIのコネクションプーリング機能で対応できないかと考えましたが、Patroni同様死活監視を行うものでロックによるフェイルオーバーを行うものではなさそうです。

www.pgpool.net

検証対象とならなかった手法

本検証はメインの開発業務の傍らで特別チームを組んで行っているため、すべてを検証することは出来ません。
そのためこの時点で優先度が低いと考えた以下の手法については検証対象外としました。

pglogicalを利用してのロック時間短縮 を除外した理由

発想としてはpg-oscと同様だが、pg-oscよりもより低レイヤを扱うツールであるため、pg-oscで必要な操作が賄えるのであればより人手を介さない手法のほうが今後の運用トラブルを避けることができると考え、pg-oscを優先しました。

Patroni + etcd / pgpool-II を除外した理由

これらは初期調査時は使えるかと思っていましたが、上述の通りそもそも用途が違っていた(ロックの監視ではなく、死活監視)ので対象外にしました。
また、Patroniの場合は新たにetcdクラスタを構築しなければならず増加する運用コストもそれなりに高額になることも見込まれたため対象外になりました。

検証の観点

今後の予定として、10月以降の後半では、以下の3つの手法について検証を進めていく予定です。

検証内容としては、アプリケーションからのSQLクエリ(SELECTによる参照や、UPDATEによる更新など一通りのパターン)が連続してリクエストされている状況をテスト環境に構築し、それぞれの手法を用いた状況でDDLクエリ(ロックレベル別に主要なDDLクエリを試します)を実行し、アプリケーション側からのSQLクエリに欠損や、エラーが出ないかを観測します。

実運用時よりも高密度な連続リクエスト環境を用意して、アプリケーションでのエラーが発生しないことを目指します。
また手法についても結果と必要によっては組み合わせた環境での検証も行い、理想的な実現方法を見つけたいと思います。

最後に

ラクスの技術推進プロジェクト(本取り組み)では、世間的に「切り替えは一瞬で完了する」「ドキュメント上は〇〇パラメータによって実現可能」と謳われている技術要素に関しても、自社の基準と照らし合わせて要求を満たしているかどうかを検証しています2

これらは一見無駄にも見える検証ではありますが、ユーザーに満足してもらえるシステムを提供するエンジニアの責任として地道に取り組んでいきたいと考えています。
検証結果は3月末(もしかしたら4月になるかも)にまた記事にして公開したいと思いますので、しばしお待ちいただければと思います。