バックフィル SQL の確認事項
INSERT INTO new_table (a, b) SELECT old_table.a, old_table.b FROM old_table INNER JOIN parent_table ON old_table.parent_table_id = parent_table_id LEFT JOIN new_table ON parent_table.id = new_table.parent_table_id WHERE new_table.parent_table_id IS NULL
のような SQL でバックフィルする際、どんなチェックをすれば安心して本番 DB で実行できるか、メモっておきました。
トランザクション張ってないか?
Rails の migration で実行する場合は、INSERT 先テーブルにロックがかかっちゃうから、strong migrations に従って disable_ddl_transaction!
でトランザクション外に実行すること。
実行計画はどんな感じか?
SELECT 文だけ切り取って頭に EXPLAIN
付けて実行してみること。
SELECT old_table.a, old_table.b FROM old_table INNER JOIN parent_table ON old_table.parent_table_id = parent_table_id LEFT JOIN new_table ON parent_table.id = new_table.parent_table_id WHERE new_table.parent_table_id IS NULL
返ってくる実行計画は例えばこういう感じで、
Aggregate (cost=23.93..23.93 rows=1 width=4) -> Index Scan using fi on foo (cost=0.00..23.92 rows=6 width=4) Index Cond: (i < 10)
最初の cost=23.93..23.93
の上限 23.93
が Total Cost で、チューニングは基本この Total Cost を減らしていきたい。試してみるチューニング案としては例えば
- 条件を
JOIN ... ON
からWHERE
に移すとか - SUBSELECT を減らすとか
Seq Scan
が一番重い処理で、減らせるなら減らす
INSERT 先テーブル 🆚 INSERT 元テーブル 🆚 SELECT 文のレコード数が一致してるか?
※ ユーザ操作で INSERT 先テーブル
と INSERT 元テーブル
両方にも書き込んでる現行システムが前提。
SELECT 文のレコード数
+ INSERT 先テーブルのレコード数
= INSERT 元テーブルのレコード数
つまり
移行予定のレコード数
+ 移行済みのレコード数
= 総レコード数学
SELECT COUNT(*) '移行予定のレコード数' FROM old_table INNER JOIN parent_table ON old_table.parent_table_id = parent_table_id LEFT JOIN new_table ON parent_table.id = new_table.parent_table_id WHERE new_table.parent_table_id IS NULL UNION SELECT COUNT(id) '移行済みのレコード数' FROM new_table UNION SELECT COUNT(id) '総レコード数' FROM old_table
SQL 文にはよるが、レコード数が一致してれば、JOIN がおかしくないと思っちゃっていいでしょう。
※ staging など全環境の DB に対して実行すること!
UNIQUE インデックスに引っ掛からないか?
INSERT 先テーブルに UNIQUE インデックスが張ってある場合は、データの中に重複するデータがないことを確認。
SELECT COUNT(*), COUNT(DISTINCT old_table.a), COUNT(DISTINCT (old_table.a, old_table.b)) FROM old_table INNER JOIN parent_table ON old_table.parent_table_id = parent_table_id LEFT JOIN new_table ON parent_table.id = new_table.parent_table_id WHERE new_table.parent_table_id IS NULL
COUNT が一致してれば、重複してないと思っちゃっていいでしょう。
NOT NULL 制約に引っ掛からないか?
INSERT 先テーブルに NOT NULL 制約がかかってる場合は、データの中にヌルがないことを確認。
SELECT COUNT(old_table.a IS NULL OR NULL) FROM old_table INNER JOIN parent_table ON old_table.parent_table_id = parent_table_id LEFT JOIN new_table ON parent_table.id = new_table.parent_table_id WHERE new_table.parent_table_id IS NULL
ゼロであれば、ヌルがないと思っちゃっていいでしょう。
FK に引っ掛からないか?
INSERT 先テーブルに FK が張ってある場合は、関連テーブルに該当レコードがちゃんと存在することを確認。
SELECT old_table.a, old_table.b FROM old_table INNER JOIN parent_table ON old_table.parent_table_id = parent_table_id LEFT JOIN new_table ON parent_table.id = new_table.parent_table_id WHERE new_table.parent_table_id IS NULL
INNER JOIN parent_table
してるのがそれ。
本番 DB のダンプに対して実行しても落ちないか?
とりあえず念のため
- エラーが発生しないか?
- 実行時間どのぐらいかかるか?
Ruby Weekly #577: 日本語サマリー
Highlights
Rails コアチームのペアレビュー実況。主にベストなメソッドインタフェースを議論してる。
Articles & Tutorials
Making a GitHub Issue-Style File Uploader Using Stimulus and Active Storage
GitHub のコピペ or D&D だけでファイルアップロード・添付する、超便利機能を Stimulus で実装する方法。
Ruby Weekly #567: 日本語サマリー
Highlights
Privacy-Aware Rails Consoles with console1984 and audits1984
rails console
でユーザの個人情報など匿名化してくれる console1984 gem と、console1984 利用ログをブラウザーで照会できるようにしてくれる audits1984 gem。
Articles & Tutorials
bundle update に強いモンキーパッチの書き方。ざっくりいうと、上書きしている API が変わっていないことをモンキーパッチ内で確認すること。変わった場合は例外を投げること。
大量のメールを deliver_later
しようとしたら、Redis のキューにジョブを積むだけでも長時間がかかり、リクエストがその間ずっと待っている状態。
Sidekiq::Client.push_bulk
使えば一瞬で終わるが、ActionMailer は対応してないから、オレオレ実装せざるを得ない。
Code & Tools
wipe_out: Library for Resetting Data in Rails ActiveRecord Models
ユーザ退会などで個人情報をマスキング・匿名化してくれる gem。
Ruby Weekly #560: 日本語サマリー
Highlights
Security Fix あり。バージョンアップしましょう。
Understanding Factory Bot Syntax by Coding Your Own Factory Bot
オレオレ FactoryBot を実装してみることで FactoryBot の仕組みを理解する。deepL で翻訳できる記事もあれば、おまけに動画もある。
動画は TDD で実装してるスクリーンキャスト。TDD に興味がある人や、英語リスニングに自信がある人はぜひ。
Articles & Tutorials
The Shortest Path to Get Set Up with Automated Accessibility Testing in Rails
RSpec + axe gem で a11y テストを自動化した実装例アプリ。
includes
vs preload
vs eager_load
。
Code & Tools
ActiveRecord のメンテナーの tenderlove 先生が Ruby で実装したアセンブラー。
Heroku の必要な DB コネクション数を計算してくれるツール。
Ruby Weekly #552: 日本語サマリー
Highlights
RSpec 作成者が gem の経歴を振り返った。
- 2001 年に TDD を教えてた仕事がそもそものきっかけ。
- XUnit 表記は分かりづらかった(actual・expected 引数の順番が直感的じゃなかった)から、授業で使える DSL 作った。
- Bob Martin 先生の名言「Testing is about specification not verification」をもとに RSpec と名付けた。
- 実務では
Test::Unit
使うべきだと信じてたから gem 化しなかった。 - RubyConf 2005 にやっと gem としてリリースしてみた。意外とバズった。
- 2006 年に David Chelimsky 先生がコントリビューターとして参戦。
Articles & Tutorials
Some Useful Active Support Features You May Not Have Heard Of
知られざる ActiveSupport 便利機能。
Callbacks
Configurable
CurrentAttributes
MessageVerifier
MessageEncryptor
TDD の際は高レベルテストから書け、というアドバイス。
多数 Sidekiq ジョブの同時実行防止方法。
- フラグで制御
- キューにジョブが積まれてるか確認。
- Sidekiq Enterprise に課金して
sidekiq_options(unique_for:)
設定 - sidekiq-unique-jobs gem(一部 Lua 言語で実装されてる)
ActiveJob の場合は
役に立つ git 裏技。
- 空コミット
- ログを読みやすくする
- 不要なローカルブランチを捨てる
git rebase
などでうっかり消したコミット復元- 部分的
git add
git bisect
自動化
Ruby Weekly #550: 日本語サマリー
Highlights
Rails 7 実装中機能。気になったのは:
- 画像が全部デフォで lazy ロードするように設定できる
has_one :through
アソシエーションもbuild
・create
できる- ActiveRecord カラム暗号化
View Component を Storybook でプレビューする話。view_component_storybook gem で。
Basecamp 社内騒動。あれ以来は社員 1/3 退職しちゃった。
MAKE="make --jobs $(sysctl -n hw.ncpu)"
環境変数を付けると、bundle install
並列実行で高速化できるらしい。