redis-ick 0.1.3 → 0.1.4

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: 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