Solid Cache

repository·main·Indexed 21 days ago

https://github.com/rails/solid_cache

A database-backed Active Support cache store for Rails designed to provide larger cache capacities than memory-only stores like Redis or Memcached by utilizing modern SSD speeds. It supports sharding across multiple databases using Maglev consistent hashing, optional value encryption via Active Record Encryption, and configurable expiry methods including background threads or jobs.

Tokens
5.5K
Snippets
19
Records
23
Agent score
76%

What's inside solid_cache

  1. Enable encryption for Solid Cache values

    main

    To encrypt cache values, enable the encrypt property. This requires your application to be configured to use Active Record Encryption.

    Via config/cache.yml:

    production:
      encrypt: true

    Via Rails configuration:

    # application.rb
    config.solid_cache.encrypt = true

    Solid Cache uses a custom encryptor and ActiveRecord::Encryption::MessagePackMessageSerializer by default, which is optimized for binary columns and can store ~40% more data than the standard serializer.

    # config/cache.yml
    production:
      encrypt: true
  2. Configure the Solid Cache database in `database.yml`

    main

    After installation, you must define the cache database connection in config/database.yml.

    For SQLite:

    production:
      primary:
        <<: *default
        database: storage/production.sqlite3
      cache:
        <<: *default
        database: storage/production_cache.sqlite3
        migrations_paths: db/cache_migrate

    For MySQL/PostgreSQL/Trilogy:

    production:
      primary: &primary_production
        <<: *default
        database: app_production
        username: app
        password: <%= ENV["APP_DATABASE_PASSWORD"] %>
      cache:
        <<: *primary_production
        database: app_production_cache
        migrations_paths: db/cache_migrate

    Once configured, run bin/rails db:prepare in production to create the database and load the schema.

    # Example SQLite configuration in config/database.yml
    production:
      primary:
        <<: *default
        database: storage/production.sqlite3
      cache:
        <<: *default
        database: storage/production_cache.sqlite3
        migrations_paths: db/cache_migrate
  3. Migrate Solid Cache schema from v0.3.x or lower to v0.4.x

    main

    If you are using Solid Cache v0.3.x or lower, you must perform a multi-step upgrade to transition to the new schema (which uses key_hash for indexing and adds a byte_size column).

    CRITICAL: You cannot upgrade directly from v0.3.x or lower to v0.5.x or higher. You must go via v0.4.x first.

    Upgrade Workflow

    1. Update Gem and Config: Upgrade to gem version v0.4.x and set config.solid_cache.key_hash_stage = :ignored in your configuration.
    2. Install Migrations: Run bin/rails solid_cache:install:migrations to copy the new migrations into your project.
    3. Apply First Migration: Run the migration AddKeyHashAndByteSizeToSolidCacheEntries to add the new columns.
    4. Enable Population: Update your config to config.solid_cache.key_hash_stage = :unindexed. This allows the application to start populating the new columns.
    5. Prepare Data: Ensure there are no NULL values in the new columns. You can either:
      • Truncate: Truncate the solid_cache_entries table (this invalidates your entire cache).
      • Backfill: Run a backfill script (see Backfill script for missing columns) to populate values without invalidating the cache.
    6. Apply Second Migration: Run the migration AddKeyHashAndByteSizeIndexesAndNullConstraintsToSolidCacheEntries. This adds constraints and indexes.
    7. Enable Indexing: Update your config to config.solid_cache.key_hash_stage = :indexed (or remove the setting, as this is the default). The application will now query via key_hash.
    8. Cleanup: Run the final migration RemoveKeyIndexFromSolidCacheEntries to remove the old index on the key column.
  4. Shard the Solid Cache across multiple databases

    main

    Solid Cache uses the Maglev consistent hashing scheme to shard data across multiple databases. To implement sharding:

    1. Add the shard databases to config/database.yml.
    2. Configure the shards in config/cache.yml using the databases key.

    Example config/database.yml:

    production:
      cache_shard1:
        database: cache1_production
      cache_shard2:
        database: cache2_production

    Example config/cache.yml:

    production:
      databases: [cache_shard1, cache_shard2]
    # config/cache.yml
    production:
      databases: [cache_shard1, cache_shard2]
  5. Install Solid Cache in a Rails application

    main

    If you are using Rails 8, Solid Cache is configured by default. For earlier versions, follow these steps to add it manually:

    1. Add the gem to your bundle:
      bundle add solid_cache
    2. Run the installer to configure the production cache store and create config/cache.yml:
      bin/rails solid_cache:install

    The installer will create either db/cache_schema.rb (for :ruby schema format) or db/cache_structure.sql (for :sql schema format).

    bundle add solid_cache
    bin/rails solid_cache:install
  6. Understand the structure of connection statistics

    main

    When calling stats on a SolidCache::Store, the connection_stats sub-hash provides per-shard metrics. Each shard's data includes:

    • max_age: The maximum age of an entry in the shard.
    • oldest_age: The time elapsed since the oldest entry was created (Time.now - oldest_created_at). Returns nil if the shard is empty.
    • max_entries: The maximum number of entries allowed in the shard.
    • entries: An id_range representing the range of entry IDs currently present in the shard.
  7. Configure Solid Cache via `config/cache.yml`

    main

    Solid Cache reads configuration from config/cache.yml or config/solid_cache.yml. You can override the config file location using the SOLID_CACHE_CONFIG environment variable.

    Example configuration structure:

    default:
      store_options: &default_store_options
        max_age: <%= 60.days.to_i %>
        namespace: <%= Rails.env %>
      size_estimate_samples: 1000
    
    development: &development
      database: cache
      store_options:
        <<: *default_store_options
        max_size: <%= 256.gigabytes %>
    
    production: &production
      databases: [production_cache1, production_cache2]
      store_options:
        <<: *default_store_options
        max_size: <%= 256.gigabytes %>
    # Example config/cache.yml
    default:
      store_options: &default_store_options
        max_age: <%= 60.days.to_i %>
        namespace: <%= Rails.env %>
      size_estimate_samples: 1000
    
    development: &development
      database: cache
      store_options:
        <<: *default_store_options
        max_size: <%= 256.gigabytes %>
    
    production: &production
      databases: [production_cache1, production_cache2]
      store_options:
        <<: *default_store_options
        max_size: <%= 256.gigabytes %>
  8. Install Solid Cache using the Rails generator

    main

    To install Solid Cache in a Rails application, run the provided generator. This will:

    1. Create a config/cache.yml configuration file.
    2. Create the necessary database schema files (either db/cache_schema.rb or a .sql file depending on your active_record.schema_format).
    3. Automatically update config/environments/production.rb to set the cache store to :solid_cache_store.

    Note: If your application uses schema_format = :sql, the generator supports the following database adapters:

    • PostgreSQL (postgresql)
    • MySQL (mysql2, trilogy)
    • SQLite (sqlite3)
    rails generate solid_cache:install
  9. Backfill script for missing columns

    main

    If you cannot truncate your solid_cache_entries table during the v0.4.x upgrade, use this script to populate the missing key_hash and byte_size values. This script iterates through entries in batches and uses SolidCache::Entry.write_multi to update them.

    Note: This script is specific to the migration path from v0.3.x to v0.4.x.

    def populate_key_hash_and_byte_size(from_id: nil, to_id: nil, batch_size: 1000, pause: 0)
      SolidCache::Entry.where(id: (from_id..to_id)).find_in_batches(batch_size: batch_size) do |entries|
        updates = entries.map { |entry| { key: entry.key, value: entry.value } }
    
        SolidCache::Entry.write_multi(updates)
        sleep pause unless pause.zero?
    
        puts "Updated to SolidCache::Entry##{entries.last.id}"
      end
    end
  10. Configure Solid Cache engine settings in Rails

    main

    You can configure engine-level settings within a Rails.application.configure block:

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

    Available engine options:

    • executor: The Rails executor used for async operations (defaults to app executor).
    • connects_to: Custom connects_to value for SolidCache::Record. Overwrites config/solid_cache.yml.
    • size_estimate_samples: Number of samples used to estimate size if max_size is set.
    • encrypted: Boolean indicating if cache values should be encrypted.
    • encryption_context_properties: Custom encryption context properties.
  11. Enable encryption for Solid Cache

    main

    You can enable encryption for cached data by setting encrypt to true in the Solid Cache configuration.

    When encryption is enabled, Solid Cache uses a default encryption context consisting of:

    1. An ActiveRecord::Encryption::Encryptor configured with compress: false (since the cache layer handles compression).
    2. An ActiveRecord::Encryption::MessagePackMessageSerializer, which is a binary-column-only serializer designed to be 40% more efficient than the default MessageSerializer.

    You can override these defaults by providing your own encryption_context_properties hash.

    SolidCache.configure do |config|
      config.encrypt = true
      # Optional: override default encryption properties
      # config.encryption_context_properties = { ... }
    end
  12. Configure cache shards in Solid Cache

    main

    When initializing the SolidCache::Store, you can configure sharding by providing a :shards option. The :shards option must be an Array or nil.

    Note that the :clusters and :cluster options are deprecated. If you use them, the library will attempt to extract the :shards configuration from them and issue a deprecation warning. It is recommended to migrate to the :shards key directly.

    Multiple clusters are no longer supported; you should define your shards within a single cluster configuration.

    # Recommended way using :shards
    SolidCache::Store.new(shards: [{ name: :shard_one, connection: :db_one }, { name: :shard_two, connection: :db_two }])
    
    # Deprecated way (will warn and use :shards from the first cluster)
    SolidCache::Store.new(clusters: [{ shards: [{ name: :shard_one, connection: :db_one }] }])