redstream 0.0.1 → 0.4.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 +4 -4
- data/.github/workflows/test.yml +28 -0
- data/.rubocop.yml +122 -0
- data/.travis.yml +5 -2
- data/CHANGELOG.md +17 -0
- data/Gemfile +0 -2
- data/README.md +6 -4
- data/Rakefile +0 -1
- data/lib/redstream.rb +12 -3
- data/lib/redstream/consumer.rb +1 -3
- data/lib/redstream/delayer.rb +1 -3
- data/lib/redstream/lock.rb +2 -4
- data/lib/redstream/message.rb +0 -2
- data/lib/redstream/model.rb +20 -9
- data/lib/redstream/producer.rb +25 -18
- data/lib/redstream/trimmer.rb +1 -3
- data/lib/redstream/version.rb +1 -3
- data/redstream.gemspec +14 -15
- data/spec/redstream/consumer_spec.rb +7 -7
- data/spec/redstream/delayer_spec.rb +0 -2
- data/spec/redstream/lock_spec.rb +1 -3
- data/spec/redstream/model_spec.rb +110 -31
- data/spec/redstream/producer_spec.rb +76 -4
- data/spec/redstream/trimmer_spec.rb +0 -2
- data/spec/redstream_spec.rb +12 -4
- data/spec/spec_helper.rb +1 -4
- metadata +40 -24
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 160386bd218415c11c556b1cdfc8824ed347e7279fac67f390c07b37c7128eff
|
|
4
|
+
data.tar.gz: '087e4ecaee15bd2f9db4367f53297a72f01c8de1cc093ae25ff3d4eb8ae29b90'
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d4d0945a2d2cb4b38dd398e5368963b5c8ba63aa93bc065bcb0c78a0e6bd7d2c0a7f825dd76b7b1d0ff79b6874bf1c3d0cb8f29843977d7676d341cfa584ed5d
|
|
7
|
+
data.tar.gz: b53bc33ce21244d64307b22f98185367e7c5806aeed9e50ed2b9dd123278b19236a049042bde26870dacd99702df49966674d1c629bf73ad955ff176f3f25a7b
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
name: test
|
|
2
|
+
on: [push, pull_request]
|
|
3
|
+
jobs:
|
|
4
|
+
build:
|
|
5
|
+
runs-on: ubuntu-latest
|
|
6
|
+
strategy:
|
|
7
|
+
matrix:
|
|
8
|
+
ruby: ['2.5', '2.6', '2.7']
|
|
9
|
+
services:
|
|
10
|
+
redis:
|
|
11
|
+
image: redis
|
|
12
|
+
ports:
|
|
13
|
+
- 6379:6379
|
|
14
|
+
steps:
|
|
15
|
+
- uses: actions/checkout@v1
|
|
16
|
+
- uses: actions/setup-ruby@v1
|
|
17
|
+
with:
|
|
18
|
+
ruby-version: ${{ matrix.ruby }}
|
|
19
|
+
- uses: actions/cache@v1
|
|
20
|
+
id: cache
|
|
21
|
+
with:
|
|
22
|
+
path: vendor/bundler
|
|
23
|
+
key: ${{ hashFiles('Gemfile.lock') }}-${{ matrix.ruby }}
|
|
24
|
+
- run: |
|
|
25
|
+
gem install bundler
|
|
26
|
+
bundle install --path=vendor/bundler
|
|
27
|
+
bundle exec rspec
|
|
28
|
+
bundle exec rubocop
|
data/.rubocop.yml
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
AllCops:
|
|
2
|
+
NewCops: enable
|
|
3
|
+
|
|
4
|
+
Lint/AssignmentInCondition:
|
|
5
|
+
Enabled: false
|
|
6
|
+
|
|
7
|
+
Gemspec/RequiredRubyVersion:
|
|
8
|
+
Enabled: false
|
|
9
|
+
|
|
10
|
+
Lint/AmbiguousBlockAssociation:
|
|
11
|
+
Enabled: false
|
|
12
|
+
|
|
13
|
+
Style/FrozenStringLiteralComment:
|
|
14
|
+
Enabled: false
|
|
15
|
+
|
|
16
|
+
Lint/RedundantRequireStatement:
|
|
17
|
+
Enabled: false
|
|
18
|
+
|
|
19
|
+
Layout/ArgumentAlignment:
|
|
20
|
+
EnforcedStyle: with_fixed_indentation
|
|
21
|
+
|
|
22
|
+
Layout/FirstArrayElementIndentation:
|
|
23
|
+
EnforcedStyle: consistent
|
|
24
|
+
|
|
25
|
+
Style/PercentLiteralDelimiters:
|
|
26
|
+
Enabled: false
|
|
27
|
+
|
|
28
|
+
Style/SpecialGlobalVars:
|
|
29
|
+
EnforcedStyle: use_english_names
|
|
30
|
+
|
|
31
|
+
Security/Eval:
|
|
32
|
+
Enabled: false
|
|
33
|
+
|
|
34
|
+
Style/WordArray:
|
|
35
|
+
EnforcedStyle: brackets
|
|
36
|
+
|
|
37
|
+
Style/ClassAndModuleChildren:
|
|
38
|
+
Enabled: false
|
|
39
|
+
|
|
40
|
+
Style/TrivialAccessors:
|
|
41
|
+
Enabled: false
|
|
42
|
+
|
|
43
|
+
Style/Alias:
|
|
44
|
+
Enabled: false
|
|
45
|
+
|
|
46
|
+
Style/StringLiteralsInInterpolation:
|
|
47
|
+
EnforcedStyle: double_quotes
|
|
48
|
+
|
|
49
|
+
Metrics/ClassLength:
|
|
50
|
+
Enabled: false
|
|
51
|
+
|
|
52
|
+
Naming/MethodParameterName:
|
|
53
|
+
Enabled: false
|
|
54
|
+
|
|
55
|
+
Style/SymbolArray:
|
|
56
|
+
EnforcedStyle: brackets
|
|
57
|
+
|
|
58
|
+
Layout/RescueEnsureAlignment:
|
|
59
|
+
Enabled: false
|
|
60
|
+
|
|
61
|
+
Layout/LineLength:
|
|
62
|
+
Enabled: false
|
|
63
|
+
|
|
64
|
+
Metrics/MethodLength:
|
|
65
|
+
Enabled: false
|
|
66
|
+
|
|
67
|
+
Metrics/ModuleLength:
|
|
68
|
+
Enabled: false
|
|
69
|
+
|
|
70
|
+
Style/ZeroLengthPredicate:
|
|
71
|
+
Enabled: false
|
|
72
|
+
|
|
73
|
+
Metrics/PerceivedComplexity:
|
|
74
|
+
Enabled: false
|
|
75
|
+
|
|
76
|
+
Metrics/AbcSize:
|
|
77
|
+
Enabled: false
|
|
78
|
+
|
|
79
|
+
Metrics/CyclomaticComplexity:
|
|
80
|
+
Enabled: false
|
|
81
|
+
|
|
82
|
+
Metrics/BlockLength:
|
|
83
|
+
Enabled: false
|
|
84
|
+
|
|
85
|
+
Metrics/BlockNesting:
|
|
86
|
+
Enabled: false
|
|
87
|
+
|
|
88
|
+
Style/NumericPredicate:
|
|
89
|
+
Enabled: false
|
|
90
|
+
|
|
91
|
+
Naming/AccessorMethodName:
|
|
92
|
+
Enabled: false
|
|
93
|
+
|
|
94
|
+
Naming/MemoizedInstanceVariableName:
|
|
95
|
+
Enabled: false
|
|
96
|
+
|
|
97
|
+
Style/StringLiterals:
|
|
98
|
+
EnforcedStyle: double_quotes
|
|
99
|
+
|
|
100
|
+
Style/Documentation:
|
|
101
|
+
Enabled: false
|
|
102
|
+
|
|
103
|
+
Naming/ConstantName:
|
|
104
|
+
Enabled: false
|
|
105
|
+
|
|
106
|
+
Style/MutableConstant:
|
|
107
|
+
Enabled: false
|
|
108
|
+
|
|
109
|
+
Layout/MultilineMethodCallIndentation:
|
|
110
|
+
EnforcedStyle: indented
|
|
111
|
+
|
|
112
|
+
Layout/ParameterAlignment:
|
|
113
|
+
EnforcedStyle: with_fixed_indentation
|
|
114
|
+
|
|
115
|
+
Lint/UnusedMethodArgument:
|
|
116
|
+
Enabled: false
|
|
117
|
+
|
|
118
|
+
Style/IfUnlessModifier:
|
|
119
|
+
Enabled: false
|
|
120
|
+
|
|
121
|
+
Style/RedundantBegin:
|
|
122
|
+
Enabled: false
|
data/.travis.yml
CHANGED
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# CHANGELOG
|
|
2
|
+
|
|
3
|
+
## v0.4.0
|
|
4
|
+
* Make delay message id params in queue methods optional
|
|
5
|
+
|
|
6
|
+
## v0.3.0
|
|
7
|
+
* Pipeline deletion of delay messages
|
|
8
|
+
|
|
9
|
+
## v0.2.0
|
|
10
|
+
* Delete delay messages after queue messages are sent
|
|
11
|
+
|
|
12
|
+
## v0.1.1
|
|
13
|
+
* Fix missing queue message in `after_commit on: :destroy`
|
|
14
|
+
|
|
15
|
+
## v0.1.0
|
|
16
|
+
* No longer queue/delay in `after_save`/`after_commit` if no changes occurred
|
|
17
|
+
* Added `Redstream.stream_size`
|
data/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
**Using redis streams to keep your primary database in sync with secondary
|
|
5
5
|
datastores (e.g. elasticsearch).**
|
|
6
6
|
|
|
7
|
-
[](https://github.com/mrkamel/redstream/actions?query=workflow%3Atest)
|
|
8
8
|
|
|
9
9
|
## Installation
|
|
10
10
|
|
|
@@ -86,9 +86,9 @@ end
|
|
|
86
86
|
More concretely, `after_save`, `after_touch` and `after_destroy` only write
|
|
87
87
|
"delay" messages to an additional redis stream. Delay message are like any
|
|
88
88
|
other messages, but they get processed by a `Redstream::Delayer` and the
|
|
89
|
-
`Delayer`will wait for some (configurable) delay/time before processing them.
|
|
89
|
+
`Delayer` will wait for some (configurable) delay/time before processing them.
|
|
90
90
|
As the `Delayer` is neccessary to fix inconsistencies, the delay must be at
|
|
91
|
-
least as long as your
|
|
91
|
+
least as long as your maximum database transaction time. Contrary,
|
|
92
92
|
`after_commit` writes messages to a redis stream from which the messages can
|
|
93
93
|
be fetched immediately to keep the secondary datastores updated in
|
|
94
94
|
near-realtime. The reasoning of all this is simple: usually, i.e. by using only
|
|
@@ -97,7 +97,9 @@ any errors occurring in between `after_save` and `after_commit` result in
|
|
|
97
97
|
inconsistencies between your primary and secondary datastore. By using these
|
|
98
98
|
kinds of "delay" messages triggered by `after_save` and fetched after e.g. 5
|
|
99
99
|
minutes, errors occurring in between `after_save` and `after_commit` can be
|
|
100
|
-
fixed when the delay message get processed.
|
|
100
|
+
fixed when the delay message get processed. Please note that redstream deletes
|
|
101
|
+
delay messages after the messages for immediate retrieval have been
|
|
102
|
+
successfully sent, such that messages will not be processed twice, usually.
|
|
101
103
|
|
|
102
104
|
Any messages are fetched in batches, such that e.g. elasticsearch can be
|
|
103
105
|
updated using its bulk API. For instance, depending on which elasticsearch ruby
|
data/Rakefile
CHANGED
data/lib/redstream.rb
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
|
|
2
1
|
require "active_support/inflector"
|
|
3
2
|
require "connection_pool"
|
|
4
3
|
require "redis"
|
|
@@ -59,6 +58,17 @@ module Redstream
|
|
|
59
58
|
@namespace
|
|
60
59
|
end
|
|
61
60
|
|
|
61
|
+
# Returns the length of the specified stream.
|
|
62
|
+
#
|
|
63
|
+
# @param stream_name [String] The stream name
|
|
64
|
+
# @return [Integer] The length of the stream
|
|
65
|
+
|
|
66
|
+
def self.stream_size(stream_name)
|
|
67
|
+
connection_pool.with do |redis|
|
|
68
|
+
redis.xlen(stream_key_name(stream_name))
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
62
72
|
# Returns the max id of the specified stream, i.e. the id of the
|
|
63
73
|
# last/newest message added. Returns nil for empty streams.
|
|
64
74
|
#
|
|
@@ -69,7 +79,7 @@ module Redstream
|
|
|
69
79
|
connection_pool.with do |redis|
|
|
70
80
|
message = redis.xrevrange(stream_key_name(stream_name), "+", "-", count: 1).first
|
|
71
81
|
|
|
72
|
-
return unless message
|
|
82
|
+
return nil unless message
|
|
73
83
|
|
|
74
84
|
message[0]
|
|
75
85
|
end
|
|
@@ -131,4 +141,3 @@ module Redstream
|
|
|
131
141
|
[namespace, "redstream"].compact.join(":")
|
|
132
142
|
end
|
|
133
143
|
end
|
|
134
|
-
|
data/lib/redstream/consumer.rb
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
|
|
2
1
|
require "thread"
|
|
3
2
|
|
|
4
3
|
module Redstream
|
|
@@ -92,7 +91,7 @@ module Redstream
|
|
|
92
91
|
end
|
|
93
92
|
|
|
94
93
|
sleep(5) unless got_lock
|
|
95
|
-
rescue => e
|
|
94
|
+
rescue StandardError => e
|
|
96
95
|
@logger.error e
|
|
97
96
|
|
|
98
97
|
sleep 5
|
|
@@ -112,4 +111,3 @@ module Redstream
|
|
|
112
111
|
end
|
|
113
112
|
end
|
|
114
113
|
end
|
|
115
|
-
|
data/lib/redstream/delayer.rb
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
|
|
2
1
|
module Redstream
|
|
3
2
|
# The Redstream::Delayer class is responsible for reading messages from
|
|
4
3
|
# special delay streams which are used to fix inconsistencies resulting from
|
|
@@ -68,7 +67,7 @@ module Redstream
|
|
|
68
67
|
|
|
69
68
|
deliver
|
|
70
69
|
end
|
|
71
|
-
rescue => e
|
|
70
|
+
rescue StandardError => e
|
|
72
71
|
@logger.error e
|
|
73
72
|
|
|
74
73
|
sleep 5
|
|
@@ -97,4 +96,3 @@ module Redstream
|
|
|
97
96
|
end
|
|
98
97
|
end
|
|
99
98
|
end
|
|
100
|
-
|
data/lib/redstream/lock.rb
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
|
|
2
1
|
require "securerandom"
|
|
3
2
|
|
|
4
3
|
module Redstream
|
|
@@ -55,7 +54,7 @@ module Redstream
|
|
|
55
54
|
end
|
|
56
55
|
|
|
57
56
|
def get_lock
|
|
58
|
-
@get_lock_script
|
|
57
|
+
@get_lock_script = <<~GET_LOCK_SCRIPT
|
|
59
58
|
local lock_key_name, id = ARGV[1], ARGV[2]
|
|
60
59
|
|
|
61
60
|
local cur = redis.call('get', lock_key_name)
|
|
@@ -71,10 +70,9 @@ module Redstream
|
|
|
71
70
|
end
|
|
72
71
|
|
|
73
72
|
return false
|
|
74
|
-
|
|
73
|
+
GET_LOCK_SCRIPT
|
|
75
74
|
|
|
76
75
|
Redstream.connection_pool.with { |redis| redis.eval(@get_lock_script, argv: [Redstream.lock_key_name(@name), @id]) }
|
|
77
76
|
end
|
|
78
77
|
end
|
|
79
78
|
end
|
|
80
|
-
|
data/lib/redstream/message.rb
CHANGED
data/lib/redstream/model.rb
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
|
|
2
1
|
module Redstream
|
|
3
2
|
# Include Redstream::Model in your model to stream the model's updates via
|
|
4
3
|
# redis streams.
|
|
@@ -13,9 +12,11 @@ module Redstream
|
|
|
13
12
|
# end
|
|
14
13
|
|
|
15
14
|
module Model
|
|
15
|
+
IVAR_DELAY_MESSAGE_ID = :@__redstream_delay_message_id__
|
|
16
|
+
|
|
16
17
|
def self.included(base)
|
|
17
18
|
base.extend(ClassMethods)
|
|
18
|
-
end
|
|
19
|
+
end
|
|
19
20
|
|
|
20
21
|
module ClassMethods
|
|
21
22
|
# Adds after_save, after_touch, after_destroy and, most importantly,
|
|
@@ -30,11 +31,22 @@ module Redstream
|
|
|
30
31
|
# responsible for writing to a redis stream
|
|
31
32
|
|
|
32
33
|
def redstream_callbacks(producer: Producer.new)
|
|
33
|
-
after_save
|
|
34
|
-
after_touch
|
|
35
|
-
after_destroy { |object| producer.delay
|
|
36
|
-
|
|
37
|
-
|
|
34
|
+
after_save { |object| instance_variable_set(IVAR_DELAY_MESSAGE_ID, producer.delay(object)) if object.saved_changes.present? }
|
|
35
|
+
after_touch { |object| instance_variable_set(IVAR_DELAY_MESSAGE_ID, producer.delay(object)) }
|
|
36
|
+
after_destroy { |object| instance_variable_set(IVAR_DELAY_MESSAGE_ID, producer.delay(object)) }
|
|
37
|
+
|
|
38
|
+
after_commit(on: [:create, :update]) do |object|
|
|
39
|
+
if object.saved_changes.present?
|
|
40
|
+
producer.queue(object, delay_message_id: instance_variable_get(IVAR_DELAY_MESSAGE_ID))
|
|
41
|
+
instance_variable_set(IVAR_DELAY_MESSAGE_ID, nil)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
after_commit(on: :destroy) do |object|
|
|
46
|
+
producer.queue(object, delay_message_id: instance_variable_get(IVAR_DELAY_MESSAGE_ID))
|
|
47
|
+
instance_variable_set(IVAR_DELAY_MESSAGE_ID, nil)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
38
50
|
|
|
39
51
|
def redstream_name
|
|
40
52
|
name.pluralize.underscore
|
|
@@ -52,6 +64,5 @@ module Redstream
|
|
|
52
64
|
def redstream_payload
|
|
53
65
|
{ id: id }
|
|
54
66
|
end
|
|
55
|
-
end
|
|
67
|
+
end
|
|
56
68
|
end
|
|
57
|
-
|
data/lib/redstream/producer.rb
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
|
|
2
1
|
module Redstream
|
|
3
2
|
# A Redstream::Producer is responsible for writing the actual messages to
|
|
4
3
|
# redis. This includes the delay messages as well as the messages for
|
|
@@ -53,11 +52,11 @@ module Redstream
|
|
|
53
52
|
def bulk(records)
|
|
54
53
|
records_array = Array(records)
|
|
55
54
|
|
|
56
|
-
bulk_delay(records_array)
|
|
55
|
+
delay_message_ids = bulk_delay(records_array)
|
|
57
56
|
|
|
58
57
|
yield
|
|
59
58
|
|
|
60
|
-
bulk_queue(records_array)
|
|
59
|
+
bulk_queue(records_array, delay_message_ids: delay_message_ids)
|
|
61
60
|
end
|
|
62
61
|
|
|
63
62
|
# @api private
|
|
@@ -65,13 +64,15 @@ module Redstream
|
|
|
65
64
|
# Writes delay messages to a delay stream in redis.
|
|
66
65
|
#
|
|
67
66
|
# @param records [#to_a] The object/objects that will be updated or deleted
|
|
67
|
+
#
|
|
68
|
+
# @return The redis message ids
|
|
68
69
|
|
|
69
70
|
def bulk_delay(records)
|
|
70
|
-
records.each_slice(250) do |slice|
|
|
71
|
+
res = records.each_slice(250).flat_map do |slice|
|
|
71
72
|
Redstream.connection_pool.with do |redis|
|
|
72
73
|
redis.pipelined do
|
|
73
|
-
slice.
|
|
74
|
-
redis.xadd
|
|
74
|
+
slice.each do |object|
|
|
75
|
+
redis.xadd(Redstream.stream_key_name("#{stream_name(object)}.delay"), payload: JSON.dump(object.redstream_payload))
|
|
75
76
|
end
|
|
76
77
|
end
|
|
77
78
|
end
|
|
@@ -81,7 +82,7 @@ module Redstream
|
|
|
81
82
|
redis.wait(@wait, 0) if @wait
|
|
82
83
|
end
|
|
83
84
|
|
|
84
|
-
|
|
85
|
+
res
|
|
85
86
|
end
|
|
86
87
|
|
|
87
88
|
# @api private
|
|
@@ -89,13 +90,15 @@ module Redstream
|
|
|
89
90
|
# Writes messages to a stream in redis for immediate retrieval.
|
|
90
91
|
#
|
|
91
92
|
# @param records [#to_a] The object/objects that will be updated deleted
|
|
93
|
+
# @param delay_message_ids [#to_a] The delay message ids to delete
|
|
92
94
|
|
|
93
|
-
def bulk_queue(records)
|
|
94
|
-
records.each_slice(250) do |slice|
|
|
95
|
+
def bulk_queue(records, delay_message_ids: nil)
|
|
96
|
+
records.each_with_index.each_slice(250) do |slice|
|
|
95
97
|
Redstream.connection_pool.with do |redis|
|
|
96
98
|
redis.pipelined do
|
|
97
|
-
slice.each do |object|
|
|
98
|
-
redis.xadd
|
|
99
|
+
slice.each do |object, index|
|
|
100
|
+
redis.xadd(Redstream.stream_key_name(stream_name(object)), payload: JSON.dump(object.redstream_payload))
|
|
101
|
+
redis.xdel(Redstream.stream_key_name("#{stream_name(object)}.delay"), delay_message_ids[index]) if delay_message_ids
|
|
99
102
|
end
|
|
100
103
|
end
|
|
101
104
|
end
|
|
@@ -108,15 +111,16 @@ module Redstream
|
|
|
108
111
|
#
|
|
109
112
|
# Writes a single delay message to a delay stream in redis.
|
|
110
113
|
#
|
|
111
|
-
# @param object The object
|
|
114
|
+
# @param object The object that will be updated, deleted, etc.
|
|
115
|
+
#
|
|
116
|
+
# @return The redis message id
|
|
112
117
|
|
|
113
118
|
def delay(object)
|
|
114
119
|
Redstream.connection_pool.with do |redis|
|
|
115
|
-
redis.xadd
|
|
120
|
+
res = redis.xadd(Redstream.stream_key_name("#{stream_name(object)}.delay"), payload: JSON.dump(object.redstream_payload))
|
|
116
121
|
redis.wait(@wait, 0) if @wait
|
|
122
|
+
res
|
|
117
123
|
end
|
|
118
|
-
|
|
119
|
-
true
|
|
120
124
|
end
|
|
121
125
|
|
|
122
126
|
# @api private
|
|
@@ -124,10 +128,14 @@ module Redstream
|
|
|
124
128
|
# Writes a single message to a stream in redis for immediate retrieval.
|
|
125
129
|
#
|
|
126
130
|
# @param object The object hat will be updated, deleted, etc.
|
|
131
|
+
# @param delay_message_id The delay message id to delete
|
|
127
132
|
|
|
128
|
-
def queue(object)
|
|
133
|
+
def queue(object, delay_message_id: nil)
|
|
129
134
|
Redstream.connection_pool.with do |redis|
|
|
130
|
-
redis.
|
|
135
|
+
redis.pipelined do
|
|
136
|
+
redis.xadd(Redstream.stream_key_name(stream_name(object)), payload: JSON.dump(object.redstream_payload))
|
|
137
|
+
redis.xdel(Redstream.stream_key_name("#{stream_name(object)}.delay"), delay_message_id) if delay_message_id
|
|
138
|
+
end
|
|
131
139
|
end
|
|
132
140
|
|
|
133
141
|
true
|
|
@@ -142,4 +150,3 @@ module Redstream
|
|
|
142
150
|
end
|
|
143
151
|
end
|
|
144
152
|
end
|
|
145
|
-
|
data/lib/redstream/trimmer.rb
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
|
|
2
1
|
module Redstream
|
|
3
2
|
# The Redstream::Trimmer class is neccessary to clean up messsages after all
|
|
4
3
|
# consumers have successfully processed and committed them. Otherwise they
|
|
@@ -79,7 +78,7 @@ module Redstream
|
|
|
79
78
|
end
|
|
80
79
|
|
|
81
80
|
sleep(5) unless got_lock
|
|
82
|
-
rescue => e
|
|
81
|
+
rescue StandardError => e
|
|
83
82
|
@logger.error e
|
|
84
83
|
|
|
85
84
|
sleep 5
|
|
@@ -88,4 +87,3 @@ module Redstream
|
|
|
88
87
|
end
|
|
89
88
|
end
|
|
90
89
|
end
|
|
91
|
-
|
data/lib/redstream/version.rb
CHANGED
data/redstream.gemspec
CHANGED
|
@@ -1,15 +1,14 @@
|
|
|
1
|
-
|
|
2
|
-
lib = File.expand_path('../lib', __FILE__)
|
|
1
|
+
lib = File.expand_path("lib", __dir__)
|
|
3
2
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
4
|
-
require
|
|
3
|
+
require "redstream/version"
|
|
5
4
|
|
|
6
5
|
Gem::Specification.new do |spec|
|
|
7
6
|
spec.name = "redstream"
|
|
8
7
|
spec.version = Redstream::VERSION
|
|
9
8
|
spec.authors = ["Benjamin Vetter"]
|
|
10
9
|
spec.email = ["vetter@plainpicture.de"]
|
|
11
|
-
spec.summary =
|
|
12
|
-
spec.description =
|
|
10
|
+
spec.summary = "Using redis streams to keep your primary database in sync with secondary datastores"
|
|
11
|
+
spec.description = "Using redis streams to keep your primary database in sync with secondary datastores"
|
|
13
12
|
spec.homepage = "https://github.com/mrkamel/redstream"
|
|
14
13
|
spec.license = "MIT"
|
|
15
14
|
|
|
@@ -18,21 +17,21 @@ Gem::Specification.new do |spec|
|
|
|
18
17
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
|
19
18
|
spec.require_paths = ["lib"]
|
|
20
19
|
|
|
21
|
-
spec.add_development_dependency "bundler"
|
|
22
|
-
spec.add_development_dependency "rake"
|
|
23
|
-
spec.add_development_dependency "rspec"
|
|
24
20
|
spec.add_development_dependency "activerecord"
|
|
21
|
+
spec.add_development_dependency "bundler"
|
|
22
|
+
spec.add_development_dependency "concurrent-ruby"
|
|
25
23
|
spec.add_development_dependency "database_cleaner"
|
|
26
|
-
spec.add_development_dependency "sqlite3", "1.3.13"
|
|
27
24
|
spec.add_development_dependency "factory_bot"
|
|
28
|
-
spec.add_development_dependency "timecop"
|
|
29
|
-
spec.add_development_dependency "concurrent-ruby"
|
|
30
|
-
spec.add_development_dependency "rspec-instafail"
|
|
31
25
|
spec.add_development_dependency "mocha"
|
|
26
|
+
spec.add_development_dependency "rake"
|
|
27
|
+
spec.add_development_dependency "rspec"
|
|
28
|
+
spec.add_development_dependency "rspec-instafail"
|
|
29
|
+
spec.add_development_dependency "rubocop"
|
|
30
|
+
spec.add_development_dependency "sqlite3"
|
|
31
|
+
spec.add_development_dependency "timecop"
|
|
32
32
|
|
|
33
|
-
spec.add_dependency "connection_pool"
|
|
34
33
|
spec.add_dependency "activesupport"
|
|
35
|
-
spec.add_dependency "
|
|
34
|
+
spec.add_dependency "connection_pool"
|
|
36
35
|
spec.add_dependency "json"
|
|
36
|
+
spec.add_dependency "redis", ">= 4.1.0"
|
|
37
37
|
end
|
|
38
|
-
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
|
|
2
1
|
require File.expand_path("../spec_helper", __dir__)
|
|
3
2
|
|
|
4
3
|
RSpec.describe Redstream::Consumer do
|
|
@@ -6,7 +5,7 @@ RSpec.describe Redstream::Consumer do
|
|
|
6
5
|
it "doesn't call the block without messages" do
|
|
7
6
|
called = false
|
|
8
7
|
|
|
9
|
-
Redstream::Consumer.new(name: "consumer", stream_name: "products", batch_size: 5).run_once do |
|
|
8
|
+
Redstream::Consumer.new(name: "consumer", stream_name: "products", batch_size: 5).run_once do |_batch|
|
|
10
9
|
called = true
|
|
11
10
|
end
|
|
12
11
|
|
|
@@ -18,9 +17,9 @@ RSpec.describe Redstream::Consumer do
|
|
|
18
17
|
|
|
19
18
|
calls = Concurrent::AtomicFixnum.new(0)
|
|
20
19
|
|
|
21
|
-
threads = Array.new(2) do |
|
|
20
|
+
threads = Array.new(2) do |_i|
|
|
22
21
|
Thread.new do
|
|
23
|
-
Redstream::Consumer.new(name: "consumer", stream_name: "products", batch_size: 5).run_once do |
|
|
22
|
+
Redstream::Consumer.new(name: "consumer", stream_name: "products", batch_size: 5).run_once do |_batch|
|
|
24
23
|
calls.increment
|
|
25
24
|
|
|
26
25
|
sleep 1
|
|
@@ -55,7 +54,7 @@ RSpec.describe Redstream::Consumer do
|
|
|
55
54
|
end
|
|
56
55
|
|
|
57
56
|
it "yields messages in batches" do
|
|
58
|
-
|
|
57
|
+
create_list(:product, 15)
|
|
59
58
|
|
|
60
59
|
consumer = Redstream::Consumer.new(name: "consumer", stream_name: "products", batch_size: 10)
|
|
61
60
|
|
|
@@ -81,10 +80,11 @@ RSpec.describe Redstream::Consumer do
|
|
|
81
80
|
|
|
82
81
|
all_messages = redis.xrange(Redstream.stream_key_name("products"), "-", "+")
|
|
83
82
|
|
|
84
|
-
Redstream::Consumer.new(name: "consumer", stream_name: "products").run_once
|
|
83
|
+
Redstream::Consumer.new(name: "consumer", stream_name: "products").run_once do
|
|
84
|
+
# nothing
|
|
85
|
+
end
|
|
85
86
|
|
|
86
87
|
expect(redis.get(Redstream.offset_key_name(stream_name: "products", consumer_name: "consumer"))).to eq(all_messages.last[0])
|
|
87
88
|
end
|
|
88
89
|
end
|
|
89
90
|
end
|
|
90
|
-
|
data/spec/redstream/lock_spec.rb
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
|
|
2
1
|
require File.expand_path("../spec_helper", __dir__)
|
|
3
2
|
|
|
4
3
|
RSpec.describe Redstream::Lock do
|
|
@@ -7,7 +6,7 @@ RSpec.describe Redstream::Lock do
|
|
|
7
6
|
lock_results = Concurrent::Array.new
|
|
8
7
|
calls = Concurrent::AtomicFixnum.new(0)
|
|
9
8
|
|
|
10
|
-
threads = Array.new(2) do |
|
|
9
|
+
threads = Array.new(2) do |_i|
|
|
11
10
|
Thread.new do
|
|
12
11
|
lock_results << Redstream::Lock.new(name: "lock").acquire do
|
|
13
12
|
calls.increment
|
|
@@ -65,4 +64,3 @@ RSpec.describe Redstream::Lock do
|
|
|
65
64
|
end
|
|
66
65
|
end
|
|
67
66
|
end
|
|
68
|
-
|
|
@@ -1,57 +1,136 @@
|
|
|
1
|
-
|
|
2
1
|
require File.expand_path("../spec_helper", __dir__)
|
|
3
2
|
|
|
4
3
|
RSpec.describe Redstream::Model do
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
describe "after_save" do
|
|
5
|
+
it "adds a delay message after_save" do
|
|
6
|
+
Product.transaction do
|
|
7
|
+
expect { create(:product) }.to change { redis.xlen(Redstream.stream_key_name("products.delay")) }
|
|
8
|
+
end
|
|
9
|
+
end
|
|
7
10
|
|
|
8
|
-
|
|
11
|
+
it "assigns the delay message id" do
|
|
12
|
+
Product.transaction do
|
|
13
|
+
expect(create(:product).instance_variable_get(Redstream::Model::IVAR_DELAY_MESSAGE_ID)).to be_present
|
|
14
|
+
end
|
|
15
|
+
end
|
|
9
16
|
|
|
10
|
-
|
|
11
|
-
|
|
17
|
+
it "adds the correct payload for the delay message" do
|
|
18
|
+
Product.transaction do
|
|
19
|
+
product = create(:product)
|
|
20
|
+
|
|
21
|
+
expect(redis.xrange(Redstream.stream_key_name("products.delay"), "-", "+").first[1]).to eq("payload" => JSON.dump(product.redstream_payload))
|
|
22
|
+
end
|
|
12
23
|
end
|
|
13
24
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
25
|
+
it "adds a queue message after_save on commit" do
|
|
26
|
+
expect { create(:product) }.to change { redis.xlen(Redstream.stream_key_name("products")) }
|
|
27
|
+
end
|
|
17
28
|
|
|
18
|
-
|
|
19
|
-
|
|
29
|
+
it "deletes the delay message on commit" do
|
|
30
|
+
product = create(:product)
|
|
20
31
|
|
|
21
|
-
|
|
32
|
+
expect(redis.xlen(Redstream.stream_key_name("products.delay"))).to eq(0)
|
|
33
|
+
expect(product.instance_variable_get(Redstream::Model::IVAR_DELAY_MESSAGE_ID)).to be_nil
|
|
34
|
+
end
|
|
22
35
|
|
|
23
|
-
|
|
36
|
+
it "does not add a delay message after_save if there are no changes" do
|
|
37
|
+
product = create(:product)
|
|
24
38
|
|
|
25
|
-
|
|
26
|
-
product.touch
|
|
39
|
+
expect { product.save }.not_to change { redis.xlen(Redstream.stream_key_name("products.delay")) }
|
|
27
40
|
end
|
|
28
41
|
|
|
29
|
-
|
|
30
|
-
|
|
42
|
+
it "does not add a queue messages after_save on commit if there are no changes" do
|
|
43
|
+
product = create(:product)
|
|
44
|
+
|
|
45
|
+
expect { product.save }.not_to change { redis.xlen(Redstream.stream_key_name("products")) }
|
|
46
|
+
end
|
|
31
47
|
end
|
|
32
48
|
|
|
33
|
-
|
|
34
|
-
|
|
49
|
+
describe "after_touch" do
|
|
50
|
+
it "adds a delay message after touch" do
|
|
51
|
+
product = create(:product)
|
|
35
52
|
|
|
36
|
-
|
|
53
|
+
Product.transaction do
|
|
54
|
+
expect { product.touch }.to change { redis.xlen(Redstream.stream_key_name("products.delay")) }
|
|
55
|
+
end
|
|
56
|
+
end
|
|
37
57
|
|
|
38
|
-
|
|
58
|
+
it "assigns the delay message id" do
|
|
59
|
+
product = create(:product)
|
|
39
60
|
|
|
40
|
-
|
|
41
|
-
|
|
61
|
+
Product.transaction do
|
|
62
|
+
product.touch
|
|
63
|
+
|
|
64
|
+
expect(product.instance_variable_get(Redstream::Model::IVAR_DELAY_MESSAGE_ID)).to be_present
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
it "sets the correct payload for the delay message" do
|
|
69
|
+
product = create(:product)
|
|
70
|
+
|
|
71
|
+
Product.transaction do
|
|
72
|
+
product.touch
|
|
73
|
+
|
|
74
|
+
expect(redis.xrange(Redstream.stream_key_name("products.delay"), "-", "+").last[1]).to eq("payload" => JSON.dump(product.redstream_payload))
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
it "adds a queue message after touch on commit" do
|
|
79
|
+
product = create(:product)
|
|
80
|
+
|
|
81
|
+
expect { product.touch }.to change { redis.xlen(Redstream.stream_key_name("products")) }
|
|
42
82
|
end
|
|
43
83
|
|
|
44
|
-
|
|
45
|
-
|
|
84
|
+
it "deletes the delay message after touch on commit" do
|
|
85
|
+
product = create(:product)
|
|
86
|
+
product.touch
|
|
87
|
+
|
|
88
|
+
expect(redis.xlen(Redstream.stream_key_name("products.delay"))).to eq(0)
|
|
89
|
+
expect(product.instance_variable_get(Redstream::Model::IVAR_DELAY_MESSAGE_ID)).to be_nil
|
|
90
|
+
end
|
|
46
91
|
end
|
|
47
92
|
|
|
48
|
-
|
|
49
|
-
|
|
93
|
+
describe "after_destroy" do
|
|
94
|
+
it "adds a delay message after destroy" do
|
|
95
|
+
product = create(:product)
|
|
96
|
+
|
|
97
|
+
Product.transaction do
|
|
98
|
+
expect { product.destroy }.to change { redis.xlen(Redstream.stream_key_name("products.delay")) }
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
it "assigns the delay message id" do
|
|
103
|
+
product = create(:product)
|
|
104
|
+
|
|
105
|
+
Product.transaction do
|
|
106
|
+
product.destroy
|
|
107
|
+
|
|
108
|
+
expect(product.instance_variable_get(Redstream::Model::IVAR_DELAY_MESSAGE_ID)).to be_present
|
|
109
|
+
end
|
|
110
|
+
end
|
|
50
111
|
|
|
51
|
-
|
|
112
|
+
it "sets the correct payload for the delay message" do
|
|
113
|
+
product = create(:product)
|
|
52
114
|
|
|
53
|
-
|
|
54
|
-
|
|
115
|
+
Product.transaction do
|
|
116
|
+
product.destroy
|
|
117
|
+
|
|
118
|
+
expect(redis.xrange(Redstream.stream_key_name("products.delay"), "-", "+").last[1]).to eq("payload" => JSON.dump(product.redstream_payload))
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
it "adds a queue messages after destroy on commit" do
|
|
123
|
+
product = create(:product)
|
|
124
|
+
|
|
125
|
+
expect { product.destroy }.to change { redis.xlen(Redstream.stream_key_name("products")) }.by(1)
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
it "deletes the delay message after destroy on commit" do
|
|
129
|
+
product = create(:product)
|
|
130
|
+
product.destroy
|
|
131
|
+
|
|
132
|
+
expect(redis.xlen(Redstream.stream_key_name("products.delay"))).to eq(0)
|
|
133
|
+
expect(product.instance_variable_get(Redstream::Model::IVAR_DELAY_MESSAGE_ID)).to be_nil
|
|
134
|
+
end
|
|
55
135
|
end
|
|
56
136
|
end
|
|
57
|
-
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
|
|
2
1
|
require File.expand_path("../spec_helper", __dir__)
|
|
3
2
|
|
|
4
3
|
RSpec.describe Redstream::Producer do
|
|
@@ -11,6 +10,17 @@ RSpec.describe Redstream::Producer do
|
|
|
11
10
|
expect { Redstream::Producer.new.queue(product) }.to change { redis.xlen(stream_key_name) }.by(1)
|
|
12
11
|
expect(redis.xrange(stream_key_name, "-", "+").last[1]).to eq("payload" => JSON.dump(product.redstream_payload))
|
|
13
12
|
end
|
|
13
|
+
|
|
14
|
+
it "deletes the delay message when given" do
|
|
15
|
+
product = create(:product)
|
|
16
|
+
|
|
17
|
+
producer = Redstream::Producer.new
|
|
18
|
+
|
|
19
|
+
id = producer.delay(product)
|
|
20
|
+
producer.queue(product, delay_message_id: id)
|
|
21
|
+
|
|
22
|
+
expect(redis.xlen(Redstream.stream_key_name("products.delay"))).to eq(0)
|
|
23
|
+
end
|
|
14
24
|
end
|
|
15
25
|
|
|
16
26
|
describe "#delay" do
|
|
@@ -32,6 +42,57 @@ RSpec.describe Redstream::Producer do
|
|
|
32
42
|
end
|
|
33
43
|
end
|
|
34
44
|
|
|
45
|
+
describe "#bulk" do
|
|
46
|
+
it "adds bulk delay messages for scopes" do
|
|
47
|
+
products = create_list(:product, 2)
|
|
48
|
+
|
|
49
|
+
stream_key_name = Redstream.stream_key_name("products")
|
|
50
|
+
|
|
51
|
+
expect(redis.xlen("#{stream_key_name}.delay")).to eq(0)
|
|
52
|
+
|
|
53
|
+
Redstream::Producer.new.bulk(Product.all) do
|
|
54
|
+
messages = redis.xrange("#{stream_key_name}.delay", "-", "+").last(2).map { |message| message[1] }
|
|
55
|
+
|
|
56
|
+
expect(messages).to eq([
|
|
57
|
+
{ "payload" => JSON.dump(products[0].redstream_payload) },
|
|
58
|
+
{ "payload" => JSON.dump(products[1].redstream_payload) }
|
|
59
|
+
])
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
it "adds bulk queue messages for scopes" do
|
|
64
|
+
products = create_list(:product, 2)
|
|
65
|
+
|
|
66
|
+
stream_key_name = Redstream.stream_key_name("products")
|
|
67
|
+
|
|
68
|
+
expect do
|
|
69
|
+
Redstream::Producer.new.bulk(Product.all) do
|
|
70
|
+
# nothing
|
|
71
|
+
end
|
|
72
|
+
end.to change { redis.xlen(stream_key_name) }.by(2)
|
|
73
|
+
|
|
74
|
+
messages = redis.xrange(stream_key_name, "-", "+").last(2).map { |message| message[1] }
|
|
75
|
+
|
|
76
|
+
expect(messages).to eq([
|
|
77
|
+
{ "payload" => JSON.dump(products[0].redstream_payload) },
|
|
78
|
+
{ "payload" => JSON.dump(products[1].redstream_payload) }
|
|
79
|
+
])
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
it "deletes the delay messages after the queue messages have been sent" do
|
|
83
|
+
products = create_list(:product, 2)
|
|
84
|
+
producer = Redstream::Producer.new
|
|
85
|
+
|
|
86
|
+
other_delay_message_id = producer.delay(create(:product))
|
|
87
|
+
|
|
88
|
+
producer.bulk(products) do
|
|
89
|
+
expect(redis.xlen(Redstream.stream_key_name("products.delay"))).to eq(3)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
expect(redis.xrange(Redstream.stream_key_name("products.delay"), "-", "+").map(&:first)).to eq([other_delay_message_id])
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
35
96
|
describe "#bulk_queue" do
|
|
36
97
|
it "adds bulk queue messages for scopes" do
|
|
37
98
|
products = create_list(:product, 2)
|
|
@@ -47,6 +108,18 @@ RSpec.describe Redstream::Producer do
|
|
|
47
108
|
{ "payload" => JSON.dump(products[1].redstream_payload) }
|
|
48
109
|
])
|
|
49
110
|
end
|
|
111
|
+
|
|
112
|
+
it "deletes the delay messages after the queue messages have been sent" do
|
|
113
|
+
products = create_list(:product, 2)
|
|
114
|
+
producer = Redstream::Producer.new
|
|
115
|
+
|
|
116
|
+
delay_message_ids = producer.bulk_delay(products)
|
|
117
|
+
other_delay_message_id = producer.delay(create(:product))
|
|
118
|
+
|
|
119
|
+
producer.bulk_queue(products, delay_message_ids: delay_message_ids)
|
|
120
|
+
|
|
121
|
+
expect(redis.xrange(Redstream.stream_key_name("products.delay"), "-", "+").map(&:first)).to eq([other_delay_message_id])
|
|
122
|
+
end
|
|
50
123
|
end
|
|
51
124
|
|
|
52
125
|
describe "#bulk_delay" do
|
|
@@ -65,8 +138,8 @@ RSpec.describe Redstream::Producer do
|
|
|
65
138
|
])
|
|
66
139
|
end
|
|
67
140
|
|
|
68
|
-
it "should
|
|
69
|
-
|
|
141
|
+
it "should respect wait for delay" do
|
|
142
|
+
create(:product)
|
|
70
143
|
|
|
71
144
|
stream_key_name = Redstream.stream_key_name("products.delay")
|
|
72
145
|
|
|
@@ -76,4 +149,3 @@ RSpec.describe Redstream::Producer do
|
|
|
76
149
|
end
|
|
77
150
|
end
|
|
78
151
|
end
|
|
79
|
-
|
data/spec/redstream_spec.rb
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
|
|
2
1
|
require File.expand_path("spec_helper", __dir__)
|
|
3
2
|
|
|
4
3
|
RSpec.describe Redstream do
|
|
@@ -31,11 +30,21 @@ RSpec.describe Redstream do
|
|
|
31
30
|
end
|
|
32
31
|
end
|
|
33
32
|
|
|
33
|
+
describe ".stream_size" do
|
|
34
|
+
it "returns the stream's size" do
|
|
35
|
+
expect(Redstream.stream_size("products")).to eq(0)
|
|
36
|
+
|
|
37
|
+
redis.xadd("redstream:stream:products", key: "value")
|
|
38
|
+
|
|
39
|
+
expect(Redstream.stream_size("products")).to eq(1)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
34
43
|
describe ".max_stream_id" do
|
|
35
44
|
it "returns the stream's max id" do
|
|
36
45
|
expect(Redstream.max_stream_id("products")).to be_nil
|
|
37
46
|
|
|
38
|
-
|
|
47
|
+
_id1 = redis.xadd("redstream:stream:products", key: "value")
|
|
39
48
|
id2 = redis.xadd("redstream:stream:products", key: "value")
|
|
40
49
|
|
|
41
50
|
expect(Redstream.max_stream_id("products")).to eq(id2)
|
|
@@ -46,7 +55,7 @@ RSpec.describe Redstream do
|
|
|
46
55
|
it "returns the consumer's max id" do
|
|
47
56
|
expect(Redstream.max_consumer_id(stream_name: "products", consumer_name: "consumer")).to be_nil
|
|
48
57
|
|
|
49
|
-
|
|
58
|
+
_id1 = redis.xadd("redstream:stream:products", key: "value")
|
|
50
59
|
id2 = redis.xadd("redstream:stream:products", key: "value")
|
|
51
60
|
|
|
52
61
|
Redstream::Consumer.new(name: "consumer", stream_name: "products").run_once do |messages|
|
|
@@ -114,4 +123,3 @@ RSpec.describe Redstream do
|
|
|
114
123
|
end
|
|
115
124
|
end
|
|
116
125
|
end
|
|
117
|
-
|
data/spec/spec_helper.rb
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
|
|
2
1
|
require File.expand_path("../lib/redstream", __dir__)
|
|
3
2
|
require "active_record"
|
|
4
3
|
require "factory_bot"
|
|
@@ -18,7 +17,7 @@ ActiveRecord::Base.connection.execute "DROP TABLE IF EXISTS products"
|
|
|
18
17
|
|
|
19
18
|
ActiveRecord::Base.connection.create_table :products do |t|
|
|
20
19
|
t.string :title
|
|
21
|
-
t.timestamps
|
|
20
|
+
t.timestamps null: false
|
|
22
21
|
end
|
|
23
22
|
|
|
24
23
|
class Product < ActiveRecord::Base
|
|
@@ -62,5 +61,3 @@ RSpec.configure do |config|
|
|
|
62
61
|
Redis.new.flushdb
|
|
63
62
|
end
|
|
64
63
|
end
|
|
65
|
-
|
|
66
|
-
|
metadata
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: redstream
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.0
|
|
4
|
+
version: 0.4.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Benjamin Vetter
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2021-04-26 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
|
-
name:
|
|
14
|
+
name: activerecord
|
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
|
16
16
|
requirements:
|
|
17
17
|
- - ">="
|
|
@@ -25,7 +25,7 @@ dependencies:
|
|
|
25
25
|
- !ruby/object:Gem::Version
|
|
26
26
|
version: '0'
|
|
27
27
|
- !ruby/object:Gem::Dependency
|
|
28
|
-
name:
|
|
28
|
+
name: bundler
|
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
|
30
30
|
requirements:
|
|
31
31
|
- - ">="
|
|
@@ -39,7 +39,7 @@ dependencies:
|
|
|
39
39
|
- !ruby/object:Gem::Version
|
|
40
40
|
version: '0'
|
|
41
41
|
- !ruby/object:Gem::Dependency
|
|
42
|
-
name:
|
|
42
|
+
name: concurrent-ruby
|
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
|
44
44
|
requirements:
|
|
45
45
|
- - ">="
|
|
@@ -53,7 +53,7 @@ dependencies:
|
|
|
53
53
|
- !ruby/object:Gem::Version
|
|
54
54
|
version: '0'
|
|
55
55
|
- !ruby/object:Gem::Dependency
|
|
56
|
-
name:
|
|
56
|
+
name: database_cleaner
|
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
|
58
58
|
requirements:
|
|
59
59
|
- - ">="
|
|
@@ -67,7 +67,7 @@ dependencies:
|
|
|
67
67
|
- !ruby/object:Gem::Version
|
|
68
68
|
version: '0'
|
|
69
69
|
- !ruby/object:Gem::Dependency
|
|
70
|
-
name:
|
|
70
|
+
name: factory_bot
|
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
|
72
72
|
requirements:
|
|
73
73
|
- - ">="
|
|
@@ -81,21 +81,21 @@ dependencies:
|
|
|
81
81
|
- !ruby/object:Gem::Version
|
|
82
82
|
version: '0'
|
|
83
83
|
- !ruby/object:Gem::Dependency
|
|
84
|
-
name:
|
|
84
|
+
name: mocha
|
|
85
85
|
requirement: !ruby/object:Gem::Requirement
|
|
86
86
|
requirements:
|
|
87
|
-
- -
|
|
87
|
+
- - ">="
|
|
88
88
|
- !ruby/object:Gem::Version
|
|
89
|
-
version:
|
|
89
|
+
version: '0'
|
|
90
90
|
type: :development
|
|
91
91
|
prerelease: false
|
|
92
92
|
version_requirements: !ruby/object:Gem::Requirement
|
|
93
93
|
requirements:
|
|
94
|
-
- -
|
|
94
|
+
- - ">="
|
|
95
95
|
- !ruby/object:Gem::Version
|
|
96
|
-
version:
|
|
96
|
+
version: '0'
|
|
97
97
|
- !ruby/object:Gem::Dependency
|
|
98
|
-
name:
|
|
98
|
+
name: rake
|
|
99
99
|
requirement: !ruby/object:Gem::Requirement
|
|
100
100
|
requirements:
|
|
101
101
|
- - ">="
|
|
@@ -109,7 +109,7 @@ dependencies:
|
|
|
109
109
|
- !ruby/object:Gem::Version
|
|
110
110
|
version: '0'
|
|
111
111
|
- !ruby/object:Gem::Dependency
|
|
112
|
-
name:
|
|
112
|
+
name: rspec
|
|
113
113
|
requirement: !ruby/object:Gem::Requirement
|
|
114
114
|
requirements:
|
|
115
115
|
- - ">="
|
|
@@ -123,7 +123,7 @@ dependencies:
|
|
|
123
123
|
- !ruby/object:Gem::Version
|
|
124
124
|
version: '0'
|
|
125
125
|
- !ruby/object:Gem::Dependency
|
|
126
|
-
name:
|
|
126
|
+
name: rspec-instafail
|
|
127
127
|
requirement: !ruby/object:Gem::Requirement
|
|
128
128
|
requirements:
|
|
129
129
|
- - ">="
|
|
@@ -137,7 +137,7 @@ dependencies:
|
|
|
137
137
|
- !ruby/object:Gem::Version
|
|
138
138
|
version: '0'
|
|
139
139
|
- !ruby/object:Gem::Dependency
|
|
140
|
-
name:
|
|
140
|
+
name: rubocop
|
|
141
141
|
requirement: !ruby/object:Gem::Requirement
|
|
142
142
|
requirements:
|
|
143
143
|
- - ">="
|
|
@@ -151,7 +151,7 @@ dependencies:
|
|
|
151
151
|
- !ruby/object:Gem::Version
|
|
152
152
|
version: '0'
|
|
153
153
|
- !ruby/object:Gem::Dependency
|
|
154
|
-
name:
|
|
154
|
+
name: sqlite3
|
|
155
155
|
requirement: !ruby/object:Gem::Requirement
|
|
156
156
|
requirements:
|
|
157
157
|
- - ">="
|
|
@@ -165,13 +165,13 @@ dependencies:
|
|
|
165
165
|
- !ruby/object:Gem::Version
|
|
166
166
|
version: '0'
|
|
167
167
|
- !ruby/object:Gem::Dependency
|
|
168
|
-
name:
|
|
168
|
+
name: timecop
|
|
169
169
|
requirement: !ruby/object:Gem::Requirement
|
|
170
170
|
requirements:
|
|
171
171
|
- - ">="
|
|
172
172
|
- !ruby/object:Gem::Version
|
|
173
173
|
version: '0'
|
|
174
|
-
type: :
|
|
174
|
+
type: :development
|
|
175
175
|
prerelease: false
|
|
176
176
|
version_requirements: !ruby/object:Gem::Requirement
|
|
177
177
|
requirements:
|
|
@@ -193,19 +193,19 @@ dependencies:
|
|
|
193
193
|
- !ruby/object:Gem::Version
|
|
194
194
|
version: '0'
|
|
195
195
|
- !ruby/object:Gem::Dependency
|
|
196
|
-
name:
|
|
196
|
+
name: connection_pool
|
|
197
197
|
requirement: !ruby/object:Gem::Requirement
|
|
198
198
|
requirements:
|
|
199
199
|
- - ">="
|
|
200
200
|
- !ruby/object:Gem::Version
|
|
201
|
-
version:
|
|
201
|
+
version: '0'
|
|
202
202
|
type: :runtime
|
|
203
203
|
prerelease: false
|
|
204
204
|
version_requirements: !ruby/object:Gem::Requirement
|
|
205
205
|
requirements:
|
|
206
206
|
- - ">="
|
|
207
207
|
- !ruby/object:Gem::Version
|
|
208
|
-
version:
|
|
208
|
+
version: '0'
|
|
209
209
|
- !ruby/object:Gem::Dependency
|
|
210
210
|
name: json
|
|
211
211
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -220,6 +220,20 @@ dependencies:
|
|
|
220
220
|
- - ">="
|
|
221
221
|
- !ruby/object:Gem::Version
|
|
222
222
|
version: '0'
|
|
223
|
+
- !ruby/object:Gem::Dependency
|
|
224
|
+
name: redis
|
|
225
|
+
requirement: !ruby/object:Gem::Requirement
|
|
226
|
+
requirements:
|
|
227
|
+
- - ">="
|
|
228
|
+
- !ruby/object:Gem::Version
|
|
229
|
+
version: 4.1.0
|
|
230
|
+
type: :runtime
|
|
231
|
+
prerelease: false
|
|
232
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
233
|
+
requirements:
|
|
234
|
+
- - ">="
|
|
235
|
+
- !ruby/object:Gem::Version
|
|
236
|
+
version: 4.1.0
|
|
223
237
|
description: Using redis streams to keep your primary database in sync with secondary
|
|
224
238
|
datastores
|
|
225
239
|
email:
|
|
@@ -228,8 +242,11 @@ executables: []
|
|
|
228
242
|
extensions: []
|
|
229
243
|
extra_rdoc_files: []
|
|
230
244
|
files:
|
|
245
|
+
- ".github/workflows/test.yml"
|
|
231
246
|
- ".gitignore"
|
|
247
|
+
- ".rubocop.yml"
|
|
232
248
|
- ".travis.yml"
|
|
249
|
+
- CHANGELOG.md
|
|
233
250
|
- Gemfile
|
|
234
251
|
- LICENSE.txt
|
|
235
252
|
- README.md
|
|
@@ -272,8 +289,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
272
289
|
- !ruby/object:Gem::Version
|
|
273
290
|
version: '0'
|
|
274
291
|
requirements: []
|
|
275
|
-
|
|
276
|
-
rubygems_version: 2.7.3
|
|
292
|
+
rubygems_version: 3.0.3
|
|
277
293
|
signing_key:
|
|
278
294
|
specification_version: 4
|
|
279
295
|
summary: Using redis streams to keep your primary database in sync with secondary
|