redis 4.8.1 → 6.0.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/CHANGELOG.md +138 -1
- data/README.md +285 -169
- data/lib/redis/client.rb +116 -611
- data/lib/redis/commands/bitmaps.rb +14 -4
- data/lib/redis/commands/cluster.rb +1 -18
- data/lib/redis/commands/connection.rb +5 -10
- data/lib/redis/commands/geo.rb +109 -7
- data/lib/redis/commands/hashes.rb +179 -8
- data/lib/redis/commands/hyper_log_log.rb +1 -1
- data/lib/redis/commands/keys.rb +32 -24
- data/lib/redis/commands/lists.rb +167 -25
- data/lib/redis/commands/modules/json.rb +530 -0
- data/lib/redis/commands/modules/search/aggregation.rb +418 -0
- data/lib/redis/commands/modules/search/dialect.rb +14 -0
- data/lib/redis/commands/modules/search/field.rb +306 -0
- data/lib/redis/commands/modules/search/hybrid.rb +359 -0
- data/lib/redis/commands/modules/search/index.rb +351 -0
- data/lib/redis/commands/modules/search/index_definition.rb +114 -0
- data/lib/redis/commands/modules/search/miscellaneous.rb +607 -0
- data/lib/redis/commands/modules/search/query.rb +738 -0
- data/lib/redis/commands/modules/search/result.rb +488 -0
- data/lib/redis/commands/modules/search/schema.rb +211 -0
- data/lib/redis/commands/modules/search.rb +19 -0
- data/lib/redis/commands/pubsub.rb +34 -25
- data/lib/redis/commands/server.rb +15 -15
- data/lib/redis/commands/sets.rb +76 -40
- data/lib/redis/commands/sorted_sets.rb +128 -19
- data/lib/redis/commands/streams.rb +75 -28
- data/lib/redis/commands/strings.rb +18 -17
- data/lib/redis/commands/transactions.rb +7 -31
- data/lib/redis/commands.rb +39 -20
- data/lib/redis/distributed.rb +407 -73
- data/lib/redis/errors.rb +20 -50
- data/lib/redis/hash_ring.rb +26 -26
- data/lib/redis/lib_identity.rb +105 -0
- data/lib/redis/pipeline.rb +47 -222
- data/lib/redis/subscribe.rb +51 -15
- data/lib/redis/version.rb +1 -1
- data/lib/redis.rb +213 -188
- metadata +25 -59
- data/lib/redis/cluster/command.rb +0 -79
- data/lib/redis/cluster/command_loader.rb +0 -33
- data/lib/redis/cluster/key_slot_converter.rb +0 -72
- data/lib/redis/cluster/node.rb +0 -120
- data/lib/redis/cluster/node_key.rb +0 -31
- data/lib/redis/cluster/node_loader.rb +0 -34
- data/lib/redis/cluster/option.rb +0 -100
- data/lib/redis/cluster/slot.rb +0 -86
- data/lib/redis/cluster/slot_loader.rb +0 -46
- data/lib/redis/cluster.rb +0 -315
- data/lib/redis/connection/command_helper.rb +0 -41
- data/lib/redis/connection/hiredis.rb +0 -68
- data/lib/redis/connection/registry.rb +0 -13
- data/lib/redis/connection/ruby.rb +0 -437
- data/lib/redis/connection/synchrony.rb +0 -148
- data/lib/redis/connection.rb +0 -11
data/README.md
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
# redis-rb [![Build Status][gh-actions-image]][gh-actions-link] [![Inline docs][
|
|
1
|
+
# redis-rb [![Build Status][gh-actions-image]][gh-actions-link] [![Inline docs][rdoc-master-image]][rdoc-master-link]
|
|
2
2
|
|
|
3
|
-
A Ruby client that tries to match [Redis][redis-home]' API one-to-one, while still
|
|
4
|
-
providing an idiomatic interface.
|
|
3
|
+
A Ruby client that tries to match [Redis][redis-home]' API one-to-one, while still providing an idiomatic interface.
|
|
5
4
|
|
|
6
5
|
See [RubyDoc.info][rubydoc] for the API docs of the latest published gem.
|
|
7
6
|
|
|
@@ -35,13 +34,9 @@ You can also specify connection options as a [`redis://` URL][redis-url]:
|
|
|
35
34
|
redis = Redis.new(url: "redis://:p4ssw0rd@10.0.1.1:6380/15")
|
|
36
35
|
```
|
|
37
36
|
|
|
38
|
-
The client expects passwords with special
|
|
37
|
+
The client expects passwords with special characters to be URL-encoded (i.e.
|
|
39
38
|
`CGI.escape(password)`).
|
|
40
39
|
|
|
41
|
-
By default, the client will try to read the `REDIS_URL` environment variable
|
|
42
|
-
and use that as URL to connect to. The above statement is therefore equivalent
|
|
43
|
-
to setting this environment variable and calling `Redis.new` without arguments.
|
|
44
|
-
|
|
45
40
|
To connect to Redis listening on a Unix socket, try:
|
|
46
41
|
|
|
47
42
|
```ruby
|
|
@@ -76,10 +71,121 @@ redis.get("mykey")
|
|
|
76
71
|
All commands, their arguments, and return values are documented and
|
|
77
72
|
available on [RubyDoc.info][rubydoc].
|
|
78
73
|
|
|
74
|
+
## Language and server support
|
|
75
|
+
|
|
76
|
+
redis-rb targets actively supported runtimes on both the language and the server side:
|
|
77
|
+
|
|
78
|
+
- **Ruby:** Ruby 3.2 and newer. See the [Ruby maintenance branches][ruby-branches] page for
|
|
79
|
+
each version's status and dates.
|
|
80
|
+
- **Redis server:** the versions designated for support by Redis. See
|
|
81
|
+
[Supported Redis database versions][redis-versions].
|
|
82
|
+
|
|
83
|
+
## Protocol (RESP3)
|
|
84
|
+
|
|
85
|
+
Starting in 6.0, the client negotiates the [RESP3 protocol][resp3] (`HELLO 3`)
|
|
86
|
+
by default. Command return values are unchanged from 5.x, with one exception:
|
|
87
|
+
`GEOPOS` and `GEOSEARCH`/`GEORADIUS` with `WITHCOORD` now return coordinates as
|
|
88
|
+
`Float` instead of `String`.
|
|
89
|
+
|
|
90
|
+
To keep the previous RESP2 behavior, pass `protocol: 2`:
|
|
91
|
+
|
|
92
|
+
```ruby
|
|
93
|
+
redis = Redis.new(protocol: 2)
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Servers without RESP3 support (Redis < 6.0, or anything replying `NOPROTO`) are
|
|
97
|
+
detected on connect and the client transparently falls back to RESP2, so no
|
|
98
|
+
configuration is needed for older servers.
|
|
99
|
+
|
|
100
|
+
### Why RESP3 is the default
|
|
101
|
+
|
|
102
|
+
RESP3's richer wire types let the parser deliver replies already in their final
|
|
103
|
+
Ruby shape. Under RESP2, structured replies arrive as flat arrays of bulk
|
|
104
|
+
strings and the client re-shapes them in Ruby: `HGETALL` turns a flat
|
|
105
|
+
`[field, value, field, value, ...]` array into a `Hash`, and sorted-set scores
|
|
106
|
+
are converted from `String` to `Float` pair by pair. Under RESP3 the server
|
|
107
|
+
tags these replies as native maps and doubles, so the final `Hash` and `Float`
|
|
108
|
+
values come straight out of the parser and the Ruby-side re-shaping pass
|
|
109
|
+
disappears entirely.
|
|
110
|
+
|
|
111
|
+
How much that saves depends on where parsing happens. In our benchmarks
|
|
112
|
+
([bench/resp_comparison.rb](bench/resp_comparison.rb), Ruby 3.4, 100-element
|
|
113
|
+
replies), hash reads (`HGETALL`) consistently use ~10–25% less client CPU per
|
|
114
|
+
call on both drivers. With the [hiredis driver](#hiredis-binding), where
|
|
115
|
+
parsing runs in C, sorted-set reads with scores gain up to 16% throughput and
|
|
116
|
+
~20% less CPU per call on top of that; with the pure-Ruby driver they are
|
|
117
|
+
unchanged, since the parser then spends in Ruby roughly what the re-shaping
|
|
118
|
+
pass used to cost. Simple string commands and stream commands are unaffected
|
|
119
|
+
either way — their reply shapes are the same in both protocols. In short:
|
|
120
|
+
RESP3 is never slower where it matters, and it pairs best with hiredis — that
|
|
121
|
+
combination moves all reply construction out of Ruby and into C.
|
|
122
|
+
|
|
123
|
+
Beyond performance, RESP3 unlocks protocol capabilities RESP2 simply doesn't
|
|
124
|
+
have. The most important is out-of-band **push messages**: the server can send
|
|
125
|
+
notifications on a connection without the client asking, which is the
|
|
126
|
+
foundation for server-assisted client-side caching (`CLIENT TRACKING`
|
|
127
|
+
invalidation events), pub/sub messages delivered over the regular command
|
|
128
|
+
connection instead of a dedicated one, and other server-initiated
|
|
129
|
+
notifications. Defaulting to RESP3 in 6.0 lays the groundwork for building
|
|
130
|
+
these features in future releases without another protocol migration.
|
|
131
|
+
|
|
132
|
+
See [the RESP3 migration guide](specs/migration-resp3.md) for full details.
|
|
133
|
+
|
|
134
|
+
## Client identification
|
|
135
|
+
|
|
136
|
+
On connect the client identifies itself to the server with `CLIENT SETINFO`, so
|
|
137
|
+
`redis-rb` and its version are visible in `CLIENT LIST` and `CLIENT INFO`:
|
|
138
|
+
|
|
139
|
+
```
|
|
140
|
+
lib-name=redis-rb lib-ver=<Redis::VERSION>
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
Libraries built on top of `redis-rb` can add their own identity with
|
|
144
|
+
`driver_info:`, which is reported alongside it. The recommended suffix format
|
|
145
|
+
is `<name>_v<version>`, following the convention used by the official client
|
|
146
|
+
libraries:
|
|
147
|
+
|
|
148
|
+
```ruby
|
|
149
|
+
Redis.new(driver_info: "my-gem_v#{MyGem::VERSION}")
|
|
150
|
+
# reported as: lib-name=redis-rb(my-gem_v1.0.0) lib-ver=<Redis::VERSION>
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
`driver_info:` also accepts an array, joined with `;` (the conventional
|
|
154
|
+
delimiter for multiple suffixes). It extends the reported name rather than
|
|
155
|
+
replacing it, so `redis-rb` stays identifiable either way. Runs of characters
|
|
156
|
+
the server would reject (spaces, non-printable bytes) and of the parentheses
|
|
157
|
+
that delimit the suffix are each replaced with a single `_`, or dropped at the
|
|
158
|
+
edges of the value.
|
|
159
|
+
|
|
160
|
+
Servers older than 7.2 don't support `CLIENT SETINFO`; they reject it, the error
|
|
161
|
+
is ignored, and the connection is used as normal. If a proxy or server can't
|
|
162
|
+
tolerate the command at all, pass `driver_info: false` to disable client
|
|
163
|
+
identification entirely.
|
|
164
|
+
|
|
165
|
+
## Connection Pooling and Thread safety
|
|
166
|
+
|
|
167
|
+
The client does not provide connection pooling. Each `Redis` instance
|
|
168
|
+
has one and only one connection to the server, and use of this connection
|
|
169
|
+
is protected by a mutex.
|
|
170
|
+
|
|
171
|
+
As such it is heavily recommended to use the [`connection_pool` gem](https://github.com/mperham/connection_pool), e.g.:
|
|
172
|
+
|
|
173
|
+
```ruby
|
|
174
|
+
module MyApp
|
|
175
|
+
def self.redis
|
|
176
|
+
@redis ||= ConnectionPool::Wrapper.new do
|
|
177
|
+
Redis.new(url: ENV["REDIS_URL"])
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
MyApp.redis.incr("some-counter")
|
|
183
|
+
```
|
|
184
|
+
|
|
79
185
|
## Sentinel support
|
|
80
186
|
|
|
81
187
|
The client is able to perform automatic failover by using [Redis
|
|
82
|
-
Sentinel](http://redis.io/topics/sentinel).
|
|
188
|
+
Sentinel](http://redis.io/topics/sentinel). Make sure to run Redis 2.8+
|
|
83
189
|
if you want to use this feature.
|
|
84
190
|
|
|
85
191
|
To connect using Sentinel, use:
|
|
@@ -88,7 +194,7 @@ To connect using Sentinel, use:
|
|
|
88
194
|
SENTINELS = [{ host: "127.0.0.1", port: 26380 },
|
|
89
195
|
{ host: "127.0.0.1", port: 26381 }]
|
|
90
196
|
|
|
91
|
-
redis = Redis.new(
|
|
197
|
+
redis = Redis.new(name: "mymaster", sentinels: SENTINELS, role: :master)
|
|
92
198
|
```
|
|
93
199
|
|
|
94
200
|
* The master name identifies a group of Redis instances composed of a master
|
|
@@ -99,91 +205,50 @@ and `slave`. When the role is `slave`, the client will try to connect to a
|
|
|
99
205
|
random slave of the specified master. If a role is not specified, the client
|
|
100
206
|
will connect to the master.
|
|
101
207
|
|
|
102
|
-
* When using
|
|
208
|
+
* When using Sentinel support, you need to specify a list of sentinels to
|
|
103
209
|
connect to. The list does not need to enumerate all your Sentinel instances,
|
|
104
210
|
but a few so that if one is down the client will try the next one. The client
|
|
105
211
|
is able to remember the last Sentinel that was able to reply correctly and will
|
|
106
|
-
use it for the next
|
|
212
|
+
use it for the next request.
|
|
107
213
|
|
|
108
|
-
|
|
214
|
+
To [authenticate](https://redis.io/docs/management/sentinel/#configuring-sentinel-instances-with-authentication) with Sentinel itself, you can specify the `sentinel_username` and `sentinel_password`. Exclude the `sentinel_username` option if you're using password-only authentication.
|
|
109
215
|
|
|
110
216
|
```ruby
|
|
111
|
-
SENTINELS = [{ host: '127.0.0.1', port: 26380
|
|
112
|
-
{ host: '127.0.0.1', port: 26381
|
|
217
|
+
SENTINELS = [{ host: '127.0.0.1', port: 26380},
|
|
218
|
+
{ host: '127.0.0.1', port: 26381}]
|
|
113
219
|
|
|
114
|
-
redis = Redis.new(
|
|
220
|
+
redis = Redis.new(name: 'mymaster', sentinels: SENTINELS, sentinel_username: 'appuser', sentinel_password: 'mysecret', role: :master)
|
|
115
221
|
```
|
|
116
222
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
`redis-rb` supports [clustering](https://redis.io/topics/cluster-spec).
|
|
223
|
+
If you specify a username and/or password at the top level for your main Redis instance, Sentinel *will not* use those credentials.
|
|
120
224
|
|
|
121
225
|
```ruby
|
|
122
|
-
#
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
# You can also specify the options as a Hash. The options are the same as for a single server connection.
|
|
127
|
-
(7000..7005).map { |port| { host: '127.0.0.1', port: port } }
|
|
128
|
-
```
|
|
129
|
-
|
|
130
|
-
You can also specify only a subset of the nodes, and the client will discover the missing ones using the [CLUSTER NODES](https://redis.io/commands/cluster-nodes) command.
|
|
131
|
-
|
|
132
|
-
```ruby
|
|
133
|
-
Redis.new(cluster: %w[redis://127.0.0.1:7000])
|
|
134
|
-
```
|
|
135
|
-
|
|
136
|
-
If you want [the connection to be able to read from any replica](https://redis.io/commands/readonly), you must pass the `replica: true`. Note that this connection won't be usable to write keys.
|
|
226
|
+
# Use 'mysecret' to authenticate against the mymaster instance, but skip authentication for the sentinels:
|
|
227
|
+
SENTINELS = [{ host: '127.0.0.1', port: 26380 },
|
|
228
|
+
{ host: '127.0.0.1', port: 26381 }]
|
|
137
229
|
|
|
138
|
-
|
|
139
|
-
Redis.new(cluster: nodes, replica: true)
|
|
230
|
+
redis = Redis.new(name: 'mymaster', sentinels: SENTINELS, role: :master, password: 'mysecret')
|
|
140
231
|
```
|
|
141
232
|
|
|
142
|
-
|
|
233
|
+
So you have to provide Sentinel credentials and Redis explicitly even if they are the same.
|
|
143
234
|
|
|
144
235
|
```ruby
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
#=> Redis::CommandError (CROSSSLOT Keys in request don't hash to the same slot)
|
|
149
|
-
|
|
150
|
-
redis.mget('{key}1', '{key}2')
|
|
151
|
-
#=> [nil, nil]
|
|
152
|
-
```
|
|
236
|
+
# Use 'mysecret' to authenticate against the mymaster instance and sentinel
|
|
237
|
+
SENTINELS = [{ host: '127.0.0.1', port: 26380 },
|
|
238
|
+
{ host: '127.0.0.1', port: 26381 }]
|
|
153
239
|
|
|
154
|
-
|
|
155
|
-
* The client support permanent node failures, and will reroute requests to promoted slaves.
|
|
156
|
-
* The client supports `MOVED` and `ASK` redirections transparently.
|
|
157
|
-
|
|
158
|
-
## Cluster mode with SSL/TLS
|
|
159
|
-
Since Redis can return FQDN of nodes in reply to client since `7.*` with CLUSTER commands, we can use cluster feature with SSL/TLS connection like this:
|
|
160
|
-
|
|
161
|
-
```ruby
|
|
162
|
-
Redis.new(cluster: %w[rediss://foo.example.com:6379])
|
|
240
|
+
redis = Redis.new(name: 'mymaster', sentinels: SENTINELS, role: :master, password: 'mysecret', sentinel_password: 'mysecret')
|
|
163
241
|
```
|
|
164
242
|
|
|
165
|
-
|
|
243
|
+
Also, the `name`, `password`, `username`, and `db` for the Redis instance can be passed as a URL:
|
|
166
244
|
|
|
167
245
|
```ruby
|
|
168
|
-
Redis.new(
|
|
246
|
+
redis = Redis.new(url: "redis://appuser:mysecret@mymaster/10", sentinels: SENTINELS, role: :master)
|
|
169
247
|
```
|
|
170
248
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
## Storing objects
|
|
174
|
-
|
|
175
|
-
Redis "string" types can be used to store serialized Ruby objects, for
|
|
176
|
-
example with JSON:
|
|
177
|
-
|
|
178
|
-
```ruby
|
|
179
|
-
require "json"
|
|
180
|
-
|
|
181
|
-
redis.set "foo", [1, 2, 3].to_json
|
|
182
|
-
# => OK
|
|
249
|
+
## Cluster support
|
|
183
250
|
|
|
184
|
-
|
|
185
|
-
# => [1, 2, 3]
|
|
186
|
-
```
|
|
251
|
+
[Clustering](https://redis.io/topics/cluster-spec). is supported via the [`redis-clustering` gem](cluster/).
|
|
187
252
|
|
|
188
253
|
## Pipelining
|
|
189
254
|
|
|
@@ -206,6 +271,39 @@ end
|
|
|
206
271
|
# => ["OK", 1]
|
|
207
272
|
```
|
|
208
273
|
|
|
274
|
+
Commands must be called on the yielded objects. If you call methods
|
|
275
|
+
on the original client objects from inside a pipeline, they will be sent immediately:
|
|
276
|
+
|
|
277
|
+
```ruby
|
|
278
|
+
redis.pipelined do |pipeline|
|
|
279
|
+
pipeline.set "foo", "bar"
|
|
280
|
+
redis.incr "baz" # => 1
|
|
281
|
+
end
|
|
282
|
+
# => ["OK"]
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
### Exception management
|
|
286
|
+
|
|
287
|
+
The `exception` flag in the `#pipelined` is a feature that modifies the pipeline execution behavior. When set
|
|
288
|
+
to `false`, it doesn't raise an exception when a command error occurs. Instead, it allows the pipeline to execute all
|
|
289
|
+
commands, and any failed command will be available in the returned array. (Defaults to `true`)
|
|
290
|
+
|
|
291
|
+
```ruby
|
|
292
|
+
results = redis.pipelined(exception: false) do |pipeline|
|
|
293
|
+
pipeline.set('key1', 'value1')
|
|
294
|
+
pipeline.lpush('key1', 'something') # This will fail
|
|
295
|
+
pipeline.set('key2', 'value2')
|
|
296
|
+
end
|
|
297
|
+
# results => ["OK", #<RedisClient::WrongTypeError: WRONGTYPE Operation against a key holding the wrong kind of value>, "OK"]
|
|
298
|
+
|
|
299
|
+
results.each do |result|
|
|
300
|
+
if result.is_a?(Redis::CommandError)
|
|
301
|
+
# Do something with the failed result
|
|
302
|
+
end
|
|
303
|
+
end
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
|
|
209
307
|
### Executing commands atomically
|
|
210
308
|
|
|
211
309
|
You can use `MULTI/EXEC` to run a number of commands in an atomic
|
|
@@ -225,24 +323,89 @@ end
|
|
|
225
323
|
### Futures
|
|
226
324
|
|
|
227
325
|
Replies to commands in a pipeline can be accessed via the *futures* they
|
|
228
|
-
emit
|
|
326
|
+
emit. All calls on the pipeline object return a
|
|
229
327
|
`Future` object, which responds to the `#value` method. When the
|
|
230
328
|
pipeline has successfully executed, all futures are assigned their
|
|
231
329
|
respective replies and can be used.
|
|
232
330
|
|
|
233
331
|
```ruby
|
|
332
|
+
set = incr = nil
|
|
234
333
|
redis.pipelined do |pipeline|
|
|
235
|
-
|
|
236
|
-
|
|
334
|
+
set = pipeline.set "foo", "bar"
|
|
335
|
+
incr = pipeline.incr "baz"
|
|
237
336
|
end
|
|
238
337
|
|
|
239
|
-
|
|
338
|
+
set.value
|
|
240
339
|
# => "OK"
|
|
241
340
|
|
|
242
|
-
|
|
341
|
+
incr.value
|
|
243
342
|
# => 1
|
|
244
343
|
```
|
|
245
344
|
|
|
345
|
+
## Bulk hash ingestion (HIMPORT)
|
|
346
|
+
|
|
347
|
+
> **Experimental:** HIMPORT support is experimental. The client API (method
|
|
348
|
+
> signatures, reply aggregation on cluster, and the automatic re-prepare
|
|
349
|
+
> behavior) may change in a future minor release without a major version bump.
|
|
350
|
+
|
|
351
|
+
Redis 8.10 adds the `HIMPORT` command family for loading many hashes that share
|
|
352
|
+
the same set of field names: register the field names once with
|
|
353
|
+
`himport_prepare`, then create each hash by sending only its values. Keys
|
|
354
|
+
written this way are regular hashes — every hash command works on them.
|
|
355
|
+
|
|
356
|
+
```ruby
|
|
357
|
+
redis.himport_prepare("users", ["name", "email", "age"])
|
|
358
|
+
redis.himport_set("user:1", "users", ["alice", "alice@example.com", "25"])
|
|
359
|
+
redis.himport_set("user:2", "users", ["bob", "bob@example.com", "30"])
|
|
360
|
+
redis.himport_discard("users") # => 1
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
Values pair positionally with the prepared fields. Note that hash enumeration
|
|
364
|
+
order (`HGETALL`, `HKEYS`) is not guaranteed to match the prepare order.
|
|
365
|
+
|
|
366
|
+
### Fieldsets are connection state
|
|
367
|
+
|
|
368
|
+
A prepared fieldset lives in the server-side session of the physical connection
|
|
369
|
+
that prepared it: it is invisible to other connections and destroyed by a
|
|
370
|
+
disconnect or `RESET`. A `himport_set` on a connection without the fieldset
|
|
371
|
+
fails with `ERR no such fieldset`.
|
|
372
|
+
|
|
373
|
+
Because a `Redis` instance transparently replaces a dead connection (see
|
|
374
|
+
[Reconnections](#reconnections)), the client keeps the last schema prepared for
|
|
375
|
+
each fieldset name and, when a `himport_set` reports the fieldset is gone,
|
|
376
|
+
re-prepares it and retries the command once. Explicitly discarded fieldsets are
|
|
377
|
+
never restored. To keep the fieldset lifecycle fully explicit instead, disable
|
|
378
|
+
the recovery:
|
|
379
|
+
|
|
380
|
+
```ruby
|
|
381
|
+
redis = Redis.new(himport_auto_prepare: false)
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
For the highest ingestion throughput, send the `PREPARE` and its `SET`s as one
|
|
385
|
+
pipeline — a single batch always executes on a single connection:
|
|
386
|
+
|
|
387
|
+
```ruby
|
|
388
|
+
redis.pipelined do |pipeline|
|
|
389
|
+
pipeline.himport_prepare("users", ["name", "email", "age"])
|
|
390
|
+
rows.each { |id, row| pipeline.himport_set("user:#{id}", "users", row) }
|
|
391
|
+
end
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
If the `PREPARE` in a batch fails, every `SET` in it fails with
|
|
395
|
+
`no such fieldset` — the `PREPARE` error is the root cause. Note that the
|
|
396
|
+
automatic re-prepare applies to direct calls only, not to commands inside
|
|
397
|
+
`pipelined`/`multi` blocks.
|
|
398
|
+
|
|
399
|
+
When using the `connection_pool` gem, each checkout may hand you a different
|
|
400
|
+
underlying connection: run `himport_prepare` and its `himport_set` calls within
|
|
401
|
+
one checkout (`pool.with { |redis| ... }`), ideally as one pipelined block.
|
|
402
|
+
|
|
403
|
+
With `Redis::Distributed`, `himport_prepare`, `himport_discard` and
|
|
404
|
+
`himport_discard_all` fan out to every ring node and return an array with one
|
|
405
|
+
reply per node; `himport_set` routes by key. With `Redis::Cluster`, the same
|
|
406
|
+
commands fan out to every master node and return a single aggregated reply,
|
|
407
|
+
matching the standalone API.
|
|
408
|
+
|
|
246
409
|
## Error Handling
|
|
247
410
|
|
|
248
411
|
In general, if something goes wrong you'll get an exception. For example, if
|
|
@@ -251,7 +414,7 @@ it can't connect to the server a `Redis::CannotConnectError` error will be raise
|
|
|
251
414
|
```ruby
|
|
252
415
|
begin
|
|
253
416
|
redis.ping
|
|
254
|
-
rescue
|
|
417
|
+
rescue Redis::BaseError => e
|
|
255
418
|
e.inspect
|
|
256
419
|
# => #<Redis::CannotConnectError: Timed out connecting to Redis on 10.0.1.1:6380>
|
|
257
420
|
|
|
@@ -265,6 +428,7 @@ See lib/redis/errors.rb for information about what exceptions are possible.
|
|
|
265
428
|
## Timeouts
|
|
266
429
|
|
|
267
430
|
The client allows you to configure connect, read, and write timeouts.
|
|
431
|
+
Starting in version 5.0, the default for each is 1. Before that, it was 5.
|
|
268
432
|
Passing a single `timeout` option will set all three values:
|
|
269
433
|
|
|
270
434
|
```ruby
|
|
@@ -298,56 +462,38 @@ If no message is received after 5 seconds, the client will unsubscribe.
|
|
|
298
462
|
|
|
299
463
|
## Reconnections
|
|
300
464
|
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
`reconnect_delay` and `reconnect_delay_max`.
|
|
465
|
+
**By default**, this gem will only **retry a connection once** and then fail, but
|
|
466
|
+
the client allows you to configure how many `reconnect_attempts` it should
|
|
467
|
+
complete before declaring a connection as failed.
|
|
305
468
|
|
|
306
469
|
```ruby
|
|
307
|
-
Redis.new(
|
|
308
|
-
|
|
309
|
-
:reconnect_delay => 1.5,
|
|
310
|
-
:reconnect_delay_max => 10.0,
|
|
311
|
-
)
|
|
470
|
+
Redis.new(reconnect_attempts: 0)
|
|
471
|
+
Redis.new(reconnect_attempts: 3)
|
|
312
472
|
```
|
|
313
473
|
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
between each attempt but it never waits longer than `reconnect_delay_max`.
|
|
317
|
-
|
|
318
|
-
This is the retry algorithm:
|
|
474
|
+
If you wish to wait between reconnection attempts, you can instead pass a list
|
|
475
|
+
of durations:
|
|
319
476
|
|
|
320
477
|
```ruby
|
|
321
|
-
|
|
478
|
+
Redis.new(reconnect_attempts: [
|
|
479
|
+
0, # retry immediately
|
|
480
|
+
0.25, # retry a second time after 250ms
|
|
481
|
+
1, # retry a third and final time after another 1s
|
|
482
|
+
])
|
|
322
483
|
```
|
|
323
484
|
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
#|Attempt wait time|Total wait time
|
|
328
|
-
:-:|:-:|:-:
|
|
329
|
-
1|1.5s|1.5s
|
|
330
|
-
2|3.0s|4.5s
|
|
331
|
-
3|6.0s|10.5s
|
|
332
|
-
4|10.0s|20.5s
|
|
333
|
-
5|10.0s|30.5s
|
|
334
|
-
6|10.0s|40.5s
|
|
335
|
-
7|10.0s|50.5s
|
|
336
|
-
8|10.0s|60.5s
|
|
337
|
-
9|10.0s|70.5s
|
|
338
|
-
10|10.0s|80.5s
|
|
485
|
+
If you wish to disable reconnection only for some commands, you can use
|
|
486
|
+
`disable_reconnection`:
|
|
339
487
|
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
488
|
+
```ruby
|
|
489
|
+
redis.get("some-key") # this may be retried
|
|
490
|
+
redis.disable_reconnection do
|
|
491
|
+
redis.incr("some-counter") # this won't be retried.
|
|
492
|
+
end
|
|
493
|
+
```
|
|
344
494
|
|
|
345
495
|
## SSL/TLS Support
|
|
346
496
|
|
|
347
|
-
This library supports natively terminating client side SSL/TLS connections
|
|
348
|
-
when talking to Redis via a server-side proxy such as [stunnel], [hitch],
|
|
349
|
-
or [ghostunnel].
|
|
350
|
-
|
|
351
497
|
To enable SSL support, pass the `:ssl => true` option when configuring the
|
|
352
498
|
Redis client, or pass in `:url => "rediss://..."` (like HTTPS for Redis).
|
|
353
499
|
You will also need to pass in an `:ssl_params => { ... }` hash used to
|
|
@@ -381,13 +527,7 @@ redis = Redis.new(
|
|
|
381
527
|
)
|
|
382
528
|
```
|
|
383
529
|
|
|
384
|
-
[
|
|
385
|
-
[hitch]: https://hitch-tls.org/
|
|
386
|
-
[ghostunnel]: https://github.com/square/ghostunnel
|
|
387
|
-
[OpenSSL::SSL::SSLContext documentation]: http://ruby-doc.org/stdlib-2.3.0/libdoc/openssl/rdoc/OpenSSL/SSL/SSLContext.html
|
|
388
|
-
|
|
389
|
-
*NOTE:* SSL is only supported by the default "Ruby" driver
|
|
390
|
-
|
|
530
|
+
[OpenSSL::SSL::SSLContext documentation]: http://ruby-doc.org/stdlib-2.5.0/libdoc/openssl/rdoc/OpenSSL/SSL/SSLContext.html
|
|
391
531
|
|
|
392
532
|
## Expert-Mode Options
|
|
393
533
|
|
|
@@ -401,17 +541,9 @@ redis = Redis.new(
|
|
|
401
541
|
Improper use of `inherit_socket` will result in corrupted and/or incorrect
|
|
402
542
|
responses.
|
|
403
543
|
|
|
404
|
-
##
|
|
544
|
+
## hiredis binding
|
|
405
545
|
|
|
406
546
|
By default, redis-rb uses Ruby's socket library to talk with Redis.
|
|
407
|
-
To use an alternative connection driver it should be specified as option
|
|
408
|
-
when instantiating the client object. These instructions are only valid
|
|
409
|
-
for **redis-rb 3.0**. For instructions on how to use alternate drivers from
|
|
410
|
-
**redis-rb 2.2**, please refer to an [older README][readme-2.2.2].
|
|
411
|
-
|
|
412
|
-
[readme-2.2.2]: https://github.com/redis/redis-rb/blob/v2.2.2/README.md
|
|
413
|
-
|
|
414
|
-
### hiredis
|
|
415
547
|
|
|
416
548
|
The hiredis driver uses the connection facility of hiredis-rb. In turn,
|
|
417
549
|
hiredis-rb is a binding to the official hiredis client library. It
|
|
@@ -421,48 +553,29 @@ extension, JRuby is not supported (by default).
|
|
|
421
553
|
It is best to use hiredis when you have large replies (for example:
|
|
422
554
|
`LRANGE`, `SMEMBERS`, `ZRANGE`, etc.) and/or use big pipelines.
|
|
423
555
|
|
|
424
|
-
In your Gemfile, include hiredis
|
|
556
|
+
In your Gemfile, include `hiredis-client`:
|
|
425
557
|
|
|
426
558
|
```ruby
|
|
427
|
-
gem "redis"
|
|
428
|
-
gem "hiredis"
|
|
559
|
+
gem "redis"
|
|
560
|
+
gem "hiredis-client"
|
|
429
561
|
```
|
|
430
562
|
|
|
431
|
-
|
|
563
|
+
If your application doesn't call `Bundler.require`, you may have
|
|
564
|
+
to require it explicitly:
|
|
432
565
|
|
|
433
566
|
```ruby
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
### synchrony
|
|
567
|
+
require "hiredis-client"
|
|
568
|
+
````
|
|
438
569
|
|
|
439
|
-
|
|
440
|
-
This makes redis-rb work with EventMachine's asynchronous I/O, while not
|
|
441
|
-
changing the exposed API. The hiredis gem needs to be available as
|
|
442
|
-
well, because the synchrony driver uses hiredis for parsing the Redis
|
|
443
|
-
protocol.
|
|
570
|
+
This makes the hiredis driver the default.
|
|
444
571
|
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
In your Gemfile, include em-synchrony and hiredis:
|
|
572
|
+
If you want to be certain hiredis is being used, when instantiating
|
|
573
|
+
the client object, specify hiredis:
|
|
448
574
|
|
|
449
575
|
```ruby
|
|
450
|
-
|
|
451
|
-
gem "hiredis", "~> 0.4.5"
|
|
452
|
-
gem "em-synchrony"
|
|
576
|
+
redis = Redis.new(driver: :hiredis)
|
|
453
577
|
```
|
|
454
578
|
|
|
455
|
-
When instantiating the client object, specify synchrony:
|
|
456
|
-
|
|
457
|
-
```ruby
|
|
458
|
-
redis = Redis.new(:driver => :synchrony)
|
|
459
|
-
```
|
|
460
|
-
|
|
461
|
-
## Testing
|
|
462
|
-
|
|
463
|
-
This library is tested against recent Ruby and Redis versions.
|
|
464
|
-
Check [Github Actions][gh-actions-link] for the exact versions supported.
|
|
465
|
-
|
|
466
579
|
## See Also
|
|
467
580
|
|
|
468
581
|
- [async-redis](https://github.com/socketry/async-redis) — An [async](https://github.com/socketry/async) compatible Redis client.
|
|
@@ -480,11 +593,14 @@ client and evangelized Redis in Rubyland. Thank you, Ezra.
|
|
|
480
593
|
requests.
|
|
481
594
|
|
|
482
595
|
|
|
483
|
-
[
|
|
484
|
-
[
|
|
485
|
-
[redis-commands]:
|
|
486
|
-
[redis-home]:
|
|
487
|
-
[redis-url]:
|
|
488
|
-
[gh-actions-image]:
|
|
489
|
-
[gh-actions-link]:
|
|
490
|
-
[rubydoc]:
|
|
596
|
+
[rdoc-master-image]: https://img.shields.io/badge/docs-rdoc.info-blue.svg
|
|
597
|
+
[rdoc-master-link]: https://rubydoc.info/github/redis/redis-rb
|
|
598
|
+
[redis-commands]: https://redis.io/commands
|
|
599
|
+
[redis-home]: https://redis.io
|
|
600
|
+
[redis-url]: https://www.iana.org/assignments/uri-schemes/prov/redis
|
|
601
|
+
[gh-actions-image]: https://github.com/redis/redis-rb/workflows/Test/badge.svg
|
|
602
|
+
[gh-actions-link]: https://github.com/redis/redis-rb/actions
|
|
603
|
+
[rubydoc]: https://rubydoc.info/gems/redis
|
|
604
|
+
[resp3]: https://github.com/redis/redis-specifications/blob/master/protocol/RESP3.md
|
|
605
|
+
[ruby-branches]: https://www.ruby-lang.org/en/downloads/branches/
|
|
606
|
+
[redis-versions]: https://redis.io/docs/latest/operate/rc/databases/version-management/#supported-database-versions
|