Code Monkey home page Code Monkey logo

solid_cache's Introduction

Solid Cache

Upgrading from v0.3.0 or earlier? Please see upgrading to version v0.4.x and beyond

Solid Cache is a database-backed Active Support cache store implementation.

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.

Usage

To set Solid Cache as your Rails cache, you should add this to your environment config:

config.cache_store = :solid_cache_store

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.

A FIFO cache is much easier to manage:

  1. We don't need to track when items are read.
  2. We can estimate and control the cache size by comparing the maximum and minimum IDs.
  3. By deleting from one end of the table and adding at the other end we can avoid fragmentation (on MySQL at least).

Installation

Add this line to your application's Gemfile:

gem "solid_cache"

And then execute:

$ bundle

Or install it yourself as:

$ gem install solid_cache

Now, you need to install the necessary migrations and configure the cache store. You can do both at once using the provided generator:

$ bin/rails generate solid_cache:install

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.

Alternatively, you can add only the migration to your app:

$ bin/rails solid_cache:install:migrations

And set Solid Cache as your application's cache store backend manually, in your environment config:

# config/environments/production.rb
config.cache_store = :solid_cache_store

Finally, you need to run the migrations:

$ bin/rails db:migrate

Configuration

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.

The format of the file is:

default:
  store_options: &default_store_options
    max_age: <%= 60.days.to_i %>
    namespace: <%= Rails.env %>
  size_estimate_samples: 1000

development: &development
  database: development_cache
  store_options:
    <<: *default_store_options
    max_size: <%= 256.gigabytes %>

production: &production
  databases: [production_cache1, production_cache2]
  store_options:
    <<: *default_store_options
    max_entries: <%= 256.gigabytes %>

For the full list of keys for store_options see Cache configuration. Any options passed to the cache lookup will overwrite those specified here.

Connection configuration

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.

Setting database to cache_db will configure with:

SolidCache::Record.connects_to database: { writing: :cache_db }

Setting databases to [cache_db, cache_db2] is the equivalent of:

SolidCache::Record.connects_to shards: { cache_db1: { writing: :cache_db1 },  cache_db2: { writing: :cache_db2 } }

If connects_to is set, it will be passed directly.

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.

Engine configuration

There are three options that can be set on the engine:

  • executor - the Rails executor used to wrap asynchronous operations, defaults to the app executor
  • 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
  • size_estimate_samples - if max_size is set on the cache, the number of the samples used to estimates the size

These can be set in your Rails configuration:

Rails.application.configure do
  config.solid_cache.size_estimate_samples = 1000
end

Cache configuration

Solid Cache supports these options in addition to the standard ActiveSupport::Cache::Store options:

  • error_handler - a Proc to call to handle any ActiveRecord::ActiveRecordErrors that are raises (default: log errors as warnings)
  • expiry_batch_size - the batch size to use when deleting old records (default: 100)
  • expiry_method - what expiry method to use thread or job (default: thread)
  • expiry_queue - which queue to add expiry jobs to (default: default)
  • max_age - the maximum age of entries in the cache (default: 2.weeks.to_i). Can be set to nil, but this is not recommended unless using max_entries to limit the size of the cache.
  • max_entries - the maximum number of entries allowed in the cache (default: nil, meaning no limit)
  • max_size - the maximum size of the cache entries (default nil, meaning no limit)
  • cluster - (deprecated) a Hash of options for the cache database cluster, e.g { shards: [:database1, :database2, :database3] }
  • clusters - (deprecated) an Array of Hashes for multiple cache clusters (ignored if :cluster is set)
  • shards - an Array of databases
  • active_record_instrumentation - whether to instrument the cache's queries (default: true)
  • clear_with - clear the cache with :truncate or :delete (default truncate, except for when Rails.env.test? then delete)
  • max_key_bytesize - the maximum size of a normalized key in bytes (default 1024)

For more information on cache clusters, see Sharding the cache

Cache expiry

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:

  1. Check if we have exceeded the max_entries or max_size values (if set). The current entries are estimated by subtracting the max and min IDs from the SolidCache::Entry table. The current size is estimated by sampling the entry byte_size columns.
  2. If we have, it will delete expiry_batch_size entries.
  3. If not, it will delete up to expiry_batch_size entries, provided they are all older than max_age.

Expiring when we reach 50% of the batch size allows us to expire records from the cache faster than we write to it when we need to reduce the cache size.

Only triggering expiry when we write means that if the cache is idle, the background thread is also idle.

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.

Using a dedicated cache database

Add database configuration to database.yml, e.g.:

development:
  cache:
    database: cache_development
    host: 127.0.0.1
    migrations_paths: "db/cache/migrate"

Create database:

$ bin/rails db:create

Install migrations:

$ bin/rails solid_cache:install:migrations

Move migrations to custom migrations folder:

$ mkdir -p db/cache/migrate
$ mv db/migrate/*.solid_cache.rb db/cache/migrate

Set the engine configuration to point to the new database:

# config/solid_cache.yml
production:
  database: cache

Run migrations:

$ bin/rails db:migrate

Sharding the cache

Solid Cache uses the Maglev consistent hashing scheme to shard the cache across multiple databases.

To shard:

  1. Add the configuration for the database shards to database.yml.
  2. Configure the shards via config.solid_cache.connects_to.
  3. Pass the shards for the cache to use via the cluster option.

For example:

# config/database.yml
production:
  cache_shard1:
    database: cache1_production
    host: cache1-db
  cache_shard2:
    database: cache2_production
    host: cache2-db
  cache_shard3:
    database: cache3_production
    host: cache3-db
# config/solid_cache.yml
production:
  databases: [cache_shard1, cache_shard2, cache_shard3]

Enabling encryption

Add this to an initializer:

ActiveSupport.on_load(:solid_cache_entry) do
  encrypts :value
end

Index size limits

The Solid Cache migrations try to create an index with 1024 byte entries. If that is too big for your database, you should:

  1. Edit the index size in the migration.
  2. Set max_key_bytesize on your cache to the new value.

Development

Run the tests with bin/rake test. By default, these will run against SQLite.

You can also run the tests against MySQL and PostgreSQL. First start up the databases:

$ docker compose up -d

Next, setup the database schema:

$ TARGET_DB=mysql bin/rails db:setup
$ TARGET_DB=postgres bin/rails db:setup

Then run the tests for the target database:

$ TARGET_DB=mysql bin/rake test
$ TARGET_DB=postgres bin/rake test

Testing with multiple Rails versions

Solid Cache relies on appraisal to test multiple Rails versions.

To run a test for a specific version run:

bundle exec appraisal rails-7-1 bin/rake test

After updating the dependencies in the Gemfile please run:

$ bundle
$ appraisal update

This ensures that all the Rails versions dependencies are updated.

License

Solid Cache is licensed under MIT.

solid_cache's People

Contributors

brunoprietog avatar caleb-t-owens avatar codergeek121 avatar dependabot[bot] avatar devstarks avatar dhh avatar djmb avatar esasse avatar fgo avatar gagalago avatar gnumarcelo avatar hahwul avatar intrip avatar jonathanhefner avatar jordelver avatar justinfrench avatar mdh avatar northeastprince avatar npezza93 avatar olimart avatar ollym avatar philipithomas avatar rafaelmontas avatar rafaelsales avatar robbyrussell avatar ron-shinall avatar simi avatar skatkov avatar xuanxu avatar yenshirak avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

solid_cache's Issues

Support for write with `unless_exist: true`

I am implementing a simple lock interface on Rails.cache
& currently using Rails.cache.fetch(lock_key, expires_in: timeout) { lock_value } == lock_value
But it is not atomic as it makes multiple SQL queries

Can we implement support for Rails.cache.write(lock_key, lock_value, unless_exist: true, expires_in: timeout)

dedicated cache database not created with steps from readme

When I follow steps from here to create dedicated cache database, I run command rails db:create but get only response that my dev database already exists. And cache_development has not been created

# database.yml
default: &default
  adapter: postgresql
  encoding: unicode
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  url: <%= ENV["DATABASE_URL"] %>

development:
  <<: *default
  database: my_app_development
  cache:
    database: cache_development
    url: <%= ENV["DATABASE_URL"] %>
    migrations_paths: "db/cache/migrate"

what am i missing? I'm using postgresql, does it supported with this syntax?

Cache expiry might not be trigger by short lived processes

By default cache expiry is triggered every 80 writes. We prefill the counter with a random figure so on average it will take 40 writes to trigger expiry. For short lived processes (e.g. AWS Lambdas) we might not trigger expiry often enough.

We could instead use a probabilistic trigger. I.e instead of every 80 writes, each write has a 1/80 chance of triggering expiry.

UNLOGGED TABLE's in postgresql.

It makes a lot of sense to turn cache table into UNLOGGED TABLE for postgresql. Or provide an option to do so.

UNLOGGED TABLES turn off WAL completely. Writting into table gets a significant boost and data sizes will be smaller and will require less vacuum.

But data is easily lost, can't be synched with replicas or will not be backed. All of these drawbacks don't seem like a big problem if youre dealing with cache.

https://www.crunchydata.com/blog/postgresl-unlogged-tables

binlog in mysql functions the same, but it is not a table-specific setting.

Should we implement support for UNLOGGED TABLES? It's easy to turn on or off, at least in PG.

Transaction behaviour inside another transaction from another connection

we had issues using the default setup for a use case where we want to execute some code on a read only connection. a short example is below. evidently this would not work if the normal connection pool is used because the cache would not be able to write, which indeed is what we where observring.

first remark is that this initially surprised us, and might make sense to add some documentation and/or warnings around the use of the standard connection pool.

User.transaction { User.connection.execute("SET TRANSACTION READ ONLY"); puts Rails.cache.fetch("2") { "hoho" }; raise ActiveRecord::Rollback; }

the solution we are implementing tries to fix this without introducing a separate database as this would be to costly (we are running on heroku still).

the gist is to configure a cache db that points to the same database URL, something like below

default: &default
  adapter: postgresql
  encoding: unicode
  url: <%= ENV["DATABASE_URL"] %>

production:
  primary:
    <<: *default
  cache:
    <<: *default

and solid cache yml

production: &production
  database: cache
  store_options:
    <<: *default_store_options
    max_entries: <%= 1.gigabytes %>

the good news is it doesn't crash any more. the system

User.transaction { puts Rails.cache.fetch("2") { "hoho" }; raise ActiveRecord::Rollback; }

we actually see the upsert happening:

User.transaction { puts Rails.cache.fetch("2") { "hoho" }; raise ActiveRecord::Rollback; }
D, [2024-04-04T11:53:15.866315 #2] DEBUG -- :   TRANSACTION (0.5ms)  BEGIN
D, [2024-04-04T11:53:15.867177 #2] DEBUG -- :   SolidCache::Entry Load (0.5ms)  SELECT "solid_cache_entries"."key", "solid_cache_entries"."value" FROM "solid_cache_entries" WHERE "solid_cache_entries"."key_hash" = $1  [[nil, -3138060911502289170]]
D, [2024-04-04T11:53:15.868425 #2] DEBUG -- :   SolidCache::Entry Upsert (0.7ms)  INSERT INTO "solid_cache_entries" ("key","value","key_hash","byte_size","created_at") VALUES ('\x32', '\x0004085b06492209686f686f063a064554', -3138060911502289170, 158, CURRENT_TIMESTAMP) ON CONFLICT ("key_hash") DO UPDATE SET "key"=excluded."key","value"=excluded."value","byte_size"=excluded."byte_size" RETURNING "id"
hoho
D, [2024-04-04T11:53:15.869289 #2] DEBUG -- :   TRANSACTION (0.5ms)  ROLLBACK
=> nil

HOWEVER to our surprise in the first approach (read only connection) and the second approach (rollback), in both cases, the cache entry is 'lost' after the transaction on the main db is finished/rolledback.

so somehow, the 'transaction' on the primary database seems to be impacting the transaction on the cache database?

in our case, it is actually the same database 'admitted', but there is a separate database pool (which seems to be correct) and hence the one transaction should not influence the cache writing ?

Allow set a different queue than default

Now, if you want expire cache using Job, this is send to default queue. This is ok, but it would be great to have flexibility to send a different queue.

I was thinking in something similar to ActiveStorage.

config.solid_cache.queues.expire = :custom_queue

Encrypts :value encrypts every column named :value

My initializer: config/initializers/solid_cache.rb

# frozen_string_literal: true

ActiveSupport.on_load(:solid_cache_entry) do
  encrypts :value
end

I'm using the rails-settings-cached gem which creates a table with a :value column.

I have a setting called domain that I use in my from address in my mailer:

app/mailers/user_mailer.rb

class UserMailer < ApplicationMailer
  default from: "no-reply@#{Setting.domain}"

  def invite
    # do work
  end
end

The app failed to boot up because it was loading the domain setting from the Setting table and trying to decrypt my plaintext setting.

I removed the line from the mailer and it booted up and started getting ActiveRecord::Encryption::Errors::Decryption: ActiveRecord::Encryption::Errors::Decryption errors wherever I called the cache method in my views.

I removed the initializer and it went back to working albeit with the cache now running in plaintext.

Nothing I'm caching at the moment is sensitive. It's mostly just to speed up the website. However, if I end up needing it encrypted, I'd like it to work.

unknown attribute 'key_hash' for SolidCache::Entry. (ActiveModel::UnknownAttributeError)

I just upgraded my Gems, and ruby version to 3.3.3, and have started getting this error

Gemfile.lock
GIT
remote: https://github.com/ausangshukla/acts_as_favoritor.git
revision: 353fe5010c225a012cf6d34881827c165eb47281
specs:
acts_as_favoritor (6.0.1)
activerecord (>= 5.0)

GIT
remote: https://github.com/nejdetkadir/devise-api.git
revision: 006bddee4bbe9f5ff9edc936d54725ce061be720
branch: main
specs:
devise-api (0.1.3)
devise (>= 4.7.2)
dry-configurable (~> 1.0, >= 1.0.1)
dry-initializer (>= 3.1.1)
dry-monads (>= 1.6.0)
dry-types (>= 1.7.0)
rails (>= 6.0.0)

GIT
remote: https://github.com/seuros/capistrano-sidekiq.git
revision: 784b04c973e5c074dc78c30746077c9e6fd2bb9a
ref: 784b04c973e5c074dc78c30746077c9e6fd2bb9a
specs:
capistrano-sidekiq (2.0.0)
capistrano (>= 3.9.0)
capistrano-bundler
sidekiq (>= 6.0)

GIT
remote: https://github.com/thimmaiah/rupees.git
revision: db8a7e8b7b4ae6f1728831d38cdb094c18aba30b
specs:
rupees (0.1.0)

GIT
remote: https://github.com/thimmaiah/xirr
revision: e630a7cbe48dc0d2b6ce49097ca9010643e49ac2
specs:
xirr (0.6.1)
RubyInline (> 3)
activesupport (
> 7.0)

GIT
remote: https://github.com/varvet/pundit.git
revision: e5f88356c0ad366100b35ae2e3ae7327f8fb9002
specs:
pundit (2.3.2)
activesupport (>= 3.0.0)

GIT
remote: https://github.com/weshatheleopard/rubyXL.git
revision: b719e4337a80aa78000bea1a0569cbfeb7783b4f
specs:
rubyXL (3.4.27)
nokogiri (>= 1.10.8)
rubyzip (>= 1.3.0)

GEM
remote: https://rubygems.org/
specs:
RubyInline (3.14.1)
ZenTest (> 4.3)
ZenTest (4.12.1)
actioncable (7.1.3.4)
actionpack (= 7.1.3.4)
activesupport (= 7.1.3.4)
nio4r (
> 2.0)
websocket-driver (>= 0.6.1)
zeitwerk (> 2.6)
actionmailbox (7.1.3.4)
actionpack (= 7.1.3.4)
activejob (= 7.1.3.4)
activerecord (= 7.1.3.4)
activestorage (= 7.1.3.4)
activesupport (= 7.1.3.4)
mail (>= 2.7.1)
net-imap
net-pop
net-smtp
actionmailer (7.1.3.4)
actionpack (= 7.1.3.4)
actionview (= 7.1.3.4)
activejob (= 7.1.3.4)
activesupport (= 7.1.3.4)
mail (
> 2.5, >= 2.5.4)
net-imap
net-pop
net-smtp
rails-dom-testing (> 2.2)
actionpack (7.1.3.4)
actionview (= 7.1.3.4)
activesupport (= 7.1.3.4)
nokogiri (>= 1.8.5)
racc
rack (>= 2.2.4)
rack-session (>= 1.0.1)
rack-test (>= 0.6.3)
rails-dom-testing (
> 2.2)
rails-html-sanitizer (> 1.6)
actiontext (7.1.3.4)
actionpack (= 7.1.3.4)
activerecord (= 7.1.3.4)
activestorage (= 7.1.3.4)
activesupport (= 7.1.3.4)
globalid (>= 0.6.0)
nokogiri (>= 1.8.5)
actionview (7.1.3.4)
activesupport (= 7.1.3.4)
builder (
> 3.1)
erubi (> 1.11)
rails-dom-testing (
> 2.2)
rails-html-sanitizer (> 1.6)
active_flag (1.6.0)
activerecord (>= 5)
active_storage_validations (1.1.4)
activejob (>= 5.2.0)
activemodel (>= 5.2.0)
activestorage (>= 5.2.0)
activesupport (>= 5.2.0)
activejob (7.1.3.4)
activesupport (= 7.1.3.4)
globalid (>= 0.3.6)
activemodel (7.1.3.4)
activesupport (= 7.1.3.4)
activemodel-serializers-xml (1.0.2)
activemodel (> 5.x)
activesupport (> 5.x)
builder (
> 3.1)
activerecord (7.1.3.4)
activemodel (= 7.1.3.4)
activesupport (= 7.1.3.4)
timeout (>= 0.4.0)
activerecord-import (1.5.0)
activerecord (>= 4.2)
activestorage (7.1.3.4)
actionpack (= 7.1.3.4)
activejob (= 7.1.3.4)
activerecord (= 7.1.3.4)
activesupport (= 7.1.3.4)
marcel (> 1.0)
activesupport (7.1.3.4)
base64
bigdecimal
concurrent-ruby (
> 1.0, >= 1.0.2)
connection_pool (>= 2.2.5)
drb
i18n (>= 1.6, < 2)
minitest (>= 5.1)
mutex_m
tzinfo (> 2.0)
acts_as_list (1.2.1)
activerecord (>= 6.1)
activesupport (>= 6.1)
addressable (2.8.7)
public_suffix (>= 2.0.2, < 7.0)
administrate (0.20.1)
actionpack (>= 6.0, < 8.0)
actionview (>= 6.0, < 8.0)
activerecord (>= 6.0, < 8.0)
jquery-rails (
> 4.6.0)
kaminari (> 1.2.2)
sassc-rails (
> 2.1)
selectize-rails (> 0.6)
administrate-field-active_storage (1.0.3)
administrate (>= 0.2.2)
rails (>= 7.0)
administrate-field-boolean_emoji (0.3.0)
administrate (< 1.0.0)
rack (
> 2.0, >= 2.0.8)
rails (>= 4.2, < 8)
administrate-field-shrine (0.0.5)
administrate (> 0.5.0)
rails (>= 4.2)
after_commit_action (1.1.0)
activerecord (>= 3.0.0)
activesupport (>= 3.0.0)
airbrussh (1.5.2)
sshkit (>= 1.6.1, != 1.7.0)
ajax-datatables-rails (1.5.0)
rails (>= 6.0)
zeitwerk
ancestry (4.3.3)
activerecord (>= 5.2.6)
annotate (3.2.0)
activerecord (>= 3.2, < 8.0)
rake (>= 10.4, < 14.0)
ast (2.4.2)
audited (5.6.0)
activerecord (>= 5.2, < 7.2)
activesupport (>= 5.2, < 7.2)
awesome_print (1.9.2)
aws-eventstream (1.3.0)
aws-partitions (1.947.0)
aws-sdk-core (3.198.0)
aws-eventstream (> 1, >= 1.3.0)
aws-partitions (
> 1, >= 1.651.0)
aws-sigv4 (> 1.8)
jmespath (
> 1, >= 1.6.1)
aws-sdk-ec2 (1.462.0)
aws-sdk-core (> 3, >= 3.198.0)
aws-sigv4 (
> 1.1)
aws-sdk-kms (1.86.0)
aws-sdk-core (> 3, >= 3.198.0)
aws-sigv4 (
> 1.1)
aws-sdk-s3 (1.153.0)
aws-sdk-core (> 3, >= 3.198.0)
aws-sdk-kms (
> 1)
aws-sigv4 (> 1.8)
aws-sigv4 (1.8.0)
aws-eventstream (
> 1, >= 1.0.2)
base64 (0.2.0)
bcrypt (3.1.20)
better_errors (2.10.1)
erubi (>= 1.0.0)
rack (>= 0.9.0)
rouge (>= 1.0.0)
better_html (2.1.1)
actionview (>= 6.0)
activesupport (>= 6.0)
ast (> 2.0)
erubi (
> 1.4)
parser (>= 2.4)
smart_properties
bigdecimal (3.1.8)
bindex (0.8.1)
binding_of_caller (1.0.1)
debug_inspector (>= 1.2.0)
blazer (2.6.5)
activerecord (>= 5)
chartkick (>= 3.2)
railties (>= 5)
safely_block (>= 0.1.1)
bootsnap (1.15.0)
msgpack (> 1.2)
brakeman (6.1.2)
racc
builder (3.3.0)
bullet (7.1.6)
activesupport (>= 3.0.0)
uniform_notifier (
> 1.11)
bundler-audit (0.9.1)
bundler (>= 1.2.0, < 3)
thor (> 1.0)
byebug (11.1.3)
capistrano (3.19.0)
airbrussh (>= 1.0.0)
i18n
rake (>= 10.0.0)
sshkit (>= 1.9.0)
capistrano-bundler (2.1.0)
capistrano (
> 3.1)
capistrano-rails (1.6.3)
capistrano (> 3.1)
capistrano-bundler (>= 1.1, < 3)
capistrano-rvm (0.1.2)
capistrano (
> 3.0)
sshkit (> 1.2)
capistrano3-puma (5.2.0)
capistrano (
> 3.7)
capistrano-bundler
puma (>= 4.0, < 6.0)
capybara (3.40.0)
addressable
matrix
mini_mime (>= 0.1.3)
nokogiri (> 1.11)
rack (>= 1.6.0)
rack-test (>= 0.6.3)
regexp_parser (>= 1.5, < 3.0)
xpath (
> 3.2)
capybara-email (3.0.2)
capybara (>= 2.4, < 4.0)
mail
caxlsx (4.1.0)
htmlentities (> 4.3, >= 4.3.4)
marcel (
> 1.0)
nokogiri (> 1.10, >= 1.10.4)
rubyzip (>= 1.3.0, < 3)
caxlsx_rails (0.6.4)
actionpack (>= 3.1)
caxlsx (>= 3.0)
character_set (1.8.0)
chartkick (5.0.7)
chewy (7.6.0)
activesupport (>= 5.2)
elasticsearch (>= 7.14.0, < 8)
elasticsearch-dsl
childprocess (4.1.0)
chronic (0.10.2)
client_side_validations (22.2.0)
js_regex (
> 3.7)
rails (>= 6.1, < 8.0)
cocoon (1.2.15)
code_ownership (1.36.2)
code_teams (> 1.0)
packs-specification
sorbet-runtime (>= 0.5.11249)
code_teams (1.0.2)
sorbet-runtime
coderay (1.1.3)
combine_pdf (1.0.26)
matrix
ruby-rc4 (>= 0.1.5)
concurrent-ruby (1.3.3)
connection_pool (2.4.1)
constant_resolver (0.2.0)
content_disposition (1.0.0)
counter_culture (3.7.0)
activerecord (>= 4.2)
activesupport (>= 4.2)
crass (1.0.6)
csv (3.3.0)
cucumber (9.2.0)
builder (
> 3.2)
cucumber-ci-environment (> 9, < 11)
cucumber-core (> 13, < 14)
cucumber-cucumber-expressions (> 17.0)
cucumber-gherkin (> 24, < 28)
cucumber-html-formatter (> 20.3, < 22)
cucumber-messages (> 19, < 25)
diff-lcs (
> 1.5)
mini_mime (> 1.1)
multi_test (
> 1.1)
sys-uname (> 1.2)
cucumber-ci-environment (10.0.1)
cucumber-core (13.0.2)
cucumber-gherkin (>= 27, < 28)
cucumber-messages (>= 20, < 23)
cucumber-tag-expressions (> 5, < 7)
cucumber-cucumber-expressions (17.1.0)
bigdecimal
cucumber-gherkin (27.0.0)
cucumber-messages (>= 19.1.4, < 23)
cucumber-html-formatter (21.4.0)
cucumber-messages (> 19, < 25)
cucumber-messages (22.0.0)
cucumber-rails (3.0.0)
capybara (>= 3.11, < 4)
cucumber (>= 5, < 10)
railties (>= 5.2, < 8)
cucumber-tag-expressions (6.1.0)
database_cleaner-active_record (2.1.0)
activerecord (>= 5.a)
database_cleaner-core (
> 2.0.0)
database_cleaner-core (2.0.1)
date (3.3.4)
debug (1.9.2)
irb (> 1.10)
reline (>= 0.3.8)
debug_inspector (1.2.0)
declarative (0.0.20)
devise (4.9.4)
bcrypt (
> 3.0)
orm_adapter (> 0.1)
railties (>= 4.1.0)
responders
warden (
> 1.2.3)
diff-lcs (1.5.1)
disposable (0.6.3)
declarative (>= 0.0.9, < 1.0.0)
representable (>= 3.1.1, < 4)
docile (1.4.0)
dotenv (3.1.2)
dotenv-rails (3.1.2)
dotenv (= 3.1.2)
railties (>= 6.1)
down (5.4.2)
addressable (> 2.8)
draper (4.0.2)
actionpack (>= 5.0)
activemodel (>= 5.0)
activemodel-serializers-xml (>= 1.0)
activesupport (>= 5.0)
request_store (>= 1.0)
ruby2_keywords
drb (2.2.1)
dry-configurable (1.1.0)
dry-core (
> 1.0, < 2)
zeitwerk (> 2.6)
dry-core (1.0.1)
concurrent-ruby (
> 1.0)
zeitwerk (> 2.6)
dry-inflector (1.0.0)
dry-initializer (3.1.1)
dry-logic (1.5.0)
concurrent-ruby (
> 1.0)
dry-core (> 1.0, < 2)
zeitwerk (
> 2.6)
dry-monads (1.6.0)
concurrent-ruby (> 1.0)
dry-core (
> 1.0, < 2)
zeitwerk (> 2.6)
dry-types (1.7.2)
bigdecimal (
> 3.0)
concurrent-ruby (> 1.0)
dry-core (
> 1.0)
dry-inflector (> 1.0)
dry-logic (
> 1.4)
zeitwerk (> 2.6)
elasticsearch (7.17.11)
elasticsearch-api (= 7.17.11)
elasticsearch-transport (= 7.17.11)
elasticsearch-api (7.17.11)
multi_json
elasticsearch-dsl (0.1.10)
elasticsearch-transport (7.17.11)
base64
faraday (>= 1, < 3)
multi_json
enumerize (2.8.1)
activesupport (>= 3.2)
erubi (1.13.0)
erubis (2.7.0)
et-orbi (1.2.11)
tzinfo
eu_central_bank (1.7.0)
money (
> 6.13, >= 6.13.6)
nokogiri (> 1.9)
event_stream_parser (1.0.0)
exception_notification (4.5.0)
actionmailer (>= 5.2, < 8)
activesupport (>= 5.2, < 8)
factory_bot (6.4.6)
activesupport (>= 5.0.0)
factory_bot_rails (6.4.3)
factory_bot (
> 6.4)
railties (>= 5.0.0)
faker (3.4.1)
i18n (>= 1.8.11, < 2)
faraday (2.9.2)
faraday-net_http (>= 2.0, < 3.2)
faraday-multipart (1.0.4)
multipart-post (> 2)
faraday-net_http (3.1.0)
net-http
ffi (1.17.0-x86_64-linux-gnu)
foreman (0.88.1)
friendly_id (5.5.1)
activerecord (>= 4.0.0)
fugit (1.11.0)
et-orbi (
> 1, >= 1.2.11)
raabro (> 1.4)
globalid (1.2.1)
activesupport (>= 6.1)
groupdate (6.4.0)
activesupport (>= 6.1)
has_scope (0.8.2)
actionpack (>= 5.2)
activesupport (>= 5.2)
hashie (5.0.0)
hightop (0.4.0)
activesupport (>= 6.1)
hirb (0.7.3)
htmlbeautifier (1.4.3)
htmlentities (4.3.4)
httparty (0.22.0)
csv
mini_mime (>= 1.0.0)
multi_xml (>= 0.5.2)
i18n (1.14.5)
concurrent-ruby (
> 1.0)
image_processing (1.12.2)
mini_magick (>= 4.9.5, < 5)
ruby-vips (>= 2.0.17, < 3)
importmap-rails (2.0.1)
actionpack (>= 6.0.0)
activesupport (>= 6.0.0)
railties (>= 6.0.0)
iniparse (1.5.0)
io-console (0.7.2)
irb (1.13.2)
rdoc (>= 4.0.0)
reline (>= 0.4.2)
jbuilder (2.12.0)
actionview (>= 5.0.0)
activesupport (>= 5.0.0)
jmespath (1.6.2)
jquery-rails (4.6.0)
rails-dom-testing (>= 1, < 3)
railties (>= 4.2.0)
thor (>= 0.14, < 2.0)
js_regex (3.11.1)
character_set (> 1.4)
regexp_parser (>= 2.6.2, < 3.0.0)
regexp_property_values (
> 1.0)
json (2.7.2)
json2table (1.0.7)
kaminari (1.2.2)
activesupport (>= 4.1.0)
kaminari-actionview (= 1.2.2)
kaminari-activerecord (= 1.2.2)
kaminari-core (= 1.2.2)
kaminari-actionview (1.2.2)
actionview
kaminari-core (= 1.2.2)
kaminari-activerecord (1.2.2)
activerecord
kaminari-core (= 1.2.2)
kaminari-core (1.2.2)
language_server-protocol (3.17.0.3)
launchy (2.5.2)
addressable (> 2.8)
letter_opener (1.10.0)
launchy (>= 2.2, < 4)
libreconv (0.9.5)
loofah (2.22.0)
crass (
> 1.0.2)
nokogiri (>= 1.12.0)
mail (2.8.1)
mini_mime (>= 0.1.1)
net-imap
net-pop
net-smtp
mail-logger (0.0.4)
mail
marcel (1.0.4)
marginalia (1.11.1)
actionpack (>= 5.2)
activerecord (>= 5.2)
matrix (0.4.2)
memoized (1.1.1)
method_source (1.1.0)
mini_magick (4.13.1)
mini_mime (1.1.5)
minitest (5.24.0)
monetize (1.13.0)
money (> 6.12)
money (6.19.0)
i18n (>= 0.6.4, <= 2)
money-rails (1.15.0)
activesupport (>= 3.0)
monetize (
> 1.9)
money (> 6.13)
railties (>= 3.0)
msgpack (1.7.2)
multi_json (1.15.0)
multi_test (1.1.0)
multi_xml (0.7.1)
bigdecimal (
> 3.1)
multipart-post (2.4.1)
mutex_m (0.2.0)
mysql2 (0.5.6)
net-http (0.4.1)
uri
net-imap (0.4.14)
date
net-protocol
net-pop (0.1.2)
net-protocol
net-protocol (0.2.2)
timeout
net-scp (4.0.0)
net-ssh (>= 2.6.5, < 8.0.0)
net-sftp (4.0.0)
net-ssh (>= 5.0.0, < 8.0.0)
net-smtp (0.5.0)
net-protocol
net-ssh (7.2.3)
nio4r (2.7.3)
nokogiri (1.16.6-x86_64-linux)
racc (> 1.4)
noticed (2.3.3)
rails (>= 6.1.0)
orm_adapter (0.5.0)
overcommit (0.58.0)
childprocess (>= 0.6.3, < 5)
iniparse (
> 1.4)
rexml (> 3.2)
packs (0.0.44)
bigdecimal
code_ownership (>= 1.33.0)
packs-specification
packwerk
parse_packwerk (>= 0.25.0)
rainbow
sorbet-runtime
thor
tty-prompt
packs-rails (0.0.5)
activesupport
packs
railties
packs-specification (0.0.10)
sorbet-runtime
packwerk (3.2.1)
activesupport (>= 6.0)
ast
better_html
bundler
constant_resolver (>= 0.2.0)
parallel
parser
prism (>= 0.25.0)
sorbet-runtime (>= 0.5.9914)
zeitwerk (>= 2.6.1)
parallel (1.25.1)
paranoia (2.6.3)
activerecord (>= 5.1, < 7.2)
parse_packwerk (0.25.0)
bigdecimal
sorbet-runtime
parser (3.3.3.0)
ast (
> 2.4.1)
racc
pastel (0.8.0)
tty-color (> 0.5)
prism (0.30.0)
pry (0.14.2)
coderay (
> 1.1)
method_source (> 1.0)
pry-byebug (3.10.1)
byebug (
> 11.0)
pry (>= 0.13, < 0.15)
psych (5.1.2)
stringio
public_suffix (6.0.0)
puma (5.6.8)
nio4r (> 2.0)
raabro (1.4.0)
racc (1.8.0)
rack (2.2.9)
rack-attack (6.7.0)
rack (>= 1.0, < 4)
rack-session (1.0.2)
rack (< 3)
rack-test (2.1.0)
rack (>= 1.3)
rackup (1.0.0)
rack (< 3)
webrick
rails (7.1.3.4)
actioncable (= 7.1.3.4)
actionmailbox (= 7.1.3.4)
actionmailer (= 7.1.3.4)
actionpack (= 7.1.3.4)
actiontext (= 7.1.3.4)
actionview (= 7.1.3.4)
activejob (= 7.1.3.4)
activemodel (= 7.1.3.4)
activerecord (= 7.1.3.4)
activestorage (= 7.1.3.4)
activesupport (= 7.1.3.4)
bundler (>= 1.15.0)
railties (= 7.1.3.4)
rails-dom-testing (2.2.0)
activesupport (>= 5.0.0)
minitest
nokogiri (>= 1.6)
rails-html-sanitizer (1.6.0)
loofah (
> 2.21)
nokogiri (> 1.14)
railties (7.1.3.4)
actionpack (= 7.1.3.4)
activesupport (= 7.1.3.4)
irb
rackup (>= 1.0.0)
rake (>= 12.2)
thor (
> 1.0, >= 1.2.2)
zeitwerk (> 2.6)
rainbow (3.1.1)
rake (13.2.1)
ransack (4.1.1)
activerecord (>= 6.1.5)
activesupport (>= 6.1.5)
i18n
rdoc (6.7.0)
psych (>= 4.0.0)
redis (4.8.1)
redis-client (0.22.2)
connection_pool
reform (2.6.2)
disposable (>= 0.5.0, < 1.0.0)
representable (>= 3.1.1, < 4)
uber (< 0.2.0)
regexp_parser (2.9.2)
regexp_property_values (1.5.2)
reline (0.5.9)
io-console (
> 0.5)
representable (3.2.0)
declarative (< 0.1.0)
trailblazer-option (>= 0.1.1, < 0.2.0)
uber (< 0.2.0)
request_store (1.7.0)
rack (>= 1.4)
responders (3.1.1)
actionpack (>= 5.2)
railties (>= 5.2)
rexml (3.3.1)
strscan
roda (3.81.0)
rack
rolify (6.0.1)
roo (2.10.1)
nokogiri (> 1)
rubyzip (>= 1.3.0, < 3.0.0)
rouge (4.3.0)
rspec-core (3.13.0)
rspec-support (
> 3.13.0)
rspec-expectations (3.13.1)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (> 3.13.0)
rspec-mocks (3.13.1)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (
> 3.13.0)
rspec-rails (6.1.3)
actionpack (>= 6.1)
activesupport (>= 6.1)
railties (>= 6.1)
rspec-core (> 3.13)
rspec-expectations (
> 3.13)
rspec-mocks (> 3.13)
rspec-support (
> 3.13)
rspec-support (3.13.1)
rswag-api (2.13.0)
activesupport (>= 3.1, < 7.2)
railties (>= 3.1, < 7.2)
rswag-ui (2.13.0)
actionpack (>= 3.1, < 7.2)
railties (>= 3.1, < 7.2)
rubocop (1.64.1)
json (> 2.3)
language_server-protocol (>= 3.17.0)
parallel (
> 1.10)
parser (>= 3.3.0.2)
rainbow (>= 2.2.2, < 4.0)
regexp_parser (>= 1.8, < 3.0)
rexml (>= 3.2.5, < 4.0)
rubocop-ast (>= 1.31.1, < 2.0)
ruby-progressbar (> 1.7)
unicode-display_width (>= 2.4.0, < 3.0)
rubocop-ast (1.31.3)
parser (>= 3.3.1.0)
rubocop-performance (1.21.1)
rubocop (>= 1.48.1, < 2.0)
rubocop-ast (>= 1.31.1, < 2.0)
rubocop-rails (2.25.0)
activesupport (>= 4.2.0)
rack (>= 1.1)
rubocop (>= 1.33.0, < 2.0)
rubocop-ast (>= 1.31.1, < 2.0)
rubocop-rspec (3.0.1)
rubocop (
> 1.61)
ruby-ole (1.2.13.1)
ruby-openai (7.1.0)
event_stream_parser (>= 0.3.0, < 2.0.0)
faraday (>= 1)
faraday-multipart (>= 1)
ruby-progressbar (1.13.0)
ruby-rc4 (0.1.5)
ruby-vips (2.2.1)
ffi (> 1.12)
ruby2_keywords (0.0.5)
ruby_http_client (3.5.5)
rubyzip (2.3.2)
rufus-scheduler (3.9.1)
fugit (
> 1.1, >= 1.1.6)
sablon (0.4.0)
nokogiri (>= 1.8.5)
rubyzip (>= 1.3.0)
safely_block (0.4.0)
sanitize_email (2.0.7)
mail (> 2.0)
version_gem (
> 1.1, >= 1.1.4)
sassc (2.4.0)
ffi (> 1.9)
sassc-rails (2.1.2)
railties (>= 4.0.0)
sassc (>= 2.0)
sprockets (> 3.0)
sprockets-rails
tilt
scout_apm (5.3.8)
parser
selectize-rails (0.12.6)
selenium-webdriver (4.10.0)
rexml (
> 3.2, >= 3.2.5)
rubyzip (>= 1.2.2, < 3.0)
websocket (> 1.0)
sendgrid-ruby (6.7.0)
ruby_http_client (
> 3.4)
shrine (3.6.0)
content_disposition (> 1.0)
down (
> 5.1)
sidekiq (7.2.4)
concurrent-ruby (< 2)
connection_pool (>= 2.3.0)
rack (>= 2.2.4)
redis-client (>= 0.19.0)
sidekiq-scheduler (5.0.3)
rufus-scheduler (> 3.2)
sidekiq (>= 6, < 8)
tilt (>= 1.4.0)
simplecov (0.22.0)
docile (
> 1.1)
simplecov-html (> 0.11)
simplecov_json_formatter (
> 0.1)
simplecov-html (0.12.3)
simplecov_json_formatter (0.1.4)
smart_properties (1.17.0)
solid_cache (0.6.0)
activejob (>= 7)
activerecord (>= 7)
railties (>= 7)
sorbet-runtime (0.5.11444)
spreadsheet (1.3.1)
bigdecimal
ruby-ole
sprockets (4.2.1)
concurrent-ruby (> 1.0)
rack (>= 2.2.4, < 4)
sprockets-rails (3.5.1)
actionpack (>= 6.1)
activesupport (>= 6.1)
sprockets (>= 3.0.0)
sshkit (1.23.0)
base64
net-scp (>= 1.1.2)
net-sftp (>= 2.1.2)
net-ssh (>= 2.8.0)
stimulus-rails (1.3.3)
railties (>= 6.0.0)
stringio (3.1.1)
strscan (3.1.0)
sys-uname (1.3.0)
ffi (
> 1.1)
thor (1.3.1)
tilt (2.3.0)
timeout (0.4.1)
to_words (1.1.1)
trailblazer (2.1.3)
trailblazer-activity-dsl-linear (>= 1.2.3, < 1.3.0)
trailblazer-developer (>= 0.1.0, < 0.2.0)
trailblazer-macro (>= 2.1.15, < 2.2.0)
trailblazer-macro-contract (>= 2.1.4, < 2.2.0)
trailblazer-operation (>= 0.9.0, < 1.0.0)
trailblazer-activity (0.16.2)
trailblazer-context (> 0.5.0)
trailblazer-option (
> 0.1.0)
trailblazer-activity-dsl-linear (1.2.5)
trailblazer-activity (>= 0.16.0, < 0.17.0)
trailblazer-declarative (>= 0.0.1, < 0.1.0)
trailblazer-context (0.5.1)
hashie (>= 3.0.0)
trailblazer-declarative (0.0.2)
trailblazer-developer (0.1.0)
hirb
trailblazer-activity-dsl-linear (>= 1.2.0, < 1.3.0)
trailblazer-macro (2.1.15)
trailblazer-activity-dsl-linear (>= 1.2.0, < 1.3.0)
trailblazer-operation (>= 0.10.1)
trailblazer-macro-contract (2.1.5)
reform (>= 2.2.0, < 3.0.0)
trailblazer-activity-dsl-linear (>= 1.2.0, < 1.3.0)
trailblazer-operation (0.10.1)
trailblazer-activity-dsl-linear (>= 1.2.0, < 1.4.0)
trailblazer-developer (>= 0.1.0, < 0.2.0)
trailblazer-option (0.1.2)
tty-color (0.6.0)
tty-cursor (0.7.1)
tty-prompt (0.23.1)
pastel (> 0.8)
tty-reader (
> 0.8)
tty-reader (0.9.0)
tty-cursor (> 0.7)
tty-screen (
> 0.8)
wisper (> 2.0)
tty-screen (0.8.2)
turbo-rails (2.0.5)
actionpack (>= 6.0.0)
activejob (>= 6.0.0)
railties (>= 6.0.0)
tzinfo (2.0.6)
concurrent-ruby (
> 1.0)
uber (0.1.0)
unicode-display_width (2.5.0)
uniform_notifier (1.16.0)
uppy-s3_multipart (1.2.1)
aws-sdk-s3 (> 1.0)
content_disposition (
> 1.0)
roda (>= 2.27, < 4)
uri (0.13.0)
version_gem (1.1.4)
view_component (3.12.1)
activesupport (>= 5.2.0, < 8.0)
concurrent-ruby (> 1.0)
method_source (
> 1.0)
warden (1.2.9)
rack (>= 2.0.9)
web-console (4.2.1)
actionview (>= 6.0.0)
activemodel (>= 6.0.0)
bindex (>= 0.4.0)
railties (>= 6.0.0)
webdrivers (5.3.1)
nokogiri (> 1.6)
rubyzip (>= 1.3.0)
selenium-webdriver (
> 4.0, < 4.11)
webrick (1.8.1)
websocket (1.2.10)
websocket-driver (0.7.6)
websocket-extensions (>= 0.1.0)
websocket-extensions (0.1.5)
whenever (1.0.0)
chronic (>= 0.6.3)
wisper (2.0.1)
xpath (3.2.0)
nokogiri (~> 1.8)
yajl-ruby (1.4.3)
zeitwerk (2.6.16)

PLATFORMS
x86_64-linux

DEPENDENCIES
active_flag
active_storage_validations
activerecord-import (= 1.5.0)
acts_as_favoritor!
acts_as_list
administrate
administrate-field-active_storage
administrate-field-boolean_emoji
administrate-field-shrine
after_commit_action
ajax-datatables-rails
ancestry
annotate
audited (> 5.0)
awesome_print
aws-sdk-ec2
aws-sdk-s3
better_errors
binding_of_caller
blazer (
> 2.6)
bootsnap (= 1.15.0)
brakeman
bullet
bundler-audit
capistrano (> 3.5)
capistrano-bundler (
> 2.0)
capistrano-rails
capistrano-rvm
capistrano-sidekiq!
capistrano3-puma
capybara
capybara-email
caxlsx
caxlsx_rails
chartkick
chewy
client_side_validations
cocoon
combine_pdf
counter_culture
cucumber-rails
database_cleaner-active_record
debug
devise
devise-api!
dotenv-rails
draper
enumerize
erubis
eu_central_bank
exception_notification
factory_bot_rails
faker
foreman
friendly_id (> 5.5.0)
groupdate
has_scope
hightop
htmlbeautifier
httparty
image_processing (
> 1.2)
importmap-rails
jbuilder
json2table
kaminari
letter_opener
libreconv
mail-logger
marginalia (> 1.11)
memoized
money-rails (
> 1.12)
mysql2 (> 0.5)
net-ssh
noticed
overcommit (
> 0.58.0)
packs-rails
paranoia
pry-byebug
puma (> 5.0)
pundit!
rack-attack
rails (= 7.1.3.4)
ransack
redis (
> 4.0)
rolify
roo
rspec-rails
rswag-api
rswag-ui
rubocop-performance
rubocop-rails
rubocop-rspec
ruby-openai
rubyXL!
rubyzip
rupees!
sablon
sanitize_email
sassc-rails
scout_apm
selenium-webdriver
sendgrid-ruby
shrine (> 3.3)
sidekiq
sidekiq-scheduler
simplecov
solid_cache (= 0.6.0)
spreadsheet
sprockets-rails
stimulus-rails
to_words
trailblazer
turbo-rails
tzinfo-data
uppy-s3_multipart (
> 1.1)
view_component
web-console
webdrivers
whenever
xirr!
yajl-ruby

BUNDLED WITH
2.3.12

Remove ActiveRecord query cache workarounds

We don't want Solid Cache to interact with the ActiveRecord query cache at all.

So results should not be cached (which we achieve by wrapping queries in uncached blocks, but also we should avoid invalidating the AR query cache when we write).

At the moment this is done by dropping down to lower level methods that are not wrapped with dirties_query_cache.

This is not ideal and is relying on undocumented behaviour that could change in future releases.

Instead I think we need a way to disable the query cache invalidation in Rails. It would be nice to be able to set something on SolidCache::Record to say it doesn't use the query cache at all, but I don't think that will work as the query cache is a connection based, not model based.

We probably need another method similar to uncached that can totally disable the query cache.

Undefined method map for nil when `config.active_record.query_log_tags_enabled` is true

On Rails 7.1, when setting config.active_record.query_log_tags_enabled to true, I get this error:

undefined method map' for nil`

        def type_casted_binds(binds)
          binds.map do |value|
            if ActiveModel::Attribute === value
              type_cast(value.value_for_database)
            else

in [activerecord (7.1.2) lib/active_record/connection_adapters/abstract/quoting.rb:235:in type_casted_binds']`

We always go into this line:

 result = connection.select_all(sanitize_sql([ query, values ]), "#{name} Load", nil, preparable: false)

and binds are nil because connection.prepare_statements? is false, due to https://github.com/rails/rails/blob/581074c40d0989ff1d981086043b40dc8daa4888/activerecord/lib/active_record/railtie.rb#L422

Do you recommend using a separate database from your application database as the cache store?

Hi there! We are toying with putting this on staging and seeing how it works (and thank you to the creators, by the way - this is an excellent cost saver for those of us sick of paying through the nose for huge Redis instances!). Before we try it out, though, I thought it would be a good idea to check on a "recommended' setup.

No warranty implied, obviously; but I'm curious if anyone has real-world experience they could point to? Did anyone find that using their application database as a cache store caused a significant enough increase in load that it caused you issues? Was it something you found you could gradually move to, or is the infrastructure change something you'd recommend right away? Not a huge deal either way, but if any real-world users have any insight, I'd be super grateful!

I realize, of course, that no two production environments are identical. I'm just curious what other peoples' experience has been.

Thanks again for everything!

Encrypts can’t decrypt binary column?

Tried to use encryption on Rails 7.0.2 for my solid cache. Seems like it doesn’t work properly, it encrypts and stores the rows but it can’t be decrypted. It raises exception ActiveRecord::Encryption::Errors::Decrypt.

I changed my value column to be a text and that solves the issue but not sure that is the correct approach? Should it work with the binary column that comes with the default migration? In that case, am I missing some configuration that is not specified in the readme? 🤷‍♂️

Using postgresql.

All the best,
Emil

Working migration

class CreateSolidCacheEntries < ActiveRecord::Migration[7.0]
  def change
    create_table :solid_cache_entries do |t|
      t.binary :key, null: false, limit: 1024
      t.text :value, null: false, limit: 512.megabytes
      t.datetime :created_at, null: false

      t.index :key, unique: true
    end
  end
end

Using Solid Cache for low-level caching produces `warning -- already initialized constant`

I'm trying to cache some query data using Rails "low level caching" and Solid Cache - pretty simple stuff. I'm doing it by the book.

My caching method as a model class method:

class Location < ApplicationRecord
  def self.location_name_cache
    Rails.cache.fetch(:location_names, expires_in: 15.minutes) do
      (Location.pluck(:name) + Observation.pluck(:where)).uniq
    end
  end

  # Check if a given place name already exists, defined as a Location name string.
  def self.location_name_exists(name)
    location_name_cache.member?(name)
  end

When I unit-test my class method (using Minitest), although the tests pass, i'm getting these warnings:

warning: already initialized constant SolidCache::Record::NULL_INSTRUMENTER
warning: previous definition of NULL_INSTRUMENTER was here

I feel like this is likely a bug.

Rails Edge breaking change with insert_all

Since rails/rails@1c0982d has been merged, activerecord/lib/active_record/insert_all.rb now expects connection to be passed in as an argument.

Current code:

def upsert_all_no_query_cache(payloads)
  insert_all = ActiveRecord::InsertAll.new(
    self,
    add_key_hash_and_byte_size(payloads),
    unique_by: upsert_unique_by,
    on_duplicate: :update,
    update_only: upsert_update_only
  )
  sql = connection.build_insert_sql(ActiveRecord::InsertAll::Builder.new(insert_all))

  message = +"#{self} "
  message << "Bulk " if payloads.many?
  message << "Upsert"
  # exec_query_method does not clear the query cache, exec_insert_all does
  connection.send exec_query_method, sql, message
end

For Rails Edge, it would need to be changed to:

def upsert_all_no_query_cache(payloads)
  insert_all = ActiveRecord::InsertAll.new(
    self,
    connection,
    add_key_hash_and_byte_size(payloads),
    unique_by: upsert_unique_by,
    on_duplicate: :update,
    update_only: upsert_update_only
  )
  sql = connection.build_insert_sql(ActiveRecord::InsertAll::Builder.new(insert_all))

  message = +"#{self} "
  message << "Bulk " if payloads.many?
  message << "Upsert"
  # exec_query_method does not clear the query cache, exec_insert_all does
  connection.send exec_query_method, sql, message
end

I am happy to work on a PR for this, but I would need some guidance on the desired approach in dealing with the different Rails versions within this gem. I don't suspect we'd want an if condition inside the upsert_all_no_query_cache method.

Require a little more clarity on the Upgrade ReadMe

I wonder if you can provide a little more clarity on the steps to upgrade to 0.4.0 README. In a typical production environment, are you suggesting at each numbered step is followed by a deploy to production?

For example:

  1. Upgrade gem to 0.4.0, adjust key_hash_stage, deploy to production
  2. Run AddKeyHashAndByteSizeToSolidCacheEntries migration, deploy to production
  3. Update config, deploy to production
    ...

Or can all the steps be followed and executed in development, committed and deployed in their entirety in production?

I'm happy to update the Readme once I understand the correct approach.
Thanks!

Undefined method error on Rails 7.2

I'm getting undefined method 'model' for class SolidCache::Entry on Rails 7.2.

I tried it on a brand new Rails project and just did gem "rails", github: "rails/rails", branch: "7-2-stable"

It happens whether I do <%= cache%> in a view or Rails.cache.fetch

SIEVE eviction algorithm support

This crossed my mind off the back of reading through rails/rails#50443 and in particular @byroot's concerns of SolidCache relying on FIFO for evictions.

Doing the rounds on HN recently is a new algorithm for cache invalidation called SIEVE. Compared to FIFO it significantly improves cache hit ratios. Compared to LRU it significantly reduces cache write frequency. Most importantly, its a very simple algorithm that in the paper is laid out in just 16 lines of pseudo-code.

I thought those qualities sounded like rather a good fit for solving the concerns laid out in rails/rails#50443. This really isn't my area of expertise, but I've spent some time taking a stab at sketching out what this algorithm might look like in SolidCache with minimal changes to the existing structure.

I have not yet got as far as actually implementing it and measuring performance. Before I go that far, is there appetite for such a change / addition to the gem?

Reduce queries when expiring old cache entries

When removing expired entries, this

def expire(count, max_age:, max_entries:)
if (ids = expiry_candidate_ids(count, max_age: max_age, max_entries: max_entries)).any?
delete(ids)
end
end
and this
def expiry_candidate_ids(count, max_age:, max_entries:)
cache_full = max_entries && max_entries < id_range
min_created_at = max_age.seconds.ago
uncached do
order(:id)
.limit(count * 3)
.pluck(:id, :created_at)
.filter_map { |id, created_at| id if cache_full || created_at < min_created_at }
.sample(count)
end
end
perform 2 SQL queries - one to get ids, another - to delete by ids. There is also a sampling on which items from the first n satisfying to delete. Is this sampling really needed? If no, then we can easily rewrite it to perform only 1 SQL query. Even if yes, we can do something like order by random(), but that is not very portable.

Can help with PR.

Bug with prepared_statements: false and read_multi

Works as you would expect:

Rails.cache.write('foo', 'bar')
Rails.cache.read_multi('foo') # => {"foo"=>"bar"}

But this does not:

SolidCache::Record.connection.unprepared_statement do
  Rails.cache.write('foo', 'bar')
  Rails.cache.read_multi('foo') # => {}
end

Looking at the SQL logs. This is with prepared statements:

SolidCache::Entry Upsert (0.7ms)  INSERT INTO "solid_cache_entries" ("key","value","key_hash","byte_size","created_at") VALUES ('\x646576656c6f706d656e743a666f6f', '\x0004085b06492208626172063a064554', -4375748427727277429, 171, CURRENT_TIMESTAMP) ON CONFLICT ("key_hash") DO UPDATE SET "key"=excluded."key","value"=excluded."value","byte_size"=excluded."byte_size" RETURNING "id"
SolidCache::Entry Load (0.1ms)  SELECT "solid_cache_entries"."key", "solid_cache_entries"."value" FROM "solid_cache_entries" WHERE "solid_cache_entries"."key_hash" = $1  [[nil, -4375748427727277429]]

And this is without:

SolidCache::Entry Upsert (1.4ms)  INSERT INTO "solid_cache_entries" ("key","value","key_hash","byte_size","created_at") VALUES ('\x646576656c6f706d656e743a666f6f', '\x0004085b06492208626172063a064554', -4375748427727277429, 171, CURRENT_TIMESTAMP) ON CONFLICT ("key_hash") DO UPDATE SET "key"=excluded."key","value"=excluded."value","byte_size"=excluded."byte_size" RETURNING "id"
SolidCache::Entry Load (0.8ms)  SELECT "solid_cache_entries"."key", "solid_cache_entries"."value" FROM "solid_cache_entries" WHERE "solid_cache_entries"."key_hash" IN ($1, $2)  [[nil, -4375748427727277429]]

I noticed that without prepared statements WHERE "solid_cache_entries"."key_hash" IN ($1, $2) you have two bind parameters, but only pass one value. PostgreSQL is equally unhappy by that:

CleanShot 2024-07-01 at 20 56 18@2x

Resulting in the failure.

Must surely be something wrong with this line:
https://github.com/rails/solid_cache/blob/main/app/models/solid_cache/entry.rb#L119

uninitialized constant SolidCache::Store::Entries::Entry

I'm using capistrano to deploy my app, and during db:migrate stage, I'm getting the following error when trying to deploy to production

NameError: uninitialized constant SolidCache::Store::Entries::Entry (NameError)
            Entry.read(key)
            ^^^^^
Did you mean?  Sentry

Anyone know why this might be?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.