Commit 75cbc78359f312faafec500d7529e21bec89fed6

Authored by Andrew Kane
1 parent 62e98f89

Improved index caching [skip ci]

lib/searchkick.rb
... ... @@ -12,6 +12,7 @@ require "forwardable"
12 12 # modules
13 13 require "searchkick/controller_runtime"
14 14 require "searchkick/index"
  15 +require "searchkick/index_cache"
15 16 require "searchkick/index_options"
16 17 require "searchkick/indexer"
17 18 require "searchkick/hash_wrapper"
... ...
lib/searchkick/index_cache.rb 0 → 100644
... ... @@ -0,0 +1,30 @@
  1 +module Searchkick
  2 + class IndexCache
  3 + def initialize(max_size: 20)
  4 + @data = {}
  5 + @mutex = Mutex.new
  6 + @max_size = max_size
  7 + end
  8 +
  9 + # probably a better pattern for this
  10 + # but keep it simple
  11 + def fetch(name)
  12 + # thread-safe in MRI without mutex
  13 + # due to how context switching works
  14 + @mutex.synchronize do
  15 + if @data.key?(name)
  16 + @data[name]
  17 + else
  18 + @data.clear if @data.size >= @max_size
  19 + @data[name] = yield
  20 + end
  21 + end
  22 + end
  23 +
  24 + def clear
  25 + @mutex.synchronize do
  26 + @data.clear
  27 + end
  28 + end
  29 + end
  30 +end
... ...
lib/searchkick/model.rb
... ... @@ -55,7 +55,7 @@ module Searchkick
55 55  
56 56 class_variable_set :@@searchkick_options, options.dup
57 57 class_variable_set :@@searchkick_klass, self
58   - class_variable_set :@@searchkick_index_cache, {}
  58 + class_variable_set :@@searchkick_index_cache, Searchkick::IndexCache.new
59 59  
60 60 class << self
61 61 def searchkick_search(term = "*", **options, &block)
... ... @@ -71,7 +71,7 @@ module Searchkick
71 71 index = name || searchkick_index_name
72 72 index = index.call if index.respond_to?(:call)
73 73 index_cache = class_variable_get(:@@searchkick_index_cache)
74   - index_cache[index] ||= Searchkick::Index.new(index, searchkick_options)
  74 + index_cache.fetch(index) { Searchkick::Index.new(index, searchkick_options) }
75 75 end
76 76 alias_method :search_index, :searchkick_index unless method_defined?(:search_index)
77 77  
... ...
test/index_cache_test.rb 0 → 100644
... ... @@ -0,0 +1,49 @@
  1 +require_relative "test_helper"
  2 +
  3 +class IndexCacheTest < Minitest::Test
  4 + def setup
  5 + Product.class_variable_get(:@@searchkick_index_cache).clear
  6 + end
  7 +
  8 + def test_default
  9 + object_id = Product.search_index.object_id
  10 + 3.times do
  11 + assert_equal object_id, Product.search_index.object_id
  12 + end
  13 + end
  14 +
  15 + def test_max_size
  16 + starting_ids = object_ids(20)
  17 + assert_equal starting_ids, object_ids(20)
  18 + Product.search_index(name: "other")
  19 + refute_equal starting_ids, object_ids(20)
  20 + end
  21 +
  22 + def test_thread_safe
  23 + object_ids = with_threads { object_ids(20) }
  24 + assert_equal object_ids[0], object_ids[1]
  25 + assert_equal object_ids[0], object_ids[2]
  26 + end
  27 +
  28 + # object ids can differ since threads progress at different speeds
  29 + # test to make sure doesn't crash
  30 + def test_thread_safe_max_size
  31 + with_threads { object_ids(1000) }
  32 + end
  33 +
  34 + private
  35 +
  36 + def object_ids(count)
  37 + count.times.map { |i| Product.search_index(name: "index#{i}").object_id }
  38 + end
  39 +
  40 + def with_threads
  41 + previous = Thread.report_on_exception
  42 + begin
  43 + Thread.report_on_exception = true
  44 + 3.times.map { Thread.new { yield } }.map(&:join).map(&:value)
  45 + ensure
  46 + Thread.report_on_exception = previous
  47 + end
  48 + end
  49 +end
... ...