들어가면서
원래는 분산락까지 함께 다루려 했지만 TTI, TTL의 내용이 많아서 다음에 다루겠습니다.
서비스에서 데이터를 일정 시간 후 만료시키는 정책은 성능과 보안을 모두 고려할 때 매우 중요합니다.
대표적인 시간 기반 만료 정책에는 다음 두 가지가 있습니다.
- TTL (Time To Live) → 저장 시점부터 고정된 시간 후 자동 만료
- TTI (Time To Idle) → 마지막 접근 이후 일정 시간이 지나면 만료
Redis는 기본적으로 TTL 기반 만료를 제공했었습니다.
그러나 Redis 6.2 이상 버전부터는 GETEX 명령어를 통해 TTI(유휴 만료)를 직접 구현할 수 있습니다.
만약 6.2 미만 버전이라면, GET과 EXPIRE 명령어를 조합하여 동일한 효과를 낼 수 있습니다.
이번 글에서는 TTL과 TTI의 개념을 정리하고, Redis를 활용해 각 만료 방식을 구현하는 예제 코드를 함께 살펴보겠습니다.
TTL (Time To Live) 개념
TTL(Time To Live)은 데이터가 저장된 시점부터 일정 시간이 지나면 자동으로 만료되는 방식입니다.
즉, Redis에 데이터를 저장할 때 유효기간을 설정하면, 해당 시간이 지나면 Redis가 데이터를 자동으로 삭제합니다.
특징
- 절대 만료 (Absolute Expiration): 데이터 생성 시점을 기준으로 만료 시간이 고정됩니다. 이후 접근이 있더라도 만료 시간이 연장되지 않습니다.
- 간단하고 효율적: 만료 시간을 갱신하는 별도의 애플리케이션 로직이 필요 없어 구현이 간단하며 오버헤드가 적습니다.
- 보안 강화: OTP, 인증 코드처럼 반드시 일정 시간 후 삭제되어야 하는 데이터 관리에 유용합니다.
Redis에서 데이터 만료정책은 수동적 만료 (Passive Expiration), 능동적 만료 (Active Expiration)가 존재합니다.
- 수동적 만료: 조회시 만료된 키이면 수동으로 삭제합니다.
- 능동적 만료: 백그라운드에서 주기적으로 만료된 키들 중에서 무작위로 삭제를 진행합니다.
수동적, 능동적 만료 복합적으로 사용하는 이유는 수동적으로만 할 경우 접근하지 않으면 계속 메모리를 사용하고 있기 떄문에 2가지 방법을 사용하고 있습니다.
활용 예시
- OTP, 이메일 인증 코드, 쿠폰, 세션 등 고정 유효기간 데이터(5분 후 자동 삭제, 24시간 후 만료)
TTL 사용 예시
# Key: session:user1, Value: userId, 만료시간: 300초
SETEX session:user1 300 "userId" # 레디스 버전 2.0.0 ~ 2.6.2까지 사용권장(이후버전도 사용가능) 이후 버전은 SET 사용 권장
SET session:user1 "userId" EX 300 # SET 방식으로 동일한 동작
TTL session:user1 # 유효시간 조회 (-1: 만료기한 없는 키, -2: 키가 없는 경우)
// 데이터 저장
ValueOperations<String, Object> valueOperations = redisTemplate.opsForValue();
valueOperations.set( "session:user1", "userId", Duration.ofSeconds(300));
TTI (Time To Idle) 개념
TTI(Time To Idle)은 데이터가 마지막으로 접근된 이후 일정 시간이 지나면 자동으로 만료되는 방식입니다.
즉, 사용자가 데이터를 계속 조회하면 만료 시간이 갱신되어 활동이 있는 동안에는 데이터가 유지됩니다.
Redis는 기본적으로 TTL(절대 만료)만 제공하지만, Redis 6.2 이상부터는 GETEX 명령어를 통해 TTI를 구현할 수 있습니다.
만약 Redis 6.2 미만 버전을 사용 중이라면, GET과 EXPIRE 명령어를 조합하여 동일한 동작을 구현할 수 있습니다.
특징
- 유휴 만료(Idle Expiration): 마지막 접근 시점 기준으로 TTL을 갱신합니다.
- 활동 기반 유지: 주기적으로 접근이 발생하면 만료되지 않습니다.
- 로그인 세션 등 사용자 중심 데이터에 적합합니다.
- 직접 구현 필요: Redis는 TTL만 자동 제공하며, TTI는 명시적 만료 갱신(GETEX or EXPIRE)이 필요합니다.
활용 예시
- 로그인 세션, 관리자 페이지, API 토큰등 마지막 사용 후 유효기간이 갱신되는 경우
TTI 사용 예시
# Key: session:user1, Value: userId, 만료시간: 300초
SET session:user1 "userId" EX 300 # SET 방식으로 동일한 동작
# 조회시에도 만료시간을 갱신해야한다.
GETEX session:user1 EX 300
TTL session:user1 # 유효시간 조회 (-1: 만료기한 없는 키, -2: 키가 없는 경우)
// 처음 데이터 저장
ValueOperations<String, Object> valueOperations = redisTemplate.opsForValue();
valueOperations.set( "session:user1", "userId", Duration.ofSeconds(300));
// 조회시 데이터 만료기간 갱신
valueOperations.getAndExpire("session:user1",Duration.ofSeconds(300));
GETEX 명령어는 Redis 6.2 이상에서 지원되며, 기존의 GET + EXPIRE 두 명령을 하나로 묶은 원자적(atomic) 명령입니다.
즉, 값을 조회하면서 동시에 TTL을 재설정하므로, TTI 구현에 가장 적합합니다.
getAndExpire() 메서드는 내부적으로 Redis 6.2 이상 환경에서 GETEX 명령어를 호출하여 값을 조회하면서 만료 시간을 갱신합니다.
단, 주의할 점은 이 메서드는 String 타입 전용이며 Hash, List, Set 등의 자료구조에는 사용할 수 없습니다.
(Hash 구조에서는 HGET 후 expire()를 별도로 호출해야 합니다.)
TTL + TTI 혼합 방식
TTL은 절대 시간 기반으로 데이터를 저장한 시점부터 일정 시간이 지나면 자동으로 삭제됩니다.
반면 TTI는 마지막 접근 시점 기준으로 만료 시간을 갱신하는 방식입니다.
TTL만 적용하면 너무 짧게 설정한 경우 사용자가 활동 중에도 세션이 만료될 수 있고, 너무 길게 잡으면 보안적으로 취약해집니다.
반대로 TTI만 적용하면 사용자가 오랜 시간 계속 활동할 경우 세션이 무한히 연장되어 보안 측면에서 위험할 수 있습니다.
그래서 실무에서는 TTL은 길게, TTI는 짧게 설정하는 혼합 전략을 사용하여 보안 취약점을 최소화하면서 사용자 편의성도 함께 제공합니다.
예를 들어 TTL(절대 만료): 하루(24시간) TTI(유휴 만료): 30분 으로 설정하면,
활동이 없을 땐 30분 후 만료되고, 아무리 활동이 계속되어도 24시간 이후에는 자동 만료됩니다.
제가 사용하는 Keycloak(오픈소스 사용자/세션 관리 시스템)도 동일한 전략을 기본 세션 정책으로 채택하고 있습니다.
Keycloak에서는 SSO Session Idle, SSO Session Max의 설정으로 TTL + TTI 방식으로 사용할 수 있습니다.
SSO Session Idle → 유휴 시간 타임아웃 (TTI)
SSO Session Max → 최대 세션 수명 (TTL)
TTL + TTI 사용 예시
이 예제는 Hash 기반 세션에 대해 Java 코드를 통해 TTL+TTI 혼합 전략을 구현한 사례입니다.
순수 CLI만으로는 TTL + TTI 방식으로 구현할 수 없어서 애플리케이션 레이어에서 일부 로직을 구현해야 합니다.
이번 예제에서의 구현 방식입니다.
- 데이터 구조: 세션 정보를 Hash에 저장(여러 필드 관리 용이).
- TTI: Redis의 키 만료(EXPIRE)를 유휴 만료용으로 사용 — 조회 시 EXPIRE를 호출해 갱신.
- TTL: 세션의 최대 만료 시각을 Hash 내 필드로 저장하고, Java에서 읽어 비교하여 만료/갱신을 결정.
주의할 점이 있습니다.
HGET(또는 GET) → EXPIRE의 조합은 원자적이지 않아서 동시성 문제가 발생할 수 있습니다.
조회와 갱신 사이에 다른 클라이언트가 키를 삭제하거나 만료시키면 예외가 발생합니다.
그래서 원자성이 반드시 필요한 시나리오라면, Lua Script를 사용하여 HGET + EXPIRE + (필요 시 TTL 검사)를 Redis 서버 측에서 한 번에 실행해야합니다.
// 세션 생성 함수
public void createSession(){
HashOperations<String,String, Object> hashOperations = redisTemplate.opsForHash();
String sessionId = UUID.randomUUID().toString();
// TTL을 생성기준 1일 후로 설정
Long ONE_DAY = 24 * 60 * 60 * 1000L;
Map<String, Object> userInfo = Map.of("userId",userId,
"userName",userName,
"TTL",System.currentTimeMillis() + ONE_DAY);
hashOperations.putAll(sessionId,userInfo);
// TTI 설정
redisTemplate.expire(sessionId, Duration.ofMinutes(30));
}
// 세션 조회 함수
public Map<String, Object> getSession(String sessionId){
HashOperations<String,String, Object> hashOperations = redisTemplate.opsForHash();
Map<String, Object> userInfo = hashOperations.entries(sessionId);
// TTI 만료 확인
if(userInfo.isEmpty()){
return null;
}
// TTL 만료 확인
if(System.currentTimeMillis() > Long.valueOf(userInfo.get("TTL").toString())){
// TTI가 만료한 케이스면 Key 삭제
redisTemplate.delete(sessionId);
return null;
}
// TTI 갱신
redisTemplate.expire(sessionId, Duration.ofMinutes(30));
return userInfo;
}
마무리하면서
Redis의 다양한 활용 방법을 고민하던 중, 이번에는 데이터 만료 정책(TTL, TTI) 에 대해 깊이 있게 공부하게 되었습니다.
특히 TTL과 TTI는 단순한 데이터 만료 이상의 의미를 가집니다.
보안, 성능, 사용자 경험까지 균형 있게 관리하려면 이 두 개념을 함께 고려해야 합니다.
제가 사용하는 Keycloak 같은 오픈소스 시스템에서도 TTI + TTL 방식으로 세션을 관리된다는 점에서 실제 서비스에서 사용되는 실용적인 개념이라고 느끼게 되었습니다.
다음에는 Redis를 활용한 분산락 구현하여 동시성 제어 패턴을 직접 구현해보겠습니다.
'Redis' 카테고리의 다른 글
| Redis 분산락 정리(직접 구현 vs Redisson 비교) (0) | 2025.11.02 |
|---|---|
| Spring RedisTemplate 자료구조별 사용법과 CLI 정리 (0) | 2025.09.29 |