Garnetで加速する?: Railsのキャッシュストアとしての利用
はじめに
Garnetは、Microsoft Researchが開発した新しいキャッシュストアシステムで、最新のハードウェア機能を十分に活用できる設計となっており、Redisなどの既存のキャッシュストアと比べて、優れたスケーラビリティと高いスループットを実現できる、とされています。
Redisと互換性のあるAPIを提供していて、既存のRedisクライアントをそのまま利用できるとのことなので、今回 Rails Cache のバックエンドとして使用してみました。
Garnetのセットアップ
Garnet のインストール
はDockerを利用するのが簡単です。公式のDockerfileを少し変更して使ってみます。
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /source
# Copy files
RUN git clone https://github.com/microsoft/garnet.git .
RUN dotnet restore
RUN dotnet build -c Release
# Copy and publish app and libraries
WORKDIR /source/main/GarnetServer
RUN dotnet publish -c Release -o /app --self-contained false -f net8.0
# Final stage/image
FROM mcr.microsoft.com/dotnet/runtime:8.0
WORKDIR /app
COPY --from=build /app .
# Run GarnetServer with an index size of 128MB
ENTRYPOINT ["/app/GarnetServer", "-i", "128m", "--port", "6379"]
Rails で使用するための設定
従来のredisクライアントから接続できるので、redisキャッシュストアの設定と同様です。
gem "redis"
config.cache_store = :redis_cache_store, { url: ENV["GARNET_URL"] }
$ bin/bundle install
$ bin/rails dev:cache
Development mode is now being cached.
Rails.cache.fetch("hoge") { "fuga" }
pp Rails.cache.fetch("hoge") # => "fuga"
ActiveSupport::Cache::RedisCacheStore を直接使う
store = ActiveSupport::Cache::RedisCacheStore.new(url: ENV["GARNET_URL"])
store.fetch("hoge") { "fuga" }
pp store.fetch("hoge") # => "fuga"
例:環境変数 REDIS_URL の設定
私の環境では DevContainer を使用しているので、 .devcontainer/docker-compose.yml
に追記しました。
version: '3'
services:
app:
# 省略
environment:
- GARNET_URL=redis://garnet:6379
network_mode: service:garnet
depends_on:
- garnet
garnet:
build:
context: .
dockerfile: Dockerfile.garnet
ベンチマーク
GarnetとRedisそれぞれで、並列にキャッシュの保存と取り出しを行うベンチマークスクリプトを作成してみました。
require "benchmark"
namespace :benchmark do
desc "Run all benchmarks"
task all: %i[garnet redis]
desc "Run the garnet benchmark"
task :garnet do
garnet = ActiveSupport::Cache::RedisCacheStore.new(url: ENV["GARNET_URL"])
write_time, read_time = benchmark_cache_store(garnet)
puts "Garnet:キャッシュ書き込み時間: #{write_time}秒"
puts "Garnet:キャッシュ読み込み時間: #{read_time}秒"
end
desc "Run the redis benchmark"
task :redis do
redis = ActiveSupport::Cache::RedisCacheStore.new(url: ENV["REDIS_URL"])
cpu_cores = Concurrent.processor_count
write_time, read_time = benchmark_cache_store(redis)
puts "Redis:キャッシュ書き込み時間: #{write_time}秒"
puts "Redis:キャッシュ読み込み時間: #{read_time}秒"
end
def benchmark_cache_store(store, cpu_cores = Concurrent.processor_count)
iteration = 100_000
key = "test_key"
value = "test_value"
write_time = Benchmark.measure do
pool = Concurrent::FixedThreadPool.new(cpu_cores)
iteration.times { |i| pool.post { store.write("#{key}_#{i}", "#{value}_#{i}") } }
pool.shutdown
pool.wait_for_termination
end
read_time = Benchmark.measure do
pool = Concurrent::FixedThreadPool.new(cpu_cores)
iteration.times { |i| pool.post { store.read("#{key}_#{i}") } }
pool.shutdown
pool.wait_for_termination
end
[ write_time.real, read_time.real ]
ensure
store.clear
end
end
結果
$ bin/rails benchmark:all
Garnet:キャッシュ書き込み時間: 21.19898375000048秒
Garnet:キャッシュ読み込み時間: 20.815654058998916秒
Redis:キャッシュ書き込み時間: 19.911595268989913秒
Redis:キャッシュ読み込み時間: 20.927295117013273秒
まとめ
今回のベンチマークテストでは、Garnetの性能がRedisと比較して顕著に優れているという結果は得られませんでした。これはベンチマークの方法や、Garnetの設定が最適化されていないことに起因する可能性があります。もっと大規模な構成では変わってくるかもしれません。ともあれ、Garnetを導入するだけで一律にパフォーマンスが向上するとは限らないことがわかります。
しかし、Garnetはまだ新しいソフトウェアであり、そのポテンシャルは未知数です。今後のアップデートやコミュニティによるチューニングの進展によって、その真価が発揮されることが期待されます。また、Garnetの開発チームが提供するガイドラインやベストプラクティスの適用により、さらなるパフォーマンスの改善が見込まれます。
タケユー・ウェブ株式会社は、このような最新技術を積極的に取り入れ、評価していきます。Garnetの将来的な進化とともに、より効率的でスケーラブルなアプリケーションの開発が可能になることを期待し、引き続きその動向を注視しきたいと思います。🚀
Discussion