Permalink
Browse files

Rewrote most of Ramaze::Cache::MemCache now that memcache-client is

deprecated. The new driver using Dalli and optionally Kgio for a nice
performance boost. The only thing left to do is to get 
Ramaze::Cache::MemCache.using() to work properly similar to how 
Ramaze::Cache::Sequel does it.
  • Loading branch information...
YorickPeterse
YorickPeterse committed May 4, 2011
1 parent 48e8987 commit 016b2d225d601a71479698e40886b15aaeaa32ec
Showing with 148 additions and 96 deletions.
  1. +0 −1 lib/ramaze/cache.rb
  2. +141 −92 lib/ramaze/cache/memcache.rb
  3. +7 −3 spec/ramaze/cache/memcache.rb
View
@@ -6,7 +6,6 @@
module Ramaze
Cache = Innate::Cache
# TODO: Describe what this class does and how it can be used.
class Cache
autoload :LRU, 'ramaze/cache/lru'
autoload :LocalMemCache, 'ramaze/cache/localmemcache'
@@ -1,124 +1,173 @@
# Copyright (c) 2009 Michael Fellinger m.fellinger@gmail.com
# All files in this distribution are subject to the terms of the Ruby license.
require 'dalli'
require 'memcache'
# Kgio gives a nice performance boost but it isn't required
begin; require 'kgio'; rescue LoadError => e; end
#:nodoc:
module Ramaze
#:nodoc:
class Cache
# Cache based on the memcache library which utilizes the memcache-daemon to
# store key/value pairs in namespaces.
##
# Cache driver for the Memcache storage engine. Memcache is a key/value store that's
# extremely useful for caching data such as views or API responses. More inforamtion
# about Memcache can be found on it's website: http://memcached.org/.
#
# Note that this cache driver requires the Dalli gem rather than Memcache Client. The
# reason for this is that the Memcache client hasn't been updated in over a year and
# Memcache has changed quite a bit. Dalli is also supposed to be faster and better
# coded. This cache driver will also try to load the kgio Gem if it's installed, if
# it's not it will just continue to operate but you won't get the nice speed boost.
#
# This driver works similar to Ramaze::Cache::Sequel in that it allows you to specify
# instance specific options uisng the using() method:
#
# Please read the documentation of memcache-client for further methods.
# Ramaze::Cache.options.view = Ramaze::Cache::Memcache.using(:compression => false)
#
# It is highly recommended to install memcache-client_extensions for
# a bit of speedup and more functionality
# All options sent to the using() method will be sent to Dalli.
#
# NOTE: There is a big issue with persisting sessions in memcache, not only
# can they be dropped at any time, essentially logging the user out
# without them noticing, but there is also a low limit to the maximum
# time-to-live. After 30 days, your session will be dropped, no
# matter what.
# Please remember that memcache is, first of all, a cache, not a
# persistence mechanism.
# @author Yorick Peterse
# @since 04-05-2011
#
# NOTE: If you try to set a higher ttl than allowed, your stored key/value
# will be expired immediately.
class MemCache
include Cache::API
include Innate::Traited
# The maximum Time To Live that can be used in Memcache
MAX_TTL = 2592000
include Cache::API
# Hash containing the default configuration options to use for Dalli
trait :default => {
# The default TTL for each item
:expires_in => 604800,
# Compresses everything with Gzip if it's over 1K
:compression => true,
# +:multithread+: May be turned off at your own risk.
# +:readonly+: You most likely want that to be false.
# +:servers+: Array containing at least one of:
# MemCache::Server instance
# Strings like "localhost", "localhost:11211", "localhost:11211:1"
# That accord to "host:port:weight", only host is required.
OPTIONS = {
:multithread => true,
:readonly => false,
:servers => ['localhost:11211:1'],
# Array containing all default Memcache servers
:servers => ['localhost:11211']
}
# Connect to memcached
def cache_setup(host, user, app, name)
@namespace = [host, user, app, name].compact.join('-')
options = {:namespace => @namespace}.merge(OPTIONS)
servers = options.delete(:servers)
@store = ::MemCache.new(servers, options)
@warned = false
##
# This method will create a subclass of Ramaze::Cache::MemCache with all the
# custom options set. All options set in this method will be sent to Dalli as well.
#
# Using this method allows you to use different memcache settings for various parts
# of Ramaze. For example, you might want to use servers A and B for storing the
# sessions but server C for only views. Most of the way this method works was
# inspired by Ramaze::Cache::Sequel which was contributed by Lars Olsson.
#
# @example
# Ramaze::Cache.options.session = Ramaze::Cache::MemCache.using(
# :compression => false,
# :username => 'ramaze',
# :password => 'ramaze123',
# :servers => ['othermachine.com:12345'] # Overwrites the default server
# )
#
# @author Yorick Peterse
# @since 04-05-2011
# @param [Hash] options A hash containing all configuration options to use for
# Dalli. For more information on all the available options you can read the README
# in their repository. This repository can be found here:
# https://github.com/mperham/dalli
#
def self.using(options = {})
#merged = Ramaze::Cache::MemCache.trait[:default].merge(options)
#klass = Class.new(self) do
# @options = merged
#end
#return klass
end
##
# Prepares the cache by creating the namespace and an instance of a Dalli client.
#
# @author Yorick Peterse
# @since 04-05-2011
# @param [String] hostname The hostname of the machine running the application.
# @param [String] username The name of the user executing the process
# @param [String] appname Unique identifier for the application.
# @param [String] cachename The namespace to use for this cache instance.
#
def cache_setup(hostname, username, appname, cachename)
# Validate the maximum TTL
if @options[:expires_in] > MAX_TTL
raise(ArgumentError, "The maximum TTL of Memcache is 30 days")
end
@options[:namespace] = [hostname, username, appname, cachename].compact.join('-')
servers = @options.delete(:servers)
@client = ::Dalli::Client.new(servers, @options)
end
# Wipe out _all_ data in memcached, use with care.
##
# Removes all items from the cache.
#
# @author Yorick Peterse
# @since 04-05-2011
#
def cache_clear
@store.flush_all
rescue ::MemCache::MemCacheError => e
Log.error(e)
nil
@client.flush
end
##]
# Removes the specified keys from the cache.
#
# @author Yorick Peterse
# @since 04-05-2011
# @param [Array] keys The keys to remove from the cache.
#
def cache_delete(*keys)
super{|key| @store.delete(key); nil }
rescue ::MemCache::MemCacheError => e
Log.error(e)
nil
super do |key|
@client.delete(key)
end
end
# NOTE:
# * We have no way of knowing whether the value really is nil, we
# assume you wouldn't cache nil and return the default instead.
##
# Fetches the specified key from the cache. It the value was nil the default value
# will be returned instead.
#
# @author Yorick Peterse
# @since 04-05-2011
# @param [String] key The name of the key to retrieve.
# @param [Mixed] default The default value.
# @return [Mixed]
#
def cache_fetch(key, default = nil)
value = @store[key]
value.nil? ? default : value
rescue ::MemCache::MemCacheError => e
Log.error(e)
nil
end
value = @client[key]
def cache_store(key, value, options = {})
ttl = options[:ttl] || 0
if ttl > MAX_TTL
unless @warned
Log.warn('MemCache cannot set a ttl greater than 2592000 seconds.')
Log.warn('Modify Ramaze.options.session.ttl to a value <= of that.')
@warned = true
end
ttl = MAX_TTL
if value.nil?
return default
else
return value
end
@store.set(key, value, ttl)
value
rescue ::MemCache::MemCacheError => e
Log.error(e)
nil
end
# statistics about usage
def stats; @store.stats; end
# current namespace
def namespace; @store.namespace; end
# switch to different namespace
def namespace=(ns) @namespace = @store.namespace = ns; end
##
# Sets the given key to the specified value. Optionally you can specify a hash with
# options specific to the key. Once a key has been stored it's value will be
# returned.
#
# @author Yorick Peterse
# @since 04-05-2011
# @param [String] key The name of the key to store.
# @param [Mixed] value The value to store in Memcache.
# @param [Fixnum] ttl The Time To Live to use for the current key.
# @param [Hash] options A hash containing options specific for the specified key.
# @return [Mixed]
#
def cache_store(key, value, ttl, options = {})
ttl = options.delete(:ttl) || @options[:expires_in]
# state of compression (true/false)
def compression; @store.compression; end
if ttl > MAX_TTL
raise(ArgumentError, "The maximum TTL of Memcache is 30 days")
end
# turn compression on or off
def compression=(bool); @store.compression = bool; end
@client.set(key, value, ttl, options)
# For everything else that we don't care to document right now.
def method_missing(*args, &block)
@store.__send__(*args, &block)
rescue ::MemCache::MemCacheError => e
Log.error(e)
nil
return value
end
end
end
end
end # MemCache
end # Cache
end # Ramaze
@@ -2,15 +2,15 @@
# All files in this distribution are subject to the terms of the Ruby license.
require File.expand_path('../../../../spec/helper', __FILE__)
require 'dalli'
spec_precondition 'memcached is running' do
require 'memcache'
cache = MemCache.new(['localhost:11211'])
cache = Dalli::Client.new('localhost:11211')
cache.set('active', true)
end
describe Ramaze::Cache::MemCache do
Ramaze.options.cache.names = [:one, :two]
Ramaze.options.cache.names = [:one, :two]
Ramaze.options.cache.default = Ramaze::Cache::MemCache
Ramaze.setup_dependencies
@@ -57,4 +57,8 @@
cache.clear
cache.fetch(:hello).should == nil
end
should 'use a custom set of options' do
# Test the method Ramaze::Cache::MemCache.using() here
end
end

0 comments on commit 016b2d2

Please sign in to comment.