Indices¶
Declaring class Index inside a document is enough for most projects. An index object is the
alternative, and it earns its keep in two situations: pointing several documents at one index, and
reusing one set of index settings across documents.
Index objects¶
Import Index from this package — not from opensearchpy. This one applies the project-wide
OPENSEARCH_INDEX_SETTINGS defaults, and its .document()
decorator registers the document for you:
# documents.py
from django_opensearch_models import Document, Index
from .models import Car
cars = Index("cars")
cars.settings(number_of_shards=1, number_of_replicas=0)
@cars.document
class CarDocument(Document):
class Django:
model = Car
fields = ["name", "color"]
Warning
Do not stack @registry.register_document on top of @cars.document. The index object’s decorator
already registers the document; applying both registers it twice.
Either style produces the same index. Mixing them across a project is fine — the registry does not care which one a given document used.
Rebuilding without downtime¶
search_index --rebuild deletes the index before recreating it. Searches issued in that window fail
against a missing index. To avoid that, rebuild behind an alias:
$ ./manage.py search_index --rebuild --use-alias
The index name in your document becomes an alias. Each rebuild creates a fresh concrete index named with a timestamp suffix, populates it, then atomically repoints the alias at it and deletes the index it replaced. Searches keep hitting the old index until the moment the alias moves, and never see an empty or half-populated one.
Keep the superseded index around — to roll back, or to inspect what changed — with:
$ ./manage.py search_index --rebuild --use-alias --use-alias-keep-index
Nothing then deletes those old indices; that becomes your job.
Note
Switching an existing index over to aliases is not automatic. A concrete index and an alias cannot
share a name, so the first --use-alias run against a name that is currently a real index will tell
you to delete it first. Plan for that on a live system.
The post_index signal¶
Sent after a bulk indexing operation finishes.
from django.dispatch import receiver
from django_opensearch_models.signals import post_index
@receiver(post_index)
def log_indexing(sender, instance, actions, response, **kwargs):
success, failed = response
...
senderThe
Documentsubclass that performed the indexing.instanceThe document instance it was performed on.
actionsThe generator of bulk actions that was sent to OpenSearch.
responseThe return value of
opensearch-py’sbulk()— a(success_count, errors)pair.
Warning
This signal is not sent when indexing runs with --parallel, because parallel_bulk() has no
single completion point to fire it from. Do not rely on it for bookkeeping that must also hold for
parallel rebuilds.
Signal-driven updates¶
Index maintenance during normal ORM use is handled by a signal processor, chosen with
OPENSEARCH_SIGNAL_PROCESSOR. Two ship with the library:
RealTimeSignalProcessorThe default. Writes to OpenSearch inline, during the same request that saved the model. Simple, and immediately consistent — at the cost of putting a network round-trip to OpenSearch inside your write path, where a slow or unreachable cluster becomes a slow or failing request.
CelerySignalProcessorHands the work to a Celery task instead. Requires the
celeryextra and a configured Celery app. Inside an atomic block it defers enqueuing totransaction.on_commit(), so a worker can never read a row before the transaction that wrote it commits.
Both are driven by post_save, post_delete, pre_delete and m2m_changed. Anything that does
not emit those signals — QuerySet.update(), bulk_create(), QuerySet.delete(), raw SQL — does
not reach the index.