Dalli
GitHub概要
petergoldstein/dalli
High performance memcached client for Ruby
スター3,107
ウォッチ55
フォーク460
作成日:2010年8月17日
言語:Ruby
ライセンス:MIT License
トピックス
なし
スター履歴
データ取得日時: 2025/10/22 08:07
キャッシュライブラリ
Dalli
概要
DalliはRuby用の高性能なMemcachedクライアントライブラリで、バイナリプロトコルを使用して優れたパフォーマンスを提供します。
詳細
Dalli(ダリ)は、Peter GoldsteinとMike Perhamによって開発されたRuby向けの高性能Memcachedクライアントライブラリです。Memcached 1.4以降で導入された新しいバイナリプロトコルを使用し、従来のmemcache-clientの代替として設計されています。Pure Rubyで実装されており、デフォルトでスレッドセーフな動作を提供します。単一接続またはコネクションプールを使用した並行処理に対応し、マルチスレッド環境でのボトルネック解消が可能です。SASL認証をサポートしており、Herokuなどのマネージドサービス環境での使用に適しています。Rails 4以降では標準のmemcache storeがDalliを使用するように更新されており、Ruby on Railsエコシステムにおいて重要な位置を占めています。圧縮、フェイルオーバー、タイムアウト設定などの高度な機能を提供し、プロダクション環境での信頼性を重視した設計となっています。
メリット・デメリット
メリット
- 高性能: バイナリプロトコルによる効率的な通信
- スレッドセーフ: デフォルトでマルチスレッド対応
- Rails統合: Rails 3/4との優れた統合とcache store対応
- コネクションプール: 並行処理でのパフォーマンス最適化
- SASL認証: Herokuなどマネージド環境でのセキュリティ
- フェイルオーバー: 適切な障害復旧とタイムアウト調整
- 圧縮サポート: 大きなデータの効率的な格納
デメリット
- Memcached依存: Memcachedサーバーの設定・運用が必要
- メモリ制限: Memcachedのメモリベースストレージ特性
- データ永続性: サーバー再起動時のデータ消失リスク
- ネットワーク依存: 分散環境でのネットワーク遅延影響
- Ruby限定: Ruby以外の言語では使用不可
主要リンク
書き方の例
基本的な使用方法
require 'dalli'
# Memcachedサーバーに接続
dc = Dalli::Client.new('localhost:11211')
# データの保存
dc.set('user:1', { name: 'Alice', age: 30 })
dc.set('count', 100, 3600) # 1時間のTTL
# データの取得
user = dc.get('user:1')
puts user['name'] # => "Alice"
count = dc.get('count')
puts count # => 100
# データの削除
dc.delete('user:1')
# データの存在確認
exists = dc.get('count')
puts exists.nil? # => false または true
複数サーバー構成
require 'dalli'
# 複数のMemcachedサーバーを指定
servers = ['192.168.1.10:11211', '192.168.1.11:11211', '192.168.1.12:11211']
dc = Dalli::Client.new(servers, {
compression: true,
expires_in: 3600,
namespace: 'myapp'
})
# データの分散保存
dc.set('user:1', { name: 'Alice', email: '[email protected]' })
dc.set('user:2', { name: 'Bob', email: '[email protected]' })
# バッチ取得
users = dc.get_multi('user:1', 'user:2')
users.each do |key, value|
puts "#{key}: #{value['name']}"
end
Rails での設定
# config/environments/production.rb
Rails.application.configure do
# Dalliを使用したmemcache store設定
config.cache_store = :mem_cache_store,
'cache1.example.com:11211',
'cache2.example.com:11211',
{
namespace: 'myapp',
expires_in: 1.day,
compress: true,
compression_min_size: 1024
}
end
# アプリケーションでの使用
class User < ApplicationRecord
def self.find_cached(id)
Rails.cache.fetch("user:#{id}", expires_in: 1.hour) do
find(id)
end
end
def cached_posts
Rails.cache.fetch("user:#{id}:posts", expires_in: 30.minutes) do
posts.includes(:comments).to_a
end
end
end
# 使用例
user = User.find_cached(1)
posts = user.cached_posts
コネクションプールの設定
require 'dalli'
require 'connection_pool'
# コネクションプールを使用
CACHE_POOL = ConnectionPool.new(size: 5, timeout: 5) do
Dalli::Client.new('localhost:11211', {
socket_timeout: 0.5,
socket_max_failures: 2,
keepalive: true
})
end
class UserCache
def self.get_user(user_id)
CACHE_POOL.with do |cache|
cache.get("user:#{user_id}")
end
end
def self.set_user(user_id, user_data)
CACHE_POOL.with do |cache|
cache.set("user:#{user_id}", user_data, 3600)
end
end
def self.delete_user(user_id)
CACHE_POOL.with do |cache|
cache.delete("user:#{user_id}")
end
end
end
# 使用例
user_data = UserCache.get_user(123)
UserCache.set_user(123, { name: 'Charlie', role: 'admin' })
高度な操作とエラーハンドリング
require 'dalli'
class CacheManager
def initialize(servers, options = {})
@client = Dalli::Client.new(servers, options)
end
def increment_counter(key, amount = 1, initial = 0)
# カウンターのインクリメント(存在しない場合は初期値設定)
@client.incr(key, amount) || begin
@client.set(key, initial)
initial
end
end
def decrement_counter(key, amount = 1)
# カウンターのデクリメント
@client.decr(key, amount)
end
def fetch_with_fallback(key, fallback_proc, ttl = 3600)
# キャッシュから取得、存在しない場合はfallback実行
cached_value = @client.get(key)
return cached_value unless cached_value.nil?
begin
fresh_value = fallback_proc.call
@client.set(key, fresh_value, ttl)
fresh_value
rescue => e
Rails.logger.error "Cache fallback failed for key #{key}: #{e.message}"
nil
end
end
def atomic_update(key, ttl = 3600)
# Compare and Swap (CAS) を使用した原子的更新
loop do
value, cas = @client.gets(key)
# 値が存在しない場合は新規作成
if value.nil?
new_value = yield(nil)
success = @client.add(key, new_value, ttl)
return new_value if success
next # 競合が発生した場合は再試行
end
# 既存値を更新
new_value = yield(value)
success = @client.cas(key, new_value, cas, ttl)
return new_value if success
# CAS失敗時は再試行
end
end
end
# 使用例
cache = CacheManager.new(['localhost:11211'])
# カウンターの操作
current_count = cache.increment_counter('page_views', 1, 0)
puts "Page views: #{current_count}"
# フォールバック付きキャッシュ
user_data = cache.fetch_with_fallback('user:123',
-> { User.find(123).to_hash },
1800
)
# 原子的更新
updated_settings = cache.atomic_update('app_settings') do |current_settings|
current_settings ||= {}
current_settings['last_updated'] = Time.now.to_i
current_settings
end
セッションストアとしての使用
# config/initializers/session_store.rb
require 'action_dispatch/middleware/session/dalli_store'
Rails.application.config.session_store :dalli_store, {
memcache_server: ['session1.example.com:11211', 'session2.example.com:11211'],
namespace: 'sessions',
key: '_myapp_session',
expire_after: 2.weeks,
compress: true,
pool_size: 10
}
# セッション管理クラス
class SessionManager
def self.cache_client
@cache_client ||= Dalli::Client.new(
Rails.application.config.session_store[1][:memcache_server],
namespace: 'custom_sessions'
)
end
def self.store_user_session(session_id, user_data)
cache_client.set(session_id, user_data, 86400) # 24時間
end
def self.get_user_session(session_id)
cache_client.get(session_id)
end
def self.invalidate_user_session(session_id)
cache_client.delete(session_id)
end
def self.extend_session(session_id, additional_time = 3600)
data = cache_client.get(session_id)
return false unless data
cache_client.set(session_id, data, additional_time)
true
end
end
パフォーマンス監視とデバッグ
require 'dalli'
class MonitoredCache
def initialize(servers, options = {})
@client = Dalli::Client.new(servers, options)
@stats = { hits: 0, misses: 0, sets: 0, deletes: 0 }
end
def get(key)
start_time = Time.now
value = @client.get(key)
duration = Time.now - start_time
if value.nil?
@stats[:misses] += 1
Rails.logger.debug "Cache MISS for key: #{key} (#{duration * 1000}ms)"
else
@stats[:hits] += 1
Rails.logger.debug "Cache HIT for key: #{key} (#{duration * 1000}ms)"
end
value
end
def set(key, value, ttl = nil)
start_time = Time.now
result = @client.set(key, value, ttl)
duration = Time.now - start_time
@stats[:sets] += 1
Rails.logger.debug "Cache SET for key: #{key} (#{duration * 1000}ms)"
result
end
def delete(key)
result = @client.delete(key)
@stats[:deletes] += 1
Rails.logger.debug "Cache DELETE for key: #{key}"
result
end
def stats
server_stats = @client.stats
{
local: @stats,
hit_rate: (@stats[:hits].to_f / [@stats[:hits] + @stats[:misses], 1].max * 100).round(2),
servers: server_stats
}
end
def reset_stats
@stats = { hits: 0, misses: 0, sets: 0, deletes: 0 }
end
end
# 使用例
monitored_cache = MonitoredCache.new(['localhost:11211'])
# 通常の使用
monitored_cache.set('test_key', 'test_value')
value = monitored_cache.get('test_key')
# 統計情報の確認
puts monitored_cache.stats