A concise guide to Redis: what it is, how to deploy it, how it persists data, when you need it, how to use it in Java, and how TTL works. Every code block is labeled as one of the following types: a Redis command, Java code, or a configuration file.
Redis at a glance
Redis is a fast in-memory data store commonly used alongside a primary database such as PostgreSQL or MySQL. In Java/Spring applications, its most common jobs are caching, shared sessions, distributed coordination, rate limiting, and real-time counters.
Highlights
- What Redis is and how its deployment models differ.
- How RDB and AOF protect data when Redis restarts.
- When Redis is useful, and when a normal database is enough.
- How Java/Spring code maps to Redis commands.
- How TTL, expiration, and eviction work in practice.
Suggested reading order: read sections 1–4 first to understand Redis; then choose the Java use case in section 5 that matches your application; finally read section 6 to understand expiration and memory management.
How to read this article
Redis articles mix several "languages" in one page, which is confusing at first. Here is how each block is labeled:
| Label | What it is | Where you use it |
|---|---|---|
| REDIS COMMAND | A command you type directly into Redis (like SQL for a SQL database). Dark background. | Terminal, running redis-cli |
| JAVA CODE | Java (Spring Boot) code that talks to Redis. Light background. | Your application source code |
| CONFIG / XML | Settings files, not code that runs by itself. | redis.conf, application.properties, pom.xml |
| LUA SCRIPT | A small script that runs inside Redis, so several steps execute as one unit. | Sent to Redis with the EVAL command |
Small things that look strange but are simple:
- Redis blocks: the
>at the start of a line is theredis-cliprompt. You type what comes after it. Lines without>are Redis's reply (for example(integer) 1means "the answer is the number 1", and(nil)means "nothing / not found"). - Command names are case-insensitive (
set=SET), but by convention they are written in UPPERCASE so they stand out from keys and values. keyandvaluein a command pattern are placeholders. You replace them with your own names.- Colons in key names such as
rate:user42orlock:product:7are only a naming habit to group keys (like folders). Redis does not treat:as special. ...inside a sentence means "and other options / and others". It is not part of the syntax.//in Java code starts a comment. Redis config files use#for comments.- Unknown word? Jump to the Glossary (section 8).
1. What is Redis?
Redis (Remote Dictionary Server) is a NoSQL key-value database that keeps data mainly in RAM, so reads and writes are extremely fast (sub-millisecond, meaning less than 1/1000 of a second).
- It is a real database: you can insert, update, delete, and query data like any other DB. The difference is that you use commands (
SET,GET,HSET,ZADD...) instead of SQL. - Data types: String, Hash, List, Set, Sorted Set, Stream, Bitmap, HyperLogLog, Geo.
- Built-in persistence, replication, transactions, pub/sub, and TTL.
A first taste. SET stores a value under a key, GET reads it back:
> SET user:1:name "Linh"
OK
> GET user:1:name
"Linh"
> GET user:999:name
(nil)The last reply is (nil) because that key does not exist.
2. Deployment Models
| Model | Description | When to use |
|---|---|---|
| Standalone | A single instance | Small/medium apps that tolerate short downtime |
| Master-Replica | One master for writes, replicas (read-only copies) for reads | Need more read capacity and a backup copy |
| Sentinel | Master-Replica plus monitoring and automatic failover (if the master dies, a replica is promoted to master automatically) | Need high availability (HA), meaning the system stays up when one server fails; data fits on one machine |
| Cluster | Data is sharded (split into pieces) across 16,384 hash slots, each shard with replicas | Data exceeds one machine's RAM; need to scale reads and writes |
Rule of thumb: start with Standalone, move to Sentinel for HA, and use Cluster when you need to scale data or writes.
3. Persistence: RDB vs AOF
Persistence means saving RAM data to disk, so it survives a restart.
RDB (snapshot) takes periodic point-in-time dumps of the whole dataset.
- Pros: compact file, fast restore, low performance impact.
- Cons: a crash loses everything since the last snapshot.
AOF (Append Only File) logs every write command.
- Pros: much more durable. With
appendfsync everysecyou lose at most ~1 second of data. - Cons: larger files and slower restore (mitigated by AOF rewrite, which compacts the log).
Why AOF if we already have RDB? RDB is only a periodic snapshot, so a crash between two snapshots loses data. AOF records continuously and minimizes that loss.
What should you choose?
- Pure cache: disable persistence, or use RDB only.
- Important data: enable RDB + AOF (
everysec). RDB gives fast backups and restores, AOF gives durability. On restart, Redis prefers the AOF because it is more complete.
Note that appendfsync everysec is not a command you type. It is a setting in the Redis config file:
# RDB: take a snapshot if at least 1 key changed within 900 seconds
save 900 1
# AOF: turn it on, and flush the log to disk once per second
appendonly yes
appendfsync everysecfsync means "force the data to be physically written to disk". everysec = do it every second (a good balance of safety and speed).
4. When Do You Actually Need Redis?
Not every application needs Redis. Adding it means more infrastructure, more operational cost, and the risk of cache inconsistency (the cache and the database showing different data).
Use Redis when:
- The workload is read-heavy and the main DB is overloaded (caching: keeping a fast copy of frequently read data).
- You need very fast responses: leaderboards, view counters, rate limiting (limiting how many requests a user may make).
- Multiple app servers must share sessions (the "this user is logged in" information).
- You need distributed locks (a lock shared across several servers), lightweight queues, or pub/sub (publish/subscribe messaging).
- You store short-lived data: OTPs (One-Time Passwords, such as the 6-digit code sent by SMS) and tokens (temporary proof of identity, such as a password-reset link code).
You probably don't need it when:
- It is an internal app with few users and the DB handles the load fine.
- You haven't optimized your indexes and queries yet. Do that first.
- You need strict ACID guarantees (reliable transactions) and complex relations. Redis does not replace an RDBMS (relational database such as MySQL or PostgreSQL).
- Budget or ops skills are limited: RAM costs more than disk, and Sentinel/Cluster need someone who can run them.
Scale hint: consider Redis when DB latency (response time) starts rising, repeated reads reach thousands of requests per second, or multiple app instances need shared state.
5. Redis in Java
Common clients (Java libraries that connect to Redis):
- Spring Data Redis (
RedisTemplate,StringRedisTemplate): the most popular choice in Spring Boot. - Lettuce (Spring Boot default, async/non-blocking) and Jedis (simple, synchronous).
- Redisson: ready-made distributed locks, semaphores, rate limiters, and collections.
The key idea: Java methods are just translated into Redis commands
When your Java code calls redisTemplate.opsForValue().increment(key), the library sends the Redis command INCR key to the server. Same thing, different "language". Keep this table handy, because the rest of the article uses both forms:
| Java code (RedisTemplate) | Redis command it sends | Meaning |
|---|---|---|
opsForValue().set(k, v) | SET k v | Store a value |
opsForValue().set(k, v, 60, TimeUnit.SECONDS) | SET k v EX 60 | Store a value that expires in 60 s |
opsForValue().get(k) | GET k | Read a value |
opsForValue().increment(k) | INCR k | Add 1 to a number |
expire(k, 1, TimeUnit.MINUTES) | EXPIRE k 60 | Set a lifespan on an existing key |
opsForZSet().incrementScore(k, m, 50) | ZINCRBY k 50 m | Add 50 points to member m |
opsForZSet().reverseRangeWithScores(k, 0, 9) | ZREVRANGE k 0 9 WITHSCORES | Top 10, highest first |
opsForZSet().reverseRank(k, m) | ZREVRANK k m | Rank of member m |
5.1 Caching with @Cacheable
Idea: before running an expensive database query, check Redis first. If the answer is there (a cache hit), return it. If not (a cache miss), query the DB and store the result in Redis for next time.
In Spring you do this with annotations. An annotation is a label starting with @ that you put above a method to add behavior without writing the logic yourself.
@Cacheable(value = "products", key = "#id") // return cached value, or run method and store it
public Product getProduct(Long id) { return repo.findById(id).orElseThrow(); }
@CachePut(value = "products", key = "#p.id") // always run and update the cache
public Product update(Product p) { return repo.save(p); }
@CacheEvict(value = "products", key = "#id") // remove from cache when data changes
public void delete(Long id) { repo.deleteById(id); }Reading the annotation parameters:
value = "products": the name of the cache (think of it as a folder name).key = "#id": the cache key.#idis a Spring expression (SpEL) meaning "use this method's parameter calledid".#p.idmeans "theidfield of parameterp".- In Redis, the entry ends up under a key like
products::5(cache name +::+ key). You can inspect it with a Redis command:
> KEYS products::*
1) "products::5"
> TTL products::5
(integer) 540KEYS pattern lists keys matching a pattern (* = anything). Use it only for debugging on a small dev instance, because it is slow on big databases.
Notes:
- When DB data changes, you must
@CacheEvictor@CachePut, or readers will see stale (outdated) data. - Caching works through a proxy (Spring wraps your class with a helper object), so calling a method from within the same class bypasses the cache.
- To prevent cache penetration (repeated queries for keys that don't exist, which always fall through to the DB), cache
nullvalues with a short TTL.
5.2 Sessions with Spring Session
Problem: behind a load balancer (a device that spreads requests across several servers), in-memory sessions are tied to one server, so users get logged out when a request lands on another.
Solution: store sessions centrally in Redis.
Step 1: add the library to your Maven build file.
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>Step 2: tell Spring where Redis is.
spring.data.redis.host=localhost
spring.data.redis.port=6379(On Spring Boot 2.x the prefix is spring.redis.* instead of spring.data.redis.*.)
That's all. Spring Boot swaps HttpSession for a Redis-backed one. Your session.setAttribute(...) code stays the same. Stored objects must be serializable (convertible to bytes so they can be saved). Sessions appear in Redis as keys starting with spring:session:, with a TTL equal to the session timeout. If you use stateless JWT (a signed token the client carries, so the server stores nothing), you usually don't need this.
5.3 Distributed Locks with Redisson
Problem: Java's synchronized only locks within a single JVM (one running Java program). Across multiple instances, two requests can still process the same resource at once (e.g., both decrementing the same stock).
Idea: use a key in Redis as the lock. Whoever creates the key first owns the lock. Under the hood, the simplest version is this Redis command:
> SET lock:product:7 server-A NX PX 10000
OK
> SET lock:product:7 server-B NX PX 10000
(nil)NX= "only set if the key does Not eXist". The first caller getsOK(lock acquired). The second gets(nil)(lock already taken).PX 10000= expire after 10,000 milliseconds (10 s), so a crashed server cannot hold the lock forever.server-Ais the owner id, so only the owner releases it.
Writing this safely by hand is tricky, so the Java library Redisson does it for you:
RLock lock = redisson.getLock("lock:product:" + productId);
try {
// wait up to 5s to acquire, auto-release after 10s
if (lock.tryLock(5, 10, TimeUnit.SECONDS)) {
// decrement stock, create order...
} else {
throw new RuntimeException("System busy, try again later");
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
if (lock.isHeldByCurrentThread()) lock.unlock();
}tryLock(5, 10, SECONDS): wait up to 5 s for someone else to release; once you get it, it auto-expires after 10 s (the "lease time").finally { ... }: this block always runs, even after an error, so the lock is always released.isHeldByCurrentThread(): only unlock if you still own it (it may have already expired).InterruptedExceptionhandling: standard Java housekeeping when a waiting thread is interrupted.
Why Redisson over a hand-rolled SET NX: it has a watchdog (a background task, which by default renews the lock every ~10 s with a 30 s lease) that extends the lock while work is still running (only when you do not pass a lease time), and it supports reentrancy (the same thread can lock twice), fair locks, read-write locks, and semaphores.
Notes: always set an expiry to avoid deadlocks if a server dies, and only unlock a lock you hold. For critical business rules, add a DB-level safeguard (unique constraint or optimistic locking) as the last line of defense.
5.4 Rate Limiting
Rate limiting = restricting how many requests one user may send in a period of time, e.g. "max 100 requests per minute". It protects your API from abuse and overload.
What does "window" mean?
The window is simply the length of time over which you count. For "100 requests per minute", the window is 1 minute. A fixed window means the time is cut into fixed blocks (12:00:00–12:00:59, then 12:01:00–12:01:59, ...) and the counter starts again from 0 at each new block.
The Redis commands behind it
The limiter uses two commands: INCR (add 1 to a counter; creates it at 1 if missing) and EXPIRE (delete the key automatically after N seconds). Here is one user's first requests:
> INCR rate:user42
(integer) 1
> EXPIRE rate:user42 60
(integer) 1
> INCR rate:user42
(integer) 2
> TTL rate:user42
(integer) 52- First request: counter becomes 1. Because it is the first, we also start the 60-second window with
EXPIRE. - Next requests: counter goes 2, 3, 4... (we do not call
EXPIREagain, or the window would keep sliding forward). - After 60 seconds the key disappears, so the next
INCRstarts again at 1: a new window. - If the counter goes above 100, we reject the request.
The same logic in Java
public boolean allow(String userId) {
String key = "rate:" + userId; // e.g. "rate:user42"
Long count = redisTemplate.opsForValue().increment(key); // = INCR rate:user42
if (count == 1) { // first request of this window
redisTemplate.expire(key, 1, TimeUnit.MINUTES); // = EXPIRE rate:user42 60
}
return count <= 100; // true = allowed, false = over the limit (max 100 requests/minute)
}If it returns false, respond with HTTP 429 Too Many Requests (the standard status code meaning "slow down").
Notes:
- Burst at the boundary. A fixed window resets abruptly. A user can send 100 requests at 12:00:59 and another 100 at 12:01:00, which is 200 requests in 2 seconds while still "within the limit". For smoother behavior use a Sliding Window (the window moves with time; implemented with a Sorted Set) or a Token Bucket (you spend tokens that refill at a steady rate).
- Not atomic.
INCRandEXPIREare two separate commands. If the app crashes between them, the key never expires and the user is blocked forever. Atomic means "all steps happen as one indivisible unit". The fix is to combine them in a Lua script, which Redis runs without interruption:
local count = redis.call('INCR', KEYS[1])
if count == 1 then
redis.call('EXPIRE', KEYS[1], ARGV[1])
end
return countRun it with the EVAL command. KEYS[1] and ARGV[1] are the values you pass in (the key name, and the window length in seconds):
> EVAL "local c = redis.call('INCR', KEYS[1]) if c == 1 then redis.call('EXPIRE', KEYS[1], ARGV[1]) end return c" 1 rate:user42 60
(integer) 1Here 1 after the script means "I am passing 1 key", then come the key (rate:user42) and the extra argument (60).
- Redisson offers
RRateLimiterout of the box, so you write no counting logic yourself:
RRateLimiter limiter = redisson.getRateLimiter("api:" + userId);
limiter.trySetRate(RateType.OVERALL, 100, 1, RateIntervalUnit.MINUTES); // 100 permits per 1 minute
if (limiter.tryAcquire(1)) { /* allowed: we took 1 permit */ }RateType.OVERALL means the limit is shared by all clients using this limiter (PER_CLIENT would give each client its own limit). A "permit" is one allowed request.
5.5 Leaderboards with Sorted Sets
A Sorted Set (ZSET) is a collection where every member has a score, and Redis keeps the members ordered by score automatically. Adding, updating, and fetching the top N are all O(log N). In plain words: even with 1,000,000 members, Redis needs only about 20 steps, far cheaper than running ORDER BY score DESC on a large SQL table.
All Sorted Set commands start with the letter Z (that is how you recognize them). REV in a name means "reverse order" (highest first).
| Redis command | Purpose |
|---|---|
ZADD / ZINCRBY | Add a member (with a score), or increment its score (INCR + BY) |
ZREVRANGE | Get the top N (highest first). Range = positions, so 0 9 means the 1st to the 10th |
ZREVRANK | Get a member's rank (0 = first place) |
ZSCORE | Get a member's score |
Redis commands in action:
> ZINCRBY leaderboard:game1 50 alice
"50"
> ZINCRBY leaderboard:game1 80 bob
"80"
> ZREVRANGE leaderboard:game1 0 9 WITHSCORES
1) "bob"
2) "80"
3) "alice"
4) "50"
> ZREVRANK leaderboard:game1 alice
(integer) 1
> ZSCORE leaderboard:game1 bob
"80"The same thing from Java (compare with the translation table above):
redis.opsForZSet().incrementScore("leaderboard:game1", userId, points); // add points
redis.opsForZSet().reverseRangeWithScores("leaderboard:game1", 0, 9); // top 10
redis.opsForZSet().reverseRank("leaderboard:game1", userId); // rank (0-based)For daily or weekly boards, put the date in the key (leaderboard:2026-10-31) and attach a TTL (e.g. EXPIRE leaderboard:2026-10-31 604800 = delete after 7 days). Ties: members with equal scores are ordered alphabetically by name. To favor "who reached the score first", encode the timestamp in the score's decimal part (earlier time = slightly larger decimal), so the earlier player ranks higher.
6. TTL: Time To Live
TTL is a key's lifespan. When it runs out, Redis deletes the key automatically. It is the main tool that keeps a cache from growing forever and stale data from living indefinitely.
6.1 Common commands
| Redis command | Meaning |
|---|---|
SET key value EX 300 | Set a value with a TTL of 300 seconds. EX = seconds; PX = milliseconds (1000 ms = 1 s) |
EXPIRE key 60 / PEXPIRE key 1500 | Set a TTL on an existing key (seconds / milliseconds) |
TTL key / PTTL key | Show remaining time (seconds / milliseconds) |
PERSIST key | Remove the TTL so the key lives forever |
Tip: a leading P in a Redis command always means milliseconds (PEXPIRE, PTTL, and the PX option).
TTL returns -1 if the key exists but has no expiry, and -2 if the key does not exist (it never existed, or it already expired).
> SET otp:user42 123456 EX 300
OK
> TTL otp:user42
(integer) 297
> GET otp:user42
"123456"
(5 minutes later)
> GET otp:user42
(nil)
> TTL otp:user42
(integer) -26.2 Common pitfalls
- A plain
SETon an existing key clears its TTL. UseSET ... KEEPTTLto keep it (here...stands for your key and value). - Reading a key (
GET) does not refresh its TTL. For sliding expiration (the lifespan restarts every time the key is used, as with sessions), callEXPIREagain or useGETEX(read and set a new TTL in one command). - TTL applies to the whole key, not to individual elements inside a Hash, List, or Set.
> SET session:1 abc EX 1800
OK
> SET session:1 xyz
OK
> TTL session:1
(integer) -1
> SET session:1 xyz KEEPTTL
OK
> GETEX session:1 EX 1800
"xyz"After the second SET, the TTL is -1 (the expiry was wiped). GETEX session:1 EX 1800 returns the value and restarts the 30-minute lifespan.
6.3 How Redis removes expired keys
- Lazy: when a key is accessed, Redis checks it and deletes it if expired.
- Active: periodically, Redis samples (randomly checks) keys that have a TTL and deletes the expired ones.
So an expired key may not free memory immediately, but you will never be able to read it. With replicas, the master decides expiration and sends a DEL (delete) command to replicas (and writes it to the AOF).
6.4 TTL vs eviction when RAM is full
Eviction means Redis deleting keys on its own because memory is full. It is different from TTL (deleting because time ran out). When memory hits maxmemory, Redis follows maxmemory-policy. Both are settings in redis.conf, not commands:
maxmemory 2gb # the RAM limit
maxmemory-policy allkeys-lru # what to delete when the limit is reachednoeviction(default): rejects new writes with an error.allkeys-lru: evicts the Least Recently Used keys (the ones not touched for the longest time) across everything. Good for pure caches.volatile-lru/volatile-ttl: evicts only among keys that have a TTL ("volatile" = has an expiry). Good when cache and must-keep data share one instance.
6.5 Choosing a TTL
| Data type | Suggested TTL | Why |
|---|---|---|
| OTP / verification codes (One-Time Password: the 6-digit code sent by SMS/email) | 1 to 5 minutes | A code that lives too long is a security risk |
| Rate-limit counters | Exactly the window length (1 second, 1 minute...) | The window is the period you count in (see 5.4). TTL = window length makes the counter reset exactly when a new window starts. Too short lets users exceed the limit; too long blocks them longer than intended |
| Sessions | 15 to 30 minutes, renewed on activity | Logs out idle users, but active users stay logged in |
| Rarely changing cache (categories, config) | A few hours to 1 day | Data rarely changes, so a long TTL saves many DB reads |
| Frequently changing cache (price, stock) | A few seconds to a few minutes | Wrong data is costly, so keep it fresh |
| Cached nulls (anti-penetration) | 30 seconds to 2 minutes | Short, because the record may be created soon |
Longer TTL saves more DB load but makes data staler. Pick based on how much staleness the business can tolerate.
6.6 Cache avalanche: avoid simultaneous expiry
If many keys are loaded together with the same TTL, they expire together and all requests hit the DB at once (a cache avalanche). Fix it by adding a random extra time, called jitter, to each TTL:
long ttl = 600 + ThreadLocalRandom.current().nextInt(0, 120); // 10 min + 0~2 min
redisTemplate.opsForValue().set(key, value, ttl, TimeUnit.SECONDS); // = SET key value EX ttlThreadLocalRandom is Java's random-number generator. nextInt(0, 120) gives a random whole number from 0 to 119, so each key lives between 600 and 719 seconds and they no longer expire at the same moment.
6.7 Per-cache TTL in Spring
Different caches often need different TTLs. In Spring you define them once in a configuration class (@Bean marks a method whose result Spring manages for you):
@Bean
public RedisCacheManager cacheManager(RedisConnectionFactory cf) {
RedisCacheConfiguration base = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(10)); // default TTL: 10 minutes
return RedisCacheManager.builder(cf)
.cacheDefaults(base)
.withCacheConfiguration("categories", base.entryTtl(Duration.ofHours(6))) // this cache: 6 hours
.withCacheConfiguration("stock", base.entryTtl(Duration.ofSeconds(30))) // this cache: 30 seconds
.build();
}The names "categories" and "stock" are the same cache names you put in @Cacheable(value = "...") (see 5.1). Duration.ofMinutes(10) is just Java's way of writing "10 minutes".
7. Summary
| Topic | Key takeaway |
|---|---|
| What it is | In-memory NoSQL key-value database, operated by commands instead of SQL |
| Deployment | Standalone, then Sentinel (HA), then Cluster (scale data) |
| Persistence | RDB for fast backup/restore, AOF for durability; use both for important data |
| When needed | DB overloaded by repeated reads, real-time speed, or shared state across instances |
| Cache | Java: @Cacheable, and remember @CacheEvict when data changes |
| Session | Spring Session shares logins across servers |
| Lock | Java: Redisson RLock (Redis idea: SET ... NX PX), always with an expiry |
| Rate limit | Redis: INCR + EXPIRE (or a Lua script); Java: Redisson RRateLimiter |
| Leaderboard | Sorted Set (ZINCRBY, ZREVRANGE), O(log N) |
| TTL | Always set it for caches, add jitter, choose by acceptable staleness |
8. Glossary: every unfamiliar term in one place
The "Type" column tells you what kind of thing each term is.
| Term | Type | Meaning |
|---|---|---|
SET / GET | Redis command | Store / read a String value under a key |
INCR | Redis command | Add 1 to a numeric key (creates it at 1 if missing). Used for counters |
EXPIRE / PEXPIRE | Redis command | Give an existing key a lifespan, in seconds / milliseconds |
TTL / PTTL | Redis command | Show remaining lifespan (seconds / milliseconds). -1 = no expiry, -2 = key missing |
PERSIST | Redis command | Remove a key's lifespan |
GETEX | Redis command | Read a value and set a new TTL at the same time |
DEL | Redis command | Delete a key |
KEYS pattern | Redis command | List keys matching a pattern. Debugging only, slow on big data |
EVAL | Redis command | Run a Lua script inside Redis |
ZADD, ZINCRBY, ZREVRANGE, ZREVRANK, ZSCORE | Redis commands | Sorted Set commands (all start with Z): add, add to score, top N, rank, read score. REV = highest first |
HSET | Redis command | Set a field inside a Hash (a key that holds several name/value pairs, like a small object) |
EX / PX | Command option | Used with SET: lifespan in seconds (EX) or milliseconds (PX) |
NX | Command option | With SET: only write if the key does Not eXist (used for locks) |
KEEPTTL | Command option | With SET: keep the old TTL instead of wiping it |
WITHSCORES | Command option | Also return each member's score, not just the names |
appendfsync everysec, appendonly, save | Config setting | Persistence settings in redis.conf (AOF on/off, flush frequency, RDB snapshot rule) |
maxmemory, maxmemory-policy | Config setting | RAM limit, and what to delete when it is reached |
@Cacheable, @CachePut, @CacheEvict | Java annotation | Spring labels: use cache / always update cache / remove from cache |
@Bean | Java annotation | Marks a method whose result Spring creates and manages |
#id | Spring expression | "The method parameter named id" |
RedisTemplate | Java class | Spring's main tool for sending commands to Redis; opsForValue() = String commands, opsForZSet() = Sorted Set commands |
RLock, RRateLimiter | Java class (Redisson) | Ready-made distributed lock / rate limiter |
KEYS[1], ARGV[1] | Lua | Inside a Lua script: the 1st key and the 1st extra argument you passed to EVAL |
| Window | Concept | The period of time over which requests are counted (e.g. 1 minute for "100 per minute") |
| Fixed window / Sliding window / Token bucket | Concept | Three ways to rate-limit: count in fixed blocks of time / count over a moving period / spend tokens that refill steadily |
| OTP | Concept | One-Time Password: a short-lived code (e.g. 6 digits by SMS) used once |
| Atomic | Concept | Several steps executed as one indivisible unit; nothing can happen in between |
| Lua script | Concept | A small script that runs inside Redis, which makes multi-step logic atomic |
| O(log N) | Concept | Speed notation: when data grows a lot, the time grows only slightly (1 million items = about 20 steps) |
| TTL | Concept | Time To Live: how long a key lives before Redis deletes it |
| Eviction / LRU | Concept | Redis deleting keys because RAM is full / choosing the least recently used ones |
| Jitter | Concept | A random extra amount added to TTLs so keys don't all expire together |
| Cache hit / miss | Concept | The data was found in the cache / not found, so the DB had to be queried |
| Cache avalanche | Concept | Many keys expire at the same moment and all requests hit the DB at once |
| Cache penetration | Concept | Repeated requests for keys that don't exist, which always reach the DB |
| Stale data | Concept | Outdated data (cache still shows the old value after the DB changed) |
| HA / failover | Concept | High availability (stays up when a server fails) / automatic switch to a backup server |
| Shard / hash slot | Concept | A piece of the data split across servers in Cluster mode; Redis has 16,384 slots that are distributed among shards |
| Serializable | Concept | Can be converted to bytes so it can be stored or sent over a network |
| HTTP 429 | Concept | Standard "Too Many Requests" response code |
Conclusion
Redis shines in speed and its variety of data structures. It usually acts as a cache and real-time layer next to your primary database rather than replacing it. Choose the deployment model based on availability needs and data size, combine RDB + AOF when durability matters, and always set a sensible TTL so your cache stays lean, data stays reasonably fresh, and your system doesn't collapse from an avalanche.
Most importantly: use Redis only when you have a real performance or distributed-architecture problem to solve, because it always comes with infrastructure cost and operational complexity.

