diff --git a/README.md b/README.md index aa8adbd..cab3eb2 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,135 @@ -# Sphinx Integration +# Sphinx::Integration -Набор надстроек на thinking sphinx. +Надор надстроек над ThinkingSphinx и Riddle +Гем служит для использования real time индексов, а также для более хитрого написания sql запросов при описании индекса. -## Возможности: +# Возможности: +## Запуск сфинкса и индексатора +Всё стандартно, как и в thinking sphinx, т.к. все его rake таски перекрыты -## Contributing ++ rake ts:conf ++ rake ts:start ++ rake ts:stop ++ rake ts:in ++ rake ts:rebuild + +*Внимание* при офлайн индексации rt индексы не очищаются. Рекоммендуется в этом случае использовать ts:rebuild + +## Поддержка RT индексов +```ruby +define_index('model') do + set_property :rt => true +end +``` + +RT индексы используются как дельта. Таким образом мы избежим существенного замедления поисковых запросов из-за фрагментации памяти +т.к. основная часть запросов будет как и раньше обслуживаться дисковым индексом + +Workflow: ++ первоначальная индексация, все данные попадают в дисковый core индекс ++ далее при обновлении записи, она попадает в rt индекс ++ и помечается в core как удалённая + +Когда запускается очередная полная индексация: ++ начинает наполнятся core индекс сфинксовым индексатором ++ но в этот момент данные могут обновляться, записываться в rt индекс они будут, но потом всё равно удаляться после завершения полной индексации ++ для того, чтобы не потерять обновления данные начинают попадать в дополнительный delta_rt индекс ++ после завершения полной индексации, очищается основной rt индекс ++ и в него перетекают данные из delta rt индекса + +Если у модели существую MVA атрибуты, которые наполняются отдельными запросами (ranged-query), то необходимо определить методы, +которые будут возвращать их значения при сохранении модели. Существую определённые правила именования таких методов. +Метод должен начинаться с mva_sphinx_attributes_for_NAME, например: +```ruby +def mva_sphinx_attributes_for_rubrics + {:rubrics => rubrics.map(&:rubric_id)} +end +``` + +## Дополнительные возможности конфигурировани индекса + +Предполагается, что весь код в примерах будет выполнятся в блоке `define_index('model') do ... end` + +### Наполнение определённого индекса из другой базы, например со слэйва + +Реквизиты базы из ключа {production}_slave +```ruby +set_property :use_slave_db => true +``` + +Реквизиты базы из ключа {production}_my-sphinx-slave +```ruby +set_property :use_slave_db => 'my-sphinx-slave' +``` + +### Common Table Expressions or CTEs +```ruby +set_property :source_cte => { + :_rubrics => <<-SQL, + select companies.id as company_id, array_agg(company_rubrics.rubric_id) as rubrics_array + from companies + inner join company_rubrics on company_rubrics.company_id = companies.id + where {{where}} + group by companies.id + SQL +} +``` + +Условие {{where}} будет заменено на нужное из основного запроса + +### Дополнительные joins, например с заданым CTE +```ruby +set_property :source_joins => { + :_rubrics => { + :as => :_rubrics, + :type => :left, + :on => '_rubrics.company_id = companies.id' +} +``` + +### Отключение группировки GROUP BY, которая делается по-умолчанию +```ruby +set_property :source_no_grouping => true +``` + +### Указание LIMIT +```ruby +set_property :sql_query_limit => 1000 +``` + +### Отключение индексации пачками +```ruby +set_property :disable_range => true +``` + +### Указание своих минимального и максимального предела индексации +```ruby +set_property :sql_query_range => "SELECT 1::int, COALESCE(MAX(id), 1::int) FROM rubrics" +``` + +### Указание набора условий для выборки из базы +```ruby +set_property :use_own_sql_query_range => true +where %[faq_posts.id in (select faq_post_id from faq_post_deltas where ("faq_post_deltas"."id" >= $start AND "faq_post_deltas"."id" <= $end))] +``` + +### Указание полей при группировке +```ruby +set_property :force_group_by => true +group_by 'search_hints.forms.id', 'search_hints.forms.value', +``` + +### Указание произвольного названия таблицы, например вьюшки +```ruby +set_property :source_table => 'prepared_table_view' +``` + +### Наполнение MVA атрибутов из произволного запроса +```ruby +has :regions, :type => :multi, :source => :ranged_query, :query => "SELECT {{product_id}} AS id, region_id AS regions FROM product_regions WHERE id>=$start AND id<=$end; SELECT MIN(id), MAX(id) FROM product_regions" +``` +В данно случае `{{product_id}}` заменится на нечто подобное `product_id * 8::INT8 + 5 AS id`, т.е. заменится на вычисление правильного внутреннего сквозного id -1. Fork it -2. Create your feature branch (`git checkout -b my-new-feature`) -3. Commit your changes (`git commit -am 'Add some feature'`) -4. Push to the branch (`git push origin my-new-feature`) -5. Create new Pull Request diff --git a/lib/sphinx/integration/extensions/thinking_sphinx/active_record.rb b/lib/sphinx/integration/extensions/thinking_sphinx/active_record.rb index aa340e6..0f0e91b 100644 --- a/lib/sphinx/integration/extensions/thinking_sphinx/active_record.rb +++ b/lib/sphinx/integration/extensions/thinking_sphinx/active_record.rb @@ -17,7 +17,7 @@ module TransmitterCallbacks end def transmitter - @transmitter ||= Sphinx::Integration::Transmitter.new(self) + Sphinx::Integration::Transmitter.new(self) end end @@ -33,9 +33,7 @@ def define_secondary_index(*args, &block) raise ArgumentError unless name define_index(name, &block) - self.sphinx_index_blocks << lambda { - self.sphinx_indexes.last.merged_with_core = true - } + self.sphinx_index_blocks << -> { self.sphinx_indexes.last.merged_with_core = true } end def reset_indexes diff --git a/lib/sphinx/integration/extensions/thinking_sphinx/attribute.rb b/lib/sphinx/integration/extensions/thinking_sphinx/attribute.rb index df3f864..ecf5ef3 100644 --- a/lib/sphinx/integration/extensions/thinking_sphinx/attribute.rb +++ b/lib/sphinx/integration/extensions/thinking_sphinx/attribute.rb @@ -12,10 +12,6 @@ def initialize_with_query_option(source, columns, options = {}) initialize_without_query_option(source, columns, options) end - def available? - true - end - def source_value_with_custom_query(offset, delta) if is_string? return "#{query_source.to_s.dasherize}; #{columns.first.__name}" diff --git a/lib/sphinx/integration/extensions/thinking_sphinx/configuration.rb b/lib/sphinx/integration/extensions/thinking_sphinx/configuration.rb index bd8f24a..173d602 100644 --- a/lib/sphinx/integration/extensions/thinking_sphinx/configuration.rb +++ b/lib/sphinx/integration/extensions/thinking_sphinx/configuration.rb @@ -3,9 +3,18 @@ module Sphinx::Integration::Extensions::ThinkingSphinx::Configuration extend ActiveSupport::Concern included do + attr_accessor :remote + alias_method_chain :enforce_common_attribute_types, :rt end + # Находится ли sphinx на другой машине + # + # Returns Boolean + def remote? + !!remote + end + # Не проверям на валидность RT индексы # Метод пришлось полностью переписать def enforce_common_attribute_types_with_rt diff --git a/lib/sphinx/integration/extensions/thinking_sphinx/index.rb b/lib/sphinx/integration/extensions/thinking_sphinx/index.rb index 54a1980..62e2d4b 100644 --- a/lib/sphinx/integration/extensions/thinking_sphinx/index.rb +++ b/lib/sphinx/integration/extensions/thinking_sphinx/index.rb @@ -46,14 +46,7 @@ def to_riddle_for_rt(delta = false) when :multi then :rt_attr_multi end - #begin - index.send(attr_type) << attr.unique_name - #rescue - # puts rt_name - # puts attr_type.inspect - # puts attr.type.inspect - # raise - #end + index.send(attr_type) << attr.unique_name end index end diff --git a/lib/sphinx/integration/extensions/thinking_sphinx/source.rb b/lib/sphinx/integration/extensions/thinking_sphinx/source.rb index 481b1eb..dfcd021 100644 --- a/lib/sphinx/integration/extensions/thinking_sphinx/source.rb +++ b/lib/sphinx/integration/extensions/thinking_sphinx/source.rb @@ -25,9 +25,9 @@ def set_source_database_settings_with_slave(source) config ||= @database_configuration - source.sql_host = config[:host] || "localhost" - source.sql_user = config[:username] || config[:user] || ENV['USER'] - source.sql_pass = (config[:password].to_s || "").gsub('#', '\#') + source.sql_host = config.fetch(:host, 'localhost') + source.sql_user = config[:username] || config[:user] || ENV['USER'] + source.sql_pass = config[:password].to_s.gsub('#', '\#') source.sql_db = config[:database] source.sql_port = config[:port] source.sql_sock = config[:socket] diff --git a/lib/sphinx/integration/fast_facet.rb b/lib/sphinx/integration/fast_facet.rb index 858d333..a37bc43 100644 --- a/lib/sphinx/integration/fast_facet.rb +++ b/lib/sphinx/integration/fast_facet.rb @@ -5,7 +5,6 @@ module Sphinx::Integration::FastFacet module ClassMethods def fast_facet_ts_args(facet, ts_args = {}) - facet = facet.to_sym if facet.is_a?(String) ts_args.merge(:group => facet, :limit => ts_args[:limit] || max_matches, :page => 1, diff --git a/lib/sphinx/integration/helper.rb b/lib/sphinx/integration/helper.rb index 0dc0914..a59203b 100644 --- a/lib/sphinx/integration/helper.rb +++ b/lib/sphinx/integration/helper.rb @@ -2,25 +2,13 @@ module Sphinx::Integration class Helper module ClassMethods - def remote_sphinx? - sphinx_addr = ThinkingSphinx::Configuration.instance.address - local_addrs = internal_ips - !local_addrs.include?(sphinx_addr) - end - - def internal_ips - @internal_ips ||= Socket.ip_address_list.map do |addr| - IPAddr.new(addr.ip_address.sub(/\%.*$/, '')) - end - end - - def config_file - ThinkingSphinx::Configuration.instance.config_file + def config + ThinkingSphinx::Configuration.instance end def sphinx_running? - if remote_sphinx? + if config.remote? `#{Rails.root}/script/sphinx --status`.present? else ThinkingSphinx.sphinx_running? @@ -33,18 +21,18 @@ def run_command(command) end def stop - if remote_sphinx? + if config.remote? run_command("#{Rails.root}/script/sphinx --stop") else - run_command "searchd --config #{config_file} --stop" + run_command "searchd --config #{config.config_file} --stop" end end def start - if remote_sphinx? + if config.remote? run_command("#{Rails.root}/script/sphinx --start") else - run_command "searchd --config #{config_file}" + run_command "searchd --config #{config.config_file}" end end @@ -55,12 +43,12 @@ def running_start def index(online = true) Redis::Mutex.with_lock(:full_reindex, :expire => 3.hours) do - if remote_sphinx? + if config.remote? run_command("#{Rails.root}/script/sphinx --reindex-offline") unless online run_command("#{Rails.root}/script/sphinx --reindex-online") if online else - run_command "indexer --config #{config_file} --all" unless online - run_command "indexer --config #{config_file} --rotate --all" if online + run_command "indexer --config #{config.config_file} --all" unless online + run_command "indexer --config #{config.config_file} --rotate --all" if online end end @@ -95,7 +83,6 @@ def prepare_rt(only_index = nil) end def configure - config = ThinkingSphinx::Configuration.instance puts "Generating Configuration to #{config.config_file}" config.build end @@ -103,8 +90,8 @@ def configure def rebuild configure - if remote_sphinx? - run_command("#{Rails.root}/script/sphinx --copy-config #{config_file}") + if config.remote? + run_command("#{Rails.root}/script/sphinx --copy-config #{config.config_file}") end stop if sphinx_running? diff --git a/lib/sphinx/integration/spec/support/thinking_sphinx.rb b/lib/sphinx/integration/spec/support/thinking_sphinx.rb index 85db91e..a8bca00 100644 --- a/lib/sphinx/integration/spec/support/thinking_sphinx.rb +++ b/lib/sphinx/integration/spec/support/thinking_sphinx.rb @@ -2,24 +2,32 @@ require 'thinking_sphinx/test' require 'database_cleaner' -require 'benchmark' class Sphinx::Integration::Spec::Support::ThinkingSphinx - def reindex - time = Benchmark.realtime do - if remote? - Core::SphinxHelper.index - else - ThinkingSphinx::Test.index - end + + class << self + attr_reader :instance + + def instance + @@instance ||= ThinkingSphinx::Support.new + end + end + + def reindex(opts = {}) + options = { + :sleep => 0.25 + }.merge(opts) + + if remote? + Sphinx::Integration::Helper.index + else + ThinkingSphinx::Test.index end - Rails.logger.info "Sphinx reindexed (#{time})" + sleep(options[:sleep]) end def remote? - sphinx_addr = ThinkingSphinx::Configuration.instance.address - local_addrs = Core::IpTools.internal_ips - !local_addrs.include?(sphinx_addr) + ThinkingSphinx::Configuration.instance.remote? end end diff --git a/sphinx-integration.gemspec b/sphinx-integration.gemspec index c6058b5..04912e4 100644 --- a/sphinx-integration.gemspec +++ b/sphinx-integration.gemspec @@ -27,7 +27,7 @@ Gem::Specification.new do |gem| gem.add_development_dependency 'rake' gem.add_development_dependency 'bundler' gem.add_development_dependency 'rspec' - gem.add_development_dependency 'rails', '~> 3.0.19' + gem.add_development_dependency 'rails', '>= 3.0.3' gem.add_development_dependency 'rspec-rails' gem.add_development_dependency 'combustion' gem.add_development_dependency 'mock_redis'