solid_cache 1.0.5 → 1.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +17 -10
- data/Rakefile +1 -1
- data/app/models/solid_cache/entry/expiration.rb +2 -0
- data/app/models/solid_cache/entry.rb +13 -5
- data/app/models/solid_cache/record.rb +14 -1
- data/lib/generators/solid_cache/install/install_generator.rb +6 -63
- data/lib/generators/solid_cache/install/templates/db/cache_schema.rb +1 -1
- data/lib/solid_cache/configuration.rb +1 -1
- data/lib/solid_cache/engine.rb +7 -4
- data/lib/solid_cache/store/connections.rb +24 -25
- data/lib/solid_cache/store/expiry.rb +0 -2
- data/lib/solid_cache/store/failsafe.rb +0 -1
- data/lib/solid_cache/version.rb +1 -1
- metadata +4 -4
- data/lib/generators/solid_cache/install/templates/config/{solid_cache.yml.tt → cache.yml.tt} +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6c96752c871ba3eb453aca3f4c838b76afa0393486787109ef6e2f1ce1266a3d
|
4
|
+
data.tar.gz: 57cf865289e1c289ceed0b0103f234b61f5283c4a693ba213d60e4e81d21f9f2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
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/
|
12
|
+
This will configure Solid Cache as the production cache store, create `config/cache.yml`, and create `db/cache_schema.rb`.
|
13
13
|
|
14
|
-
|
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
|
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/
|
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
|
186
|
+
To encrypt the cache values, you can add the encrypt property.
|
180
187
|
|
181
188
|
```yaml
|
182
|
-
# config/
|
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
|
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
@@ -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
|
-
|
25
|
-
|
26
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
7
|
-
|
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
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
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.
|
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
|
-
{
|
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
|
data/lib/solid_cache/engine.rb
CHANGED
@@ -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
|
-
|
13
|
+
config_paths = %w[config/cache config/solid_cache]
|
14
14
|
|
15
|
-
|
16
|
-
|
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,
|
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
|
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,
|
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
|
92
|
+
yield grouped_entries
|
94
93
|
end
|
95
94
|
end
|
96
95
|
end
|
data/lib/solid_cache/version.rb
CHANGED
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.
|
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:
|
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/
|
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.
|
161
|
+
rubygems_version: 3.5.22
|
162
162
|
signing_key:
|
163
163
|
specification_version: 4
|
164
164
|
summary: A database backed ActiveSupport::Cache::Store
|
data/lib/generators/solid_cache/install/templates/config/{solid_cache.yml.tt → cache.yml.tt}
RENAMED
@@ -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
|