ar_cache 2.0.0 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d4d25cbd1abb845a90dbd29d50d1dfb5b622e454e6c0bfe4a1ae65b4eddb818f
4
- data.tar.gz: 821f3036d2df0db1f143a3212cffacf306ac30aa2335385defde6b41edf5f46d
3
+ metadata.gz: 2d9f5ff76912fe1e2276c795f619cf2e1e88780e7592e7fcb659e55a75bd8aff
4
+ data.tar.gz: 51ccc09d008c7d345dc9901c2898463ef5d2ebd40649b829b63ba801b13948da
5
5
  SHA512:
6
- metadata.gz: a0a9706501fa606ee7cf02931bdbfc5efd3e2668fb5b51c55b971d616233b43595363b30ccf279d500df64789268398c140870c8683b7e79e8b70717ebd2983a
7
- data.tar.gz: 46e0a6c9a76401dec6d2f3c11f52e4e9a3e0d1b975c827df7eca05f0068612d78755238db00c5f170ab30523c5aee15eb6556b3956599490d0a812d4f8d917d5
6
+ metadata.gz: f8422672824015d7496620282919ff08b380066c8e05d8bde53bd8ef732f6b3a1c475f86bdd258f188be151fd78734b79bddbfa00bb20ce9b78be4443e96a168
7
+ data.tar.gz: 3059b104bf49417391e3edb84c5005822ba652b8c29bf8fd0de7c23080444fbd8345da5776a8bd0ba81b515845e567776c22b0a925c0235042c7383f4610cbc6
data/CHANGELOG.md CHANGED
@@ -1,3 +1,9 @@
1
1
  # Change log
2
2
 
3
3
  ## main
4
+
5
+ ## 2.1.0 (2018-06-29 UTC)
6
+
7
+ - [PR [#4](https://github.com/OuYangJinTing/ar_cache/pull/4)] Fix high concurrency causes write dirty cache ([@OuYangJinTing](https://github.com/OuYangJinTing))
8
+ - [PR [#3](https://github.com/OuYangJinTing/ar_cache/pull/3)] Shouldn't open database transaction when use cache lock ([@OuYangJinTing](https://github.com/OuYangJinTing))
9
+ - [PR [#2](https://github.com/OuYangJinTing/ar_cache/pull/2)] Use configuration template as default values ([@OuYangJinTing](https://github.com/OuYangJinTing))
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- ar_cache (2.0.0)
4
+ ar_cache (2.1.0)
5
5
  activerecord (>= 6.1, < 7)
6
6
  oj (>= 3, < 4)
7
7
 
data/lib/ar_cache.rb CHANGED
@@ -71,5 +71,11 @@ module ArCache
71
71
  def load_attributes(attributes)
72
72
  memcached? || redis? ? Oj.load(attributes) : attributes
73
73
  end
74
+
75
+ def lock_key(key)
76
+ ArCache.write(key, PLACEHOLDER, raw: true, expires_in: 1.hour)
77
+ end
74
78
  end
75
79
  end
80
+
81
+ require_relative './generators/ar_cache/templates/configuration'
@@ -14,7 +14,7 @@ module ArCache
14
14
 
15
15
  def handle_ar_cache_primary_keys(keys)
16
16
  if ArCache::Configuration.cache_lock?
17
- keys.each { |k| ArCache.write(k, ArCache::PLACEHOLDER, raw: true, expires_in: 1.day) }
17
+ keys.each { |k| ArCache.lock_key(k) }
18
18
  else
19
19
  ArCache.delete_multi(keys)
20
20
  end
@@ -3,7 +3,7 @@
3
3
  module ArCache
4
4
  class Configuration
5
5
  class << self
6
- attr_writer :cache_lock
6
+ attr_writer :cache_lock, :lock_statement
7
7
  attr_reader :cache_store, :tables_options
8
8
  attr_accessor :disabled, :select_disabled, :expires_in
9
9
 
@@ -46,6 +46,19 @@ module ArCache
46
46
  options[:unique_indexes] = Array(options[:unique_indexes]).map { |index| Array(index).map(&:to_s).uniq }.uniq
47
47
  options
48
48
  end
49
+
50
+ def lock_statement
51
+ @lock_statement ||= case ::ActiveRecord::Base.connection.class.name
52
+ when 'ActiveRecord::ConnectionAdapters::PostgreSQLAdapter'
53
+ 'FOR SHARE'
54
+ when 'ActiveRecord::ConnectionAdapters::Mysql2Adapter'
55
+ 'LOCK IN SHARE MODE'
56
+ when 'ActiveRecord::ConnectionAdapters::SQLite3Adapter'
57
+ raise "SQLite3 don't support lock statement, please use cache lock."
58
+ else
59
+ raise "Arcache can't identify database, please defined lock statement or use cache lock"
60
+ end
61
+ end
49
62
  end
50
63
  end
51
64
  end
@@ -11,12 +11,19 @@ module ArCache
11
11
  ArCache.delete_multi(ids.map { |id| primary_cache_key(id) })
12
12
  end
13
13
 
14
- def write(records)
14
+ def write(records) # rubocop:disable Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
15
15
  return -1 if disabled?
16
16
 
17
17
  records.each do |attributes|
18
18
  key = primary_cache_key(attributes[primary_key])
19
- ArCache.write(key, dump_attributes(attributes), unless_exist: cache_lock?, raw: true, expires_in: expires_in)
19
+ stringify_attributes = dump_attributes(attributes)
20
+ bool = ArCache.write(key, stringify_attributes, unless_exist: cache_lock?, raw: true, expires_in: expires_in)
21
+ if cache_lock? && !bool
22
+ value = ArCache.read(key, raw: true)
23
+ next if value == ArCache::PLACEHOLDER
24
+ next ArCache.lock_key(key) if value != stringify_attributes
25
+ end
26
+
20
27
  unique_indexes.each_with_index do |index, i|
21
28
  # The first index is primary key, should skip it.
22
29
  ArCache.write(cache_key(attributes, index), key, raw: true, expires_in: expires_in) unless i.zero?
@@ -26,14 +33,15 @@ module ArCache
26
33
  0
27
34
  end
28
35
 
29
- def read(where_clause, select_values = nil, &block) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/MethodLength
36
+ def read(where_clause, select_values = nil, &block) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/MethodLength
30
37
  entries_hash = ArCache.read_multi(*where_clause.cache_hash.keys, raw: true)
31
38
  where_clause.cache_hash.each_key do |k|
32
39
  v = entries_hash[k]
33
40
 
34
- if v.nil?
41
+ case v
42
+ when nil
35
43
  where_clause.add_missed_values(k)
36
- elsif v == ArCache::PLACEHOLDER
44
+ when ArCache::PLACEHOLDER
37
45
  where_clause.add_missed_values(k)
38
46
  where_clause.add_blank_primary_cache_key(k)
39
47
  entries_hash.delete(k)
@@ -2,9 +2,7 @@
2
2
 
3
3
  module ArCache
4
4
  class Query
5
- @lock_statement = 'FOR SHARE'
6
- singleton_class.attr_accessor :lock_statement
7
- delegate :lock_statement, :lock_statement=, to: 'self.class'
5
+ delegate :lock_statement, :cache_lock?, to: ArCache::Configuration
8
6
 
9
7
  attr_reader :relation, :table, :where_clause
10
8
 
@@ -21,19 +19,16 @@ module ArCache
21
19
  records = table.read(where_clause, @select_values, &block)
22
20
 
23
21
  if where_clause.missed_hash.any?
24
- begin
25
- missed_relation = relation.rewhere(where_clause.missed_hash).reselect('*').lock(lock_statement)
26
- missed_relation.arel.singleton_class.attr_accessor(:klass_and_select_values)
27
- missed_relation.arel.klass_and_select_values = [relation.klass, @select_values]
22
+ missed_relation = relation.rewhere(where_clause.missed_hash).reselect('*')
23
+ missed_relation = missed_relation.lock(lock_statement) unless cache_lock?
24
+ missed_relation.arel.singleton_class.attr_accessor(:klass_and_select_values)
25
+ missed_relation.arel.klass_and_select_values = [relation.klass, @select_values]
26
+ if cache_lock?
27
+ records += missed_relation.find_by_sql(missed_relation.arel, &block)
28
+ else
28
29
  missed_relation.connection.transaction do
29
30
  records += missed_relation.find_by_sql(missed_relation.arel, &block)
30
31
  end
31
- rescue ::ActiveRecord::StatementInvalid => e
32
- raise e if relation.connection.class.name != 'ActiveRecord::ConnectionAdapters::Mysql2Adapter'
33
- raise e if lock_statement == 'LOCK IN SHARE MODE'
34
-
35
- self.lock_statement = 'LOCK IN SHARE MODE'
36
- retry
37
32
  end
38
33
  end
39
34
 
@@ -64,7 +64,7 @@ module ArCache
64
64
  return '' if disabled?
65
65
 
66
66
  key = "#{identity_cache_key}:#{short_sha1}:#{Time.now.to_f}"
67
- ArCache.write(identity_cache_key, key, raw: true, expires_in: 100.years)
67
+ ArCache.write(identity_cache_key, key, raw: true, expires_in: 20.years)
68
68
  key
69
69
  end
70
70
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ArCache
4
- VERSION = '2.0.0'
4
+ VERSION = '2.1.0'
5
5
  end
@@ -2,26 +2,31 @@
2
2
 
3
3
  # For more information, please see: https://github.com/OuYangJinTing/ar_cache/README.md
4
4
  ArCache.configure do |config|
5
- # NOTE: Please change true if database happened some problems
6
- # Arcache default use database share lock to ensure that the cache is correct.
7
- config.cache_lock = false # Boolean
8
-
9
- # The cache tool.
5
+ # The cache tool. It must be an instance of ActiveSupport::Cache::Store.
10
6
  config.cache_store = defined?(Rails) ? Rails.cache : ActiveSupport::Cache::MemoryStore.new
11
7
 
8
+ # NOTE: Please use cache lock if it casue database happened dead lock.
9
+ # ArCache default use database share lock('FOR SHARE' or 'LOCK IN SHARE MODE') to ensure that the cache is correct.
10
+ # You can customize the lock statement, if your database don't support lock (eg: SQLite3), please use cache lock.
11
+ # config.lock_statement = 'custom lock statement'
12
+
13
+ # WARNING: If the cache store is not Redis nor Memcached, the cache lock may be unreliable.
14
+ config.cache_lock = false # Boolean
15
+
12
16
  # The cache key valid time.
13
17
  config.expires_in = 1.week # Integer
14
18
 
15
- # ArCache switch.
19
+ # ArCache switch (default).
16
20
  config.disabled = false # Boolean
17
21
 
18
- # Whether to support select column sql.
22
+ # Whether to support select column sql (default)..
19
23
  config.select_disabled = true # Boolean
20
24
 
25
+ # WARNING: If you use database lock, you should not custom unique index, otherwise may be happen lock table.
21
26
  config.tables_options = {
22
- # table_name: {
23
- # disabled: Boolean,
24
- # select_disabled: Boolean,
27
+ # table_name: { # Database's table name.
28
+ # disabled: Boolean, # sArCache switch.
29
+ # select_disabled: Boolean, # Whether to support select column sql.
25
30
  # unique_indexes: Array # eg: [:id, [:name, :statue]], The default is the unique index column of the table.
26
31
  # },
27
32
  # ...
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ar_cache
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.0
4
+ version: 2.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - OuYangJinTing
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-05-29 00:00:00.000000000 Z
11
+ date: 2021-06-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord