Reputation: 25
I'm doing a query based suggestion API in Rails, with suggestions being returned to the user as they type. In order to avoid hitting the database too often, I decided to cache the records.
def cached_values
Rails.cache.fetch(:values_cached, expires_in: 1.day) do
Table.verified.select(:value).all
end
end
cached_values
=>
[#<Table:0x000056406fc70370 id: nil, value: "xxx">,
#<Table:0x000056406fc77f80 id: nil, value: "xxx">,
#<Table:0x000056406fc77d00 id: nil, value: "xxx">
...
I'm aware it's not a good practice to cache ActiveRecord entries, but the "verified" scope is relatively small (~6k rows) and I want to query it further. So when a call to the API is made, I query the cached values (simplified, the real one is sanitized):
def query_cached(query)
cached_values.where("value LIKE '%#{query}%'").to_a
end
The issue here is that I have tested both cached and uncached queries, and the later has better performance. Setting Rails.logger.level = 0
, I noticed the cached query still logs a database query:
pry(main)> query_cached("a")
Table Load (1.2ms) SELECT "table"."value" FROM "table" WHERE "table"."verified" = TRUE AND (value LIKE '%a%')
My guess is that the cached search is both opening a connection to the database and loading the cached records, taking more time but being effectively useless. Is there any reliable way to check that? If the cache is just slower, maybe it is still worth keeping it and preventing too many connections to the database.
Benchmark for 10000 queries each:
user system total real
uncached 20.110681 0.369983 20.480664 ( 26.935934)
cached 23.750934 0.753414 24.504348 ( 34.198694)
Upvotes: 0
Views: 1000
Reputation: 106852
It is important to note that all
doesn't return all records in an array. Instead it returns an ActiveRecord::Relation
object. Such a relation represents a database query that might be called later or that can be extended by more conditions like, for example, .where("value LIKE '%#{query}%'")
. If all
already returned an array of records then you would not be able to add the additional condition with .where("value LIKE '%#{query}%'")
to it because where
doesn't exist on arrays.
Because you only cached the Relation that represents a database query that can be run by calling a method that needs the actual records (like each
, to_a
, first
) but hasn't run yet, the caching is useless in this case.
Additionally, I would argue that caching is not useful in the context of this example at all because you would need to cache different values for each different user input. That means if the user searched for foo
then you can cache that result but if another user then searches for bar
you would still need to run another query to the database. Only if two users search for the same string the cache might be useful.
In your example, a full-text index in the database might be the better choice.
Upvotes: 1