perf(storage): get_topic_message_count 改用 COUNT(*) 查询

原实现调用 load_messages_for_topic 加载该 topic 的所有消息
(含 content、tool_calls_json 等大字段反序列化)到内存,
仅为调用 .len() 计数。长会话可能加载数千条消息,且在
list_sessions 列出所有 topic 时会按 topic 数量倍增。

改为 SELECT COUNT(*) FROM messages WHERE topic_id = ?1,
在数据库侧计数:无需反序列化任何字段,命中已有的
idx_messages_topic_seq(topic_id, seq) 索引。

对 N 条消息的 topic,从 O(N) 内存 + 反序列化降为 O(1)
内存 + 索引扫描。新增测试覆盖计数正确性与隔离性。
This commit is contained in:
oudecheng 2026-08-04 15:18:21 +08:00
parent 924017fe7b
commit 07247ed140
2 changed files with 50 additions and 3 deletions

View File

@ -1581,10 +1581,19 @@ impl SessionStore {
} }
} }
/// 获取指定话题的消息数量(动态计算,确保准确) /// 获取指定话题的消息数量。
///
/// 使用 `SELECT COUNT(*)` 在数据库侧计数,避免将所有消息
/// (含 content、tool_calls_json 等大字段反序列化)加载到内存。
/// 查询命中 `idx_messages_topic_seq(topic_id, seq)` 索引。
pub fn get_topic_message_count(&self, topic_id: &str) -> Result<usize, StorageError> { pub fn get_topic_message_count(&self, topic_id: &str) -> Result<usize, StorageError> {
self.load_messages_for_topic(topic_id, None) let conn = self.pool.get()?;
.map(|msgs| msgs.len()) let count: i64 = conn.query_row(
"SELECT COUNT(*) FROM messages WHERE topic_id = ?1",
params![topic_id],
|row| row.get(0),
)?;
Ok(count as usize)
} }
pub fn load_all_messages(&self, session_id: &str) -> Result<Vec<ChatMessage>, StorageError> { pub fn load_all_messages(&self, session_id: &str) -> Result<Vec<ChatMessage>, StorageError> {

View File

@ -687,3 +687,41 @@ fn test_scheduler_job_roundtrip_and_runtime_update() {
assert_eq!(fetched.run_count, 1); assert_eq!(fetched.run_count, 1);
assert_eq!(fetched.completed_at, Some(1_700_000_000_100)); assert_eq!(fetched.completed_at, Some(1_700_000_000_100));
} }
#[test]
fn test_get_topic_message_count_uses_count_query() {
let store = SessionStore::in_memory().unwrap();
let session = store.create_cli_session(Some("topic-count")).unwrap();
let topic = store
.create_topic(&session.id, "topic-1", None)
.unwrap();
// 初始计数为 0
assert_eq!(store.get_topic_message_count(&topic.id).unwrap(), 0);
// 追加 3 条带 topic_id 的消息
for content in ["m1", "m2", "m3"] {
store
.append_message_with_topic(
&session.id,
Some(&topic.id),
&ChatMessage::user(content),
)
.unwrap();
}
// 计数应为 3且不需要加载消息内容
assert_eq!(store.get_topic_message_count(&topic.id).unwrap(), 3);
// 另一个 topic 的计数应为 0隔离验证
let other_topic = store
.create_topic(&session.id, "topic-2", None)
.unwrap();
assert_eq!(store.get_topic_message_count(&other_topic.id).unwrap(), 0);
// 不存在的 topic_id 返回 0
assert_eq!(
store.get_topic_message_count("topic:nonexistent").unwrap(),
0
);
}