solid_cache 1.0.2 → 1.0.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +30 -104
- data/lib/generators/solid_cache/install/install_generator.rb +57 -9
- data/lib/generators/solid_cache/install/templates/config/solid_cache.yml.tt +2 -0
- data/lib/generators/solid_cache/install/templates/db/cache_schema.rb +14 -0
- data/lib/solid_cache/store/failsafe.rb +11 -1
- data/lib/solid_cache/version.rb +1 -1
- metadata +4 -4
- data/db/migrate/20240820123641_create_solid_cache_entries.rb +0 -29
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 557f25039474c31f96924767eaa99756e7171355f531239fb8452770d7ceb8fd
|
4
|
+
data.tar.gz: 2b719db3b62c8b2711961e4ab5ce7a9543fdb03f33739a2e48ead12c80af284a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 25187adeb93d8e1edfdfc4e754a9c064b61e128a3d5ca96a3c7b8d523b61aca3cb689878be0079c6cd387baf91cd5de8c57ae331a6efa82d087fe0123587e1bd
|
7
|
+
data.tar.gz: 55ed9ccacea8c5de554147d838a6ccb4c64970d1c8479c226114179bfd93e2680f2b1d77196b2b86393047bd2052e11df44310c4f24b6002735ad5bba5c0dcd3
|
data/README.md
CHANGED
@@ -1,105 +1,18 @@
|
|
1
1
|
# Solid Cache
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
Solid Cache is a database-backed Active Support cache store implementation.
|
6
|
-
|
7
|
-
Using SQL databases backed by SSDs we can have caches that are much larger and cheaper than traditional memory-only Redis or Memcached backed caches.
|
8
|
-
|
9
|
-
## Introduction
|
10
|
-
|
11
|
-
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.
|
12
|
-
|
13
|
-
A FIFO cache is much easier to manage:
|
14
|
-
1. We don't need to track when items are read.
|
15
|
-
2. We can estimate and control the cache size by comparing the maximum and minimum IDs.
|
16
|
-
3. By deleting from one end of the table and adding at the other end we can avoid fragmentation (on MySQL at least).
|
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.
|
17
4
|
|
18
5
|
## Installation
|
19
|
-
Add this line to your application's `Gemfile`:
|
20
|
-
|
21
|
-
```ruby
|
22
|
-
gem "solid_cache"
|
23
|
-
```
|
24
|
-
|
25
|
-
And then execute:
|
26
|
-
```bash
|
27
|
-
$ bundle
|
28
|
-
```
|
29
|
-
|
30
|
-
Or install it yourself as:
|
31
|
-
```bash
|
32
|
-
$ gem install solid_cache
|
33
|
-
```
|
34
6
|
|
35
|
-
|
7
|
+
Solid Cache is configured by default in new Rails 8 applications. But if you're running an earlier version, you can add it manually following these steps:
|
36
8
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
You can use the primary database for your cache like this:
|
41
|
-
|
42
|
-
```yaml
|
43
|
-
# config/database.yml
|
44
|
-
production:
|
45
|
-
primary: &production_primary
|
46
|
-
...
|
47
|
-
cache:
|
48
|
-
<<: *production_primary
|
49
|
-
```
|
50
|
-
|
51
|
-
Or a separate database like this:
|
52
|
-
|
53
|
-
```yaml
|
54
|
-
production:
|
55
|
-
primary:
|
56
|
-
...
|
57
|
-
cache:
|
58
|
-
database: cache_development
|
59
|
-
host: 127.0.0.1
|
60
|
-
migrations_paths: "db/cache/migrate"
|
61
|
-
```
|
9
|
+
1. `bundle add solid_cache`
|
10
|
+
2. `bin/rails solid_cache:install`
|
11
|
+
3. `bin/rails db:migrate`
|
62
12
|
|
63
|
-
|
13
|
+
This will configure Solid Queue as the production cache store, create `config/solid_cache.yml`, and alter `config/database.yml` to include the new cache database.
|
64
14
|
|
65
|
-
|
66
|
-
|
67
|
-
```bash
|
68
|
-
# If using the primary database
|
69
|
-
$ bin/rails generate solid_cache:install
|
70
|
-
|
71
|
-
# Or if using a dedicated database
|
72
|
-
$ DATABASE=cache bin/rails generate solid_cache:install
|
73
|
-
```
|
74
|
-
|
75
|
-
This will set solid_cache as the cache store in production, and will copy the optional configuration file and the required migration over to your app.
|
76
|
-
|
77
|
-
Alternatively, you can add only the migration to your app:
|
78
|
-
|
79
|
-
```bash
|
80
|
-
# If using the primary database
|
81
|
-
$ bin/rails generate solid_cache:install:migrations
|
82
|
-
|
83
|
-
# Or if using a dedicated database
|
84
|
-
$ DATABASE=cache bin/rails generate solid_cache:install:migrations
|
85
|
-
```
|
86
|
-
|
87
|
-
And set Solid Cache as your application's cache store backend manually, in your environment config:
|
88
|
-
|
89
|
-
```ruby
|
90
|
-
# config/environments/production.rb
|
91
|
-
config.cache_store = :solid_cache_store
|
92
|
-
```
|
93
|
-
|
94
|
-
#### Run migrations
|
95
|
-
|
96
|
-
Finally, you need to run the migrations:
|
97
|
-
|
98
|
-
```bash
|
99
|
-
$ bin/rails db:migrate
|
100
|
-
```
|
101
|
-
|
102
|
-
### Configuration
|
15
|
+
## Configuration
|
103
16
|
|
104
17
|
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.
|
105
18
|
|
@@ -127,7 +40,7 @@ production: &production
|
|
127
40
|
|
128
41
|
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.
|
129
42
|
|
130
|
-
|
43
|
+
### Connection configuration
|
131
44
|
|
132
45
|
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`.
|
133
46
|
|
@@ -145,10 +58,9 @@ SolidCache::Record.connects_to shards: { cache_db1: { writing: :cache_db1 }, ca
|
|
145
58
|
|
146
59
|
If `connects_to` is set, it will be passed directly.
|
147
60
|
|
148
|
-
If none of these are set, Solid Cache will use the `ActiveRecord::Base` connection pool. This means that cache reads and writes will be part of any wrapping
|
149
|
-
database transaction.
|
61
|
+
If none of these are set, Solid Cache will use the `ActiveRecord::Base` connection pool. This means that cache reads and writes will be part of any wrapping database transaction.
|
150
62
|
|
151
|
-
|
63
|
+
### Engine configuration
|
152
64
|
|
153
65
|
There are three options that can be set on the engine:
|
154
66
|
|
@@ -166,11 +78,11 @@ Rails.application.configure do
|
|
166
78
|
end
|
167
79
|
```
|
168
80
|
|
169
|
-
|
81
|
+
### Cache configuration
|
170
82
|
|
171
83
|
Solid Cache supports these options in addition to the standard `ActiveSupport::Cache::Store` options:
|
172
84
|
|
173
|
-
- `error_handler` - a Proc to call to handle any
|
85
|
+
- `error_handler` - a Proc to call to handle any transient database errors that are raised (default: log errors as warnings)
|
174
86
|
- `expiry_batch_size` - the batch size to use when deleting old records (default: `100`)
|
175
87
|
- `expiry_method` - what expiry method to use `thread` or `job` (default: `thread`)
|
176
88
|
- `expiry_queue` - which queue to add expiry jobs to (default: `default`)
|
@@ -186,7 +98,7 @@ Solid Cache supports these options in addition to the standard `ActiveSupport::C
|
|
186
98
|
|
187
99
|
For more information on cache clusters, see [Sharding the cache](#sharding-the-cache)
|
188
100
|
|
189
|
-
|
101
|
+
## Cache expiry
|
190
102
|
|
191
103
|
Solid Cache tracks writes to the cache. For every write it increments a counter by 1. Once the counter reaches 50% of the `expiry_batch_size` it adds a task to run on a background thread. That task will:
|
192
104
|
|
@@ -202,7 +114,7 @@ Only triggering expiry when we write means that if the cache is idle, the backgr
|
|
202
114
|
|
203
115
|
If you want the cache expiry to be run in a background job instead of a thread, you can set `expiry_method` to `:job`. This will enqueue a `SolidCache::ExpiryJob`.
|
204
116
|
|
205
|
-
|
117
|
+
## Sharding the cache
|
206
118
|
|
207
119
|
Solid Cache uses the [Maglev](https://static.googleusercontent.com/media/research.google.com/en//pubs/archive/44824.pdf) consistent hashing scheme to shard the cache across multiple databases.
|
208
120
|
|
@@ -233,7 +145,7 @@ production:
|
|
233
145
|
databases: [cache_shard1, cache_shard2, cache_shard3]
|
234
146
|
```
|
235
147
|
|
236
|
-
|
148
|
+
## Enabling encryption
|
237
149
|
|
238
150
|
To encrypt the cache values, you can add set the encrypt property.
|
239
151
|
|
@@ -271,7 +183,7 @@ config.solid_cache.encryption_context_properties = {
|
|
271
183
|
Encryption currently does not work for PostgreSQL, as Rails does not yet support encrypting binary columns for it.
|
272
184
|
See https://github.com/rails/rails/pull/52650.
|
273
185
|
|
274
|
-
|
186
|
+
## Index size limits
|
275
187
|
The Solid Cache migrations try to create an index with 1024 byte entries. If that is too big for your database, you should:
|
276
188
|
|
277
189
|
1. Edit the index size in the migration.
|
@@ -322,5 +234,19 @@ $ appraisal update
|
|
322
234
|
|
323
235
|
This ensures that all the Rails versions dependencies are updated.
|
324
236
|
|
237
|
+
## Implementation
|
238
|
+
|
239
|
+
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.
|
240
|
+
|
241
|
+
A FIFO cache is much easier to manage:
|
242
|
+
1. We don't need to track when items are read.
|
243
|
+
2. We can estimate and control the cache size by comparing the maximum and minimum IDs.
|
244
|
+
3. By deleting from one end of the table and adding at the other end we can avoid fragmentation (on MySQL at least).
|
245
|
+
|
246
|
+
|
247
|
+
## Upgrading
|
248
|
+
|
249
|
+
**Upgrading from v0.3.0 or earlier? Please see [upgrading to version v0.4.x and beyond](upgrading_to_version_0.4.x.md)**
|
250
|
+
|
325
251
|
## License
|
326
252
|
Solid Cache is licensed under MIT.
|
@@ -3,22 +3,70 @@
|
|
3
3
|
class SolidCache::InstallGenerator < Rails::Generators::Base
|
4
4
|
source_root File.expand_path("templates", __dir__)
|
5
5
|
|
6
|
-
class_option :skip_migrations, type: :boolean, default: nil,
|
7
|
-
desc: "Skip migrations"
|
8
|
-
|
9
6
|
def add_rails_cache
|
10
|
-
|
11
|
-
|
12
|
-
end
|
7
|
+
gsub_file app_root.join("config/environments/production.rb"),
|
8
|
+
/(# )?config\.cache_store = (:.*)/, "config.cache_store = :solid_cache_store"
|
13
9
|
end
|
14
10
|
|
15
11
|
def create_config_solid_cache_yml
|
16
12
|
template "config/solid_cache.yml"
|
17
13
|
end
|
18
14
|
|
19
|
-
def
|
20
|
-
|
21
|
-
|
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
|
22
20
|
end
|
23
21
|
end
|
22
|
+
|
23
|
+
def add_solid_cache_db_schema
|
24
|
+
template "db/cache_schema.rb"
|
25
|
+
end
|
26
|
+
|
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
|
24
72
|
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
ActiveRecord::Schema[8.0].define(version: 1) do
|
4
|
+
create_table "solid_cache_entries", force: :cascade do |t|
|
5
|
+
t.binary "key", limit: 1024, null: false
|
6
|
+
t.binary "value", limit: 536870912, null: false
|
7
|
+
t.datetime "created_at", null: false
|
8
|
+
t.integer "key_hash", limit: 8, null: false
|
9
|
+
t.integer "byte_size", limit: 4, null: false
|
10
|
+
t.index ["byte_size"], name: "index_solid_cache_entries_on_byte_size"
|
11
|
+
t.index ["key_hash", "byte_size"], name: "index_solid_cache_entries_on_key_hash_and_byte_size"
|
12
|
+
t.index ["key_hash"], name: "index_solid_cache_entries_on_key_hash", unique: true
|
13
|
+
end
|
14
|
+
end
|
@@ -3,6 +3,16 @@
|
|
3
3
|
module SolidCache
|
4
4
|
class Store
|
5
5
|
module Failsafe
|
6
|
+
TRANSIENT_ACTIVE_RECORD_ERRORS = [
|
7
|
+
ActiveRecord::AdapterTimeout,
|
8
|
+
ActiveRecord::ConnectionNotEstablished,
|
9
|
+
ActiveRecord::ConnectionTimeoutError,
|
10
|
+
ActiveRecord::Deadlocked,
|
11
|
+
ActiveRecord::LockWaitTimeout,
|
12
|
+
ActiveRecord::QueryCanceled,
|
13
|
+
ActiveRecord::StatementTimeout
|
14
|
+
]
|
15
|
+
|
6
16
|
DEFAULT_ERROR_HANDLER = ->(method:, returning:, exception:) do
|
7
17
|
if Store.logger
|
8
18
|
Store.logger.error { "SolidCacheStore: #{method} failed, returned #{returning.inspect}: #{exception.class}: #{exception.message}" }
|
@@ -20,7 +30,7 @@ module SolidCache
|
|
20
30
|
|
21
31
|
def failsafe(method, returning: nil)
|
22
32
|
yield
|
23
|
-
rescue
|
33
|
+
rescue *TRANSIENT_ACTIVE_RECORD_ERRORS => error
|
24
34
|
ActiveSupport.error_reporter&.report(error, handled: true, severity: :warning)
|
25
35
|
error_handler&.call(method: method, exception: error, returning: returning)
|
26
36
|
returning
|
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.3
|
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-
|
11
|
+
date: 2024-09-03 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -112,11 +112,11 @@ files:
|
|
112
112
|
- app/models/solid_cache/entry/size/estimate.rb
|
113
113
|
- app/models/solid_cache/entry/size/moving_average_estimate.rb
|
114
114
|
- app/models/solid_cache/record.rb
|
115
|
-
- db/migrate/20240820123641_create_solid_cache_entries.rb
|
116
115
|
- lib/active_support/cache/solid_cache_store.rb
|
117
116
|
- lib/generators/solid_cache/install/USAGE
|
118
117
|
- lib/generators/solid_cache/install/install_generator.rb
|
119
118
|
- lib/generators/solid_cache/install/templates/config/solid_cache.yml.tt
|
119
|
+
- lib/generators/solid_cache/install/templates/db/cache_schema.rb
|
120
120
|
- lib/solid_cache.rb
|
121
121
|
- lib/solid_cache/configuration.rb
|
122
122
|
- lib/solid_cache/connections.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.17
|
162
162
|
signing_key:
|
163
163
|
specification_version: 4
|
164
164
|
summary: A database backed ActiveSupport::Cache::Store
|
@@ -1,29 +0,0 @@
|
|
1
|
-
class CreateSolidCacheEntries < ActiveRecord::Migration[7.2]
|
2
|
-
def up
|
3
|
-
create_table :solid_cache_entries, if_not_exists: true do |t|
|
4
|
-
t.binary :key, null: false, limit: 1024
|
5
|
-
t.binary :value, null: false, limit: 512.megabytes
|
6
|
-
t.datetime :created_at, null: false
|
7
|
-
t.integer :key_hash, null: false, limit: 8
|
8
|
-
t.integer :byte_size, null: false, limit: 4
|
9
|
-
|
10
|
-
t.index :key_hash, unique: true
|
11
|
-
t.index [:key_hash, :byte_size]
|
12
|
-
t.index :byte_size
|
13
|
-
end
|
14
|
-
|
15
|
-
raise "column \"key_hash\" does not exist" unless column_exists? :solid_cache_entries, :key_hash
|
16
|
-
rescue => e
|
17
|
-
if e.message =~ /(column "key_hash" does not exist|no such column: key_hash)/
|
18
|
-
raise \
|
19
|
-
"Could not find key_hash column on solid_cache_entries, if upgrading from v0.3 or earlier, have you followed " \
|
20
|
-
"the steps in https://github.com/rails/solid_cache/blob/main/upgrading_to_version_0.4.x.md?"
|
21
|
-
else
|
22
|
-
raise
|
23
|
-
end
|
24
|
-
end
|
25
|
-
|
26
|
-
def down
|
27
|
-
drop_table :solid_cache_entries
|
28
|
-
end
|
29
|
-
end
|