Why We Added Redis + Sidekiq to Our Rails 5 App

Problem:

Users were hitting Submit and waiting 2-3 seconds. Not because our DB was slow but because our controller was doing too much inline: sending emails, firing Slack notifications etc etc. All of it blocking the response.

The fix was not making those things faster. It was making them happen after we respond to the user. That is where Redis + Sidekiq came in.

How it works?

Redis acts as the job queue, a fast in-memory store where your Rails app drops a job message (“send welcome email to user 452”). Sidekiq runs as a separate process, watches Redis, picks up those messages and processes them. The user gets a response in milliseconds. The email goes out a second later in the background.

How we set up Redis?

We wrapped Redis behind a single file so nothing touches the client directly:

# lib/redis_store.rb
@redis = Redis.new(url: GlobalConstant::REDIS_URL)

redis.get(key)
redis.set(key, value)
redis.setex(key, ttl_seconds, value)   # set with expiry
redis.del(key)
redis.hset(hash, field, value)         # hash write
redis.hget(hash, field)                # hash read
redis.hdel(hash, field)                # hash delete
redis.expire(hash, ttl_seconds)        # set TTL on a hash

We also added JSON helpers since most of our cached values were objects:

def json_get(key)
  value = redis.get(key)
  value && JSON.parse(value)
end

def json_setex(key, time, value)
  redis.setex(key, time, value.to_json)
end

And we namespaced the Redis connection so keys from different environments never collide:

# config/initializers/redis.rb
$redis = Redis::Namespace.new(
  "myapp",
  redis: Redis.new(host: redis_host, port: redis_port)
)

How we implemented Sidekiq?

Since Sidekiq needed its own Redis connection, separate from the one our app uses for caching so we configured both server and client sides:

# config/initializers/sidekiq.rb
Sidekiq.configure_server do |config|
  config.redis = { url: "redis://#{redis_host}:#{redis_port}/0", namespace: 'myapp' }
end

Sidekiq.configure_client do |config|
  config.redis = { url: "redis://#{redis_host}:#{redis_port}/0", namespace: 'myapp' }
end

Server = the Sidekiq worker process.

Client = our Rails app (the one enqueuing jobs). Both need to point at the same Redis.

What a Job looks like?

class WelcomeEmailJob < ApplicationJob
  queue_as :default

  def perform(user_id)
    user = User.find(user_id)
    UserMailer.welcome(user).deliver_now
  end
end

Always pass IDs, not objects. The job runs later so the object you serialized at enqueue time could be stale by then.

Enqueue it from your controller:

WelcomeEmailJob.perform_later(@user.id)

That’s it. The controller moves on. Sidekiq handles the rest.

What actually improved?

  • Response time dropped from 2-3s to under 300ms on our Lead flow
  • Third-party failures stopped breaking user flows: a Slack timeout used to give users a 500, now it is just a failed job that can retry itself
  • Automatic retries: Sidekiq retries failed jobs up to 3 times after which we check why multiple failures
  • Scheduled tasks got clean: replaced cron tasks with sidekiq-cron and everything became visible in the UI

Things worth knowing before you take the feature Live…

Use multiple queues. A slow bulk-export job can block a password reset email if they are in the same queue.
Split them: critical, default, low. Higher priority queues get processed first.

Make jobs idempotent. Sidekiq retries on failure so your job might run twice. Add a guard:

return if user.welcome_email_sent?

Watch your DB connection pool. If Sidekiq concurrency is 25, your pool needs to be at least 25 too. Mismatch = timeouts.

Don’t just mount the Web UI but also authenticate:

authenticate :user, ->(u) { u.admin? } do
  mount Sidekiq::Web => '/sidekiq'
end

It shows live queues, failures, retry counts and throughput. You can use it every time something looks wrong.

Conclusion

If your Rails app is slow because of emails, API calls or anything the user doesn’t need to wait for, move it to a background job. Redis + Sidekiq is the standard way to do that in Rails, the setup is minimal and the improvement you will see is immediate.

Leave a Reply

Your email address will not be published. Required fields are marked *