Solid Cache README: DBベースのキャッシュストア(翻訳)
Solid Cacheは、データベースを保存場所として利用するActive Supportキャッシュストアで、従来のRedisやMemcachedストアのようなメモリのみで用いられることが多いキャッシュストアよりもずっと大きなキャッシュを保持できます。最新のSSDドライブは高速なので、ほとんどのキャッシュ目的では、ディスクとRAMの違いによるアクセス時間のペナルティは重要ではありません。簡単に言えば、メモリ内に小さなキャッシュを配置するよりも、ディスク上に巨大なキャッシュを保持する方が通常は効果的です。
🔗 インストール方法
Rails 8の新規アプリケーションでは、デフォルトでSolid Cacheが設定されます。ただし、それより前のバージョンのRailsを実行している場合は、以下の手順に沿うことで手動で追加できます。
bundle add solid_cache
bin/rails solid_cache:install
上のコマンドを実行することで、Solid Cacheがproduction向けのキャッシュストアとして設定され、config/cache.yml
とdb/cache_schema.rb
が作成されます。
次に、config/database.yml
ファイルにキュー用のデータベース設定を追加する必要があります。SQLiteを使っている場合は、以下のような設定になります。
production:
primary:
<<: *default
database: storage/production.sqlite3
cache:
<<: *default
database: storage/production_cache.sqlite3
migrations_paths: db/cache_migrate
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
設定が終わったら、db:prepare
を実行して、データベースの作成とスキーマ読み込みが行われるようにしてください。
🔗 設定方法
config/cache.yml
設定ファイルまたはconfig/solid_cache.yml
設定ファイルが読み込まれます。このファイルの置き場所はSOLID_CACHE_CONFIG
環境変数で変更できます。
設定ファイルのフォーマットは以下のとおりです。
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 %>
store_options
の完全なリストについては、後述のキャッシュの設定を参照してください。キャッシュ探索に渡したオプションは、この設定ファイルで指定したオプションを上書きします。
🔗 コネクションの設定方法
この設定ファイルには、database
、databases
、connects_to
のいずれか1つを設定できます。これらは、SolidCache::Record#connects_to
でキャッシュデータベースを設定するのに使われます。
database
にcache_db
を指定すると、以下のように設定されます。
SolidCache::Record.connects_to database: { writing: :cache_db }
databases
に[cache_db, cache_db2]
を指定することは、以下と同等です。
SolidCache::Record.connects_to shards: { cache_db1: { writing: :cache_db1 }, cache_db2: { writing: :cache_db2 } }
connects_to
が設定されている場合は、直接渡されます。
上記のどの項目も設定されていない場合、Solid CacheはActiveRecord::Base
コネクションプールを利用します。この設定は、キャッシュの読み出しと書き込みが、データベーストランサクションの一部としてラップされることを意味します。
🔗 エンジンの設定方法
エンジンには以下の5つのオプションを設定できます。
executor
- RailsのExecutorを利用して非同期操作をラップします。デフォルトはアプリのExecutorです。
connects_to
- Active Recordの
SolidCache::Record
抽象モデルで利用するカスタム接続先の値です。メインアプリでシャーディングや別のキャッシュデータベースを利用する場合に必要です。このオプションを指定することでconfig/solid_cache.yml
ファイルの値が上書きされます。 size_estimate_samples
- キャッシュに
max_size
が設定されている場合、サイズの推定に利用するサンプル数を指定します。 encrypted
- キャッシュ値を暗号化すべきかどうかを指定します(詳しくは暗号化を有効にするを参照)。
encryption_context_properties
- 暗号化コンテキストのカスタムプロパティです。
これらのオプションは、以下のようにRailsの設定ファイルで指定できます。
Rails.application.configure do
config.solid_cache.size_estimate_samples = 1000
end
🔗 キャッシュの設定方法
Solid Cacheでは、標準のActiveSupport::Cache::Store
にあるオプションに加えて、以下のオプションもサポートしています。
error_handler
- 発生したすべての
ActiveRecord::ActiveRecordError
を処理するために呼び出すProcを指定します(デフォルト: エラーを"warning"としてログ出力する)。 expiry_batch_size
- 古いレコードを削除するときのバッチサイズ(デフォルト:
100
) expiry_method
- 期限を失効させる方法を
:thread
または:job
で指定します(デフォルト::thread
)。 expiry_queue
- 失効ジョブをどのキューに追加するかを指定します(デフォルト:
:default
) max_age
- キャッシュ内でエントリが失効せずに持続する最大期間(デフォルト:
2.weeks.to_i
)。nil
に設定することも可能ですが、次のmax_entries
でキャッシュサイズの上限を設定しない限り利用は推奨されません。 max_entries
- キャッシュに保存できるエントリの最大数を指定します(デフォルト:
nil
-- 上限なしを意味する)。 max_size
- キャッシュエントリの最大サイズを指定します(デフォルト:
nil
-- 上限なしを意味する)。 cluster
(非推奨化)- キャッシュデータベースのクラスタに渡すオプションをハッシュで指定します(例:
{ shards: [:database1, :database2, :database3] }
)。 clusters
(非推奨化)- 複数のキャッシュクラスタに渡すオプションをハッシュの配列で指定します(
:cluster
オプションが設定済みの場合は無視されます)。 shards
- データベースの配列またはハッシュを指定します。
active_record_instrumentation
- キャッシュのクエリをinstrumentation(計測)の対象にするかどうかを指定します(デフォルト:
true
)。 clear_with
- キャッシュを
:truncate
と:delete
のどちらでクリアするかを指定します(デフォルトは:truncate
ですが、Rails.env.test?
の場合は:delete
が使われます)。 max_key_bytesize
- 正規化されたキーの最大サイズをバイト単位で指定します(デフォルト:
1024
)。
キャッシュクラスタについて詳しくは、キャッシュをシャーディングするを参照してください。
🔗 キャッシュの失効
Solid Cacheは、キャッシュへの書き込みをトラッキングします。書き込みごとにカウンタが1ずつ増加し、カウンタがexpiry_batch_size
の50%に達すると、バックグラウンドスレッドで実行するタスクを追加します。このタスクでは以下を行います。
max_entries
またはmax_size
が設定済みの場合は、これらの値を超えたかどうかをチェックする
現在のエントリは、SolidCache::Entry
テーブルに登録されている最大のIDから最小のIDを減算することで推測します。
現在のサイズは、エントリのbyte_size
カラムをサンプリングすることで推測します。-
値を超えた場合は、
expiry_batch_size
の個数分のエントリを削除する - 超えていない場合は、
max_age
より長い期間存在しているエントリを最大expiry_batch_size
の個数分削除する
バッチサイズの50%の個数に達したら失効処理を行うようになっています。これにより、キャッシュサイズを削減する必要が生じたときに、キャッシュの書き込みよりも早期のタイミングでレコード上のキャッシュを失効させることが可能になります。
失効処理をトリガーするタイミングを書き込み時のみに限定しているので、キャッシュがアイドリング状態であればバックグラウンドのスレッドもアイドリング状態になります。
キャッシュの失効処理をスレッドではなくバックグラウンドで行いたい場合は、 expiry_method
に:job
を指定します。これにより、SolidCache::ExpiryJob
がエンキューされるようになります。
🔗 キャッシュをシャーディングする
Solid Cacheでは、複数データベースにわたるキャッシュのシャーディング(sharding)で、Maglevの一貫したハッシュスキームを利用しています。
シャーディングを行う手順は次のとおりです。
database.yml
ファイルにデータベースシャーディング用の設定を追加するconfig.solid_cache.connects_to
でシャーディングを設定する- キャッシュのシャードをクラスタオプション経由で渡す
例:
# 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/cache.yml
production:
databases: [cache_shard1, cache_shard2, cache_shard3]
追記(2024/10/21)
以下は削除されました。
🔗 セカンダリのキャッシュクラスタ
セカンダリのキャッシュクラスタを追加可能です。読み取りはプライマリクラスタ(=リストの冒頭にあるクラスタ)だけに送信されます。
書き込みはすべてのクラスタに対して行われます。プライマリクラスタへの書き込みは同期的に行われますが、セカンダリクラスタへの書き込みは非同期的に行われます。
以下の操作を実行することで、複数のクラスタを指定できます。
# config/solid_cache.yml production: databases: [cache_primary_shard1, cache_primary_shard2, cache_secondary_shard1, cache_secondary_shard2] store_options: clusters: - shards: [cache_primary_shard1, cache_primary_shard2] - shards: [cache_secondary_shard1, cache_secondary_shard2]
🔗 シャードに名前を付ける
シャーディングで利用するノードキーには、デフォルトで
database.yml
ファイルに記載されているデータベースの名前が使われます。クラスター設定内で以下のようにシャーディング用の名前を追加できます。これにより、一貫性のあるハッシュを壊さずに、シャードをシャッフルしたり削除したりできるようになります。
production: databases: [cache_primary_shard1, cache_primary_shard2, cache_secondary_shard1, cache_secondary_shard2] store_options: clusters: shards: cache_primary_shard1: node1 cache_primary_shard2: node2
🔗 暗号化を有効にする
以下のようにencrypt
プロパティを追加することでキャッシュ値を暗号化できます。
# config/cache.yml
production:
encrypt: true
または
# application.rb
config.solid_cache.encrypt = true
アプリケーションでActive Recordの暗号化機能を設定しておく必要があります。
Solid Cacheは、デフォルトで、それ用に最適化されたカスタムencryptorとメッセージシリアライザーを利用します。
まず、encryptorのActiveRecord::Encryption::Encryptor.new(compress: false)
で圧縮を無効にします(キャッシュデータは既に圧縮されているため)。
次に、シリアライザとしてActiveRecord::Encryption::MessagePackMessageSerializer.new
を利用します。このシリアライザはバイナリカラムでのみ利用可能ですが、標準のシリアライザよりも約40%多くのデータを保存できます。
必要に応じて、以下のように独自のコンテキストプロパティを選択することも可能です。
# application.rb
config.solid_cache.encryption_context_properties = {
encryptor: ActiveRecord::Encryption::Encryptor.new,
message_serializer: ActiveRecord::Encryption::MessageSerializer.new
}
🔗 インデックスの上限サイズ
Solid Cacheのマイグレーションでは、1024バイトのエントリを持つインデックスの作成を試行します。このサイズがデータベースに対して大きすぎる場合は、以下を行う必要があります。
- マイグレーションファイルでインデックスのサイズを編集する
- キャッシュの
max_key_bytesize
に新しい値を設定する
🔗 開発
bin/rake test
でテストを実行します。デフォルトではSQLiteに対して実行されます。
MySQLやPostgreSQLに対してテストを実行することも可能です。
最初にデータベースを起動します。
$ docker compose up -d
次に、以下を実行してデータベーススキーマをセットアップします。
$ TARGET_DB=mysql bin/rails db:setup
$ TARGET_DB=postgres bin/rails db:setup
次に、対象データベースに対してテストを実行します。
$ TARGET_DB=mysql bin/rake test
$ TARGET_DB=postgres bin/rake test
🔗 複数バージョンのRailsでテストする
Solid Cacheでは、複数バージョンのRailsをテストするためにappraisalに依存しています。
Railsのバージョンを指定してテストを実行するには、以下のようにします。
bundle exec appraisal rails-7-1 bin/rake test
Gemfile
内の依存関係を更新したときは、必ず以下を実行してください。
$ bundle
$ appraisal update
これにより、すべてのRailsバージョンで依存関係が確実に更新されます。
🔗 Solid Cacheの実装について
Solid CacheはFIFO(first in, first out: 先入れ先出し)方式のキャッシュです。FIFOはLRU(least recently used: 直近で最も使われていないものから削除する)方式ほど効率は高くありませんが、キャッシュの寿命を長くすることで効率を補っています。
FIFOキャッシュは以下の理由で管理がずっと簡単です。
- 項目がいつ読み出されたかをトラッキングする必要がない。
-
最大IDと最小IDを比較することでキャッシュサイズの推定と制御が行える。
-
テーブルの一方の端から削除し、反対側の端に追加することで断片化を回避できる(少なくともMySQLでは)。
🔗 アップグレード
Solid Cache v0.3.0以前からアップグレードする場合は、「v0.4.0以上にアップグレードする場合」ドキュメントを参照してください。
🔗 ライセンス
Solid Cache is licensed under MIT.
概要
MITライセンスに基づいて翻訳・公開いたします。
日本語タイトルは内容に即したものにしました。