Skip to content

Commit 126a3d0

Browse files
author
dori
committed
fix: Clean old session if needed - check is done on create new session
1 parent ae7ca12 commit 126a3d0

File tree

5 files changed

+130
-180
lines changed

5 files changed

+130
-180
lines changed

src/mcp_as_a_judge/constants.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,3 @@
1515
DATABASE_URL = "sqlite://:memory:"
1616
MAX_SESSION_RECORDS = 20 # Maximum records to keep per session (FIFO)
1717
MAX_TOTAL_SESSIONS = 50 # Maximum total sessions to keep (LRU cleanup)
18-
RECORD_RETENTION_DAYS = 10 # Optional time-based cleanup (fallback)

src/mcp_as_a_judge/db/cleanup_service.py

Lines changed: 12 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
"""
22
Database cleanup service for conversation history records.
33
4-
This service handles time-based cleanup operations for conversation history records,
5-
removing records older than the retention period (default: 1 day).
4+
This service handles LRU-based cleanup operations for conversation history records,
5+
removing least recently used sessions when session limits are exceeded.
66
"""
77

8-
from datetime import UTC, datetime, timedelta
8+
from datetime import UTC, datetime
99

1010
from sqlalchemy import Engine, func
1111
from sqlmodel import Session, select
1212

13-
from mcp_as_a_judge.constants import MAX_TOTAL_SESSIONS, RECORD_RETENTION_DAYS
13+
from mcp_as_a_judge.constants import MAX_TOTAL_SESSIONS
1414
from mcp_as_a_judge.db.interface import ConversationRecord
1515
from mcp_as_a_judge.logging_config import get_logger
1616

@@ -20,11 +20,11 @@
2020

2121
class ConversationCleanupService:
2222
"""
23-
Service for cleaning up old conversation history records.
23+
Service for cleaning up conversation history records.
2424
2525
Implements session-based LRU cleanup strategy:
26-
- Maintains max 2000 sessions by removing least recently used sessions
27-
- Runs once per day to avoid performance overhead
26+
- Maintains session limit by removing least recently used sessions
27+
- Runs immediately when new sessions are created and limit is exceeded
2828
2929
LRU vs FIFO for Better User Experience:
3030
- LRU (Least Recently Used): Keeps sessions that users are actively using,
@@ -45,53 +45,8 @@ def __init__(self, engine: Engine) -> None:
4545
"""
4646
self.engine = engine
4747
self.max_total_sessions = MAX_TOTAL_SESSIONS
48-
self.retention_days = RECORD_RETENTION_DAYS
49-
self.last_cleanup_time = datetime.now(UTC)
50-
self.last_session_cleanup_time = datetime.now(UTC)
5148

52-
def cleanup_old_records(self) -> int:
53-
"""
54-
Remove records older than retention_days.
55-
This runs once per day to avoid excessive cleanup operations.
56-
57-
Returns:
58-
Number of records deleted
59-
"""
60-
# Only run cleanup once per day
61-
if (datetime.now(UTC) - self.last_cleanup_time).days < 1:
62-
return 0
63-
64-
cutoff_date = datetime.now(UTC) - timedelta(days=self.retention_days)
65-
66-
with Session(self.engine) as session:
67-
# Count old records
68-
old_count_stmt = select(ConversationRecord).where(
69-
ConversationRecord.timestamp < cutoff_date
70-
)
71-
old_records = session.exec(old_count_stmt).all()
72-
old_count = len(old_records)
73-
74-
if old_count == 0:
75-
logger.info(
76-
f"🧹 Daily cleanup: No records older than {self.retention_days} days"
77-
)
78-
self.last_cleanup_time = datetime.now(UTC)
79-
return 0
80-
81-
# Delete old records
82-
for record in old_records:
83-
session.delete(record)
8449

85-
session.commit()
86-
87-
# Reset cleanup tracking
88-
self.last_cleanup_time = datetime.now(UTC)
89-
90-
logger.info(
91-
f"🧹 Daily cleanup: Deleted {old_count} records older than "
92-
f"{self.retention_days} days"
93-
)
94-
return old_count
9550

9651
def get_session_count(self) -> int:
9752
"""
@@ -178,34 +133,27 @@ def cleanup_excess_sessions(self) -> int:
178133
179134
This implements LRU (Least Recently Used) cleanup strategy:
180135
- Keeps sessions that users are actively using (better UX than FIFO)
181-
- Only runs once per day to avoid excessive cleanup operations
182-
- During the day, session count can exceed limit
183-
(e.g., 5000 sessions is not a memory issue)
184-
- Daily cleanup brings it back to the target limit (2000 sessions)
136+
- Runs immediately when session limit is exceeded (no daily restriction)
185137
- Removes entire sessions (all records for those session_ids)
138+
- Called every time a new session is created to maintain session limit
186139
187140
Returns:
188141
Number of records deleted
189142
"""
190-
# Only run session cleanup once per day
191-
if (datetime.now(UTC) - self.last_session_cleanup_time).days < 1:
192-
return 0
193-
194143
current_session_count = self.get_session_count()
195144

196145
if current_session_count <= self.max_total_sessions:
197146
logger.info(
198-
f"🧹 Daily session LRU cleanup: {current_session_count} sessions "
147+
f"🧹 Session LRU cleanup: {current_session_count} sessions "
199148
f"(max: {self.max_total_sessions}) - no cleanup needed"
200149
)
201-
self.last_session_cleanup_time = datetime.now(UTC)
202150
return 0
203151

204152
# Calculate how many sessions to remove
205153
sessions_to_remove = current_session_count - self.max_total_sessions
206154

207155
logger.info(
208-
f"🧹 Daily session LRU cleanup: {current_session_count} sessions exceeds limit "
156+
f"🧹 Session LRU cleanup: {current_session_count} sessions exceeds limit "
209157
f"({self.max_total_sessions}), removing {sessions_to_remove} "
210158
f"least recently used sessions"
211159
)
@@ -215,17 +163,13 @@ def cleanup_excess_sessions(self) -> int:
215163

216164
if not lru_session_ids:
217165
logger.warning("🧹 No sessions found for LRU cleanup")
218-
self.last_session_cleanup_time = datetime.now(UTC)
219166
return 0
220167

221168
# Delete all records for these sessions
222169
deleted_count = self.delete_sessions(lru_session_ids)
223170

224-
# Reset cleanup tracking
225-
self.last_session_cleanup_time = datetime.now(UTC)
226-
227171
logger.info(
228-
f"✅ Daily session LRU cleanup completed: removed {sessions_to_remove} sessions, "
172+
f"✅ Session LRU cleanup completed: removed {sessions_to_remove} sessions, "
229173
f"deleted {deleted_count} records"
230174
)
231175

src/mcp_as_a_judge/db/db_config.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
DATABASE_URL,
1010
MAX_SESSION_RECORDS,
1111
MAX_TOTAL_SESSIONS,
12-
RECORD_RETENTION_DAYS,
1312
)
1413

1514

@@ -63,7 +62,6 @@ def __init__(self) -> None:
6362
self.url = DATABASE_URL
6463
self.max_session_records = MAX_SESSION_RECORDS
6564
self.max_total_sessions = MAX_TOTAL_SESSIONS
66-
self.record_retention_days = RECORD_RETENTION_DAYS
6765

6866

6967
class Config:

src/mcp_as_a_judge/db/providers/sqlite_provider.py

Lines changed: 23 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ class SQLiteProvider(ConversationHistoryDB):
3030
- SQLModel with SQLAlchemy for type safety
3131
- In-memory or file-based SQLite storage
3232
- Two-level cleanup strategy:
33-
1. Daily session-based LRU cleanup (max 2000 sessions, removes least recently used)
33+
1. Session-based LRU cleanup (runs when new sessions are created, removes least recently used)
3434
2. Per-session FIFO cleanup (max 20 records per session, runs on every save)
3535
- Session-based conversation retrieval
3636
"""
@@ -51,7 +51,7 @@ def __init__(self, max_session_records: int = 20, url: str = "") -> None:
5151

5252
self._max_session_records = max_session_records
5353

54-
# Initialize cleanup service for time-based cleanup
54+
# Initialize cleanup service for LRU session cleanup
5555
self._cleanup_service = ConversationCleanupService(engine=self.engine)
5656

5757
# Create tables
@@ -60,8 +60,7 @@ def __init__(self, max_session_records: int = 20, url: str = "") -> None:
6060
logger.info(
6161
f"🗄️ SQLModel SQLite provider initialized: {connection_string}, "
6262
f"max_records_per_session={max_session_records}, "
63-
f"max_total_sessions={self._cleanup_service.max_total_sessions}, "
64-
f"retention_days={self._cleanup_service.retention_days}"
63+
f"max_total_sessions={self._cleanup_service.max_total_sessions}"
6564
)
6665

6766
def _parse_sqlite_url(self, url: str) -> str:
@@ -82,18 +81,13 @@ def _create_tables(self) -> None:
8281
SQLModel.metadata.create_all(self.engine)
8382
logger.info("📋 Created conversation_history table with SQLModel")
8483

85-
def _cleanup_old_records(self) -> int:
86-
"""
87-
Remove records older than retention_days using the cleanup service.
88-
This runs once per day to avoid excessive cleanup operations.
89-
"""
90-
return self._cleanup_service.cleanup_old_records()
84+
9185

9286
def _cleanup_excess_sessions(self) -> int:
9387
"""
9488
Remove least recently used sessions when total sessions exceed limit.
95-
This implements daily LRU cleanup to maintain max 2000 sessions for better memory management.
96-
Runs once per day - during the day session count can exceed limit without issues.
89+
This implements LRU cleanup to maintain session limit for better memory management.
90+
Runs immediately when new sessions are created and limit is exceeded.
9791
"""
9892
return self._cleanup_service.cleanup_excess_sessions()
9993

@@ -147,6 +141,16 @@ def _cleanup_old_messages(self, session_id: str) -> int:
147141
)
148142
return len(old_records)
149143

144+
def _is_new_session(self, session_id: str) -> bool:
145+
"""Check if this is a new session (no existing records)."""
146+
with Session(self.engine) as session:
147+
existing_record = session.exec(
148+
select(ConversationRecord).where(
149+
ConversationRecord.session_id == session_id
150+
).limit(1)
151+
).first()
152+
return existing_record is None
153+
150154
async def save_conversation(
151155
self, session_id: str, source: str, input_data: str, output: str
152156
) -> str:
@@ -159,6 +163,9 @@ async def save_conversation(
159163
f"for session {session_id}, source {source} at {timestamp}"
160164
)
161165

166+
# Check if this is a new session before saving
167+
is_new_session = self._is_new_session(session_id)
168+
162169
# Create new record
163170
record = ConversationRecord(
164171
id=record_id,
@@ -175,8 +182,10 @@ async def save_conversation(
175182

176183
logger.info("✅ Successfully inserted record into conversation_history table")
177184

178-
# Daily session LRU cleanup: maintain max 2000 sessions (runs once per day)
179-
self._cleanup_excess_sessions()
185+
# Session LRU cleanup: only run when a new session is created
186+
if is_new_session:
187+
logger.info(f"🆕 New session detected: {session_id}, running LRU cleanup")
188+
self._cleanup_excess_sessions()
180189

181190
# Per-session FIFO cleanup: maintain max 20 records per session (runs on every save)
182191
self._cleanup_old_messages(session_id)

0 commit comments

Comments
 (0)