ar_cache 2.0.0 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
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