はじめに
ゲームの運営では日々お客様から「ログインが出来なくなってしまった」「受け取れるはずのアイテムが受け取れなかった」などのお問い合わせをいただきます。
そのお客様が仰ったことが本当に発生したかどうか、あるいは本当とすれば原因は何かを考えるために、私達はそのお客様のデータや操作ログをサーバーのデータベースに保存しております。
さらにはゲームのイベント開催情報やカードのステータスなど、運営に関わるデータ(マスターデータ)と横断的に照らし合わせた上で、判断しております。
ここではそんなカスタマーサポートに役立てているMariaDB Spiderエンジンの紹介と、今回それをDocker上に移設して保守も便利にした話を書こうと思います。
MariaDB Spider エンジンとは
Spiderエンジンはデータベースのエンジンでありながら、内部にデータを保存することは行いません。
代わりに、テーブル構造とデータのあるホストを指定すると、Spiderエンジンのテーブルでクエリを実行するたびに指定したホストからデータを取得してくれます。
さらにはカラムの値によってデータベースの接続先を振り分けることも可能なため、1つのSpiderエンジンのテーブルから、水平分割されたデータベースからクエリに合わせて自動的に適した場所にアクセスすることも可能になります。
なぜSpiderエンジンがカスタマーサポートで便利なのか
私達のチームではデータベースにかかるインフラの負荷上、マスターデータやユーザーデータおよびログを1台のサーバーに集約することが出来ません。
さらはユーザーデータやログについては、1台で全ての負荷を受け止めることは出来ないため、さらにユーザーごともしくはテーブルごとにそれぞれ複数台に分割してデータを管理しております。
一方で様々なデータを横断的に検索するカスタマーサポートでは、もちろん出来ればデータの内容に合わせて手動で参照先を切り替える面倒は省きたいですね。
そこで、一つのSpiderエンジンから複数に散らばった必要な全てのデータを取得できるよう構築することがカスタマーサポートに大きく役立ちます。
Docker上で使用する理由
一方でSpiderエンジンはややマイナーなエンジンであるため、AWSのRDSから作ったデータベースにはインストールされておりません。 そのためAWSを使用している場合は、RDSを使うのは諦めてEC2インスタンスの上にMySQLサーバーを立ち上げた上で、Spiderエンジンをインストールする必要があります。
従来はSpiderエンジンを直接EC2インスタンスにインストールしていましたが、以下の問題を抱えていました。
- データ構造や設定が管理されていないので、サーバーの立て直しが困難になってしまった。
- その結果、EC2インスタンスのOS等のバージョンアップがしづらいので古くなってしまった。
- アプリケーションのバージョンアップに伴いデータ構造が変わる際は、Spiderエンジンのデータベースにも都度合わせて手動で差分クエリを発行して変更する必要がある。そのためミスや抜け漏れによって実データベースと構造の差異が発生しやすい。
- 特に使用頻度の低い場所については適切なアップデートがされなかった結果、過去に発生した構造の差異を埋めるのも困難な状態になってしまった。
そこで、今回はOS等のバージョンアップのためサーバーを立て直すのと合わせて、今後保守を行いやすいようDocker化を行うことにしました。
実装
まずはDockerfileの中身から見ていただけたらと思います:
FROM mariadb:10.3-bionic RUN apt-get update && apt-get -y install \ ssh \ mariadb-plugin-spider \ gcc \ make \ libssl-dev \ libmariadb-dev RUN cp /usr/share/mysql/install_spider.sql /docker-entrypoint-initdb.d/00_install_spider.sql COPY 01_init.sql /docker-entrypoint-initdb.d/ COPY create_tables.sql /root/scripts/ COPY spider.cnf /etc/mysql/conf.d/ COPY mysql_init.sh /usr/local/bin/ RUN sed -i -e "s/exec \"\$\@\"/exit 0;/" /usr/local/bin/docker-entrypoint.sh ENTRYPOINT ["mysql_init.sh"]
やっている内容としましては、
1. Spiderエンジン・MariaDB起動に必要なライブラリを追加します
2. /docker-entrypoint-initdb.d/
に、Docker起動後に実行したいSQL文のファイルを指定します
- まず、Dockerイメージの中にSpiderをインストールするためのSQLファイル
/usr/share/mysql/install_spider.sql
があるので、それを移します。 - またデータベース起動時に実行させたいSQLファイルも合わせて作成し、移します。(今回は例として
01_init.sql
に起動時に必要なユーザーやデータベース等の設定を記述します。)- なお、テーブルの追加 (
create_tables.sql
) については後述する理由(「ハマったポイント」を参照)から別の場所に移して別のタイミングで実行させます。
- なお、テーブルの追加 (
- ファイル名の昇順に実行されるため、頭に数字等を入れると順序が整理しやすいかもしれません。
01_init.sql
の中身の例としては、以下のとおりです。
/*データベースの追加*/ CREATE DATABASE {Spider側のデータベースの名前}; /*ユーザーの追加*/ CREATE USER `{作成したいユーザー名}`@'%' IDENTIFIED BY '{作成したいパスワード名}'; GRANT SELECT, SHOW VIEW ON {作成したデータベース名}.* TO '{作成したユーザー名}'@'%'; /*接続先の追加*/ CREATE OR REPLACE SERVER {サーバー名} FOREIGN DATA WRAPPER mysql OPTIONS(HOST '{リモートのホスト名}', DATABASE '{リモートのDB名}', USER '{リモートのユーザー名}', PASSWORD '{リモートのパスワード名}', PORT 3306);
3. spider.cnf
を設置します。
spider.cnf
の中身の例は以下のとおりです。適宜ご利用の状況に合わせて書き換えてください。
各パラメータのドキュメントはココにあります
[mysqld] table_open_cache=6600 max_connections=100 general_log=ON general_log_file=/var/lib/mysql/mysql-running.log slow_query_log=ON slow_query_log_file=/var/lib/mysql/mariadb-slow.log tmp_table_size=167772160 max_heap_table_size=167772160 open_files_limit=65535
4. Dockerのエントリポイント用のファイルを作成して指定します。
Dockerfile の例では、 mysql_init.sh
がそれに当たります。理由と詳細な中身については後述の「ハマったポイント」を参照してください。
5. 最後に、 docker-compose.yml
を設置します。
docker-compose.yml
の例は以下のとおりです。
version: '2' services: db: container_name: container_name build: context: . dockerfile: Dockerfile environment: MYSQL_ROOT_PASSWORD: test ports: - "3306:3306"
ハマったポイント
また一方でまだまだ発展途上のエンジンですので、以下のようなエラーも発生してしまいました。 私もまだまだ力不足ゆえ根本解決には至りませんでしたが、回避策を記したいと思います。
1回MySQLが再起動すると、SPIDERでパーティション分割されたDBを読み込めなくなってしまう。
デフォルトのエントリーポイントである/docker-entrypoint.sh mysqld
を実行すると、 /docker-entrypoint-initdb.d/
以下のSQLファイルを実行した後に一回MySQLを再起動してしまいます。
それによって再起動した際に、パーティション分割されたDBを読み込めなくなってしまう問題があるため、/docker-entrypoint-initdb.d/
にてテーブル作成SQLを書いてしまうと、正しく読み込まれなくなってしまう問題があります。
そこで、今回はDockerfileのエントリポイントを以下のファイルにすることで回避しました。(今回の例では mysql_init.sh
としております。)
mysql_init.sh
の中身の例
#!/bin/bash /docker-entrypoint.sh mysqld /etc/init.d/mysql start mysql -u root -p${MYSQL_ROOT_PASSWORD} -h localhost < /root/scripts/create_tables.sql while sleep 30; do ps aux | grep mysqld | grep -q -v grep PROCESS_1_STATUS=$? # If the greps above find anything, they exit with 0 status # If they are not both 0, then something is wrong if [ $PROCESS_1_STATUS -ne 0 ]; then echo "One of the processes has already exited." exit 1 fi done
内容としてはテーブル作成など再起動後に行いたい処理(この例では create_tables.sql
)をその後別途行うようにエントリーポイントのファイルを上書きします。
その後はこの例ですと30秒毎にプロセスの死活監視を行い、もしプロセスが終了してしまった場合は停止させます。 こちらの記事が参考になります
また create_tables.sql
の中身の例は以下のとおりです。
USE {Spider側のデータベースの名前}; CREATE TABLE `users` ( `id` int(10) unsigned NOT NULL DEFAULT 0, `name` varchar(255) COLLATE utf8_unicode_ci NOT NULL, `created_at` datetime DEFAULT NULL, `updated_at` datetime DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=SPIDER DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci COMMENT='table "users"' PARTITION BY RANGE (`id`) (PARTITION `p1` VALUES LESS THAN (200000000) COMMENT = 'server "{サーバー名}"' ENGINE = SPIDER, PARTITION `p2` VALUES LESS THAN (300000000) COMMENT = 'server "{サーバー名}"' ENGINE = SPIDER, ...)
※ {サーバー名}
の部分には、先程の 01_init.sql
にて CREATE OR REPLACE SERVER
コマンドを実行した時に指定した名前が入ります。
以下の条件で、UNION ALLが正常にできないケースがある(カラムがずれる、CASE文を挿入するとエラーが発生するなど)
- SPIDERエンジンのテーブルに対して、SELECT文に固定値もしくはcase文を入れた場合
- かつ、partitionを使用していないテーブルに対して実行した場合
これについては、パーティションを必要としない(水平分割していない)テーブルについても、パーティションを用意することで回避しました。
Dockerを使用した効果
Docker化したことによって、課題であったサーバーの立て直しのしづらさと実データとの差異は無事解消することが出来ました。
これは実際にはデータを扱っていないことを活かして、更新する際は単にDockerイメージを自動で一から作り直してDockerコンテナを差し替える運用にした結果、以下のことが容易に出来た要因が大きいと考えます。
- 実データと構造の差異が発生しても差分を考慮して都度クエリを発行せずに、
create_tables.sql
の中身を実データベースのshow create tables
の内容をベースに生成してDockerイメージを差し替えることで対応できる - その結果、別途スクリプトで自動化してDockerイメージを作り直すのと同時に実データベースの変更を反映させることも可能になった。
一般的にはマネージドでないサービスでのデータベースの管理はDockerを使っても大変そうかと思いましたが、こと実データの取り扱いがないSpiderエンジンでは、Dockerと合わせることで想像以上に保守が楽になりました!