こんにちは、サーバーサイドエンジニアの長谷川(@hasehiro25)です。
今回の『ClinPeerアプリ開発の裏側』連載では、BIツール運用の実践的なTipsをご紹介します。 tech.medpeer.co.jp
BIツールの運用における課題として、「テーブルやカラムが追加された際のメンテナンスに手間がかかる」があります。
参照できるカラムをリストで管理する「許可リスト方式」では更新漏れが起きやすく、逆に参照できないカラムを管理する「拒否リスト方式」では、意図せず個人情報などのカラムが参照可能になってしまうリスクがあります。
そこでClinPeerでは、テーブルやカラムが変更された際にCIでチェックを行い、BIで参照できるデータを安全に更新する仕組みを導入しています。これにより、権限設定の抜け漏れを防いでいます。
本格的な分析用BIツールと、エラー調査用の簡易BIツールとしてBlazerをClinPeerでは導入しており、今回は後者のBlazerをメインにご紹介します。なお、両ツールとも共通でBI用スキーマファイルを使用しています。
specによるschemaチェック
ClinPeerでは、schema_for_bi.yml
というファイルで、Blazerから参照できるスキーマ情報を管理しています。
ファイルの中身は以下のようなイメージです。
articles: - id - created_at - updated_at - title - body users: - id - created_at - updated_at - nick_name # - email
YAMLファイルの構造は、articles:
のようにまずテーブル名を記述し、その配下にカラムをリスト形式で記述するシンプルなものです。
ここで重要なのが、リストの中でも#
でコメントアウトされているカラムの扱いです。これらは意図的に参照を許可しないカラムとして扱われます。
コメントアウトを用いる目的は、カラムの存在を把握しつつ、「これは意図的に許可してないよ」という状態を誰が見ても分かるようにするためです。 もし「許可リスト方式」の場合、それが意図的に書いてないのか、単なる追加忘れなのかを区別できません。コメントアウトによって、その曖昧さをなくしています。
このYAMLファイルがデータベースの現状と一致していることを担保するために、CIで以下のようなspecを実行しています。
it "テーブル定義順がABC順であること" do tables = YAML.load_file("db/schema_for_bi.yml").keys expect(tables).to eq(tables.sort) end it "全てのスキーマ情報が記載されていること", aggregate_failures: false do expect_tokens = [] ActiveRecord::Base.connection.tables.sort_by(&:itself).each do |table| expect_tokens << table columns = ActiveRecord::Base.connection.columns(table).map(&:name) columns.each { |column| expect_tokens << column } end yml_lines = File.readlines("db/schema_for_bi.yml") yml_lines.fill("", yml_lines.size..expect_tokens.size - 1).zip(expect_tokens).each do |actual_line, expect_token| expect(actual_line).to include(expect_token) end end
内容はシンプルで、ActiveRecord::Base.connection.tables
でテーブルとカラムの情報を全て取得し、File.readlines
で読み込んだYAMLファイルの内容と一致するかを一行ずつ確認します。(コメントアウト部分含む)
このspecがパスすれば、YAMLファイルがデータベースの現状を正しく反映していることが保証され、安全にBlazerの権限設定に進めます。
Rakeタスクによる権限更新
specのチェックを通過したYAMLファイルを使い、実際に権限を更新するためのRakeタスクがこちらです。
namespace :bi do task initialize_user: :environment do exec = ->(sql) { ActiveRecord::Base.connection.execute(sql) } exec["DROP USER IF EXISTS #{Setting::Blazer.db_user_name};"] # 権限リセット exec["CREATE USER #{Setting::Blazer.db_user_name} IDENTIFIED BY '#{Setting::Blazer.db_user_password}';"] exec["GRANT SHOW VIEW ON * TO #{Setting::Blazer.db_user_name};"] end task grant_select_columns: :initialize_user do YAML.load_file("db/schema_for_bi.yml").each do |table, columns| # 「read」など、MySQLの予約語に定義されたカラム名を、テーブル登録するカラム名として認識されるように、バッククォートで囲う columns = columns.map { |s| "`#{s}`" }.join(",") ActiveRecord::Base.connection.execute("GRANT SELECT (#{columns}) ON #{table} TO #{Setting::Blazer.db_user_name};") end end end
initialize_user
で古い権限設定を無効化して、新たな権限を適用する初期化処理を実行します。
そのあとgrant_select_columns
ではYAML.load_file
を使ってYAMLファイルを読み込み、それぞれのテーブルとカラムに対して権限設定を行なっています。コメントアウト部分は無視されるため、結果として参照を許可したいカラムにのみ SELECT 権限を付与する GRANT 文が実行される仕組みになっています。
grant_select_columns
の実行タイミングですが、他のデプロイ用タスクと合わせて追加しており、デプロイ時に自動で実行されるように設定しています。
namespace :deploy do task pre_hook: %i[setting:validate db:create db:migrate db:seed bi:grant_select_columns] end
まとめ
BIツールの権限設定は後回しにされがちで、「いざデータを見たい!」という時に参照できない、といったことが起こりがちです。
今回ご紹介したように、テーブル構造の変更と同時に権限設定もチェックする仕組みをCIに組み込むことで、最新かつ安全な状態でデータを参照できる環境を維持できます。
「いつかやろう」と溜まりがちな作業は、CIを活用して日々コツコツと対応していくことで、少しずつ快適な開発環境に繋がっていくと思いますので、ぜひ参考にしてみてください。
是非読者になってください!
メドピアでは一緒に働く仲間を募集しています。 ご応募をお待ちしております!
■募集ポジションはこちら medpeer.co.jp
■エンジニア紹介ページはこちら engineer.medpeer.co.jp
■メドピア公式YouTube www.youtube.com
■メドピア公式note
style.medpeer.co.jp