perf: opening a chat no longer loads the whole conversation to mark it read

Every time a chat is opened, the server marks it as read, and to do so it loaded and parsed the full chat, images included, just to save the last-read time. Marking a chat as unread and removing a tag from a chat did the same. These now touch only the few chat fields they change, which makes them 4 to 6 times faster on large chats.

Measured on SQLite, best of 7 runs, sizes are the chat as stored in the database:

| Chat | Action | Before | After |
| --- | --- | --- | --- |
| 7 MB, text only | open (mark read) | 19.8 ms | 3.5 ms |
| 7 MB, text only | mark as unread | 20.7 ms | 3.5 ms |
| 7 MB, text only | remove tag | 21.2 ms | 3.4 ms |
| 49 MB, with images | open (mark read) | 122.2 ms | 30.0 ms |
| 49 MB, with images | mark as unread | 124.2 ms | 29.1 ms |
| 49 MB, with images | remove tag | 120.5 ms | 28.6 ms |
This commit is contained in:
Classic298 2026-11-03 15:21:27 +01:00
parent 8957ac1397
commit 2a8dfd6994

View file

@ -942,14 +942,17 @@ class ChatTable:
) -> tuple[int, bool] | None:
try:
async with get_async_db_context(db) as session:
chat = await session.get(Chat, id)
if chat and chat.user_id == user_id:
last_read_at = int(time.time())
was_unread = chat.last_read_at is None or chat.updated_at > chat.last_read_at
chat.last_read_at = last_read_at
await session.commit()
return last_read_at, was_unread
return None
row = (
await session.execute(select(Chat.updated_at, Chat.last_read_at).filter_by(id=id, user_id=user_id))
).one_or_none()
if row is None:
return None
last_read_at = int(time.time())
was_unread = row.last_read_at is None or row.updated_at > row.last_read_at
await session.execute(update(Chat).filter_by(id=id).values(last_read_at=last_read_at))
await session.commit()
return last_read_at, was_unread
except Exception:
return None
@ -958,18 +961,23 @@ class ChatTable:
) -> ChatTitleIdResponse | None:
try:
async with get_async_db_context(db) as session:
chat = await session.get(Chat, id)
if chat and chat.user_id == user_id:
chat.last_read_at = 0
await session.commit()
return ChatTitleIdResponse(
id=chat.id,
title=chat.title,
updated_at=chat.updated_at,
created_at=chat.created_at,
last_read_at=chat.last_read_at,
row = (
await session.execute(
select(Chat.title, Chat.updated_at, Chat.created_at).filter_by(id=id, user_id=user_id)
)
return None
).one_or_none()
if row is None:
return None
await session.execute(update(Chat).filter_by(id=id).values(last_read_at=0))
await session.commit()
return ChatTitleIdResponse(
id=id,
title=row.title,
updated_at=row.updated_at,
created_at=row.created_at,
last_read_at=0,
)
except Exception:
return None
@ -2796,15 +2804,16 @@ class ChatTable:
) -> bool:
try:
async with get_async_db_context(db) as session:
chat = await session.get(Chat, id)
tags = chat.meta.get('tags', [])
row = (await session.execute(select(Chat.meta).filter_by(id=id))).one_or_none()
if row is None:
return False
meta = row[0]
tags = meta.get('tags', [])
tag_id = tag_name.replace(' ', '_').lower()
tags = [tag for tag in tags if tag != tag_id]
chat.meta = {
**chat.meta,
'tags': list(set(tags)),
}
await session.execute(update(Chat).filter_by(id=id).values(meta={**meta, 'tags': list(set(tags))}))
await session.commit()
return True
except Exception: