CIの改善のために、DBコンテナのマイグレーションの速度を改善する

こんにちは!VoicyのBEエンジニアのミックです

本日は「CIの改善のために、DBコンテナのマイグレーションの速度を改善する」話です。

きっかけ

マージしたコードのテストのCIを実行過程を眺めながら「もう少しだけ早くならないかなぁ」とふと思いました。

弊社のCIの速度は平均10m以下に収まっていて十分に速いのですが、 少しでも改善できる&手軽に改善できるところはないかと確認したところ、DBコンテナのセットアップで76秒ほどかかっているのがわかりました。

ローカル環境でDBコンテナを起動も同じ手法でセットアップしており、改善できれば一石二鳥ということで、手始めにDBコンテナのセットアップ速度を改善できないか検証してみることにしました。

調査

定義を確認すると、現状のDBコンテナのセットアップに3つのコンテナを使っていました。

  1. mysql
  2. migration/migration
  3. jwilder/dockerize

各コンテナ起動のフローと、実行時間の内訳のをまとめると以下の通りです。

  1. mysqlコンテナを起動 23s
  2. jwilder/dockerizeコンテナ起動 & mysqlサービスの立ち上げが終了するまでwaitする 12s
  3. migrate/migrateコンテナ起動 & テーブル構成をmigrateする 12s
  4. mysqlコンテナにログインして & ダミーデータをimportする 29s

単純に、mysqlの起動時にデータの挿入ができればセットアップ時間の短縮ができるだろう、と仮定して、何かしら方法はないかと公式の Readmeをとりあえず読んでみたところ、公式のMysql Dockerイメージにはカスタムスクリプトを実行する機能があったので、これを使って解決できないか試してみることにしました。

hub.docker.com

Initializing a fresh instance

When a container is started for the first time, a new database with the specified name will be created and initialized with the provided configuration variables. Furthermore, it will execute files with extensions .sh, .sql and .sql.gz that are found in /docker-entrypoint-initdb.d. Files will be executed in alphabetical order. You can easily populate your mysql services by mounting a SQL dump into that directory and provide custom images with contributed data. SQL files will be imported by default to the database specified by the MYSQL_DATABASE variable.

具体的に行ったこと

ステップとしては2つとシンプル

  1. マイグレーションしたいDDLをコンテナにマウント
  2. マイグレーション用のスクリプトをdocker-entrypoint-initdb.dにマウント

サンプル

実際のコードとは少し改変しています。

docker-compose

version: "3.7"
services:
  db:
    image: mysql:5.7
    ports:
      - 30300:3306
    volumes:
      - mysql_data:/var/lib/mysql
      - ./mysql/conf.d:/etc/mysql/conf.d
      - ./mysql/dummy/:/migration/dummy/
      - ./mysql/table/:/migration/table
      - ./mysql/init.d/:/docker-entrypoint-initdb.d/
    environment:
      DUMMY_FILE: ${DUMMY_FILE:-dummy}
      MYSQL_ROOT_PASSWORD: root
      MYSQL_USER:  test
      MYSQL_PASSWORD: test
      MYSQL_DATABASE: db
      TZ: Asia/Tokyo

カスタムスクリプトの実行タイミングは以下の通りなので、DB名とユーザー定義はdocker-composeに追加しました。

  1. MySQLのDocker imageでコンテナの初回起動で、指定の名前のデータベースを作成、指定の設定変数で初期化
  2. /docker-entrypoint-initdb.d ディレクトリにある.sh, .sql, .sql.gzのファイルををアルファベット順に実行

また、弊社のディレクトリ構成の都合で、migrationに必要なファイルをフィルターするためにdocker-entrypoint-initdb.dにスクリプトがあるディレクトリをマウントして実行するようにしました。

一部のダミーファイルをdocker-composeの外部から環境変数を使って切り替えられるようにスクリプトも追加しました。 外部から指定がなければデフォルトのファイルをインポートできるようにしています。

migration用スクリプト

ファイルの順番で読み込まれるため、実行したい順でファイルのprefixに数値を足しました。

/mysql/init.d/001_migrate.sh

#!/bin/bash

for file in `ls /migration/table*.up.sql`; do
    echo "file: ${file}";
    MYSQL_PWD=root mysql -uroot db < ${file}
done

/mysql/init.d/002_import_dummy.sh

#!/bin/bash

for file in `ls /migrate/${DUMMY_FILE}.sql`; do
    echo "file: ${file}";
    MYSQL_PWD=root mysql -uroot db < ${file}
done

MakeFile

.PHONY: up
up: 
    docker-compose -f docker-compose.yml -p db up ${DUMMY_FILE}

タスクライナーとしてMakeFileにコンテナの立ち上げコマンドを追加します。 また、立ち上げの際に、環境変数を渡すようにして、migarationするダミーファイルを切り替えられるようにしています。

呼び出し

make up DUMMY_FILE=./dummy_data

結果&終わりに

結果としてセットアップで1m以上かかっていたところを、平均30秒ほどに改善することは出来ました。劇的な改善というほどでもないですが、少しでもという当初の目的は達せられたように思います。

DBコンテナの立ち上げ以外の部分でもCIの速度改善できるところがないか引き続き調査していきたいと思います。

この記事が少しでも参考になれば幸いです。