Skip to content

Commit 5983c82

Browse files
committed
INTPYTHON-730 Make search index creation and deletion synchronous
1 parent f597d82 commit 5983c82

File tree

4 files changed

+44
-18
lines changed

4 files changed

+44
-18
lines changed

django_mongodb_backend/schema.py

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
from time import monotonic, sleep
2+
13
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
24
from django.db.models import Index, UniqueConstraint
35
from pymongo.operations import SearchIndexModel
@@ -269,10 +271,12 @@ def add_index(
269271
)
270272
if idx:
271273
model = parent_model or model
274+
collection = self.get_collection(model._meta.db_table)
272275
if isinstance(idx, SearchIndexModel):
273-
self.get_collection(model._meta.db_table).create_search_index(idx)
276+
collection.create_search_index(idx)
277+
self.wait_until_index_created(collection, index.name)
274278
else:
275-
self.get_collection(model._meta.db_table).create_indexes([idx])
279+
collection.create_indexes([idx])
276280

277281
def _add_composed_index(self, model, field_names, column_prefix="", parent_model=None):
278282
"""Add an index on the given list of field_names."""
@@ -290,12 +294,14 @@ def _add_field_index(self, model, field, *, column_prefix=""):
290294
def remove_index(self, model, index):
291295
if index.contains_expressions:
292296
return
297+
collection = self.get_collection(model._meta.db_table)
293298
if isinstance(index, SearchIndex):
294299
# Drop the index if it's supported.
295300
if self.connection.features.supports_atlas_search:
296-
self.get_collection(model._meta.db_table).drop_search_index(index.name)
301+
collection.drop_search_index(index.name)
302+
self.wait_until_index_deleted(collection, index.name)
297303
else:
298-
self.get_collection(model._meta.db_table).drop_index(index.name)
304+
collection.drop_index(index.name)
299305

300306
def _remove_composed_index(
301307
self, model, field_names, constraint_kwargs, column_prefix="", parent_model=None
@@ -420,6 +426,27 @@ def _field_should_have_unique(self, field):
420426
# The _id column is automatically unique.
421427
return db_type and field.unique and field.column != "_id"
422428

429+
@staticmethod
430+
def wait_until_index_created(collection, index_name, timeout=60, interval=0.5):
431+
start = monotonic()
432+
while monotonic() - start < timeout:
433+
indexes = list(collection.list_search_indexes())
434+
for idx in indexes:
435+
if idx["name"] == index_name and idx["status"] == "READY":
436+
return True
437+
sleep(interval)
438+
raise TimeoutError(f"Index {index_name} not ready after {timeout} seconds.")
439+
440+
@staticmethod
441+
def wait_until_index_deleted(collection, index_name, timeout=60, interval=0.5):
442+
start = monotonic()
443+
while monotonic() - start < timeout:
444+
indexes = list(collection.list_search_indexes())
445+
if all(idx["name"] != index_name for idx in indexes):
446+
return True
447+
sleep(interval)
448+
raise TimeoutError(f"Index {index_name} not deleted after {timeout} seconds.")
449+
423450

424451
# GISSchemaEditor extends some SchemaEditor methods.
425452
class DatabaseSchemaEditor(GISSchemaEditor, BaseSchemaEditor):

docs/source/releases/5.2.x.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,13 @@ Backwards incompatible changes
1616
cast their input using ``$toString`` but this caused some queries to perform
1717
poorly because MongoDB couldn't use indexes when running the query.
1818

19+
Bug fixes
20+
---------
21+
22+
- Made migrations operations that add or delete search indexes block until the
23+
index is created or deletion to prevent conflicts when running multiple
24+
operations sequentially.
25+
1926
5.2.0 beta 2
2027
============
2128

tests/atlas_search_/test_search.py

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -28,21 +28,11 @@
2828
SearchVector,
2929
SearchWildcard,
3030
)
31+
from django_mongodb_backend.schema import DatabaseSchemaEditor
3132

3233
from .models import Article, Location, Writer
3334

3435

35-
def wait_until_index_ready(collection, index_name, timeout: float = 5, interval: float = 0.5):
36-
start = monotonic()
37-
while monotonic() - start < timeout:
38-
indexes = list(collection.list_search_indexes())
39-
for idx in indexes:
40-
if idx["name"] == index_name and idx["status"] == "READY":
41-
return True
42-
sleep(interval)
43-
raise TimeoutError(f"Index {index_name} not ready after {timeout} seconds")
44-
45-
4636
def _delayed_assertion(timeout: float = 4, interval: float = 0.5):
4737
def decorator(assert_func):
4838
@wraps(assert_func)
@@ -91,13 +81,16 @@ def _get_collection(model):
9181

9282
@classmethod
9383
def create_search_index(cls, model, index_name, definition, type="search"):
84+
# TODO: create/delete indexes using DatabaseSchemaEditor when
85+
# SearchIndexes support mappings (INTPYTHON-729).
9486
collection = cls._get_collection(model)
9587
idx = SearchIndexModel(definition=definition, name=index_name, type=type)
9688
collection.create_search_index(idx)
97-
wait_until_index_ready(collection, index_name)
89+
DatabaseSchemaEditor.wait_until_index_created(collection, index_name)
9890

9991
def drop_index():
10092
collection.drop_search_index(index_name)
93+
DatabaseSchemaEditor.wait_until_index_deleted(collection, index_name)
10194

10295
cls.addClassCleanup(drop_index)
10396

tests/indexes_/test_base.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,7 @@
33

44
class SchemaAssertionMixin:
55
def assertAddRemoveIndex(self, editor, model, index):
6-
with self.assertNumQueries(1):
7-
editor.add_index(index=index, model=model)
6+
editor.add_index(index=index, model=model)
87
try:
98
self.assertIn(
109
index.name,

0 commit comments

Comments
 (0)