PrismaのMigrationをAWS Lambdaで実行する


はじめに

TypeScript用のORMであるPrismaは、DBのスキーマをMigrationという方法で管理することができる。
ActiveRecordのMigrationとよく似ており、CREATE/ALTER TABLEのSQLクエリを順次適用していく感じのもの。

Migrationは、DBと疎通している環境から prisma migrate というコマンドを打つことで適用できる。
本記事では、このPrismaのMigrationをAWS Lambdaでサーバーレスに実行する方法を紹介する。

方法

大まかなアーキテクチャは下図。
なお本記事はDBがAWS上にあることを前提としているが、他のクラウドでも同様の方法は取れるはず。

実際のコードは、こちらのリポジトリを参照。

Migrationを実行するLambdaのコードの抜粋は下記。
Prismaには現状コードからMigrationコマンドを直接呼び出す方法はない(参照)。
このため、Prisma CLIを別プロセスで起動することにより、Migrationを実行している。
Migrationプロセスを起動する際に env 引数で本体の環境変数(Lambdaに設定したもの)を指定することで、DBの接続情報など必要な情報を渡すことができる。

export const handler: Handler = async (event, _) => {
  try {
    const exitCode = await new Promise((resolve, _) => {
      execFile(
        path.resolve("./node_modules/prisma/build/index.js"),
        ["migrate", "deploy"],
        {
          env: {
            ...process.env,
          },
        },
        (error, stdout, stderr) => {
          console.log(stdout);
          if (error != null) {
            console.log(`prisma migrate deploy exited with error ${error.message}`);
            resolve(error.code ?? 1);
          } else {
            resolve(0);
          }
        },
      );
    });

    if (exitCode != 0) throw Error(`migration failed with exit code ${exitCode}`);
  } catch (e) {
    console.log(e);
    throw e;
  }
};

AWS CDKを利用して、上記のLambdaをデプロイする。
DBの接続情報は、CloudFormationのデプロイ時にAWS Secrets Managerから読み込み、Lambdaの環境変数に渡される。

このLambdaを下記コマンドなどで起動することで、Migrationを実行することができる。

aws lambda invoke --function-name FUNCTION_ARN res.json

考察

以上が、この記事で紹介するMigrationの方法である。
ここからは、この方法の良し悪しについて考えていく。

他にどのような方法があるか

まずは比較のため、他にどのような方法があるか思いつく限り列挙してみる。

  1. CI/CDサーバー上で実行する
    CI/CDのランナーから直接Migrationを実行する方法。
    アプリケーションをデプロイするパイプラインの中でMigrationを実行するのは定石なので、実行もCI/CDのサーバーに任せてしまう。
    CI/CDサーバーとDBが疎通する必要があるので、CodeBuildをVPCと疎通させたり、SaaSの場合はSelf-hosted runnerを使ったりする。

  2. 専用のサーバーを立てて、sshで接続して実行する
    EC2などで作ったサーバーにsshなどで接続し、Migrationを手動で実行する方法。
    もちろんサーバーはDBと疎通するネットワークに配置する必要がある。
    CI/CDの考え方に染まっていないチームでは、こういう方法を採るところもあるのでは。

  3. ローカル環境から実行する
    各開発者のローカル開発端末からMigrationを実行する方法。
    非常に小規模なサービス・チームだと、このような方法を採る場合もあるかもしれない。
    VPCと開発環境のネットワークを疎通させる必要があるので、逆に難しいかも。

とりあえず3つほど挙げてみた。
他にこのような方法があるという方はぜひ教えて下さい。

Lambdaを使う方法のメリット

それでは、上記の方法と比較しながら、今回紹介した方法の良さを考えていく。
メリットは下記のようにいくつかある:

  • DBの認証情報をあちこちに置く必要がない
    認証情報をセキュアに保つためには、できる限り認証情報を直接利用する場所を減らすべきである。
    例えば、各開発者のローカル環境それぞれに認証情報が保存されている状態は、開発端末の数だけ漏洩のリスクが増すため、望ましくない。
    今回の方法では、DBの認証情報の配置は必要最小限のスコープに抑えられており、またCDK+Secrets Managerによりセキュアな形で一元管理することもできている。

  • CI/CDのサーバーからRDBMSサーバーに疎通可能にする必要がない
    CI/CDからDBにアクセスするためだけにSelf-hosted runnerを立てるのは、明らかに大変だと思う。
    また、CodeBuildでVPCとつなぐのも良いが、それほどCodeBuildは使いたくないケースやセキュリティ的に望ましくないケースもあると思われる。
    Migration専用のLambdaをVPC上で実行するのは、要件に対して最小限のソリューションに近いので好ましい。

  • サーバーレス
    これは一般的なサーバーレスのメリットと同じだが、EC2などでサーバーを立てるのに比べて管理コストが低い。
    サーバーのセキュリティパッチは考えなくてよいし、アクセス権限の管理はIAMで可能。
    また、実行ログや監査ログを残すことも容易である。

Lambdaを使う方法のデメリット

上記のように多くのメリットがあるが、一つ欠点がある。
それはLambdaの実行時間の上限が15分であることである。

Migrationの状況によっては、この制限が問題となる場合がある。
例えば作成するテーブルが非常に多い場合や、運用の長期化によりMigrationファイルが増えてきた場合、データ量が多くALTER TABLEに時間がかかる場合など。
(とはいえ、個人的な感覚で言えば、15分以上かかるのは極稀なケースではないかと思う。)

その場合は、ECS Fargateを利用してMigrationを実行するのが有効と思われる。
ECSクラスタの作成などやや必要な作業は増えるが、実行時間の制限はなくなるためより多くのユースケースに対応可能となる。
また、ECSではLambdaよりセキュアに環境変数で認証情報を渡すことができる点も優位点である。

ECSでMigrationを実行するには、 prisma migrate を実行するDockerfileを書けば良い。
比較的容易に実装できるため、この記事では詳細な手順を割愛する。

まとめ

AWS LambdaでPrismaのMigrationを実行する方法を紹介した。
他の方法と比べてもメリットの方が多いので、ぜひ試してみてください。