Skip to content

Commit

Permalink
Disable the query cache without workarounds
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
djmb committed May 1, 2024
1 parent af7e25b commit 6db79fe
Showing 1 changed file with 16 additions and 45 deletions.
61 changes: 16 additions & 45 deletions app/models/solid_cache/entry.rb
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ def clear_delete

def lock_and_write(key, &block)
transaction do
uncached do
without_query_cache do
result = lock.where(key_hash: key_hash_for(key)).pick(:key, :value)
new_value = block.call(result&.first == key ? result[1] : nil)
write(key, new_value)
Expand All @@ -59,31 +59,24 @@ def lock_and_write(key, &block)
end

def id_range
uncached do
without_query_cache do
pick(Arel.sql("max(id) - min(id) + 1")) || 0
end
end

private
def upsert_all_no_query_cache(payloads)
args = [ self,
connection_for_insert_all,
add_key_hash_and_byte_size(payloads) ].compact
options = { unique_by: upsert_unique_by,
on_duplicate: :update,
update_only: upsert_update_only }
insert_all = ActiveRecord::InsertAll.new(*args, **options)
sql = connection.build_insert_sql(ActiveRecord::InsertAll::Builder.new(insert_all))

message = +"#{self} "
message << "Bulk " if payloads.many?
message << "Upsert"
# exec_query_method does not clear the query cache, exec_insert_all does
connection.send exec_query_method, sql, message
without_query_cache do
upsert_all \
add_key_hash_and_byte_size(payloads),
unique_by: upsert_unique_by, on_duplicate: :update, update_only: [ :key, :value, :byte_size ]
end
end

def connection_for_insert_all
Rails.version >= "7.2" ? connection : nil
def delete_no_query_cache(attribute, values)
without_query_cache do
where(attribute => values).delete_all
end
end

def add_key_hash_and_byte_size(payloads)
Expand All @@ -95,18 +88,10 @@ def add_key_hash_and_byte_size(payloads)
end
end

def exec_query_method
connection.respond_to?(:internal_exec_query) ? :internal_exec_query : :exec_query
end

def upsert_unique_by
connection.supports_insert_conflict_target? ? :key_hash : nil
end

def upsert_update_only
[ :key, :value, :byte_size ]
end

def get_sql
@get_sql ||= build_sql(where(key_hash: 1).select(:key, :value))
end
Expand All @@ -130,7 +115,7 @@ def build_sql(relation)
end

def select_all_no_query_cache(query, values)
uncached do
without_query_cache do
if connection.prepared_statements?
result = connection.select_all(sanitize_sql(query), "#{name} Load", Array(values), preparable: true)
else
Expand All @@ -141,24 +126,6 @@ def select_all_no_query_cache(query, values)
end
end

def delete_no_query_cache(attribute, values)
uncached do
relation = where(attribute => values)
sql = connection.to_sql(relation.arel.compile_delete(relation.table[primary_key]))

# exec_delete does not clear the query cache
if connection.prepared_statements?
connection.exec_delete(sql, "#{name} Delete All", Array(values))
else
connection.exec_delete(sql, "#{name} Delete All")
end
end
end

def to_binary(key)
ActiveModel::Type::Binary.new.serialize(key)
end

def key_hash_for(key)
# Need to unpack this as a signed integer - Postgresql and SQLite don't support unsigned integers
Digest::SHA256.digest(key.to_s).unpack("q>").first
Expand All @@ -167,6 +134,10 @@ def key_hash_for(key)
def byte_size_for(payload)
payload[:key].to_s.bytesize + payload[:value].to_s.bytesize + ESTIMATED_ROW_OVERHEAD
end

def without_query_cache(&block)
uncached(dirties: false, &block)
end
end
end
end
Expand Down

0 comments on commit 6db79fe

Please sign in to comment.