redis-ick 0.1.3 → 0.1.4

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: e8a5daa6d8971a38cd1612550866901e98cab6a9aa217dbda97e4072cb5f372f
4
- data.tar.gz: 9d3909a5c10d7729ae29ccbc1c55090bfa701885e7156b563c1289e6e51ca427
3
+ metadata.gz: 5a3b91d98498be4ca9b767fa2af1a90aeccb041038d50e1df47eb8d0218ab4d5
4
+ data.tar.gz: 439ccf38a34252f6b1dd52c8d44cad3bb2b3f4cdbb9955455db966b51cc73cc4
5
5
  SHA512:
6
- metadata.gz: 12cd3ee7acca87ee3d09c3569ef214b5073dd768f2fb10a8650435573f0f5d8c27fa02b628bec772fd42c1aad4f70632ddaa7ae554e35be09744e1cd89dc4539
7
- data.tar.gz: 579ce38a15bc79ca62a7d03948b11ac74f113aadeb3f3585fa30209b86aa052df9bd9ae4824724647991785e0bde62792e8432d5266dbdd7212432efebf71c95
6
+ metadata.gz: 42542453d2526f2a0e8fc10debc07648f80252e8c349ee9349eefecdd26756596da846a61c5f38e5b025b8c6eaae7ad1689737b953616504acfbd52c2771989b
7
+ data.tar.gz: aea66c00ee53155fe58367975988f2c450e8d9e366f4182a9a6268e036cf03a9235043dafc45812cb5122f55fe791a11cbabce5e10929fdea444e49ff824061c
@@ -6,54 +6,24 @@ AllCops:
6
6
  Exclude:
7
7
  - 'gemfiles/vendor/**/*'
8
8
 
9
- # Broadly speaking, test code gets a pass for most of the Metrics family.
9
+ # Some classes and methods need to be big.
10
10
  #
11
- # IMO test code is not the place get pedantic about class length,
12
- # method complexity, etc. One should be encouraged to add more tests
13
- # with minimal friction, not forced to make a hard choice between
14
- # cutting tests or splitting up my test suites.
11
+ # I'll break them down for testability or composability or
12
+ # applicability, but not because I crossed an invisible line that
13
+ # somebody made up.
15
14
  #
16
- Metrics/BlockLength:
17
- Exclude:
18
- - 'test/**/*.rb'
19
- Metrics/ClassLength:
20
- Max: 400
21
- Exclude:
22
- - 'test/**/*.rb'
23
-
24
- # I like this Metric in principle, but I don't like the default max of
25
- # 15.
26
- #
27
- # Also, as per Metrics/ClassLength IMO this kind of limit should not
28
- # apply to test code (I get up to 318 over there).
29
- #
30
- Metrics/AbcSize:
31
- Max: 30
32
- Exclude:
33
- - 'test/**/*.rb'
34
-
35
- # I like this Metric in principle, but I don't like the default max of
36
- # 10.
37
- #
38
- # Also, as per Metrics/ClassLength IMO this kind of limit should not
39
- # apply to test code.
40
- #
41
- Metrics/MethodLength:
42
- Max: 50
43
- Exclude:
44
- - 'test/**/*.rb'
15
+ Metrics:
16
+ Enabled: false
45
17
 
46
18
  # I put extra spaces in a lot of expressions for a lot of different
47
19
  # reasons, including especially readability.
48
20
  #
49
- # I reject these cops.
50
- #
51
21
  Layout:
52
22
  Enabled: false
53
23
 
54
24
  # As a group, the Style cops are bewilderingly opiniated.
55
25
  #
56
- # In some cases IMO they are harmful e.g. Style/TernaryParentheses.
26
+ # In some cases IMO they are actively harmful e.g. Style/TernaryParentheses.
57
27
  #
58
28
  # I reject these cops.
59
29
  #
@@ -1,4 +1,11 @@
1
- ## 0.1.3 (2019-06-067)
1
+ ## 0.1.4 (2019-11-18)
2
+ - ICKUNLINK, as ICKDEL but uses Redis UNLINK for O(1) time.
3
+ - Clear recommendation not to use backwash unless clearly needed.
4
+ - Assuming a full cset, it can more than double the cost of ICKEXCHANGE.
5
+ - Performance optimizations in Redis Lua code.
6
+ - 13%-25% faster for large reservations on large Icks
7
+
8
+ ## 0.1.3 (2019-06-07)
2
9
  - Support for redis >= 4.0.0 added.
3
10
  - Breaking changes at redis v4.0.0 addressed.
4
11
  - Support for ruby < 2.2.2 dropped.
@@ -80,19 +80,47 @@ class Redis
80
80
  #
81
81
  # @param ick_key String the base key for the Ick
82
82
  #
83
+ # @param unlink true to use UNLINK, default false to use DEL.
84
+ #
83
85
  # @return an integer, the number of Redis keys deleted, which will
84
86
  # be >= 1 if an Ick existed at key.
85
87
  #
86
- def ickdel(ick_key)
88
+ def ickdel(ick_key,unlink: false)
87
89
  if !ick_key.is_a?(String)
88
90
  raise ArgumentError, "bogus non-String ick_key #{ick_key}"
89
91
  end
90
- _statsd_increment('profile.ick.ickdel.calls')
91
- _statsd_time('profile.ick.ickdel.time') do
92
- _eval(LUA_ICKDEL,ick_key)
92
+ stats_prefix = unlink ? 'profile.ick.ickunlink' : 'profile.ick.ickdel'
93
+ redis_cmd = unlink ? 'UNLINK' : 'DEL'
94
+ _statsd_increment("#{stats_prefix}.calls")
95
+ _statsd_time("#{stats_prefix}.time") do
96
+ _eval(
97
+ LUA_ICK_PREFIX +
98
+ "return redis.call('#{redis_cmd}',ick_key,ick_pset_key,ick_cset_key)",
99
+ ick_key
100
+ )
93
101
  end
94
102
  end
95
103
 
104
+ # Removes all data associated with the Ick in Redis at key.
105
+ #
106
+ # Similar to UNLINK key, http://redis.io/commands/unlink, but may
107
+ # unlink multiple keys which together implement the Ick data
108
+ # structure.
109
+ #
110
+ # UNLINK is O(1) in the number of messages in the Ick, and is
111
+ # available with redis-server >= 4.0.0. Physical space
112
+ # reclamation in Redis, which can be O(N), is deferred to
113
+ # asynchronous server threads.
114
+ #
115
+ # @param ick_key String the base key for the Ick
116
+ #
117
+ # @return an integer, the number of Redis keys unlinked, which will
118
+ # be >= 1 if an Ick existed at key.
119
+ #
120
+ def ickunlink(ick_key)
121
+ ickdel(ick_key,unlink: true)
122
+ end
123
+
96
124
  # Fetches stats.
97
125
  #
98
126
  # @param ick_key String the base key for the Ick
@@ -575,21 +603,6 @@ class Redis
575
603
  end
576
604
  }.freeze
577
605
 
578
- #######################################################################
579
- # LUA_ICKDEL
580
- #######################################################################
581
- #
582
- # Removes all keys associated with the Ick at KEYS[1].
583
- #
584
- # @param uses no ARGV
585
- #
586
- # @return the number of Redis keys deleted, which will be 0 if and
587
- # only if no Ick existed at KEYS[1]
588
- #
589
- LUA_ICKDEL = (LUA_ICK_PREFIX + %{
590
- return redis.call('DEL',ick_key,ick_pset_key,ick_cset_key)
591
- }).freeze
592
-
593
606
  #######################################################################
594
607
  # LUA_ICKSTATS
595
608
  #######################################################################
@@ -747,54 +760,76 @@ class Redis
747
760
  # cset by the commit function followed by up to ARGV[1] pairs
748
761
  # [member,score,...] from the reserve funciton.
749
762
  #
750
- # Note: This Lua unpacks ARGV with the iterator ipairs() instead
751
- # of unpack() to avoid a "too many results to unpack" failure at
752
- # 8000 args. However, the loop over many redis.call is
753
- # regrettably heavy-weight. From a performance standpoint it
754
- # would be preferable to call ZREM in larger batches.
763
+ # Note: This Lua code calls unpack(ARGV,i,j) in limited-size
764
+ # slices, no larger than 7990, to avoid a "too many results to
765
+ # unpack" failure which has been observed when unpacking tables as
766
+ # small as 8000.
755
767
  #
756
768
  LUA_ICKEXCHANGE = (LUA_ICK_PREFIX + %{
757
769
  local reserve_size = tonumber(ARGV[1])
758
770
  local backwash = ARGV[2]
759
771
  local argc = table.getn(ARGV)
760
772
  local num_committed = 0
761
- for i = 3,argc,1 do
762
- local num_zrem = redis.call('ZREM',ick_cset_key,ARGV[i])
763
- num_committed = num_committed + num_zrem
764
- end
765
- if 'backwash' == backwash then
766
- local cset_all = redis.call('ZRANGE',ick_cset_key,0,-1,'WITHSCORES')
767
- local cset_size = table.getn(cset_all)
768
- for i = 1,cset_size,2 do
769
- local member = cset_all[i]
770
- local score = cset_all[i+1]
771
- local old_score = redis.call('ZSCORE',ick_pset_key,member)
772
- if false == old_score then
773
- redis.call('ZADD',ick_pset_key,score,member)
774
- elseif tonumber(score) < tonumber(old_score) then
775
- redis.call('ZADD',ick_pset_key,score,member)
773
+ local unpack_limit = 7990
774
+ for i = 3,argc,unpack_limit do
775
+ local max = math.min(i+unpack_limit,argc)
776
+ local num_zrem = redis.call('ZREM',ick_cset_key,unpack(ARGV,i,max))
777
+ num_committed = num_committed + num_zrem
778
+ end
779
+ local ick_fold = function(key_from,key_to,max_size_key_to)
780
+ while true do
781
+ local size_key_to = redis.call('ZCARD',key_to)
782
+ local num = math.min(
783
+ max_size_key_to - size_key_to,
784
+ unpack_limit / 2 -- room for both scores and members
785
+ )
786
+ if num < 1 then
787
+ break
788
+ end
789
+ local head_from =
790
+ redis.call('ZRANGE',key_from,0,num-1,'WITHSCORES')
791
+ local head_size = table.getn(head_from)
792
+ if 0 == head_size then
793
+ break
794
+ end
795
+ local to_zadd = {} -- both scores and members
796
+ local to_zrem = {} -- members only
797
+ for i = 1,head_size,2 do
798
+ local member = head_from[i]
799
+ local score_from = tonumber(head_from[i+1])
800
+ local score_to = redis.call('ZSCORE',key_to,member)
801
+ if false == score_to or score_from < tonumber(score_to) then
802
+ to_zadd[#to_zadd+1] = score_from
803
+ to_zadd[#to_zadd+1] = member
804
+ end
805
+ to_zrem[#to_zrem+1] = member
806
+ end
807
+ redis.call('ZREM',key_from,unpack(to_zrem))
808
+ if 0 < table.getn(to_zadd) then
809
+ redis.call('ZADD',key_to,unpack(to_zadd))
776
810
  end
777
- end
778
- redis.call('DEL',ick_cset_key)
779
- end
780
- while true do
781
- local cset_size = redis.call('ZCARD',ick_cset_key)
782
- if cset_size and reserve_size <= cset_size then
783
- break
784
- end
785
- local first_pset = redis.call('ZRANGE',ick_pset_key,0,0,'WITHSCORES')
786
- if 0 == table.getn(first_pset) then
787
- break
788
- end
789
- local first_member = first_pset[1]
790
- local first_score = tonumber(first_pset[2])
791
- redis.call('ZREM',ick_pset_key,first_member)
792
- local old_score = redis.call('ZSCORE',ick_cset_key,first_member)
793
- if false == old_score or first_score < tonumber(old_score) then
794
- redis.call('ZADD',ick_cset_key,first_score,first_member)
795
811
  end
796
812
  end
813
+ if 'backwash' == backwash then
814
+ --
815
+ -- Fold everything in the cset back into the pset.
816
+ --
817
+ local pset_size = redis.call('ZCARD',ick_pset_key) or 0
818
+ local cset_size = redis.call('ZCARD',ick_cset_key) or 0
819
+ ick_fold(ick_cset_key,ick_pset_key,pset_size+cset_size)
820
+ end
821
+ --
822
+ -- Fold enough from the pset to the cset to grow the cset
823
+ -- to at most reserve_size members.
824
+ --
825
+ ick_fold(ick_pset_key,ick_cset_key,reserve_size)
826
+ --
827
+ -- Make sure ick_key exists per specification.
828
+ --
797
829
  redis.call('SETNX', ick_key, 'ick.v1')
830
+ --
831
+ -- Package up return results, which may be smaller than the cset.
832
+ --
798
833
  local result = { num_committed }
799
834
  if reserve_size > 0 then
800
835
  local max = reserve_size - 1
@@ -1,5 +1,5 @@
1
1
  class Redis
2
2
  class Ick
3
- VERSION = '0.1.3'.freeze
3
+ VERSION = '0.1.4'.freeze
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: redis-ick
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.3
4
+ version: 0.1.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - jhwillett
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2019-06-07 00:00:00.000000000 Z
11
+ date: 2019-11-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: redis