Giter VIP home page Giter VIP logo

redis-rb's Introduction

redis-rb Build Status Inline docs

A Ruby client that tries to match Redis' API one-to-one, while still providing an idiomatic interface.

See RubyDoc.info for the API docs of the latest published gem.

Getting started

Install with:

$ gem install redis

You can connect to Redis by instantiating the Redis class:

require "redis"

redis = Redis.new

This assumes Redis was started with a default configuration, and is listening on localhost, port 6379. If you need to connect to a remote server or a different port, try:

redis = Redis.new(host: "10.0.1.1", port: 6380, db: 15)

You can also specify connection options as a redis:// URL:

redis = Redis.new(url: "redis://:[email protected]:6380/15")

The client expects passwords with special chracters to be URL-encoded (i.e. CGI.escape(password)).

To connect to Redis listening on a Unix socket, try:

redis = Redis.new(path: "/tmp/redis.sock")

To connect to a password protected Redis instance, use:

redis = Redis.new(password: "mysecret")

To connect a Redis instance using ACL, use:

redis = Redis.new(username: 'myname', password: 'mysecret')

The Redis class exports methods that are named identical to the commands they execute. The arguments these methods accept are often identical to the arguments specified on the Redis website. For instance, the SET and GET commands can be called like this:

redis.set("mykey", "hello world")
# => "OK"

redis.get("mykey")
# => "hello world"

All commands, their arguments, and return values are documented and available on RubyDoc.info.

Connection Pooling and Thread safety

The client does not provide connection pooling. Each Redis instance has one and only one connection to the server, and use of this connection is protected by a mutex.

As such it is heavilly recommended to use the connection_pool gem, e.g.:

module MyApp
  def self.redis
    @redis ||= ConnectionPool::Wrapper.new do
      Redis.new(url: ENV["REDIS_URL"])
    end
  end
end

MyApp.redis.incr("some-counter")

Sentinel support

The client is able to perform automatic failover by using Redis Sentinel. Make sure to run Redis 2.8+ if you want to use this feature.

To connect using Sentinel, use:

SENTINELS = [{ host: "127.0.0.1", port: 26380 },
             { host: "127.0.0.1", port: 26381 }]

redis = Redis.new(name: "mymaster", sentinels: SENTINELS, role: :master)
  • The master name identifies a group of Redis instances composed of a master and one or more slaves (mymaster in the example).

  • It is possible to optionally provide a role. The allowed roles are master and slave. When the role is slave, the client will try to connect to a random slave of the specified master. If a role is not specified, the client will connect to the master.

  • When using the Sentinel support you need to specify a list of sentinels to connect to. The list does not need to enumerate all your Sentinel instances, but a few so that if one is down the client will try the next one. The client is able to remember the last Sentinel that was able to reply correctly and will use it for the next requests.

To authenticate Sentinel itself, you can specify the sentinel_username and sentinel_password. Exclude the sentinel_username option if you're using password-only authentication.

SENTINELS = [{ host: '127.0.0.1', port: 26380},
             { host: '127.0.0.1', port: 26381}]

redis = Redis.new(name: 'mymaster', sentinels: SENTINELS, sentinel_username: 'appuser', sentinel_password: 'mysecret', role: :master)

If you specify a username and/or password at the top level for your main Redis instance, Sentinel will not using thouse credentials

# Use 'mysecret' to authenticate against the mymaster instance, but skip authentication for the sentinels:
SENTINELS = [{ host: '127.0.0.1', port: 26380 },
             { host: '127.0.0.1', port: 26381 }]

redis = Redis.new(name: 'mymaster', sentinels: SENTINELS, role: :master, password: 'mysecret')

So you have to provide Sentinel credential and Redis explictly even they are the same

# Use 'mysecret' to authenticate against the mymaster instance and sentinel
SENTINELS = [{ host: '127.0.0.1', port: 26380 },
             { host: '127.0.0.1', port: 26381 }]

redis = Redis.new(name: 'mymaster', sentinels: SENTINELS, role: :master, password: 'mysecret', sentinel_password: 'mysecret')

Also the name, password, username and db for Redis instance can be passed as an url:

redis = Redis.new(url: "redis://appuser:mysecret@mymaster/10", sentinels: SENTINELS, role: :master)

Cluster support

Clustering. is supported via the redis-clustering gem.

Pipelining

When multiple commands are executed sequentially, but are not dependent, the calls can be pipelined. This means that the client doesn't wait for reply of the first command before sending the next command. The advantage is that multiple commands are sent at once, resulting in faster overall execution.

The client can be instructed to pipeline commands by using the #pipelined method. After the block is executed, the client sends all commands to Redis and gathers their replies. These replies are returned by the #pipelined method.

redis.pipelined do |pipeline|
  pipeline.set "foo", "bar"
  pipeline.incr "baz"
end
# => ["OK", 1]

Commands must be called on the yielded objects. If you call methods on the original client objects from inside a pipeline, they will be sent immediately:

redis.pipelined do |pipeline|
  pipeline.set "foo", "bar"
  redis.incr "baz" # => 1
end
# => ["OK"]

Exception management

The exception flag in the #pipelined is a feature that modifies the pipeline execution behavior. When set to false, it doesn't raise an exception when a command error occurs. Instead, it allows the pipeline to execute all commands, and any failed command will be available in the returned array. (Defaults to true)

results = redis.pipelined(exception: false) do |pipeline|
  pipeline.set('key1', 'value1')
  pipeline.lpush('key1', 'something') # This will fail
  pipeline.set('key2', 'value2')
end
# results => ["OK", #<RedisClient::WrongTypeError: WRONGTYPE Operation against a key holding the wrong kind of value>, "OK"]

results.each do |result|
  if result.is_a?(Redis::CommandError)
    # Do something with the failed result
  end
end

Executing commands atomically

You can use MULTI/EXEC to run a number of commands in an atomic fashion. This is similar to executing a pipeline, but the commands are preceded by a call to MULTI, and followed by a call to EXEC. Like the regular pipeline, the replies to the commands are returned by the #multi method.

redis.multi do |transaction|
  transaction.set "foo", "bar"
  transaction.incr "baz"
end
# => ["OK", 1]

Futures

Replies to commands in a pipeline can be accessed via the futures they emit. All calls on the pipeline object return a Future object, which responds to the #value method. When the pipeline has successfully executed, all futures are assigned their respective replies and can be used.

set = incr = nil
redis.pipelined do |pipeline|
  set = pipeline.set "foo", "bar"
  incr = pipeline.incr "baz"
end

set.value
# => "OK"

incr.value
# => 1

Error Handling

In general, if something goes wrong you'll get an exception. For example, if it can't connect to the server a Redis::CannotConnectError error will be raised.

begin
  redis.ping
rescue Redis::BaseError => e
  e.inspect
# => #<Redis::CannotConnectError: Timed out connecting to Redis on 10.0.1.1:6380>

  e.message
# => Timed out connecting to Redis on 10.0.1.1:6380
end

See lib/redis/errors.rb for information about what exceptions are possible.

Timeouts

The client allows you to configure connect, read, and write timeouts. Passing a single timeout option will set all three values:

Redis.new(:timeout => 1)

But you can use specific values for each of them:

Redis.new(
  :connect_timeout => 0.2,
  :read_timeout    => 1.0,
  :write_timeout   => 0.5
)

All timeout values are specified in seconds.

When using pub/sub, you can subscribe to a channel using a timeout as well:

redis = Redis.new(reconnect_attempts: 0)
redis.subscribe_with_timeout(5, "news") do |on|
  on.message do |channel, message|
    # ...
  end
end

If no message is received after 5 seconds, the client will unsubscribe.

Reconnections

By default, this gem will only retry a connection once and then fail, but the client allows you to configure how many reconnect_attempts it should complete before declaring a connection as failed.

Redis.new(reconnect_attempts: 0)
Redis.new(reconnect_attempts: 3)

If you wish to wait between reconnection attempts, you can instead pass a list of durations:

Redis.new(reconnect_attempts: [
  0, # retry immediately
  0.25, # retry a second time after 250ms
  1, # retry a third and final time after another 1s
])

If you wish to disable reconnection only for some commands, you can use disable_reconnection:

redis.get("some-key") # this may be retried
redis.disable_reconnection do
  redis.incr("some-counter") # this won't be retried.
end

SSL/TLS Support

To enable SSL support, pass the :ssl => true option when configuring the Redis client, or pass in :url => "rediss://..." (like HTTPS for Redis). You will also need to pass in an :ssl_params => { ... } hash used to configure the OpenSSL::SSL::SSLContext object used for the connection:

redis = Redis.new(
  :url        => "rediss://:[email protected]:6381/15",
  :ssl_params => {
    :ca_file => "/path/to/ca.crt"
  }
)

The options given to :ssl_params are passed directly to the OpenSSL::SSL::SSLContext#set_params method and can be any valid attribute of the SSL context. Please see the OpenSSL::SSL::SSLContext documentation for all of the available attributes.

Here is an example of passing in params that can be used for SSL client certificate authentication (a.k.a. mutual TLS):

redis = Redis.new(
  :url        => "rediss://:[email protected]:6381/15",
  :ssl_params => {
    :ca_file => "/path/to/ca.crt",
    :cert    => OpenSSL::X509::Certificate.new(File.read("client.crt")),
    :key     => OpenSSL::PKey::RSA.new(File.read("client.key"))
  }
)

Expert-Mode Options

  • inherit_socket: true: disable safety check that prevents a forked child from sharing a socket with its parent; this is potentially useful in order to mitigate connection churn when:

    • many short-lived forked children of one process need to talk to redis, AND
    • your own code prevents the parent process from using the redis connection while a child is alive

    Improper use of inherit_socket will result in corrupted and/or incorrect responses.

hiredis binding

By default, redis-rb uses Ruby's socket library to talk with Redis.

The hiredis driver uses the connection facility of hiredis-rb. In turn, hiredis-rb is a binding to the official hiredis client library. It optimizes for speed, at the cost of portability. Because it is a C extension, JRuby is not supported (by default).

It is best to use hiredis when you have large replies (for example: LRANGE, SMEMBERS, ZRANGE, etc.) and/or use big pipelines.

In your Gemfile, include hiredis-client:

gem "redis"
gem "hiredis-client"

If your application doesn't call Bundler.require, you may have to require it explictly:

require "hiredis-client"

This makes the hiredis driver the default.

If you want to be certain hiredis is being used, when instantiating the client object, specify hiredis:

redis = Redis.new(driver: :hiredis)

Testing

This library is tested against recent Ruby and Redis versions. Check Github Actions for the exact versions supported.

See Also

Contributors

Several people contributed to redis-rb, but we would like to especially mention Ezra Zygmuntowicz. Ezra introduced the Ruby community to many new cool technologies, like Redis. He wrote the first version of this client and evangelized Redis in Rubyland. Thank you, Ezra.

Contributing

Fork the project and send pull requests.

redis-rb's People

Contributors

allomov avatar antirez avatar badboy avatar bitterb avatar bpo avatar byroot avatar darshanime avatar defunkt avatar dependabot[bot] avatar djanowski avatar esmarkowski avatar evanphx avatar fatkodima avatar igrigorik avatar ioquatix avatar jeremy avatar jodosha avatar matflores avatar mperham avatar not-a-robot[bot] avatar petergoldstein avatar pietern avatar qrush avatar quixoten avatar rsanheim avatar soveran avatar supercaracal avatar tarcieri avatar thesmartnik avatar yaauie avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

redis-rb's Issues

llrange on empty list

I dont know if this is a bug or a feature, but redis.rb v2 behaves differently on empty lists than v1:

     ruby-1.8.7-p249 > gem "redis", "~>1.0.1"
 => true 
ruby-1.8.7-p249 > require 'redis'
 => true 
ruby-1.8.7-p249 > Redis::VERSION
 => "1.0.7" 
ruby-1.8.7-p249 > r = Redis.new
 => #<Redis::Client:0x1023f3c40 @host="127.0.0.1", @pubsub=false, @db=0, @binary_keys=nil, @logger=nil, @password=nil, @sock=nil, @timeout=5, @thread_safe=nil, @port=6379> 
ruby-1.8.7-p249 > r.lrange "testlist", 0, -1
 => [] 
ruby-1.8.7-p249 > r.llen "testlist"
 => 0
ruby-1.8.7-p249 > exit
[11:32 AM:redisk(master)] $ irb
ruby-1.8.7-p249 > gem "redis", "~>2.0.1"
 => true 
ruby-1.8.7-p249 > require 'redis'
 => true 
ruby-1.8.7-p249 > Redis::VERSION
 => "2.0.1" 
ruby-1.8.7-p249 > r = Redis.new
 => #<Redis:0x1023d9a70 @client=#<Redis::Client:0x1023d99d0 @db=0, @host="127.0.0.1", @sock=nil, @password=nil, @logger=nil, @timeout=5, @port=6379> 
ruby-1.8.7-p249 > r.lrange "testlist", 0, -1
 => nil 
ruby-1.8.7-p249 > r.llen "testlist"
 => 0 

I have some code that expects lrange to always return an Array. Easy to fix on myside, but curious if it shouldnt be compatible with the way ~>1.0 handled it.

multibyte chars consider

counting bytes rather than chars..
(for ruby 1.9)
line 194
#argv[-1] = bulk.length
argv[-1] = bulk.bytes.count

1.0?

This gem is awesome and production ready - can we get a 1.0?

Semantic Versioning would be a nice bonus, but I'll take what I can get ;)

Occasional protocol error when using with threads

I'll have a look at your code and see if it's supposed to be thread safe.

If it's not intended to be then disregard. I'll just synchronize the object myself.

Here's the error if it helps:
Protocol error, got '6' as initial reply byte

This is from JRuby btw, might get a diff error on MRI.

Calling a command with wrong params breaks the subsequent commands

It seems the command params aren't cleared from some kind of buffer after an error.

For example: If you connect to redis from irb, run the info command, then run another command, say zrank but only pass it one arg, then the subsequent call gets messed up. i.e. if you tried to recall info, it would fail.

irb(main):001:0> require 'rubygems'
=> true
irb(main):002:0> require 'redis'
=> true
irb(main):003:0> r = Redis.new
=> #<Redis::Client:0x5e6870 @binary_keys=nil, @password=nil, @logger=nil, @sock=nil, @timeout=5, @PORT=6379, @thread_safe=nil, @host="127.0.0.1", @PubSub=false, @db=0>
irb(main):004:0> r.info
=> {:changes_since_last_save=>"154", }
irb(main):007:0> r.zrank 'gbgb'
RuntimeError: -ERR unknown command 'zrank'
irb(main):008:0> r.info
RuntimeError: -ERR unknown command 'gbgb' OOPS!

Failing tests in SREM command (minor issue)

$ rake
Loaded suite /home/anibal/.gem/ruby/1.8/gems/rake-0.8.7/lib/rake/rake_test_loader
Started
..P.P................................................P...P..P.P..P..P...........................F.F.........P..............
Finished in 10.152636 seconds.

  1. Failure:
    test_Does_not_send_extra_commands_on_errors(RedisTest::TestInternals) [./test/redis_test.rb:66]:
    <[]> exception expected but was
    Class:
    Message: <"-ERR wrong number of arguments for 'srem' command">
    ---Backtrace---
    ./test/../lib/redis/client.rb:545:in format_error_reply' ./test/../lib/redis/client.rb:535:informat_reply'
    ./test/../lib/redis/client.rb:474:in read_reply' ./test/../lib/redis/client.rb:444:inprocess_command'
    ./test/../lib/redis/client.rb:442:in map' ./test/../lib/redis/client.rb:442:inprocess_command'
    ./test/../lib/redis/client.rb:431:in raw_call_command' ./test/../lib/redis/client.rb:452:incall'
    ./test/../lib/redis/client.rb:452:in maybe_lock' ./test/../lib/redis/client.rb:428:inraw_call_command'
    ./test/../lib/redis/client.rb:332:in call_command' ./test/../lib/redis/client.rb:381:inmethod_missing'
    ./test/redis_test.rb:67:in `test_Does_not_send_extra_commands_on_errors'

./test/redis_test.rb:66:in `test_Does_not_send_extra_commands_on_errors'

  1. Failure:
    test_Recovers_from_failed_commands(RedisTest::TestInternals) [./test/redis_test.rb:54]:
    <[]> exception expected but was
    Class:
    Message: <"-ERR wrong number of arguments for 'srem' command">
    ---Backtrace---
    ./test/../lib/redis/client.rb:545:in format_error_reply' ./test/../lib/redis/client.rb:535:informat_reply'
    ./test/../lib/redis/client.rb:474:in read_reply' ./test/../lib/redis/client.rb:444:inprocess_command'
    ./test/../lib/redis/client.rb:442:in map' ./test/../lib/redis/client.rb:442:inprocess_command'
    ./test/../lib/redis/client.rb:431:in raw_call_command' ./test/../lib/redis/client.rb:452:incall'
    ./test/../lib/redis/client.rb:452:in maybe_lock' ./test/../lib/redis/client.rb:428:inraw_call_command'
    ./test/../lib/redis/client.rb:332:in call_command' ./test/../lib/redis/client.rb:381:inmethod_missing'
    ./test/redis_test.rb:55:in `test_Recovers_from_failed_commands'

./test/redis_test.rb:54:in `test_Recovers_from_failed_commands'

114 tests, 584 assertions, 2 failures, 0 errors
rake aborted!
Command failed with status (1): [/usr/bin/ruby1.8 -I"lib" "/home/anibal/.ge...]

(See full trace by running task with --trace)

REDIS-RB INFO:

$ git branch

  • master
    $ git remote
    origin
    $ git log -1
    commit 9e43fde
    Author: Damian Janowski [email protected]
    Date: Thu Apr 8 09:45:45 2010 -0300

    Check for Ruby 1.9 as early as possible.

REDIS INFO:

$ git branch

  • master
    $ git remote -v
    origin git://github.com/antirez/redis.git
    $ git log -1
    commit dae121d9aa38626db9962ff02879b55eb7ca36bf
    Author: antirez [email protected]
    Date: Sat Apr 10 11:14:11 2010 +0200

    dict.c fixed to play well with enabling/disabling of the hash table

$ ./redis-server
[15568] 10 Apr 08:06:52 # Warning: no config file specified, using the default config. In order to specify a config file use 'redis-server /path/to/redis.conf'
[15568] 10 Apr 08:06:52 * Server started, Redis version 1.3.8
[15568] 10 Apr 08:06:52 # WARNING overcommit_memory is set to 0! Background save may fail under low condition memory. To fix this issue add 'vm.overcommit_memory = 1' to /etc/sysctl.conf and then reboot or run the command 'sysctl vm.overcommit_memory=1' for this to take effect.
[15568] 10 Apr 08:06:52 * DB loaded from disk: 0 seconds
[15568] 10 Apr 08:06:52 * The server is now ready to accept connections on port 6379
[15568] 10 Apr 08:06:53 - DB 0: 3 keys (0 volatile) in 4 slots HT.
[15568] 10 Apr 08:06:53 - 0 clients connected (0 slaves), 568567 bytes in use, 0 shared objects

Example of pipelining

Would be super great to have code example demonstrating the use of pipelining.

Thanks,

marc

WITHSCORES on ZRANGE, etc.

Would be nice if the lib provided the option to do various zset operations using WITHSCORES, or perhaps even by default for WITHSCORES to be used...

Difference between Redis#keys and DistRedis#keys

I was wondering why DistRedis#keys doesn't follow the same api as Redis#keys:

# A DistRedis with two hosts
dist_redis.keys('*') # => [['k1'],['k2']]
redis.keys('*') # => ['k1','k2']

I'd prefer that the existing behavior ofDistRedis#keys be moved to another method name (DistRedis#node_keys?) and DistRedis#keys return a flattened array of keys. Seems to me that I should be able to swap out an instance of Redis with DistRedis in an application with no changes. I'd happily create a patch...

mget regression?

Formerly, one could use this:

require 'redis'

redis = Redis.new

redis.set("k1", "v1")
redis.set("k2", "v2")

kz = ["k1", "k2"]
kvz = redis.mget(kz)

puts kvz.inspect

And get ["v1","v2"]

Now it return nil - what gives?

It seems now if you want to use mget you have to pass all keys as arguments, e.g.
mget("k1", "k2") ?

Why the regression / deprecation ?

EBADF errors

We recently converted our MRI rails app to JRuby. After running a couple of days, we started getting multiple EBADF - Bad File Descriptor errors. We pass threadsafe on new and the redis server hasn't bounced. We're running redis 1.0.7, Rails 2.3.2 and JRuby 1.5.1. Any advice on what the cause is or what I can do to investigate the cause?

A Errno::EBADF occurred in admin#load_widget:
Bad file descriptor - Bad file descriptor
[RAILS_ROOT]/vendor/gems/redis-1.0.7/lib/redis/client.rb:452:in process_command' [RAILS_ROOT]/vendor/gems/redis-1.0.7/lib/redis/client.rb:442:inraw_call_command'
[RAILS_ROOT]/vendor/gems/redis-1.0.7/lib/redis/client.rb:461:in maybe_lock' [RAILS_ROOT]/vendor/gems/redis-1.0.7/lib/redis/client.rb:439:inraw_call_command'
[RAILS_ROOT]/vendor/gems/redis-1.0.7/lib/redis/client.rb:343:in call_command' [RAILS_ROOT]/vendor/gems/redis-1.0.7/lib/redis/client.rb:392:inmethod_missing'
[RAILS_ROOT]/app/controllers/admin_controller.rb:285:in `load_widget'

Reconnect logic threadsafe?

I'm curious if the reconnect logic for the redis gem is threadsafe. It seems like their could be a race condition if 2 threads try to reconnect at the same time and one does the disconnect between the others connect and write. We are occasionally seeing the following traceback:

A NoMethodError occurred in resque#overview:
 undefined method `write' for nil:NilClass
[RAILS_ROOT]/vendor/gems/redis-2.0.2/lib/redis/client.rb:60:in `process'
 /opt/jruby-1.5.1/lib/ruby/1.8/monitor.rb:191:in `mon_synchronize'
 [RAILS_ROOT]/vendor/gems/redis-2.0.2/lib/redis/client.rb:274:in `synchronize'
 [RAILS_ROOT]/vendor/gems/redis-2.0.2/lib/redis/client.rb:279:in `ensure_connected'
 [RAILS_ROOT]/lib/redis_extensions.rb:9:in `ensure_connected'
 [RAILS_ROOT]/vendor/gems/redis-2.0.2/lib/redis/client.rb:278:in `ensure_connected'

My monkey-patched /lib/redis-extensions just adds EBADF to the caught exceptions. We were seeing occasionaly EBADF errors even after setting the idle timeout to 0 (any chance this change could make it to the next release?):

require 'redis/client'

class Redis
  class Client
    def ensure_connected
      connect unless connected?

      begin
        yield
      rescue Errno::ECONNRESET, Errno::EPIPE, Errno::ECONNABORTED, Errno::EBADF
        if reconnect
          yield
        else
          raise Errno::ECONNRESET
        end
      end
    end
  end
end

When we see the above errors, we often see the following errors around the same timeframe. This part has me baffled but since they tend to occur within minutes of each other, I believe it's related:

A ConcurrencyError occurred in resque#overview:
 No message available
/opt/jruby-1.5.1/lib/ruby/1.8/monitor.rb:240:in `mon_acquire'
 /opt/jruby-1.5.1/lib/ruby/1.8/monitor.rb:166:in `mon_enter'
 /opt/jruby-1.5.1/lib/ruby/1.8/monitor.rb:165:in `mon_enter'
 /opt/jruby-1.5.1/lib/ruby/1.8/monitor.rb:189:in `mon_synchronize'
 [RAILS_ROOT]/vendor/gems/redis-2.0.2/lib/redis/client.rb:274:in `synchronize'
 [RAILS_ROOT]/vendor/gems/redis-2.0.2/lib/redis/client.rb:279:in `ensure_connected'
 [RAILS_ROOT]/lib/redis_extensions.rb:9:in `ensure_connected'
 [RAILS_ROOT]/vendor/gems/redis-2.0.2/lib/redis/client.rb:278:in `ensure_connected'
 [RAILS_ROOT]/vendor/gems/redis-2.0.2/lib/redis/client.rb:59:in `process'

ZREM does not work, but zrem does ...

when i try to use ZREM i get
Redis.new.ZREM 'y', 'x'
Errno::EAGAIN: Resource temporarily unavailable - Timeout reading from the socket
from /usr/local/lib/ruby/gems/1.8/gems/redis-1.0.4/lib/redis/client.rb:469:in read_reply' from /usr/local/lib/ruby/gems/1.8/gems/redis-1.0.4/lib/redis/client.rb:444:inprocess_command'
from /usr/local/lib/ruby/gems/1.8/gems/redis-1.0.4/lib/redis/client.rb:442:in map' from /usr/local/lib/ruby/gems/1.8/gems/redis-1.0.4/lib/redis/client.rb:442:inprocess_command'
from /usr/local/lib/ruby/gems/1.8/gems/redis-1.0.4/lib/redis/client.rb:431:in raw_call_command' from /usr/local/lib/ruby/gems/1.8/gems/redis-1.0.4/lib/redis/client.rb:452:incall'
from /usr/local/lib/ruby/gems/1.8/gems/redis-1.0.4/lib/redis/client.rb:452:in maybe_lock' from /usr/local/lib/ruby/gems/1.8/gems/redis-1.0.4/lib/redis/client.rb:428:inraw_call_command'
from /usr/local/lib/ruby/gems/1.8/gems/redis-1.0.4/lib/redis/client.rb:332:in call_command' from /usr/local/lib/ruby/gems/1.8/gems/redis-1.0.4/lib/redis/client.rb:381:inmethod_missing'

same call with zrem works...

Gem?

I can't seem to find a gem on Rubyforge or Gemcutter that's not empty. Can you push one when you have time?

Thanks!

pubsub issue - Protocol error, got '1' as initial reply byte

This error is to do with pubsub. Basically, I think that the wrong TCPSockets are getting subscription messages. The '1', referred to below, was published to redis, but by a different instance of the Redis class (and therefore a different connection and TCPSocket)

Protocol error, got '1' as initial reply byte
/Users/Alex/.bundle/ruby/1.9.1/gems/redis-1.0.6/lib/redis/client.rb:554:in format_reply' /Users/Alex/.bundle/ruby/1.9.1/gems/redis-1.0.6/lib/redis/client.rb:486:inread_reply'
/Users/Alex/.bundle/ruby/1.9.1/gems/redis-1.0.6/lib/redis/client.rb:456:in block in process_command' /Users/Alex/.bundle/ruby/1.9.1/gems/redis-1.0.6/lib/redis/client.rb:454:inmap'
/Users/Alex/.bundle/ruby/1.9.1/gems/redis-1.0.6/lib/redis/client.rb:454:in process_command' /Users/Alex/.bundle/ruby/1.9.1/gems/redis-1.0.6/lib/redis/client.rb:443:inblock in raw_call_command'
/Users/Alex/.bundle/ruby/1.9.1/gems/redis-1.0.6/lib/redis/client.rb:464:in call' /Users/Alex/.bundle/ruby/1.9.1/gems/redis-1.0.6/lib/redis/client.rb:464:inmaybe_lock'
/Users/Alex/.bundle/ruby/1.9.1/gems/redis-1.0.6/lib/redis/client.rb:440:in raw_call_command' /Users/Alex/.bundle/ruby/1.9.1/gems/redis-1.0.6/lib/redis/client.rb:344:incall_command'
/Users/Alex/.bundle/ruby/1.9.1/gems/redis-1.0.6/lib/redis/client.rb:178:in `get'

To replicate:

  • Publish to a key
  • Do any other redis operation, like 'get' a value

Getting results of pipelined commands

I was using the pipeline command to do "atomic gets" on a few related items and I found that it was impossible with redis-rb today.

Naive attempt:
@r.set("k1","v1")
@r.set("k2","v2")
@r.pipelined do |pipeline|
v1 = pipeline.get("k1")
v2 = pipeline.get("k2")
end
assert_equal "v1", v1
assert_equal "v2", v2

Running this test fails beacause v1 actually equals nil since the get result hasn't been collected yet.

One way to solve this with minimal work is to return an array with the result of all pipelined operations from the pipelined block. I looked at the code and it turns out this was very easy to implement. All of the results are being collected we just needed to return them at the end of the pipeline block. Now we can write:

v1, v2 = @r.pipelined do |pipeline|
  pipeline.get("k1")
  pipeline.get("k2")
end
assert_equal "v1", v1
assert_equal "v2", v2

Another way to handle this would be with futures but that would be a bit more magical and would lead people to think they could do things like:

@r.pipelined do |pipeline|
  v1_future = pipeline.get("k1")
  pipeline.set("k2", v1_future.get)
end

This is not valid since the set command is sent to the server with the parameters already fixed. For now I think returning the values is a neat way to handle the issue. I am sending you a pull request with the change and a test case.

Add Gemspec to github repo

Could you add the redis-rb gemspec to the github repo. That way you can use the github version of redis-rb in your Gemfiles with bundler.

open connection timeout

We had an outage due to redis calls not timing out (in a timely fashion) here. Since that call opens the connection it probably deserves a timeout, no? Possibly relatedly, what's the current purpose of Redis::Timer? Would you be interested in a patch to add a timeout to TCPSocket.new?

r.keys should retrurn an array

prior to 2.0, r.keys(pattern) returned the matching keys as an array. now it returns a single, blank separated string.

This new behavior breaks our beetle gem. We could work around it, but I consider this as a bug, as keys with embedded blanks seem to be supported by redis.

Redis.connect overrides options

Hi, I've noticed that Redis.connect overrides options with the attributes parsed from url. Perhaps the operator should be changed to ||= ?

The problem I'm encountering is with Redis::Distributed. It calls Redis.connect when instantiating the HashRing. If any options are passed, Redis.connect ignores them. Maybe I'm missing something?

Thanks!

push tags of releases

Hey Ezra,

Will be great if the tags for the gem releases were pushed, so we can use branch list of GitHub to figure out the list of changes ;-)

Thank you

@sock.close raises Errno::ECONNRESET

I'm using d14c202 on freebsd. I get this error:

    Errno::ECONNRESET: Connection reset by peer
    /usr/local/lib/ruby/gems/1.8/gems/redis-0.1/lib/redis.rb:192:in `close'
    /usr/local/lib/ruby/gems/1.8/gems/redis-0.1/lib/redis.rb:192:in `call_command'
    /usr/local/lib/ruby/gems/1.8/gems/redis-0.1/lib/redis.rb:178:in `method_missing'

The code is
def call_command(argv)
@logger.debug { argv.inspect } if @logger

        # this wrapper to raw_call_command handle reconnection on socket
        # error. We try to reconnect just one time, otherwise let the error
        # araise.
        connect_to_server if !@sock

        begin
          raw_call_command(argv.dup)
        rescue Errno::ECONNRESET, Errno::EPIPE, Errno::ECONNABORTED
          @sock.close # 192
          @sock = nil
          connect_to_server
          raw_call_command(argv.dup)
        end
      end

Subscription class polluting main namespace

I have a rails app running resque and the redis gem overrides my ActiveRecord model named 'Subscription'. Can the 'Subscription' class in lib/subscribe be namespaced inside of a class or module so it doesn't cause conflicts with Base classes?

A SignalException occurred... SIGTERM

We're not sure why this is happening, but we saw significant app slowdowns after implementing Vanity, and saw this in the logs:

A SignalException occurred in friends#new:

SIGTERM
/opt/local/ruby-enterprise-1.8.6-20090610/lib/ruby/gems/1.8/gems/vanity-1.2.0/vendor/redis-rb/lib/redis.rb:246:in `write'


Backtrace:

/opt/local/ruby-enterprise-1.8.6-20090610/lib/ruby/gems/1.8/gems/vanity-1.2.0/vendor/redis-rb/lib/redis.rb:246:in write' /opt/local/ruby-enterprise-1.8.6-20090610/lib/ruby/gems/1.8/gems/vanity-1.2.0/vendor/redis-rb/lib/redis.rb:246:inprocess_command'
/opt/local/ruby-enterprise-1.8.6-20090610/lib/ruby/gems/1.8/gems/vanity-1.2.0/vendor/redis-rb/lib/redis.rb:240:in raw_call_command' /opt/local/ruby-enterprise-1.8.6-20090610/lib/ruby/gems/1.8/gems/vanity-1.2.0/vendor/redis-rb/lib/redis.rb:257:incall'
/opt/local/ruby-enterprise-1.8.6-20090610/lib/ruby/gems/1.8/gems/vanity-1.2.0/vendor/redis-rb/lib/redis.rb:257:in maybe_lock' /opt/local/ruby-enterprise-1.8.6-20090610/lib/ruby/gems/1.8/gems/vanity-1.2.0/vendor/redis-rb/lib/redis.rb:240:inraw_call_command'
/opt/local/ruby-enterprise-1.8.6-20090610/lib/ruby/gems/1.8/gems/vanity-1.2.0/vendor/redis-rb/lib/redis.rb:195:in call_command' /opt/local/ruby-enterprise-1.8.6-20090610/lib/ruby/gems/1.8/gems/vanity-1.2.0/vendor/redis-rb/lib/redis.rb:183:inmethod_missing'
/opt/local/ruby-enterprise-1.8.6-20090610/lib/ruby/gems/1.8/gems/vanity-1.2.0/lib/vanity/experiment/base.rb:167:in active?' /opt/local/ruby-enterprise-1.8.6-20090610/lib/ruby/gems/1.8/gems/vanity-1.2.0/lib/vanity/experiment/ab_test.rb:189:inchoose'
/opt/local/ruby-enterprise-1.8.6-20090610/lib/ruby/gems/1.8/gems/vanity-1.2.0/lib/vanity/rails/helpers.rb:91:in `ab_test'

Protocol error, got ' ' as initial reply byte

Hello,
I got this error after some time working on production. I use redis-1.2.6. Here is a trace:

 Protocol error, got ' ' as initial reply byte
   /usr/local/lib/ruby/gems/1.8/gems/ezmobius-redis-0.1/lib/redis.rb:296:in `read_reply'
   /usr/local/lib/ruby/gems/1.8/gems/ezmobius-redis-0.1/lib/redis.rb:200:in `raw_call_command'
   /usr/local/lib/ruby/gems/1.8/gems/ezmobius-redis-0.1/lib/redis.rb:198:in `map'
   /usr/local/lib/ruby/gems/1.8/gems/ezmobius-redis-0.1/lib/redis.rb:198:in `raw_call_command'
   /usr/local/lib/ruby/gems/1.8/gems/ezmobius-redis-0.1/lib/redis.rb:163:in `call_command'
   /usr/local/lib/ruby/gems/1.8/gems/redis-store-0.3.7/lib/redis/marshaled_redis.rb:13:in `get'
   /usr/local/lib/ruby/gems/1.8/gems/redis-store-0.3.7/lib/cache/rails/redis_store.rb:27:in `read'

I assume this is a redis bug?

hash_ring.rb returns index -1

Hi,

I noticed the binary_search function sometimes returns -1. This occurs when 'value' is lower than ary[0].

I modified and added this:

end
if upper < 0
upper = ary.size - 1
end
return upper
end

Remove reliance on method_missing

I wrote out a branch to remove the reliance on method missing as it was causing a lot of problems for us, primarily when running rails specs (get and delete are already defined), so rather than write more methods like the #type and #exec methods I looped through all the methods and defined them.

http://github.com/dougcole/redis-rb/commits/remove_method_missing

If this isn't something you're interested in merging can we at least add a #get and #delete method so redis-rb plays nice with rspec?

"proc without block" problems with sinatra and padrino

Apparently because sinatra's application class (and/or padrino's router class) magically mixes in methods with common names like 'set' and 'delete', it causes all kinds of problems. What happens is that the method_missing method in redis.rb ends up delegating redis commands to sinatra!

I added explicit 'set' and 'delete' methods in redis.rb (see my fork) to get around this. I haven't fully tested every redis command after my modifications to check for other possible collisions.

Interested in simple connection pool?

Mostly we run under thin, but we do have a threaded job server. Found the :thread_safe option but really need a pool. Right now we're basically making our own "pool" by creating a connection per job class, but that's getting a bit crazy.

I was thinking

Redis.new(:host => 'localhost', :pool => 10)

The :pool option would imply :thread_safe. Looking through the code, it seems like a pretty straightforward patch to maybe_lock()

Just wanted to check on what you thought about this approach before jumping into implementation

Thanks,
Nate

Can I have a disconnect plz

Just doesn't feel right without one. Either Redis.close or Redis.disconnect will do.

Thanks for the great gem.

Using hmset loses the last value

To reproduce, in script/console:

r = Redis.new
=> #<Redis::Client:0x105b58938 @sock=nil, @thread_safe=nil, @timeout=5, @host="127.0.0.1", @db=0, @password=nil, @binary_keys=nil, @PubSub=false, @logger=nil, @PORT=6379>
r.hmset(123, "foo", "bar", "foo2", "bar2")
=> "OK"

Then in redis-cli:
redis> hgetall 123

  1. foo
  2. bar
  3. foo2

The value of key "foo2" is empty. I tested mset and it seems to be okay, so since it looks like both commands share code I'm guessing there might be a difference in the protocol, but I haven't really studied that yet.

I also noticed some odd behavior with hget:

r.hgetall(123)
RuntimeError: -ERR wrong number of arguments for 'set' command
r.hget(123, "foo2")
=> ["foo", "bar", "foo2", ""]
r.hget(123)
=> ""

I just came across this and haven't explored further yet, though.

Regression: redis.set returns "OK" rather than true

Ezra-

My redis-objects gem is based on redis-rb, and as of 1.0.3/1.0.4 timeframe, this command:

redis.set(key, value)

Now returns "OK" rather than true. I think this is a regression, as all of the rest of the redis-rb commands that would get "OK" back from redis-server are properly being mapped to true still.

Thanks,
Nate

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.