Skip to content

Commit 74a396d

Browse files
ikreymertw4l
andauthored
simplify deletion of dedupe index: (#3200)
- if index not used by a crawl, allow fast deletion - delete any index jobs and the collindex object right away - ensure index state not reset to 'idle' if empty/marked for deletion --------- Co-authored-by: Tessa Walsh <tessa@bitarchivist.net>
1 parent a4e4d07 commit 74a396d

File tree

8 files changed

+80
-13
lines changed

8 files changed

+80
-13
lines changed

backend/btrixcloud/colls.py

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -803,9 +803,11 @@ async def delete_dedupe_index(
803803
if not coll.indexState:
804804
raise HTTPException(status_code=404, detail="no_dedupe_index")
805805

806-
# if index is not idle, can't delete it yet
807-
if coll.indexState != "idle":
808-
raise HTTPException(status_code=400, detail="dedupe_index_is_in_use")
806+
# if index is not idle/ready, check if any crawls running
807+
if coll.indexState not in ("idle", "ready"):
808+
# if crawls running using index, can't delete
809+
if await self.crawl_ops.has_active_crawls_with_dedupe_coll(org.id, coll.id):
810+
raise HTTPException(status_code=400, detail="dedupe_index_is_in_use")
809811

810812
if coll.indexFile:
811813
if not await self.storage_ops.delete_file_object(org, coll.indexFile):
@@ -816,17 +818,24 @@ async def delete_dedupe_index(
816818
raise HTTPException(status_code=400, detail="file_deletion_error")
817819

818820
await self.collections.find_one_and_update(
819-
{"_id": coll.id, "indexState": "idle"},
821+
{"_id": coll.id},
820822
{
821823
"$set": {
822824
"indexStats": None,
823825
"indexState": None,
824826
"indexFile": None,
825827
"indexLastSavedAt": None,
828+
"indexDiskSpaceUsed": None,
826829
}
827830
},
828831
)
829832

833+
# if not idle, delete k8s dedupe resources
834+
if coll.indexState != "idle":
835+
await self.crawl_manager.delete_dedupe_index_resources(
836+
str(org.id), str(coll.id)
837+
)
838+
830839
if remove_from_workflows:
831840
await self.crawl_configs.update_many(
832841
{"oid": org.id, "dedupeCollId": coll.id},
@@ -840,7 +849,7 @@ async def update_dedupe_index_stats(
840849
):
841850
"""update dedupe index stats for specified collection"""
842851
self.collections.find_one_and_update(
843-
{"_id": coll_id},
852+
{"_id": coll_id, "indexState": {"$ne": None}},
844853
{
845854
"$set": {
846855
"indexStats": stats.dict(),
@@ -855,14 +864,20 @@ async def update_dedupe_index_info(
855864
state: TYPE_DEDUPE_INDEX_STATES,
856865
index_file: Optional[DedupeIndexFile] = None,
857866
dt: Optional[datetime] = None,
867+
if_exists=False,
858868
):
859869
"""update the state, and optionally, dedupe index file info"""
860870
query: dict[str, Any] = {"indexState": state}
861871
if index_file and dt:
862872
query["indexLastSavedAt"] = dt
863873
query["indexFile"] = index_file.model_dump()
864874

865-
res = self.collections.find_one_and_update({"_id": coll_id}, {"$set": query})
875+
match: dict[str, Any] = {"_id": coll_id}
876+
# only update if index already exists
877+
if if_exists:
878+
match["indexState"] = {"$ne": None}
879+
880+
res = self.collections.find_one_and_update(match, {"$set": query})
866881
return res is not None
867882

868883
async def get_dedupe_index_saved(self, coll_id: UUID) -> Optional[datetime]:
@@ -886,6 +901,13 @@ async def get_dedupe_index_disk_size(self, coll_id: UUID) -> int:
886901
return coll.get("indexDiskSpaceUsed", 0)
887902
return 0
888903

904+
async def has_dedupe_index(self, coll_id: UUID, oid: UUID) -> bool:
905+
"""return true if collection exists and indexState is set on collection"""
906+
coll = await self.collections.find_one(
907+
{"_id": coll_id, "oid": oid}, projection={"indexState"}
908+
)
909+
return coll and coll.get("indexState") is not None
910+
889911
# END DEDUPE OPS
890912

891913
async def recalculate_org_collection_stats(self, org: Organization):

backend/btrixcloud/crawlmanager.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,12 @@ async def run_index_import_job(
269269

270270
return name
271271

272+
async def delete_dedupe_index_resources(self, oid: str, coll_id: str) -> None:
273+
"""Delete dedupe index-related jobs and index itself"""
274+
await self._delete_jobs(f"role=index-import-job,oid={oid},coll={coll_id}")
275+
276+
await self.delete_custom_object(f"collindex-{coll_id}", "collindexes")
277+
272278
async def ensure_cleanup_seed_file_cron_job_exists(self):
273279
"""ensure cron background job to clean up unused seed files weekly exists"""
274280

backend/btrixcloud/crawls.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -412,6 +412,19 @@ async def get_active_crawls_pending_size(self, oid: UUID) -> int:
412412

413413
return results[0].get("totalSum") or 0
414414

415+
async def has_active_crawls_with_dedupe_coll(
416+
self, oid: UUID, coll_id: UUID
417+
) -> bool:
418+
"""return true/false if any active crawls exist that use given coll_id for dedupe"""
419+
res = await self.crawls.find_one(
420+
{
421+
"state": {"$in": RUNNING_AND_WAITING_STATES},
422+
"oid": oid,
423+
"dedupeCollId": coll_id,
424+
}
425+
)
426+
return bool(res)
427+
415428
async def delete_crawls(
416429
self,
417430
org: Organization,

backend/btrixcloud/k8sapi.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -461,6 +461,14 @@ async def list_crawl_jobs(self, label: str = "") -> List[dict[str, Any]]:
461461
)
462462
return resp.get("items", [])
463463

464+
async def _delete_jobs(self, label: str) -> None:
465+
"""Delete namespaced jobs"""
466+
await self.batch_api.delete_collection_namespaced_job(
467+
namespace=self.namespace,
468+
label_selector=label,
469+
propagation_policy="Foreground",
470+
)
471+
464472
async def _delete_cron_jobs(self, label: str) -> None:
465473
"""Delete namespaced cron jobs (e.g. crawl configs, bg jobs)"""
466474
await self.batch_api.delete_collection_namespaced_cron_job(

backend/btrixcloud/models.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1720,7 +1720,7 @@ class Collection(BaseMongoModel):
17201720
indexStats: Optional[DedupeIndexStats] = None
17211721

17221722
# size of db on disk when in use
1723-
indexDiskSpaceUsed: int = 0
1723+
indexDiskSpaceUsed: Optional[int] = None
17241724

17251725

17261726
# ============================================================================

backend/btrixcloud/operator/baseoperator.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import os
55
import json
66
from typing import TYPE_CHECKING, Any
7+
from uuid import UUID
78
from kubernetes.utils import parse_quantity
89

910
import yaml
@@ -238,7 +239,9 @@ async def ensure_coll_index_ready(
238239

239240
# if index not found, create it
240241
if not found:
241-
await self.k8s.create_or_update_coll_index(coll_id, oid)
242+
# ensure dedupe index exists
243+
if await self.coll_ops.has_dedupe_index(UUID(coll_id), UUID(oid)):
244+
await self.k8s.create_or_update_coll_index(coll_id, oid)
242245

243246
return False
244247

backend/btrixcloud/operator/collindexes.py

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ async def mc_finalize_index(data: MCSyncData):
140140
async def mc_related(data: MCBaseRequest):
141141
return self.get_related(data)
142142

143-
# pylint: disable=too-many-locals, too-many-branches
143+
# pylint: disable=too-many-locals, too-many-branches, too-many-statements
144144
async def sync_index(self, data: MCSyncData):
145145
"""sync CollIndex object with existing state"""
146146
spec = CollIndexSpec(**data.parent.get("spec", {}))
@@ -168,15 +168,28 @@ async def sync_index(self, data: MCSyncData):
168168
is_done = True
169169
else:
170170
try:
171-
await self.coll_ops.get_collection_raw(spec.id, spec.oid)
171+
coll = await self.coll_ops.get_collection_raw(spec.id, spec.oid)
172+
# if index state is not set, index has been deleted
173+
# also delete immediately
174+
if not coll.get("indexState"):
175+
# if pod still exists, send SIGUSR2 to exit immediately
176+
if redis_pod:
177+
await self.k8s.send_signal_to_pod(
178+
redis_name, "SIGUSR2", "save"
179+
)
180+
is_done = True
172181
# pylint: disable=bare-except
173182
except:
174183
# collection not found, delete index
175184
is_done = True
176185

177186
if is_done:
178187
print(f"CollIndex removed: {spec.id}")
179-
return {"status": status.dict(), "children": [], "finalized": True}
188+
return {
189+
"status": status.dict(),
190+
"children": [],
191+
"finalized": not redis_pod,
192+
}
180193

181194
try:
182195
# determine if index was previously saved before initing redis
@@ -337,8 +350,7 @@ async def set_state(
337350
status.state = state
338351
status.lastStateChangeAt = date_to_str(dt_now())
339352

340-
# self.run_task(self.coll_ops.update_dedupe_index_info(coll_id, state))
341-
await self.coll_ops.update_dedupe_index_info(coll_id, state)
353+
await self.coll_ops.update_dedupe_index_info(coll_id, state, if_exists=True)
342354

343355
async def do_delete(self, coll_id: UUID):
344356
"""delete the CollIndex object"""

chart/app-templates/redis.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,9 @@ spec:
152152
command: ["sh", "-c"]
153153
args:
154154
- |
155+
# exit on SIGUSR2
156+
trap 'echo "SIGUSR2, aborting save" && exit 0' SIGUSR2
157+
155158
# wait for SIGUSR1
156159
trap ':' SIGUSR1
157160

0 commit comments

Comments
 (0)