본문 바로가기
개발 (ENG)

Spring Boot(Kotlin) — ep.7 Redis Configuration in Spring Boot: Why Your Application Needs It

by 새싹 아빠 2025. 11. 25.

📚 Spring Boot(Kotlin) Server Setup — Series Overview

  1. Why Multi-Module Architecture? (Architecture Philosophy & Overall Design)
  2. API Response Format Design
  3. Global Exception Handling (GlobalExceptionHandler)
  4. Swagger (OpenAPI) Configuration
  5. Security (JWT) Basic Structure
  6. JWT TokenProvider
  7. Redis Configuration ← Current Article
  8. Validation Configuration
  9. Logging + MDC(traceId)
  10. application.yml Profile Separation (local/dev/prod)
  11. Multi-Module + JPA Basic Structure
  12. Project Template git Distribution

📌 Why Do We Need Redis?

When building an API server, it’s natural to think, “Isn’t a single database enough?” But as soon as traffic increases, DB connection pool limits and query performance become real bottlenecks.

✔ MySQL/RDBs have a fixed connection pool size (e.g., 151 connections)
✔ Each API call borrows one DB connection and returns it afterward
✔ When many requests arrive at once, the pool becomes full and remaining requests wait
✔ If the wait exceeds the timeout → errors occur → potential service disruption

Especially for read-heavy services, “a simple list API” can consume many DB connections. Even quickly tapping a button several times can fill the connection pool instantly.

So instead of relying solely on DB queries, I decided to move read-heavy workloads to a Redis cache layer.

✔ Redis is an in-memory key-value data store
✔ Extremely fast read performance, ideal for absorbing high traffic
✔ Perfect for caching frequently accessed but infrequently updated data
✔ Allows the DB to focus on queries that actually require strong consistency

In this series, we already implemented a RefreshToken store using Redis in the previous article.
This article focuses on the foundational Redis Configuration (RedisConfig) and explores typical Redis use cases.

📌 What Can Redis Be Used For?

Redis is widely used for the following purposes:

1) Cache
- Cache frequently accessed DB data
- Examples: home-screen summaries, code tables, ranking lists, configuration values
- DB is only used on cache-miss → reduces DB load and improves response speed

2) Session Store
- Store login/session data in Redis instead of server memory
- Multiple servers can share the same Redis → easy horizontal scaling
- With JWT, Redis can store “extra session state” or token blacklist info

3) Token / Authentication Data
- RefreshToken, email verification codes, password reset tokens
- Automatically cleaned up using TTL (expire time)

4) Distributed Lock
- Prevent concurrency issues when multiple servers update the same resource
- Examples: inventory updates, preventing duplicate payments, sequential processing
- Uses Redis SETNX or libraries like Redisson

5) Counter / Ranking / Real-time Aggregation
- Likes, views, online user count, real-time rankings
- Redis is extremely fast with increment/decrement operations

In short, Redis is ideal for data that is “fast, lightweight, and non-critical.”
RDBs are still essential — but relying solely on DBs will quickly reach scaling limits.
That’s why Redis became a crucial part of this project’s architecture.

📌 Where Should Redis Configuration Live?

This project is structured using a multi-module architecture.

✔ API Module: controllers, request/response models, security, exception handling, Swagger
✔ Application Module: business logic, domain services, infrastructure access (Redis, DB)

Redis is part of the infrastructure layer used directly by domain and application services. Since components like RefreshTokenRepository or cache repositories operate at the application level, I decided to place RedisConfig inside the Application module.

In short:

✔ API module focuses solely on “HTTP requests and responses”
✔ Application module manages “how data is stored and retrieved”
→ Redis configuration belongs in the Application module

📌 Required Dependencies

Here are the basic dependencies needed to use Redis in a Spring Boot application (using Gradle as an example):

dependencies {
    implementation("org.springframework.boot:spring-boot-starter-data-redis")

    // Optional: Kotlin + JSON serialization support
    implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
}
✔ spring-boot-starter-data-redis: provides RedisConnectionFactory, RedisTemplate, StringRedisTemplate, etc.
✔ Spring Boot 3.x uses Lettuce as the default Redis client (Netty-based, async support)
✔ Without additional configuration, Spring uses LettuceConnectionFactory internally

📌 application.yml Example

The Redis connection configuration should not be hard-coded. Instead, define it in application.yml:

redis:
  host: localhost
  port: 6379

In real environments, you can override this in dev.yml or prod.yml with different host/port values—plus authentication, sentinel, or cluster configs if needed.

📌 RedisConfig (Application Module)

package com.example.application.config

import org.springframework.beans.factory.annotation.Value
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.data.redis.connection.RedisConnectionFactory
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory
import org.springframework.data.redis.core.StringRedisTemplate

@Configuration
class RedisConfig(
    @Value("\${redis.host}") private val host: String,
    @Value("\${redis.port}") private val port: Int
) {

    @Bean
    fun redisConnectionFactory(): RedisConnectionFactory {
        return LettuceConnectionFactory(host, port)
    }

    @Bean
    fun stringRedisTemplate(connectionFactory: RedisConnectionFactory): StringRedisTemplate {
        return StringRedisTemplate(connectionFactory)
    }
}

📌 RedisConfig Code Breakdown

1️⃣ @Configuration + @Bean

- @Configuration: marks this class as a Spring configuration component
- All @Bean methods inside are registered in the Spring container
- Other services/repositories can directly inject StringRedisTemplate

2️⃣ @Value("\${redis.host}") / @Value("\${redis.port}")

Inject values defined in application.yml.

- Allows different host/port settings for each environment (local/dev/prod)
- Local: localhost
- Production: internal Redis endpoint inside your VPC
- Easy to integrate with CI/CD environment variables or secret management systems

3️⃣ redisConnectionFactory()

fun redisConnectionFactory(): RedisConnectionFactory {
    return LettuceConnectionFactory(host, port)
}
- LettuceConnectionFactory sets up Redis connectivity using the Lettuce client
- Manages connections using the provided host/port
- Spring Data Redis internally uses this connection factory to execute Redis commands

4️⃣ StringRedisTemplate

fun stringRedisTemplate(connectionFactory: RedisConnectionFactory): StringRedisTemplate {
    return StringRedisTemplate(connectionFactory)
}

StringRedisTemplate is optimized for key-value pairs where both key and value are Strings.

- Perfect for most token, cache key, and counter operations
- Examples:
    refresh:123 → "refresh token string"
    config:main-banner → JSON string
- Complex structures can be serialized/deserialized as JSON strings

📌 Simple Usage Example with StringRedisTemplate

Here’s a simple service demonstrating how to use StringRedisTemplate in practice:

package com.example.application.sample

import org.springframework.data.redis.core.StringRedisTemplate
import org.springframework.stereotype.Service
import java.util.concurrent.TimeUnit

@Service
class SampleCacheService(
    private val redis: StringRedisTemplate
) {

    fun putSampleData(key: String, value: String, ttlSeconds: Long) {
        redis.opsForValue().set(key, value, ttlSeconds, TimeUnit.SECONDS)
    }

    fun getSampleData(key: String): String? {
        return redis.opsForValue().get(key)
    }

    fun removeSampleData(key: String) {
        redis.delete(key)
    }
}

This structure can be easily extended to implement RefreshTokenRepository, cache repositories, distributed lock handlers, and more.

📌 Rethinking DB vs Redis: Role Separation

Let’s revisit the earlier DB connection pool discussion from the perspective of role separation:

RDB (MySQL, etc.)
- Handles mission-critical transactional data (orders, payments, user accounts)
- Requires strong consistency
- Best suited for normalized schema and complex queries

Redis
- Ultra-fast read/write, simple key-value structures
- Perfect for ephemeral or regeneratable data
- Ideal for cache, tokens, sessions, counters, and distributed locks

So the design principle becomes:

✔ DB stores “must-not-lose” core data
✔ Redis stores “fast, high-volume, lightweight” data

By separating responsibilities this way, the system remains stable even under high read traffic, and features like RefreshToken storage, session replacement, and distributed locks can be implemented flexibly on top of Redis.

📌 Conclusion

📌 Redis is an in-memory data store ideal for fast, lightweight operations that help reduce DB load.
📌 It supports a wide range of use cases: cache, token storage, session replacement, distributed locks, counters, etc.
📌 RedisConfig is placed in the Application module to be used naturally within the business logic layer.
📌 With StringRedisTemplate and RedisConnectionFactory, the infrastructure becomes ready for full-scale Redis utilization.

Ultimately, the goal is simple:
“Let the DB do what only the DB should do, and let Redis handle the rest.”
This separation ensures that as the service grows, the architecture remains scalable without major rewrites.

📌 Next Article Preview

Next — Validation Configuration
We will look at how to validate incoming request data using Bean Validation (@Valid), how validation ties naturally into the GlobalExceptionHandler, and how to structure clean request validation flows.

 

https://jaemoi8.tistory.com/43

 

Spring Boot(Kotlin) — ep.8 Validation Configuration

This post explains how to configure Validation in a Spring Boot multi-module backend project.Validation is applied from the moment you start creating DTOs, and automatically checks the data sent by the client before the controller method is executed.Since

jaemoi8.tistory.com