freeeの開発情報ポータルサイト

AWSマネージドサービス+Terraformを活用してDBオペレーションをより安全&簡単に(pt-oscの例)

おはこんばんちは、Database Reliability Engineerの橋本です。

以前、freee Developers Hubにて、MySQLのスキーマ変更をオンラインで実施するためのpt-online-schema-changeの導入に関する検討記事を書かせていただきました。こちらはカラムの型変更のようなテーブルロックがかかりオンラインで実施できないような変更や、オンラインで実施できても完了までに長時間かかるようなケースで用いています*1

運用の都合上、pt-online-schema-changeをそのまま用いるのではなく、以下の4つのフェーズにそれぞれ作業を分割し、個別に実行できるようにしています。前回の記事を要約すると次の流れとなります:

  1. pt-online-schema-change コマンドを実行し、スキーマ変更済みテーブルを新規作成し、pt-online-schema-changeによって既存レコードをコピーするとともに、トリガーによって更新分を別途コピーさせる。トリガー作成時のメタデータロック回避のため lock_wait_timeout を十分短くし、タイムアウト時にはリトライさせるようにする。コマンドのデフォルトの挙動では新旧テーブル間のスワップと旧テーブルのDropもあわせて実施されるが、負荷上昇やメタデータロックの懸念があるため抑止している
  2. 1.が実施完了したあと、リクエストの少ない時間帯を狙って新旧テーブル間のスワップを実施する。スワップは実質RENAME TABLEであり、こちらもメタデータロックのおそれがあるため、lock_wait_timeout を十分短くし、タイムアウト時にはリトライさせるようにする
  3. アプリケーションのスキーマファイルを更新する
  4. 任意のタイミングで旧テーブルをDROP TABLEする

その後、社内における利活用の実績が増えるとともに、安全かつ少ない作業工数で実施可能となるよう効率化されたプロセスが整備されてきましたので、今回はその仕組みを紹介いたします。

課題

pt-online-schema-changeを素直に実行する場合、いくつかの課題が発生していました。

一つは、pt-online-schema-changeを実行できるコンソールにログインしてコマンドを実行していくことになりますが、そのコンソールはデータベースを直接読み書きできる環境であるため、DBREのような限られたメンバしかログインできないようにする必要があります。
チームメンバは少人数である一方、スキーマ変更に関する依頼は少なくないため、DBREチームの作業工数がつねにスキーマ変更に割かれてしまう懸念がありました。

また、以前の記事でも解説しましたが、安全のためスキーマ変更をいくつかの手順に分割しているため、それに比例してコンソール作業も煩雑になってしまいます。各作業はスクリプトである程度簡略化しているとはいえ、実行は手作業であるため、完全なコマンドを記述した作業手順書を用意して逐一コピー&ペーストで実行……という流れになります。
コンソール作業を行わずに自動実行されるようになれば、作業工数が減るとともに人間が直接データベースにアクセスしないようになるため、よりセキュアなオペレーションになることが期待されます。

スキーマ変更作業のイメージ図。DBREのメンバのみが踏み台サーバを経由して作業用コンソールにログインできるが、都度pt-online-schema-changeコマンドを発行する必要があり、その作業量が多い
スキーマ変更依頼があれば都度コンソールから作業しているが、作業量が多い

課題に対して解決したい事項をまとめると、おおよそ次のようになります:

  • DBRE以外の開発者でもpt-online-schema-changeによるスキーマ変更を実施できる仕組みを用意したい
  • 作業の簡略化と安全な実行環境を用意したい

アーキテクチャ

このセクションでは、課題解決のために構築したシステムを紹介します。以下に実行環境の概要図を示します。freeeのサービスは基本的にAWS上で動作しており、紹介するシステムも複数のAWSマネージドサービスを組み合わせて構築しています。AWS Step Functionsを起点としてAWS Fargateを起動し、AWS Fargateの各タスクにてデータベースに対する操作を行ないます。

pt-online-schema-changeの実行環境の概要図。おもにAWS Step Functions, AWS Fargate, Amazon CloudWatch Logsから構成される
pt-online-schema-changeの実行環境の概要図

以降は詳細をそれぞれ解説します。

AWS Step FunctionsおよびAWS Fargate

AWS Step Functions(以降Step Functionsとよぶ)では、状態をもつワークフロー(ステートマシン)を構築可能で、ステートマシン内のタスクではほかのAWSサービスを呼び出すこともできます。freeeにおいて、Step FunctionsはおもにAWS Fargate(以降Fargateとよぶ)タスクを実行するための環境として用意しており、運用のためのワンショットジョブ*2を実行する起点として用いています。
ワンショットジョブでは、そのコンテナ内で実行されるスクリプトなどによってデータベースの読み書きやAWS APIを操作しています。エラーが発生した場合のリトライ処理や通知の仕組みは、別途スクリプト内やステートマシン内のタスクとして定義しておきます。
なお、Fargateのコンテナ内の標準出力および標準エラー出力は自動的にAmazon CloudWatch Logsに転送されるため、進捗やエラーの詳細は適宜AWSマネジメントコンソールから確認することができます。

pt-online-schemaの実行においても、操作対象のデータベースごとに

  • pt-online-schema-changeの実行(新しいスキーマで作成したテーブルへのレコードのコピーのみ。Dry runも別途用意)
  • 新しいスキーマのテーブルと、変更前のテーブル間のスワップ
  • スワップして使われなくなったテーブルのDrop

のそれぞれの操作に対してStep Functionsステートマシンを定義して、任意の時刻にて個別に実行可能な状態にしています。

インフラ定義

freee内のAWSリソースは原則Terraformによってコード管理されており、今回紹介する仕組みも例外ではありません。

前述のワンショットジョブの構築のためのStep Functions, Fargate, CloudWatch Logs、およびそれらに必要なIAM Role/Policyなどをまとめて管理するためのTerraform moduleを用意しており、たとえば新しいデータベースや環境に対してシステムを展開したい場合は、ホスト名や変更対象のテーブル名のような最低限の引数を与えた状態でモジュールを定義することで、少ないコード変更で環境を整えられるようにしています。

locals {
  # スキーマ変更を実施したいサービスを列挙する。
  service_names = [
    "service_a",
    "service_b",
  ]
  # 以下はサービスによらず共通の変数となる。
  ptosc_commands = {
    ptosc-dryrun  = ["./pt-osc.sh", "--dry-run"]
    ptosc-execute = ["./pt-osc.sh", "-e"]
  }

  # 各サービスごとの設定値を追加・編集する。ここにサービスを追加するだけで、一連のAWSリソースが作成されるようになっている。
  alter_table_query = {
    # pt-online-schema-changeの--alterに相当する内容
    service_a = "ADD COLUMN `txt_column` VARCHAR(255) DEFAULT NULL"
    service_b = "ADD INDEX `index_a` (`column_a`, `column_b`)"
  }
  table_name = {
    # スキーマ変更対象のテーブル名
    service_a = "table_a"
    service_b = "table_b"
  }
  ...
}

# pt-online-schema-changeを実行するためのStep Functionsステートマシンを用意するTerraform module。
#
# for_each = { ... } によって、service_a, service_bそれぞれに対して、ptosc-dryrun, ptosc-executeの
# 2つのStep Functionsステートマシンと関連するAWSリソースを作成する。
#
# テーブルスワップやDropも同様にmoduleを定義する。
module "ptosc_commands" {

  # each.keyにはservice_namesの要素とptosc_commandsの各キーをハイフンで連結した文字列が入る。たとえばservice_a-ptosc-dryrun, service_b-ptosc-executeなど。
  # each.value[0]にはservice_namesの要素が入る
  # each.value[1]にはptosc_commandsのそれぞれのキーが入る
  for_each = { for v in setproduct(local.service_names, keys(local.ptosc_commands)) : join("-", v) => v }
  source = "<Terraform moduleのソース>"

  # e.g. service_a-online-schema-change-ptosc-dryrun, service_a-online-schema-change-ptosc-execute, ...
  ecs_task_definition_name = "${each.value[0]}-online-schema-change-${each.value[1]}"

  ecs_command = local.ptosc_commands[each.value[1]]

  # 設定値をFargateの環境変数として指定する。
  environment_variables = [
    {
      name  = "ALTER_TABLE_QUERY"
      value = local.alter_table_query[each.value[0]]
    },
    {
      name  = "TABLE_NAME"
      value = local.table_name[each.value[0]]
    },
    ...
  ]

  ... # その他、ネットワークやタイムアウトなどのFargateタスク定義やStep Functionsに必要な情報を記述する。
}

実際に運用してみて

ここまで紹介した仕組みによって、次のような利点を得られるようになりました:

  • アプリケーション開発者がTerraformの各種変数を変更し適用するだけで、スキーマ変更の準備が完了する
  • 施策リリースまでのリードタイム短縮や、DBREの運用工数の削減につながる

手動のインフラ変更は限られたメンバのみその権限が与えられている場合が多いですが、Terraformコードはアプリケーション開発者でも編集できるようになっているため、アプリケーション開発者が主導して作業を開始することができます。ただし、そのコードレビューにはDBREが関わるようにし、実行したいスキーマ変更が意図したとおりかどうかなどを議論できるようにしています。コードレビューが承認されると、CI/CDフローによってterraform applyが実行され、Terraformコードの変更内容がAWSに反映されます。

また、作成・編集したStep Functionsステートマシンをアプリケーション開発者が実行できるようにしており、ほとんどの操作を委譲するようにしています。開発者の実行権限もまたIAM Roleによるアクセス制御がされており、Terraformで管理されています。ただし、操作できるステートマシンは対象のデータベースを持つサービスの開発マネージャのみとなるようにし、その実行結果の詳細なログをCloudWatch Logsで確認できるよう最小限の権限となるように調整しています(意図せずほかのデータベースに対して誤った操作をしないようにするため)。
操作が初めてである場合はペアオペレーションに立ち会うこともありますが、一連の操作や注視すべきメトリクスについて開発者側の理解が深まれば、より主体的に行なってもらうようにしています。

運用後に効果のあった事例として、開発者がパフォーマンス改善のためのスキーマ変更を思い立ったときに導入することができ、効果検証をスピーディーにおこなうことができたようです。また、以前の記事でも取り上げたようなidカラムをINTEGERからBIGINTに変更するような作業も多く、従来はDBREが変更作業を行なっていました。今回のシステムを用いて各データベースを所有するサービスの開発者がスキーマ変更を実施することで、DBREの業務が各サービスに依存せずに横断的な改善に取り組む余地が生まれることが期待されます。

まとめ

AWSのマネージドサービスの活用やTerraformによるインフラ定義のコード化によって、スキーマ変更作業の省力化を図るとともに、その作業を各サービスのエンジニアに委譲できるようにしました。
freeeのDBREチームの特徴として、データベース管理者として一元的に作業を請け負うよりも、アプリケーション開発者と協調して運用改善に取り組んだりデータベースにまつわる知見を広める活動にフォーカスを当てています。今回紹介したシステムにおいても、その役割を果たすことができたと感じています。
この記事などをとおしてfreeeのDBREの活動に関心をもったかたは、カジュアル面談の機会を設けていますのでぜひお越しください。詳細は freee株式会社 採用情報 を参照ください。

*1:オンラインで実施できない操作はMySQL :: MySQL 8.0 リファレンスマニュアル :: 15.12.1 オンライン DDL 操作で「インプレース:いいえ」となっているものが対象。オンラインDDLにおいても、対象のテーブルに書き込みDMLが行われた際の一時ログファイルがあふれそうな場合を考慮する

*2:たとえば検証環境のRDSインスタンスを使わない時間帯に停止したり、本番環境から検証環境へデータベースをコピーするようなジョブなどがあります