Просмотр исходного кода

fix(langgraph): 修复 ChromaDB 数据膨胀导致的 gunicorn OOM 循环

- _delete_by_prefix: 改用 metadata where 过滤删除, 避免 vectorstore.get() 全量拉取
  282 万条 embedding 到内存 (13GB) 引发 OOM
- retriever.initialize: 改为 upsert 语义(先删 type=article 旧索引再写入), 避免
  每次 worker 启动重复追加累积
- docker-compose: langgraph-svc 加 mem_limit 4g 硬限制, 防止异常膨胀吃满宿主机
- requirements_frozen: numpy 降级 1.26.4 (2.5.x 移除 np.complex_, tensorflow 不兼容)

效果: embeddings 282万条(23.5GB) -> 2.6万条(286MB), worker 内存 13GB -> 1GB
Xiaogang Liao 1 месяц назад
Родитель
Сommit
22bfdfb305

+ 8 - 1
cfc-langgraph/app/rag/retriever.py

@@ -52,7 +52,14 @@ class RagRetriever:
                 logger.info("知识库初始化:无有效文章内容")
                 logger.info("知识库初始化:无有效文章内容")
                 return
                 return
 
 
-            # 添加或更新向量数据库
+            # upsert: 先删除旧的粗粒度文章索引(type=article), 再写入最新数据,
+            # 避免每次 worker 启动重复追加导致 embedding 无限累积
+            try:
+                self.vectorstore._collection.delete(where={"type": "article"})
+            except Exception as e:
+                logger.warning("知识库初始化: 删除旧文章索引失败: %s", e)
+
+            # 添加最新文章索引
             await self.vectorstore.aadd_texts(texts=texts, metadatas=metadatas)
             await self.vectorstore.aadd_texts(texts=texts, metadatas=metadatas)
             logger.info("知识库向量化完成:%d 篇文章", len(metadatas))
             logger.info("知识库向量化完成:%d 篇文章", len(metadatas))
         except Exception as e:
         except Exception as e:

+ 14 - 5
cfc-langgraph/app/tasks/knowledge_sync.py

@@ -31,14 +31,23 @@ def _save_sync_state(state: dict):
 
 
 
 
 def _delete_by_prefix(vectorstore, prefix: str):
 def _delete_by_prefix(vectorstore, prefix: str):
+    """删除指定 source 的所有旧块。
+
+    修复: 原实现 vectorstore.get() 会拉取整个 collection (数百万条 embedding)
+    到内存, 导致 gunicorn worker OOM 反复重启。改为按 metadata where 过滤
+    只取目标 source 的 ids 再删除, 内存占用与数据总量无关。
+    """
     try:
     try:
-        existing = vectorstore.get()
-        ids = [i for i in existing.get("ids", []) if i.startswith(prefix)]
+        collection = vectorstore._collection
+        existing = collection.get(where={"source": prefix}, include=[])
+        ids = existing.get("ids", []) or []
         if ids:
         if ids:
-            vectorstore.delete(ids)
-            logger.info("已删除前缀 %s 的 %d 个旧块", prefix, len(ids))
+            collection.delete(ids=ids)
+            logger.info("已删除 source=%s 的 %d 个旧块", prefix, len(ids))
+        else:
+            logger.info("无 source=%s 旧块可删除", prefix)
     except Exception as e:
     except Exception as e:
-        logger.warning("删除前缀 %s 旧块失败: %s", prefix, e)
+        logger.warning("删除 source=%s 旧块失败: %s", prefix, e)
 
 
 
 
 def _build_chunks(splitter, items: list[dict], source: str) -> list[Document]:
 def _build_chunks(splitter, items: list[dict], source: str) -> list[Document]:

+ 3 - 1
cfc-langgraph/docker-compose.yml

@@ -21,7 +21,7 @@ services:
       retries: 3
       retries: 3
 
 
   langgraph-svc:
   langgraph-svc:
-    image: cfc-langgraph-langgraph-svc:v7
+    image: cfc-langgraph-langgraph-svc:v8
     build:
     build:
       context: .
       context: .
       dockerfile: Dockerfile
       dockerfile: Dockerfile
@@ -43,6 +43,8 @@ services:
     networks:
     networks:
       - cfc-net
       - cfc-net
     restart: unless-stopped
     restart: unless-stopped
+    # 内存硬限制:防止 ChromaDB/搜索任务异常膨胀吃满宿主机内存导致整机 OOM
+    mem_limit: 4g
     healthcheck:
     healthcheck:
       test: ["CMD", "python", "-c", "import urllib.request,sys; sys.exit(0 if urllib.request.urlopen('http://localhost:9000/health', timeout=5).status==200 else 1)"]
       test: ["CMD", "python", "-c", "import urllib.request,sys; sys.exit(0 if urllib.request.urlopen('http://localhost:9000/health', timeout=5).status==200 else 1)"]
       interval: 30s
       interval: 30s

+ 1 - 1
cfc-langgraph/requirements_frozen.txt

@@ -58,7 +58,7 @@ mdurl==0.1.2
 mmh3==5.2.1
 mmh3==5.2.1
 multidict==6.7.1
 multidict==6.7.1
 mypy_extensions==1.1.0
 mypy_extensions==1.1.0
-numpy==2.5.1
+numpy==1.26.4
 oauthlib==3.3.1
 oauthlib==3.3.1
 onnxruntime==1.27.0
 onnxruntime==1.27.0
 openai==2.46.0
 openai==2.46.0