I've noticed a strange thing about the behaviour of ISearchResponse.HitsMetadata.Total property in NEST library. Whenever I delete a document async and want to immediately retrieve the remaining documents from Elasticsearch, the HitsMetadata.Total field, which is available on ISearchResponse object, almost never gets updated correctly. It ususally indicates the total number of documents at the moment preceding the deletion operation. The behaviour gets back to normal when I pause the execution of a request for at least 700 milliseconds as if NEST (or maybe Elasticsearch itself) needed more time to update the property's state. I'm new to using NEST and Elasticsearch so it is posssible that I'm doing something wrong here or I might not fully understand the workings of the library, yet I have spent quite a lot of time on the problem and can't get around it. As a result the pagination metadata I send to the client get computed wrongly. I'm using NEST 6.6.0 and Elasticsearch 6.6.2.
The DELETE action:
[HttpDelete("errors/{index}/{logeventId}")]
public async Task<IActionResult> DeleteErrorLog([FromRoute] string index, [FromRoute] string logeventId)
{
if (string.IsNullOrEmpty(index))
{
return BadRequest();
}
if (string.IsNullOrEmpty(logeventId))
{
return BadRequest();
}
var getResponse = await _client.GetAsync<Logevent>(new GetRequest(index, typeof(Logevent), logeventId));
if(!getResponse.Found)
{
return NotFound();
}
var deleteResponse = await _client.DeleteAsync(new DeleteRequest(index, typeof(Logevent), logeventId));
if (!deleteResponse.IsValid)
{
throw new Exception($"Deleting document id {logeventId} failed");
}
return NoContent();
}
The GET action:
[HttpGet("errors/{index}", Name = "GetErrors")]
public async Task<IActionResult> GetErrorLogs([FromRoute] string index,
[FromQuery]int pageNumber = 1, [FromQuery] int pageSize = 5)
{
if (string.IsNullOrEmpty(index))
{
return BadRequest();
}
if(pageSize > MAX_PAGE_SIZE || pageSize < 1)
{
pageSize = 5;
}
if(pageNumber < 1)
{
pageNumber = 1;
}
var from = (pageNumber - 1) * pageSize;
ISearchResponse<Logevent> searchResponse = await GetSearchResponse(index, from, pageSize);
if (searchResponse.Hits.Count == 0)
{
return NotFound();
}
int totalPages = GetTotalPages(searchResponse, pageSize);
var previousPageLink = pageNumber > 1 ?
CreateGetLogsForIndexResourceUri(ResourceUriType.PreviousPage, pageNumber, pageSize, "GetErrors") : null;
var nextPageLink = pageNumber < totalPages ?
CreateGetLogsForIndexResourceUri(ResourceUriType.NextPage, pageNumber, pageSize, "GetErrors") : null;
/* HERE, WHEN EXECUTED IMMMEDIATELY (UP TO 700 MILISSECONDS, THE
totalCount FIELD GETS MISCALCULATED AS IT RETURNS THE VALUE PRECEDING
THE DELETION OF A DOCUMENT
*/
var totalCount = searchResponse.HitsMetadata.Total;
var count = searchResponse.Hits.Count;
var paginationMetadata = new
{
totalCount = searchResponse.HitsMetadata.Total,
totalPages,
pageSize,
currentPage = pageNumber,
previousPageLink,
nextPageLink
};
Response.Headers.Add("X-Pagination", Newtonsoft.Json.JsonConvert.SerializeObject(paginationMetadata));
var logeventsDtos = Mapper.Map<IEnumerable<LogeventDto>>(searchResponse.Hits);
return Ok(logeventsDtos);
}
The GetSearchResponseMethod:
private async Task<ISearchResponse<Logevent>> GetSearchResponse(string index, int from, int pageSize)
{
return await _client.SearchAsync<Logevent>(s =>
s.Index(index).From(from).Size(pageSize).Query(q => q.MatchAll()));
}
The code on the client side initiating the server-side actions:
async deleteLogevent(item){
this.deleteDialog = false;
let logeventId = item.logeventId;
let level = this.defaultSelected.name;
let index = 'logstash'.concat('-', this.defaultSelected.value, '-', this.date);
LogsService.deleteLogevent(level, index, logeventId).then(response => {
if(response.status == 204){
let logeventIndex = this.logs.findIndex(element => {return element.logeventId === item.logeventId});
this.logs.splice(logeventIndex, 1);
LogsService.getLogs(level, index, this.pageNumber).then(reloadResponse => {
this.logs.splice(0);
reloadResponse.data.forEach(element => {
this.logs.push(element)
});
this.setPaginationMetadata(reloadResponse.headers["x-pagination"]);
})
}
}).catch(error => {
})