solid_cache 1.0.2 → 1.0.3

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d8807bd93a8e16495e40542655163d95bcfac3b9a0d96de0ccbea687899ba600
4
- data.tar.gz: 15107cc9ed202a7c8cf01d962c7ec8182eed76c3677e86155f104fac07eae6b2
3
+ metadata.gz: 557f25039474c31f96924767eaa99756e7171355f531239fb8452770d7ceb8fd
4
+ data.tar.gz: 2b719db3b62c8b2711961e4ab5ce7a9543fdb03f33739a2e48ead12c80af284a
5
5
  SHA512:
6
- metadata.gz: 2c1bdc5f7ae9313d017388821a1792228b9ff59d9570cc46129f2b0127648aeda3e103b3f387758c37b4119010929f48452def1638dad43a90cb46d201db2274
7
- data.tar.gz: a0352691be83a3ee2227c501235bfd7c8639ea08b717c0bcbfcf002a3901b367c142d430677afd30778cafef0918192ed4afc987b9082987524dbdc327d6e42b
6
+ metadata.gz: 25187adeb93d8e1edfdfc4e754a9c064b61e128a3d5ca96a3c7b8d523b61aca3cb689878be0079c6cd387baf91cd5de8c57ae331a6efa82d087fe0123587e1bd
7
+ data.tar.gz: 55ed9ccacea8c5de554147d838a6ccb4c64970d1c8479c226114179bfd93e2680f2b1d77196b2b86393047bd2052e11df44310c4f24b6002735ad5bba5c0dcd3
data/README.md CHANGED
@@ -1,105 +1,18 @@
1
1
  # Solid Cache
2
2
 
3
- **Upgrading from v0.3.0 or earlier? Please see [upgrading to version v0.4.x and beyond](upgrading_to_version_0.4.x.md)**
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
- #### Cache database configuration
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
- The default installation of Solid Cache expects a database named `cache` in `database.yml`. It should
38
- have it's own connection pool to avoid mixing cache queries in other transactions.
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
- #### Install Solid Cache
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
- Now, you need to install the necessary migrations and configure the cache store. You can do both at once using the provided generator:
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
- #### Connection configuration
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
- #### Engine configuration
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
- #### Cache configuration
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 `ActiveRecord::ActiveRecordError`s that are raises (default: log errors as warnings)
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
- ### Cache expiry
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
- ### Sharding the cache
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
- ### Enabling encryption
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
- ### Index size limits
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
- if (env_config = Pathname(destination_root).join("config/environments/production.rb")).exist?
11
- gsub_file env_config, /(# )?config\.cache_store = (:.*)/, "config.cache_store = :solid_cache_store"
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 create_migrations
20
- unless options[:skip_migrations]
21
- rails_command "railties:install:migrations FROM=solid_cache", inline: true
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
@@ -1,6 +1,8 @@
1
1
  default: &default
2
2
  database: <%= ENV.fetch("DATABASE", "cache") %>
3
3
  store_options:
4
+ # Cap age of oldest cache entry to fulfill retention policies
5
+ # max_age: <%%= 60.days.to_i %>
4
6
  max_size: <%%= 256.megabytes %>
5
7
  namespace: <%%= Rails.env %>
6
8
 
@@ -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 ActiveRecord::ActiveRecordError => error
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SolidCache
4
- VERSION = "1.0.2"
4
+ VERSION = "1.0.3"
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.2
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-02 00:00:00.000000000 Z
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.11
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