Skip to content

Commit 46f9f23

Browse files
jaortega527ijallison
authored andcommitted
CMR-11452 - Remove hour break down in bulk-index/after-date-time & bulk-index/between-date-time to improve performance (#2487)
* CMR-11452 - Removed hour "chunking" from bulk-index/after-date-time and bulk-index/between-date-time * CMR-11452 - Updated default max window to 720 hours (30 days) and added support for header override to allow any size window * CMR-11452 - Small improvement to error message when after_date_time range is too large * CMR-11452 - Update header retrieval to use parseBoolean instead of using string comparison
1 parent 62ee725 commit 46f9f23

9 files changed

Lines changed: 110 additions & 98 deletions

File tree

bootstrap-app/README.md

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -253,8 +253,19 @@ Operator can use the `start_index` parameter to index concepts with sequence num
253253
### Bulk index concepts newer than a given date-time
254254

255255
The `/after_date_time` endpoint is retained for compatibility. New callers should use
256-
`/between_date_time`, which bounds the request and splits large ranges into smaller indexing chunks.
256+
`/between_date_time`, which bounds the request to an explicit time range.
257257
Compatibility requests to `/after_date_time` are implicitly bounded from `date_time` to the request timestamp and are rejected when that window exceeds the configured maximum.
258+
The maximum is 30 days by default.
259+
260+
To allow an authorized request to exceed the configured maximum time range:
261+
262+
curl -i \
263+
-X POST \
264+
-H "CMR-Bulk-Index-Ignore-Time-Range-Limit: true" \
265+
"http://localhost:3006/bulk_index/after_date_time?date_time=2015-02-02T10:00:00Z"
266+
267+
Only a case-insensitive value of `true` disables the time-range limit. If the header is omitted or
268+
has any other value, the configured maximum remains enforced.
258269

259270
For all providers and all system concepts:
260271

@@ -295,8 +306,8 @@ To provide a range in hours instead of an explicit end date-time:
295306
"http://localhost:3006/bulk_index/between_date_time?start_date_time=2015-02-02T10:00:00Z&hours=2"
296307

297308
The `start_date_time` parameter is required. Callers can provide either `end_date_time` or `hours`.
298-
If neither is provided, bootstrap uses the end of the start date's day. Internally, bootstrap enforces
299-
smaller chunks across the requested time range before publishing indexing work.
309+
If neither is provided, bootstrap uses the end of the start date's day. Bootstrap publishes one
310+
indexing request per provider covering the complete requested time range.
300311

301312
### Bulk index all system concepts (tags/acls/access-groups)
302313

bootstrap-app/src/cmr/bootstrap/api/bulk_index.clj

Lines changed: 25 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@
1010
[cmr.common.services.errors :as errors]
1111
[cmr.common.time-keeper :as time-keeper]))
1212

13+
(def ^:private ignore-after-date-time-limit-header
14+
"cmr-bulk-index-ignore-time-range-limit")
15+
1316
(defn- parse-date-time-param
1417
[_param-name date-time]
1518
(if-let [date-time-value (date-time-parser/try-parse-datetime date-time)]
@@ -77,6 +80,11 @@
7780
:invalid-data
7881
(msg/after-date-time-window-exceeded max-window-hours)))))
7982

83+
(defn- ignore-after-date-time-limit?
84+
[headers]
85+
(Boolean/parseBoolean
86+
(get headers ignore-after-date-time-limit-header)))
87+
8088
(defn index-provider
8189
"Index all the collections and granules for a given provider."
8290
[context provider-id-map params]
@@ -112,20 +120,23 @@
112120

113121
(defn data-later-than-date-time
114122
"Index all data with a revision-date later than the given date-time, with the upper bound set to the request time."
115-
[context body params]
116-
(let [dispatcher (api-util/get-dispatcher context params :index-data-between-date-time)
117-
provider-ids (get body "provider_ids")
118-
date-time (:date_time params)
119-
start-date-time (parse-date-time-param :date_time date-time)
120-
end-date-time (time-keeper/now)]
121-
(validate-date-time-range start-date-time end-date-time)
122-
(validate-after-date-time-window start-date-time end-date-time)
123-
{:status 202
124-
:body {:message (msg/data-later-than-date-time
125-
params
126-
(service/index-data-between-date-time
127-
context dispatcher provider-ids start-date-time end-date-time)
128-
date-time)}}))
123+
([context body params]
124+
(data-later-than-date-time context body params {}))
125+
([context body params headers]
126+
(let [dispatcher (api-util/get-dispatcher context params :index-data-between-date-time)
127+
provider-ids (get body "provider_ids")
128+
date-time (:date_time params)
129+
start-date-time (parse-date-time-param :date_time date-time)
130+
end-date-time (time-keeper/now)]
131+
(validate-date-time-range start-date-time end-date-time)
132+
(when-not (ignore-after-date-time-limit? headers)
133+
(validate-after-date-time-window start-date-time end-date-time))
134+
{:status 202
135+
:body {:message (msg/data-later-than-date-time
136+
params
137+
(service/index-data-between-date-time
138+
context dispatcher provider-ids start-date-time end-date-time)
139+
date-time)}})))
129140

130141
(defn data-between-date-time
131142
"Index data with revision-date in the requested date-time range.

bootstrap-app/src/cmr/bootstrap/api/messages.clj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@
9898
(defn after-date-time-window-exceeded
9999
[max-window-hours]
100100
(format (str "The requested time window exceeds the /bulk_index/after_date_time limit of %d hours. "
101-
"Please use a smaller date_time value so the range to now is within %d hours.")
101+
"Please use a later date_time value so the range to now is within %d hours.")
102102
max-window-hours
103103
max-window-hours))
104104

bootstrap-app/src/cmr/bootstrap/api/routes.clj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,9 +61,9 @@
6161
(POST "/collections" {:keys [request-context body params]}
6262
(acl/verify-ingest-management-permission request-context :update)
6363
(bulk-index/index-collection request-context body params))
64-
(POST "/after_date_time" {:keys [request-context body params]}
64+
(POST "/after_date_time" {:keys [request-context body params headers]}
6565
(acl/verify-ingest-management-permission request-context :update)
66-
(bulk-index/data-later-than-date-time request-context body params))
66+
(bulk-index/data-later-than-date-time request-context body params headers))
6767
(POST "/between_date_time" {:keys [request-context body params]}
6868
(acl/verify-ingest-management-permission request-context :update)
6969
(bulk-index/data-between-date-time request-context body params))

bootstrap-app/src/cmr/bootstrap/config.clj

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -46,15 +46,10 @@
4646
{:default 1
4747
:type Long})
4848

49-
(defconfig bulk-index-between-date-time-window-hours
50-
"Maximum number of hours for each /bulk_index/between_date_time indexing chunk."
51-
{:default 1
52-
:type Long})
53-
5449
(defconfig bulk-index-after-date-time-max-window-hours
5550
"Maximum number of hours allowed for /bulk_index/after_date_time. Larger windows should use
5651
/bulk_index/between_date_time explicitly."
57-
{:default 168
52+
{:default 720
5853
:type Long})
5954

6055
(declare initialize-kms-on-boot)

bootstrap-app/src/cmr/bootstrap/services/dispatch/impl/message_queue.clj

Lines changed: 6 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
"Functions implementing the dispatch protocol to support bootstrap operations using a message
33
queue."
44
(:require
5-
[clj-time.core :as time]
65
[cmr.bootstrap.api.messages-bulk-index :as msg]
76
[cmr.bootstrap.config :as config]
87
[cmr.bootstrap.data.bulk-index :as bulk-index]
@@ -123,40 +122,22 @@
123122
(message-queue/bootstrap-provider-event provider-id nil date-time)))
124123
(info (msg/index-data-later-than-date-time-done))))
125124

126-
(defn- date-time-chunks
127-
"Returns half-open date-time ranges no larger than chunk-hours."
128-
[start-date-time end-date-time chunk-hours]
129-
(let [chunk-period (time/hours chunk-hours)]
130-
(loop [chunk-start start-date-time
131-
chunks []]
132-
(if (time/before? chunk-start end-date-time)
133-
(let [candidate-end (time/plus chunk-start chunk-period)
134-
chunk-end (if (time/before? candidate-end end-date-time)
135-
candidate-end
136-
end-date-time)]
137-
(recur chunk-end (conj chunks [chunk-start chunk-end])))
138-
chunks))))
139-
140125
(defn- index-data-between-date-time
141126
"Bulk index all the concepts with revision dates between the given date-times."
142127
[_this context provider-ids start-date-time end-date-time]
143128
(let [provider-ids (if (seq provider-ids)
144129
provider-ids
145130
;; all providers including CMR provider which is for system concepts
146-
(conj (map :provider-id (helper/get-providers (:system context))) "CMR"))
147-
chunk-hours (max 1 (config/bulk-index-between-date-time-window-hours))
148-
chunks (date-time-chunks start-date-time end-date-time chunk-hours)]
149-
(doseq [provider-id provider-ids
150-
[chunk-start chunk-end] chunks]
131+
(conj (map :provider-id (helper/get-providers (:system context))) "CMR"))]
132+
(doseq [provider-id provider-ids]
151133
(message-queue/publish-bootstrap-concepts-event
152134
context
153135
(message-queue/bootstrap-provider-between-date-time-event
154-
provider-id chunk-start chunk-end)))
155-
(info (format "Published %d bulk index messages between [%s] and [%s] using %d hour chunks."
156-
(* (count provider-ids) (count chunks))
136+
provider-id start-date-time end-date-time)))
137+
(info (format "Published %d bulk index messages between [%s] and [%s], one per provider."
138+
(count provider-ids)
157139
start-date-time
158-
end-date-time
159-
chunk-hours))))
140+
end-date-time))))
160141

161142
(defn- fingerprint-variables
162143
"Update fingerprints of variables. If a provider is passed, only update fingerprints of the

bootstrap-app/test/cmr/bootstrap/test/api/bulk_index_test.clj

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -197,13 +197,49 @@
197197
bootstrap-config/bulk-index-after-date-time-max-window-hours (constantly 3)]
198198
(is (= {:type :invalid-data
199199
:errors [(str "The requested time window exceeds the /bulk_index/after_date_time limit of 3 hours. "
200-
"Please use a smaller date_time value so the range to now is within 3 hours.")]}
200+
"Please use a later date_time value so the range to now is within 3 hours.")]}
201201
(service-error
202202
#(bulk-index/data-later-than-date-time
203203
context
204204
{}
205205
{:date_time "2026-05-13T01:00:00Z"})))))))
206206

207+
(deftest data-later-than-date-time-supports-time-range-limit-override
208+
(let [context {:system :system}
209+
service-call (atom nil)]
210+
(with-redefs [api-util/get-dispatcher (constantly :dispatcher)
211+
time-keeper/now (constantly (time/date-time 2026 5 13 5 0))
212+
bootstrap-config/bulk-index-after-date-time-max-window-hours (constantly 3)
213+
service/index-data-between-date-time
214+
(fn [& args]
215+
(reset! service-call args)
216+
{:message "indexed"})]
217+
(testing "When the override header is true, then a range over the configured limit is accepted"
218+
(is (= 202
219+
(:status
220+
(bulk-index/data-later-than-date-time
221+
context
222+
{"provider_ids" ["PROV1"]}
223+
{:date_time "2026-05-13T01:00:00Z"}
224+
{"cmr-bulk-index-ignore-time-range-limit" "TRUE"}))))
225+
(is (= [context
226+
:dispatcher
227+
["PROV1"]
228+
(time/date-time 2026 5 13 1 0)
229+
(time/date-time 2026 5 13 5 0)]
230+
@service-call)))
231+
232+
(testing "When the override header is not true, then the configured range limit is enforced"
233+
(doseq [header-value ["false" "invalid"]]
234+
(is (= :invalid-data
235+
(:type
236+
(service-error
237+
#(bulk-index/data-later-than-date-time
238+
context
239+
{}
240+
{:date_time "2026-05-13T01:00:00Z"}
241+
{"cmr-bulk-index-ignore-time-range-limit" header-value}))))))))))
242+
207243
(deftest data-later-than-date-time-validates-range
208244
(let [context {:system :system}]
209245
(with-redefs [api-util/get-dispatcher (constantly :dispatcher)

bootstrap-app/test/cmr/bootstrap/test/services/dispatch/impl/message_queue_test.clj

Lines changed: 21 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -2,30 +2,12 @@
22
(:require
33
[clj-time.core :as time]
44
[clojure.test :refer [deftest is testing]]
5-
[cmr.bootstrap.config :as config]
65
[cmr.bootstrap.data.bulk-index :as bulk-index-data]
76
[cmr.bootstrap.data.message-queue :as message-queue]
87
[cmr.bootstrap.embedded-system-helper :as helper]
98
[cmr.bootstrap.services.dispatch.core :as dispatch]
109
[cmr.bootstrap.services.dispatch.impl.message-queue :as message-queue-dispatcher]))
1110

12-
(deftest date-time-chunks-creates-half-open-ranges
13-
(let [start (time/date-time 2026 5 13 1 0)
14-
end (time/date-time 2026 5 13 4 30)]
15-
(is (= [[(time/date-time 2026 5 13 1 0)
16-
(time/date-time 2026 5 13 2 0)]
17-
[(time/date-time 2026 5 13 2 0)
18-
(time/date-time 2026 5 13 3 0)]
19-
[(time/date-time 2026 5 13 3 0)
20-
(time/date-time 2026 5 13 4 0)]
21-
[(time/date-time 2026 5 13 4 0)
22-
(time/date-time 2026 5 13 4 30)]]
23-
(#'message-queue-dispatcher/date-time-chunks start end 1)))))
24-
25-
(deftest date-time-chunks-returns-empty-when-range-is-empty
26-
(let [start (time/date-time 2026 5 13 1 0)]
27-
(is (= [] (#'message-queue-dispatcher/date-time-chunks start start 1)))))
28-
2911
(deftest bootstrap-provider-between-date-time-event-test
3012
(let [start (time/date-time 2026 5 13 1 0)
3113
end (time/date-time 2026 5 13 2 0)]
@@ -38,34 +20,31 @@
3820
start
3921
end)))))
4022

41-
(deftest index-data-between-date-time-publishes-provider-chunks
23+
(deftest index-data-between-date-time-publishes-full-range-per-provider
4224
(let [published (atom [])
43-
context {:system {:providers [{:provider-id "PROV1"}]}}
25+
context {:system {:providers [{:provider-id "PROV1"}
26+
{:provider-id "PROV2"}]}}
4427
start (time/date-time 2026 5 13 1 0)
4528
end (time/date-time 2026 5 13 3 30)]
46-
(with-redefs [config/bulk-index-between-date-time-window-hours (constantly 1)
47-
message-queue/publish-bootstrap-concepts-event
29+
(with-redefs [message-queue/publish-bootstrap-concepts-event
4830
(fn [_context msg]
4931
(swap! published conj msg))]
50-
(dispatch/index-data-between-date-time
51-
(message-queue-dispatcher/->MessageQueueDispatcher)
52-
context
53-
["PROV1"]
54-
start
55-
end)
56-
(is (= [{:action :index-provider-between-date-time
57-
:provider-id "PROV1"
58-
:start-date-time (time/date-time 2026 5 13 1 0)
59-
:end-date-time (time/date-time 2026 5 13 2 0)}
60-
{:action :index-provider-between-date-time
61-
:provider-id "PROV1"
62-
:start-date-time (time/date-time 2026 5 13 2 0)
63-
:end-date-time (time/date-time 2026 5 13 3 0)}
64-
{:action :index-provider-between-date-time
65-
:provider-id "PROV1"
66-
:start-date-time (time/date-time 2026 5 13 3 0)
67-
:end-date-time (time/date-time 2026 5 13 3 30)}]
68-
@published)))))
32+
(testing "When provider IDs are supplied, then one full-range event is published per provider"
33+
(dispatch/index-data-between-date-time
34+
(message-queue-dispatcher/->MessageQueueDispatcher)
35+
context
36+
["PROV1" "PROV2"]
37+
start
38+
end)
39+
(is (= [{:action :index-provider-between-date-time
40+
:provider-id "PROV1"
41+
:start-date-time (time/date-time 2026 5 13 1 0)
42+
:end-date-time (time/date-time 2026 5 13 3 30)}
43+
{:action :index-provider-between-date-time
44+
:provider-id "PROV2"
45+
:start-date-time (time/date-time 2026 5 13 1 0)
46+
:end-date-time (time/date-time 2026 5 13 3 30)}]
47+
@published))))))
6948

7049
(deftest index-data-between-date-time-expands-empty-provider-list
7150
(let [published (atom [])
@@ -74,11 +53,10 @@
7453
end (time/date-time 2026 5 13 2 0)]
7554
(with-redefs [helper/get-providers (constantly [{:provider-id "PROV1"}
7655
{:provider-id "PROV2"}])
77-
config/bulk-index-between-date-time-window-hours (constantly 0)
7856
message-queue/publish-bootstrap-concepts-event
7957
(fn [_context msg]
8058
(swap! published conj msg))]
81-
(testing "empty provider ids uses all providers plus CMR and clamps chunk hours to one"
59+
(testing "When provider IDs are omitted, then one full-range event is published for all providers plus CMR"
8260
(dispatch/index-data-between-date-time
8361
(message-queue-dispatcher/->MessageQueueDispatcher)
8462
context

system-int-test/test/cmr/system_int_test/bootstrap/bulk_index/concepts_test.clj

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -318,13 +318,13 @@
318318
(deftest ^:oracle bulk-index-after-date-time-rejects-large-window
319319
(s/only-with-real-database
320320
(try
321-
(dev-sys-util/freeze-time! "3016-01-10T00:00:00Z")
321+
(dev-sys-util/freeze-time! "3016-02-01T00:00:00Z")
322322
(let [{:keys [status errors]} (bootstrap/bulk-index-after-date-time
323-
"3016-01-02T00:00:00Z"
323+
"3016-01-01T00:00:00Z"
324324
{tc/token-header (tc/echo-system-token)}
325325
["PROV1"])]
326326
(is (= 422 status))
327-
(is (re-find #"The requested time window exceeds the /bulk_index/after_date_time limit of 168 hours"
327+
(is (re-find #"The requested time window exceeds the /bulk_index/after_date_time limit of 720 hours"
328328
(first errors))))
329329
(finally
330330
(dev-sys-util/clear-current-time!)))))

0 commit comments

Comments
 (0)