Point in time API

edit

A search request by default executes against the most recent visible data of the target indices, which is called point in time. Elasticsearch pit (point in time) is a lightweight view into the state of the data as it existed when initiated. In some cases, it’s preferred to perform multiple search requests using the same point in time. For example, if refreshes happen between search_after requests, then the results of those requests might not be consistent as changes happening between searches are only visible to the more recent point in time.

Prerequisites

edit
  • If the Elasticsearch security features are enabled, you must have the read index privilege for the target data stream, index, or alias.

    To search a point in time (PIT) for an alias, you must have the read index privilege for the alias’s data streams or indices.

Request body

edit
index_filter
(Optional, query object Allows to filter indices if the provided query rewrites to match_none on every shard.

Examples

edit

A point in time must be opened explicitly before being used in search requests. The keep_alive parameter tells Elasticsearch how long it should keep a point in time alive, e.g. ?keep_alive=5m.

resp = client.open_point_in_time(
    index="my-index-000001",
    keep_alive="1m",
)
print(resp)
response = client.open_point_in_time(
  index: 'my-index-000001',
  keep_alive: '1m'
)
puts response
const response = await client.openPointInTime({
  index: "my-index-000001",
  keep_alive: "1m",
});
console.log(response);
POST /my-index-000001/_pit?keep_alive=1m

The result from the above request includes a id, which should be passed to the id of the pit parameter of a search request.

resp = client.search(
    size=100,
    query={
        "match": {
            "title": "elasticsearch"
        }
    },
    pit={
        "id": "46ToAwMDaWR5BXV1aWQyKwZub2RlXzMAAAAAAAAAACoBYwADaWR4BXV1aWQxAgZub2RlXzEAAAAAAAAAAAEBYQADaWR5BXV1aWQyKgZub2RlXzIAAAAAAAAAAAwBYgACBXV1aWQyAAAFdXVpZDEAAQltYXRjaF9hbGw_gAAAAA==",
        "keep_alive": "1m"
    },
)
print(resp)
const response = await client.search({
  size: 100,
  query: {
    match: {
      title: "elasticsearch",
    },
  },
  pit: {
    id: "46ToAwMDaWR5BXV1aWQyKwZub2RlXzMAAAAAAAAAACoBYwADaWR4BXV1aWQxAgZub2RlXzEAAAAAAAAAAAEBYQADaWR5BXV1aWQyKgZub2RlXzIAAAAAAAAAAAwBYgACBXV1aWQyAAAFdXVpZDEAAQltYXRjaF9hbGw_gAAAAA==",
    keep_alive: "1m",
  },
});
console.log(response);
POST /_search  
{
    "size": 100,  
    "query": {
        "match" : {
            "title" : "elasticsearch"
        }
    },
    "pit": {
	    "id":  "46ToAwMDaWR5BXV1aWQyKwZub2RlXzMAAAAAAAAAACoBYwADaWR4BXV1aWQxAgZub2RlXzEAAAAAAAAAAAEBYQADaWR5BXV1aWQyKgZub2RlXzIAAAAAAAAAAAwBYgACBXV1aWQyAAAFdXVpZDEAAQltYXRjaF9hbGw_gAAAAA==", 
	    "keep_alive": "1m"  
    }
}

A search request with the pit parameter must not specify index, routing, or preference as these parameters are copied from the point in time.

Just like regular searches, you can use from and size to page through search results, up to the first 10,000 hits. If you want to retrieve more hits, use PIT with search_after.

The id parameter tells Elasticsearch to execute the request using contexts from this point in time.

The keep_alive parameter tells Elasticsearch how long it should extend the time to live of the point in time.

The open point in time request and each subsequent search request can return different id; thus always use the most recently received id for the next search request.

Keeping point in time alive

edit

The keep_alive parameter, which is passed to a open point in time request and search request, extends the time to live of the corresponding point in time. The value (e.g. 1m, see Time units) does not need to be long enough to process all data — it just needs to be long enough for the next request.

Normally, the background merge process optimizes the index by merging together smaller segments to create new, bigger segments. Once the smaller segments are no longer needed they are deleted. However, open point-in-times prevent the old segments from being deleted since they are still in use.

Keeping older segments alive means that more disk space and file handles are needed. Ensure that you have configured your nodes to have ample free file handles. See File Descriptors.

Additionally, if a segment contains deleted or updated documents then the point in time must keep track of whether each document in the segment was live at the time of the initial search request. Ensure that your nodes have sufficient heap space if you have many open point-in-times on an index that is subject to ongoing deletes or updates. Note that a point-in-time doesn’t prevent its associated indices from being deleted.

You can check how many point-in-times (i.e, search contexts) are open with the nodes stats API:

$params = [
    'metric' => 'indices',
    'index_metric' => 'search',
];
$response = $client->nodes()->stats($params);
resp = client.nodes.stats(
    metric="indices",
    index_metric="search",
)
print(resp)
response = client.nodes.stats(
  metric: 'indices',
  index_metric: 'search'
)
puts response
res, err := es.Nodes.Stats(
	es.Nodes.Stats.WithMetric([]string{"indices"}...),
	es.Nodes.Stats.WithIndexMetric([]string{"search"}...),
)
fmt.Println(res, err)
const response = await client.nodes.stats({
  metric: "indices",
  index_metric: "search",
});
console.log(response);
GET /_nodes/stats/indices/search

Close point in time API

edit

Point-in-time is automatically closed when its keep_alive has been elapsed. However keeping point-in-times has a cost, as discussed in the previous section. Point-in-times should be closed as soon as they are no longer used in search requests.

resp = client.close_point_in_time(
    id="46ToAwMDaWR5BXV1aWQyKwZub2RlXzMAAAAAAAAAACoBYwADaWR4BXV1aWQxAgZub2RlXzEAAAAAAAAAAAEBYQADaWR5BXV1aWQyKgZub2RlXzIAAAAAAAAAAAwBYgACBXV1aWQyAAAFdXVpZDEAAQltYXRjaF9hbGw_gAAAAA==",
)
print(resp)
response = client.close_point_in_time(
  body: {
    id: '46ToAwMDaWR5BXV1aWQyKwZub2RlXzMAAAAAAAAAACoBYwADaWR4BXV1aWQxAgZub2RlXzEAAAAAAAAAAAEBYQADaWR5BXV1aWQyKgZub2RlXzIAAAAAAAAAAAwBYgACBXV1aWQyAAAFdXVpZDEAAQltYXRjaF9hbGw_gAAAAA=='
  }
)
puts response
const response = await client.closePointInTime({
  id: "46ToAwMDaWR5BXV1aWQyKwZub2RlXzMAAAAAAAAAACoBYwADaWR4BXV1aWQxAgZub2RlXzEAAAAAAAAAAAEBYQADaWR5BXV1aWQyKgZub2RlXzIAAAAAAAAAAAwBYgACBXV1aWQyAAAFdXVpZDEAAQltYXRjaF9hbGw_gAAAAA==",
});
console.log(response);
DELETE /_pit
{
    "id" : "46ToAwMDaWR5BXV1aWQyKwZub2RlXzMAAAAAAAAAACoBYwADaWR4BXV1aWQxAgZub2RlXzEAAAAAAAAAAAEBYQADaWR5BXV1aWQyKgZub2RlXzIAAAAAAAAAAAwBYgACBXV1aWQyAAAFdXVpZDEAAQltYXRjaF9hbGw_gAAAAA=="
}

The API returns the following response:

{
   "succeeded": true, 
   "num_freed": 3     
}

If true, all search contexts associated with the point-in-time id are successfully closed

The number of search contexts have been successfully closed

Search slicing

edit

When paging through a large number of documents, it can be helpful to split the search into multiple slices to consume them independently:

resp = client.search(
    slice={
        "id": 0,
        "max": 2
    },
    query={
        "match": {
            "message": "foo"
        }
    },
    pit={
        "id": "46ToAwMDaWR5BXV1aWQyKwZub2RlXzMAAAAAAAAAACoBYwADaWR4BXV1aWQxAgZub2RlXzEAAAAAAAAAAAEBYQADaWR5BXV1aWQyKgZub2RlXzIAAAAAAAAAAAwBYgACBXV1aWQyAAAFdXVpZDEAAQltYXRjaF9hbGw_gAAAAA=="
    },
)
print(resp)

resp1 = client.search(
    slice={
        "id": 1,
        "max": 2
    },
    pit={
        "id": "46ToAwMDaWR5BXV1aWQyKwZub2RlXzMAAAAAAAAAACoBYwADaWR4BXV1aWQxAgZub2RlXzEAAAAAAAAAAAEBYQADaWR5BXV1aWQyKgZub2RlXzIAAAAAAAAAAAwBYgACBXV1aWQyAAAFdXVpZDEAAQltYXRjaF9hbGw_gAAAAA=="
    },
    query={
        "match": {
            "message": "foo"
        }
    },
)
print(resp1)
const response = await client.search({
  slice: {
    id: 0,
    max: 2,
  },
  query: {
    match: {
      message: "foo",
    },
  },
  pit: {
    id: "46ToAwMDaWR5BXV1aWQyKwZub2RlXzMAAAAAAAAAACoBYwADaWR4BXV1aWQxAgZub2RlXzEAAAAAAAAAAAEBYQADaWR5BXV1aWQyKgZub2RlXzIAAAAAAAAAAAwBYgACBXV1aWQyAAAFdXVpZDEAAQltYXRjaF9hbGw_gAAAAA==",
  },
});
console.log(response);

const response1 = await client.search({
  slice: {
    id: 1,
    max: 2,
  },
  pit: {
    id: "46ToAwMDaWR5BXV1aWQyKwZub2RlXzMAAAAAAAAAACoBYwADaWR4BXV1aWQxAgZub2RlXzEAAAAAAAAAAAEBYQADaWR5BXV1aWQyKgZub2RlXzIAAAAAAAAAAAwBYgACBXV1aWQyAAAFdXVpZDEAAQltYXRjaF9hbGw_gAAAAA==",
  },
  query: {
    match: {
      message: "foo",
    },
  },
});
console.log(response1);
GET /_search
{
  "slice": {
    "id": 0,                      
    "max": 2                      
  },
  "query": {
    "match": {
      "message": "foo"
    }
  },
  "pit": {
    "id": "46ToAwMDaWR5BXV1aWQyKwZub2RlXzMAAAAAAAAAACoBYwADaWR4BXV1aWQxAgZub2RlXzEAAAAAAAAAAAEBYQADaWR5BXV1aWQyKgZub2RlXzIAAAAAAAAAAAwBYgACBXV1aWQyAAAFdXVpZDEAAQltYXRjaF9hbGw_gAAAAA=="
  }
}

GET /_search
{
  "slice": {
    "id": 1,
    "max": 2
  },
  "pit": {
    "id": "46ToAwMDaWR5BXV1aWQyKwZub2RlXzMAAAAAAAAAACoBYwADaWR4BXV1aWQxAgZub2RlXzEAAAAAAAAAAAEBYQADaWR5BXV1aWQyKgZub2RlXzIAAAAAAAAAAAwBYgACBXV1aWQyAAAFdXVpZDEAAQltYXRjaF9hbGw_gAAAAA=="
  },
  "query": {
    "match": {
      "message": "foo"
    }
  }
}

The id of the slice

The maximum number of slices

The result from the first request returns documents belonging to the first slice (id: 0) and the result from the second request returns documents in the second slice. Since the maximum number of slices is set to 2 the union of the results of the two requests is equivalent to the results of a point-in-time search without slicing. By default the splitting is done first on the shards, then locally on each shard. The local splitting partitions the shard into contiguous ranges based on Lucene document IDs.

For instance if the number of shards is equal to 2 and the user requested 4 slices then the slices 0 and 2 are assigned to the first shard and the slices 1 and 3 are assigned to the second shard.

The same point-in-time ID should be used for all slices. If different PIT IDs are used, then slices can overlap and miss documents. This is because the splitting criterion is based on Lucene document IDs, which are not stable across changes to the index.