solid_cache 1.0.2 → 1.0.4

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: d8807bd93a8e16495e40542655163d95bcfac3b9a0d96de0ccbea687899ba600
4
- data.tar.gz: 15107cc9ed202a7c8cf01d962c7ec8182eed76c3677e86155f104fac07eae6b2
3
+ metadata.gz: 4166bf227d59acde335e056a12f2a382863fe469fa8417827e19da72bc12fb7b
4
+ data.tar.gz: af1066a13d699e9ca3716c7b8b12f0b50e379b5cc5940757f613c51a08b9114a
5
5
  SHA512:
6
- metadata.gz: 2c1bdc5f7ae9313d017388821a1792228b9ff59d9570cc46129f2b0127648aeda3e103b3f387758c37b4119010929f48452def1638dad43a90cb46d201db2274
7
- data.tar.gz: a0352691be83a3ee2227c501235bfd7c8639ea08b717c0bcbfcf002a3901b367c142d430677afd30778cafef0918192ed4afc987b9082987524dbdc327d6e42b
6
+ metadata.gz: 8fe05aebd5d0279edb79654b8cd432efc78232d5f998b661d8026b23dcfb7ab73de74c407ce2594a15d01809a377711fb7bb8ffb060823619341ac0020564973
7
+ data.tar.gz: 78995f9165d9f0c3da70bfe8499d89cf4fefb17cd7a5c8d0971815e21688ff71303b9da040a301aed7cf6bac46d6a6245c32657d170e4b1631278327715d110f
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
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "active_support"
4
+ require "active_record"
4
5
 
5
6
  module SolidCache
6
7
  class Engine < ::Rails::Engine
@@ -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.4"
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.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-02 00:00:00.000000000 Z
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