Commit 75cbc78359f312faafec500d7529e21bec89fed6
1 parent
62e98f89
Exists in
master
and in
2 other branches
Improved index caching [skip ci]
Showing
4 changed files
with
82 additions
and
2 deletions
Show diff stats
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" | ... | ... |
... | ... | @@ -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 | ... | ... |
... | ... | @@ -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 | ... | ... |