What Is Redis and Why Is It Used?

What Is Redis and Why Is It Used?
Redis appears in almost every web project sooner or later: as a cache, a queue for background jobs, a session store or a real-time counter. In the Rails world it has long been the default companion of Sidekiq and Action Cable. In this article we will look at what Redis is, how it works, which data structures it offers, the most common use cases with Ruby and Rails examples, its limitations, and what changed with Redis licensing and Rails 8.
What is Redis?
Redis (REmote DIctionary Server) is an in-memory data store. It keeps data in RAM and accesses it by key, which makes reads and writes extremely fast: a typical operation takes well under a millisecond.
Redis was created in 2009 by Salvatore Sanfilippo. It is often called a key-value database, but that is an understatement: Redis is a data structure server. A value can be not only a string but also a list, hash, set, sorted set, stream and more, and Redis provides atomic commands for working with each of these types.
Why is Redis so fast?
- Data lives in memory. There is no disk access when reading data.
- Simple, efficient data structures implemented in C.
- Single-threaded command execution. Commands are executed one at a time, so there are no locks and no race conditions inside Redis: every command is atomic. Since Redis 6, network I/O can use extra threads, but commands themselves still run sequentially.
- A lightweight protocol (RESP) and support for pipelining, which lets a client send many commands in one round trip.
Main data types
Type What it is Typical use String Text, number or binary data up to 512 MB Cache, counters, flags List Ordered list of strings Simple queues, latest items Hash Field–value pairs inside one key Objects, user profiles, settings Set Unique unordered values Tags, unique visitors, “who liked” Sorted set Unique values with a score, sorted by score Leaderboards, rankings, scheduling Stream Append-only log of events Event sourcing, message processing Bitmap, HyperLogLog, Geo Specialized structures Activity flags, approximate unique counts, nearby searchRedis 8 also includes JSON documents, search and vector sets in the core distribution, which used to require separate modules.
A quick look at redis-cli
$ redis-cli
127.0.0.1:6379> SET greeting "Hello"
OK
127.0.0.1:6379> GET greeting
"Hello"
127.0.0.1:6379> SET session:abc123 "user_42" EX 3600 # expires in 1 hour
OK
127.0.0.1:6379> TTL session:abc123
(integer) 3600
127.0.0.1:6379> INCR page:views
(integer) 1
127.0.0.1:6379> HSET user:42 name "Anna" city "Warsaw"
(integer) 2
127.0.0.1:6379> HGETALL user:42
1) "name"
2) "Anna"
3) "city"
4) "Warsaw"
One of the most important features here is TTL (time to live): any key can be given an expiration time, after which Redis deletes it automatically. This is exactly what you need for caches, sessions and temporary tokens.
Why is Redis used? Main use cases
In Ruby you work with Redis through the redis gem:
# Gemfile: gem "redis"
require "redis"
REDIS = Redis.new(url: ENV.fetch("REDIS_URL", "redis://localhost:6379/0"))
1. Caching
The most popular use. Heavy SQL queries, results of external API calls or rendered HTML fragments are stored in Redis and returned instantly on the next request. In Rails you only need to configure the cache store:
# config/environments/production.rb
config.cache_store = :redis_cache_store, {
url: ENV.fetch("REDIS_URL", "redis://localhost:6379/1"),
expires_in: 1.day
}
# Anywhere in the application
def dashboard_stats
Rails.cache.fetch("dashboard/stats/#{Date.current}", expires_in: 10.minutes) do
{
users: User.count,
orders: Order.where(created_at: Date.current.all_day).count,
revenue: Order.paid.sum(:total)
}
end
end
The first call runs the queries and saves the result; the following calls within 10 minutes read it straight from Redis.
2. Background job queues
Sidekiq, the most popular background job processor for Ruby, stores its queues in Redis. Sending emails, generating reports, processing images: all this is moved out of the web request and handled by worker processes.
# app/sidekiq/welcome_email_job.rb
class WelcomeEmailJob
include Sidekiq::Job
sidekiq_options queue: :mailers, retry: 5
def perform(user_id)
user = User.find(user_id)
UserMailer.with(user: user).welcome.deliver_now
end
end
# In a controller or model
WelcomeEmailJob.perform_async(user.id)
3. Sessions and temporary data
Storing sessions in Redis lets several application servers share them, and TTL removes old sessions automatically. The same approach works for one-time codes, password reset tokens and email confirmation links.
4. Rate limiting
Atomic counters with TTL make it easy to limit the number of requests, for example, 100 API calls per minute per user:
class RateLimiter
def initialize(redis: REDIS, limit: 100, period: 60)
@redis = redis
@limit = limit
@period = period
end
def allowed?(identifier)
window = Time.now.to_i / @period
key = "rate:#{identifier}:#{window}"
count, _ = @redis.multi do |tx|
tx.incr(key)
tx.expire(key, @period)
end
count <= @limit
end
end
limiter = RateLimiter.new
limiter.allowed?("user:42") # => true
Because INCR is atomic, this works correctly even when many application servers hit the same key at the same time. Since Rails 7.2, controllers also have a built-in rate_limit method, which uses the cache store under the hood.
5. Real-time features and pub/sub
Redis supports the publish/subscribe model: one process publishes a message to a channel, and all subscribers receive it instantly. Action Cable uses this to deliver WebSocket messages between several servers:
# config/cable.yml
production:
adapter: redis
url: <%= ENV.fetch("REDIS_URL", "redis://localhost:6379/2") %>
channel_prefix: myapp_production
6. Leaderboards and rankings
Sorted sets keep elements ordered by score, so a top-10 is just one command:
REDIS.zincrby("leaderboard", 50, "anna")
REDIS.zincrby("leaderboard", 30, "oleg")
REDIS.zincrby("leaderboard", 70, "maria")
REDIS.zrevrange("leaderboard", 0, 9, with_scores: true)
# => [["maria", 70.0], ["anna", 50.0], ["oleg", 30.0]]
REDIS.zrevrank("leaderboard", "anna") # => 1 (position, starting from 0)
7. Distributed locks
When a task must not run twice at the same time across several servers (for example, a scheduled import), you can use a lock based on SET with the NX option:
def with_lock(name, ttl: 30)
token = SecureRandom.uuid
acquired = REDIS.set("lock:#{name}", token, nx: true, ex: ttl)
return false unless acquired
begin
yield
ensure
# Delete the lock only if it is still ours
REDIS.eval(<<~LUA, keys: ["lock:#{name}"], argv: [token])
if redis.call("get", KEYS[1]) == ARGV[1] then
return redis.call("del", KEYS[1])
end
return 0
LUA
end
end
with_lock("daily_import") { DailyImport.run }
Persistence: does Redis lose data on restart?
Although Redis keeps data in memory, it can save it to disk:
- RDB (snapshots): Redis periodically saves a full snapshot of the data. Compact and fast to restore, but changes since the last snapshot can be lost.
- AOF (append-only file): every write command is appended to a log. Much smaller risk of data loss (by default, at most about one second), but the file is larger.
- Both together: a common choice when data matters.
- No persistence: fine for a pure cache that can be rebuilt.
When memory runs out: eviction
The maxmemory setting limits how much RAM Redis can use, and maxmemory-policy decides what happens when the limit is reached:
allkeys-lruorallkeys-lfu: delete the least recently or least frequently used keys. A good choice for a cache.noeviction: refuse new writes. Required for Sidekiq and other queues, because silently deleting jobs is much worse than an error.
That is why in production a cache and a job queue are often kept in separate Redis instances with different settings.
Installing Redis on Ubuntu
sudo apt update
sudo apt install redis-server
sudo systemctl enable --now redis-server
redis-cli ping # => PONG
Key lines in /etc/redis/redis.conf:
bind 127.0.0.1 -::1 # listen only on localhost
protected-mode yes
requirepass a-long-random-password # or use ACL users
maxmemory 512mb
maxmemory-policy allkeys-lru # for a cache; noeviction for Sidekiq
appendonly yes # enable AOF if the data matters
After changing the config, restart the service: sudo systemctl restart redis-server.
Limitations and pitfalls
- RAM is expensive and limited. All data must fit in memory, so Redis is not suitable for storing large volumes of data.
- Not a replacement for the main database. There are no relations, no SQL and no complex queries, and durability guarantees are weaker than in PostgreSQL or MySQL. Redis complements the database rather than replacing it.
- Security. Never expose port 6379 to the internet. Open Redis instances without a password are regularly found and attacked.
- Slow commands block everyone. Because commands run one at a time,
KEYS *on a large database or a hugeSMEMBERSwill stop all other clients. UseSCANinstead ofKEYS. - Cache invalidation. A cache can return stale data. Think carefully about TTL and when to clear keys.
Redis, licenses and Valkey
For many years Redis was distributed under the permissive BSD license. In 2024 Redis Inc. switched to source-available licenses (RSALv2 and SSPL), which were not considered open source. In response, the Linux Foundation and large cloud providers created Valkey, a fully open source fork of Redis 7.2 that is compatible with Redis clients and commands.
In 2025, with the release of Redis 8, Redis added the open source AGPLv3 license as an option. Today both Redis and Valkey are actively developed. For a typical Rails application the choice rarely matters: the redis gem, Sidekiq and Rails cache work with both.
Do you still need Redis in Rails 8?
Rails 8 introduced the “Solid” trio, which by default uses the main database instead of Redis:
- Solid Cache: cache stored in a database table (on disk, so it can be much larger than RAM).
- Solid Queue: background jobs for Active Job.
- Solid Cable: the Action Cable adapter.
This means a new Rails application can run without Redis at all, which simplifies deployment on a small VPS. Redis is still a great choice when you need:
- Sidekiq and its ecosystem (Pro/Enterprise features, Web UI, many plugins);
- the lowest possible latency for cache and counters;
- sorted sets, streams, pub/sub, atomic counters, rate limiting and locks;
- very high load with many real-time WebSocket messages.
Best practices
- Always set a TTL for cache and temporary data.
- Use clear key names with prefixes:
user:42:profile,rate:api:42. - Separate cache and queues into different instances or at least different databases with suitable eviction policies.
- Configure
maxmemoryso Redis does not take all the server's memory. - Protect Redis: bind to localhost or a private network, set a password or ACL, and use TLS between servers.
- Avoid
KEYSand huge values; useSCANand keep values small. - Monitor it:
INFO,SLOWLOG, memory usage and the hit rate of the cache. - Do not keep the only copy of important data in Redis. The source of truth should be your main database.
Conclusion
Redis is a very fast in-memory data store with rich data structures. It is used for caching, background job queues, sessions, rate limiting, real-time messaging, leaderboards and distributed locks. It does not replace a relational database, but it takes over everything that needs to be fast, temporary or shared between servers. In Rails 8 you can start without Redis thanks to Solid Cache, Solid Queue and Solid Cable, but as the load and requirements grow, Redis (or Valkey) remains one of the most useful tools in a web developer's toolbox.
Back



