solid_cache 1.0.2 → 1.0.4
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 +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/engine.rb +1 -0
- data/lib/solid_cache/store/failsafe.rb +11 -1
- data/lib/solid_cache/version.rb +1 -1
- metadata +3 -3
- 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: 4166bf227d59acde335e056a12f2a382863fe469fa8417827e19da72bc12fb7b
|
4
|
+
data.tar.gz: af1066a13d699e9ca3716c7b8b12f0b50e379b5cc5940757f613c51a08b9114a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8fe05aebd5d0279edb79654b8cd432efc78232d5f998b661d8026b23dcfb7ab73de74c407ce2594a15d01809a377711fb7bb8ffb060823619341ac0020564973
|
7
|
+
data.tar.gz: 78995f9165d9f0c3da70bfe8499d89cf4fefb17cd7a5c8d0971815e21688ff71303b9da040a301aed7cf6bac46d6a6245c32657d170e4b1631278327715d110f
|
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
|
data/lib/solid_cache/engine.rb
CHANGED
@@ -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.4
|
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-04 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
|
@@ -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
|