Every request to the home page reads the same data from the database again. With few users that's tolerable; the more requests come in, the longer each waits. Redis solves this by keeping hot data right in RAM.
Commands from every client land in one queue, and a single thread runs them — one at a time, in full. Keys live in RAM, and the time to live drops a key with no help from the application: the next read returns (nil).
Why Redis is so fast
Redis is an in-memory key-value store. Unlike PostgreSQL or MySQL, which keep data on disk, Redis holds everything in RAM — orders of magnitude faster than disk: microseconds instead of milliseconds.
The second reason is single-threaded command processing: Redis runs commands one after another and spends nothing on locks or coordination between threads. Since version six it reads from the network and parses requests with several threads, but execution still happens in one — and that is what makes timing predictable. Every command (GET, SET, ZADD) is atomic: no partial states.
When to use Redis
Redis fits tasks that need fast access or temporary storage:
Cache — the most common case. The result of a heavy query goes to Redis for a few minutes, and the next user gets it instantly, without touching the database.
Sessions — the session lives in Redis instead of the database or one process's memory, so any of several application instances will find it.
Counters — the atomic INCR command increments a number with no data race: view counts, likes, ratings.
Rate limiting — a counter of requests over the last minute with automatic expiration (TTL) enforces "no more than 100 requests per minute".
Task queues — the list structure (LIST) makes a simple queue: one process adds tasks, another takes and runs them.
When Redis is not a good fit
By default Redis keeps everything in memory. If the process crashes with no persistence enabled, the data is gone — fine for a cache, unacceptable for orders, invoices, or profiles.
Don't store large volumes in Redis: RAM is more expensive than disk, and Redis can't run complex JOIN queries or full-text search.
Rule: Redis is an accelerator next to the primary database, not a replacement.
Basic commands
It helps to try Redis directly in the redis-cli client first:
redis-cli
SET and GET — write and read
live example
# Write a string
SET user:42:name "Ivan"
# Read
GET user:42:name
# → "Ivan"
Run
Running examples is part of paid access. There the same code runs inside the article: editor, run and check next to the paragraph. Three free days →
The key is an arbitrary string; names are built with colons by convention: entity:id:field.
EXPIRE and TTL — key lifetime
TTL (time to live) — how many seconds the key has left. When the time runs out, Redis deletes it automatically.
live example
# Set a TTL of 60 seconds
EXPIRE user:42:name 60
# Check how much is left
TTL user:42:name
# → 58 (2 seconds have passed)
# → -1 (no TTL set, the key lives forever)
# → -2 (the key has already been deleted)
Run
Running examples is part of paid access. There the same code runs inside the article: editor, run and check next to the paragraph. Three free days →
Key and expiry in one command:
live example
# SET with the EX option — expires in 300 seconds
SET session:abc123 "session data" EX 300
Run
Running examples is part of paid access. There the same code runs inside the article: editor, run and check next to the paragraph. Three free days →
The mechanics are simple: a timestamp sits next to the value and is checked against the clock on read. The same logic in plain Java:
live example
import java.util.HashMap;
import java.util.Map;
public class TtlDemo {
record Entry(String value, long expiresAt) {}
static final Map<String, Entry> keys = new HashMap<>();
static long now = 0;
static void set(String key, String value, long ttlSeconds) {
long expiresAt = ttlSeconds > 0 ? now + ttlSeconds : Long.MAX_VALUE;
keys.put(key, new Entry(value, expiresAt));
}
static String get(String key) {
Entry entry = keys.get(key);
if (entry != null && entry.expiresAt() <= now) {
keys.remove(key);
entry = null;
}
return entry == null ? "(nil)" : entry.value();
}
public static void main(String[] args) {
set("session:abc", "user 42", 300);
set("user:42:name", "Ivan", 0);
System.out.println("t=0 GET session:abc -> " + get("session:abc"));
now = 301;
System.out.println("t=301 GET session:abc -> " + get("session:abc"));
System.out.println("t=301 GET user:42:name -> " + get("user:42:name"));
System.out.println("keys left: " + keys.size());
}
}
Run
Running examples is part of paid access. There the same code runs inside the article: editor, run and check next to the paragraph. Three free days →
Redis doesn't wait for a read: it drops expired keys in the background too, otherwise a forgotten key would hold memory forever. Either way an expired key is never handed back.
DEL — delete manually
live example
DEL user:42:name
# → 1 (one key deleted)
Run
Running examples is part of paid access. There the same code runs inside the article: editor, run and check next to the paragraph. Three free days →
EXISTS — check for presence
live example
EXISTS user:42:name
# → 1 (present) or 0 (absent)
Run
Running examples is part of paid access. There the same code runs inside the article: editor, run and check next to the paragraph. Three free days →
SET NX — write only if the key doesn't exist
SET with the NX flag (from "not exists") is atomic: the key is set only when it doesn't exist yet — the foundation for distributed locks.
live example
SET lock:order:99 "worker-1" NX EX 30
# → OK (success — nobody holds the lock)
# → (nil) (the key already exists — someone else grabbed it)
Run
Running examples is part of paid access. There the same code runs inside the article: editor, run and check next to the paragraph. Three free days →
The EX 30 flag is not decoration. A lock with no expiry is a trap: the process holding it crashes before releasing the key, the lock stays in Redis forever, and the queue stalls. The older SETNX command sets no expiry — that is why it is outdated: the expiry would be a second step, and a process can die between the two.
Connecting from Java / Spring Boot
In Spring Boot, a Redis connection rests on three things: a dependency, configuration in application.yml, and a RedisTemplate bean or caching via @Cacheable.
Dependency
// build.gradle
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
Spring Boot pulls in Lettuce — an asynchronous Redis client.
Connection configuration
# application.yml
spring:
data:
redis:
host: localhost
port: 6379
Writing and reading via RedisTemplate
RedisTemplate is the low-level way: full control over keys, values, and TTL.
@Service
public class CacheService {
private final RedisTemplate<String, String> redisTemplate;
public CacheService(RedisTemplate<String, String> redisTemplate) {
this.redisTemplate = redisTemplate;
}
public void save(String key, String value, long ttlSeconds) {
redisTemplate.opsForValue().set(key, value, Duration.ofSeconds(ttlSeconds));
}
public String load(String key) {
return redisTemplate.opsForValue().get(key);
}
}
Automatic caching via @Cacheable
For method results Spring offers caching annotations — no manual wiring needed.
@Service
public class ProductService {
// The method result is stored in Redis under the key "products::<id>"
@Cacheable(value = "products", key = "#id")
public Product findById(Long id) {
// called only on a cache miss
return productRepository.findById(id).orElseThrow();
}
// On update — evict the cache for this key
@CacheEvict(value = "products", key = "#product.id")
public void update(Product product) {
productRepository.save(product);
}
}
For @Cacheable to know about Redis, add to the configuration:
@Configuration
@EnableCaching
public class CacheConfig {
}
In short
- Redis is an in-memory key-value store; RAM plus the single-threaded model give sub-millisecond latency per operation.
- It fits caching, sessions, counters, queues, and rate limiting.
- It does not replace the primary database: without persistence, data lives only while the process runs.
- Core commands:
SET/GET/DEL/EXISTS/EXPIRE/TTL, andSET … NX EXfor a lock. - TTL — a key's lifetime; Redis deletes the key automatically when it expires.
- In Spring Boot it connects via
spring-boot-starter-data-redis(Lettuce); for caching —@Cacheable/@CacheEvict.
What to read next
- Redis data structures — strings, lists, sets, hashes, sorted sets, and when to choose which.
- Caching patterns — cache-aside, read-through, write-through: building a cache and evicting it.
- Redis beyond caching — locks, rate limiting, queues, and Pub/Sub on the same commands.
- Redis and Spring Boot — a deep dive into
RedisTemplate, serialization, Pub/Sub, and sessions.