https://chatglm.cn/main/alltoolsdetail?t1783048760261langzhcid6a4729d82e7bd8bd2e93725fedge我发现我仅仅是运行这一部分代码都会报错# scripts/test_chroma_query.py“”“隔离测试直接查 ChromaDB复现段错误”“”import chromadbfrom chromadb.config import Settingsimport sysCHROMA_PATH “/home/shaolingxuan/project/FlexSearch/search_cache_db/chroma”client chromadb.PersistentClient(pathCHROMA_PATH,settingsSettings(anonymized_telemetryFalse),)问题列出所有 collectionprint(“ 所有 collections ”)for c in client.list_collections():print(f {c.name}: count{c.count()})(flexsearch) shaolingxuank8s-master:~/project/FlexSearch/results/GoogleSearch_qwen-3.5-9b_seal0/回溯9-波动性控制/运行代码$ ls -la /home/shaolingxuan/project/FlexSearch/search_cache_db/chroma/total 20180drwxrwxr-x 3 shaolingxuan shaolingxuan 4096 Jul 3 11:34 .drwxrwxr-x 11 shaolingxuan shaolingxuan 4096 Jul 3 11:25 …drwxrwxr-x 2 shaolingxuan shaolingxuan 4096 Jun 24 21:45 4502f8c4-d9c6-4af1-b3c9-573b755c8fb9-rw-r–r-- 1 shaolingxuan shaolingxuan 20647936 Jul 3 11:34 chroma.sqlite3(flexsearch) shaolingxuank8s-master:~/project/FlexSearch/results/GoogleSearch_qwen-3.5-9b_seal0/回溯9-波动性控制/运行代码$ 码$ du -sh /home/shaolingxuan/project/FlexSearch/search_cache_db/chroma/*48M /home/shaolingxuan/project/FlexSearch/search_cache_db/chroma/4502f8c4-d9c6-4af1-b3c9-573b755c8fb920M /home/shaolingxuan/project/FlexSearch/search_cache_db/chroma/chroma.sqlite3(flexsearch) shaolingxuank8s-master:~/project/FlexSearch/results/GoogleSearch_qwen-3.5-9b_seal0/回溯9-波动性控制/运行代码$ 请你告诉我改怎么解决解决确认ChromaDB 索引已损坏100% 是元凶你的隔离测试连list_collections()/count()都段错误说明HNSW 索引文件已经物理损坏不是某个 query 的问题是整个 collection 的索引二进制文件坏了。好消息是你的search_cache.dbSQLite 主库存着完整的搜索结果是好的ChromaDB 只是用来做向量相似匹配的辅助索引。删掉 ChromaDB 目录从 SQLite 重新生成一遍就行搜索结果数据一条都不丢。完整修复步骤按顺序执行第 1 步备份# # 第 1 步备份必须做# cp-r/home/shaolingxuan/project/FlexSearch/search_cache_db \/home/shaolingxuan/project/FlexSearch/search_cache_db.bak.$(date%Y%m%d_%H%M%S)# 确认备份成功ls-d/home/shaolingxuan/project/FlexSearch/search_cache_db.bak.*# # 第 2 步确认 SQLite 主库完好# sqlite3/home/shaolingxuan/project/FlexSearch/search_cache_db/search_cache.db \PRAGMA integrity_check;# 期望输出ok# 看看主库里有多少条数据按引擎模型分组sqlite3/home/shaolingxuan/project/FlexSearch/search_cache_db/search_cache.db \SELECT search_engine, embedding_model, COUNT(*) as cnt FROM search_cache GROUP BY search_engine, embedding_model;第 3 步删除损坏的 ChromaDB 目录# 直接删掉整个 chroma 目录SQLite 主库不受影响rm-rf/home/shaolingxuan/project/FlexSearch/search_cache_db/chroma# 确认已删除ls-la/home/shaolingxuan/project/FlexSearch/search_cache_db/# 应该看不到 chroma 目录了第 4 步从 SQLite 主库重建 ChromaDB把下面的代码保存为/home/shaolingxuan/project/FlexSearch/scripts/rebuild_chroma.py#!/usr/bin/env python3 从 SQLite 主库重建 ChromaDB。 - SQLite 主库search_cache.db保存着完整的搜索结果不丢 - ChromaDB 只是向量索引删了重建即可 - 重建后语义相似匹配功能恢复 importsqlite3importjsonimportloggingimportreimportsysfrompathlibimportPathfromcollectionsimportdefaultdict# 把项目根目录加到 pathPROJECT_ROOTPath(/home/shaolingxuan/project/FlexSearch)sys.path.insert(0,str(PROJECT_ROOT))fromagent.embedderimportVLLMEmbedderimportchromadbfromchromadb.configimportSettings logging.basicConfig(levellogging.INFO,format[%(levelname)s] %(message)s)loggerlogging.getLogger(__name__)CACHE_DIRPROJECT_ROOT/search_cache_dbCHROMA_PATHstr(CACHE_DIR/chroma)DB_PATHCACHE_DIR/search_cache.db# ── 工具函数和 cache.py 里完全一致──────────────────def_engine_collection_name(search_engine:str,embedding_model:str)-str:safe_enginere.sub(r[^a-zA-Z0-9_],_,search_engine).lower()safe_modelre.sub(r[^a-zA-Z0-9_],_,embedding_model).lower()ifsafe_model:namefsq_{safe_engine}_{safe_model}[:63]else:namefsq_{safe_engine}[:63]returnnameiflen(name)3elsesq_default# ── 主流程 ─────────────────────────────────────────────defmain():# 1. 检查 SQLite 主库logger.info(*60)logger.info(第 1 步检查 SQLite 主库)logger.info(*60)connsqlite3.connect(str(DB_PATH))conn.row_factorysqlite3.Row# 完整性检查integrityconn.execute(PRAGMA integrity_check;).fetchone()[0]logger.info(f integrity_check:{integrity})ifintegrity!ok:logger.error(❌ SQLite 主库损坏请从备份恢复。)sys.exit(1)# 读出所有不重复的 (query_hash, query_norm, search_engine, embedding_model)rowsconn.execute(SELECT DISTINCT query_hash, query_norm, search_engine, embedding_model FROM search_cache WHERE embedding_model ! ).fetchall()conn.close()logger.info(f SQLite 主库完好共{len(rows)}条记录待重建)ifnotrows:logger.warning(没有数据需要重建退出。)return# 2. 初始化 Embedderlogger.info()logger.info(*60)logger.info(第 2 步初始化 vLLM Embedder)logger.info(*60)# 取第一条记录的 embedding_model 作为默认你应该只有一种default_modelrows[0][embedding_model]logger.info(f 使用 embedding 模型:{default_model})embedderVLLMEmbedder(base_urlhttp://localhost:1827/v1,model_namedefault_model,)# 测试 embedder 能否正常工作test_vecembedder.encode(test,normalize_embeddingsTrue)logger.info(f Embedder 测试通过向量维度:{len(test_vec)})# 3. 初始化新的 ChromaDBlogger.info()logger.info(*60)logger.info(第 3 步创建新的 ChromaDB)logger.info(*60)# 确保旧目录已删除chroma_dirPath(CHROMA_PATH)ifchroma_dir.exists():logger.warning(f chroma 目录已存在先删除:{CHROMA_PATH})importshutil shutil.rmtree(CHROMA_PATH)clientchromadb.PersistentClient(pathCHROMA_PATH,settingsSettings(anonymized_telemetryFalse),)# 4. 按 (engine, model) 分组groupsdefaultdict(list)forrinrows:groups[(r[search_engine],r[embedding_model])].append(dict(r))logger.info(f 共{len(groups)}个 (engine, model) 分组:)for(engine,model),itemsingroups.items():coll_name_engine_collection_name(engine,model)logger.info(f{coll_name}:{len(items)}条)# 5. 逐组重建logger.info()logger.info(*60)logger.info(第 4 步逐组重建 ChromaDB collection)logger.info(*60)BATCH_SIZE64# 每批 embedding 64 条避免 vLLM 一次性请求太大for(engine,model),itemsingroups.items():coll_name_engine_collection_name(engine,model)logger.info(f\n--- 重建{coll_name}({len(items)}条) ---)collectionclient.get_or_create_collection(namecoll_name,metadata{hnsw:space:cosine},)totallen(items)foriinrange(0,total,BATCH_SIZE):batchitems[i:iBATCH_SIZE]texts[b[query_norm]forbinbatch]# 文档侧不加 prompt和 cache.py 的 _embed 一致embsembedder.encode(texts,normalize_embeddingsTrue,promptNone)# 处理单条返回 1D 的情况importnumpyasnpifisinstance(embs,np.ndarray)andembs.ndim1:embsembs.reshape(1,-1)collection.upsert(ids[b[query_hash]forbinbatch],embeddingsembs.tolist(),metadatas[{query_norm:b[query_norm],query_hash:b[query_hash],search_engine:engine,embedding_model:model,}forbinbatch],documentstexts,)donemin(iBATCH_SIZE,total)logger.info(f [{done}/{total}] 已写入)# 验证countcollection.count()logger.info(f ✅{coll_name}重建完成count{count})# 测试 query 是否正常ifcount0:peekcollection.peek(limit1)ifpeek[ids]:existingcollection.get(ids[peek[ids][0]],include[embeddings])test_resultcollection.query(query_embeddings[existing[embeddings][0]],n_results1,include[distances,metadatas],)logger.info(f ✅ query 测试通过: fdistance{test_result[distances][0][0]:.4f})logger.info()logger.info(*60)logger.info( ChromaDB 重建全部完成)logger.info(*60)# 最终统计logger.info(最终 collection 列表:)forcinclient.list_collections():logger.info(f{c.name}: count{c.count()})if__name____main__:main()运行cd/home/shaolingxuan/project/FlexSearch python scripts/rebuild_chroma.py预计耗时7273 条数据batch_size64约 114 批vLLM embedding 每批约 2-3 秒总共约 5-8 分钟。第 5 步验证不再段错误python scripts/test_chroma_query.py如果输出✅ 查询成功distance0.0000自己查自己距离为 0说明 ChromaDB 已恢复正常。第 6 步给 cache.py 加锁防止再次损坏ChromaDB 的PersistentClient不是线程安全的。你的web_browser.py里ThreadPoolExecutor并发 3 个 query 同时调cache.get()→collection.query()这就是索引损坏的根源。在cache.py中做以下修改# cache.py 修改 1文件顶部加 import importthreadingimporthashlibimportjsonimportloggingimportreimportsqlite3importtimeimportsignal# ★ 新增importfaulthandler# ★ 新增importos# ★ 新增frompathlibimportPathfromtypingimportOptionalfromdatetimeimportdatetime# ... 其他 import 不变 ...# ★ 新增启用 faulthandler段错误时把调用栈写到文件方便下次定位_FAULT_LOGos.path.join(/home/shaolingxuan/project/FlexSearch/search_cache_db,faulthandler.log)os.makedirs(os.path.dirname(_FAULT_LOG),exist_okTrue)_fault_fpopen(_FAULT_LOG,a)faulthandler.enable(file_fault_fp,all_threadsTrue)try:faulthandler.register(signal.SIGSEGV,file_fault_fp,all_threadsTrue)exceptException:pass# 非 Linux 平台可能不支持# cache.py 修改 2SearchCache.__init__ 加 chroma_lock classSearchCache:def__init__(self,...):# ... 原有代码不变 ...self._write_lockthreading.Lock()# 已有self._chroma_lockthreading.Lock()# ★ 新增ChromaDB 读写锁# ... 后续代码不变 ...# cache.py 修改 3_similar_get 加锁 def_similar_get(self,embedding:list,collection)-Optional[tuple]:try:# ★ 加锁ChromaDB 的 query 不是线程安全的withself._chroma_lock:resultcollection.query(query_embeddings[embedding],n_results1,include[distances,metadatas],)print(fCache类 _similar_get() collection.query找到的result:{result})print(fCache类 _similar_get() distance:{result[distances][0][0]})print(fCache类 _similar_get() matched_hash:{result[metadatas][0][0][query_hash]})ifnotresult[ids]ornotresult[ids][0]:returnNonedistanceresult[distances][0][0]similarity1.0-distance matched_hashresult[metadatas][0][0][query_hash]ifsimilarityself.similarity_threshold:returnsimilarity,matched_hashreturnNoneexceptExceptionase:logger.warning(f[SearchCache] ChromaDB 查询异常:{e},exc_infoTrue)returnNone# cache.py 修改 4set() 里的 upsert 加锁 defset(self,...):# ... 前面代码不变 ...withself._write_lock:# ... SQLite 写入不变 ...collectionself._get_collection(search_engine,embedding_model)embeddingself._embed(query_norm)# ★ 加锁upsert 也要串行化防止并发写损坏 HNSW 索引withself._chroma_lock:collection.upsert(ids[query_hash],embeddings[embedding],metadatas[{query_norm:query_norm,query_hash:query_hash,search_engine:search_engine,embedding_model:embedding_model,}],documents[query_norm],)# ... 后续日志不变 ...第 7 步可选但推荐限制 web_browser 并发数在web_browser.py的search()方法中把并发数从len(queries)限制为2# web_browser.py 的 search() 方法中# 原来# with ThreadPoolExecutor(max_workerslen(queries)) as executor:# 改成max_workersmin(len(queries),2)# ★ 最多 2 个并发减轻 ChromaDB 压力withThreadPoolExecutor(max_workersmax_workers)asexecutor:futures{executor.submit(_search_one,q):qforqinqueries}# ... 后续不变 ...总结执行顺序第 1 步cp -r 备份 ← 10 秒 第 2 步sqlite3 integrity_check ← 5 秒 第 3 步rm -rf chroma 目录 ← 1 秒 第 4 步python scripts/rebuild_chroma.py ← 5-8 分钟 第 5 步python scripts/test_chroma_query.py ← 10 秒验证 第 6 步修改 cache.py 加 _chroma_lock ← 2 分钟 第 7 步修改 web_browser.py 限制并发 ← 1 分钟 重跑数据集验证核心要点ChromaDB 索引损坏不可修复只能删除重建——但你的数据在 SQLite 主库里一条都不丢重建后必须加锁_chroma_lock否则多线程并发会再次损坏索引faulthandler能在下次段错误时把 C 层调用栈写到日志文件帮你快速定位
flexsearch静默错误
发布时间:2026/7/6 14:08:34
https://chatglm.cn/main/alltoolsdetail?t1783048760261langzhcid6a4729d82e7bd8bd2e93725fedge我发现我仅仅是运行这一部分代码都会报错# scripts/test_chroma_query.py“”“隔离测试直接查 ChromaDB复现段错误”“”import chromadbfrom chromadb.config import Settingsimport sysCHROMA_PATH “/home/shaolingxuan/project/FlexSearch/search_cache_db/chroma”client chromadb.PersistentClient(pathCHROMA_PATH,settingsSettings(anonymized_telemetryFalse),)问题列出所有 collectionprint(“ 所有 collections ”)for c in client.list_collections():print(f {c.name}: count{c.count()})(flexsearch) shaolingxuank8s-master:~/project/FlexSearch/results/GoogleSearch_qwen-3.5-9b_seal0/回溯9-波动性控制/运行代码$ ls -la /home/shaolingxuan/project/FlexSearch/search_cache_db/chroma/total 20180drwxrwxr-x 3 shaolingxuan shaolingxuan 4096 Jul 3 11:34 .drwxrwxr-x 11 shaolingxuan shaolingxuan 4096 Jul 3 11:25 …drwxrwxr-x 2 shaolingxuan shaolingxuan 4096 Jun 24 21:45 4502f8c4-d9c6-4af1-b3c9-573b755c8fb9-rw-r–r-- 1 shaolingxuan shaolingxuan 20647936 Jul 3 11:34 chroma.sqlite3(flexsearch) shaolingxuank8s-master:~/project/FlexSearch/results/GoogleSearch_qwen-3.5-9b_seal0/回溯9-波动性控制/运行代码$ 码$ du -sh /home/shaolingxuan/project/FlexSearch/search_cache_db/chroma/*48M /home/shaolingxuan/project/FlexSearch/search_cache_db/chroma/4502f8c4-d9c6-4af1-b3c9-573b755c8fb920M /home/shaolingxuan/project/FlexSearch/search_cache_db/chroma/chroma.sqlite3(flexsearch) shaolingxuank8s-master:~/project/FlexSearch/results/GoogleSearch_qwen-3.5-9b_seal0/回溯9-波动性控制/运行代码$ 请你告诉我改怎么解决解决确认ChromaDB 索引已损坏100% 是元凶你的隔离测试连list_collections()/count()都段错误说明HNSW 索引文件已经物理损坏不是某个 query 的问题是整个 collection 的索引二进制文件坏了。好消息是你的search_cache.dbSQLite 主库存着完整的搜索结果是好的ChromaDB 只是用来做向量相似匹配的辅助索引。删掉 ChromaDB 目录从 SQLite 重新生成一遍就行搜索结果数据一条都不丢。完整修复步骤按顺序执行第 1 步备份# # 第 1 步备份必须做# cp-r/home/shaolingxuan/project/FlexSearch/search_cache_db \/home/shaolingxuan/project/FlexSearch/search_cache_db.bak.$(date%Y%m%d_%H%M%S)# 确认备份成功ls-d/home/shaolingxuan/project/FlexSearch/search_cache_db.bak.*# # 第 2 步确认 SQLite 主库完好# sqlite3/home/shaolingxuan/project/FlexSearch/search_cache_db/search_cache.db \PRAGMA integrity_check;# 期望输出ok# 看看主库里有多少条数据按引擎模型分组sqlite3/home/shaolingxuan/project/FlexSearch/search_cache_db/search_cache.db \SELECT search_engine, embedding_model, COUNT(*) as cnt FROM search_cache GROUP BY search_engine, embedding_model;第 3 步删除损坏的 ChromaDB 目录# 直接删掉整个 chroma 目录SQLite 主库不受影响rm-rf/home/shaolingxuan/project/FlexSearch/search_cache_db/chroma# 确认已删除ls-la/home/shaolingxuan/project/FlexSearch/search_cache_db/# 应该看不到 chroma 目录了第 4 步从 SQLite 主库重建 ChromaDB把下面的代码保存为/home/shaolingxuan/project/FlexSearch/scripts/rebuild_chroma.py#!/usr/bin/env python3 从 SQLite 主库重建 ChromaDB。 - SQLite 主库search_cache.db保存着完整的搜索结果不丢 - ChromaDB 只是向量索引删了重建即可 - 重建后语义相似匹配功能恢复 importsqlite3importjsonimportloggingimportreimportsysfrompathlibimportPathfromcollectionsimportdefaultdict# 把项目根目录加到 pathPROJECT_ROOTPath(/home/shaolingxuan/project/FlexSearch)sys.path.insert(0,str(PROJECT_ROOT))fromagent.embedderimportVLLMEmbedderimportchromadbfromchromadb.configimportSettings logging.basicConfig(levellogging.INFO,format[%(levelname)s] %(message)s)loggerlogging.getLogger(__name__)CACHE_DIRPROJECT_ROOT/search_cache_dbCHROMA_PATHstr(CACHE_DIR/chroma)DB_PATHCACHE_DIR/search_cache.db# ── 工具函数和 cache.py 里完全一致──────────────────def_engine_collection_name(search_engine:str,embedding_model:str)-str:safe_enginere.sub(r[^a-zA-Z0-9_],_,search_engine).lower()safe_modelre.sub(r[^a-zA-Z0-9_],_,embedding_model).lower()ifsafe_model:namefsq_{safe_engine}_{safe_model}[:63]else:namefsq_{safe_engine}[:63]returnnameiflen(name)3elsesq_default# ── 主流程 ─────────────────────────────────────────────defmain():# 1. 检查 SQLite 主库logger.info(*60)logger.info(第 1 步检查 SQLite 主库)logger.info(*60)connsqlite3.connect(str(DB_PATH))conn.row_factorysqlite3.Row# 完整性检查integrityconn.execute(PRAGMA integrity_check;).fetchone()[0]logger.info(f integrity_check:{integrity})ifintegrity!ok:logger.error(❌ SQLite 主库损坏请从备份恢复。)sys.exit(1)# 读出所有不重复的 (query_hash, query_norm, search_engine, embedding_model)rowsconn.execute(SELECT DISTINCT query_hash, query_norm, search_engine, embedding_model FROM search_cache WHERE embedding_model ! ).fetchall()conn.close()logger.info(f SQLite 主库完好共{len(rows)}条记录待重建)ifnotrows:logger.warning(没有数据需要重建退出。)return# 2. 初始化 Embedderlogger.info()logger.info(*60)logger.info(第 2 步初始化 vLLM Embedder)logger.info(*60)# 取第一条记录的 embedding_model 作为默认你应该只有一种default_modelrows[0][embedding_model]logger.info(f 使用 embedding 模型:{default_model})embedderVLLMEmbedder(base_urlhttp://localhost:1827/v1,model_namedefault_model,)# 测试 embedder 能否正常工作test_vecembedder.encode(test,normalize_embeddingsTrue)logger.info(f Embedder 测试通过向量维度:{len(test_vec)})# 3. 初始化新的 ChromaDBlogger.info()logger.info(*60)logger.info(第 3 步创建新的 ChromaDB)logger.info(*60)# 确保旧目录已删除chroma_dirPath(CHROMA_PATH)ifchroma_dir.exists():logger.warning(f chroma 目录已存在先删除:{CHROMA_PATH})importshutil shutil.rmtree(CHROMA_PATH)clientchromadb.PersistentClient(pathCHROMA_PATH,settingsSettings(anonymized_telemetryFalse),)# 4. 按 (engine, model) 分组groupsdefaultdict(list)forrinrows:groups[(r[search_engine],r[embedding_model])].append(dict(r))logger.info(f 共{len(groups)}个 (engine, model) 分组:)for(engine,model),itemsingroups.items():coll_name_engine_collection_name(engine,model)logger.info(f{coll_name}:{len(items)}条)# 5. 逐组重建logger.info()logger.info(*60)logger.info(第 4 步逐组重建 ChromaDB collection)logger.info(*60)BATCH_SIZE64# 每批 embedding 64 条避免 vLLM 一次性请求太大for(engine,model),itemsingroups.items():coll_name_engine_collection_name(engine,model)logger.info(f\n--- 重建{coll_name}({len(items)}条) ---)collectionclient.get_or_create_collection(namecoll_name,metadata{hnsw:space:cosine},)totallen(items)foriinrange(0,total,BATCH_SIZE):batchitems[i:iBATCH_SIZE]texts[b[query_norm]forbinbatch]# 文档侧不加 prompt和 cache.py 的 _embed 一致embsembedder.encode(texts,normalize_embeddingsTrue,promptNone)# 处理单条返回 1D 的情况importnumpyasnpifisinstance(embs,np.ndarray)andembs.ndim1:embsembs.reshape(1,-1)collection.upsert(ids[b[query_hash]forbinbatch],embeddingsembs.tolist(),metadatas[{query_norm:b[query_norm],query_hash:b[query_hash],search_engine:engine,embedding_model:model,}forbinbatch],documentstexts,)donemin(iBATCH_SIZE,total)logger.info(f [{done}/{total}] 已写入)# 验证countcollection.count()logger.info(f ✅{coll_name}重建完成count{count})# 测试 query 是否正常ifcount0:peekcollection.peek(limit1)ifpeek[ids]:existingcollection.get(ids[peek[ids][0]],include[embeddings])test_resultcollection.query(query_embeddings[existing[embeddings][0]],n_results1,include[distances,metadatas],)logger.info(f ✅ query 测试通过: fdistance{test_result[distances][0][0]:.4f})logger.info()logger.info(*60)logger.info( ChromaDB 重建全部完成)logger.info(*60)# 最终统计logger.info(最终 collection 列表:)forcinclient.list_collections():logger.info(f{c.name}: count{c.count()})if__name____main__:main()运行cd/home/shaolingxuan/project/FlexSearch python scripts/rebuild_chroma.py预计耗时7273 条数据batch_size64约 114 批vLLM embedding 每批约 2-3 秒总共约 5-8 分钟。第 5 步验证不再段错误python scripts/test_chroma_query.py如果输出✅ 查询成功distance0.0000自己查自己距离为 0说明 ChromaDB 已恢复正常。第 6 步给 cache.py 加锁防止再次损坏ChromaDB 的PersistentClient不是线程安全的。你的web_browser.py里ThreadPoolExecutor并发 3 个 query 同时调cache.get()→collection.query()这就是索引损坏的根源。在cache.py中做以下修改# cache.py 修改 1文件顶部加 import importthreadingimporthashlibimportjsonimportloggingimportreimportsqlite3importtimeimportsignal# ★ 新增importfaulthandler# ★ 新增importos# ★ 新增frompathlibimportPathfromtypingimportOptionalfromdatetimeimportdatetime# ... 其他 import 不变 ...# ★ 新增启用 faulthandler段错误时把调用栈写到文件方便下次定位_FAULT_LOGos.path.join(/home/shaolingxuan/project/FlexSearch/search_cache_db,faulthandler.log)os.makedirs(os.path.dirname(_FAULT_LOG),exist_okTrue)_fault_fpopen(_FAULT_LOG,a)faulthandler.enable(file_fault_fp,all_threadsTrue)try:faulthandler.register(signal.SIGSEGV,file_fault_fp,all_threadsTrue)exceptException:pass# 非 Linux 平台可能不支持# cache.py 修改 2SearchCache.__init__ 加 chroma_lock classSearchCache:def__init__(self,...):# ... 原有代码不变 ...self._write_lockthreading.Lock()# 已有self._chroma_lockthreading.Lock()# ★ 新增ChromaDB 读写锁# ... 后续代码不变 ...# cache.py 修改 3_similar_get 加锁 def_similar_get(self,embedding:list,collection)-Optional[tuple]:try:# ★ 加锁ChromaDB 的 query 不是线程安全的withself._chroma_lock:resultcollection.query(query_embeddings[embedding],n_results1,include[distances,metadatas],)print(fCache类 _similar_get() collection.query找到的result:{result})print(fCache类 _similar_get() distance:{result[distances][0][0]})print(fCache类 _similar_get() matched_hash:{result[metadatas][0][0][query_hash]})ifnotresult[ids]ornotresult[ids][0]:returnNonedistanceresult[distances][0][0]similarity1.0-distance matched_hashresult[metadatas][0][0][query_hash]ifsimilarityself.similarity_threshold:returnsimilarity,matched_hashreturnNoneexceptExceptionase:logger.warning(f[SearchCache] ChromaDB 查询异常:{e},exc_infoTrue)returnNone# cache.py 修改 4set() 里的 upsert 加锁 defset(self,...):# ... 前面代码不变 ...withself._write_lock:# ... SQLite 写入不变 ...collectionself._get_collection(search_engine,embedding_model)embeddingself._embed(query_norm)# ★ 加锁upsert 也要串行化防止并发写损坏 HNSW 索引withself._chroma_lock:collection.upsert(ids[query_hash],embeddings[embedding],metadatas[{query_norm:query_norm,query_hash:query_hash,search_engine:search_engine,embedding_model:embedding_model,}],documents[query_norm],)# ... 后续日志不变 ...第 7 步可选但推荐限制 web_browser 并发数在web_browser.py的search()方法中把并发数从len(queries)限制为2# web_browser.py 的 search() 方法中# 原来# with ThreadPoolExecutor(max_workerslen(queries)) as executor:# 改成max_workersmin(len(queries),2)# ★ 最多 2 个并发减轻 ChromaDB 压力withThreadPoolExecutor(max_workersmax_workers)asexecutor:futures{executor.submit(_search_one,q):qforqinqueries}# ... 后续不变 ...总结执行顺序第 1 步cp -r 备份 ← 10 秒 第 2 步sqlite3 integrity_check ← 5 秒 第 3 步rm -rf chroma 目录 ← 1 秒 第 4 步python scripts/rebuild_chroma.py ← 5-8 分钟 第 5 步python scripts/test_chroma_query.py ← 10 秒验证 第 6 步修改 cache.py 加 _chroma_lock ← 2 分钟 第 7 步修改 web_browser.py 限制并发 ← 1 分钟 重跑数据集验证核心要点ChromaDB 索引损坏不可修复只能删除重建——但你的数据在 SQLite 主库里一条都不丢重建后必须加锁_chroma_lock否则多线程并发会再次损坏索引faulthandler能在下次段错误时把 C 层调用栈写到日志文件帮你快速定位