LlamaIndex Key-Value Stores 存储抽象全解析:Simple、MongoDB 与 Tablestore 的实现原理与实战
【免费下载链接】llama_indexLlamaIndex is the document processing platform for AI项目地址: https://gitcode.com/GitHub_Trending/ll/llama_index
导读
本文围绕 LlamaIndex 框架中支撑 Document Store 与 Index Store 的底层存储抽象——Key-Value Store(KV Store)展开,系统梳理其抽象接口设计、内置的三种实现(内存版 SimpleKVStore、MongoDB 版与 Tablestore 版)以及它们如何被上层存储组件消费。读完本文,你将掌握 KV Store 的统一操作语义(put/get/delete及异步变体)、持久化与序列化机制、三种实现的选型依据,以及如何用 KV Store 自定义底层存储来构建可复用的文档与索引存储。
关联文档:docs/src/content/docs/framework/module_guides/storing/kv_stores.md
一、KV Store 在 LlamaIndex 存储体系中的定位
在 LlamaIndex 的存储体系中,KV Store 是最底层的"键值存取"抽象,它是 Document Store 与 Index Store 的存储底座。也就是说,节点(Node)内容的存取、索引结构(IndexStruct)的序列化,最终都会落到 KV Store 的key -> dict操作上。
从 核心存储目录结构 可以看到清晰的分层:
storage/ ├── kvstore/ # KV Store 抽象与实现(本文主题) │ ├── types.py # BaseKVStore 抽象基类与常量 │ ├── simple_kvstore.py # 内存版实现 │ └── __init__.py ├── docstore/ # 文档/节点存储(依赖 kvstore) │ ├── keyval_docstore.py # KVDocumentStore │ └── simple_docstore.py # SimpleDocumentStore ├── index_store/ # 索引结构存储(依赖 kvstore) │ ├── keyval_index_store.py # KVIndexStore │ └── simple_index_store.py # SimpleIndexStore └── storage_context.py # StorageContext 统一装配入口文档明确指出:KV Store 是驱动 Document Store 和 Index Store 的底层存储抽象。原文档同时强调"目前这些存储抽象并非面向外部用户的公开 API",因此本文把它当作理解 LlamaIndex 存储机制的内部知识来深入讲解。
注意:原文档列出三种 KV Store——内存版(Simple Key-Value Store)、MongoDB 版与 Tablestore 版。其中后两种以独立集成包的形式维护在 llama-index-integrations/storage/kvstore/ 目录下,读者可按需单独安装。
二、统一抽象:BaseKVStore 接口设计
所有 KV Store 实现都继承自 BaseKVStore 抽象基类。它定义了一套统一的操作契约,每个方法都同时提供同步与异步(带a前缀)两个版本:
| 方法 | 作用 | 说明 |
|---|---|---|
put(key, val, collection)/aput(...) | 写入键值对 | 每个实现都必须支持 |
put_all(kv_pairs, collection, batch_size)/aput_all(...) | 批量写入 | 基类默认仅支持batch_size=1,即逐个调用put;不支持批量的实现会抛出NotImplementedError |
get(key, collection)/aget(...) | 读取单个值 | 不存在时返回None |
get_all(collection)/aget_all(...) | 读取整个 collection 的全部键值 | 返回Dict[str, dict] |
delete(key, collection)/adelete(...) | 删除键 | 返回布尔值表示是否删除成功 |
基类还定义了两个重要常量(types.py):
DEFAULT_COLLECTION = "data":默认 collection 名称。collection 是 KV Store 中用于逻辑分区的命名空间,类似 MongoDB 中的集合(collection)或关系型数据库中的表——不同用途的数据(如节点正文、ref_doc 信息、索引结构)会写入不同的 collection。DEFAULT_BATCH_SIZE = 1:批量写入的默认批次大小。
除BaseKVStore外,抽象层还提供两个中间基类:
BaseInMemoryKVStore(types.py#L77-L89):在BaseKVStore之上补充persist(persist_path, fs)与from_persist_path(persist_path)两个持久化相关的抽象方法,是"可落盘内存型 KV Store"的契约。MutableMappingKVStore(types.py#L95-L183):泛型实现,内部用_collections_mappings: Dict[str, MutableMappingT]把"collection -> 可变映射"组织起来,通过mapping_factory工厂函数创建每个 collection 的实际映射容器。它实现了大部分读写逻辑,但persist/from_persist_path默认抛NotImplementedError,提示"请使用SimpleKVStore等子类"——这正是SimpleKVStore存在的原因。
从源码结构看,这套抽象刻意把"接口契约"(BaseKVStore)、"通用内存映射逻辑"(MutableMappingKVStore)与"具体后端"(SimpleKVStore、MongoDBKVStore、TablestoreKVStore)三层分离,使得上层KVDocumentStore/KVIndexStore无需关心底层是内存、MongoDB 还是 Tablestore。
三、Simple Key-Value Store:内存实现与持久化
SimpleKVStore 是 LlamaIndex 内置的纯内存 KV Store,继承自MutableMappingKVStore[dict],数据格式为Dict[str, Dict[str, dict]](即collection -> (key -> value))。
3.1 核心能力
- 初始化:
SimpleKVStore(data=None),可选传入已有数据字典进行恢复。 - 持久化:
persist(persist_path, fs=None)将整个 store 以 JSON 形式写入磁盘;fs使用 fsspec 文件系统抽象,默认fsspec.filesystem("file"),因此天然支持传 S3、GCS 等 fsspec 兼容文件系统。写入前会自动创建目录。 - 从磁盘加载:类方法
from_persist_path(persist_path, fs=None)读取 JSON 并构造新实例。 - 字典互转:
to_dict()/from_dict()支持把整个 store 导出为普通字典,或在SimpleKVStore与字典之间互转。
由于它完全驻留内存,进程退出后数据即丢失,只有显式调用persist才会落盘——这是它与 MongoDB / Tablestore 版本最本质的区别,适用于原型验证、单机小规模场景。
3.2 序列化细节
persist直接执行json.dumps(self._collections_mappings),即把collection -> {key: value_dict}的嵌套结构整体序列化。这要求 value 必须是可 JSON 序列化的字典;上层KVDocumentStore在写入前会通过doc_to_json把 Node 转为 JSON 字典,恰好满足这一前提。
四、MongoDB Key-Value Store:服务化后端
MongoDBKVStore 将 MongoDB 作为 KV Store 后端,包名为llama-index-storage-kvstore-mongodb。它直接继承BaseKVStore,是面向生产环境的分布式存储方案。
4.1 构造与连接
| 参数 | 类型 | 说明 |
|---|---|---|
mongo_client | Any | 必填,外部传入的 pymongoMongoClient |
mongo_aclient | Optional | 可选的 pymongoAsyncMongoClient,用于异步操作 |
uri/host/port | Optional | 连接信息(用于记录,实际连接由 client 建立) |
db_name | Optional | 数据库名,默认"db_docstore" |
两种推荐的构造方式:
# 方式一:URI 连接 from llama_index.storage.kvstore.mongodb import MongoDBKVStore kvstore = MongoDBKVStore.from_uri("mongodb://localhost:27017", db_name="my_kvstore") # 方式二:主机与端口连接 kvstore = MongoDBKVStore.from_host_and_port("localhost", 27017, db_name="my_kvstore")源码中两类工厂方法都会同时创建同步MongoClient与异步AsyncMongoClient(异步客户端用于aget/aput等异步 API),并以appname="Llama-Index-KVStore-Python"标识应用。若系统缺少pymongo,会抛出提示pip install pymongo的ImportError。
4.2 存储映射与读写实现
MongoDBKVStore 的映射策略非常直接(base.py#L186-L298):
- collection 映射到 MongoDB 的 collection:
self._db[collection]; - key 映射到文档的
_id字段:写入时构造{"_id": key, **value},即 value 的所有字段平铺到文档中; - 写入使用
UpdateOne(..., upsert=True)批量执行:put_all按batch_size分片,对每个文档执行 upsert,保证幂等写入; - 读取时剥离
_id:get/get_all取出文档后pop("_id"),将剩余字段作为 value 返回,对上层透明; - 删除按
_id执行:delete_one({"_id": key}),返回deleted_count > 0作为成功标志。
异步版本(aput_all/aget/aget_all/adelete)依赖异步客户端,若未传入mongo_aclient会抛出ValueError提示"未使用异步客户端初始化"。
MongoDB 版本支持真正的batch_size批量写入(这是它相对基类默认行为的重要增强),并原生具备数据持久化、副本集与分布式能力,适合对数据可靠性要求高的场景。
五、Tablestore Key-Value Store:阿里云表格存储后端
TablestoreKVStore 以阿里云 Tablestore(表格存储,OTS)为后端,包名为llama-index-storage-kvstore-tablestore。
5.1 构造参数
| 参数 | 类型 | 说明 |
|---|---|---|
tablestore_client | Optional[OTSClient] | 外部传入的 OTS 客户端;一旦传入,以下四个参数全部被忽略 |
endpoint | Optional[str] | Tablestore 实例端点 |
instance_name | Optional[str] | Tablestore 实例名 |
access_key_id/access_key_secret | Optional[str] | 阿里云访问密钥 |
当不传客户端时,内部自动构造tablestore.OTSClient(endpoint, access_key_id, access_key_secret, instance_name, retry_policy=tablestore.WriteRetryPolicy(), **kwargs),其中显式启用了写入重试策略。
5.2 Tablestore 特有的表管理与序列化
与 MongoDB 不同,Tablestore 是"表 + 主键列 + 属性列"模型,因此实现上有两个显著特色:
- collection 映射为表,表不存在时自动创建(base.py#L71-L93):
_create_collection_if_not_exist会先查list_table(),若目标表不存在,则以单主键列[("pk", "STRING")]建表(预留吞吐CapacityUnit(0, 0)),创建成功后sleep(5)等待表生效。 - value 序列化策略(base.py#L54-L64):
_flatten_dict_to_json_strings把 value 字典中非标量类型(非 bool/bytearray/float/int/binary/str)的值json.dumps成字符串存储,标量原样保留;读取时_parse_row反向尝试json.loads还原 JSON 字符串。这一机制规避了 Tablestore 对列类型的限制。
5.3 读写与遍历
put:以key作为主键("pk", key),把 value 的属性列写入tablestore.Row并put_row;get:get_row按主键读取,解析后返回;当服务端返回OTSParameterInvalid且错误信息包含table not exist时返回None;get_all:使用get_range从INF_MIN到INF_MAX正向遍历整表(每批limit=5000),通过next_start_primary_key分页循环取完全部数据;delete_all:同样用get_range遍历并逐行删除。
需要特别说明:Tablestore 版本的异步方法(aput/aget/aget_all/adelete)目前直接抛出NotImplementedError,仅支持同步 API——这是从源码确认的实现现状,选用时需注意。
六、KV Store 如何驱动 Document Store 与 Index Store
理解了三种实现之后,再看它们在上层如何被消费,就能完整还原 KV Store 的价值。
6.1 KVDocumentStore:文档存储的 KV 视角
KVDocumentStore 是 LlamaIndex 文档存储的通用实现,它把文档(Node)存储建模为三个逻辑 collection(源码常量定义):
| Collection 常量 | 实际名称(默认 namespace=docstore) | 存储内容 |
|---|---|---|
DEFAULT_COLLECTION_DATA_SUFFIX | docstore/data | 每个 Node 的正文与属性(序列化 JSON) |
DEFAULT_REF_DOC_COLLECTION_SUFFIX | docstore/ref_doc_info | 文档 -> 其节点 ID 列表的映射(RefDocInfo) |
DEFAULT_METADATA_COLLECTION_SUFFIX | docstore/metadata | 节点 -> 所属 ref_doc_id 与 doc_hash 的元数据 |
add_documents会把 Node 转换成三类键值对(_prepare_kv_pairs),再通过kvstore.put_all(..., batch_size=batch_size)分别批量写入三个 collection;读取、删除、哈希校验等操作也都落在kvstore.get/get_all/delete上。因此,KVDocumentStore 的底层行为完全由你传入的 KV Store 决定——换一个 KV Store 实现,就等于换了一套存储后端。
6.2 KVIndexStore:索引结构的 KV 视角
KVIndexStore 同样依赖BaseKVStore,把IndexStruct序列化为 JSON 后存入默认 collectionindex_store/data(namespace=index_store+ 后缀/data)。add_index_struct以index_struct.index_id为 key 写入,get_index_struct/index_structs从 KV Store 读回并反序列化。
6.3 组装方式与共享存储
两个通用实现分别派生出默认的简单版本:
SimpleDocumentStore(KVDocumentStore):simple_docstore.py,默认使用SimpleKVStore;SimpleIndexStore(KVIndexStore):simple_index_store.py,默认同样使用SimpleKVStore。
提示:
KVDocumentStore/KVIndexStore的命名空间(namespace)与 collection 后缀均可自定义,MongoDBKVStore、TablestoreKVStore也可以直接作为这两个类的kvstore参数传入,实现"同一套存储逻辑、不同的底层后端"。
七、实战:三种 KV Store 的选型与用法速查
7.1 安装
# 内存版已包含在 llama-index-core 中,无需额外安装 pip install llama-index-core # MongoDB 版 pip install llama-index-storage-kvstore-mongodb # Tablestore 版 pip install llama-index-storage-kvstore-tablestore7.2 直接使用 KV Store
# SimpleKVStore:内存 + 持久化 from llama_index.core.storage.kvstore import SimpleKVStore kv = SimpleKVStore() kv.put("key1", {"content": "hello"}, collection="my_coll") print(kv.get("key1", collection="my_coll")) # {'content': 'hello'} kv.persist("./kvstore.json") # 落盘 loaded = SimpleKVStore.from_persist_path("./kvstore.json")# MongoDBKVStore:服务化后端 from llama_index.storage.kvstore.mongodb import MongoDBKVStore kv = MongoDBKVStore.from_uri("mongodb://localhost:27017", db_name="my_db") kv.put("key1", {"content": "hello"}, collection="my_coll") print(kv.get("key1", collection="my_coll"))7.3 将 KV Store 接入 Document Store / Index Store
from llama_index.core.storage.docstore import KVDocumentStore from llama_index.core.storage.index_store import KVIndexStore from llama_index.storage.kvstore.mongodb import MongoDBKVStore kvstore = MongoDBKVStore.from_uri("mongodb://localhost:27017", db_name="my_db") # 使用同一个 KV Store 构建 docstore 与 index_store docstore = KVDocumentStore(kvstore=kvstore) index_store = KVIndexStore(kvstore=kvstore)7.4 选型建议
| 维度 | SimpleKVStore | MongoDBKVStore | TablestoreKVStore |
|---|---|---|---|
| 存储位置 | 进程内存(可选落盘 JSON) | MongoDB 服务 | 阿里云 Tablestore |
| 异步 API | ✅ 支持 | ✅ 支持(需异步客户端) | ❌ 抛NotImplementedError |
| 批量写入 | 仅batch_size=1 | ✅ 原生批量 + upsert | 逐行写入 |
| 持久化/高可用 | 手动persist | 由 MongoDB 提供 | 由 Tablestore 提供 |
| 典型场景 | 原型、单机、临时缓存 | 生产环境、多实例共享 | 阿里云生态内生产环境 |
八、总结
KV Store 是 LlamaIndex 存储体系的"地基":BaseKVStore定义了put/get/delete与异步变体的统一契约,MutableMappingKVStore提供了通用的内存映射骨架,而SimpleKVStore、MongoDBKVStore、TablestoreKVStore则分别面向"进程内存 + JSON 落盘""MongoDB 服务""阿里云表格存储"三种后端。上层KVDocumentStore与KVIndexStore通过 collection 划分逻辑空间,把节点内容、ref_doc 关系、索引结构全部映射为键值操作。
理解这层抽象后,你可以:
- 用
SimpleKVStore.persist快速实现可复现的原型; - 用
MongoDBKVStore.from_uri无缝切换生产级后端,且获得原生批量写入与异步支持; - 结合 StorageContext 统一装配自定义的 docstore / index_store,实现多索引共享同一套底层存储。
更深入的接口签名与参数细节,可查阅 API Reference(KV Store 部分) 及本文引用的各源码文件。
【免费下载链接】llama_indexLlamaIndex is the document processing platform for AI项目地址: https://gitcode.com/GitHub_Trending/ll/llama_index
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考