rastam on rails

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

レビューしやすい PR:私のコミット戦略

面倒で時間がかかりがちな、プルリクのレビュー。一秒でも早く終わらせたいあまりに、コミットをなるべく小さい単位で積むようにしている。

Spike

まずは spike 書いて、要件の理解を深めながら、クラス構造を築いていく。この段階ではまだコミットしない。アイデアを膨らませ、実装方針のヒントを集めるだけ。

生成したコード

CLI コマンドなどでコードを生成した場合は、生成したままのコードを 1 つのコミットに積む。コミットメッセージには CLI コマンドを記載。こうすることで、レビュワーはコードの生成方法を確認でき、レビューの過程でこういったコミットをスキップすることができる。

生成したコードのカスタマイズ

生成したコードをコミットした後、そのコードのカスタマイズは別のコミットに載せること。自動生成したものと手動で修正したものの区別が明確になり、レビュワーが後者に集中できる。

新規クラス

新しく実装したクラスが最後まで残る確信を得たら、単体テストと一緒にコミットする。こうやってクラス単位でコミットすることで、後からコミットを並べ替えたり削除したりする柔軟性が生まれる。

リファクター

リファクターは、個別のコミットに。コミットメッセージは Martin Fowler のカタログ上のリファクター名にすれば良し。これにより、レビュワーは挙動が変わっていないことの確認に集中できる。

英語原文

medium.com

バックフィル 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 先生が作ってくれた。