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...
1 parent 48e8987 commit 016b2d225d601a71479698e40886b15aaeaa32ec YorickPeterse committed May 4, 2011
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.