From 41f3f81a4781a8116d2fe39efc020e9b726b78f7 Mon Sep 17 00:00:00 2001 From: Sebastian Fiedlschuster Date: Fri, 3 Apr 2015 22:54:24 +0200 Subject: [PATCH 1/6] =?UTF-8?q?caching:=20increasing=20the=20precision=20o?= =?UTF-8?q?f=20the=20timestamps=20in=20the=20database=20to=20=C2=B5s.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit trello: https://trello.com/c/jh6CpwdX/811-caching-zeit-auflosung --- config/initializers/datetime_precision.rb | 8 ++++ ...rease_datetime_precision_for_timestamps.rb | 37 ++++++++++++++++++ demo_app/my_platform.rails4/Gemfile.lock | 4 +- ..._precision_for_timestamps.your_platform.rb | 38 +++++++++++++++++++ demo_app/my_platform.rails4/db/schema.rb | 27 +++++++------ .../active_record_cache_extension_spec.rb | 14 +++++++ 6 files changed, 115 insertions(+), 13 deletions(-) create mode 100644 config/initializers/datetime_precision.rb create mode 100644 db/migrate/20150403202151_increase_datetime_precision_for_timestamps.rb create mode 100644 demo_app/my_platform.rails4/db/migrate/20150403203339_increase_datetime_precision_for_timestamps.your_platform.rb diff --git a/config/initializers/datetime_precision.rb b/config/initializers/datetime_precision.rb new file mode 100644 index 000000000..65f8ac442 --- /dev/null +++ b/config/initializers/datetime_precision.rb @@ -0,0 +1,8 @@ +# In order to handle cache_keys properly, we need to store timestamps +# with µs precision. +# +# Trello: https://trello.com/c/jh6CpwdX/811-caching-zeit-auflosung +# +# Gist: https://gist.github.com/iamatypeofwalrus/d074d22a736d49459b15 +# +Time::DATE_FORMATS.merge!({ db: '%Y-%m-%d %H:%M:%S.%6N' }) diff --git a/db/migrate/20150403202151_increase_datetime_precision_for_timestamps.rb b/db/migrate/20150403202151_increase_datetime_precision_for_timestamps.rb new file mode 100644 index 000000000..4247bb8ca --- /dev/null +++ b/db/migrate/20150403202151_increase_datetime_precision_for_timestamps.rb @@ -0,0 +1,37 @@ +# In order to handle cache_keys properly, we need to store timestamps +# with µs precision. +# +# Trello: https://trello.com/c/jh6CpwdX/811-caching-zeit-auflosung +# +# Gist: https://gist.github.com/iamatypeofwalrus/d074d22a736d49459b15 +# +class IncreaseDatetimePrecisionForTimestamps < ActiveRecord::Migration + # Include non default date stamps here + # Key :table_name + # value [:column_names] + # NOTE: only MySQL 5.6.4 and above supports DATETIME's with more precision than a second. + TABLES_AND_COLUMNS = { + users: [:created_at, :updated_at], + groups: [:created_at, :updated_at], + dag_links: [:created_at, :updated_at], + profile_fields: [:created_at, :updated_at], + pages: [:created_at, :updated_at] + } + + def up + TABLES_AND_COLUMNS.each do |table, columns| + columns.each do |column| + # MySQL supports time precision down to microseconds -- DATETIME(6) + change_column table, column, :datetime, limit: 6 + end + end + end + + def down + TABLES_AND_COLUMNS.each do |table, columns| + columns.each do |column| + echange_column table, column, :datetime + end + end + end +end \ No newline at end of file diff --git a/demo_app/my_platform.rails4/Gemfile.lock b/demo_app/my_platform.rails4/Gemfile.lock index 5cbcdeeda..4bbf8054e 100644 --- a/demo_app/my_platform.rails4/Gemfile.lock +++ b/demo_app/my_platform.rails4/Gemfile.lock @@ -32,7 +32,7 @@ GIT PATH remote: ../.. specs: - your_platform (1.0.0) + your_platform (1.0.1) acts-as-dag (>= 2.5.7) acts_as_tree bcrypt (>= 3.0.1) @@ -227,7 +227,7 @@ GEM yajl-ruby font-awesome-rails (4.3.0.0) railties (>= 3.2, < 5.0) - foreigner (1.7.3) + foreigner (1.7.4) activerecord (>= 3.0.0) foreman (0.78.0) thor (~> 0.19.1) diff --git a/demo_app/my_platform.rails4/db/migrate/20150403203339_increase_datetime_precision_for_timestamps.your_platform.rb b/demo_app/my_platform.rails4/db/migrate/20150403203339_increase_datetime_precision_for_timestamps.your_platform.rb new file mode 100644 index 000000000..698ebebfa --- /dev/null +++ b/demo_app/my_platform.rails4/db/migrate/20150403203339_increase_datetime_precision_for_timestamps.your_platform.rb @@ -0,0 +1,38 @@ +# This migration comes from your_platform (originally 20150403202151) +# In order to handle cache_keys properly, we need to store timestamps +# with µs precision. +# +# Trello: https://trello.com/c/jh6CpwdX/811-caching-zeit-auflosung +# +# Gist: https://gist.github.com/iamatypeofwalrus/d074d22a736d49459b15 +# +class IncreaseDatetimePrecisionForTimestamps < ActiveRecord::Migration + # Include non default date stamps here + # Key :table_name + # value [:column_names] + # NOTE: only MySQL 5.6.4 and above supports DATETIME's with more precision than a second. + TABLES_AND_COLUMNS = { + users: [:created_at, :updated_at], + groups: [:created_at, :updated_at], + dag_links: [:created_at, :updated_at], + profile_fields: [:created_at, :updated_at], + pages: [:created_at, :updated_at] + } + + def up + TABLES_AND_COLUMNS.each do |table, columns| + columns.each do |column| + # MySQL supports time precision down to microseconds -- DATETIME(6) + change_column table, column, :datetime, limit: 6 + end + end + end + + def down + TABLES_AND_COLUMNS.each do |table, columns| + columns.each do |column| + echange_column table, column, :datetime + end + end + end +end \ No newline at end of file diff --git a/demo_app/my_platform.rails4/db/schema.rb b/demo_app/my_platform.rails4/db/schema.rb index 3fac9d35b..f300c69d5 100644 --- a/demo_app/my_platform.rails4/db/schema.rb +++ b/demo_app/my_platform.rails4/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20150325105893) do +ActiveRecord::Schema.define(version: 20150403203339) do create_table "activities", force: true do |t| t.integer "trackable_id" @@ -62,8 +62,8 @@ t.string "descendant_type" t.boolean "direct" t.integer "count" - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", limit: 6 + t.datetime "updated_at", limit: 6 t.datetime "valid_to" t.datetime "valid_from" end @@ -112,8 +112,8 @@ create_table "groups", force: true do |t| t.string "name" - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", limit: 6 + t.datetime "updated_at", limit: 6 t.string "token" t.string "extensive_name" t.string "internal_token" @@ -132,6 +132,10 @@ add_index "last_seen_activities", ["user_id"], name: "last_seen_activities_user_id_fk", using: :btree + create_table "my_structureables", force: true do |t| + t.string "name" + end + create_table "nav_nodes", force: true do |t| t.string "url_component" t.string "breadcrumb_item" @@ -151,8 +155,8 @@ create_table "pages", force: true do |t| t.string "title" t.text "content" - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", limit: 6 + t.datetime "updated_at", limit: 6 t.string "redirect_to" t.integer "author_user_id" t.string "type" @@ -181,8 +185,8 @@ t.string "label" t.string "type" t.text "value" - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", limit: 6 + t.datetime "updated_at", limit: 6 t.string "profileable_type" t.integer "parent_id" end @@ -245,8 +249,8 @@ t.string "alias" t.string "first_name" t.string "last_name" - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", limit: 6 + t.datetime "updated_at", limit: 6 t.boolean "female" t.string "accepted_terms" t.datetime "accepted_terms_at" @@ -286,6 +290,7 @@ t.datetime "updated_at" end + Foreigner.load add_foreign_key "attachments", "users", name: "attachments_author_user_id_fk", column: "author_user_id" add_foreign_key "bookmarks", "users", name: "bookmarks_user_id_fk" diff --git a/spec/models/active_record_cache_extension_spec.rb b/spec/models/active_record_cache_extension_spec.rb index cb0fb749d..36cc6de0f 100644 --- a/spec/models/active_record_cache_extension_spec.rb +++ b/spec/models/active_record_cache_extension_spec.rb @@ -9,6 +9,20 @@ @user = create(:user) end + describe "#cache_key" do + subject { @user.cache_key } + it "should be the same before and after a reload from the database" do + cache_key_before_reload = @user.cache_key + @user.reload + subject.should == cache_key_before_reload + end + it "should allow to retrieve the cached value after reload from database" do + Rails.cache.write [@user, :foobar], "Value written before reload" + @user.reload + Rails.cache.read([@user, :foobar]).should == "Value written before reload" + end + end + describe "#cached" do subject { @user.cached(:title) } From 29981df01161aba8e35a0f7eb3a7ddd8e3cd305e Mon Sep 17 00:00:00 2001 From: Sebastian Fiedlschuster Date: Fri, 3 Apr 2015 23:30:56 +0200 Subject: [PATCH 2/6] using mysql 5.6 on travis. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Since https://github.com/fiedl/your_platform/commit/41f3f81a4781a8116d2fe39efc 020e9b726b78f7 (µs timestamps), we need mysql 5.6. Travis supports only mysql 5.5 by default at the moment. Trello: https://trello.com/c/jh6CpwdX/811-caching-zeit-auflosung See also: * https://github.com/piwik/piwik/commit/20bd2e1c24e5d673dce3feb256204ad48c 29f160 * https://github.com/travis-ci/travis-ci/issues/1986 --- .travis.yml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/.travis.yml b/.travis.yml index eae9edcfe..010e9280e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,6 +5,24 @@ env: services: - redis-server before_install: + # INSTALL MYSQL 5.6 + # (https://github.com/piwik/piwik/commit/20bd2e1c24e5d673dce3feb256204ad48c29f160) + # TODO: Remove when mysql 5.6 is provided by travis. + # Otherwise, our migrations will raise a syntax error. + - "sudo apt-get remove mysql-common mysql-server-5.5 mysql-server-core-5.5 mysql-client-5.5 mysql-client-core-5.5" + - "sudo apt-get autoremove" + - "sudo apt-get install libaio1" + - "wget -O mysql-5.6.14.deb http://dev.mysql.com/get/Downloads/MySQL-5.6/mysql-5.6.14-debian6.0-x86_64.deb/from/http://cdn.mysql.com/" + - "sudo dpkg -i mysql-5.6.14.deb" + - "sudo cp /opt/mysql/server-5.6/support-files/mysql.server /etc/init.d/mysql.server" + - "sudo ln -s /opt/mysql/server-5.6/bin/* /usr/bin/" + - "sudo sed -i'' 's/table_cache/table_open_cache/' /etc/mysql/my.cnf" + - "sudo sed -i'' 's/log_slow_queries/slow_query_log/' /etc/mysql/my.cnf" + - "sudo sed -i'' 's/basedir[^=]\\+=.*$/basedir = \\/opt\\/mysql\\/server-5.6/' /etc/mysql/my.cnf" + - "sudo /etc/init.d/mysql.server start" + - mysql --version + - mysql -e "SELECT VERSION();" + # /END MYSQL 5.6 - travis_retry gem update --system - travis_retry gem install bundler install: From 9cbe9b8ce37e313a9b09bee3cceb27b8a5a876fd Mon Sep 17 00:00:00 2001 From: Sebastian Fiedlschuster Date: Sat, 4 Apr 2015 13:14:09 +0200 Subject: [PATCH 3/6] travis: removing host option from travis configuration after migrating to rails 4. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit trying to avoid „Mysql2::Error: Can't connect to local MySQL server through socket '/tmp/mysql.sock‘“ see: http://stackoverflow.com/questions/5499035/ruby-on-rails-3-cant-connect- to-local-mysql-server-through-socket-tmp-mysql-s --- demo_app/my_platform.rails4/config/database.travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/demo_app/my_platform.rails4/config/database.travis.yml b/demo_app/my_platform.rails4/config/database.travis.yml index ed9f56fff..9d022f35b 100644 --- a/demo_app/my_platform.rails4/config/database.travis.yml +++ b/demo_app/my_platform.rails4/config/database.travis.yml @@ -6,4 +6,3 @@ test: pool: 5 username: root password: - host: localhost From 87e0fb317f38d4a525390cc21e23898104f657e4 Mon Sep 17 00:00:00 2001 From: Sebastian Fiedlschuster Date: Sat, 4 Apr 2015 15:28:17 +0200 Subject: [PATCH 4/6] specify sock in travis db configuration. --- demo_app/my_platform.rails4/config/database.travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/demo_app/my_platform.rails4/config/database.travis.yml b/demo_app/my_platform.rails4/config/database.travis.yml index 9d022f35b..b02459519 100644 --- a/demo_app/my_platform.rails4/config/database.travis.yml +++ b/demo_app/my_platform.rails4/config/database.travis.yml @@ -6,3 +6,4 @@ test: pool: 5 username: root password: + sock: /var/run/mysqld/mysql.sock \ No newline at end of file From 0a1a30d661fb43f43cdbea9bb6a6f410134ccbe5 Mon Sep 17 00:00:00 2001 From: Sebastian Fiedlschuster Date: Sat, 4 Apr 2015 16:01:09 +0200 Subject: [PATCH 5/6] travis: trying to connect to database through 127.0.0.1. --- demo_app/my_platform.rails4/config/database.travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/demo_app/my_platform.rails4/config/database.travis.yml b/demo_app/my_platform.rails4/config/database.travis.yml index b02459519..12a62ad32 100644 --- a/demo_app/my_platform.rails4/config/database.travis.yml +++ b/demo_app/my_platform.rails4/config/database.travis.yml @@ -1,9 +1,9 @@ test: adapter: mysql2 encoding: utf8 - reconnect: false + reconnect: true database: my_platform_test pool: 5 username: root password: - sock: /var/run/mysqld/mysql.sock \ No newline at end of file + host: 127.0.0.1 \ No newline at end of file From f01d42abd7b0dcfb135f598146c74cabe0c0674e Mon Sep 17 00:00:00 2001 From: Sebastian Fiedlschuster Date: Mon, 6 Apr 2015 22:06:38 +0200 Subject: [PATCH 6/6] working on dag link cache. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit I’m increasing the test coverage of dag links in the context of cache invalidation. In Rails 3, we had a special patch there, which might be unnecessary in Rails 4. Trello: * https://trello.com/c/BEDtKK83/810-caching-delete-cache-fill-cache-renew- cache * https://trello.com/c/jh6CpwdX/811-caching-zeit-auflosung --- .../active_record_associations_patches.rb | 1 + app/models/active_record_cache_extension.rb | 2 +- spec/models/dag_link_spec.rb | 56 ++++++++++++++++++- 3 files changed, 57 insertions(+), 2 deletions(-) diff --git a/app/models/active_record_associations_patches.rb b/app/models/active_record_associations_patches.rb index 3df9ad255..af4b5c56d 100644 --- a/app/models/active_record_associations_patches.rb +++ b/app/models/active_record_associations_patches.rb @@ -14,6 +14,7 @@ def destroy(*records) through_association.load_target records.each do |record| through_records_for(record).each do |through_record| + p "DELETE CACHE THROUGH HAS THROUGH ASSOCIATION." through_record.delete_cache if through_record.respond_to? :delete_cache end end diff --git a/app/models/active_record_cache_extension.rb b/app/models/active_record_cache_extension.rb index 2a0bc433e..1cd583d11 100644 --- a/app/models/active_record_cache_extension.rb +++ b/app/models/active_record_cache_extension.rb @@ -110,7 +110,7 @@ def bulk_delete_cached(method_name, objects) end def delete_cache - # p "DEBUG DELETE CACHE #{self}" + # p "DEBUG DELETE CACHE #{self.cache_key}/*" Rails.cache.delete_matched "#{self.cache_key}/*" end diff --git a/spec/models/dag_link_spec.rb b/spec/models/dag_link_spec.rb index 0a2c1008c..e3af52eae 100644 --- a/spec/models/dag_link_spec.rb +++ b/spec/models/dag_link_spec.rb @@ -1,5 +1,59 @@ require 'spec_helper' +describe DagLink do + # Changes on dag links are reflected on attributes of dag nodes (e.g. Users, Groups, etc.). + # Therefore, it is important to invalidate or renew the cache of these objects. + # + describe "(Cache Callbacks)" do + before do + + class User + def cached_group_names + cached { self.groups.pluck(:name) } + end + end + class Group + def cached_member_names + cached { self.members.collect(&:name) } + end + end + + @user = create :user + @group = create :group + @membership = @group.assign_user @user + + @user.cached_group_names + @group.cached_member_names + end + + describe "after_destroy" do + describe "through the parent object" do + subject { @group.members.destroy(@user); time_travel(2.seconds); @user.reload; @group.reload } + it "should delete the cache of the associated child object" do + subject + Rails.cache.read([@user, 'cached_group_names']).present?.should be_false + end + it "should delete the cache of the associated parent object" do + subject + Rails.cache.read([@group, 'cached_member_names']).present?.should be_false + end + end + describe "through the child object" do + subject { @user.groups.destroy(@group); time_travel(2.seconds); @user.reload; @group.reload } + it "should delete the cache of the associated child object" do + subject + Rails.cache.read([@user, 'cached_group_names']).present?.should be_false + end + it "should delete the cache of the associated parent object" do + subject + Rails.cache.read([@group, 'cached_member_names']).present?.should be_false + end + end + + end + end +end + # The dag link functionality is tested extensively in the corresponding `acts-as-dag` gem. # This test is just to make sure that the integration is propery done. Therefore, some basic scenarios are tested here. # @@ -45,4 +99,4 @@ def setup_pages end end -end +end \ No newline at end of file