Skip to content

Commit 8587ba9

Browse files
committed
perf(chat): seek cached events by sequence
Signed-off-by: duanjialing.777 <duanjialing.777@bytedance.com>
1 parent 3e10aff commit 8587ba9

2 files changed

Lines changed: 40 additions & 3 deletions

File tree

‎loopx/chat_store.py‎

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
from __future__ import annotations
44

5+
from bisect import bisect_right
56
from datetime import datetime, timedelta, timezone
67
import json
78
import os
@@ -1319,14 +1320,19 @@ def events_after(self, session_id: str, turn_id: str, event_id: str | None) -> l
13191320
with self._event_lock:
13201321
cached = self._event_cache.get(key)
13211322
rows = (
1322-
list(cached)
1323+
cached
13231324
if cached is not None and self._event_cache_revision.get(key) == revision
13241325
else None
13251326
)
13261327
if rows is None:
13271328
with exclusive_file_lock(path, agent_id="loopx-chat", operation="read_chat_events"):
1328-
rows = list(self._event_rows_locked(session_id, turn_id))
1329-
return [row for row in rows if int(row.get("sequence") or 0) > after]
1329+
rows = self._event_rows_locked(session_id, turn_id)
1330+
start = bisect_right(
1331+
rows,
1332+
after,
1333+
key=lambda row: int(row.get("sequence") or 0),
1334+
)
1335+
return rows[start:]
13301336

13311337
def compact_completed_events(self, *, older_than_hours: float = 24.0) -> int:
13321338
"""Drop replay-only deltas after the durable final message is old enough."""

‎tests/test_chat_event_cursor.py‎

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
from __future__ import annotations
2+
3+
from pathlib import Path
4+
5+
from loopx.chat_store import ChatSessionStore
6+
7+
8+
def test_events_after_does_not_scan_the_cached_prefix(tmp_path: Path) -> None:
9+
class NonIterableRows(list[dict[str, object]]):
10+
def __iter__(self):
11+
raise AssertionError("events_after scanned the cached prefix")
12+
13+
store = ChatSessionStore(tmp_path)
14+
session_id = "session"
15+
turn_id = "turn"
16+
event_path = store._event_path(session_id, turn_id)
17+
event_path.parent.mkdir(parents=True)
18+
event_path.touch()
19+
key = (session_id, turn_id)
20+
store._event_cache[key] = NonIterableRows(
21+
[
22+
{"sequence": 1, "event_id": "1"},
23+
{"sequence": 4, "event_id": "4"},
24+
{"sequence": 7, "event_id": "7"},
25+
]
26+
)
27+
store._event_cache_revision[key] = store._event_revision(event_path)
28+
29+
assert store.events_after(session_id, turn_id, "4") == [
30+
{"sequence": 7, "event_id": "7"}
31+
]

0 commit comments

Comments
 (0)