背景
ゲームサーバーで扱うデータはとにかく多いです。 特にイベントなどをほぼ毎日運用する場合はサービス開始から1年でレコード数が無視できない量になり、それに伴って更新に要する時間が激増してしまいます。
以前執筆した9分43秒のデプロイを19秒にした話でもデータ更新を速くする為に変更テーブルをgit差分から取得する方法を紹介しました。 アカツキではデータの追加や管理の為に、seed_fuというデータ更新用Gemを使っています。差分データファイルを検出し、seed_fuに一致するテーブルだけを指定する方式で毎回データベースを更新していました。 しかしseed_fuが何らかの方法で失敗した場合やgitの操作ミスで差分が無くなってしまった場合のリカバリに欠けていたため、結局seed_fuを全てのテーブルで実行し直すという自体が少なからずありました。
外部ツールを追加してこのへんを管理できるかなと思ってしばらく探してみたのですが、gitの中に別のバージョン管理を入れるのはいかがなものかなというのと、運営チームのオペレーションが複雑化するのが懸念されたので一旦考え直すことに。 そこで結局git等の外部ツールに頼らずにseed_fuの実行毎に更新差分だけ自動でテーブルを指定する方法を設計したのでここでご紹介します。
要件定義
- 外部ツールに依存しない方法で作成する。
- 各テーブルの最新状況を保存できる方法を用意する。
- 各テーブルが更新が必要かどうかを判別できる方法を用意する。
実際にやったこと
データベースに新しいテーブルを追加する
まず管理用データの保存先として、データベースに新しいテーブルを追加します。
class CreateSeedCaches < ActiveRecord::Migration
def change
create_table :seed_caches do |t|
t.string :table_name, null: false
t.string :cache_value, default: "0"
t.timestamps
end
end
end
ここではテーブル名と保存値(cache)を登録できるようにします。timestampsカラムも追加しましたがこちらはデバッグやエラー発生の時に便利だと思っただけなので必須ではありません。実行速度改善のためにindexをtable_nameにつけてもいいですが、テーブルの数はそこまで増えないのでここでは省略しています。
各テーブルの最新情報を保存する
次に更新元となるymlファイルのハッシュ値(md5)を毎回格納するようにします。 各テーブルをフラット化した際のハッシュデータを格納する方法も考えたのですが、オーバーヘッドに時間がかかりすぎたので断念しました。
# ymlファイルからmd5を計算
def yml_to_md5(table)
Digest::MD5.file("フォルダパス/#{table}.yml").hexdigest
end
# ハッシュ値を更新
def update_cache(tables=[])
tables.each do |table|
cache_info = SeedCache.find_or_create_by(table_name: table)
cache_info.cache_value = yml_to_md5(table)
cache_info.save!
end
end
各テーブルが更新が必要かどうかを判別する
ハッシュ値の差分から更新するテーブルを判別して、seed_fuを実行します。
def hoge
#seed可能なテーブルを全抽出
end
desc "seed_fu with cache mechanism"
task express: :environment do
tables = hoge
targets = [] # 更新対象を登録場所
tables.each do |table|
digest = yml_to_md5(table)
cache_info = SeedCache.find_or_create_by(table_name: table)
# ハッシュ値が違っていたら更新対象に登録
if digest != cache_info.cache_value
targets.append(table)
end
end
# 更新されたテーブルが無い!
next if targets.size < 1
# テーブル指定
ENV['TABLES'] = targets.join(",")
pp "Executing seed_fu TABLES=#{ENV['TABLES']}"
# seed_fu呼び出し
Rake::Task['db:seed_fu'].invoke
end
end
これでseed_fu高速版、seed_fu:expressの完成です。 が、同僚から「何もないseed_fu実行時もハッシュ値を保存すれば、seed_fu:express移管して直ぐ使えますよね」と提案されたのでseed_fuにenhanceを導入して拡張しました。
Rake::Task['db:seed_fu'].enhance do
update_cache( ENV["TABLES"].try(:split, ",") || [] )
end
これをするとseed_fuを打てば必ずテーブルキャッシュが更新されるようになります。二回更新が走らないようにseed_fu:expressの最後のキャッシュ更新を削除しましょう。
まとめ
いかがでしたでしょうか? いままで入力ミスで実行に失敗したする度にseed_fuかける必要がなくなりました。 副産物では有りますが、ローカル環境のサーバーブランチを切り替えた後のデータ更新も切り替えが発生したファイルのみが対象になるようになったのでかなり短縮できるようになりました。
seed_fuはRailsエンジニアなら一度は見たことがあるほど有名なGemかと思います。 しかしdb依存する内容であるからなのかキャッシュ化や高速化についてはあまり言及されていないようです。この手の高速化は意外と見つからないのですが、やってみるとそこまで難しくないのだなと思いました。