identity_cache 1.1.0 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +5 -4
  3. data/.github/workflows/cla.yml +22 -0
  4. data/.rubocop.yml +7 -3
  5. data/.spin/bootstrap +7 -0
  6. data/.spin/svc.yml +2 -0
  7. data/CHANGELOG.md +10 -0
  8. data/Gemfile +15 -5
  9. data/LICENSE +1 -1
  10. data/README.md +5 -6
  11. data/Rakefile +13 -12
  12. data/dev.yml +2 -4
  13. data/gemfiles/Gemfile.latest-release +12 -7
  14. data/gemfiles/Gemfile.min-supported +11 -6
  15. data/gemfiles/Gemfile.rails-edge +8 -5
  16. data/identity_cache.gemspec +15 -27
  17. data/{railgun.yml → isogun.yml} +0 -5
  18. data/lib/identity_cache/belongs_to_caching.rb +1 -0
  19. data/lib/identity_cache/cache_fetcher.rb +241 -16
  20. data/lib/identity_cache/cache_hash.rb +7 -6
  21. data/lib/identity_cache/cache_invalidation.rb +1 -0
  22. data/lib/identity_cache/cache_key_generation.rb +22 -19
  23. data/lib/identity_cache/cache_key_loader.rb +2 -2
  24. data/lib/identity_cache/cached/association.rb +2 -4
  25. data/lib/identity_cache/cached/attribute.rb +3 -3
  26. data/lib/identity_cache/cached/attribute_by_multi.rb +1 -1
  27. data/lib/identity_cache/cached/belongs_to.rb +3 -0
  28. data/lib/identity_cache/cached/embedded_fetching.rb +2 -0
  29. data/lib/identity_cache/cached/primary_index.rb +3 -2
  30. data/lib/identity_cache/cached/recursive/association.rb +2 -0
  31. data/lib/identity_cache/cached/recursive/has_many.rb +1 -0
  32. data/lib/identity_cache/cached/recursive/has_one.rb +1 -0
  33. data/lib/identity_cache/cached/reference/association.rb +1 -0
  34. data/lib/identity_cache/cached/reference/has_many.rb +1 -0
  35. data/lib/identity_cache/cached/reference/has_one.rb +1 -0
  36. data/lib/identity_cache/cached.rb +1 -0
  37. data/lib/identity_cache/configuration_dsl.rb +1 -0
  38. data/lib/identity_cache/encoder.rb +2 -1
  39. data/lib/identity_cache/expiry_hook.rb +1 -0
  40. data/lib/identity_cache/fallback_fetcher.rb +6 -1
  41. data/lib/identity_cache/mem_cache_store_cas.rb +15 -5
  42. data/lib/identity_cache/memoized_cache_proxy.rb +15 -15
  43. data/lib/identity_cache/parent_model_expiration.rb +3 -1
  44. data/lib/identity_cache/query_api.rb +3 -0
  45. data/lib/identity_cache/railtie.rb +1 -0
  46. data/lib/identity_cache/should_use_cache.rb +1 -0
  47. data/lib/identity_cache/version.rb +2 -1
  48. data/lib/identity_cache/with_primary_index.rb +37 -10
  49. data/lib/identity_cache/without_primary_index.rb +7 -3
  50. data/lib/identity_cache.rb +38 -24
  51. data/performance/cache_runner.rb +12 -9
  52. data/performance/cpu.rb +6 -5
  53. data/performance/externals.rb +6 -5
  54. data/performance/profile.rb +7 -6
  55. metadata +27 -123
  56. data/.github/probots.yml +0 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5196973719eb77354c40188d33d291b31ac94b23311bd78ba263e1e9c973e1f1
4
- data.tar.gz: ce1be3c774f20c56b288272b9d30ce4abcb93d60536dedb1a232b566de92e3df
3
+ metadata.gz: 561965856845fc5d5094581d735584924eda58557edfb3831c39bce2498635aa
4
+ data.tar.gz: 33a85428a3b2a298076d81a79b662b742c00a25971bf453f8f57eeab7cba2681
5
5
  SHA512:
6
- metadata.gz: 4e1bfc8ec934bedf3ea3c451b87b903b8c373c033ab062535cd1e2abcb75cf0da508887440e1d5024a492ea9cb6f34e26aac5b18c53a9a21af96c072bbbaf7fe
7
- data.tar.gz: e3105dbebe6f2da6f25e13f05a45edd53182bbe8058ac8dbdfe800a6981d765bf7cbefb7a27caa4c21966c23a6630df0994f7f308dac70f61a35f43c801a7430
6
+ metadata.gz: 6a8c691d8883acfd69f11724dfff13a3ef4858a2abd2ca658659981f1d08b36dea244100c4030b8ef5d39ac8509e639fea6cd02127e09d157be636fcc2bee77a
7
+ data.tar.gz: e357c3a4f02a288a5ddb3290cbd11176937821bb3c9052fb318d9139ac253650f7018a8249b2ebc9f115015a6434baf7cb1b7e76bfb0dbf1e98ee745e3e95ed7
@@ -12,17 +12,18 @@ jobs:
12
12
  runs-on: ubuntu-latest
13
13
 
14
14
  strategy:
15
+ fail-fast: false
15
16
  matrix:
16
17
  entry:
17
18
  - name: 'Minimum supported'
18
- ruby: 2.5
19
+ ruby: '2.5'
19
20
  gemfile: "Gemfile.min-supported"
20
21
  - name: 'Latest released & run rubocop'
21
- ruby: 2.7
22
+ ruby: '3.0'
22
23
  gemfile: "Gemfile.latest-release"
23
24
  rubocop: true
24
25
  - name: 'Rails edge'
25
- ruby: 2.7
26
+ ruby: '3.0'
26
27
  gemfile: "Gemfile.rails-edge"
27
28
  edge: true
28
29
 
@@ -70,7 +71,7 @@ jobs:
70
71
  sudo apt-get -y install libmemcached-dev libmysqlclient-dev libpq-dev libsasl2-dev
71
72
  - uses: actions/checkout@v2
72
73
  - name: Set up Ruby
73
- uses: actions/setup-ruby@v1
74
+ uses: ruby/setup-ruby@v1
74
75
  with:
75
76
  ruby-version: ${{ matrix.entry.ruby }}
76
77
  - name: Install bundler and gems
@@ -0,0 +1,22 @@
1
+ name: Contributor License Agreement (CLA)
2
+
3
+ on:
4
+ pull_request_target:
5
+ types: [opened, synchronize]
6
+ issue_comment:
7
+ types: [created]
8
+
9
+ jobs:
10
+ cla:
11
+ runs-on: ubuntu-latest
12
+ if: |
13
+ (github.event.issue.pull_request
14
+ && !github.event.issue.pull_request.merged_at
15
+ && contains(github.event.comment.body, 'signed')
16
+ )
17
+ || (github.event.pull_request && !github.event.pull_request.merged)
18
+ steps:
19
+ - uses: Shopify/shopify-cla-action@v1
20
+ with:
21
+ github-token: ${{ secrets.GITHUB_TOKEN }}
22
+ cla-token: ${{ secrets.CLA_TOKEN }}
data/.rubocop.yml CHANGED
@@ -1,5 +1,9 @@
1
- inherit_from:
2
- - https://shopify.github.io/ruby-style-guide/rubocop.yml
1
+ inherit_gem:
2
+ rubocop-shopify: rubocop.yml
3
3
 
4
4
  AllCops:
5
- TargetRubyVersion: 2.4
5
+ TargetRubyVersion: 2.5
6
+ NewCops: disable
7
+
8
+ Layout/BeginEndAlignment:
9
+ EnforcedStyleAlignWith: start_of_line
data/.spin/bootstrap ADDED
@@ -0,0 +1,7 @@
1
+ #!/bin/bash
2
+
3
+ set -e
4
+
5
+ sudo apt-get install -y libsasl2-dev libpq-dev
6
+ mysql -u root -P "$MYSQL_PORT" -e 'create database identity_cache_test'
7
+ bundle install
data/.spin/svc.yml ADDED
@@ -0,0 +1,2 @@
1
+ - memcached
2
+ - mysql
data/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  # Identity Cache Changelog
2
2
 
3
+ ## 1.2.0
4
+
5
+ ### Fixes
6
+ - Fix mem_cache_store adapter with pool_size (#489)
7
+ - Fix dalli deprecation warning about requiring 'dalli/cas/client' (#511)
8
+ - Make transitionary method IdentityCache.with_fetch_read_only_records thread-safe (#503)
9
+
10
+ ### Features
11
+ - Add support for fill lock with lock wait to avoid thundering herd problem (#373)
12
+
3
13
  ## 1.1.0
4
14
 
5
15
  ### Fixes
data/Gemfile CHANGED
@@ -1,8 +1,18 @@
1
1
  # frozen_string_literal: true
2
- source 'https://rubygems.org'
2
+
3
+ source "https://rubygems.org"
3
4
  gemspec
4
5
 
5
- gem 'mysql2', '~> 0.5.3'
6
- gem 'pg', ">= 0.18", "< 2.0"
7
- gem 'rubocop', '~> 1.5'
8
- gem 'byebug', platform: :mri
6
+ gem "rubocop", "~> 1.5"
7
+
8
+ gem "rubocop-shopify", "~> 2.9.0", require: false
9
+
10
+ gem "mysql2", "~> 0.5.3", platform: :mri
11
+ gem "pg", ">= 0.18", "< 2.0", platform: :mri
12
+ gem "memcached", "~> 1.8.0", platform: :mri
13
+ gem "memcached_store", "~> 1.0.0", platform: :mri
14
+ gem "dalli", "~> 2.7.11"
15
+ gem "cityhash", "~> 0.6.0", platform: :mri
16
+
17
+ gem "byebug", platform: :mri
18
+ gem "stackprof", platform: :mri
data/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2013-2021 Shopify
1
+ Copyright (c) 2013-2022 Shopify
2
2
 
3
3
  MIT License
4
4
 
data/README.md CHANGED
@@ -1,5 +1,5 @@
1
1
  # IdentityCache
2
- [![Build Status](https://travis-ci.org/Shopify/identity_cache.svg?branch=master)](https://travis-ci.org/Shopify/identity_cache)
2
+ [![Build Status](https://github.com/Shopify/identity_cache/workflows/CI/badge.svg?branch=main)](https://github.com/Shopify/identity_cache/actions?query=branch%3Amain)
3
3
 
4
4
  Opt in read through ActiveRecord caching used in production and extracted from Shopify. IdentityCache lets you specify how you want to cache your model objects, at the model level, and adds a number of convenience methods for accessing those objects through the cache. Memcached is used as the backend cache store, and the database is only hit when a copy of the object cannot be found in Memcached.
5
5
 
@@ -29,7 +29,7 @@ Add the following to all your environment/*.rb files (production/development/tes
29
29
 
30
30
  ```ruby
31
31
  config.identity_cache_store = :mem_cache_store, "mem1.server.com", "mem2.server.com", {
32
- expires_in: 6.hours.to_i, # in case of network errors when sending a delete
32
+ expires_in: 6.hours.to_i, # in case of network errors when sending a cache invalidation
33
33
  failover: false, # avoids more cache consistency issues
34
34
  }
35
35
  ```
@@ -48,7 +48,7 @@ config.identity_cache_store = :memcached_store,
48
48
  Memcached.new(["mem1.server.com"],
49
49
  support_cas: true,
50
50
  auto_eject_hosts: false, # avoids more cache consistency issues
51
- ), { expires_in: 6.hours.to_i } # in case of network errors when sending a delete
51
+ ), { expires_in: 6.hours.to_i } # in case of network errors when sending a cache invalidation
52
52
  ```
53
53
 
54
54
  Add an initializer with this code:
@@ -254,14 +254,13 @@ Cache keys include a version number by default, specified in `IdentityCache::CAC
254
254
 
255
255
  ## Caveats
256
256
 
257
- A word of warning. Some versions of rails will silently rescue all exceptions in `after_commit` hooks. If an `after_commit` fails before the cache expiry `after_commit` the cache will not be expired and you will be left with stale data.
257
+ A word of warning. If an `after_commit` fails before the cache expiry `after_commit` the cache will not be expired and you will be left with stale data.
258
258
 
259
259
  Since everything is being marshalled and unmarshalled from Memcached changing Ruby or Rails versions could mean your objects cannot be unmarshalled from Memcached. There are a number of ways to get around this such as namespacing keys when you upgrade or rescuing marshal load errors and treating it as a cache miss. Just something to be aware of if you are using IdentityCache and upgrade Ruby or Rails.
260
260
 
261
- IdentityCache is also very much _opt-in_ by deliberate design. This means IdentityCache does not mess with the way normal Rails associations work, and including it in a model won't change any clients of that model until you switch them to use `fetch` instead of `find`. This is because there is no way IdentityCache is ever going to be 100% consistent. Processes die, exceptions happen, and network blips occur, which means there is a chance that some database transaction might commit but the corresponding memcached DEL operation does not make it. This means that you need to think carefully about when you use `fetch` and when you use `find`. For example, at Shopify, we never use any `fetch`ers on the path which moves money around, because IdentityCache could simply be wrong, and we want to charge people the right amount of money. We do however use the fetchers on performance critical paths where absolute correctness isn't the most important thing, and this is what IdentityCache is intended for.
261
+ IdentityCache is also very much _opt-in_ by deliberate design. This means IdentityCache does not mess with the way normal Rails associations work, and including it in a model won't change any clients of that model until you switch them to use `fetch` instead of `find`. This is because there is no way IdentityCache is ever going to be 100% consistent. Processes die, exceptions happen, and network blips occur, which means there is a chance that some database transaction might commit but the corresponding memcached cache invalidation operation does not make it. This means that you need to think carefully about when you use `fetch` and when you use `find`. For example, at Shopify, we never use any `fetch`ers on the path which moves money around, because IdentityCache could simply be wrong, and we want to charge people the right amount of money. We do however use the fetchers on performance critical paths where absolute correctness isn't the most important thing, and this is what IdentityCache is intended for.
262
262
 
263
263
  ## Notes
264
264
 
265
- - JRuby will not work with this current version, as we are using the memcached gem internally to interface with memcache.
266
265
  - See CHANGELOG.md for a list of changes to the library over time.
267
266
  - The library is MIT licensed and we welcome contributions. See CONTRIBUTING.md for more information.
data/Rakefile CHANGED
@@ -1,31 +1,32 @@
1
1
  #!/usr/bin/env rake
2
2
  # frozen_string_literal: true
3
- require 'bundler/gem_tasks'
4
3
 
5
- require 'rake/testtask'
6
- require 'rdoc/task'
4
+ require "bundler/gem_tasks"
7
5
 
8
- desc('Default: run tests and style checks.')
6
+ require "rake/testtask"
7
+ require "rdoc/task"
8
+
9
+ desc("Default: run tests and style checks.")
9
10
  task(default: [:test, :rubocop])
10
11
 
11
- desc('Test the identity_cache plugin.')
12
+ desc("Test the identity_cache plugin.")
12
13
  Rake::TestTask.new(:test) do |t|
13
- t.libs << 'lib'
14
- t.libs << 'test'
15
- t.pattern = 'test/**/*_test.rb'
14
+ t.libs << "lib"
15
+ t.libs << "test"
16
+ t.pattern = "test/**/*_test.rb"
16
17
  t.verbose = true
17
18
  end
18
19
 
19
20
  task :rubocop do
20
- require 'rubocop/rake_task'
21
+ require "rubocop/rake_task"
21
22
  RuboCop::RakeTask.new
22
23
  end
23
24
 
24
- desc('Update serialization format test fixture.')
25
+ desc("Update serialization format test fixture.")
25
26
  task :update_serialization_format do
26
- %w(mysql2 postgresql).each do |db|
27
+ ["mysql2", "postgresql"].each do |db|
27
28
  ENV["DB"] = db
28
- ruby './test/helpers/update_serialization_format.rb'
29
+ ruby "./test/helpers/update_serialization_format.rb"
29
30
  end
30
31
  end
31
32
 
data/dev.yml CHANGED
@@ -2,18 +2,16 @@ name: identity-cache
2
2
 
3
3
  up:
4
4
  - homebrew:
5
- - postgresql
6
5
  - mysql-client@5.7:
7
6
  or: [mysql@5.7]
8
7
  conflicts: [mysql-connector-c, mysql, mysql-client]
9
- - ruby: 2.6.5
10
- - railgun
8
+ - ruby: 2.7.2
9
+ - isogun
11
10
  - bundler
12
11
 
13
12
  env:
14
13
  RAILGUN_HOST: identity-cache.railgun
15
14
  MYSQL_HOST: identity-cache.railgun
16
- POSTGRES_HOST: identity-cache.railgun
17
15
  MEMCACHED_HOST: identity-cache.railgun
18
16
 
19
17
  commands:
@@ -1,8 +1,13 @@
1
- source 'https://rubygems.org'
2
- gemspec path: '..'
1
+ source "https://rubygems.org"
2
+ gemspec path: ".."
3
3
 
4
- gem 'activerecord'
5
- gem 'activesupport'
6
- gem "mysql2", ">= 0.4.4"
7
- gem "pg", ">= 0.18", "< 2.0"
8
- gem 'rubocop', '~> 1.5'
4
+ gem "rubocop", "~> 1.5"
5
+ gem "rubocop-shopify", "~> 2.9.0", require: false
6
+
7
+ gem "activerecord"
8
+ gem "activesupport"
9
+ gem "mysql2", "~> 0.5"
10
+ gem "pg", "~> 1.1"
11
+ gem "memcached_store"
12
+ gem "dalli"
13
+ gem "cityhash"
@@ -1,7 +1,12 @@
1
- source 'https://rubygems.org'
2
- gemspec path: '..'
1
+ source "https://rubygems.org"
2
+ gemspec path: ".."
3
3
 
4
- gem 'ar_transaction_changes', '~> 1.1.0'
5
- gem 'activerecord', '~> 5.2.0'
6
- gem 'mysql2', '~> 0.4.4'
7
- gem 'pg', '~> 0.18.0'
4
+ gem "ar_transaction_changes", "~> 1.1.0"
5
+
6
+ gem "activerecord", "~> 5.2.0"
7
+ gem "mysql2", "~> 0.4.4"
8
+ gem "pg", "~> 0.18.0"
9
+ gem "memcached", "~> 1.8.0"
10
+ gem "memcached_store", "~> 1.0.0"
11
+ gem "dalli", "~> 2.7.11"
12
+ gem "cityhash", "~> 0.6.0"
@@ -1,7 +1,10 @@
1
- source 'https://rubygems.org'
2
- gemspec path: '..'
1
+ source "https://rubygems.org"
2
+ gemspec path: ".."
3
3
 
4
- gem 'activerecord', github: 'rails/rails'
5
- gem 'activesupport', github: 'rails/rails'
6
- gem "mysql2", ">= 0.4.4"
4
+ gem "activerecord", github: "rails/rails", branch: "main"
5
+ gem "activesupport", github: "rails/rails", branch: "main"
6
+ gem "mysql2", "~> 0.5"
7
7
  gem "pg", "~> 1.1"
8
+ gem "memcached_store"
9
+ gem "dalli"
10
+ gem "cityhash"
@@ -1,6 +1,7 @@
1
1
  # -*- encoding: utf-8 -*-
2
2
  # frozen_string_literal: true
3
- require File.expand_path('../lib/identity_cache/version', __FILE__)
3
+
4
+ require File.expand_path("../lib/identity_cache/version", __FILE__)
4
5
 
5
6
  Gem::Specification.new do |gem|
6
7
  gem.authors = [
@@ -15,43 +16,30 @@ Gem::Specification.new do |gem|
15
16
  gem.email = ["gems@shopify.com"]
16
17
  gem.description = "Opt-in read through Active Record caching."
17
18
  gem.summary = "IdentityCache lets you specify how you want to cache your " \
18
- "model objects, at the model level, and adds a number of " \
19
- "convenience methods for accessing those objects through " \
20
- "the cache. Memcached is used as the backend cache store, " \
21
- "and the database is only hit when a copy of the object " \
22
- "cannot be found in Memcached."
19
+ "model objects, at the model level, and adds a number of " \
20
+ "convenience methods for accessing those objects through " \
21
+ "the cache. Memcached is used as the backend cache store, " \
22
+ "and the database is only hit when a copy of the object " \
23
+ "cannot be found in Memcached."
23
24
  gem.homepage = "https://github.com/Shopify/identity_cache"
24
25
 
25
26
  gem.files = Dir.chdir(File.expand_path(__dir__)) do
26
27
  %x(git ls-files -z).split("\x0").reject { |f| f.match(%r{^test/}) }
27
28
  end
28
29
  gem.executables = gem.files.grep(%r{^bin/}).map { |f| File.basename(f) }
29
- gem.test_files = gem.files.grep(%r{^test/})
30
30
  gem.name = "identity_cache"
31
31
  gem.require_paths = ["lib"]
32
32
  gem.version = IdentityCache::VERSION
33
33
 
34
- gem.required_ruby_version = '>= 2.5.0'
35
-
36
- gem.metadata['allowed_push_host'] = 'https://rubygems.org'
34
+ gem.required_ruby_version = ">= 2.5.0"
37
35
 
38
- gem.add_dependency('ar_transaction_changes', '~> 1.1')
39
- gem.add_dependency('activerecord', '>= 5.2')
36
+ gem.metadata["allowed_push_host"] = "https://rubygems.org"
40
37
 
41
- gem.add_development_dependency('memcached', '~> 1.8.0')
42
- gem.add_development_dependency('memcached_store', '~> 1.0.0')
43
- gem.add_development_dependency('dalli')
44
- gem.add_development_dependency('rake')
45
- gem.add_development_dependency('mocha', '0.14.0')
46
- gem.add_development_dependency('spy')
47
- gem.add_development_dependency('minitest', '>= 2.11.0')
38
+ gem.add_dependency("activerecord", ">= 5.2")
39
+ gem.add_dependency("ar_transaction_changes", "~> 1.1")
48
40
 
49
- if RUBY_PLATFORM == 'java'
50
- raise NotImplementedError
51
- else
52
- gem.add_development_dependency('cityhash', '0.6.0')
53
- gem.add_development_dependency('mysql2')
54
- gem.add_development_dependency('pg')
55
- gem.add_development_dependency('stackprof')
56
- end
41
+ gem.add_development_dependency("minitest", "~> 5.14")
42
+ gem.add_development_dependency("mocha", "~> 1.12")
43
+ gem.add_development_dependency("rake", "~> 13.0")
44
+ gem.add_development_dependency("spy", "~> 1.0")
57
45
  end
@@ -2,15 +2,10 @@
2
2
  name: identity-cache
3
3
 
4
4
  vm:
5
- image: /opt/dev/misc/railgun-images/default
6
5
  ip_address: 192.168.64.98
7
6
  memory: 1G
8
7
  cores: 2
9
8
 
10
- volumes:
11
- root: 1G
12
-
13
9
  services:
14
10
  - mysql
15
- - postgresql
16
11
  - memcached
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module IdentityCache
3
4
  module BelongsToCaching
4
5
  extend ActiveSupport::Concern