`track_variants: false` 時のミラーリングで発生する `IntegrityError` を修正
track_variants: false の環境でバリアントをミラーリングすると ActiveStorage::IntegrityError が発生するバグが修正されました。primary.open に verify: checksum.present? を渡すことで、チェックサムが存在しない場合の整合性チェックをスキップします。
背景
track_variants: false を設定すると、バリアントのメタデータがデータベースに保存されなくなり、チェックサムも記録されません。その結果、MirrorJob が mirror(key, checksum: nil) を呼び出すことになります。
問題は primary.open の内部にある verify_integrity_of メソッドにありました。このメソッドはファイルの実際のMD5ハッシュと引数の checksum を比較しますが、checksum が nil の場合は常に不一致となり ActiveStorage::IntegrityError が送出されます。#57164 で報告されたこのバグは、Mirror サービスを使用しつつ track_variants: false を設定しているすべての環境で再現します。
Active Storage のドキュメントには「整合性チェックはチェックサムが存在する場合にのみ行われる」と記載されており、nil チェックサムで整合性チェックが実行されること自体が仕様外の動作でした。
技術的な変更
mirror_service.rb の mirror メソッドに対して、primary.open の呼び出しに verify: オプションを追加するという1行の変更が加えられました。
変更前:
primary.open(key, checksum: checksum) do |io|
変更後:
primary.open(key, checksum: checksum, verify: checksum.present?) do |io|
checksum.present? は checksum が nil または空文字列の場合に false を返すため、整合性チェックが条件付きで実行されるようになります。チェックサムが存在する通常のケースでは verify: true が渡され、従来と同じ動作が維持されます。
テストとして activestorage/test/service/mirror_service_test.rb に「チェックサムなしでのミラーリングが例外を発生させないこと」を確認するケースが追加されています。テストでは checksum: nil を渡して mirror を呼び出し、assert_nothing_raised で正常終了を確認した上で、すべてのミラーにデータが正しく複製されていることも検証しています。
設計判断
verify: オプションによる条件付きスキップ という方式が採用されました。
nil チェックサムを許容するよう verify_integrity_of 自体を変更する方法もありますが、今回は呼び出し側で verify: checksum.present? を明示的に渡すアプローチが取られています。これにより、整合性チェックを省略する意図がコードの呼び出し箇所に明示され、primary.open の汎用的な挙動を変えることなく mirror メソッド固有の要件に対応できます。
また、checksum: nil をそのまま primary.open に渡し続けている点も注目に値します。チェックサムを nil からデフォルト値に置き換えるのではなく、verify: フラグで動作を制御することで、呼び出しシグネチャの一貫性を保っています。
まとめ
本PRは1行の変更でドキュメント通りの動作を実現した修正です。チェックサムが存在しない場合に整合性チェックを呼び出し側で明示的にスキップする設計は、「チェックサムが存在する場合にのみ検証を行う」という既存の仕様をコードレベルで忠実に表現しており、track_variants: false とミラーサービスを組み合わせた構成を安全に利用できるようになります。