作者:来自 Elastic Stas Malyshev 及 Luigi Dell'Aquila

在 Elasticsearch Serverless 中,使用项目路由将跨项目搜索范围限定为完全跳过不匹配的项目,或使用项目标签字段在查询内部按标签进行过滤、聚合和排序。
通过 Elastic Cloud Serverless 摆脱运维负担。自动扩展、处理负载峰值,并专注于构建 ------ 立即开始 14 天免费试用,亲自体验!
你可以按照这些指南构建 AI 驱动的搜索体验,或跨业务系统和软件进行搜索。
Elastic Cloud Serverless 中的跨项目搜索( CPS )允许你在单个请求中查询多个项目的数据,这是许多组织都有的需求。借助项目标签和路由,你可以轻松限定搜索范围。项目路由会在查询运行之前按项目别名进行过滤,因此 Elasticsearch 完全不会触碰不匹配的项目:无需索引解析、无需协调、无需计算。项目标签字段则像其他字段一样在查询内部工作,因此你可以按环境或部门等标签值对结果进行过滤、聚合、排序和分组。
本文介绍了 Serverless 项目标签,并详细说明了如何在跨项目搜索场景中使用它们,以便在查询中定位特定项目并提升查询性能。
项目标签背景
每个 Serverless 项目都关联了一组键值对,用于对项目进行分类和组织。这些被称为项目标签( project tags )。标签有两种类型:
- 预构建标签( Prebuilt tags ):每个项目都存在的预定义标签,并会自动生成;例如, _alias 、 _id 、 _type ,分别对应项目别名、项目 ID 和项目类型。预定义标签名称始终以下划线开头。
- 自定义标签( Custom tags ):用户定义的标签;它们可以使用任何以字母开头的名称,并且可以包含小写字母、数字、下划线和连字符。例如,你可以为 QA 环境项目使用标签 env:qa ,为生产环境项目使用标签 env:production 。
在 Elastic Cloud 控制台中,你可以在"管理项目( Manage project )"页面中管理项目标签:

你也可以通过编程方式,使用 Elasticsearch API GET _project/tags 查看这些标签:
bash
`
1. {
2. "origin": {
3. "f58ef00d1538476f884c137bf7d304ff": {
4. "_alias": "my-origin-project-1",
5. "_csp": "aws",
6. "_id": "f58ef00d1538476f884c137bf7d304ff",
7. "_organization": "500590167",
8. "_region": "aws-eu-west-1",
9. "_type": "security"
10. }
11. },
12. "linked_projects": {
13. "a4f8c2fa86824395a845e4a055e1ce83": {
14. "_alias": "my-linked-1",
15. "_csp": "aws",
16. "_id": "a4f8c2fa86824395a845e4a055e1ce83",
17. "_organization": "500590167",
18. "_region": "aws-eu-west-1",
19. "_type": "observability",
20. "env": "qa",
21. "test": "abcd"
22. }
23. }
24. }
`AI写代码
在查询中,你可以使用项目标签将搜索范围限制为已关联项目中的某个子集;例如,仅搜索可观测性项目,或仅搜索属于计费部门的项目。这被称为项目路由( project routing );对于每次搜索,你都可以提供一个过滤表达式,将搜索范围限定为标签匹配该表达式的项目。这是一种非常高效的缩小搜索范围的方法,因为它不需要访问任何不包含匹配数据的项目。
此外,你还可以像使用普通索引字段一样,在查询中使用项目标签:用于匹配、聚合、排序、作为搜索字段输出的一部分等等。以这种方式使用时,项目标签看起来与任何映射字段完全相同,但它们的值并不存储在索引中;而是从项目配置中动态加载。这为在搜索和聚合中使用项目标签信息提供了更大的灵活性。
以这种方式使用时,项目标签始终带有 _project. 前缀;例如,项目标签 _alias 会变成 _project._alias ,项目标签 department 会变成 _project.department 。
项目路由
项目路由( project routing )通过在查询处理开始之前应用过滤表达式,将跨项目搜索限制在已关联项目的某个子集内;例如, _alias:my_search_project 。该过滤表达式使用的是 Lucene 查询语法的一个子集。这是一种非常高效的查询限制方式,因为过滤会在任何操作执行之前完成,不匹配过滤条件的关联项目甚至不会在此次查询中被访问。不过,这种过滤方式也存在限制,因为它只能用于排除整个项目。
这种过滤会应用到每个索引表达式上。例如,在没有路由表达式的情况下执行 GET logs/_search 时,它会在源项目以及所有关联项目中查找 logs 索引;但如果使用路由表达式 _alias:my-o11y-project ,则只会使用别名为 my-o11y-project 的项目。
为了方便使用,你可以将常用的路由表达式保存为命名项目路由表达式( named project routing expressions ),并在不同查询之间复用,只需指定表达式名称即可: @routing-expression 。
在 Technical Preview 版本中 ,项目路由仅支持基于 _alias 标签匹配进行过滤;例如, _alias:my-security-* 。在未来版本中,我们计划支持所有标签以及大部分 Lucene 过滤语法。
Query DSL
可以通过请求体字段为搜索指定项目路由:
bash
`
1. GET logs/_search
2. {
3. "project_routing": "_alias:my_search_project"
4. }
`AI写代码
Elasticsearch 查询语言( ES|QL )
你可以通过在主查询之前添加语句来指定项目路由:
ini
`
1. SET project_routing = "_alias:my-project-alias" ;
2. FROM my_index
3. | LIMIT 10
`AI写代码
或者,你也可以在 API 调用中使用 project_routing 查询参数,但在 ES|QL 中,更推荐使用 SET 语法。
优先级规则 :如果同时使用了 SET project_routing 语法,并且 API 也提供了 project_routing 查询参数,则以 SET 语法为准。
项目标签作为字段
项目标签可以通过 _project. 前缀在查询中作为字段使用(用于获取、匹配、聚合和排序)。这是通过创建一种特殊的动态映射类型实现的,并将其挂载到名为 _project 的字段上。这个字段本身没有任何数据,不能直接使用,但它包含与项目标签名称对应的子字段;例如 _project._alias 或 _project.env 。这些字段会返回常量 keyword 值,这些值直接来自内存中存储的项目标签映射。
查询 DSL
在 Query DSL 中,你可以像使用普通字段一样,在任何可以使用字段名的地方使用项目标签字段,例如:
获取:
json
`
1. {
2. "fields": ["count", "_project._id", "_project._alias", "_project.env"]
3. }
`AI写代码
通配符模式也同样适用:
json
`
1. {
2. "fields": ["count", "_project.my-tag-*"]
3. }
`AI写代码
但是默认情况下,项目标签不会出现在输出字段中(即使使用 * 作为字段也一样);你始终需要通过 _project. 前缀显式地将它们包含进来。你不需要将项目标签加入输出字段就可以在匹配或聚合中使用它们;这些功能彼此是独立的。
匹配:
markdown
`
1. "query": {
2. "match": {
3. "_project._alias": "my-project"
4. }
5. }
`AI写代码
对于这个简单示例,项目路由可能是一种更高效的方式;不过,也可以使用更复杂的匹配方式:
bash
`
1. {
2. "query": {
3. "bool": {
4. "should": [
5. { "term": { "_project._alias": "my-project" } },
6. { "term": { "_project.env": "qa" } }
7. ],
8. "minimum_should_match": 1
9. }
10. }
11. }
`AI写代码
与项目路由不同,这里可以使用完整的 Elasticsearch 匹配表达式集,即使在 Technical Preview 版本中也是如此。但需要注意,这会带来性能开销:索引表达式中指定的所有索引仍然会被解析,并且所有关联项目仍然会被访问,即使最终匹配表达式排除了它们的全部数据。
你也可以在项目标签上使用聚合:
bash
`
1. {
2. "size": 0,
3. "aggs": {
4. "by_project": {
5. "terms": {
6. "field": "_project._alias"
7. },
8. "aggs": {
9. "total_count": {
10. "sum": {
11. "field": "int_count"
12. }
13. }
14. }
15. }
16. }
17. }
`AI写代码
并且可以按项目标签进行排序:
markdown
`
1. {
2. "size": 4,
3. "sort": [
4. {
5. "_project._alias": {
6. "order": "asc"
7. }
8. }
9. ],
10. "fields": [
11. "count",
12. "_project._id",
13. "_project._alias"
14. ]
15. }
`AI写代码
ES|QL
要在查询结果中将项目标签作为字段使用,你必须在 FROM 子句中使用 METADATA 关键字显式包含它们:
css
`
1. FROM my_index METADATA _project._alias
2. | LIMIT 5
`AI写代码
通配符可用于包含多个标签:
markdown
`
1. FROM my_index METADATA _project.*
2. | LIMIT 5
`AI写代码
一旦包含,这些标签字段就会像其他字段一样工作,可以被所有 ES|QL 命令使用;例如:
sql
`
1. FROM my_index METADATA _project._alias
2. | WHERE _project._alias == "production-data"
3. | STATS count() by status
`AI写代码
在 ES|QL 中,在使用 project_routing 和在 WHERE 命令中基于项目标签定义过滤条件之间存在一些重要差异。
索引解析:
- 使用
project_routing="..."时,索引只会在指定的项目中解析,因此你不会看到仅存在于被排除项目映射中的字段。 - 而使用简单过滤,例如
WHERE _project._alias LIKE "..."时,索引解析会在所有项目中进行,因此你也会看到仅存在于那些不匹配别名模式的项目中的列。
查询路由:
- 使用
project_routing="..."时,查询只会路由到相关的项目。 - 使用
WHERE _project._alias LIKE "..."时,查询会路由到所有项目,因此你至少需要承担查询协调的成本。
查询执行:
- 使用
project_routing="..."时,查询只会在匹配表达式的节点上执行。 - 使用
WHERE _project._alias LIKE "..."时,查询会路由到所有节点,但 ES|QL 会进行第二阶段优化,并将项目标签替换为常量,对匹配条件的项目执行WHERE true,对不匹配的项目执行WHERE false。在这种情况下,引擎会识别该查询不会返回结果,因此实际上不会发生执行。结论是,你仍然需要承担协调成本和本地重新规划成本,但实际执行将是空操作。
灵活性:
- 在 Technical Preview 版本中,
project_routing仅支持基于项目别名的过滤。未来版本将支持更复杂的语法和所有标签。 - ES|QL 语言支持在任何允许字段名的位置使用项目标签,包括过滤器,即使是非常复杂的表达式。
结论
项目路由和项目标签字段都可以控制跨项目搜索中参与的项目,但它们的用途不同。
当性能最重要时使用项目路由 。无论是在 Query DSL 请求体中设置 project_routing,还是在 ES|QL 中使用 SET project_routing,效果都是一样的:在查询开始之前就排除不匹配的项目;没有索引解析、没有协调开销、没有浪费计算资源。如果你已经明确知道要查询哪些项目,这始终是最优路径。
当你需要灵活性时使用项目标签字段。项目标签在 Query DSL 和 ES|QL 中都像普通字段一样工作。你可以进行过滤、聚合、排序并将其包含在输出中。这支持项目路由无法实现的场景,例如将标签与布尔逻辑组合、按项目分组结果,或按"env""department"等自定义标签过滤。代价是,即使最终过滤掉数据,所有关联项目仍然会被访问。
结合两者可以获得最佳效果。先使用项目路由缩小相关项目范围,然后在查询中使用项目标签字段进行更细粒度逻辑处理。这样既能获得早期过滤带来的性能优势,又能保留完整查询表达能力。