From 887dd17e8aa0caff233564d5666f61ba4d4101b4 Mon Sep 17 00:00:00 2001 From: Mat Brown Date: Mon, 21 Dec 2009 14:11:13 -0500 Subject: [PATCH] Documentation for session proxies --- lib/sunspot.rb | 1 + lib/sunspot/session_proxy.rb | 25 ++++ .../session_proxy/abstract_session_proxy.rb | 2 +- .../class_sharding_session_proxy.rb | 30 +++++ .../id_sharding_session_proxy.rb | 39 +++++- .../master_slave_session_proxy.rb | 18 ++- .../session_proxy/sharding_session_proxy.rb | 117 ++++++++++++++++++ .../thread_local_session_proxy.rb | 13 +- 8 files changed, 240 insertions(+), 5 deletions(-) diff --git a/lib/sunspot.rb b/lib/sunspot.rb index 0e56b4d3e..9edb6f961 100644 --- a/lib/sunspot.rb +++ b/lib/sunspot.rb @@ -39,6 +39,7 @@ module Sunspot NoAdapterError = Class.new(Exception) NoSetupError = Class.new(Exception) IllegalSearchError = Class.new(Exception) + NotImplementedError = Class.new(Exception) class <Concrete subclasses must implement this method. + # + 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) @@ -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) diff --git a/lib/sunspot/session_proxy/id_sharding_session_proxy.rb b/lib/sunspot/session_proxy/id_sharding_session_proxy.rb index 931b86b2c..dbc7c056d 100644 --- a/lib/sunspot/session_proxy/id_sharding_session_proxy.rb +++ b/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. + # + # If you change the number of shard sessions that this proxy + # encapsulates, all objects will point to a different shard. 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) diff --git a/lib/sunspot/session_proxy/master_slave_session_proxy.rb b/lib/sunspot/session_proxy/master_slave_session_proxy.rb index cec465f4b..a96481cad 100644 --- a/lib/sunspot/session_proxy/master_slave_session_proxy.rb +++ b/lib/sunspot/session_proxy/master_slave_session_proxy.rb @@ -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, @@ -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 diff --git a/lib/sunspot/session_proxy/sharding_session_proxy.rb b/lib/sunspot/session_proxy/sharding_session_proxy.rb index b1e89b5a6..bbca67e66 100644 --- a/lib/sunspot/session_proxy/sharding_session_proxy.rb +++ b/lib/sunspot/session_proxy/sharding_session_proxy.rb @@ -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. + # + # Concrete subclasses must implement this method. + # + def session_for(object) + raise NotImplementedError + end + + # + # Return all shard sessions. + # + # Concrete subclasses must implement this method. + # + 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." @@ -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." @@ -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) @@ -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 } diff --git a/lib/sunspot/session_proxy/thread_local_session_proxy.rb b/lib/sunspot/session_proxy/thread_local_session_proxy.rb index 311710207..17ed5c188 100644 --- a/lib/sunspot/session_proxy/thread_local_session_proxy.rb +++ b/lib/sunspot/session_proxy/thread_local_session_proxy.rb @@ -2,12 +2,23 @@ module Sunspot module SessionProxy + # + # This class implements a session proxy that creates a different Session + # object for each thread. Any multithreaded application should use this + # proxy. + # class ThreadLocalSessionProxy < AbstractSessionProxy + # The configuration with which the thread-local sessions are initialized. attr_reader :config delegate :batch, :commit, :commit_if_delete_dirty, :commit_if_dirty, :delete_dirty?, :dirty?, :index, :index!, :new_search, :remove, :remove!, :remove_all, :remove_all!, :remove_by_id, :remove_by_id!, :search, :to => :session - def initialize(config) + # + # Optionally pass an existing Sunspot::Configuration object. If none is + # passed, a default configuration is used; it can then be modified using + # the #config attribute. + # + def initialize(config = Sunspot::Configuration.new) @config = config end