identity_cache 1.0.0 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (61) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +76 -9
  3. data/.github/workflows/cla.yml +22 -0
  4. data/.gitignore +1 -0
  5. data/.rubocop.yml +7 -3
  6. data/.spin/bootstrap +7 -0
  7. data/.spin/svc.yml +2 -0
  8. data/CAVEATS.md +25 -0
  9. data/CHANGELOG.md +56 -22
  10. data/Gemfile +15 -5
  11. data/LICENSE +1 -1
  12. data/README.md +27 -8
  13. data/Rakefile +13 -12
  14. data/dev.yml +5 -4
  15. data/gemfiles/Gemfile.latest-release +12 -5
  16. data/gemfiles/Gemfile.min-supported +12 -0
  17. data/gemfiles/Gemfile.rails-edge +9 -5
  18. data/identity_cache.gemspec +15 -24
  19. data/{railgun.yml → isogun.yml} +0 -5
  20. data/lib/identity_cache/belongs_to_caching.rb +1 -0
  21. data/lib/identity_cache/cache_fetcher.rb +241 -16
  22. data/lib/identity_cache/cache_hash.rb +7 -6
  23. data/lib/identity_cache/cache_invalidation.rb +2 -1
  24. data/lib/identity_cache/cache_key_generation.rb +22 -19
  25. data/lib/identity_cache/cache_key_loader.rb +2 -2
  26. data/lib/identity_cache/cached/association.rb +2 -4
  27. data/lib/identity_cache/cached/attribute.rb +3 -3
  28. data/lib/identity_cache/cached/attribute_by_multi.rb +1 -1
  29. data/lib/identity_cache/cached/belongs_to.rb +24 -14
  30. data/lib/identity_cache/cached/embedded_fetching.rb +2 -0
  31. data/lib/identity_cache/cached/prefetcher.rb +12 -2
  32. data/lib/identity_cache/cached/primary_index.rb +3 -3
  33. data/lib/identity_cache/cached/recursive/association.rb +55 -12
  34. data/lib/identity_cache/cached/recursive/has_many.rb +1 -0
  35. data/lib/identity_cache/cached/recursive/has_one.rb +1 -0
  36. data/lib/identity_cache/cached/reference/association.rb +1 -0
  37. data/lib/identity_cache/cached/reference/has_many.rb +3 -2
  38. data/lib/identity_cache/cached/reference/has_one.rb +3 -2
  39. data/lib/identity_cache/cached.rb +1 -0
  40. data/lib/identity_cache/configuration_dsl.rb +1 -0
  41. data/lib/identity_cache/encoder.rb +2 -1
  42. data/lib/identity_cache/expiry_hook.rb +2 -1
  43. data/lib/identity_cache/fallback_fetcher.rb +6 -1
  44. data/lib/identity_cache/mem_cache_store_cas.rb +63 -0
  45. data/lib/identity_cache/memoized_cache_proxy.rb +33 -23
  46. data/lib/identity_cache/parent_model_expiration.rb +6 -3
  47. data/lib/identity_cache/query_api.rb +29 -66
  48. data/lib/identity_cache/railtie.rb +1 -0
  49. data/lib/identity_cache/should_use_cache.rb +1 -0
  50. data/lib/identity_cache/version.rb +2 -1
  51. data/lib/identity_cache/with_primary_index.rb +37 -10
  52. data/lib/identity_cache/without_primary_index.rb +7 -3
  53. data/lib/identity_cache.rb +66 -26
  54. data/performance/cache_runner.rb +12 -51
  55. data/performance/cpu.rb +7 -6
  56. data/performance/externals.rb +6 -5
  57. data/performance/profile.rb +7 -6
  58. metadata +32 -112
  59. data/.github/probots.yml +0 -2
  60. data/.travis.yml +0 -45
  61. data/gemfiles/Gemfile.rails52 +0 -6
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9236be16375bc831c91f69bcc86968cf036c361c173e79cfd8ea4eb43d707787
4
- data.tar.gz: 45e4acef2ef66d7262ce1b4c6d7b7fa33762c26fa99f31700cd44a4091dde8c4
3
+ metadata.gz: 561965856845fc5d5094581d735584924eda58557edfb3831c39bce2498635aa
4
+ data.tar.gz: 33a85428a3b2a298076d81a79b662b742c00a25971bf453f8f57eeab7cba2681
5
5
  SHA512:
6
- metadata.gz: 7f9c79056ccbb938299b3fb6fd3e62af44f9117140df1b714a758be2aa4d864ffb1df589ba0a6220786db3bdb005326e794ea9340629146e478ffb2f3a3574cf
7
- data.tar.gz: abc0e209fe35c8fc5ed6e470221a3e86cdeca23e81fc5c294d817cf3011c9ccf3a8dbf58096414c84be4b6b1e40ce4816b36703ed2b72da7dbafa4a78235e51d
6
+ metadata.gz: 6a8c691d8883acfd69f11724dfff13a3ef4858a2abd2ca658659981f1d08b36dea244100c4030b8ef5d39ac8509e639fea6cd02127e09d157be636fcc2bee77a
7
+ data.tar.gz: e357c3a4f02a288a5ddb3290cbd11176937821bb3c9052fb318d9139ac253650f7018a8249b2ebc9f115015a6434baf7cb1b7e76bfb0dbf1e98ee745e3e95ed7
@@ -1,26 +1,93 @@
1
1
  name: CI
2
2
 
3
- on: [push]
3
+ on:
4
+ push: {}
5
+ pull_request:
6
+ types: [opened, synchronize]
4
7
 
5
8
  jobs:
6
9
  build:
10
+ if: github.event_name == 'push' || github.event.pull_request.head.repo.owner.login != 'Shopify'
7
11
 
8
12
  runs-on: ubuntu-latest
9
13
 
14
+ strategy:
15
+ fail-fast: false
16
+ matrix:
17
+ entry:
18
+ - name: 'Minimum supported'
19
+ ruby: '2.5'
20
+ gemfile: "Gemfile.min-supported"
21
+ - name: 'Latest released & run rubocop'
22
+ ruby: '3.0'
23
+ gemfile: "Gemfile.latest-release"
24
+ rubocop: true
25
+ - name: 'Rails edge'
26
+ ruby: '3.0'
27
+ gemfile: "Gemfile.rails-edge"
28
+ edge: true
29
+
30
+ name: ${{ matrix.entry.name }}
31
+
32
+ continue-on-error: ${{ matrix.entry.edge || false }}
33
+
34
+ env:
35
+ BUNDLE_GEMFILE: gemfiles/${{ matrix.entry.gemfile }}
36
+
37
+ services:
38
+ memcached:
39
+ image: memcached
40
+ ports:
41
+ - 11211:11211
42
+ mysql:
43
+ image: mysql:5.7
44
+ env:
45
+ MYSQL_ALLOW_EMPTY_PASSWORD: yes
46
+ MYSQL_DATABASE: identity_cache_test
47
+ options: >-
48
+ --health-cmd="mysqladmin ping"
49
+ --health-interval=10s
50
+ --health-timeout=5s
51
+ --health-retries=5
52
+ ports:
53
+ - 3306:3306
54
+ postgres:
55
+ image: postgres
56
+ env:
57
+ POSTGRES_PASSWORD: postgres
58
+ POSTGRES_DB: identity_cache_test
59
+ options: >-
60
+ --health-cmd pg_isready
61
+ --health-interval 10s
62
+ --health-timeout 5s
63
+ --health-retries 5
64
+ ports:
65
+ - 5432:5432
66
+
10
67
  steps:
11
- - uses: actions/checkout@v2
12
- - name: Set up Ruby 2.6
13
- uses: actions/setup-ruby@v1
14
- with:
15
- ruby-version: 2.6.x
16
68
  - name: Install required packages
17
69
  run: |
18
70
  sudo apt-get update
19
- sudo apt-get -y install libmysqlclient-dev libpq-dev libsasl2-dev
71
+ sudo apt-get -y install libmemcached-dev libmysqlclient-dev libpq-dev libsasl2-dev
72
+ - uses: actions/checkout@v2
73
+ - name: Set up Ruby
74
+ uses: ruby/setup-ruby@v1
75
+ with:
76
+ ruby-version: ${{ matrix.entry.ruby }}
20
77
  - name: Install bundler and gems
21
78
  run: |
22
79
  gem install bundler
23
80
  bundle install --jobs 4 --retry 3
81
+ - name: Test with mysql
82
+ env:
83
+ DB: mysql2
84
+ run: bundle exec rake test
85
+ - name: Test with postgres and memcached_store
86
+ env:
87
+ DB: postgresql
88
+ POSTGRES_PASSWORD: postgres
89
+ ADAPTER: memcached
90
+ run: bundle exec rake test
24
91
  - name: Run rubocop
25
- run: |
26
- bundle exec rubocop
92
+ if: matrix.entry.rubocop
93
+ run: bundle exec rubocop
@@ -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/.gitignore CHANGED
@@ -16,3 +16,4 @@ test/tmp
16
16
  test/version_tmp
17
17
  tmp
18
18
  .rubocop-http*
19
+ .byebug_history
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.6
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/CAVEATS.md ADDED
@@ -0,0 +1,25 @@
1
+ While IdentityCache allows to massively scale access to ActiveRecord objects, it's important to note what problems might arise with it over time and what are things that it's not solving in the current design.
2
+
3
+ ## Large blobs
4
+
5
+ For [god objects](https://en.wikipedia.org/wiki/God_object) (like the Shop model at Shopify), it’s typical to have many associations. It’s appealing for developers to embed many of those associations into the god model’s IDC blob, such as `cache_has_many :images, embed: true`. As a side effect, over time, the god model’s IDC blob may grow very large. It gets even worse when some of the embedded records get larger themselves, and the total IDC blob gets into as much as a megabyte in size. This puts pressure on both the application, memcached, and the network fibre because this large blob needs to be serialized and unserialized each time and transferred over the network.
6
+
7
+ If the cache blob gets larger than the maximum cache value in memcached, then the cache fill would always fail, causing it to have to load all that data from the database each time.
8
+
9
+ ### Overfetching
10
+
11
+ There's currently no way to retrieve/fill the cache with a subset of a model's associations. So, for models with many embedded associations, every `Model.fetch` would result in fetching all of the blob over the network and unserializing all embedded records, even if the client has only accessed one of those associations. That's a lot of useless CPU cycles and IO operations that happen on each access. It will also increase the network and memory bandwidth used on both the client and the server.
12
+
13
+ Another possible issue is that cache invalidations for a model can be amplified by the number of models that embed them. This can further aggravate the large cache blob problem by embedding an association that gets invalidated more frequently.
14
+
15
+ ## Hot keys
16
+
17
+ To scale out memcache, it’s typical to arrange memcached instances in rings. Most memcache clients/proxies use [consistent hashing](https://github.com/facebook/mcrouter/wiki/Pools#hash-functions) to route the operation to one of the instances in the ring. This means that an IDC entry will always end up in the same memcached instance.
18
+
19
+ If the key associated with a record is on a hot codepath, that memcached instance will possibly experience saturation on the network and the number of bytes that it can send out. In cloud environments, this is a limit that is easy to hit. This especially aggravates the server-side large blob problem.
20
+
21
+ ## Thundering herd
22
+
23
+ When a record gets updated, its IDC entry in memcache is expired, and it only gets populated the next time it is accessed. When many clients request the same key concurrently (e.g. on a hot codepath), they all find out that the IDC entry is missing at the same, and hence they will all go to the database to fill the IDC key at the same time. The massive number of clients hitting the DB will create a [thundering herd problem](https://en.wikipedia.org/wiki/Thundering_herd_problem) and overload the DB.
24
+
25
+ Check out https://github.com/Shopify/identity_cache/pull/373 for more context and for the proposed solution for this problem.
data/CHANGELOG.md CHANGED
@@ -1,6 +1,40 @@
1
- # IdentityCache changelog
1
+ # Identity Cache Changelog
2
2
 
3
- #### 1.0.0
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
+
13
+ ## 1.1.0
14
+
15
+ ### Fixes
16
+ - Fix double debug logging of cache hits and misses (#474)
17
+ - Fix a Rails 6.1 deprecation warning for Rails 7.0 compatibility (#482)
18
+ - Recursively install parent expiry hooks when expiring parent caches (#476)
19
+ - Expire caches before other `after_commit` callbacks (#471)
20
+ - Avoid unnecessary record cache expiry on save with no DB update (#464)
21
+ - Fix an Active Record deprecation warning by not using `Connection#type_cast` (#459)
22
+ - Fix broken `prefetch_associations` of a polymorphic `cache_belongs_to` (#461)
23
+ - Fix `should_use_cache?` check to avoid calling it on the wrong class (#454)
24
+ - Fix fetch `has_many` embedded association on record after adding to it (#449)
25
+
26
+ ### Features
27
+ - Support multiple databases and transactional tests in `IdentityCache.should_use_cache?` (#293)
28
+ - Add support for the default `MemCacheStore` from `ActiveSupport` (#465)
29
+
30
+ ### Breaking Changes
31
+ - Drop ruby 2.4 support, since it is no longer supported upstream (#468)
32
+
33
+ ## 1.0.1
34
+
35
+ - Fix expiry of cache_has_one association with scope and `embed: :id` (#442)
36
+
37
+ ## 1.0.0
4
38
 
5
39
  - Remove inverse_name option. Specify inverse_of on the Active Record association instead. (#439)
6
40
  - Bump the minimum Active Record version to 5.2 (#438)
@@ -21,16 +55,16 @@
21
55
  - Remove deprecated `never_set_inverse_association` option (#319)
22
56
  - Lazy load associated classes (#306)
23
57
 
24
- #### 0.5.1
58
+ ## 0.5.1
25
59
 
26
60
  - Fix bug in prefetch_associations for cache_has_one associations that may be nil
27
61
 
28
- #### 0.5.0
62
+ ## 0.5.0
29
63
 
30
64
  - `never_set_inverse_association` and `fetch_read_only_records` are now `true` by default (#315)
31
65
  - Store the class name instead of the class itself (#311)
32
66
 
33
- #### 0.4.1
67
+ ## 0.4.1
34
68
 
35
69
  - Deprecated embedded associations on models that don't use IDC (#305)
36
70
  - Remove a respond_to? check that hides mistakes in includes hash (#307)
@@ -41,11 +75,11 @@
41
75
  - Clone instead of dup record when readonlyifying fetched records (#292)
42
76
  - Consistently store the array for cached has many associations (#288)
43
77
 
44
- #### 0.4.0
78
+ ## 0.4.0
45
79
 
46
80
  - Return an array from fetched association to prevent chaining. Up to now, a relation was returned by default. (#287)
47
81
 
48
- #### 0.3.2
82
+ ## 0.3.2
49
83
 
50
84
  - Deprecate returning non read-only records when cache is used. Set IdentityCache.fetch_readonly_records to true to avoid this. (#282)
51
85
  - Use loaded association first when fetching a cache_has_many id embedded association (#280)
@@ -53,11 +87,11 @@
53
87
  - Fetch association returns relation or array depending on the configuration. It was only returning a relation for cache_has_many fetch association methods. (#276)
54
88
  - Stop sharing the same attributes hash between the fetched record and the memoized cache, which could interfere with dirty tracking (#267)
55
89
 
56
- #### 0.3.1
90
+ ## 0.3.1
57
91
 
58
92
  - Fix cache_index for non-id primary key
59
93
 
60
- #### 0.3.0
94
+ ## 0.3.0
61
95
 
62
96
  - Add support for includes option on cache_index and fetch_by_id
63
97
  - Use ActiveRecord instantiate
@@ -70,37 +104,37 @@
70
104
  - Fix cache_belongs_to on polymorphic assocations.
71
105
  - Fetching a cache_belongs_to association no longer loads the belongs_to association
72
106
 
73
- #### 0.2.5
107
+ ## 0.2.5
74
108
 
75
109
  - Fixed support for namespaced model classes
76
110
  - Added some deduplication for parent cache expiry
77
111
  - Fixed some deprecation warnings in rails 4.2
78
112
 
79
- #### 0.2.4
113
+ ## 0.2.4
80
114
 
81
115
  - Refactoring, documentation and test changes
82
116
 
83
- #### 0.2.3
117
+ ## 0.2.3
84
118
 
85
119
  - PostgreSQL support
86
120
  - Rails 4.2 compatibility
87
121
  - Fix: Don't connect to database when calling `IdentityCache.should_use_cache?`
88
122
  - Fix: Fix invalid parent cache invalidation if object is embedded in different parents
89
123
 
90
- #### 0.2.2
124
+ ## 0.2.2
91
125
 
92
126
  - Change: memcached is no longer a runtime dependency
93
127
  - Use cache for read-only models.
94
128
 
95
- #### 0.2.1
129
+ ## 0.2.1
96
130
 
97
131
  - Add a fallback backend using local memory.
98
132
 
99
- #### 0.2.0
133
+ ## 0.2.0
100
134
 
101
135
  - Memcache CAS support
102
136
 
103
- #### 0.1.0
137
+ ## 0.1.0
104
138
 
105
139
  - Backwards incompatible change: Stop expiring cache on after_touch callback.
106
140
  - Change: fetch_multi accepts an array of keys as argument
@@ -111,29 +145,29 @@
111
145
  - Fix: Avoid unused preload on fetch_multi with :includes option for cache miss
112
146
  - Fix: reload will invalidate the local instance cache
113
147
 
114
- #### 0.0.7
148
+ ## 0.0.7
115
149
 
116
150
  - Add support for non-integer primary keys
117
151
  - Fix: Not implemented error for cache_has_one with embed: false
118
152
  - Fix: cache key to change when adding a cache_has_many association with :embed => false
119
153
  - Fix: Compatibility rails 4.1 for `quote_value`, which needs default column.
120
154
 
121
- #### 0.0.6
155
+ ## 0.0.6
122
156
 
123
157
  - Fix: bug where previously nil-cached attribute caches weren't expired on record creation
124
158
  - Fix: cache key to not change when adding a non-embedded association.
125
159
  - Perf: Rails 4 Only create `CollectionProxy` when using it
126
160
 
127
- #### 0.0.5
161
+ ## 0.0.5
128
162
 
129
163
 
130
- #### 0.0.4
164
+ ## 0.0.4
131
165
 
132
166
  - Fix: only marshal attributes, embedded associations and normalized association IDs
133
167
  - Add cache version number to cache keys
134
168
  - Add test case to ensure version number is updated when the marshalled format changes
135
169
 
136
- #### 0.0.3
170
+ ## 0.0.3
137
171
 
138
172
  - Fix: memoization for multi hits actually work
139
173
  - Fix: quotes `SELECT` projection elements on cache misses
@@ -141,7 +175,7 @@
141
175
  - Fix: table names are not hardcoded anymore
142
176
  - Logger now differentiates memoized vs non memoized hits
143
177
 
144
- #### 0.0.2
178
+ ## 0.0.2
145
179
 
146
180
  - Fix: Existent embedded entries will no longer raise when `ActiveModel::MissingAttributeError` when accessing a newly created attribute.
147
181
  - Fix: Do not marshal raw ActiveRecord associations
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.21.0'
7
- gem 'rubocop'
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 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
 
@@ -12,7 +12,10 @@ Add this line to your application's Gemfile:
12
12
  ```ruby
13
13
  gem 'identity_cache'
14
14
  gem 'cityhash' # optional, for faster hashing (C-Ruby only)
15
- gem 'memcached_store' # for CAS support, needed for cache consistency
15
+
16
+ gem 'dalli' # To use :mem_cache_store
17
+ # alternatively
18
+ gem 'memcached_store' # to use the old libmemcached based client
16
19
  ```
17
20
 
18
21
  And then execute:
@@ -22,13 +25,30 @@ And then execute:
22
25
 
23
26
  Add the following to all your environment/*.rb files (production/development/test):
24
27
 
28
+ ### If you use Dalli (recommended)
29
+
30
+ ```ruby
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 cache invalidation
33
+ failover: false, # avoids more cache consistency issues
34
+ }
35
+ ```
36
+
37
+ Add an initializer with this code:
38
+
39
+ ```ruby
40
+ IdentityCache.cache_backend = ActiveSupport::Cache.lookup_store(*Rails.configuration.identity_cache_store)
41
+ ```
42
+
43
+
44
+ ### If you use Memcached (old client)
45
+
25
46
  ```ruby
26
47
  config.identity_cache_store = :memcached_store,
27
- Memcached::Rails.new(servers: ["mem1.server.com"],
48
+ Memcached.new(["mem1.server.com"],
28
49
  support_cas: true,
29
50
  auto_eject_hosts: false, # avoids more cache consistency issues
30
- expires_in: 6.hours.to_i, # in case of network errors when sending a delete
31
- )
51
+ ), { expires_in: 6.hours.to_i } # in case of network errors when sending a cache invalidation
32
52
  ```
33
53
 
34
54
  Add an initializer with this code:
@@ -234,14 +254,13 @@ Cache keys include a version number by default, specified in `IdentityCache::CAC
234
254
 
235
255
  ## Caveats
236
256
 
237
- 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.
238
258
 
239
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.
240
260
 
241
- 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, execeptions 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.
242
262
 
243
263
  ## Notes
244
264
 
245
- - JRuby will not work with this current version, as we are using the memcached gem internally to interface with memcache.
246
265
  - See CHANGELOG.md for a list of changes to the library over time.
247
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,15 +2,16 @@ name: identity-cache
2
2
 
3
3
  up:
4
4
  - homebrew:
5
- - postgresql
6
- - ruby: 2.4.1
7
- - railgun
5
+ - mysql-client@5.7:
6
+ or: [mysql@5.7]
7
+ conflicts: [mysql-connector-c, mysql, mysql-client]
8
+ - ruby: 2.7.2
9
+ - isogun
8
10
  - bundler
9
11
 
10
12
  env:
11
13
  RAILGUN_HOST: identity-cache.railgun
12
14
  MYSQL_HOST: identity-cache.railgun
13
- POSTGRES_HOST: identity-cache.railgun
14
15
  MEMCACHED_HOST: identity-cache.railgun
15
16
 
16
17
  commands:
@@ -1,6 +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"
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"
@@ -0,0 +1,12 @@
1
+ source "https://rubygems.org"
2
+ gemspec path: ".."
3
+
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,6 +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
+ 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,40 +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.4.0'
34
+ gem.required_ruby_version = ">= 2.5.0"
35
35
 
36
- gem.add_dependency('ar_transaction_changes', '~> 1.0')
37
- gem.add_dependency('activerecord', '>= 5.2')
36
+ gem.metadata["allowed_push_host"] = "https://rubygems.org"
38
37
 
39
- gem.add_development_dependency('memcached', '~> 1.8.0')
40
- gem.add_development_dependency('memcached_store', '~> 1.0.0')
41
- gem.add_development_dependency('rake')
42
- gem.add_development_dependency('mocha', '0.14.0')
43
- gem.add_development_dependency('spy')
44
- gem.add_development_dependency('minitest', '>= 2.11.0')
38
+ gem.add_dependency("activerecord", ">= 5.2")
39
+ gem.add_dependency("ar_transaction_changes", "~> 1.1")
45
40
 
46
- if RUBY_PLATFORM == 'java'
47
- raise NotImplementedError
48
- else
49
- gem.add_development_dependency('cityhash', '0.6.0')
50
- gem.add_development_dependency('mysql2')
51
- gem.add_development_dependency('pg', '~> 0.18')
52
- gem.add_development_dependency('stackprof')
53
- 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")
54
45
  end