8
8
9
9
package org .opensearch .plugin .insights .core .exporter ;
10
10
11
+ import static org .opensearch .plugin .insights .settings .QueryInsightsSettings .DEFAULT_DELETE_AFTER_VALUE ;
12
+
13
+ import java .time .Instant ;
14
+ import java .time .LocalDate ;
11
15
import java .time .ZoneOffset ;
12
16
import java .time .ZonedDateTime ;
13
17
import java .time .format .DateTimeFormatter ;
18
+ import java .time .format .DateTimeParseException ;
14
19
import java .util .List ;
20
+ import java .util .Map ;
21
+ import java .util .concurrent .TimeUnit ;
15
22
import org .apache .logging .log4j .LogManager ;
16
23
import org .apache .logging .log4j .Logger ;
24
+ import org .opensearch .action .admin .indices .delete .DeleteIndexRequest ;
17
25
import org .opensearch .action .bulk .BulkRequestBuilder ;
18
26
import org .opensearch .action .bulk .BulkResponse ;
19
27
import org .opensearch .action .index .IndexRequest ;
20
28
import org .opensearch .client .Client ;
29
+ import org .opensearch .cluster .metadata .IndexMetadata ;
21
30
import org .opensearch .common .unit .TimeValue ;
22
31
import org .opensearch .common .xcontent .XContentFactory ;
23
32
import org .opensearch .core .action .ActionListener ;
@@ -36,6 +45,7 @@ public final class LocalIndexExporter implements QueryInsightsExporter {
36
45
private final Logger logger = LogManager .getLogger ();
37
46
private final Client client ;
38
47
private DateTimeFormatter indexPattern ;
48
+ private int deleteAfter ;
39
49
40
50
/**
41
51
* Constructor of LocalIndexExporter
@@ -46,6 +56,7 @@ public final class LocalIndexExporter implements QueryInsightsExporter {
46
56
public LocalIndexExporter (final Client client , final DateTimeFormatter indexPattern ) {
47
57
this .indexPattern = indexPattern ;
48
58
this .client = client ;
59
+ this .deleteAfter = DEFAULT_DELETE_AFTER_VALUE ;
49
60
}
50
61
51
62
/**
@@ -61,11 +72,9 @@ public DateTimeFormatter getIndexPattern() {
61
72
* Setter of indexPattern
62
73
*
63
74
* @param indexPattern index pattern
64
- * @return the current LocalIndexExporter
65
75
*/
66
- public LocalIndexExporter setIndexPattern (DateTimeFormatter indexPattern ) {
76
+ void setIndexPattern (DateTimeFormatter indexPattern ) {
67
77
this .indexPattern = indexPattern ;
68
- return this ;
69
78
}
70
79
71
80
/**
@@ -75,15 +84,15 @@ public LocalIndexExporter setIndexPattern(DateTimeFormatter indexPattern) {
75
84
*/
76
85
@ Override
77
86
public void export (final List <SearchQueryRecord > records ) {
78
- if (records == null || records .size () == 0 ) {
87
+ if (records == null || records .isEmpty () ) {
79
88
return ;
80
89
}
81
90
try {
82
- final String index = getDateTimeFromFormat ();
91
+ final String indexName = buildLocalIndexName ();
83
92
final BulkRequestBuilder bulkRequestBuilder = client .prepareBulk ().setTimeout (TimeValue .timeValueMinutes (1 ));
84
93
for (SearchQueryRecord record : records ) {
85
94
bulkRequestBuilder .add (
86
- new IndexRequest (index ).source (record .toXContent (XContentFactory .jsonBuilder (), ToXContent .EMPTY_PARAMS ))
95
+ new IndexRequest (indexName ).source (record .toXContent (XContentFactory .jsonBuilder (), ToXContent .EMPTY_PARAMS ))
87
96
);
88
97
}
89
98
bulkRequestBuilder .execute (new ActionListener <BulkResponse >() {
@@ -110,7 +119,91 @@ public void close() {
110
119
logger .debug ("Closing the LocalIndexExporter.." );
111
120
}
112
121
113
- private String getDateTimeFromFormat () {
114
- return indexPattern .format (ZonedDateTime .now (ZoneOffset .UTC ));
122
+ /**
123
+ * Builds the local index name using the current UTC datetime
124
+ *
125
+ * @return A string representing the index name in the format "top_queries-YYYY.MM.dd-01234".
126
+ */
127
+ String buildLocalIndexName () {
128
+ return indexPattern .format (ZonedDateTime .now (ZoneOffset .UTC )) + "-" + generateLocalIndexDateHash ();
129
+ }
130
+
131
+ /**
132
+ * Set local index exporter data retention period
133
+ *
134
+ * @param deleteAfter the number of days after which Top N local indices should be deleted
135
+ */
136
+ public void setDeleteAfter (final int deleteAfter ) {
137
+ this .deleteAfter = deleteAfter ;
138
+ }
139
+
140
+ /**
141
+ * Delete Top N local indices older than the configured data retention period
142
+ *
143
+ * @param indexMetadataMap Map of index name {@link String} to {@link IndexMetadata}
144
+ */
145
+ public void deleteExpiredIndices (final Map <String , IndexMetadata > indexMetadataMap ) {
146
+ long expirationMillisLong = System .currentTimeMillis () - TimeUnit .DAYS .toMillis (deleteAfter );
147
+ for (Map .Entry <String , IndexMetadata > entry : indexMetadataMap .entrySet ()) {
148
+ String indexName = entry .getKey ();
149
+ if (!isTopQueriesIndex (indexName )) {
150
+ continue ;
151
+ }
152
+ if (entry .getValue ().getCreationDate () <= expirationMillisLong ) {
153
+ // delete this index
154
+ client .admin ().indices ().delete (new DeleteIndexRequest (indexName ));
155
+ }
156
+ }
157
+ }
158
+
159
+ /**
160
+ * Validates if the input string is a Query Insights local index name
161
+ * in the format "top_queries-YYYY.MM.dd-XXXXX".
162
+ *
163
+ * @param indexName the string to validate.
164
+ * @return {@code true} if the string is valid, {@code false} otherwise.
165
+ */
166
+ static boolean isTopQueriesIndex (String indexName ) {
167
+ // Split the input string by '-'
168
+ String [] parts = indexName .split ("-" );
169
+
170
+ // Check if the string has exactly 3 parts
171
+ if (parts .length != 3 ) {
172
+ return false ;
173
+ }
174
+
175
+ // Validate the first part is "top_queries"
176
+ if (!"top_queries" .equals (parts [0 ])) {
177
+ return false ;
178
+ }
179
+
180
+ // Validate the second part is a valid date in "YYYY.MM.dd" format
181
+ DateTimeFormatter formatter = DateTimeFormatter .ofPattern ("yyyy.MM.dd" );
182
+ try {
183
+ LocalDate .parse (parts [1 ], formatter );
184
+ } catch (DateTimeParseException e ) {
185
+ return false ;
186
+ }
187
+
188
+ // Validate the third part is exactly 5 digits
189
+ if (!parts [2 ].matches ("\\ d{5}" )) {
190
+ return false ;
191
+ }
192
+
193
+ return true ;
194
+ }
195
+
196
+ /**
197
+ * Generates a consistent 5-digit numeric hash based on the current UTC date.
198
+ * The generated hash is deterministic, meaning it will return the same result for the same date.
199
+ *
200
+ * @return A 5-digit numeric string representation of the current date's hash.
201
+ */
202
+ public static String generateLocalIndexDateHash () {
203
+ // Get the current date in UTC (yyyy-MM-dd format)
204
+ String currentDate = DateTimeFormatter .ISO_LOCAL_DATE .format (Instant .now ().atOffset (ZoneOffset .UTC ).toLocalDate ());
205
+
206
+ // Generate a 5-digit numeric hash from the date's hashCode
207
+ return String .format ("%05d" , Math .abs (currentDate .hashCode ()) % 100000 );
115
208
}
116
209
}
0 commit comments