你的LlamaIndex索引构建为什么这么慢?

某法律科技公司用LlamaIndex构建了一个法律文书RAG系统。初期索引2000份PDF,花了5分钟——可以接受。但当文档量增长到50万份时,全量重建索引需要42小时。这期间系统不可用,用户只能在"没索引"和"停在旧索引"之间选择。

问题的根源是:LlamaIndex的默认索引构建是同步的、单线程的、全量重建的。 面对大规模数据,这个模式完全不可行。

我帮他们用IngestionPipeline重构了索引构建流程,将速度从50页/秒提升到500页/秒。以下是完整方案。

问题诊断:默认索引构建的三个瓶颈

  1. 同步处理:文档加载、分块、Embedding、索引写入是串行的。一个文档的Embedding API调用(200ms)阻塞了后续所有文档。
  2. 全量重建:每次更新都重建整个索引,而不是增量更新。50万份文档中99%没有变化,但都被重新处理了。
  3. 无缓存:相同的文档被重复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
内存占用48GB16GB3x降低

关键发现:增量更新不仅节省时间,更重要的是节省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的"高级功能"都是付费的——但通常比你自己实现节省的时间更值钱。