← Back to the section

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 different clients line up in one queue and run one at a time clients command queue, one thread memory: keys and expiry client A client B client C SET user:42:name SET session:abc EX 300 GET user:42:name GET session:abc SET user:42:name SET session:abc EX 300 GET user:42:name GET session:abc user:42:name= Ivan, no expiry session:abc = user 42, TTL 300 = user 42, TTL 120 = user 42, TTL 0 session:abcexpired — key is gone five minutes pass — Redis drops the key itself what the client got back GET user:42:name → Ivan GET session:abc → (nil)

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, and SET … NX EX for 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.