1、PIT是什么?

PIT的全称为Point in time,elastic官方的定义是这样的:

A search request by default executes against the most recent visible data of the target indices, which is called point in time.

我的理解是这样的,一个检索请求执行时,它是在执行检索前最近的这个时刻点索引内所有可见数据范围内执行检索,而不是针对其他时刻点索引内所有可见数据的范围内,关键字是【最近】的时刻点,这个点很重要,需要细细的琢磨、理解。

那么pit要解决什么问题呢?它要解决的是,一个检索请求在不同时间点检索结果出现不一致的问题,正常来说,同样检索条件,应该有相同的输出,但由于索引内数据的变化,比如增加了新数据,则会导致一个请求在不同时刻点检索出现不一样的结果,或者在search_after的请求之间,索引数据发生变化,就会影响search_after的检索结果,那么如何消除时间变量对检索结果的影响呢?这就是pit要解决的问题了。

2、search_after+pit全量检索方案

这个方案,首先利用pit创建时间点,保留索引当前状态,创建pit后,索引内数据的变化不会影响使用该pit检索的结果:

POST /new_tag_202411/_pit?keep_alive=1m

上面的命令会生成一个pid id。

keep_alive参数的含义是:时间点上下文的有效时间,意思是说,索引的数据状态最长保持时间,如果超过这个时间,同一个检索请求的输出就不保证是一样的了。

接下来使用pit构造检索请求:


GET /_search
{
  "query": {
    "bool": {
      "filter": [
        {"term":{
          "rule_id":1
        }}
      ]
    }
  },
  "sort": [
    {
      "doc_id": {
        "order": "asc"
      }
    }
  ],
  "pit": {
    "id": "yvaYBAEObmV3X3RhZ18yMDI0MTEWRGxYMlBuODlSUm01dkdRSW8xT09oQQABFllkSWhOeXJTUUZpODFBX1BHWldNR1EAAQAAAAAAAHRdFmpZa0tWNHpFVGNlYnVDdlVIQ3VqM2cAARZEbFgyUG44OVJSbTV2R1FJbzFPT2hBAAA=",
    "keep_alive": "1m"
  },
  "size": 5
}

返回的数据:

{
  "pit_id": "yvaYBAEObmV3X3RhZ18yMDI0MTEWRGxYMlBuODlSUm01dkdRSW8xT09oQQABFllkSWhOeXJTUUZpODFBX1BHWldNR1EAAQAAAAAAAAJRFlZ2ejdMLWxzVFpDTk9RV0V0V3JEZHcAARZEbFgyUG44OVJSbTV2R1FJbzFPT2hBAAA=",
  "took": 2,
  "timed_out": false,
  "_shards": {
    "total": 1,
    "successful": 1,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total": {
      "value": 10000,
      "relation": "gte"
    },
    "max_score": null,
    "hits": [
      {
        "_index": "new_tag_202411",
        "_id": "京CJXXXX_weather_RAIN_MODE_1_1732923345279_1732923525287_1732930057549",
        "_score": null,
        "_source": {
          "car_id": "京CJXXXX",
          "create_time": "2024-12-23 21:44:36",
          "doc_id": 11000,
          "end_time": 1732923525287,
          "rule_id": 1,
          "rule_priority": 1,
          "rule_version": "2.1.0.0",
          "start_time": 1732923345279,
          "tag_additional_info": {},
          "tag_name": "weather",
          "user": "liupeng",
          "weather": "RAIN_MODE"
        },
        "sort": [
          1732923345279,
          10999
        ]
      }
    ]
  }
}

以上是pit的使用方法,再加上search_after的功能就可以实现索引内数据的全量检索:

GET /_search
{
  "sort": [
    {
      "start_time": {
        "order": "asc"
      }
    }
  ],
  "pit": {
    "id": "yvaYBAEObmV3X3RhZ18yMDI0MTEWRGxYMlBuODlSUm01dkdRSW8xT09oQQABFllkSWhOeXJTUUZpODFBX1BHWldNR1EAAQAAAAAAA528FlZ2ejdMLWxzVFpDTk9RV0V0V3JEZHcAARZEbFgyUG44OVJSbTV2R1FJbzFPT2hBAAA=",
    "keep_alive": "10m"
  },
  "size": 10,
  "search_after": [1732923345279,
          10999]
}

需要注意的是,pit_id以及search_after需要不断更新为上一次请求返回的结果。关于search_after的用法,可以参考这篇文章:Elasticsearch滚动查询官方推荐方案:search_after检索实现(golang)

3、滚动查询全量检索方案

elastic的滚动查询,之前已经分享过:快速掌握Elasticsearch检索之二:滚动查询获取全量数据(golang),首先是创建一个滚动查询:


GET /new_tag_202411/_search?scroll=10m
{
  "size": 1,
  "sort":[
    {
      "doc_id":{
        "order": "asc"
      }
    }
  ]
}

这个请求会返回一个scroll_id以及查询到的数据:


{
    "_scroll_id": "FGluY2x1ZGVfY29udGV4dF91dWlkDXF1ZXJ5QW5kRmV0Y2gBFlZ2ejdMLWxzVFpDTk9RV0V0V3JEZHcAAAAAAAO4TRZZZEloTnlyU1FGaTgxQV9QR1pXTUdR",
    "took": 1,
    "timed_out": false,
    "_shards": {
        "total": 1,
        "successful": 1,
        "skipped": 0,
        "failed": 0
    },
    "hits": {
        "total": {
            "value": 11000,
            "relation": "eq"
        },
        "max_score": null,
        "hits": [
            {
                "_index": "new_tag_202411",
                "_id": "xxxxx_xxxx_JUNCTION_STRAIGHT_1_1732982399960_1732982409280_1733030220901",
                "_score": null,
                "_source": {
                    "adc_behavior": "JUNCTION_STRAIGHT",
                    "car_id": "xxxxxx",
                    "create_time": "2024-12-23 21:34:00",
                    "doc_id": 1,
                    "end_time": 1732982409280,
                    "rule_id": 1,
                    "rule_priority": 1,
                    "rule_version": "2.1.0.0",
                    "start_time": 1732982399960,
                    "tag_name": "adc_xxxx",
                    "user": "liupeng"
                },
                "sort": [
                    1
                ]
            }
        ]
    }
}

每次的滚动查询需要更新scroll_id,使用上一次请求返回的id。

4、两种方案的比较

首先,search_after+pit,pit是和时间相关的概念,在某一个时刻创建pit,所有的检索请求都可以使用此pit进行检索,那么也就是说,多个检索请求是可以共用pit的。

而对于滚动查询,每个检索请求都会创建一个滚动查询的请求,当查询量较大时,如果es节点创建的滚动上下文超过最大限制(一次滚动查询就会打开一个上下文),那么检索将会失效,无法检索到任何内容。

但对于pit不会存在这个问题,因为pit和检索请求的数量是解耦的,只和时间点有关系,pit可以共用,但滚动查询却不能,所以我个人认为search_after+pit的方案才是全量检索的最佳方案,当然如果你的项目对数据实时性要求不高,完全可以只使用search_after实现全量检索也未尝不可。

 

Logo

火山引擎开发者社区是火山引擎打造的AI技术生态平台,聚焦Agent与大模型开发,提供豆包系列模型(图像/视频/视觉)、智能分析与会话工具,并配套评测集、动手实验室及行业案例库。社区通过技术沙龙、挑战赛等活动促进开发者成长,新用户可领50万Tokens权益,助力构建智能应用。

更多推荐