New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Sentinel support #79
Sentinel support #79
Conversation
@DouweM Could you check this out? |
@@ -0,0 +1,31 @@ | |||
# Cache gems in between builds |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
MailRoom uses travis ci: https://travis-ci.org/tpitale/mail_room.
Are there reasons why we would also run gitlab ci?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi @tpitale, this was there because my initial fork was running here: https://gitlab.com/brodock/mail_room/tree/sentinel-support so I needed that file to run it with our CI.
It should do no harm, but I can remove it from the commit if you want. (as a side-effect, anyone who clone / fork / mirror using GitLab gets CI enabled automatically if the file stays)
def initialize(mailbox) | ||
redis_url = mailbox.arbitration_options[:redis_url] || "redis://localhost:6379" | ||
namespace = mailbox.arbitration_options[:namespace] | ||
sentinels = mailbox.arbitration_options[:sentinels] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is the structure of sentinels
? Would you mind updating the README to include usage for this configuration?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
redis = ::Redis.new(url: options.redis_url) | ||
sentinels = options.sentinels | ||
if sentinels | ||
redis = ::Redis.new(url: options.redis_url, sentinels: sentinels) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What happens if sentinels
is just an empty array? Does Redis
ignore that option? Is it possible to simply default the value to an empty array if no option is set (and thus avoid the conditional and local variable)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Blargh. Okay, thanks.
|
||
context 'when sentinel is present' do | ||
let(:redis_url) { 'redis://:mypassword@sentinel-master:6379' } | ||
let(:sentinels) { [host: '10.0.0.1', port: '26379'] } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this configuration intended to be an array of hashes? Could we make that more explicit (adding {}
or an additional sentinel config would make it more clear)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@brodock Doesn't look done :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
o.0 ohhh i fixed the other spec that is similar, will commit to this one.
) | ||
} | ||
|
||
before { ::Redis::Client::Connector::Sentinel.any_instance.stubs(:resolve).returns(sentinels) } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This any_instance
stubbing worries me, just a little. Combined with the instance_variable_get
in the test has me curious.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's really hard to make any kind of test/mock for sentinel... I've opted here for checking library API contract, but I'm open to any idea you can have. (this is very far from a full integration test)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've opened an issue to have RedisMock available in the main Gem or into a separated Gem: redis/redis-rb#641
This would allow us to make a better test.
Thanks for your work @brodock! |
@DouweM anything you'd like to add? |
if sentinels | ||
redis = ::Redis.new(url: options.redis_url, sentinels: sentinels) | ||
else | ||
redis = ::Redis.new(url: options.redis_url) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't really like repeating ::Redis.new(url: options.redis_url)
. What do you think about:
redis_options = {}
redis_options[:url] = options.redis_url
redis_options[:sentinels] = options.sentinels if options.sentinels
::Redis.new(redis_options)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Or (one line shorter 😸 ):
redis_options = {url: options.redis_url}
redis_options[:sentinels] = options.sentinels if options.sentinels
::Redis.new(redis_options)
client = Redis.new(url: options.redis_url) | ||
sentinels = options.sentinels | ||
if sentinels | ||
client = Redis.new(url: options.redis_url, sentinels: sentinels) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same comment as https://github.com/tpitale/mail_room/pull/79/files#r84497595!
Travis failed due to GH outage. Will retest in a bit. |
Okay, now it's failing for some specific reason:
|
@tpitale it's fixed. |
@brodock ready for me to merge? |
@tpitale yes :) |
Released v0.9.0. Thanks again @brodock. |
Sentinel with the ruby client is a little tricky to get right, because they use a special convention in "Redis URL" to pass additional params.
When you have sentinel, it always handle the first connection your client will make to, and it redirects your to a working "master".
For a production setup, you need at least 3 independent machines, each one running redis and sentinel. One of these machines will have a master redis, the others will have slave redis.
For sake of testing, you can do 1 machine with 1 sentinel and 1 master and 1 slave redis running in different ports.
Here is a minimal
sentinel.conf
file:myredis
is the<master-name>
as stated in sentinel.conf documentation: http://download.redis.io/redis-stable/sentinel.confWith that setup, here is how you connect to sentinel with Redis client (in ruby):
What can go wrong?
This is the most common error:
Redis::CannotConnectError: No sentinels available.
and the most frustating one to debug.It can mean literally anything. Your password is wrong, you are pointing to an unreachable address, invalid port, you typed the wrong "master-name" in the url, etc.
With this PR you can make MailRoom use Sentinel to connect to Redis for both Sidekiq and Arbitration
Here is a sample config.yml:
fixes #55