1. 정의
슬라이딩 윈도우(Sliding Window, 미끄러지는 창) = “시간 측정 기준점이 사용자 활동마다 앞으로 끌려가는 방식”.
한마디로 **“마지막으로 뭔가 한 순간부터 X시간”**을 세는 방식. 유저가 계속 활동하면 만료 시점이 계속 뒤로 밀린다.
반대는 고정 윈도우(Fixed Window). 처음 생성된 시점부터 X시간 뒤에 무조건 만료. 유저 활동과 무관.
2. 내 코드가 슬라이딩 윈도우인 이유
[src/db/redis_manager.py:143-161] 흐름:
python
def save_session(self, player_id, npc_id, session_data):
key = self._get_session_key(player_id, npc_id)
session_data["last_active_at"] = datetime.now().isoformat()
# TTL과 함께 저장 (24시간 후 자동 삭제)
self.client.setex(
key, SESSION_TTL, json.dumps(session_data, ensure_ascii=False)
)[src/db/redis_manager.py:163-189]의 update_session:
python
def update_session(self, player_id, npc_id, updates):
session = self.load_session(...)
...
# 저장
self.save_session(player_id, npc_id, session) # ← 매번 setex 재호출
return session핵심 메커니즘: setex는 “이미 있는 키의 TTL도 덮어쓰는” 명령. 따라서:
- 유저 대화 1회 =
update_session호출 =setex호출 = TTL 86400초로 리셋 - 남아 있던 게 12시간이든 3시간이든 전부 24시간으로 초기화
이게 슬라이딩 윈도우 정의에 정확히 부합하는 동작.
3. 나란히 비교: 고정 윈도우 vs 슬라이딩 윈도우
같은 “24시간”이어도 결과는 완전히 다름.
| 관점 | 고정 윈도우 | 슬라이딩 윈도우 |
|---|---|---|
| 기준점 | 세션 생성 시각 | 마지막 활동 시각 |
| 유저가 활동하면? | 영향 없음 | 만료 뒤로 연장 |
| 구현 방식 | SET key value + EXPIRE key 86400 한 번만 | 매 저장마다 SETEX key 86400 value |
| Redis 명령 | EXPIRE 1회 | SETEX 반복 |
| 수학적 모델 | expire_at = created_at + 24h | expire_at = last_active_at + 24h |
4. 일상 비유: 넷플릭스 vs 물품보관함
고정 윈도우 = 넷플릭스 한 달 구독
- 7월 1일 결제 → 무조건 7월 31일 만료
- 영화를 100편 보든 0편 보든 만료일은 절대 안 밀림
슬라이딩 윈도우 = 편의점 물품보관함
- 물건 넣음 → 24시간 타이머 시작
- 중간에 열어서 뭔가 추가 → 타이머 다시 24시간으로 리셋
- 계속 사용하는 한 물건은 영원히 안 사라짐
내 코드는 후자. 유저가 계속 대화하면 세션은 영원히 유지되고, 손을 떼는 순간부터 24시간 카운트다운이 시작.
5. 타임라인 시각화: 하루 동안의 유저 흐름
고정 윈도우였다면:
0시 9시 18시(저녁 식사) 24시
│────────│────────────│────────────────────────│
로그인 대화 대화 *만료*
(TTL 24h) 세션 증발
슬라이딩 윈도우 (실제 코드 동작):
0시 9시 18시 (+24h from 18시)=익일 18시
│────────│────────────│──────────────────────────│
로그인 대화 대화(마지막) *만료*
TTL=24h TTL 리셋→24h TTL 리셋→24h 세션 증발
유저가 18시에 마지막 대화를 하면 만료 시점은 0시+24h=24시가 아니라 18시+24h=익일 18시로 밀림. 실질적 세션 수명 42시간.
6. 왜 슬라이딩 윈도우를 선택했나 (설계 타당성)
장점
- 유저 체감 자연스러움: “방금 전까지 대화했는데 왜 맥락이 사라지지?” 같은 불만 방지
- 활성 유저 우대: 자주 접속하는 유저일수록 세션이 오래 유지돼 개인화 경험이 축적됨
- 구현 간결성:
SETEX반복 호출만으로 자동 달성. 별도 로직 불필요
단점 (감수한 Trade-off)
- 영원히 만료 안 될 위험: 24시간 내내 1분마다 대화하는 유저가 있으면 세션이 몇 년이고 유지됨 → 메모리 누수 위험
- 대책: [src/db/redis_manager.py:229]에
is_session_expired(session_data, hours=1)메소드가 따로 있음. 애플리케이션 레벨에서 “1시간 비활동 시 대화 맥락 리셋” 같은 추가 정책 구성 가능 (Redis TTL보다 빡빡한 내부 기준)
대안: 하이브리드 윈도우 (고려 가능)
- 최대 수명 상한 + 슬라이딩 조합
- 예: “마지막 활동 후 24시간” AND “최초 생성 후 최대 7일”
- Redis 단일
SETEX로는 구현 불가, 별도created_at비교 로직 필요 - 과금 시스템이나 법적 로그 보관 요구사항이 있을 때 주로 씀
7. 어디에 또 쓰이는가
슬라이딩 윈도우는 세션 관리 외에도 자주 등장.
| 용도 | 예시 |
|---|---|
| 웹 로그인 세션 | 대부분의 웹사이트. 활동 중엔 안 끊김 |
| Rate Limiting (요청 제한) | “최근 1분 내 10회 이하 요청”. 창이 계속 미끄러짐 |
| 캐시 정책(LRU) | “최근 덜 쓴 것부터 버림”. 비슷한 철학 |
| OAuth 토큰 갱신 | refresh 토큰 사용 시 access 토큰 수명 갱신 |
8. 직접 확인
로컬 Redis에서 TTL이 실제로 리셋되는지 눈으로 볼 수 있음.
bash
docker exec -it game_redis redis-cli
# 1단계: 10초짜리 키 생성
127.0.0.1:6379> SETEX test_key 10 "hello"
OK
# 2단계: 3초 대기 후 TTL 확인
127.0.0.1:6379> TTL test_key
(integer) 7 ← 7초 남음
# 3단계: 같은 키에 SETEX 재호출 (수명 20초로)
127.0.0.1:6379> SETEX test_key 20 "hello updated"
OK
# 4단계: TTL 다시 확인
127.0.0.1:6379> TTL test_key
(integer) 20 ← 다시 20초로 리셋 (7초 + 20초 = 27초가 아님)핵심 증거: 7 + 20 = 27이 아니라 덮어쓰기로 20. 이게 내 코드에서 대화할 때마다 발생하는 일.
9. 주의: 슬라이딩 윈도우의 함정
update_session이 아닌 load_session만 호출하면 TTL은 리셋되지 않음. load_session은 setex를 안 부르니까.
python
# 단순 조회. TTL 영향 없음
session = manager.load_session(1, 1)
# 조회 + 저장. TTL 리셋
session = manager.update_session(1, 1, {"turn_count": 5})Redis 표준 대안으로 EXPIRE 명령만 따로 호출해서 “값은 그대로, TTL만 갱신”하는 패턴도 있음. 내 코드는 “저장이 곧 TTL 갱신” 단순 구조라 이런 구분이 없음. 가독성 면에서는 이 단순 구조가 더 낫다.
한마디 요약
슬라이딩 윈도우 = “마지막 활동 시각부터 X시간을 세는 방식”. 내 코드는 세션을 저장할 때마다
setex(key, 86400, ...)를 호출하므로 TTL이 매번 24시간으로 리셋되고, 유저가 계속 대화하면 세션이 사실상 무한히 유지된다. 편의점 물품보관함처럼 “손대면 타이머 재시작” 구조.