solid_cache 1.0.5 → 1.0.7

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9a27107ddce2c64a1884881e7cd3385533c32c1c13e2eee486a8c856de233c00
4
- data.tar.gz: a1eb035fd95c8cadfbcce86603ed4ec2dcc0a133c9516101baf0dca94f1baf7d
3
+ metadata.gz: 6c96752c871ba3eb453aca3f4c838b76afa0393486787109ef6e2f1ce1266a3d
4
+ data.tar.gz: 57cf865289e1c289ceed0b0103f234b61f5283c4a693ba213d60e4e81d21f9f2
5
5
  SHA512:
6
- metadata.gz: 8d0633c3f33b02df9f18e0f77b1ca6b200333bd6961d74af4462311a2ede3f264f6bf6f845da48ed187049affa9984f9aa43cfa39529ddaf192715c3940445bc
7
- data.tar.gz: 819e979a65563ec3ce5e16c09b8800a80e5764b63938f1e7e2b64d38ea453a06dec8f6ba63c8589cd4bbff05632b65d47c595de5c949997ef18f47305435cfd5
6
+ metadata.gz: be0dad91c5e93e775c0f7f528106b0d8ca69709d3d6d1fe4d95e834249abd267b86c5828d857e173fa1357da967362d290079df47f27bbcbcf56c3b7e9c80683
7
+ data.tar.gz: ebc91d895a6721f1a8acf2ea4ee611bc8619aa3a432be492f9a934c7eae5796c6539cba51ebba9396245b5a09ca197cfbe6c8abd9c10fda60957c0f477987d51
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Solid Cache
2
2
 
3
- Solid Cache is a database-backed Active Support cache store that let's you keep a much larger cache than is typically possible with traditional memory-only Redis or Memcached stores. This is thanks to the speed of modern SSD drives, which make the access-time penalty of using disk vs RAM insignificant for most caching purposes. Simply put, you're now usually better off keeping a huge cache on disk rather than a small cache in memory.
3
+ Solid Cache is a database-backed Active Support cache store that lets you keep a much larger cache than is typically possible with traditional memory-only Redis or Memcached stores. This is thanks to the speed of modern SSD drives, which make the access-time penalty of using disk vs RAM insignificant for most caching purposes. Simply put, you're now usually better off keeping a huge cache on disk rather than a small cache in memory.
4
4
 
5
5
  ## Installation
6
6
 
@@ -9,9 +9,9 @@ Solid Cache is configured by default in new Rails 8 applications. But if you're
9
9
  1. `bundle add solid_cache`
10
10
  2. `bin/rails solid_cache:install`
11
11
 
12
- This will configure Solid Cache as the production cache store, create `config/solid_cache.yml`, create `db/cache_schema.rb`, and attempt to alter `config/database.yml` to include the new cache database.
12
+ This will configure Solid Cache as the production cache store, create `config/cache.yml`, and create `db/cache_schema.rb`.
13
13
 
14
- If you've already made material changes to your `config/database.yml` file, the installer may not be able to add the cache db configuration directly. If you need to do it yourself, this is what it should look like:
14
+ You will then have to add the configuration for the cache database in `config/database.yml`. If you're using sqlite, it'll look like this:
15
15
 
16
16
  ```yaml
17
17
  production:
@@ -43,7 +43,7 @@ Then run `db:prepare` in production to ensure the database is created and the sc
43
43
 
44
44
  ## Configuration
45
45
 
46
- Configuration will be read from `config/solid_cache.yml`. You can change the location of the config file by setting the `SOLID_CACHE_CONFIG` env variable.
46
+ Configuration will be read from `config/cache.yml` or `config/solid_cache.yml`. You can change the location of the config file by setting the `SOLID_CACHE_CONFIG` env variable.
47
47
 
48
48
  The format of the file is:
49
49
 
@@ -69,6 +69,13 @@ production: &production
69
69
 
70
70
  For the full list of keys for `store_options` see [Cache configuration](#cache-configuration). Any options passed to the cache lookup will overwrite those specified here.
71
71
 
72
+ After running `solid_cache:install`, `environments/production.rb` will replace your cache store with Solid Cache, but you can also do this manually:
73
+
74
+ ```ruby
75
+ # config/environments/production.rb
76
+ config.cache_store = :solid_cache_store
77
+ ```
78
+
72
79
  ### Connection configuration
73
80
 
74
81
  You can set one of `database`, `databases` and `connects_to` in the config file. They will be used to configure the cache databases in `SolidCache::Record#connects_to`.
@@ -91,7 +98,7 @@ If none of these are set, Solid Cache will use the `ActiveRecord::Base` connecti
91
98
 
92
99
  ### Engine configuration
93
100
 
94
- There are three options that can be set on the engine:
101
+ There are five options that can be set on the engine:
95
102
 
96
103
  - `executor` - the [Rails executor](https://guides.rubyonrails.org/threading_and_code_execution.html#executor) used to wrap asynchronous operations, defaults to the app executor
97
104
  - `connects_to` - a custom connects to value for the abstract `SolidCache::Record` active record model. Required for sharding and/or using a separate cache database to the main app. This will overwrite any value set in `config/solid_cache.yml`
@@ -169,17 +176,17 @@ production:
169
176
  ```
170
177
 
171
178
  ```yaml
172
- # config/solid_cache.yml
179
+ # config/cache.yml
173
180
  production:
174
181
  databases: [cache_shard1, cache_shard2, cache_shard3]
175
182
  ```
176
183
 
177
184
  ## Enabling encryption
178
185
 
179
- To encrypt the cache values, you can add set the encrypt property.
186
+ To encrypt the cache values, you can add the encrypt property.
180
187
 
181
188
  ```yaml
182
- # config/solid_cache.yml
189
+ # config/cache.yml
183
190
  production:
184
191
  encrypt: true
185
192
  ```
@@ -189,7 +196,7 @@ or
189
196
  config.solid_cache.encrypt = true
190
197
  ```
191
198
 
192
- You will need to set up your application to (use Active Record Encryption)[https://guides.rubyonrails.org/active_record_encryption.html].
199
+ You will need to set up your application to [use Active Record Encryption](https://guides.rubyonrails.org/active_record_encryption.html).
193
200
 
194
201
  Solid Cache by default uses a custom encryptor and message serializer that are optimised for it.
195
202
 
@@ -260,7 +267,7 @@ This ensures that all the Rails versions dependencies are updated.
260
267
 
261
268
  ## Implementation
262
269
 
263
- Solid Cache is a FIFO (first in, first out) cache. While this is not as efficient as an LRU cache, it is mitigated by the longer cache lifespan.
270
+ Solid Cache is a FIFO (first in, first out) cache. While this is not as efficient as an LRU (least recently used) cache, it is mitigated by the longer cache lifespan.
264
271
 
265
272
  A FIFO cache is much easier to manage:
266
273
  1. We don't need to track when items are read.
data/Rakefile CHANGED
@@ -44,7 +44,7 @@ configs.each do |config|
44
44
  if config == :default
45
45
  sh("bin/rails test")
46
46
  else
47
- sh("SOLID_CACHE_CONFIG=config/solid_cache_#{config}.yml bin/rails test")
47
+ sh("SOLID_CACHE_CONFIG=config/cache_#{config}.yml bin/rails test")
48
48
  end
49
49
  end
50
50
  end
@@ -40,6 +40,8 @@ module SolidCache
40
40
  candidates.pluck(:id)
41
41
  else
42
42
  min_created_at = max_age.seconds.ago
43
+ # We don't have an index on created_at, but we can select
44
+ # the records by id and they'll be in created_at order.
43
45
  candidates.pluck(:id, :created_at)
44
46
  .filter_map { |id, created_at| id if created_at < min_created_at }
45
47
  end
@@ -14,6 +14,8 @@ module SolidCache
14
14
 
15
15
  KEY_HASH_ID_RANGE = -(2**63)..(2**63 - 1)
16
16
 
17
+ MULTI_BATCH_SIZE = 1000
18
+
17
19
  class << self
18
20
  def write(key, value)
19
21
  write_multi([ { key: key, value: value } ])
@@ -21,9 +23,11 @@ module SolidCache
21
23
 
22
24
  def write_multi(payloads)
23
25
  without_query_cache do
24
- upsert_all \
25
- add_key_hash_and_byte_size(payloads),
26
- unique_by: upsert_unique_by, on_duplicate: :update, update_only: [ :key, :value, :byte_size ]
26
+ payloads.each_slice(MULTI_BATCH_SIZE).each do |payload_batch|
27
+ upsert_all \
28
+ add_key_hash_and_byte_size(payload_batch),
29
+ unique_by: upsert_unique_by, on_duplicate: :update, update_only: [ :key, :value, :byte_size ]
30
+ end
27
31
  end
28
32
  end
29
33
 
@@ -33,9 +37,13 @@ module SolidCache
33
37
 
34
38
  def read_multi(keys)
35
39
  without_query_cache do
36
- query = Arel.sql(select_sql(keys), *key_hashes_for(keys))
40
+ {}.tap do |results|
41
+ keys.each_slice(MULTI_BATCH_SIZE).each do |keys_batch|
42
+ query = Arel.sql(select_sql(keys_batch), *key_hashes_for(keys_batch))
37
43
 
38
- connection.select_all(query, "SolidCache::Entry Load").cast_values(attribute_types).to_h
44
+ results.merge!(connection.select_all(query, "SolidCache::Entry Load").cast_values(attribute_types).to_h)
45
+ end
46
+ end
39
47
  end
40
48
  end
41
49
 
@@ -10,7 +10,20 @@ module SolidCache
10
10
 
11
11
  class << self
12
12
  def disable_instrumentation(&block)
13
- connection.with_instrumenter(NULL_INSTRUMENTER, &block)
13
+ with_instrumenter(NULL_INSTRUMENTER, &block)
14
+ end
15
+
16
+ def with_instrumenter(instrumenter, &block)
17
+ if connection.respond_to?(:with_instrumenter)
18
+ connection.with_instrumenter(instrumenter, &block)
19
+ else
20
+ begin
21
+ old_instrumenter, ActiveSupport::IsolatedExecutionState[:active_record_instrumenter] = ActiveSupport::IsolatedExecutionState[:active_record_instrumenter], instrumenter
22
+ block.call
23
+ ensure
24
+ ActiveSupport::IsolatedExecutionState[:active_record_instrumenter] = old_instrumenter
25
+ end
26
+ end
14
27
  end
15
28
 
16
29
  def with_shard(shard, &block)
@@ -3,70 +3,13 @@
3
3
  class SolidCache::InstallGenerator < Rails::Generators::Base
4
4
  source_root File.expand_path("templates", __dir__)
5
5
 
6
- def add_rails_cache
7
- gsub_file app_root.join("config/environments/production.rb"),
8
- /(# )?config\.cache_store = (:.*)/, "config.cache_store = :solid_cache_store"
9
- end
10
-
11
- def create_config_solid_cache_yml
12
- template "config/solid_cache.yml"
13
- end
14
-
15
- def add_cache_db_to_database_yml
16
- if app_is_using_sqlite?
17
- gsub_file database_yml, /production:\s*<<: \*default.*/m, sqlite_database_config_with_cache
18
- else
19
- gsub_file database_yml, /production:\s*<<: \*default.*/m, generic_database_config_with_cache
20
- end
21
- end
22
-
23
- def add_solid_cache_db_schema
6
+ def copy_files
7
+ template "config/cache.yml"
24
8
  template "db/cache_schema.rb"
25
9
  end
26
10
 
27
- private
28
- def app_root
29
- Pathname.new(destination_root)
30
- end
31
-
32
- def database_yml
33
- app_root.join("config/database.yml")
34
- end
35
-
36
- def app_is_using_sqlite?
37
- database_yml.read.match?(/production:.*sqlite3/m)
38
- end
39
-
40
- def sqlite_database_config_with_cache
41
- <<~YAML
42
- production:
43
- primary:
44
- <<: *default
45
- database: storage/production.sqlite3
46
- cache:
47
- <<: *default
48
- database: storage/production_cache.sqlite3
49
- migrations_paths: db/cache_migrate
50
- YAML
51
- end
52
-
53
- def app_name_from_production_database_name
54
- database_yml.read.scan(/database: (\w+)_production/).flatten.first
55
- end
56
-
57
- def generic_database_config_with_cache
58
- app_name = app_name_from_production_database_name
59
-
60
- <<~YAML
61
- production:
62
- primary: &production_primary
63
- <<: *default
64
- database: #{app_name}_production
65
- username: #{app_name}
66
- password: <%= ENV["#{app_name.upcase}_DATABASE_PASSWORD"] %>
67
- cache:
68
- <<: *production_primary
69
- database: #{app_name}_production_cache
70
- YAML
71
- end
11
+ def configure_cache_store_adapter
12
+ gsub_file Pathname.new(destination_root).join("config/environments/production.rb"),
13
+ /(# )?config\.cache_store = (:.*)/, "config.cache_store = :solid_cache_store"
14
+ end
72
15
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- ActiveRecord::Schema[7.1].define(version: 1) do
3
+ ActiveRecord::Schema[7.2].define(version: 1) do
4
4
  create_table "solid_cache_entries", force: :cascade do |t|
5
5
  t.binary "key", limit: 1024, null: false
6
6
  t.binary "value", limit: 536870912, null: false
@@ -35,7 +35,7 @@ module SolidCache
35
35
  @connects_to =
36
36
  case
37
37
  when database
38
- { database: { writing: database.to_sym } }
38
+ { shards: { database.to_sym => { writing: database.to_sym } } }
39
39
  when databases
40
40
  { shards: databases.map(&:to_sym).index_with { |database| { writing: database } } }
41
41
  when connects_to
@@ -10,13 +10,16 @@ module SolidCache
10
10
  config.solid_cache = ActiveSupport::OrderedOptions.new
11
11
 
12
12
  initializer "solid_cache.config", before: :initialize_cache do |app|
13
- app.paths.add "config/solid_cache", with: ENV["SOLID_CACHE_CONFIG"] || "config/solid_cache.yml"
13
+ config_paths = %w[config/cache config/solid_cache]
14
14
 
15
- options = {}
16
- if (config_path = Pathname.new(app.config.paths["config/solid_cache"].first)).exist?
17
- options = app.config_for(config_path).to_h.deep_symbolize_keys
15
+ config_paths.each do |path|
16
+ app.paths.add path, with: ENV["SOLID_CACHE_CONFIG"] || "#{path}.yml"
18
17
  end
19
18
 
19
+ config_pathname = config_paths.map { |path| Pathname.new(app.config.paths[path].first) }.find(&:exist?)
20
+
21
+ options = config_pathname ? app.config_for(config_pathname).to_h.deep_symbolize_keys : {}
22
+
20
23
  options[:connects_to] = config.solid_cache.connects_to if config.solid_cache.connects_to
21
24
  options[:size_estimate_samples] = config.solid_cache.size_estimate_samples if config.solid_cache.size_estimate_samples
22
25
  options[:encrypt] = config.solid_cache.encrypt if config.solid_cache.encrypt
@@ -34,26 +34,6 @@ module SolidCache
34
34
  end
35
35
  end
36
36
 
37
- def with_connection_for(key, async: false, &block)
38
- connections.with_connection_for(key) do
39
- execute(async, &block)
40
- end
41
- end
42
-
43
- def with_connection(name, async: false, &block)
44
- connections.with(name) do
45
- execute(async, &block)
46
- end
47
- end
48
-
49
- def group_by_connection(keys)
50
- connections.assign(keys)
51
- end
52
-
53
- def connection_names
54
- connections.names
55
- end
56
-
57
37
  def connections
58
38
  @connections ||= SolidCache::Connections.from_config(@shard_options)
59
39
  end
@@ -63,6 +43,26 @@ module SolidCache
63
43
  connections
64
44
  end
65
45
 
46
+ def with_connection_for(key, async: false, &block)
47
+ connections.with_connection_for(key) do
48
+ execute(async, &block)
49
+ end
50
+ end
51
+
52
+ def with_connection(name, async: false, &block)
53
+ connections.with(name) do
54
+ execute(async, &block)
55
+ end
56
+ end
57
+
58
+ def group_by_connection(keys)
59
+ connections.assign(keys)
60
+ end
61
+
62
+ def connection_names
63
+ connections.names
64
+ end
65
+
66
66
  def reading_key(key, failsafe:, failsafe_returning: nil, &block)
67
67
  failsafe(failsafe, returning: failsafe_returning) do
68
68
  with_connection_for(key, &block)
@@ -70,16 +70,15 @@ module SolidCache
70
70
  end
71
71
 
72
72
  def reading_keys(keys, failsafe:, failsafe_returning: nil)
73
- group_by_connection(keys).map do |connection, keys|
73
+ group_by_connection(keys).map do |connection, grouped_keys|
74
74
  failsafe(failsafe, returning: failsafe_returning) do
75
75
  with_connection(connection) do
76
- yield keys
76
+ yield grouped_keys
77
77
  end
78
78
  end
79
79
  end
80
80
  end
81
81
 
82
-
83
82
  def writing_key(key, failsafe:, failsafe_returning: nil, &block)
84
83
  failsafe(failsafe, returning: failsafe_returning) do
85
84
  with_connection_for(key, &block)
@@ -87,10 +86,10 @@ module SolidCache
87
86
  end
88
87
 
89
88
  def writing_keys(entries, failsafe:, failsafe_returning: nil)
90
- group_by_connection(entries).map do |connection, entries|
89
+ group_by_connection(entries).map do |connection, grouped_entries|
91
90
  failsafe(failsafe, returning: failsafe_returning) do
92
91
  with_connection(connection) do
93
- yield entries
92
+ yield grouped_entries
94
93
  end
95
94
  end
96
95
  end
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "concurrent/atomic/atomic_fixnum"
4
-
5
3
  module SolidCache
6
4
  class Store
7
5
  module Expiry
@@ -6,7 +6,6 @@ module SolidCache
6
6
  TRANSIENT_ACTIVE_RECORD_ERRORS = [
7
7
  ActiveRecord::AdapterTimeout,
8
8
  ActiveRecord::ConnectionNotEstablished,
9
- ActiveRecord::ConnectionTimeoutError,
10
9
  ActiveRecord::Deadlocked,
11
10
  ActiveRecord::LockWaitTimeout,
12
11
  ActiveRecord::QueryCanceled,
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SolidCache
4
- VERSION = "1.0.5"
4
+ VERSION = "1.0.7"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: solid_cache
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.5
4
+ version: 1.0.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Donal McBreen
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-09-05 00:00:00.000000000 Z
11
+ date: 2025-02-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -115,7 +115,7 @@ files:
115
115
  - lib/active_support/cache/solid_cache_store.rb
116
116
  - lib/generators/solid_cache/install/USAGE
117
117
  - lib/generators/solid_cache/install/install_generator.rb
118
- - lib/generators/solid_cache/install/templates/config/solid_cache.yml.tt
118
+ - lib/generators/solid_cache/install/templates/config/cache.yml.tt
119
119
  - lib/generators/solid_cache/install/templates/db/cache_schema.rb
120
120
  - lib/solid_cache.rb
121
121
  - lib/solid_cache/configuration.rb
@@ -158,7 +158,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
158
158
  - !ruby/object:Gem::Version
159
159
  version: '0'
160
160
  requirements: []
161
- rubygems_version: 3.5.17
161
+ rubygems_version: 3.5.22
162
162
  signing_key:
163
163
  specification_version: 4
164
164
  summary: A database backed ActiveSupport::Cache::Store
@@ -1,5 +1,4 @@
1
1
  default: &default
2
- database: <%= ENV.fetch("DATABASE", "cache") %>
3
2
  store_options:
4
3
  # Cap age of oldest cache entry to fulfill retention policies
5
4
  # max_age: <%%= 60.days.to_i %>
@@ -13,4 +12,5 @@ test:
13
12
  <<: *default
14
13
 
15
14
  production:
15
+ database: <%= ENV.fetch("DATABASE", "cache") %>
16
16
  <<: *default