14
14
import org .elasticsearch .action .search .SearchResponse ;
15
15
import org .elasticsearch .action .support .ThreadedActionListener ;
16
16
import 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 ;
22
17
import org .elasticsearch .index .query .BoolQueryBuilder ;
23
18
import org .elasticsearch .index .query .QueryBuilder ;
24
19
import org .elasticsearch .index .query .QueryBuilders ;
30
25
import org .elasticsearch .search .SearchHits ;
31
26
import org .elasticsearch .search .builder .SearchSourceBuilder ;
32
27
import org .elasticsearch .threadpool .ThreadPool ;
28
+ import org .elasticsearch .xpack .core .common .time .TimeUtils ;
33
29
import org .elasticsearch .xpack .core .ml .job .config .Job ;
34
30
import org .elasticsearch .xpack .core .ml .job .persistence .AnomalyDetectorsIndex ;
35
31
import org .elasticsearch .xpack .core .ml .job .persistence .ElasticsearchMappings ;
38
34
import org .elasticsearch .xpack .core .ml .job .results .Result ;
39
35
import org .elasticsearch .xpack .ml .MachineLearning ;
40
36
41
- import java .io .IOException ;
42
- import java .io .InputStream ;
43
37
import java .time .Clock ;
44
38
import java .time .Instant ;
45
39
import java .util .ArrayList ;
@@ -85,6 +79,11 @@ public void remove(ActionListener<Boolean> listener, Supplier<Boolean> isTimedOu
85
79
.filter (QueryBuilders .existsQuery (ForecastRequestStats .EXPIRY_TIME .getPreferredName ())));
86
80
source .size (MAX_FORECASTS );
87
81
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
+
88
87
89
88
// _doc is the most efficient sort order and will also disable scoring
90
89
source .sort (ElasticsearchMappings .ES_DOC );
@@ -96,11 +95,9 @@ public void remove(ActionListener<Boolean> listener, Supplier<Boolean> isTimedOu
96
95
}
97
96
98
97
private void deleteForecasts (SearchResponse searchResponse , ActionListener <Boolean > listener , Supplier <Boolean > isTimedOutSupplier ) {
99
- List <ForecastRequestStats > forecastsToDelete ;
100
- try {
101
- forecastsToDelete = findForecastsToDelete (searchResponse );
102
- } catch (IOException e ) {
103
- listener .onFailure (e );
98
+ List <JobForecastId > forecastsToDelete = findForecastsToDelete (searchResponse );
99
+ if (forecastsToDelete .isEmpty ()) {
100
+ listener .onResponse (true );
104
101
return ;
105
102
}
106
103
@@ -131,39 +128,51 @@ public void onFailure(Exception e) {
131
128
});
132
129
}
133
130
134
- private List <ForecastRequestStats > findForecastsToDelete (SearchResponse searchResponse ) throws IOException {
135
- List <ForecastRequestStats > forecastsToDelete = new ArrayList <>();
131
+ private List <JobForecastId > findForecastsToDelete (SearchResponse searchResponse ) {
132
+ List <JobForecastId > forecastsToDelete = new ArrayList <>();
136
133
137
134
SearchHits hits = searchResponse .getHits ();
138
135
if (hits .getTotalHits ().value > MAX_FORECASTS ) {
139
136
LOGGER .info ("More than [{}] forecasts were found. This run will only delete [{}] of them" , MAX_FORECASTS , MAX_FORECASTS );
140
137
}
141
138
142
139
for (SearchHit hit : hits .getHits ()) {
143
- try (InputStream stream = hit .getSourceRef ().streamInput ();
144
- XContentParser parser = XContentFactory .xContent (XContentType .JSON ).createParser (
145
- NamedXContentRegistry .EMPTY , LoggingDeprecationHandler .INSTANCE , stream )) {
146
- ForecastRequestStats forecastRequestStats = ForecastRequestStats .LENIENT_PARSER .apply (parser , null );
147
- if (forecastRequestStats .getExpiryTime ().toEpochMilli () < cutoffEpochMs ) {
148
- forecastsToDelete .add (forecastRequestStats );
140
+ String expiryTime = stringFieldValueOrNull (hit , ForecastRequestStats .EXPIRY_TIME .getPreferredName ());
141
+ if (expiryTime == null ) {
142
+ LOGGER .warn ("Forecast request stats document [{}] has a null [{}] field" , hit .getId (),
143
+ ForecastRequestStats .EXPIRY_TIME .getPreferredName ());
144
+ continue ;
145
+ }
146
+ long expiryMs = TimeUtils .parseToEpochMs (expiryTime );
147
+ if (expiryMs < cutoffEpochMs ) {
148
+ JobForecastId idPair = new JobForecastId (
149
+ stringFieldValueOrNull (hit , Job .ID .getPreferredName ()),
150
+ stringFieldValueOrNull (hit , Forecast .FORECAST_ID .getPreferredName ()));
151
+
152
+ if (idPair .hasNullValue () == false ) {
153
+ forecastsToDelete .add (idPair );
149
154
}
155
+
150
156
}
157
+
151
158
}
152
159
return forecastsToDelete ;
153
160
}
154
161
155
- private DeleteByQueryRequest buildDeleteByQuery (List <ForecastRequestStats > forecastsToDelete ) {
162
+ private DeleteByQueryRequest buildDeleteByQuery (List <JobForecastId > ids ) {
156
163
DeleteByQueryRequest request = new DeleteByQueryRequest ();
157
164
request .setSlices (AbstractBulkByScrollRequest .AUTO_SLICES );
158
165
159
166
request .indices (RESULTS_INDEX_PATTERN );
160
167
BoolQueryBuilder boolQuery = QueryBuilders .boolQuery ().minimumShouldMatch (1 );
161
168
boolQuery .must (QueryBuilders .termsQuery (Result .RESULT_TYPE .getPreferredName (),
162
169
ForecastRequestStats .RESULT_TYPE_VALUE , Forecast .RESULT_TYPE_VALUE ));
163
- for (ForecastRequestStats forecastToDelete : forecastsToDelete ) {
164
- boolQuery .should (QueryBuilders .boolQuery ()
165
- .must (QueryBuilders .termQuery (Job .ID .getPreferredName (), forecastToDelete .getJobId ()))
166
- .must (QueryBuilders .termQuery (Forecast .FORECAST_ID .getPreferredName (), forecastToDelete .getForecastId ())));
170
+ for (JobForecastId jobForecastId : ids ) {
171
+ if (jobForecastId .hasNullValue () == false ) {
172
+ boolQuery .should (QueryBuilders .boolQuery ()
173
+ .must (QueryBuilders .termQuery (Job .ID .getPreferredName (), jobForecastId .jobId ))
174
+ .must (QueryBuilders .termQuery (Forecast .FORECAST_ID .getPreferredName (), jobForecastId .forecastId )));
175
+ }
167
176
}
168
177
QueryBuilder query = QueryBuilders .boolQuery ().filter (boolQuery );
169
178
request .setQuery (query );
@@ -173,4 +182,18 @@ private DeleteByQueryRequest buildDeleteByQuery(List<ForecastRequestStats> forec
173
182
174
183
return request ;
175
184
}
185
+
186
+ private static class JobForecastId {
187
+ private final String jobId ;
188
+ private final String forecastId ;
189
+
190
+ private JobForecastId (String jobId , String forecastId ) {
191
+ this .jobId = jobId ;
192
+ this .forecastId = forecastId ;
193
+ }
194
+
195
+ boolean hasNullValue () {
196
+ return jobId == null || forecastId == null ;
197
+ }
198
+ }
176
199
}
0 commit comments