Skip to content
This repository was archived by the owner on Jan 27, 2021. It is now read-only.

Conversation

@Kenneth-KT
Copy link

@Kenneth-KT Kenneth-KT commented Nov 20, 2018

This PR adds Rails 5.0 support to the activerecord-delay_touching gem.
This PR does not break Rails 4.0 compatibility.
Rspec has passed against both latest Rails version 5.2.1 and 4.2.9.

One drawback of this implementation is it does not respect time in xxx.touch(time: time), it will only be filled with current time.

@mtuckergh
Copy link

Hi @Kenneth-KT. Our general stance has been that this library will not need to support Rails 5, due to changes being made in Rails core that have similar goals. See #13 (comment).

Is there something that this library does that's missing in the Rails 5 solution, which would make it beneficial for us to support Rails 5?

@Kenneth-KT
Copy link
Author

Kenneth-KT commented Nov 21, 2018

Hi @Kenneth-KT. Our general stance has been that this library will not need to support Rails 5, due to changes being made in Rails core that have similar goals. See #13 (comment).

Is there something that this library does that's missing in the Rails 5 solution, which would make it beneficial for us to support Rails 5?

@mtuckergd Yes, this library does have features that ActiveRecord 5.0 does not provide.

  1. It only touches the same record once across multiple transactions.
  2. It aggregates touch calls of multiple records within the same table in one SQL.

Consider the following code sample:

person = Person.create!
pets = 100.times.collect { Pet.create!(person: person) }
pets.each_slice(10) do |part_of_pets|
  ActiveRecord::Base.transaction do
    part_of_pets.each { |x| x.touch_later }
  end
end

This code sample generates 110 queries:

Query #1: UPDATE "pets" SET "updated_at" = ? WHERE "pets"."id" = ?
Query #2: UPDATE "people" SET "updated_at" = ? WHERE "people"."id" = ?
Query #3: UPDATE "pets" SET "updated_at" = ? WHERE "pets"."id" = ?
Query #4: UPDATE "pets" SET "updated_at" = ? WHERE "pets"."id" = ?
Query #5: UPDATE "pets" SET "updated_at" = ? WHERE "pets"."id" = ?
Query #6: UPDATE "pets" SET "updated_at" = ? WHERE "pets"."id" = ?
Query #7: UPDATE "pets" SET "updated_at" = ? WHERE "pets"."id" = ?
Query #8: UPDATE "pets" SET "updated_at" = ? WHERE "pets"."id" = ?
Query #9: UPDATE "pets" SET "updated_at" = ? WHERE "pets"."id" = ?
Query #10: UPDATE "pets" SET "updated_at" = ? WHERE "pets"."id" = ?
Query #11: UPDATE "pets" SET "updated_at" = ? WHERE "pets"."id" = ?
Query #12: UPDATE "pets" SET "updated_at" = ? WHERE "pets"."id" = ?
Query #13: UPDATE "people" SET "updated_at" = ? WHERE "people"."id" = ?
Query #14: UPDATE "pets" SET "updated_at" = ? WHERE "pets"."id" = ?
Query #15: UPDATE "pets" SET "updated_at" = ? WHERE "pets"."id" = ?
Query #16: UPDATE "pets" SET "updated_at" = ? WHERE "pets"."id" = ?
Query #17: UPDATE "pets" SET "updated_at" = ? WHERE "pets"."id" = ?
Query #18: UPDATE "pets" SET "updated_at" = ? WHERE "pets"."id" = ?
Query #19: UPDATE "pets" SET "updated_at" = ? WHERE "pets"."id" = ?
Query #20: UPDATE "pets" SET "updated_at" = ? WHERE "pets"."id" = ?
Query #21: UPDATE "pets" SET "updated_at" = ? WHERE "pets"."id" = ?
Query #22: UPDATE "pets" SET "updated_at" = ? WHERE "pets"."id" = ?
Query #23: UPDATE "pets" SET "updated_at" = ? WHERE "pets"."id" = ?
Query #24: UPDATE "people" SET "updated_at" = ? WHERE "people"."id" = ?
Query #25: UPDATE "pets" SET "updated_at" = ? WHERE "pets"."id" = ?
Query #26: UPDATE "pets" SET "updated_at" = ? WHERE "pets"."id" = ?
Query #27: UPDATE "pets" SET "updated_at" = ? WHERE "pets"."id" = ?
Query #28: UPDATE "pets" SET "updated_at" = ? WHERE "pets"."id" = ?
Query #29: UPDATE "pets" SET "updated_at" = ? WHERE "pets"."id" = ?
Query #30: UPDATE "pets" SET "updated_at" = ? WHERE "pets"."id" = ?
Query #31: UPDATE "pets" SET "updated_at" = ? WHERE "pets"."id" = ?
Query #32: UPDATE "pets" SET "updated_at" = ? WHERE "pets"."id" = ?
Query #33: UPDATE "pets" SET "updated_at" = ? WHERE "pets"."id" = ?
Query #34: UPDATE "pets" SET "updated_at" = ? WHERE "pets"."id" = ?
Query #35: UPDATE "people" SET "updated_at" = ? WHERE "people"."id" = ?
Query #36: UPDATE "pets" SET "updated_at" = ? WHERE "pets"."id" = ?
Query #37: UPDATE "pets" SET "updated_at" = ? WHERE "pets"."id" = ?
Query #38: UPDATE "pets" SET "updated_at" = ? WHERE "pets"."id" = ?
Query #39: UPDATE "pets" SET "updated_at" = ? WHERE "pets"."id" = ?
Query #40: UPDATE "pets" SET "updated_at" = ? WHERE "pets"."id" = ?
Query #41: UPDATE "pets" SET "updated_at" = ? WHERE "pets"."id" = ?
Query #42: UPDATE "pets" SET "updated_at" = ? WHERE "pets"."id" = ?
Query #43: UPDATE "pets" SET "updated_at" = ? WHERE "pets"."id" = ?
Query #44: UPDATE "pets" SET "updated_at" = ? WHERE "pets"."id" = ?
Query #45: UPDATE "pets" SET "updated_at" = ? WHERE "pets"."id" = ?
Query #46: UPDATE "people" SET "updated_at" = ? WHERE "people"."id" = ?
Query #47: UPDATE "pets" SET "updated_at" = ? WHERE "pets"."id" = ?
Query #48: UPDATE "pets" SET "updated_at" = ? WHERE "pets"."id" = ?
Query #49: UPDATE "pets" SET "updated_at" = ? WHERE "pets"."id" = ?
Query #50: UPDATE "pets" SET "updated_at" = ? WHERE "pets"."id" = ?
Query #51: UPDATE "pets" SET "updated_at" = ? WHERE "pets"."id" = ?
Query #52: UPDATE "pets" SET "updated_at" = ? WHERE "pets"."id" = ?
Query #53: UPDATE "pets" SET "updated_at" = ? WHERE "pets"."id" = ?
Query #54: UPDATE "pets" SET "updated_at" = ? WHERE "pets"."id" = ?
Query #55: UPDATE "pets" SET "updated_at" = ? WHERE "pets"."id" = ?
Query #56: UPDATE "pets" SET "updated_at" = ? WHERE "pets"."id" = ?
Query #57: UPDATE "people" SET "updated_at" = ? WHERE "people"."id" = ?
Query #58: UPDATE "pets" SET "updated_at" = ? WHERE "pets"."id" = ?
Query #59: UPDATE "pets" SET "updated_at" = ? WHERE "pets"."id" = ?
Query #60: UPDATE "pets" SET "updated_at" = ? WHERE "pets"."id" = ?
Query #61: UPDATE "pets" SET "updated_at" = ? WHERE "pets"."id" = ?
Query #62: UPDATE "pets" SET "updated_at" = ? WHERE "pets"."id" = ?
Query #63: UPDATE "pets" SET "updated_at" = ? WHERE "pets"."id" = ?
Query #64: UPDATE "pets" SET "updated_at" = ? WHERE "pets"."id" = ?
Query #65: UPDATE "pets" SET "updated_at" = ? WHERE "pets"."id" = ?
Query #66: UPDATE "pets" SET "updated_at" = ? WHERE "pets"."id" = ?
Query #67: UPDATE "pets" SET "updated_at" = ? WHERE "pets"."id" = ?
Query #68: UPDATE "people" SET "updated_at" = ? WHERE "people"."id" = ?
Query #69: UPDATE "pets" SET "updated_at" = ? WHERE "pets"."id" = ?
Query #70: UPDATE "pets" SET "updated_at" = ? WHERE "pets"."id" = ?
Query #71: UPDATE "pets" SET "updated_at" = ? WHERE "pets"."id" = ?
Query #72: UPDATE "pets" SET "updated_at" = ? WHERE "pets"."id" = ?
Query #73: UPDATE "pets" SET "updated_at" = ? WHERE "pets"."id" = ?
Query #74: UPDATE "pets" SET "updated_at" = ? WHERE "pets"."id" = ?
Query #75: UPDATE "pets" SET "updated_at" = ? WHERE "pets"."id" = ?
Query #76: UPDATE "pets" SET "updated_at" = ? WHERE "pets"."id" = ?
Query #77: UPDATE "pets" SET "updated_at" = ? WHERE "pets"."id" = ?
Query #78: UPDATE "pets" SET "updated_at" = ? WHERE "pets"."id" = ?
Query #79: UPDATE "people" SET "updated_at" = ? WHERE "people"."id" = ?
Query #80: UPDATE "pets" SET "updated_at" = ? WHERE "pets"."id" = ?
Query #81: UPDATE "pets" SET "updated_at" = ? WHERE "pets"."id" = ?
Query #82: UPDATE "pets" SET "updated_at" = ? WHERE "pets"."id" = ?
Query #83: UPDATE "pets" SET "updated_at" = ? WHERE "pets"."id" = ?
Query #84: UPDATE "pets" SET "updated_at" = ? WHERE "pets"."id" = ?
Query #85: UPDATE "pets" SET "updated_at" = ? WHERE "pets"."id" = ?
Query #86: UPDATE "pets" SET "updated_at" = ? WHERE "pets"."id" = ?
Query #87: UPDATE "pets" SET "updated_at" = ? WHERE "pets"."id" = ?
Query #88: UPDATE "pets" SET "updated_at" = ? WHERE "pets"."id" = ?
Query #89: UPDATE "pets" SET "updated_at" = ? WHERE "pets"."id" = ?
Query #90: UPDATE "people" SET "updated_at" = ? WHERE "people"."id" = ?
Query #91: UPDATE "pets" SET "updated_at" = ? WHERE "pets"."id" = ?
Query #92: UPDATE "pets" SET "updated_at" = ? WHERE "pets"."id" = ?
Query #93: UPDATE "pets" SET "updated_at" = ? WHERE "pets"."id" = ?
Query #94: UPDATE "pets" SET "updated_at" = ? WHERE "pets"."id" = ?
Query #95: UPDATE "pets" SET "updated_at" = ? WHERE "pets"."id" = ?
Query #96: UPDATE "pets" SET "updated_at" = ? WHERE "pets"."id" = ?
Query #97: UPDATE "pets" SET "updated_at" = ? WHERE "pets"."id" = ?
Query #98: UPDATE "pets" SET "updated_at" = ? WHERE "pets"."id" = ?
Query #99: UPDATE "pets" SET "updated_at" = ? WHERE "pets"."id" = ?
Query #100: UPDATE "pets" SET "updated_at" = ? WHERE "pets"."id" = ?
Query #101: UPDATE "people" SET "updated_at" = ? WHERE "people"."id" = ?
Query #102: UPDATE "pets" SET "updated_at" = ? WHERE "pets"."id" = ?
Query #103: UPDATE "pets" SET "updated_at" = ? WHERE "pets"."id" = ?
Query #104: UPDATE "pets" SET "updated_at" = ? WHERE "pets"."id" = ?
Query #105: UPDATE "pets" SET "updated_at" = ? WHERE "pets"."id" = ?
Query #106: UPDATE "pets" SET "updated_at" = ? WHERE "pets"."id" = ?
Query #107: UPDATE "pets" SET "updated_at" = ? WHERE "pets"."id" = ?
Query #108: UPDATE "pets" SET "updated_at" = ? WHERE "pets"."id" = ?
Query #109: UPDATE "pets" SET "updated_at" = ? WHERE "pets"."id" = ?
Query #110: UPDATE "pets" SET "updated_at" = ? WHERE "pets"."id" = ?

You can see that the same People record has got updated 10 times because touch_later could not work across multiple transactions.
Also, there are 100 updates to pets because it cannot aggregate multiple record touches within the same table into one single SQL.

If we wrap the code with delay_touching, as in this code sample:

person = Person.create!
pets = 100.times.collect { Pet.create!(person: person) }
ActiveRecord::Base.delay_touching do
  pets.each_slice(10) do |part_of_pets|
    ActiveRecord::Base.transaction do
      part_of_pets.each { |x| x.touch_later }
    end
  end
end

It only generates 2 queries:

Query #1: UPDATE "pets" SET "updated_at" = '2018-11-21 13:38:51.589657' WHERE "pets"."id" IN (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
Query #2: UPDATE "people" SET "updated_at" = '2018-11-21 13:38:51.599752' WHERE "people"."id" = ?

We are running a production system that needs to support bulk importing and updating 100k+ record on daily basis, all those queries saved dramatically improved performance of our system.

super
if ActiveRecord::VERSION::MAJOR >= 5
def touch(*names, time: nil)
names = self.class.send(:timestamp_attributes_for_update_in_model) if names.empty?
Copy link

@oehlschl oehlschl Jan 22, 2019

Choose a reason for hiding this comment

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

FYI @Kenneth-KT, these changes failed for us locally on Rails v5.0.7 with the following error:

NoMethodError:
  undefined method `timestamp_attributes_for_update_in_model' for #<Class:0x00007fbcde0ace48>

(To clarify, we're in the process of upgrading an application that currently uses DelayTouching from Rails 4.2 to 5.0.)

From the source, it looks like even through v5.2.2, this method is accessed on the instance, not class, level:
https://github.com/rails/rails/blob/v5.0.7/activerecord/lib/active_record/touch_later.rb#L18
https://github.com/rails/rails/blob/v5.2.2/activerecord/lib/active_record/touch_later.rb#L20

We're now using the following instead:

self.send(:timestamp_attributes_for_update_in_model) if names.empty?

Choose a reason for hiding this comment

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

Also, I'm admittedly not familiar with the Rails 5 persistence implementation, but what's the need for timestamp_attributes_for_update_in_model here?

It seems like a nil value is expected in the case of only touching defaults, and is handled here:

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants