Don't mutate the names array passed to `Cache::Store#delete_multi`
ActiveSupport::Cache::Store#delete_multi が呼び出し元の配列を破壊的に変更していた問題を解消し、同系統メソッドとの振る舞いを統一しました。
背景
Cache::Store#delete_multi は名前空間付きキーに正規化する過程で names.map! を用いて配列をインプレースで書き換えていました。このため、delete_multi を呼び出した直後に names の内容が "ns:foo" のように変化し、同じ配列を再利用すると二重名前空間化や FrozenError が発生していました。
同じくマルチキー操作を提供する read_multi, write_multi, fetch_multi はすべて新しいコレクションを生成しており、配列を破壊しない実装となっていました。結果として delete_multi だけが例外的な振る舞いを示し、開発者に予期しない副作用をもたらしていました。
この不整合はテストでも再現でき、配列が変更されることで二度目の削除が失敗し、キャッシュに残存データが残ることが確認されていました。
技術的な変更
delete_multi の実装を names.map! から非破壊的な names.map に置き換え、正規化後の配列をローカル変数に再代入するだけにしました。これにより呼び出し元の配列は変更されません。
@@ -702,7 +702,7 @@ def delete_multi(names, options = nil)
return 0 if names.empty?
options = merged_options(options)
- names.map! { |key| normalize_key(key, options) }
+ names = names.map { |key| normalize_key(key, options) }
instrument_multi(:delete_multi, names, options) do
delete_multi_entries(names, **options)
さらに、activesupport/test/cache/behaviors/cache_store_behavior.rb に delete_multi が配列を変化させないことを保証するテスト を追加しました。テストは凍結配列でも正しく削除でき、呼び出し後に元のキー配列が保持されていることを明示しています。
@@
def test_delete_multi_does_not_mutate_names
key = SecureRandom.alphanumeric
other_key = SecureRandom.alphanumeric
names = [key, other_key].freeze
@cache.write(key, "bar")
@cache.write(other_key, "world")
assert_equal 2, @cache.delete_multi(names)
assert_equal [key, other_key], names
@cache.write(key, "bar")
@cache.write(other_key, "world")
assert_equal 2, @cache.delete_multi(names)
assert_not @cache.exist?(key)
assert_not @cache.exist?(other_key)
end
設計判断
delete_multi の実装を 破壊的な map! から非破壊的な map に変更した根拠は、一貫性 と 副作用回避 です。周辺の read_multi・write_multi・fetch_multi がすべて新しいコレクションを作成していることから、同様の振る舞いを期待するのは自然です。また、下流の delete_multi_entries や instrument_multi は正規化済み配列を 読み取り専用 で扱うだけで、配列のミューテーションに依存していません。したがって、実装を変更しても既存ロジックへの影響はありません。
この変更により、呼び出し側が配列を再利用できるだけでなく、凍結配列を渡した際に例外が発生しなくなるため、API の堅牢性が向上します。設計上のトレードオフは無く、コード量の増加も map への置き換えとテスト追加のみという最小限の改修で済んでいます。
まとめ
delete_multi が配列を破壊しないようにしたことで、Rails のキャッシュ API が全マルチキー操作で同一の非破壊的振る舞いを示すようになり、予期しない副作用や凍結配列でのエラーが解消されました。テスト追加により変更が検証され、今後の拡張でも安全に利用できる基盤が整いました。