kbdb: searchByTemplate 修 N+1(100 筆 record 逐筆 getRecord ≈ 19 秒 → 批次 IN 查詢 1.3 秒)
leo 客戶測試「圖譜/AI 查詢 20-30 秒」的病根:by-template 列表對每筆 record 各跑一次 getRecord(每筆一次 D1 往返)。改為 record id 以 90 一組(D1 綁定 參數上限 100)批次 IN 撈 slot 值,輸出排序與形狀不變。 實測 uncle6:/records/by-template/triplet 19s→1.3s、portal 圖譜 20s→3s、 總圖 1.8s。kbdb 測試 20/20 綠、tsc 0。
This commit is contained in:
@@ -181,10 +181,31 @@ export async function searchByTemplate(db: D1Database, template: string, owner_i
|
||||
.prepare(`SELECT DISTINCT record_id FROM entry_values WHERE template_id = ? ORDER BY created_at DESC LIMIT ?`)
|
||||
.bind(tpl.id, cap)
|
||||
.all<{ record_id: string }>();
|
||||
const out: RecordResult[] = [];
|
||||
for (const { record_id } of res.results ?? []) {
|
||||
const rec = await getRecord(db, record_id);
|
||||
if (rec) out.push(rec);
|
||||
// 批次撈齊所有 record 的 slot 值(2026-07-18 修 N+1:原本逐筆 getRecord=每筆 1 次 D1
|
||||
// 往返,100 筆 triplet ≈ 19 秒——graph/總圖/rag_chat 的「查詢 20-30 秒」病根就是這裡)。
|
||||
// D1 綁定參數上限 100 → id 以 90 一組分批 IN 查詢;輸出保持原排序(created_at DESC)。
|
||||
const ids = (res.results ?? []).map((r) => r.record_id);
|
||||
if (ids.length === 0) return [];
|
||||
const byId = new Map<string, RecordResult>();
|
||||
for (let i = 0; i < ids.length; i += 90) {
|
||||
const chunk = ids.slice(i, i + 90);
|
||||
const placeholders = chunk.map(() => '?').join(',');
|
||||
const evRes = await db
|
||||
.prepare(
|
||||
`SELECT ev.record_id as record_id, ev.slot_name as slot, e.content as content, ev.template_id as template_id
|
||||
FROM entry_values ev JOIN entries e ON ev.entry_id = e.id
|
||||
WHERE ev.record_id IN (${placeholders})`,
|
||||
)
|
||||
.bind(...chunk)
|
||||
.all<{ record_id: string; slot: string; content: string; template_id: string }>();
|
||||
for (const r of evRes.results ?? []) {
|
||||
let rec = byId.get(r.record_id);
|
||||
if (!rec) {
|
||||
rec = { record_id: r.record_id, template_id: r.template_id, values: {} };
|
||||
byId.set(r.record_id, rec);
|
||||
}
|
||||
rec.values[r.slot] = r.content;
|
||||
}
|
||||
}
|
||||
return out;
|
||||
return ids.map((id) => byId.get(id)).filter((r): r is RecordResult => !!r);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user