Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add unique constraint to reader and readable #78

Merged
merged 3 commits into from
Aug 22, 2016
Merged

Add unique constraint to reader and readable #78

merged 3 commits into from
Aug 22, 2016

Conversation

allenwq
Copy link
Contributor

@allenwq allenwq commented Aug 20, 2016

The readable and reader is not unique before, and mark_array_as_read is not thread safe.

Our production app has lots of duplicates generated under high load. And this further result in duplicated records returned in .with_readmarks_for(user).

This PR adds a unique constraint to the table and handle the error.

Still working on a test for this.

@allenwq
Copy link
Contributor Author

allenwq commented Aug 20, 2016

@ledermann Let me know your comments, thanks !

@coveralls
Copy link

coveralls commented Aug 20, 2016

Coverage Status

Coverage remained the same at 100.0% when pulling 6361e5d on allenwq:master into a77b2f9 on ledermann:master.

@allenwq
Copy link
Contributor Author

allenwq commented Aug 20, 2016

@ledermann Tests added, sorry I have been testing travis here and so got spammed by coveralls.

@coveralls
Copy link

coveralls commented Aug 20, 2016

Coverage Status

Coverage remained the same at 100.0% when pulling f1d9e45 on allenwq:master into a77b2f9 on ledermann:master.

rm.timestamp = timestamp
rm.save!
rescue ActiveRecord::RecordNotUnique
raise ActiveRecord::Rollback
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line seems not to be required. A rollback is already made on this exception, or am I wrong?

Copy link
Contributor Author

@allenwq allenwq Aug 21, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Initially, the code here was next, but it did not work and I debugged for quite long time. I realised that most of the databases do not support nested transactions, and AR uses savepoints to mitigate that, this Rollback is needed in order to let AR rollback to the savepoint. ( Check here http://api.rubyonrails.org/classes/ActiveRecord/Transactions/ClassMethods.html )

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In my tests with SQLite and MySQL removing the raise ActiveRecord::Rollback works fine. IMHO there is no need for a rollback here, because there is nothing to rollback. A Rollback is required if you want to undo a successful write operation, but we don't have one here, because the only write operation has already failed.

Some further investigation leads me to the following simplification of your change:

else
  rm = obj.read_marks.where(:reader_id => reader.id, :reader_type => reader.class.base_class.name).first || obj.read_marks.build
  rm.reader_id   = reader.id
  rm.reader_type = reader.class.base_class.name
  rm.timestamp   = timestamp
  rm.save! rescue ActiveRecord::RecordNotUnique

No inner transaction needed, no thinking about savepoints, just add a rescue for the save! operation. What do you think?

Copy link
Contributor Author

@allenwq allenwq Aug 22, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yah, MySQL works, but Postgres won't work. ( see Nested Transactions section in the link which I posted earlier ).

The solution you suggested will rollback the entire transaction, which means all elements in that array will be rollback if one of them is not unique. Is that what we want ?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are right, Postgres needs some special handling, because of the error message "current transaction is aborted, commands ignored until end of transaction block". The inner transaction with the additional rollback both seems to be needed to make it compatible with Postgres. For SQLite and MySQL it's not needed.

@ledermann
Copy link
Owner

Thank you very much for contribution, @allenwq! I'm very interested to improve the gem to fix edge cases like the one you described. Before merging, please see me comments above.

@allenwq
Copy link
Contributor Author

allenwq commented Aug 21, 2016

@ledermann Thanks for the comments !


expect do
Email.mark_as_read! [ @email1, @email2 ], for: @reader
end.to change(ReadMark, :count).by(1)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ledermann How about this ? think it's more explicit.

@coveralls
Copy link

coveralls commented Aug 21, 2016

Coverage Status

Coverage remained the same at 100.0% when pulling d7e724f on allenwq:master into a77b2f9 on ledermann:master.

1 similar comment
@coveralls
Copy link

Coverage Status

Coverage remained the same at 100.0% when pulling d7e724f on allenwq:master into a77b2f9 on ledermann:master.

@ledermann ledermann merged commit a865b7e into ledermann:master Aug 22, 2016
@ledermann
Copy link
Owner

Thanks for your great work, @allenwq!

@allenwq
Copy link
Contributor Author

allenwq commented Aug 22, 2016

@ledermann My pleasure, thanks for comments and writing this awesome gem !

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants