rastam on rails

東京在住のマレーシア人 Rubyist

バックフィル 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 #613: 日本語サマリー

rubyweekly.com

Articles Stories & Videos

Why Ruby is More Readable than Python

Python 🆚 Rubyシンタックス比較。Pythonインスタンス変数を外から簡単に書き換えれて怖い。

Did You Know..

.. you can quickly 'test drive' over 100 programming-focused fonts on programmingfonts.org?

プログラミング用フォントがプレビューできるサイト。全角文字に明確に対応してるのは M PLUS CodeBinchotan Sharp

Ruby Weekly #609: 日本語サマリー

rubyweekly.com

Articles & Tutorials

Scaling Rails WebSockets in Kubernetes with AnyCable

Action Cable だと数千ソケットに耐えきれないらしい。AnyCable は同じ Rails + Action Cable を使いながら、ソケット周りを Go プロセスで処理してくれるから爆速。

Caught Out by fetch's Second Argument

foo[:bar] || bazfoo.fetch(:bar, baz) に書き換える前に要注意。後者だと baz は常に実行されるから。

Code & Tools

Motion 0.7: Pure Ruby Reactive Frontend UI Components for Rails

ViewComponent に Action Cable 機能を簡単に追加してくれる gem。

Ruby Weekly #603: 日本語サマリー

rubyweekly.com

Highlights

There's been a bit of chatter online over the past week about the future of Heroku that you might want to keep an eye on if you're a user.

Salesforce は Heroku をこれ以上メンテしない(まさかサービス終了?)かもという噂。Heroku の中の人の暴露記事と HN スレより。

  • Salesforce は Periwinkle という新規プロダクト開発中。無料枠はなく、Heroku より機能が少ない。Periwinkle リリース後は Heroku サービス終了かも。
  • Salesforce は Heroku にこれ以上投資しない。ここ数年は機能追加しなくなった。エンジニア採用もしてない、かつエンジニア大量流出。
  • Heroku のソースがレガシー化してて、メンテできる人材が足りなくなった。先日の GitHub OAuth トークン漏洩への対応が遅かったのもこの人手不足が原因らしい。

Ruby Weekly #600: 日本語サマリー

rubyweekly.com

Articles & Tutorials

Custom Ranges in Ruby

#<==> #succ メソッド実装だけでオブジェクトが Range 化できる。

Code & Tools

command_mapper: A Way to Map External Commands to Ruby Classes

CLI コマンドのラッパークラスを生成してくれる gem。コマンドの --help や man ページをパースしてオプションを全部生成という、結構すごいことやってくれるらしい。

Blazer 2.6.0: A Ruby-Powered Business Intelligence Tool

Metabase の OSS 版的な gem を ankane 先生が作ってくれた。

Ruby Weekly #591: 日本語サマリー

rubyweekly.com

Highlights

Introducing Propshaft: The Future of Asset Pipelining in Rails?

Rails の新しい asset pipeline になる Propshaft 紹介 by DHH。

Take the 2022 Ruby on Rails Survey

今年の Ruby on Rails ユーザ動向調査。

Ruby 3.1's Error Highlighting Gem

Ruby 3.1 のエラー発生箇所をより分かりやすくしてくれる gem。

Spruce up your projects' README.md files on GitHub with GitHub's new support for Mermaid.js diagrams direct within Markdown.

GitHub のマークダウンで図描けるようになった。

A React developer shared some thoughts on why they were won over by Rails for building their next app.

筆者が React から Rails に惚れた理由。フォルダー構造、ORM など決める手間が省けて、Rails の Convention over Configuration に全任せれできたから。

Articles & Tutorials

Writing Ruby Gem Native Extensions in Rust

Rust で gem を書く手順。

When I Do TDD and When I Don't

スパイク時は TDD しない。

Code & Tools

Gemsmith v17: A CLI Tool for Smithing New Ruby Gems

gem 開発用 CLI。新規 gem プロジェクト生成や、rubygems へプッシュしてくれるコマンド完備。

Rambulance 2.2: Dynamically Render Error Pages or JSON Responses for Rails Apps

HTTP ステータス別エラー画面管理 gem。