🚀

Garnetで加速する?: Railsのキャッシュストアとしての利用

2024/03/25に公開

はじめに

https://www.microsoft.com/en-us/research/blog/introducing-garnet-an-open-source-next-generation-faster-cache-store-for-accelerating-applications-and-services/

Garnetは、Microsoft Researchが開発した新しいキャッシュストアシステムで、最新のハードウェア機能を十分に活用できる設計となっており、Redisなどの既存のキャッシュストアと比べて、優れたスケーラビリティと高いスループットを実現できる、とされています。

Redisと互換性のあるAPIを提供していて、既存のRedisクライアントをそのまま利用できるとのことなので、今回 Rails Cache のバックエンドとして使用してみました。

Garnetのセットアップ

Garnet のインストール

はDockerを利用するのが簡単です。公式のDockerfileを少し変更して使ってみます。

Dockerfile.garnet
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/environments/development.rb
    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