perf(state): a display read hashes only rows a removed copy could match

RT-39 live pass: a warm session.resume of a 30k-row session ran ~12% slower
than main (real tui_gateway over a WS, in-process uvicorn: 1.837 s vs 1.634 s
median, 8 warm runs each, alternating). The composed tree matched this branch
(1.822 s), so #140971 owns the regression.

The cause is the warm path's display read (get_messages_as_conversation with
include_compacted), which went from 0.30 s to 0.47 s. Since 7e31a90112,
_dedupe_display_generations(drop_removed=True) computed a sha256 of
repr(display key) for EVERY live row, to compare against the removed rows'
stored identities. That is 30k repr+sha256 calls per resume, plus the
allocation churn behind them. The second lineage query and the sort were noise.

Every display key carries the row's timestamp, so a live row can share a
removed row's identity only at one of the removed rows' timestamps. Now:
- no removed row in the lineage: no hashing at all;
- otherwise only live rows at a removed row's timestamp are hashed. The
  message_uid / absorbed-uid copies are still compared for every row.

Round 2's behavior is unchanged: removed rows are still read for identity
only (no bodies), archived-before-live ordering holds, and a delete during
resume still answers not_found.

The guard (tests/hermes_state/test_display_read_identity_work.py) counts
_display_identity calls instead of timing anything. A 400-row session with no
removed row: 0 hashes (it was 400 on 0bf1ed99ce). With a two-row rewind:
at most 2.

Live-pass finding on #140971 (package 10)
Refs #134889 (RT-39)
Refs #140719
This commit is contained in:
Teknium committed 2026-10-18 06:50:07 -07:00
1 parent 0bf1ed99ce
commit c6961c62af
2 files changed
+76 -1

No files matched your search

+11 -1
View File
@@ -1141,6 +1141,14 @@ class SessionMessagesMixin(SessionDetachedResultsMixin, SessionTodoResultMixin):
first_id: dict[tuple[Any, ...], int] = {}
removed: dict[Any, bool] = {} # copy identity -> its newest copy is removed (rows come in id order)
identities: dict[tuple[Any, ...], list[Any]] = {}
removed_ts: set[Any] = set()
if drop_removed:
rows = rows if isinstance(rows, list) else list(rows)
# Every display key carries the row's timestamp third, so a live row can share a removed row's identity
# only at one of the removed rows' timestamps; hash just those (a sha256 per live row cost a warm
# 30k-row resume ~0.17 s, RT-39). No removed row at all: nothing can hide a copy.
removed_ts = {row["timestamp"] for row in rows if not row["active"] and not row["compacted"]}
drop_removed = bool(removed_ts)
for row in rows:
copies, key = None, None
if drop_removed:
@@ -1148,7 +1156,9 @@ class SessionMessagesMixin(SessionDetachedResultsMixin, SessionTodoResultMixin):
gone = not row["active"] and not row["compacted"]
stored = row["display_identity"] if gone else None
key = None if stored else self._display_dedupe_key(row)
copies = [stored or self._display_identity(key), *(
hashed = stored or (self._display_identity(key) if key is not None and (
gone or row["timestamp"] in removed_ts) else None)
copies = [*((hashed,) if hashed else ()), *(
("uid", uid) for uid in (row["message_uid"], *_uid_list(row["absorbed_message_uids"])) if uid)]
removed.update(dict.fromkeys(copies, gone))
if gone:
@@ -0,0 +1,65 @@
"""A display read hashes a display identity only where a removed copy could match it (RT-39).
``drop_removed`` hides a message whose newest copy was removed. Hashing every live row's display key to
compare against the removed rows cost a warm 30k-row resume ~0.17 s (+12% end to end), though a live row
can only share a removed row's key at one of the removed rows' timestamps. The guard counts hashes, not
seconds. Real SessionDB, real rewind.
"""
from hermes_state import SessionDB
LIVE_TURNS = 200
def _session(db, *, rewind):
db.create_session('s', source='test')
msgs = []
for i in range(LIVE_TURNS):
msgs += [{'role': 'user', 'content': f'U{i}', 'timestamp': 1_000_000 + 2 * i},
{'role': 'assistant', 'content': f'A{i}', 'timestamp': 1_000_001 + 2 * i}]
db.append_messages_batch('s', msgs)
if rewind:
db.append_messages_batch('s', [{'role': 'user', 'content': 'GONE', 'timestamp': 2_000_000},
{'role': 'assistant', 'content': 'NO', 'timestamp': 2_000_001}])
db.rewind_to_message('s', next(m['id'] for m in db.get_messages('s') if m['content'] == 'GONE'))
def _hashes(db, monkeypatch, read):
calls = []
real = SessionDB._display_identity
def spy(key):
calls.append(key)
return real(key)
monkeypatch.setattr(SessionDB, '_display_identity', staticmethod(spy))
shown = read()
monkeypatch.undo()
return shown, len(calls)
def _reads(db):
return (lambda: db.get_messages_as_conversation('s', include_ancestors=True, include_compacted=True),
lambda: db.get_resume_conversations('s')[1],
lambda: db.get_messages_as_conversation('s', include_compacted=True))
def test_a_display_read_with_no_removed_row_hashes_nothing(tmp_path, monkeypatch):
with SessionDB(db_path=tmp_path / 'state.db') as db:
_session(db, rewind=False)
for read in _reads(db):
shown, hashes = _hashes(db, monkeypatch, read)
assert len(shown) == 2 * LIVE_TURNS
assert hashes == 0
def test_a_display_read_hashes_only_rows_at_a_removed_rows_timestamp(tmp_path, monkeypatch):
with SessionDB(db_path=tmp_path / 'state.db') as db:
_session(db, rewind=True)
removed = db._read_one('SELECT COUNT(*) FROM messages WHERE active = 0 AND compacted = 0')[0]
assert removed == 2
for read in _reads(db):
shown, hashes = _hashes(db, monkeypatch, read)
assert [m['content'] for m in shown][-2:] == [f'U{LIVE_TURNS - 1}', f'A{LIVE_TURNS - 1}']
assert 'GONE' not in [m['content'] for m in shown]
# At most the removed rows themselves (a legacy row without a stored identity); no live row shares
# their timestamps here.
assert hashes <= removed, hashes