本例用一个 Collection(统一定义字段的数据集合)管理两个租户,无需为每个租户单独维护索引。租户分别对应 tenant-a 和 tenant-b 两个 Slice(业务数据分区),写入和查询时通过 _slice 指定租户。这种按业务标识选择数据范围的方式称为命名空间模式(exact),也是默认模式。
完成首次检索只需创建 Collection → 写入数据 → 等待数据可搜索并查询。下文将预创建 Backing、提前注册 Slice 和缓存预热标为可选;批量开通租户前推荐先预创建 Backing 并注册 Slice,对首次查询延迟敏感时推荐先预热。
准备实例
需要一个可用的 Agent 引擎版实例。尚未创建时,先完成创建和连接实例。后续请求在 Kibana Dev Tools 中执行,操作账号需要 Collection 管理和数据读写权限,见 Collection 权限。
应用通过 Collection 名和业务 Slice 名访问数据。Slice 用于限定数据范围,不是账号权限边界;租户身份校验和访问授权仍需由应用完成。
步骤一:创建 Collection
创建 knowledge-chunks,统一定义租户标识、标题、正文和向量等字段。示例向量可直接用于试用;接入真实业务时,向量维度和相似度应与模型一致。
PUT /_slice_collection/knowledge-chunks
{
"max_slices_per_shard": 200,
"settings": {
"index.number_of_shards": 2,
"index.number_of_replicas": 2
},
"mappings": {
"properties": {
"tenant_id": { "type": "keyword" },
"document_id": { "type": "keyword" },
"title": { "type": "text" },
"content": { "type": "text" },
"category": { "type": "keyword" },
"updated_at": { "type": "date" },
"embedding": {
"type": "dense_vector",
"dims": 4,
"index": true,
"similarity": "cosine",
"index_options": {
"type": "bbq_disk"
}
}
}
}
}确认返回 acknowledged: true,表示 Collection 配置已创建。
系统将文档存放在 Backing(底层索引)中,一个 Backing 可以承载多个 Slice。max_slices_per_shard 用于控制每个 Backing 接收新 Slice 的数量;调小它会让新 Slice 更早使用其他 Backing。它不限制单个 Slice 的文档数,容量计算见容量参数如何生效。
预创建 Backing(可选,推荐)
首次试用可以跳过,系统会按需创建。批量开通租户或导入数据前,推荐先准备 5 个 Backing,减少首次写入时创建索引的等待,让新租户的数据分散到多个底层索引上,有助于分散读写负载:
PUT /_slice_collection/knowledge-chunks/backings?count=5确认返回 acknowledged: true,且 backings 中有 5 个名称。重复执行会检查并补齐同一批索引;已有数据不会因此重新分布。生产环境应结合数据量和分片资源调整数量,参数及分配规则见预创建 Backing。
提前注册 Slice(可选)
默认允许首次写入时自动注册 Slice。在租户开通时提前准备业务分区,可减少首次业务写入时的注册等待:
PUT /_slice_collection/knowledge-chunks/slices
{
"slices": ["tenant-a", "tenant-b"]
}确认顶层 errors 为 false,每个 items[].result 为 created 或 noop。created 表示新注册,noop 表示该 Slice 已存在且无需更改;某项为 failed 时,先处理其错误再写入。
步骤二:写入数据
常规写入不应传 refresh,也不要在应用代码中调用 _refresh。 这类操作可能显著增加数据发布、对象存储读取和查询节点刷新开销。仅在低频同步点确需使用 refresh=wait_for 时,先核对适用条件与代价。
批量写入
使用一次 Bulk 请求,向两个租户各写入两条文档。两个租户使用相同的 _id,但文档内容不同,用于验证:_id 只需在同一个 Slice 内唯一,不同 Slice 的同 ID 文档不会相互覆盖。
POST /_bulk
{ "index": { "_index": "knowledge-chunks", "_id": "chunk-1001", "_slice": "tenant-a" } }
{ "tenant_id": "tenant-a", "document_id": "doc-refund-policy", "title": "退款与退货政策", "content": "重复购买的商品可以在订单完成后的七天内申请退款。", "category": "after-sales", "updated_at": "2026-07-30T10:00:00Z", "embedding": [0.82, 0.10, 0.05, 0.03] }
{ "index": { "_index": "knowledge-chunks", "_id": "chunk-1001", "_slice": "tenant-b" } }
{ "tenant_id": "tenant-b", "document_id": "doc-shipping-status", "title": "查询物流状态", "content": "在订单详情页打开物流信息,即可查看最新配送状态。", "category": "shipping", "updated_at": "2026-07-30T10:01:00Z", "embedding": [0.12, 0.78, 0.06, 0.04] }
{ "index": { "_index": "knowledge-chunks", "_id": "chunk-1002", "_slice": "tenant-a" } }
{ "tenant_id": "tenant-a", "document_id": "doc-change-address", "title": "修改收货地址", "content": "订单发货前,可以在订单详情页修改收货地址。", "category": "orders", "updated_at": "2026-07-30T10:02:00Z", "embedding": [0.75, 0.16, 0.06, 0.03] }
{ "index": { "_index": "knowledge-chunks", "_id": "chunk-1002", "_slice": "tenant-b" } }
{ "tenant_id": "tenant-b", "document_id": "doc-invoice", "title": "申请电子发票", "content": "订单完成后,可以在发票管理页面申请电子发票。", "category": "billing", "updated_at": "2026-07-30T10:03:00Z", "embedding": [0.18, 0.70, 0.08, 0.04] }Bulk 使用 NDJSON 格式,每条操作和文档各占一行,末尾保留换行。确认响应顶层 errors 为 false;出现部分失败时,逐项检查 items 中的错误,处理后再继续。
写入响应中的 _index 可能是 .sc-* 底层索引名;读取或查询响应中的 _routing 可能是系统生成的 Slice 内部标识(slice_uid),与传入的业务 Slice 名不同。这是正常现象。后续请求仍使用 knowledge-chunks 和 tenant-a 等业务名称,不要把这些内部值当作 Collection 名或 _slice 参数,也不要解析其格式。
按 ID 验证写入(可选)
GET 默认实时读取,正常情况下无需等待搜索刷新。分别读取两个租户的 chunk-1001:
GET /knowledge-chunks/_doc/chunk-1001?_slice=tenant-aGET /knowledge-chunks/_doc/chunk-1001?_slice=tenant-b两次响应的 found 应均为 true,标题分别为“退款与退货政策”和“查询物流状态”,说明同 ID 文档分别保存在两个 Slice 中。主分片不可用时的读取边界见按 ID 确认写入。
步骤三:查询租户内的数据
写入成功后,等待约 5 秒,让后台自动刷新使数据可搜索,再执行预热或查询。若结果尚未包含新数据,再等一个自动刷新周期后重试;实际可搜索时间还受负载影响,5 秒不是完成时限。
预热待查询的 Slice(可选,推荐)
预热可减少首次查询从对象存储加载冷数据的等待。接下来查询 tenant-a,可以先将它的可搜索数据加载到查询节点缓存,让后续查询复用。此处同步等待,便于观察结果:
POST /knowledge-chunks/_warm_slice?_slice=tenant-a&wait_for_completion=true&timeout=60s确认请求成功且 _shards.failed 为 0。本例已写入数据;若 _shards.total=0,先确认查询分片可用,并用实际查询确认数据已可搜索,再按需重试预热。超时或分片失败时,检查错误后再重试。预热只覆盖当时已可搜索的数据,不包含之后的新数据。
生产环境推荐在用户选中知识库等可预测的访问时机提前异步预热,并控制并发;后续查询复用缓存,无需每条查询都等待预热。异步返回 ACCEPTED 只表示已提交,结果判断见预热 Slice 缓存。
执行向量检索
只查询 tenant-a 中的文档,并返回用于业务展示和范围核对的字段:
POST /knowledge-chunks/_search?_slice=tenant-a
{
"knn": {
"field": "embedding",
"query_vector": [0.80, 0.12, 0.05, 0.03],
"k": 10,
"num_candidates": 100
},
"_source": ["tenant_id", "document_id", "title", "content"]
}确认 timed_out 为 false、_shards.failed 为 0。结果应包含“退款与退货政策”和“修改收货地址”,且所有结果的 tenant_id 都是 tenant-a。将 URL 中的 _slice 改为 tenant-b 后,应得到“查询物流状态”和“申请电子发票”。两个租户使用相同文档 ID,因此应通过租户字段和标题核对范围,不能只比较 ID。
tenant_id 是用于展示和核对的普通字段,实际查询范围由 _slice 决定。命名空间模式默认要求明确指定范围,省略 _slice 会报错;常规业务查询不应使用 _slice=_all。多 Slice 查询及全量查询的适用条件见指定查询范围。常规检索无需取回向量值,服务端会完成 KNN 计算。