你的LlamaIndex索引构建为什么这么慢?
某法律科技公司用LlamaIndex构建了一个法律文书RAG系统。初期索引2000份PDF,花了5分钟——可以接受。但当文档量增长到50万份时,全量重建索引需要42小时。这期间系统不可用,用户只能在"没索引"和"停在旧索引"之间选择。
问题的根源是:LlamaIndex的默认索引构建是同步的、单线程的、全量重建的。 面对大规模数据,这个模式完全不可行。
我帮他们用IngestionPipeline重构了索引构建流程,将速度从50页/秒提升到500页/秒。以下是完整方案。
问题诊断:默认索引构建的三个瓶颈
- 同步处理:文档加载、分块、Embedding、索引写入是串行的。一个文档的Embedding API调用(200ms)阻塞了后续所有文档。
- 全量重建:每次更新都重建整个索引,而不是增量更新。50万份文档中99%没有变化,但都被重新处理了。
- 无缓存:相同的文档被重复Embedding,浪费API调用和计算资源。
解决方案:IngestionPipeline + 异步 + 增量更新
第一步:异步IngestionPipeline
from llama_index.core.ingestion import IngestionPipeline
from llama_index.core.node_parser import SentenceSplitter
from llama_index.embeddings.openai import OpenAIEmbedding
from llama_index.vector_stores.milvus import MilvusVectorStore
pipeline = IngestionPipeline(
transformations=[
SentenceSplitter(chunk_size=800, chunk_overlap=200),
OpenAIEmbedding(model="text-embedding-3-large"),
],
vector_store=MilvusVectorStore(
uri="http://localhost:19530",
collection_name="legal_docs",
dim=3072,
),
)
# 异步运行Pipeline
pipeline.run(
documents=documents,
num_workers=8, # 8个并发worker
show_progress=True,
)
关键参数:num_workers=8。这开启了8个并发的Embedding+写入线程。瓶颈从API调用延迟变成了API速率限制。
第二步:文档哈希缓存
避免重复Embedding已经处理过的文档:
import hashlib
def compute_doc_hash(doc):
return hashlib.md5(doc.text.encode()).hexdigest()
# 在Pipeline中增加缓存检查
processed_hashes = set() # 从数据库/Redis加载
new_docs = []
for doc in documents:
doc_hash = compute_doc_hash(doc)
if doc_hash not in processed_hashes:
new_docs.append(doc)
processed_hashes.add(doc_hash)
pipeline.run(documents=new_docs, num_workers=8)
第三步:增量更新策略
from llama_index.core.ingestion import DocstoreStrategy
pipeline = IngestionPipeline(
transformations=[...],
vector_store=vector_store,
docstore_strategy=DocstoreStrategy.UPSERTS, # 增量更新
)
UPSERTS策略:如果文档ID已存在,更新;否则插入。配合文档哈希,只处理新增和修改的文档。
性能对比
| 指标 | 默认方案 | 优化后 | 提升 |
|---|---|---|---|
| 全量索引(50万份) | 42小时 | 4.2小时 | 10x |
| 增量更新(1000份新增) | 4分钟 | 40秒 | 6x |
| API调用次数/全量 | 50万次 | 50万次(首次) | — |
| API调用次数/增量 | 50万次(全量) | 1000次(增量) | 500x |
| 内存占用 | 48GB | 16GB | 3x降低 |
关键发现:增量更新不仅节省时间,更重要的是节省API调用费。如果每次更新都全量重建,50万份文档的Embedding费用是$6,500(按OpenAI定价)。增量更新降到$13。
金句:IngestionPipeline不是"让代码更优雅",而是"让成本可控"。50万份文档的增量更新,从$6,500降到$13。
额外优化:多模态文档处理
LlamaIndex的LlamaParse可以处理PDF中的表格和图片:
from llama_parse import LlamaParse
parser = LlamaParse(
result_type="markdown",
parsing_instruction="""
提取所有文本、表格和图片的alt-text。
表格保留行列结构。
图片保留上下文描述。
""",
)
documents = await parser.aload_data("legal_docs/")
LlamaParse的表格式理解比开源PDF解析器(如PyPDF2、pdfplumber)好得多,但代价是$0.003/页。50万页的解析费是$1,500。如果预算有限,可以在LlamaParse和开源解析器之间做分层——重要文档用LlamaParse,普通文档用开源解析器。
金句:LlamaIndex的"高级功能"都是付费的——但通常比你自己实现节省的时间更值钱。