Skip to content

Commit

Permalink
Documentation for session proxies
Browse files Browse the repository at this point in the history
  • Loading branch information
Mat Brown committed Dec 21, 2009
1 parent 53468b6 commit 887dd17
Show file tree
Hide file tree
Showing 8 changed files with 240 additions and 5 deletions.
1 change: 1 addition & 0 deletions lib/sunspot.rb
Expand Up @@ -39,6 +39,7 @@ module Sunspot
NoAdapterError = Class.new(Exception)
NoSetupError = Class.new(Exception)
IllegalSearchError = Class.new(Exception)
NotImplementedError = Class.new(Exception)


class <<self
Expand Down
25 changes: 25 additions & 0 deletions lib/sunspot/session_proxy.rb
@@ -1,4 +1,29 @@
module Sunspot
#
# This module contains several Session Proxy implementations, which can be
# used to decorate one or more Session objects and add extra functionality.
# The user can also implement their own Session Proxy classes; a Session Proxy
# must simply implement the same public API as the Sunspot::Session class.
#
# When implementing a session proxy, some methods of Session may not be
# practical, or even logical, to implement. In this case, the method should
# raise a Sunspot::SessionProxy::NotSupportedError (several methods in the
# built-in session proxies raise this error).
#
# To use a session proxy in normal Sunspot usage, you can use the
# Sunspot.session= method, which will cause Sunspot to delegate all of its
# session-related class methods (most of them) to the proxy. Session proxies
# can also easily be chained, although the details of chaining depend on the
# proxy implementation.
#
# ===== Example: Chain a MasterSlaveSessionProxy with a ThreadLocalSessionProxy
#
# master_session = Sunspot::SessionProxy::ThreadLocalSessionProxy.new
# slave_session = Sunspot::SessionProxy::ThreadLocalSessionProxy.new
# master_session.config.solr.url = 'http://master-solr.local:9080/solr'
# slave_session.config.solr.url = 'http://slave-solr.local:9080/solr'
# Sunspot.session = Sunspot::SessionProxy::MasterSlaveSessionProxy.new(master_session, slave_session)
#
module SessionProxy
NotSupportedError = Class.new(Exception)

Expand Down
2 changes: 1 addition & 1 deletion lib/sunspot/session_proxy/abstract_session_proxy.rb
@@ -1,6 +1,6 @@
module Sunspot
module SessionProxy
class AbstractSessionProxy
class AbstractSessionProxy #:nodoc:
class <<self
def delegate(*args)
options = Util.extract_options_from(args)
Expand Down
30 changes: 30 additions & 0 deletions lib/sunspot/session_proxy/class_sharding_session_proxy.rb
@@ -1,15 +1,42 @@
module Sunspot
module SessionProxy
#
# An abstract subclass of ShardingSessionProxy that shards by class.
# Concrete subclasses should not override the #session_for method, but
# should instead implement the #session_for_class method. They must also
# still implement the #all_sessions method.
#
# Unlike its parent class, ClassShardingSessionProxy implements
# #remove_by_id and all flavors of #remove_all.
#
class ClassShardingSessionProxy < ShardingSessionProxy
#
# Remove the Session object pointing at the shard that indexes the given
# class.
#
# <strong>Concrete subclasses must implement this method.</strong>
#
def session_for_class(clazz)
raise NotImplementedError
end

#
# See Sunspot.remove_by_id
#
def remove_by_id(clazz, id)
session_for_class(clazz).remove_by_id(clazz, id)
end

#
# See Sunspot.remove_by_id!
#
def remove_by_id!(clazz, id)
session_for_class(clazz).remove_by_id!(clazz, id)
end

#
# See Sunspot.remove_all
#
def remove_all(clazz = nil)
if clazz
session_for_class(clazz).remove_all(clazz)
Expand All @@ -18,6 +45,9 @@ def remove_all(clazz = nil)
end
end

#
# See Sunspot.remove_all!
#
def remove_all!(clazz = nil)
if clazz
session_for_class(clazz).remove_all!(clazz)
Expand Down
39 changes: 37 additions & 2 deletions lib/sunspot/session_proxy/id_sharding_session_proxy.rb
@@ -1,24 +1,59 @@
module Sunspot
module SessionProxy
#
# A concrete implementation of ShardingSessionProxy that determines the
# shard for a given object based on the hash of its class and ID.
#
# <strong>If you change the number of shard sessions that this proxy
# encapsulates, all objects will point to a different shard.</strong> If you
# plan on adding more shards over time, consider your own
# ShardingSessionProxy implementation that does not determine the session
# using modular arithmetic (e.g., IDs 1-10000 go to shard 1, 10001-20000 go
# to shard 2, etc.)
#
# This implementation will, on average, yield an even distribution of
# objects across shards.
#
# Unlike the abstract ShardingSessionProxy, this proxy supports the
# #remove_by_id method.
#
class IdShardingSessionProxy < ShardingSessionProxy
#
# The shard sessions encapsulated by this class.
#
attr_reader :sessions
alias_method :all_sessions, :sessions
alias_method :all_sessions, :sessions #:nodoc:

#
# Initialize with a search session (see ShardingSessionProxy.new) and a
# collection of one or more shard sessions. See note about changing the
# number of shard sessions in the documentation for this class.
#
def initialize(search_session, shard_sessions)
super(search_session)
@sessions = shard_sessions
end

def session_for(object)
#
# Return a session based on the hash of the class and ID, modulo the
# number of shard sessions.
#
def session_for(object) #:nodoc:
session_for_index_id(Adapters::InstanceAdapter.adapt(object).index_id)
end

#
# See Sunspot.remove_by_id
#
def remove_by_id(clazz, id)
session_for_index_id(
Adapters::InstanceAdapter.index_id_for(clazz, id)
).remove_by_id(clazz, id)
end

#
# See Sunspot.remove_by_id!
#
def remove_by_id!(clazz, id)
session_for_index_id(
Adapters::InstanceAdapter.index_id_for(clazz, id)
Expand Down
18 changes: 17 additions & 1 deletion lib/sunspot/session_proxy/master_slave_session_proxy.rb
Expand Up @@ -2,8 +2,20 @@

module Sunspot
module SessionProxy
#
# This session proxy implementation allows Sunspot to be used with a
# master/slave Solr deployment. All write methods are delegated to a master
# session, and read methods are delegated to a slave session.
#
class MasterSlaveSessionProxy < AbstractSessionProxy
attr_reader :master_session, :slave_session
#
# The session that connects to the master Solr instance.
#
attr_reader :master_session
#
# The session that connects to the slave Solr instance.
#
attr_reader :slave_session

delegate :batch, :commit, :commit_if_delete_dirty, :commit_if_dirty,
:config, :delete_dirty?, :dirty?, :index, :index!, :remove,
Expand All @@ -15,6 +27,10 @@ def initialize(master_session, slave_session)
@master_session, @slave_session = master_session, slave_session
end

#
# By default, return the configuration for the master session. If the
# +delegate+ param is +:slave+, then return config for the slave session.
#
def config(delegate = :master)
case delegate
when :master then @master_session.config
Expand Down
117 changes: 117 additions & 0 deletions lib/sunspot/session_proxy/sharding_session_proxy.rb
Expand Up @@ -2,29 +2,94 @@

module Sunspot
module SessionProxy
#
# This is a generic abstract implementation of a session proxy that allows
# Sunspot to be used with a distributed (sharded) Solr deployment. Concrete
# subclasses should implement the #session_for method, which takes a
# searchable object and returns a Session that points to the appropriate
# Solr shard for that object. Subclasses should also implement the
# #all_sessions object, which returns the collection of all sharded Session
# objects.
#
# The class is initialized with a session that points to the Solr instance
# used to perform searches. Searches will have the +:shards+ param injected,
# containing references to the Solr instances returned by #all_sessions.
#
# For more on distributed search, see:
# http://wiki.apache.org/solr/DistributedSearch
#
# The following methods are not supported (although subclasses may in some
# cases be able to support them):
#
# * batch
# * config
# * remove_by_id
# * remove_by_id!
# * remove_all with an argument
# * remove_all! with an argument
#
class ShardingSessionProxy < AbstractSessionProxy
not_supported :batch, :config, :remove_by_id, :remove_by_id!

#
# +search_session+ is the session that should be used for searching.
#
def initialize(search_session = Sunspot.session.new)
@search_session = search_session
end

#
# Return the appropriate shard session for the object.
#
# <strong>Concrete subclasses must implement this method.</strong>
#
def session_for(object)
raise NotImplementedError
end

#
# Return all shard sessions.
#
# <strong>Concrete subclasses must implement this method.</strong>
#
def all_sessions
raise NotImplementedError
end

#
# See Sunspot.index
#
def index(*objects)
using_sharded_session(objects) { |session, group| session.index(group) }
end

#
# See Sunspot.index!
#
def index!(*objects)
using_sharded_session(objects) { |session, group| session.index!(group) }
end

#
# See Sunspot.remove
#
def remove(*objects)
using_sharded_session(objects) { |session, group| session.remove(group) }
end

#
# See Sunspot.remove!
#
def remove!(*objects)
using_sharded_session(objects) { |session, group| session.remove!(group) }
end

#
# If no argument is passed, behaves like Sunspot.remove_all
#
# If an argument is passed, will raise NotSupportedError, as the proxy
# does not know which session(s) to which to delegate this operation.
#
def remove_all(clazz = nil)
if clazz
raise NotSupportedError, "Sharding session proxy does not support remove_all with an argument."
Expand All @@ -33,6 +98,12 @@ def remove_all(clazz = nil)
end
end

#
# If no argument is passed, behaves like Sunspot.remove_all!
#
# If an argument is passed, will raise NotSupportedError, as the proxy
# does not know which session(s) to which to delegate this operation.
#
def remove_all!(clazz = nil)
if clazz
raise NotSupportedError, "Sharding session proxy does not support remove_all! with an argument."
Expand All @@ -41,18 +112,40 @@ def remove_all!(clazz = nil)
end
end

#
# Commit all shards. See Sunspot.commit
#
def commit
all_sessions.each { |session| session.commit }
end

#
# Commit all dirty sessions. Only dirty sessions will be committed.
#
# See Sunspot.commit_if_dirty
#
def commit_if_dirty
all_sessions.each { |session| session.commit_if_dirty }
end

#
# Commit all delete-dirty sessions. Only delete-dirty sessions will be
# committed.
#
# See Sunspot.commit_if_delete_dirty
#
def commit_if_delete_dirty
all_sessions.each { |session| session.commit_if_delete_dirty }
end

#
# Instantiate a new Search object, but don't execute it. The search will
# have an extra :shards param injected into the query, which will tell the
# Solr instance referenced by the search session to search across all
# shards.
#
# See Sunspot.new_search
#
def new_search(*types)
shard_urls = all_sessions.map { |session| session.config.solr.url }
search = @search_session.new_search(*types)
Expand All @@ -63,20 +156,44 @@ def new_search(*types)
search
end

#
# Build and execute a new Search. The search will have an extra :shards
# param injected into the query, which will tell the Solr instance
# referenced by the search session to search across all shards.
#
# See Sunspot.search
#
def search(*types, &block)
new_search(*types).execute
end

#
# True if any shard session is dirty. Note that directly using the
# #commit_if_dirty method is more efficient if that's what you're
# trying to do, since in that case only the dirty sessions are committed.
#
# See Sunspot.dirty?
#
def dirty?
all_sessions.any? { |session| session.dirty? }
end

#
# True if any shard session is delete-dirty. Note that directly using the
# #commit_if_delete_dirty method is more efficient if that's what you're
# trying to do, since in that case only the delete-dirty sessions are
# committed.
#
def delete_dirty?
all_sessions.any? { |session| session.delete_dirty? }
end

private

#
# Group the objects by which shard session they correspond to, and yield
# each session and is corresponding group of objects.
#
def using_sharded_session(objects)
grouped_objects = Hash.new { |h, k| h[k] = [] }
objects.flatten.each { |object| grouped_objects[session_for(object)] << object }
Expand Down

0 comments on commit 887dd17

Please sign in to comment.