Skip to content

Commit 97ed255

Browse files
committed
Disable the query cache without workarounds
Use the new `dirties: false` option to `uncached` to disable the query cache and avoid the messy workarounds. Currently only available in Rails edge so this will need to wait for its release. Will be part of Solid Cache v1.0, which will drop support for Rails 7.1 and earlier.
1 parent 8eaf08b commit 97ed255

File tree

2 files changed

+25
-71
lines changed

2 files changed

+25
-71
lines changed

app/models/solid_cache/entry.rb

Lines changed: 23 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -12,43 +12,46 @@ class Entry < Record
1212

1313
class << self
1414
def write(key, value)
15-
upsert_all_no_query_cache([ { key: key, value: value } ])
15+
write_multi([ { key: key, value: value } ])
1616
end
1717

1818
def write_multi(payloads)
19-
upsert_all_no_query_cache(payloads)
19+
without_query_cache do
20+
upsert_all \
21+
add_key_hash_and_byte_size(payloads),
22+
unique_by: upsert_unique_by, on_duplicate: :update, update_only: [ :key, :value, :byte_size ]
23+
end
2024
end
2125

2226
def read(key)
23-
result = select_all_no_query_cache([key]).first
24-
result[1] if result&.first == key
27+
read_multi([key])[key]
2528
end
2629

2730
def read_multi(keys)
28-
results = select_all_no_query_cache(keys).to_h
29-
results.except!(results.keys - keys)
30-
end
31-
32-
def delete_by_key(key)
33-
delete_no_query_cache(:key_hash, key_hash_for(key)) > 0
31+
without_query_cache do
32+
find_by_sql([select_sql(keys), *key_hashes_for(keys)]).pluck(:key, :value).to_h
33+
end
3434
end
3535

36-
def delete_multi(keys)
37-
serialized_keys = keys.map { |key| key_hash_for(key) }
38-
delete_no_query_cache(:key_hash, serialized_keys)
36+
def delete_by_key(*keys)
37+
without_query_cache do
38+
where(key_hash: key_hashes_for(keys)).delete_all
39+
end
3940
end
4041

4142
def clear_truncate
4243
connection.truncate(table_name)
4344
end
4445

4546
def clear_delete
46-
in_batches.delete_all
47+
without_query_cache do
48+
in_batches.delete_all
49+
end
4750
end
4851

4952
def lock_and_write(key, &block)
5053
transaction do
51-
uncached do
54+
without_query_cache do
5255
result = lock.where(key_hash: key_hash_for(key)).pick(:key, :value)
5356
new_value = block.call(result&.first == key ? result[1] : nil)
5457
write(key, new_value) if new_value
@@ -58,33 +61,12 @@ def lock_and_write(key, &block)
5861
end
5962

6063
def id_range
61-
uncached do
64+
without_query_cache do
6265
pick(Arel.sql("max(id) - min(id) + 1")) || 0
6366
end
6467
end
6568

6669
private
67-
def upsert_all_no_query_cache(payloads)
68-
args = [ self.all,
69-
connection_for_insert_all,
70-
add_key_hash_and_byte_size(payloads) ].compact
71-
options = { unique_by: upsert_unique_by,
72-
on_duplicate: :update,
73-
update_only: upsert_update_only }
74-
insert_all = ActiveRecord::InsertAll.new(*args, **options)
75-
sql = connection.build_insert_sql(ActiveRecord::InsertAll::Builder.new(insert_all))
76-
77-
message = +"#{self} "
78-
message << "Bulk " if payloads.many?
79-
message << "Upsert"
80-
# exec_query_method does not clear the query cache, exec_insert_all does
81-
connection.send exec_query_method, sql, message
82-
end
83-
84-
def connection_for_insert_all
85-
Rails.version >= "7.2" ? connection : nil
86-
end
87-
8870
def add_key_hash_and_byte_size(payloads)
8971
payloads.map do |payload|
9072
payload.dup.tap do |payload|
@@ -94,24 +76,10 @@ def add_key_hash_and_byte_size(payloads)
9476
end
9577
end
9678

97-
def exec_query_method
98-
connection.respond_to?(:internal_exec_query) ? :internal_exec_query : :exec_query
99-
end
100-
10179
def upsert_unique_by
10280
connection.supports_insert_conflict_target? ? :key_hash : nil
10381
end
10482

105-
def upsert_update_only
106-
[ :key, :value, :byte_size ]
107-
end
108-
109-
def select_all_no_query_cache(keys)
110-
uncached do
111-
find_by_sql([select_sql(keys), *key_hashes_for(keys)]).pluck(:key, :value)
112-
end
113-
end
114-
11583
def select_sql(keys)
11684
@get_sql ||= {}
11785
@get_sql[keys.count] ||= \
@@ -121,24 +89,6 @@ def select_sql(keys)
12189
.gsub("1111, 2222", (["?"] * keys.count).join(", "))
12290
end
12391

124-
def delete_no_query_cache(attribute, values)
125-
uncached do
126-
relation = where(attribute => values)
127-
sql = connection.to_sql(relation.arel.compile_delete(relation.table[primary_key]))
128-
129-
# exec_delete does not clear the query cache
130-
if connection.prepared_statements?
131-
connection.exec_delete(sql, "#{name} Delete All", Array(values))
132-
else
133-
connection.exec_delete(sql, "#{name} Delete All")
134-
end
135-
end
136-
end
137-
138-
def to_binary(key)
139-
ActiveModel::Type::Binary.new.serialize(key)
140-
end
141-
14292
def key_hash_for(key)
14393
# Need to unpack this as a signed integer - Postgresql and SQLite don't support unsigned integers
14494
Digest::SHA256.digest(key.to_s).unpack("q>").first
@@ -151,6 +101,10 @@ def key_hashes_for(keys)
151101
def byte_size_for(payload)
152102
payload[:key].to_s.bytesize + payload[:value].to_s.bytesize + ESTIMATED_ROW_OVERHEAD
153103
end
104+
105+
def without_query_cache(&block)
106+
uncached(dirties: false, &block)
107+
end
154108
end
155109
end
156110
end

lib/solid_cache/store/entries.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,13 +65,13 @@ def entry_write_multi(entries)
6565

6666
def entry_delete(key)
6767
writing_key(key, failsafe: :delete_entry, failsafe_returning: false) do
68-
Entry.delete_by_key(key)
68+
Entry.delete_by_key(key) > 0
6969
end
7070
end
7171

7272
def entry_delete_multi(entries)
7373
writing_keys(entries, failsafe: :delete_multi_entries, failsafe_returning: 0) do
74-
Entry.delete_multi(entries)
74+
Entry.delete_by_key(*entries)
7575
end
7676
end
7777
end

0 commit comments

Comments
 (0)