Getting Stale object error. Optimistic Locking: How does it work? - ruby-on-rails

I think I've eliminated everything, but I'm not sure I understand OL perfectly enough to be sure. In general, let's say you and I are on a team to keep a foo up to date. I'm in one room and decide to save time I'll update the foo myself. So I start updating it. A minute later you have the same idea and log onto the edit page to update it as well. What happens if I finish first? What happens if you finish first? In a configuration where it fails how does it distinguish between someone editing and someone reading. If I catch and reload to update the lock I lose all my changes, how is this solved? Here, it's simple to redo the update, but potentially it's part of a more complicated form object.
My specific problem came when (best I can make out) loaded one copy in my browser, later forgot about it and then one in my console (also lock: 0?) couldn't update the one in my console with stale object error. Noticed the browser thing. Closed my console. Tried to reload my browser and got stale object error as well. Here's the code that's failing:
=> 7: self.update_attributes({
8: failed_view_attempts: self.failed_view_attempts += 1,
9: failed_view_at: Time.now
10: })
11: end
(byebug) self
#<Product id: 12... lock_version: 0>
#=> ActiveRecord::StaleObjectError (Attempted to update a stale object: Product.)
Things I've tried:
To see if another instance was being loaded I added puts "CALLED !!!!" in an after_initializecallback, but it only printed once.
And checking self.changed after rescuing from the error and get back ["updated_at", "failed_view_attempts", "failed_view_at"]

Need to set lock_version column default to zero (0).

Optimistic locking is based on an object version number.
Reading and object should not affect the version number at all.
If you try to update some object, this version number is compared (in sql actually update statement is used for example "update ... where version = 1 and ..." ) and increased on update.
If the comparison fails when trying to update, you get the stale object error. This means that the object was modified by somebody else while you was doing your changes.
To resolve this error you need to load the object again to get the actual version and maybe merge the changes manually (presenting the user some info about it, and let user decide for example).

Related

Grails 2.2.5 - domain object not being saved, but no errors

I have run into a baffling situation in a Grails 2 2.5 application where my domain object is not being persisted to the MySQL database and yet no errors are being returned. I've worked with Grails for a number of years so I think I've avoided the more obvious pitfalls, and I've never seen this one before. Here's the code:
if (sale.save(flush:true,failOnError: true)) {
println "nettTotal = "+sale.nettTotal
map.response = "OK"
map.sale = sale
} else {
map.error = "Unable to save sale"
sale.errors.allErrors.each {
println it
}
}
When I run this, I see the message 'nettTotal = 290.36', showing that the domain object has in fact been saved as far as Grails is concerned (and I don't see any errors which I would do if it had failed). Yet when I do a select in the MySQL database, I see that the row in question remains unchanged ('nett_total' remains NULL, other columns are also unchanged).
The code runs in a controller, without any explicit transaction which might have failed to commit. I'm not sure where else to look. It has to be something simple, but I'm stuck.
(I'm not sure this is relevant, but this application has been proving curiously problematic with spurious errors over the last couple of weeks suggesting some kind of sporadic corruption. For example, previously working findBy* dynamic methods have suddenly started throwing errors, which have been cleared up by a 'deep clean' of the application, i.e., grails clean and the deletion of the project directory in .grails/2.2.5/projects. Yesterday I had a wholly mystifying JSON error from unchanged code which had previously worked, which was once again cleared up by a deep clean. I'm not sure why this is happening, but it worries me and I'd like to prevent it).
The problem turned out to be with a def beforeUpdate() in the Sale object. There's nothing I can see that's wrong with it, but once I'd commented it out, the update to the database was OK again.

Mongoid identity_map and memory usage, memory leaks

When I executing query
Mymodel.all.each do |model|
# ..do something
end
It uses allot of memory and amount of used memory increases at all the time and at the and it crashes. I found out that to fix it I need to disable identity_map but when I adding to my mongoid.yml file identity_map_enabled: false I am getting error
Invalid configuration option: identity_map_enabled.
Summary:
A invalid configuration option was provided in your mongoid.yml, or a typo is potentially present. The valid configuration options are: :include_root_in_json, :include_type_for_serialization, :preload_models, :raise_not_found_error, :scope_overwrite_exception, :duplicate_fields_exception, :use_activesupport_time_zone, :use_utc.
Resolution:
Remove the invalid option or fix the typo. If you were expecting the option to be there, please consult the following page with repect to Mongoid's configuration:
I am using Rails 4 and Mongoid 4, Mymodel.all.count => 3202400
How can I fix it or maybe some one know other way to reduce amount of memory used during executing query .all.each ..?
Thank you very much for the help!!!!
I started with something just like you by doing loop through millions of record and the memory just keep increasing.
Original code:
#portal.listings.each do |listing|
listing.do_something
end
I've gone through many forum answers and I tried them out.
1st attempt: I try to use the combination of WeakRef and GC.start but no luck, I fail.
2nd attempt: Adding listing = nil to the first attempt, and still fail.
Success Attempt:
#start_date = 10.years.ago
#end_date = 1.day.ago
while #start_date < #end_date
#portal.listings.where(created_at: #start_date..#start_date.next_month).each do |listing|
listing.do_something
end
#start_date = #start_date.next_month
end
Conclusion
All the memory allocated for the record will never be released during
the query request. Therefore, trying with small number of record every
request does the job, and memory is in good condition since it will be
released after each request.
Your problem isn't the identity map, I don't think Mongoid4 even has an identity map built in, hence the configuration error when you try to turn it off. Your problem is that you're using all. When you do this:
Mymodel.all.each
Mongoid will attempt to instantiate every single document in the db.mymodels collection as a Mymodel instance before it starts iterating. You say that you have about 3.2 million documents in the collection, that means that Mongoid will try to create 3.2 million model instances before it tries to iterate. Presumably you don't have enough memory to handle that many objects.
Your Mymodel.all.count works fine because that just sends a simple count call into the database and returns a number, it won't instantiate any models at all.
The solution is to not use all (and preferably forget that it exists). Depending on what "do something" does, you could:
Page through all the models so that you're only working with a reasonable number of them at a time.
Push the logic into the database using mapReduce or the aggregation framework.
Whenever you're working with real data (i.e. something other than a trivially small database), you should push as much work as possible into the database because databases are built to manage and manipulate big piles of data.

ActiveRecord changing position on memory

I have a rather odd situation here.
My OrdersController has an edit method and at some point I have the following statement:
order_item.item = item
As you can guess, order_item.item expects a reference to an Item object, which is exactly what item is. Here comes the mistery. The first time I send a request to orders/{some_order_id}/edit, I receive the expected response. But if I send a new request (even if I just press F5) the application crashes and I receive the following error message:
ActiveRecord::AssociationTypeMismatch in OrdersController#edit
Item(#177601092) expected, got Item(#67520280)
What have I discovered so far? I have put an puts "#{Item.object_id}" just before the order_item.item = item and found out that Item.object_id changes from a request to another, which means that the Item ActiveRecord is changing its position on memory. (For instance, in the above example, during the first request Item.object_id is 67520280, while in the second it changes to 177601092.)
My question is: What can be causing the Item ActiveRecord to change its position on memory?
Useful info: I'm using rails 3.0.20
Update:
I found out that this change of ID is pretty common. But I still can't figure out why the expectation doesn't change as the Item.object_id does.
Despite the fact that I am not using Factory Girl, this answer and this discussion helped me a lot.
tl;dr
The problem was solved when I set cached_classes to true in my environment.

Database lock not working as expected with Rails & Postgres

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.

Saving updates to objects in rails

I'm trying to update one of my objects in my rails app and the changes just don't stick. There are no errors, and stepping through with the debugger just reveals that it thinks everything is updating.
Anyway, here is the code in question...
qm = QuestionMembership.find(:first, :conditions => ["question_id = ? AND form_id = ?", q_id, form_id])
qm.position = x
qm.save
For reference sake, QuestionMembership has question_id, form_id, and position fields. All are integers, and have no db constraints.
That is basically my join table between Forms and Questions.
Stepping through the code, qm gets a valid object, the position of the object does get changed to the value of x, and save returns 'true'.
However, after the method exits, the object in the db is unchanged.
What am I missing?
You may not be finding the object that you think you are. Some experimenting in irb might be enlightening.
Also, as a general rule when changing only one attribute, it's better to write
qm.update_attribute(:position, x)
instead of setting and saving. Rails will then update only that column instead of the entire row. And you also get the benefit of the data being scrubbed.
Is there an after_save?
Is the correct SQL being emitted?
In development log, you can actually see the sql that is generated.
For something like this:
qm = QuestionMembership.find(:first, :conditions => ["question_id = ? AND form_id = ?", q_id, form_id])
qm.position = x
qm.save
You should see something to the effect of:
SELECT * FROM question_memberships WHERE question_id=2 AND form_id=6 LIMIT 1
UPDATE question_memberships SET position = x WHERE id = 5
Can you output what sql you are actually seeing so we can compare?
Either update the attribute or call:
qm.reload
after the qm.save
What is the result of qm.save? True or false? And what about qm.errors, does that provide anything that makes sense to you? And what does the development.log say?
I have run into this problem rather frequently. (I was about to say consistently, but I cannot, as that would imply that I would know when it was about to happen.)
While I have no solution to the underlying issue, I have found that it seems to happen to me only when I am trying to update mysql text fields. My workaround has been to set the field to do something like:
qm.position = ""
qm.save
qm.position = x
qm.save
And to answer everyone else... when I run qm.save! I get no errors. I have not tried qm.save?
When I run through my code in the rails console everything works perfectly as evidenced by re-finding the object using the same query brings the expected results.
I have the same issue when using qm.update_attribute(... as well
My workaround has gotten me limping this far, but hopefully someone on this thread will be able to help.
Try changing qm.save to qm.save! and see if you get an exception message.
Edit: What happens when you watch the log on the call to .save!? Does it generate the expected SQL?
Use ./script/console and run this script.. step by step..
see if the position field for the object is update or not when you run line 2
then hit qm.save or qm.save!... to test
see what happens. Also as mentioned by Tim .. check the logs
Check your QuestionMembership class and verify that position does not have something like
attr_readonly :position
Best way to debug this is to do
tail -f log/development.log
And then open another console and do the code executing the save statement. Verify that the actual SQL Update statement is executed.
Check to make sure your database settings are correct. If you're working with multiple databases (or haven't changed the default sqlite3 database to MySQL) you may be working with the wrong database.
Run the commands in ./script/console to see if you see the same behavior.
Verify that a similar object (say a Form or Question) saves.
If the Form or Question saves, find the difference between the QuestionMembership and Form or Question object.
Turns out that it was emitting the wrong SQL. Basically it was looking for the QuestionMembeship object by the id column which doesn't exist.
I was under the impression that that column was unnecessary with has_many_through relationships, although it seems I was misguided.
To fix, I simply added the id column to the table as a primary key. Thanks for all the pointers.

Resources