1414import org .elasticsearch .action .search .SearchResponse ;
1515import org .elasticsearch .action .support .ThreadedActionListener ;
1616import org .elasticsearch .client .OriginSettingClient ;
17- import org .elasticsearch .common .xcontent .LoggingDeprecationHandler ;
18- import org .elasticsearch .common .xcontent .NamedXContentRegistry ;
19- import org .elasticsearch .common .xcontent .XContentFactory ;
20- import org .elasticsearch .common .xcontent .XContentParser ;
21- import org .elasticsearch .common .xcontent .XContentType ;
2217import org .elasticsearch .index .query .BoolQueryBuilder ;
2318import org .elasticsearch .index .query .QueryBuilder ;
2419import org .elasticsearch .index .query .QueryBuilders ;
3025import org .elasticsearch .search .SearchHits ;
3126import org .elasticsearch .search .builder .SearchSourceBuilder ;
3227import org .elasticsearch .threadpool .ThreadPool ;
28+ import org .elasticsearch .xpack .core .common .time .TimeUtils ;
3329import org .elasticsearch .xpack .core .ml .job .config .Job ;
3430import org .elasticsearch .xpack .core .ml .job .persistence .AnomalyDetectorsIndex ;
3531import org .elasticsearch .xpack .core .ml .job .persistence .ElasticsearchMappings ;
3834import org .elasticsearch .xpack .core .ml .job .results .Result ;
3935import org .elasticsearch .xpack .ml .MachineLearning ;
4036
41- import java .io .IOException ;
42- import java .io .InputStream ;
4337import java .time .Clock ;
4438import java .time .Instant ;
4539import java .util .ArrayList ;
@@ -85,6 +79,11 @@ public void remove(float requestsPerSec, ActionListener<Boolean> listener, Suppl
8579 .filter (QueryBuilders .existsQuery (ForecastRequestStats .EXPIRY_TIME .getPreferredName ())));
8680 source .size (MAX_FORECASTS );
8781 source .trackTotalHits (true );
82+ source .fetchSource (false );
83+ source .docValueField (Job .ID .getPreferredName (), null );
84+ source .docValueField (ForecastRequestStats .FORECAST_ID .getPreferredName (), null );
85+ source .docValueField (ForecastRequestStats .EXPIRY_TIME .getPreferredName (), "epoch_millis" );
86+
8887
8988 // _doc is the most efficient sort order and will also disable scoring
9089 source .sort (ElasticsearchMappings .ES_DOC );
@@ -101,11 +100,9 @@ private void deleteForecasts(
101100 ActionListener <Boolean > listener ,
102101 Supplier <Boolean > isTimedOutSupplier
103102 ) {
104- List <ForecastRequestStats > forecastsToDelete ;
105- try {
106- forecastsToDelete = findForecastsToDelete (searchResponse );
107- } catch (IOException e ) {
108- listener .onFailure (e );
103+ List <JobForecastId > forecastsToDelete = findForecastsToDelete (searchResponse );
104+ if (forecastsToDelete .isEmpty ()) {
105+ listener .onResponse (true );
109106 return ;
110107 }
111108
@@ -117,7 +114,7 @@ private void deleteForecasts(
117114 DeleteByQueryRequest request = buildDeleteByQuery (forecastsToDelete )
118115 .setRequestsPerSecond (requestsPerSec )
119116 .setAbortOnVersionConflict (false );
120- client .execute (DeleteByQueryAction .INSTANCE , request , new ActionListener <BulkByScrollResponse >() {
117+ client .execute (DeleteByQueryAction .INSTANCE , request , new ActionListener <>() {
121118 @ Override
122119 public void onResponse (BulkByScrollResponse bulkByScrollResponse ) {
123120 try {
@@ -138,39 +135,51 @@ public void onFailure(Exception e) {
138135 });
139136 }
140137
141- private List <ForecastRequestStats > findForecastsToDelete (SearchResponse searchResponse ) throws IOException {
142- List <ForecastRequestStats > forecastsToDelete = new ArrayList <>();
138+ private List <JobForecastId > findForecastsToDelete (SearchResponse searchResponse ) {
139+ List <JobForecastId > forecastsToDelete = new ArrayList <>();
143140
144141 SearchHits hits = searchResponse .getHits ();
145142 if (hits .getTotalHits ().value > MAX_FORECASTS ) {
146143 LOGGER .info ("More than [{}] forecasts were found. This run will only delete [{}] of them" , MAX_FORECASTS , MAX_FORECASTS );
147144 }
148145
149146 for (SearchHit hit : hits .getHits ()) {
150- try (InputStream stream = hit .getSourceRef ().streamInput ();
151- XContentParser parser = XContentFactory .xContent (XContentType .JSON ).createParser (
152- NamedXContentRegistry .EMPTY , LoggingDeprecationHandler .INSTANCE , stream )) {
153- ForecastRequestStats forecastRequestStats = ForecastRequestStats .LENIENT_PARSER .apply (parser , null );
154- if (forecastRequestStats .getExpiryTime ().toEpochMilli () < cutoffEpochMs ) {
155- forecastsToDelete .add (forecastRequestStats );
147+ String expiryTime = stringFieldValueOrNull (hit , ForecastRequestStats .EXPIRY_TIME .getPreferredName ());
148+ if (expiryTime == null ) {
149+ LOGGER .warn ("Forecast request stats document [{}] has a null [{}] field" , hit .getId (),
150+ ForecastRequestStats .EXPIRY_TIME .getPreferredName ());
151+ continue ;
152+ }
153+ long expiryMs = TimeUtils .parseToEpochMs (expiryTime );
154+ if (expiryMs < cutoffEpochMs ) {
155+ JobForecastId idPair = new JobForecastId (
156+ stringFieldValueOrNull (hit , Job .ID .getPreferredName ()),
157+ stringFieldValueOrNull (hit , Forecast .FORECAST_ID .getPreferredName ()));
158+
159+ if (idPair .hasNullValue () == false ) {
160+ forecastsToDelete .add (idPair );
156161 }
162+
157163 }
164+
158165 }
159166 return forecastsToDelete ;
160167 }
161168
162- private DeleteByQueryRequest buildDeleteByQuery (List <ForecastRequestStats > forecastsToDelete ) {
169+ private DeleteByQueryRequest buildDeleteByQuery (List <JobForecastId > ids ) {
163170 DeleteByQueryRequest request = new DeleteByQueryRequest ();
164171 request .setSlices (AbstractBulkByScrollRequest .AUTO_SLICES );
165172
166173 request .indices (RESULTS_INDEX_PATTERN );
167174 BoolQueryBuilder boolQuery = QueryBuilders .boolQuery ().minimumShouldMatch (1 );
168175 boolQuery .must (QueryBuilders .termsQuery (Result .RESULT_TYPE .getPreferredName (),
169176 ForecastRequestStats .RESULT_TYPE_VALUE , Forecast .RESULT_TYPE_VALUE ));
170- for (ForecastRequestStats forecastToDelete : forecastsToDelete ) {
171- boolQuery .should (QueryBuilders .boolQuery ()
172- .must (QueryBuilders .termQuery (Job .ID .getPreferredName (), forecastToDelete .getJobId ()))
173- .must (QueryBuilders .termQuery (Forecast .FORECAST_ID .getPreferredName (), forecastToDelete .getForecastId ())));
177+ for (JobForecastId jobForecastId : ids ) {
178+ if (jobForecastId .hasNullValue () == false ) {
179+ boolQuery .should (QueryBuilders .boolQuery ()
180+ .must (QueryBuilders .termQuery (Job .ID .getPreferredName (), jobForecastId .jobId ))
181+ .must (QueryBuilders .termQuery (Forecast .FORECAST_ID .getPreferredName (), jobForecastId .forecastId )));
182+ }
174183 }
175184 QueryBuilder query = QueryBuilders .boolQuery ().filter (boolQuery );
176185 request .setQuery (query );
@@ -180,4 +189,18 @@ private DeleteByQueryRequest buildDeleteByQuery(List<ForecastRequestStats> forec
180189
181190 return request ;
182191 }
192+
193+ private static class JobForecastId {
194+ private final String jobId ;
195+ private final String forecastId ;
196+
197+ private JobForecastId (String jobId , String forecastId ) {
198+ this .jobId = jobId ;
199+ this .forecastId = forecastId ;
200+ }
201+
202+ boolean hasNullValue () {
203+ return jobId == null || forecastId == null ;
204+ }
205+ }
183206}
0 commit comments