stac-fastapi-opensearch 6.1.0__py3-none-any.whl → 6.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- stac_fastapi/opensearch/app.py +1 -1
- stac_fastapi/opensearch/config.py +1 -0
- stac_fastapi/opensearch/database_logic.py +89 -67
- stac_fastapi/opensearch/version.py +1 -1
- {stac_fastapi_opensearch-6.1.0.dist-info → stac_fastapi_opensearch-6.2.0.dist-info}/METADATA +79 -4
- stac_fastapi_opensearch-6.2.0.dist-info/RECORD +10 -0
- stac_fastapi_opensearch-6.1.0.dist-info/RECORD +0 -10
- {stac_fastapi_opensearch-6.1.0.dist-info → stac_fastapi_opensearch-6.2.0.dist-info}/WHEEL +0 -0
- {stac_fastapi_opensearch-6.1.0.dist-info → stac_fastapi_opensearch-6.2.0.dist-info}/entry_points.txt +0 -0
- {stac_fastapi_opensearch-6.1.0.dist-info → stac_fastapi_opensearch-6.2.0.dist-info}/top_level.txt +0 -0
stac_fastapi/opensearch/app.py
CHANGED
|
@@ -118,7 +118,7 @@ post_request_model = create_post_request_model(search_extensions)
|
|
|
118
118
|
app_config = {
|
|
119
119
|
"title": os.getenv("STAC_FASTAPI_TITLE", "stac-fastapi-opensearch"),
|
|
120
120
|
"description": os.getenv("STAC_FASTAPI_DESCRIPTION", "stac-fastapi-opensearch"),
|
|
121
|
-
"api_version": os.getenv("STAC_FASTAPI_VERSION", "6.
|
|
121
|
+
"api_version": os.getenv("STAC_FASTAPI_VERSION", "6.2.0"),
|
|
122
122
|
"settings": settings,
|
|
123
123
|
"extensions": extensions,
|
|
124
124
|
"client": CoreClient(
|
|
@@ -4,7 +4,7 @@ import asyncio
|
|
|
4
4
|
import logging
|
|
5
5
|
from base64 import urlsafe_b64decode, urlsafe_b64encode
|
|
6
6
|
from copy import deepcopy
|
|
7
|
-
from typing import Any, Dict, Iterable, List, Optional, Tuple, Type
|
|
7
|
+
from typing import Any, Dict, Iterable, List, Optional, Tuple, Type
|
|
8
8
|
|
|
9
9
|
import attr
|
|
10
10
|
import orjson
|
|
@@ -26,7 +26,7 @@ from stac_fastapi.opensearch.config import (
|
|
|
26
26
|
AsyncOpensearchSettings as AsyncSearchSettings,
|
|
27
27
|
)
|
|
28
28
|
from stac_fastapi.opensearch.config import OpensearchSettings as SyncSearchSettings
|
|
29
|
-
from stac_fastapi.sfeos_helpers import filter
|
|
29
|
+
from stac_fastapi.sfeos_helpers import filter as filter_module
|
|
30
30
|
from stac_fastapi.sfeos_helpers.database import (
|
|
31
31
|
apply_free_text_filter_shared,
|
|
32
32
|
apply_intersects_filter_shared,
|
|
@@ -34,8 +34,6 @@ from stac_fastapi.sfeos_helpers.database import (
|
|
|
34
34
|
delete_item_index_shared,
|
|
35
35
|
get_queryables_mapping_shared,
|
|
36
36
|
index_alias_by_collection_id,
|
|
37
|
-
index_by_collection_id,
|
|
38
|
-
indices,
|
|
39
37
|
mk_actions,
|
|
40
38
|
mk_item_id,
|
|
41
39
|
populate_sort_shared,
|
|
@@ -55,15 +53,18 @@ from stac_fastapi.sfeos_helpers.mappings import (
|
|
|
55
53
|
COLLECTIONS_INDEX,
|
|
56
54
|
DEFAULT_SORT,
|
|
57
55
|
ES_COLLECTIONS_MAPPINGS,
|
|
58
|
-
ES_ITEMS_MAPPINGS,
|
|
59
|
-
ES_ITEMS_SETTINGS,
|
|
60
56
|
ITEM_INDICES,
|
|
61
57
|
ITEMS_INDEX_PREFIX,
|
|
62
58
|
Geometry,
|
|
63
59
|
)
|
|
60
|
+
from stac_fastapi.sfeos_helpers.search_engine import (
|
|
61
|
+
BaseIndexInserter,
|
|
62
|
+
BaseIndexSelector,
|
|
63
|
+
IndexInsertionFactory,
|
|
64
|
+
IndexSelectorFactory,
|
|
65
|
+
)
|
|
64
66
|
from stac_fastapi.types.errors import ConflictError, NotFoundError
|
|
65
67
|
from stac_fastapi.types.links import resolve_links
|
|
66
|
-
from stac_fastapi.types.rfc3339 import DateTimeType
|
|
67
68
|
from stac_fastapi.types.stac import Collection, Item
|
|
68
69
|
|
|
69
70
|
logger = logging.getLogger(__name__)
|
|
@@ -104,33 +105,6 @@ async def create_collection_index() -> None:
|
|
|
104
105
|
await client.close()
|
|
105
106
|
|
|
106
107
|
|
|
107
|
-
async def create_item_index(collection_id: str) -> None:
|
|
108
|
-
"""
|
|
109
|
-
Create the index for Items. The settings of the index template will be used implicitly.
|
|
110
|
-
|
|
111
|
-
Args:
|
|
112
|
-
collection_id (str): Collection identifier.
|
|
113
|
-
|
|
114
|
-
Returns:
|
|
115
|
-
None
|
|
116
|
-
|
|
117
|
-
"""
|
|
118
|
-
client = AsyncSearchSettings().create_client
|
|
119
|
-
|
|
120
|
-
index_name = f"{index_by_collection_id(collection_id)}-000001"
|
|
121
|
-
exists = await client.indices.exists(index=index_name)
|
|
122
|
-
if not exists:
|
|
123
|
-
await client.indices.create(
|
|
124
|
-
index=index_name,
|
|
125
|
-
body={
|
|
126
|
-
"aliases": {index_alias_by_collection_id(collection_id): {}},
|
|
127
|
-
"mappings": ES_ITEMS_MAPPINGS,
|
|
128
|
-
"settings": ES_ITEMS_SETTINGS,
|
|
129
|
-
},
|
|
130
|
-
)
|
|
131
|
-
await client.close()
|
|
132
|
-
|
|
133
|
-
|
|
134
108
|
async def delete_item_index(collection_id: str) -> None:
|
|
135
109
|
"""Delete the index for items in a collection.
|
|
136
110
|
|
|
@@ -152,6 +126,9 @@ class DatabaseLogic(BaseDatabaseLogic):
|
|
|
152
126
|
async_settings: AsyncSearchSettings = attr.ib(factory=AsyncSearchSettings)
|
|
153
127
|
sync_settings: SyncSearchSettings = attr.ib(factory=SyncSearchSettings)
|
|
154
128
|
|
|
129
|
+
async_index_selector: BaseIndexSelector = attr.ib(init=False)
|
|
130
|
+
async_index_inserter: BaseIndexInserter = attr.ib(init=False)
|
|
131
|
+
|
|
155
132
|
client = attr.ib(init=False)
|
|
156
133
|
sync_client = attr.ib(init=False)
|
|
157
134
|
|
|
@@ -159,6 +136,10 @@ class DatabaseLogic(BaseDatabaseLogic):
|
|
|
159
136
|
"""Initialize clients after the class is instantiated."""
|
|
160
137
|
self.client = self.async_settings.create_client
|
|
161
138
|
self.sync_client = self.sync_settings.create_client
|
|
139
|
+
self.async_index_inserter = IndexInsertionFactory.create_insertion_strategy(
|
|
140
|
+
self.client
|
|
141
|
+
)
|
|
142
|
+
self.async_index_selector = IndexSelectorFactory.create_selector(self.client)
|
|
162
143
|
|
|
163
144
|
item_serializer: Type[ItemSerializer] = attr.ib(default=ItemSerializer)
|
|
164
145
|
collection_serializer: Type[CollectionSerializer] = attr.ib(
|
|
@@ -234,15 +215,23 @@ class DatabaseLogic(BaseDatabaseLogic):
|
|
|
234
215
|
with the index for the Collection as the target index and the combined `mk_item_id` as the document id.
|
|
235
216
|
"""
|
|
236
217
|
try:
|
|
237
|
-
|
|
218
|
+
response = await self.client.search(
|
|
238
219
|
index=index_alias_by_collection_id(collection_id),
|
|
239
|
-
|
|
220
|
+
body={
|
|
221
|
+
"query": {"term": {"_id": mk_item_id(item_id, collection_id)}},
|
|
222
|
+
"size": 1,
|
|
223
|
+
},
|
|
240
224
|
)
|
|
225
|
+
if response["hits"]["total"]["value"] == 0:
|
|
226
|
+
raise NotFoundError(
|
|
227
|
+
f"Item {item_id} does not exist inside Collection {collection_id}"
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
return response["hits"]["hits"][0]["_source"]
|
|
241
231
|
except exceptions.NotFoundError:
|
|
242
232
|
raise NotFoundError(
|
|
243
233
|
f"Item {item_id} does not exist inside Collection {collection_id}"
|
|
244
234
|
)
|
|
245
|
-
return item["_source"]
|
|
246
235
|
|
|
247
236
|
async def get_queryables_mapping(self, collection_id: str = "*") -> dict:
|
|
248
237
|
"""Retrieve mapping of Queryables for search.
|
|
@@ -296,31 +285,21 @@ class DatabaseLogic(BaseDatabaseLogic):
|
|
|
296
285
|
|
|
297
286
|
@staticmethod
|
|
298
287
|
def apply_datetime_filter(
|
|
299
|
-
search: Search,
|
|
300
|
-
) -> Search:
|
|
288
|
+
search: Search, datetime: Optional[str]
|
|
289
|
+
) -> Tuple[Search, Dict[str, Optional[str]]]:
|
|
301
290
|
"""Apply a filter to search on datetime, start_datetime, and end_datetime fields.
|
|
302
291
|
|
|
303
292
|
Args:
|
|
304
293
|
search: The search object to filter.
|
|
305
|
-
|
|
306
|
-
- A single datetime string (e.g., "2023-01-01T12:00:00")
|
|
307
|
-
- A datetime range string (e.g., "2023-01-01/2023-12-31")
|
|
308
|
-
- A datetime object
|
|
309
|
-
- A tuple of (start_datetime, end_datetime)
|
|
294
|
+
datetime: Optional[str]
|
|
310
295
|
|
|
311
296
|
Returns:
|
|
312
297
|
The filtered search object.
|
|
313
298
|
"""
|
|
314
|
-
|
|
315
|
-
return search
|
|
299
|
+
datetime_search = return_date(datetime)
|
|
316
300
|
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
datetime_search = return_date(interval)
|
|
320
|
-
except (ValueError, TypeError) as e:
|
|
321
|
-
# Handle invalid interval formats if return_date fails
|
|
322
|
-
logger.error(f"Invalid interval format: {interval}, error: {e}")
|
|
323
|
-
return search
|
|
301
|
+
if not datetime_search:
|
|
302
|
+
return search, datetime_search
|
|
324
303
|
|
|
325
304
|
if "eq" in datetime_search:
|
|
326
305
|
# For exact matches, include:
|
|
@@ -387,7 +366,10 @@ class DatabaseLogic(BaseDatabaseLogic):
|
|
|
387
366
|
),
|
|
388
367
|
]
|
|
389
368
|
|
|
390
|
-
return
|
|
369
|
+
return (
|
|
370
|
+
search.query(Q("bool", should=should, minimum_should_match=1)),
|
|
371
|
+
datetime_search,
|
|
372
|
+
)
|
|
391
373
|
|
|
392
374
|
@staticmethod
|
|
393
375
|
def apply_bbox_filter(search: Search, bbox: List):
|
|
@@ -484,7 +466,7 @@ class DatabaseLogic(BaseDatabaseLogic):
|
|
|
484
466
|
otherwise the original Search object.
|
|
485
467
|
"""
|
|
486
468
|
if _filter is not None:
|
|
487
|
-
es_query =
|
|
469
|
+
es_query = filter_module.to_es(await self.get_queryables_mapping(), _filter)
|
|
488
470
|
search = search.filter(es_query)
|
|
489
471
|
|
|
490
472
|
return search
|
|
@@ -511,6 +493,7 @@ class DatabaseLogic(BaseDatabaseLogic):
|
|
|
511
493
|
token: Optional[str],
|
|
512
494
|
sort: Optional[Dict[str, Dict[str, str]]],
|
|
513
495
|
collection_ids: Optional[List[str]],
|
|
496
|
+
datetime_search: Dict[str, Optional[str]],
|
|
514
497
|
ignore_unavailable: bool = True,
|
|
515
498
|
) -> Tuple[Iterable[Dict[str, Any]], Optional[int], Optional[str]]:
|
|
516
499
|
"""Execute a search query with limit and other optional parameters.
|
|
@@ -521,6 +504,7 @@ class DatabaseLogic(BaseDatabaseLogic):
|
|
|
521
504
|
token (Optional[str]): The token used to return the next set of results.
|
|
522
505
|
sort (Optional[Dict[str, Dict[str, str]]]): Specifies how the results should be sorted.
|
|
523
506
|
collection_ids (Optional[List[str]]): The collection ids to search.
|
|
507
|
+
datetime_search (Dict[str, Optional[str]]): Datetime range used for index selection.
|
|
524
508
|
ignore_unavailable (bool, optional): Whether to ignore unavailable collections. Defaults to True.
|
|
525
509
|
|
|
526
510
|
Returns:
|
|
@@ -537,7 +521,9 @@ class DatabaseLogic(BaseDatabaseLogic):
|
|
|
537
521
|
search_body: Dict[str, Any] = {}
|
|
538
522
|
query = search.query.to_dict() if search.query else None
|
|
539
523
|
|
|
540
|
-
index_param =
|
|
524
|
+
index_param = await self.async_index_selector.select_indexes(
|
|
525
|
+
collection_ids, datetime_search
|
|
526
|
+
)
|
|
541
527
|
if len(index_param) > ES_MAX_URL_LENGTH - 300:
|
|
542
528
|
index_param = ITEM_INDICES
|
|
543
529
|
query = add_collections_to_body(collection_ids, query)
|
|
@@ -614,6 +600,7 @@ class DatabaseLogic(BaseDatabaseLogic):
|
|
|
614
600
|
geometry_geohash_grid_precision: int,
|
|
615
601
|
geometry_geotile_grid_precision: int,
|
|
616
602
|
datetime_frequency_interval: str,
|
|
603
|
+
datetime_search,
|
|
617
604
|
ignore_unavailable: Optional[bool] = True,
|
|
618
605
|
):
|
|
619
606
|
"""Return aggregations of STAC Items."""
|
|
@@ -647,7 +634,10 @@ class DatabaseLogic(BaseDatabaseLogic):
|
|
|
647
634
|
if k in aggregations
|
|
648
635
|
}
|
|
649
636
|
|
|
650
|
-
index_param =
|
|
637
|
+
index_param = await self.async_index_selector.select_indexes(
|
|
638
|
+
collection_ids, datetime_search
|
|
639
|
+
)
|
|
640
|
+
|
|
651
641
|
search_task = asyncio.create_task(
|
|
652
642
|
self.client.search(
|
|
653
643
|
index=index_param,
|
|
@@ -840,8 +830,13 @@ class DatabaseLogic(BaseDatabaseLogic):
|
|
|
840
830
|
item = await self.async_prep_create_item(
|
|
841
831
|
item=item, base_url=base_url, exist_ok=exist_ok
|
|
842
832
|
)
|
|
833
|
+
|
|
834
|
+
target_index = await self.async_index_inserter.get_target_index(
|
|
835
|
+
collection_id, item
|
|
836
|
+
)
|
|
837
|
+
|
|
843
838
|
await self.client.index(
|
|
844
|
-
index=
|
|
839
|
+
index=target_index,
|
|
845
840
|
id=mk_item_id(item_id, collection_id),
|
|
846
841
|
body=item,
|
|
847
842
|
refresh=refresh,
|
|
@@ -920,13 +915,28 @@ class DatabaseLogic(BaseDatabaseLogic):
|
|
|
920
915
|
script = operations_to_script(script_operations)
|
|
921
916
|
|
|
922
917
|
try:
|
|
923
|
-
await self.client.
|
|
918
|
+
search_response = await self.client.search(
|
|
924
919
|
index=index_alias_by_collection_id(collection_id),
|
|
920
|
+
body={
|
|
921
|
+
"query": {"term": {"_id": mk_item_id(item_id, collection_id)}},
|
|
922
|
+
"size": 1,
|
|
923
|
+
},
|
|
924
|
+
)
|
|
925
|
+
if search_response["hits"]["total"]["value"] == 0:
|
|
926
|
+
raise NotFoundError(
|
|
927
|
+
f"Item {item_id} does not exist inside Collection {collection_id}"
|
|
928
|
+
)
|
|
929
|
+
document_index = search_response["hits"]["hits"][0]["_index"]
|
|
930
|
+
await self.client.update(
|
|
931
|
+
index=document_index,
|
|
925
932
|
id=mk_item_id(item_id, collection_id),
|
|
926
933
|
body={"script": script},
|
|
927
934
|
refresh=True,
|
|
928
935
|
)
|
|
929
|
-
|
|
936
|
+
except exceptions.NotFoundError:
|
|
937
|
+
raise NotFoundError(
|
|
938
|
+
f"Item {item_id} does not exist inside Collection {collection_id}"
|
|
939
|
+
)
|
|
930
940
|
except exceptions.RequestError as exc:
|
|
931
941
|
raise HTTPException(
|
|
932
942
|
status_code=400, detail=exc.info["error"]["caused_by"]
|
|
@@ -945,8 +955,8 @@ class DatabaseLogic(BaseDatabaseLogic):
|
|
|
945
955
|
"script": {
|
|
946
956
|
"lang": "painless",
|
|
947
957
|
"source": (
|
|
948
|
-
f"""ctx._id = ctx._id.replace('{collection_id}', '{new_collection_id}');"""
|
|
949
|
-
f"""ctx._source.collection = '{new_collection_id}';"""
|
|
958
|
+
f"""ctx._id = ctx._id.replace('{collection_id}', '{new_collection_id}');""" # noqa: E702
|
|
959
|
+
f"""ctx._source.collection = '{new_collection_id}';""" # noqa: E702
|
|
950
960
|
),
|
|
951
961
|
},
|
|
952
962
|
},
|
|
@@ -1000,9 +1010,9 @@ class DatabaseLogic(BaseDatabaseLogic):
|
|
|
1000
1010
|
)
|
|
1001
1011
|
|
|
1002
1012
|
try:
|
|
1003
|
-
await self.client.
|
|
1013
|
+
await self.client.delete_by_query(
|
|
1004
1014
|
index=index_alias_by_collection_id(collection_id),
|
|
1005
|
-
|
|
1015
|
+
body={"query": {"term": {"_id": mk_item_id(item_id, collection_id)}}},
|
|
1006
1016
|
refresh=refresh,
|
|
1007
1017
|
)
|
|
1008
1018
|
except exceptions.NotFoundError:
|
|
@@ -1093,8 +1103,10 @@ class DatabaseLogic(BaseDatabaseLogic):
|
|
|
1093
1103
|
body=collection,
|
|
1094
1104
|
refresh=refresh,
|
|
1095
1105
|
)
|
|
1096
|
-
|
|
1097
|
-
|
|
1106
|
+
if self.async_index_inserter.should_create_collection_index():
|
|
1107
|
+
await self.async_index_inserter.create_simple_index(
|
|
1108
|
+
self.client, collection_id
|
|
1109
|
+
)
|
|
1098
1110
|
|
|
1099
1111
|
async def find_collection(self, collection_id: str) -> Collection:
|
|
1100
1112
|
"""Find and return a collection from the database.
|
|
@@ -1303,6 +1315,7 @@ class DatabaseLogic(BaseDatabaseLogic):
|
|
|
1303
1315
|
await self.client.delete(
|
|
1304
1316
|
index=COLLECTIONS_INDEX, id=collection_id, refresh=refresh
|
|
1305
1317
|
)
|
|
1318
|
+
# Delete the item index for the collection
|
|
1306
1319
|
await delete_item_index(collection_id)
|
|
1307
1320
|
|
|
1308
1321
|
async def bulk_async(
|
|
@@ -1356,9 +1369,13 @@ class DatabaseLogic(BaseDatabaseLogic):
|
|
|
1356
1369
|
return 0, []
|
|
1357
1370
|
|
|
1358
1371
|
raise_on_error = self.async_settings.raise_on_bulk_error
|
|
1372
|
+
actions = await self.async_index_inserter.prepare_bulk_actions(
|
|
1373
|
+
collection_id, processed_items
|
|
1374
|
+
)
|
|
1375
|
+
|
|
1359
1376
|
success, errors = await helpers.async_bulk(
|
|
1360
1377
|
self.client,
|
|
1361
|
-
|
|
1378
|
+
actions,
|
|
1362
1379
|
refresh=refresh,
|
|
1363
1380
|
raise_on_error=raise_on_error,
|
|
1364
1381
|
)
|
|
@@ -1413,6 +1430,11 @@ class DatabaseLogic(BaseDatabaseLogic):
|
|
|
1413
1430
|
f"Performing bulk insert for collection {collection_id} with refresh={refresh}"
|
|
1414
1431
|
)
|
|
1415
1432
|
|
|
1433
|
+
# Handle empty processed_items
|
|
1434
|
+
if not processed_items:
|
|
1435
|
+
logger.warning(f"No items to insert for collection {collection_id}")
|
|
1436
|
+
return 0, []
|
|
1437
|
+
|
|
1416
1438
|
# Handle empty processed_items
|
|
1417
1439
|
if not processed_items:
|
|
1418
1440
|
logger.warning(f"No items to insert for collection {collection_id}")
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
"""library version."""
|
|
2
|
-
__version__ = "6.
|
|
2
|
+
__version__ = "6.2.0"
|
{stac_fastapi_opensearch-6.1.0.dist-info → stac_fastapi_opensearch-6.2.0.dist-info}/METADATA
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: stac-fastapi-opensearch
|
|
3
|
-
Version: 6.
|
|
3
|
+
Version: 6.2.0
|
|
4
4
|
Summary: Opensearch stac-fastapi backend.
|
|
5
5
|
Home-page: https://github.com/stac-utils/stac-fastapi-elasticsearch-opensearch
|
|
6
6
|
License: MIT
|
|
@@ -15,8 +15,8 @@ Classifier: Programming Language :: Python :: 3.13
|
|
|
15
15
|
Classifier: License :: OSI Approved :: MIT License
|
|
16
16
|
Requires-Python: >=3.9
|
|
17
17
|
Description-Content-Type: text/markdown
|
|
18
|
-
Requires-Dist: stac-fastapi-core==6.
|
|
19
|
-
Requires-Dist: sfeos-helpers==6.
|
|
18
|
+
Requires-Dist: stac-fastapi-core==6.2.0
|
|
19
|
+
Requires-Dist: sfeos-helpers==6.2.0
|
|
20
20
|
Requires-Dist: opensearch-py~=2.8.0
|
|
21
21
|
Requires-Dist: opensearch-py[async]~=2.8.0
|
|
22
22
|
Requires-Dist: uvicorn~=0.23.0
|
|
@@ -122,6 +122,7 @@ This project is built on the following technologies: STAC, stac-fastapi, FastAPI
|
|
|
122
122
|
- [Auth](#auth)
|
|
123
123
|
- [Aggregation](#aggregation)
|
|
124
124
|
- [Rate Limiting](#rate-limiting)
|
|
125
|
+
- [Datetime-Based Index Management](#datetime-based-index-management)
|
|
125
126
|
|
|
126
127
|
## Documentation & Resources
|
|
127
128
|
|
|
@@ -267,6 +268,81 @@ You can customize additional settings in your `.env` file:
|
|
|
267
268
|
> [!NOTE]
|
|
268
269
|
> The variables `ES_HOST`, `ES_PORT`, `ES_USE_SSL`, `ES_VERIFY_CERTS` and `ES_TIMEOUT` apply to both Elasticsearch and OpenSearch backends, so there is no need to rename the key names to `OS_` even if you're using OpenSearch.
|
|
269
270
|
|
|
271
|
+
## Datetime-Based Index Management
|
|
272
|
+
|
|
273
|
+
### Overview
|
|
274
|
+
|
|
275
|
+
SFEOS supports two indexing strategies for managing STAC items:
|
|
276
|
+
|
|
277
|
+
1. **Simple Indexing** (default) - One index per collection
|
|
278
|
+
2. **Datetime-Based Indexing** - Time-partitioned indexes with automatic management
|
|
279
|
+
|
|
280
|
+
The datetime-based indexing strategy is particularly useful for large temporal datasets. When a user provides a datetime parameter in a query, the system knows exactly which index to search, providing **multiple times faster searches** and significantly **reducing database load**.
|
|
281
|
+
|
|
282
|
+
### When to Use
|
|
283
|
+
|
|
284
|
+
**Recommended for:**
|
|
285
|
+
- Systems with large collections containing millions of items
|
|
286
|
+
- Systems requiring high-performance temporal searching
|
|
287
|
+
|
|
288
|
+
**Pros:**
|
|
289
|
+
- Multiple times faster queries with datetime filter
|
|
290
|
+
- Reduced database load - only relevant indexes are searched
|
|
291
|
+
|
|
292
|
+
**Cons:**
|
|
293
|
+
- Slightly longer item indexing time (automatic index management)
|
|
294
|
+
- Greater management complexity
|
|
295
|
+
|
|
296
|
+
### Configuration
|
|
297
|
+
|
|
298
|
+
#### Enabling Datetime-Based Indexing
|
|
299
|
+
|
|
300
|
+
Enable datetime-based indexing by setting the following environment variable:
|
|
301
|
+
|
|
302
|
+
```bash
|
|
303
|
+
ENABLE_DATETIME_INDEX_FILTERING=true
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
### Related Configuration Variables
|
|
307
|
+
|
|
308
|
+
| Variable | Description | Default | Example |
|
|
309
|
+
|----------|-------------|---------|---------|
|
|
310
|
+
| `ENABLE_DATETIME_INDEX_FILTERING` | Enables time-based index partitioning | `false` | `true` |
|
|
311
|
+
| `DATETIME_INDEX_MAX_SIZE_GB` | Maximum size limit for datetime indexes (GB) - note: add +20% to target size due to ES/OS compression | `25` | `50` |
|
|
312
|
+
| `STAC_ITEMS_INDEX_PREFIX` | Prefix for item indexes | `items_` | `stac_items_` |
|
|
313
|
+
|
|
314
|
+
## How Datetime-Based Indexing Works
|
|
315
|
+
|
|
316
|
+
### Index and Alias Naming Convention
|
|
317
|
+
|
|
318
|
+
The system uses a precise naming convention:
|
|
319
|
+
|
|
320
|
+
**Physical indexes:**
|
|
321
|
+
```
|
|
322
|
+
{ITEMS_INDEX_PREFIX}{collection-id}_{uuid4}
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
**Aliases:**
|
|
326
|
+
```
|
|
327
|
+
{ITEMS_INDEX_PREFIX}{collection-id} # Main collection alias
|
|
328
|
+
{ITEMS_INDEX_PREFIX}{collection-id}_{start-datetime} # Temporal alias
|
|
329
|
+
{ITEMS_INDEX_PREFIX}{collection-id}_{start-datetime}_{end-datetime} # Closed index alias
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
**Example:**
|
|
333
|
+
|
|
334
|
+
*Physical indexes:*
|
|
335
|
+
- `items_sentinel-2-l2a_a1b2c3d4-e5f6-7890-abcd-ef1234567890`
|
|
336
|
+
|
|
337
|
+
*Aliases:*
|
|
338
|
+
- `items_sentinel-2-l2a` - main collection alias
|
|
339
|
+
- `items_sentinel-2-l2a_2024-01-01` - active alias from January 1, 2024
|
|
340
|
+
- `items_sentinel-2-l2a_2024-01-01_2024-03-15` - closed index alias (reached size limit)
|
|
341
|
+
|
|
342
|
+
### Index Size Management
|
|
343
|
+
|
|
344
|
+
**Important - Data Compression:** Elasticsearch and OpenSearch automatically compress data. The configured `DATETIME_INDEX_MAX_SIZE_GB` limit refers to the compressed size on disk. It is recommended to add +20% to the target size to account for compression overhead and metadata.
|
|
345
|
+
|
|
270
346
|
## Interacting with the API
|
|
271
347
|
|
|
272
348
|
- **Creating a Collection**:
|
|
@@ -575,4 +651,3 @@ You can customize additional settings in your `.env` file:
|
|
|
575
651
|
- Ensures fair resource allocation among all clients
|
|
576
652
|
|
|
577
653
|
- **Examples**: Implementation examples are available in the [examples/rate_limit](examples/rate_limit) directory.
|
|
578
|
-
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
stac_fastapi/opensearch/__init__.py,sha256=iJWMUgn7mUvmuPQSO_FlyhJ5eDdbbfmGv1qnFOX5-qk,28
|
|
2
|
+
stac_fastapi/opensearch/app.py,sha256=WHo0C9c4EvNUW8l_2eiNu2yLPGe23RfHdu_F9-deoL0,5642
|
|
3
|
+
stac_fastapi/opensearch/config.py,sha256=zGx4-4c5zEnu_Bh8Ra3SkIC83tluDiz-wKYQclRRDJA,5179
|
|
4
|
+
stac_fastapi/opensearch/database_logic.py,sha256=0NzzWZqxrgZ-zcxqBcypOXeuFqf5EYia91za9NHepzY,55834
|
|
5
|
+
stac_fastapi/opensearch/version.py,sha256=ro2d3oERQL2KxSo7qmbU0z6qT77XShwY6vsqrLf2VFw,45
|
|
6
|
+
stac_fastapi_opensearch-6.2.0.dist-info/METADATA,sha256=0YnHUYtuSPuibokLp7ToJsEw4v9gIKbUoALnSchZuak,35019
|
|
7
|
+
stac_fastapi_opensearch-6.2.0.dist-info/WHEEL,sha256=tZoeGjtWxWRfdplE7E3d45VPlLNQnvbKiYnx7gwAy8A,92
|
|
8
|
+
stac_fastapi_opensearch-6.2.0.dist-info/entry_points.txt,sha256=zjZ0Xsr9BUNJqMkdPpl6zEIUykv1uFdJtNELFRChp0w,76
|
|
9
|
+
stac_fastapi_opensearch-6.2.0.dist-info/top_level.txt,sha256=vqn-D9-HsRPTTxy0Vk_KkDmTiMES4owwBQ3ydSZYb2s,13
|
|
10
|
+
stac_fastapi_opensearch-6.2.0.dist-info/RECORD,,
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
stac_fastapi/opensearch/__init__.py,sha256=iJWMUgn7mUvmuPQSO_FlyhJ5eDdbbfmGv1qnFOX5-qk,28
|
|
2
|
-
stac_fastapi/opensearch/app.py,sha256=EBaN0W8-jP9Q568J6UlC_xM7uWx7PkfE4RulnxYJCYs,5642
|
|
3
|
-
stac_fastapi/opensearch/config.py,sha256=tR-CP3l96pte0gdbQqDHAQVZrWbL57krMrFalLKCTBc,5178
|
|
4
|
-
stac_fastapi/opensearch/database_logic.py,sha256=9c2UKJcFaaZ9fcXUkCYnDy06G16BHGu96kb13Clg0ow,54664
|
|
5
|
-
stac_fastapi/opensearch/version.py,sha256=7IrY7mbr0cGVqZsk6wmCeITxZjDgz_mPHUswrziX5ME,45
|
|
6
|
-
stac_fastapi_opensearch-6.1.0.dist-info/METADATA,sha256=CgFBwwx65wUV-jcw3sbSFhUKcre3GgfWUBlwEhOQRuM,32250
|
|
7
|
-
stac_fastapi_opensearch-6.1.0.dist-info/WHEEL,sha256=tZoeGjtWxWRfdplE7E3d45VPlLNQnvbKiYnx7gwAy8A,92
|
|
8
|
-
stac_fastapi_opensearch-6.1.0.dist-info/entry_points.txt,sha256=zjZ0Xsr9BUNJqMkdPpl6zEIUykv1uFdJtNELFRChp0w,76
|
|
9
|
-
stac_fastapi_opensearch-6.1.0.dist-info/top_level.txt,sha256=vqn-D9-HsRPTTxy0Vk_KkDmTiMES4owwBQ3ydSZYb2s,13
|
|
10
|
-
stac_fastapi_opensearch-6.1.0.dist-info/RECORD,,
|
|
File without changes
|
{stac_fastapi_opensearch-6.1.0.dist-info → stac_fastapi_opensearch-6.2.0.dist-info}/entry_points.txt
RENAMED
|
File without changes
|
{stac_fastapi_opensearch-6.1.0.dist-info → stac_fastapi_opensearch-6.2.0.dist-info}/top_level.txt
RENAMED
|
File without changes
|