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 + 24hexpire_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_sessionsetex를 안 부르니까.

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시간으로 리셋되고, 유저가 계속 대화하면 세션이 사실상 무한히 유지된다. 편의점 물품보관함처럼 “손대면 타이머 재시작” 구조.