Search that runs a database LIKE over a text column works
until it does not. It is slow once the table is large, and it has no
idea which result is more relevant than another. Elasticsearch is built
for exactly this job, and Django integrates with it cleanly.
Why not just the database
A wildcard text match cannot use a normal index, so it scans. It also treats every match as equal: a term in the title ranks the same as a term buried in a description. Real search needs tokenizing, ranking, and tolerance for typos, and that is a search engine, not a column.
Define a document
With django-elasticsearch-dsl, a document declares which
model fields get indexed and how.
from django_elasticsearch_dsl import Document, fields
from django_elasticsearch_dsl.registries import registry
from products.models import Product
@registry.register_document
class ProductDocument(Document):
vendor = fields.TextField(attr="vendor.name")
class Index:
name = "products"
class Django:
model = Product
fields = ["name", "sku", "description"]
Keep the index in sync
The library hooks model save and delete to update the index
automatically. The gap is bulk operations: a bulk_update
does not fire those hooks, so anything bulk needs an explicit reindex
afterwards.
Querying with relevance in mind
A multi-match query searches several fields at once, and a boost makes a hit in the name count for more than a hit in the description.
results = ProductDocument.search().query(
"multi_match",
query=term,
fields=["name^3", "sku^2", "description"],
fuzziness="AUTO",
)
Relevance is the whole point
Field boosts and fuzziness are not extras. They are the
reason to use a search engine at all. A user who types a slightly wrong
SKU should still find the product, and the closest match should sit at
the top.
Elasticsearch is a secondary store
The database remains the source of truth. The index can lag, and it can be thrown away and rebuilt at any time. Never make a write decision based on a search result. Read from search, write against the database.
Operational notes
Keep a management command that rebuilds the index from scratch. Any change to the mapping, such as a new field or a changed analyzer, means creating a fresh index and reindexing into it. Plan for that rebuild rather than being surprised by it.