If I have in:
config/initializers/foobar.rb
which has:
$foos = User.where(:foo => true).pluck(:id)
In my app when i call $foo, does it hit the db everytime?
Or does $foo only hits the db, on server start?
You're storing a scope (i.e. a set of criteria). However that scope will cache what it has loaded, so doing (for example) $foos.each {|user| ...} will only execute a query once, at the point of first use (not when assigned).
Some operations will always trigger a query though, for example $foos.count will always do a select count(*)... and others will depend on whether the scope has been loaded.
Lastly, further refinements on that scope would also hit the db, for example
$foos.order('blah').first
would trigger an new query each time it was used. It should be easy to see whether this is happening, although be wary when testing in irb: irb calls inspect on the results of expressions which causes the scope to be loaded.
$foos is just a variable so it will only hit the database when it gets assigned.
Related
I'm working on a project where i want to do a mysql query from time to time. The query is too long, and actually it's done when the user does a request.
I'm afraid if many users does the request, the application will be too slow to respond. So, I want to do the query and load it with the query response from time to time, and then, on a request, the action from the controller will use this variable, instead of doing the query again and again.
How can I do that using Whenever?
on the schedule.rb
every 5.minutes do
runner "variable = Model.method"
end
and on the controller
def some_action
"the variable should be loaded here"
end
I agree with Damien Roche, you need to cache the results of the query. But, I don't think the example he gives is the best answer for you because you don't want for a user to wait for the query when it isn't cached, at the times when the cache is expired, even if this is a rare occurrence.
So you need to combine the periodic query with whenever, like you suggested, with a caching mechanism to store your query result, and retrieve it from the cache in your controller. since the runner is a different process, you will have to use a cache that is available to both the runner and your app. I recommend you look into Redis. it should be very simple to get it to work so that the runner runs the query and when it finishes writes a result set to the Redis cahce. The controller will then read the result set from the cache.
I'm a bit confused by active record, it just seems to fire the query at any time you stop, ie.
#model.where( :store_id => #sid )
Which is fine, but what if I want to build a query like this:
query = #model.where( :store_id => #sid )
if(some_condition)
query.offset(50)
and then execute the query (not actually what I'm doing but a very simple example). Is there a way to put together the query in steps and then tell it to execute?
Actually, ActiveRecord will do exactly what you want. It's called lazy loading. You might be getting confused by the rails console, which calls .inspect behinds the scenes on the result of the line.
Check out this question: Lazy loading in Rails 3.2.6
This already works like you want it too.
where() returns an instance of ActiveRecord::Relation.
The relation won't execute it's database call until it needs to. The reason you might be experiencing otherwise is that you're testing it in the console, which prints the output of each statement (thus loading the relation). You can test whether a relation has been loaded via the loaded() method.
Try this on the console:
m = #model.where(:store_id => #sid); # the semicolon will silence the output
m.loaded? # nil
m # executes db call, will print out the contents of the relation
m.loaded? # true
Observers and Sweepers are removed from Rails 4. Cool.
But what is the way to cache and clear cache then ?
I read about russian doll caching. It nice and all but it only concerns the view rendering cache. It doesn't prevent the database from being hit.
For instance:
<% cache #product do %>
Some HTML code here
<% end %>
You still need to get #product from the db to get its cache_key. So page or action caching can still be useful to prevent unnecessary load.
I could use some timeout to clear the cache sometimes but what for if the records didn't change ?
At least with sweepers you have control on that aspect. What is/will be the right way to do cache and to clear it ?
Thanks ! :)
Welcome to one of the two hard problems in computer science, cache invalidation :)
You would have to handle that manually since the logic for when a cached object, unlike a cached view which can be simply derived from the objects it displays, should be invalidated is application and situation dependent.
You goto method for this is the Rails.cache.fetch method. Rails.cache.fetch takes 3 arguments; the cache key, an options hash, and a block. It first tries to read a valid cache record based on the key; if that key exists and hasn’t expired it will return the value from the cache. If it can’t find a valid record it instead takes the return value from the block and stores it in the cache with your specified key.
For example:
#models = Rails.cache.fetch my_cache_key do
Model.where(condition: true).all
end
This will cache the block and reuse the result until something (tm) invalidates the key, forcing the block to be reevaluated. Also note the .all at the end of the method chain. Normally Rails would return an ActiveRecord relation object that would be cached and this would then be evaluated when you tried to use #models for the first time, neatly sidestepping the cache. The .all call forces Rails to eager load the records and ensure that it's the result that we cache, not the question.
So now that you get all your cache on and never talk to the database again we have to make sure we cover the other end, invalidating the cache. This is done with the Rails.cache.delete method that simply takes a cache key and removes it, causing a miss the next time you try to fetch it. You can also use the force: trueoption with fetch to force a re-evaluation of the block. Whichever suits you.
The science of it all is where to call Rails.cache.delete, in the naïve case this would be on update and delete for a single instance and update, delete, create on any member for a collection. There will always bee corner cases and they are always application specific, so I can't help you much there.
I assume in this answer that you will set up some sane cache store, like memcached or Redis.
Also remember to add this to config/environments/development.rb:
config.cache_store = :null_store
or you development environment will cache and you will end up hairless from frustration.
For further reference read: Everyone should be using low level caching in Rails and The rails API docs
It is also worth noting that functionality is not removed from Rails 4, merely extracted into a gem. If you need or would like the full features of the sweepers simply add it back to your app with a gem 'rails-observers' line in your Gemfile. That gem contains both the sweepers and observers that where removed from Rails 4 core.
I hope that helpt you get started.
I want to run NOn_cached queries using rails. Any help?
Rails is only going to cache the query result DURING the request-response cycle, so it's only going to live for the duration of the request.
You should be able to use the uncached command
uncached do
find(blah)
end
at least within a controller action.
Another (BAD!) way to do it is add a query attribute that busts the cache
where("name= ? OR 0= ?", 'smith', rand(100))
or similar, so that you get OR '0=0.3567' which is always false AND different for each request.
You really want to use uncached though :)
I have the following code in a rails model:
foo = Food.find(...)
foo.with_lock do
if bar = foo.bars.find_by_stuff(stuff)
# do something with bar
else
bar = foo.bars.create!
# do something with bar
end
end
The goal is to make sure that a Bar of the type being created is not being created twice.
Testing with_lock works at the console confirms my expectations. However, in production, it seems that in either some or all cases the lock is not working as expected, and the redundant Bar is being attempted -- so, the with_lock doesn't (always?) result in the code waiting for its turn.
What could be happening here?
update
so sorry to everyone who was saying "locking foo won't help you"!! my example initially didin't have the bar lookup. this is fixed now.
You're confused about what with_lock does. From the fine manual:
with_lock(lock = true)
Wraps the passed block in a transaction, locking the object before yielding. You pass can the SQL locking clause as argument (see lock!).
If you check what with_lock does internally, you'll see that it is little more than a thin wrapper around lock!:
lock!(lock = true)
Obtain a row lock on this record. Reloads the record to obtain the requested lock.
So with_lock is simply doing a row lock and locking foo's row.
Don't bother with all this locking nonsense. The only sane way to handle this sort of situation is to use a unique constraint in the database, no one but the database can ensure uniqueness unless you want to do absurd things like locking whole tables; then just go ahead and blindly try your INSERT or UPDATE and trap and ignore the exception that will be raised when the unique constraint is violated.
The correct way to handle this situation is actually right in the Rails docs:
http://apidock.com/rails/v4.0.2/ActiveRecord/Relation/find_or_create_by
begin
CreditAccount.find_or_create_by(user_id: user.id)
rescue ActiveRecord::RecordNotUnique
retry
end
("find_or_create_by" is not atomic, its actually a find and then a create. So replace that with your find and then create. The docs on this page describe this case exactly.)
Why don't you use a unique constraint? It's made for uniqueness
A reason why a lock wouldn't be working in a Rails app in query cache.
If you try to obtain an exclusive lock on the same row multiple times in a single request, query cached kicks in so subsequent locking queries never reach the DB itself.
The issue has been reported on Github.