Memilih arsitektur search engine pada aplikasi Django berpusat pada trade-off antara kesederhanaan operasional single-database dan performa dedicated search engine. PostgreSQL Full-Text Search (FTS) memanfaatkan fungsionalitas bawaan database relasional melalui django.contrib.postgres.search. Meilisearch memisahkan beban komputasi pencarian ke dedicated external service melalui protokol REST. Keputusan ini menentukan alokasi sumber daya CPU/IO, konsistensi data, dan arsitektur deployment aplikasi.

Arsitektur Direct Query vs Event-Driven Sync

Postgres FTS bekerja langsung pada primary database atau read-replica menggunakan SQL query biasa. Tidak ada komponen middleware tambahan, tidak ada transport layer network baru, dan data bersifat strongly consistent (ACID). Begitu transaksi INSERT atau UPDATE selesai, data langsung terbaca oleh query search berikutnya.

Meilisearch menerapkan model event-driven synchronization. Aplikasi Django tidak bisa langsung mengandalkan query database. Pola standarnya menggunakan Django model signals yang memicu task Celery untuk mengirimkan dokumen JSON ke API Meilisearch secara asynchronous. Pendekatan ini memunculkan eventual consistency lag: terdapat jeda waktu antara commit data ke database dan tersedianya data tersebut pada hasil pencarian Meilisearch.

Beban I/O, CPU, dan Performa Latensi Query

1. PostgreSQL FTS (Native)

Postgres FTS memproses teks mentah menjadi token tsvector dan membandingkannya dengan tsquery menggunakan kamus bahasa tertentu (stemming dan stop words). Untuk mencegah pembacaan sekuensial (full table scan), index GIN (Generalized Inverted Index) wajib digunakan.

  • Beban CPU: Operasi stemming dan parsing teks pada runtime membutuhkan siklus CPU signifikan jika dilakukan on-the-fly. Solusi standarnya adalah menyimpan pre-computed vector pada kolom SearchVectorField. Perhitungan ranking (ts_rank atau ts_rank_cd) tetap membebani CPU saat query dieksekusi.
  • Beban I/O: Index GIN berukuran relatif besar. Jika ukuran index melampaui alokasi shared_buffers, Postgres akan membaca disk I/O secara intensif, yang dapat mendegradasi performa query transaksional (OLTP) lainnya.
  • Typo Tolerance: Postgres tidak mendukung typo tolerance bawaan di modul FTS standarnya. Untuk mengimplementasikannya, Anda harus menggabungkan ekstensi pg_trgm (trigram matching). Pendekatan ini membuat indeks membengkak secara eksponensial dan memperlambat waktu respons query di atas dataset ratusan ribu baris.

2. Meilisearch (Dedicated Engine)

Meilisearch dirancang spesifik untuk responsivitas instan (search-as-you-type) menggunakan struktur data memory-mapped (LMDB).

  • Isolasi Beban: Beban komputasi tokenisasi, ranking, dan disk traversal sepenuhnya berpindah ke instance Meilisearch. Primary database terbebas dari load spikes akibat query pencarian kompleks.
  • Typo Tolerance & Latensi: Algoritma berbasis Damerau-Levenshtein distance bawaan mengeksekusi pencarian dengan toleransi salah ketik pada tingkat latensi sub-50 milidetik tanpa konfigurasi indeks manual yang rumit.

Implementasi Teknis pada Django

Pola 1: PostgreSQL FTS Teroptimasi

Pola optimal pada Postgres menghindari komputasi SearchVector saat query. Buat kolom terdedikasi yang diupdate via trigger database atau method save() model, lalu pasang GIN index.

from django.contrib.postgres.indexes import GinIndex
from django.contrib.postgres.search import SearchVectorField, SearchQuery, SearchRank
from django.db import models

class Product(models.Model):
    title = models.CharField(max_length=255)
    description = models.TextField()
    search_vector = SearchVectorField(null=True, blank=True)

    class Meta:
        indexes = [
            GinIndex(fields=["search_vector"]),
        ]

    # Query implementation
    @classmethod
    def search(cls, term: str):
        query = SearchQuery(term, search_type="websearch", config="indonesian")
        return (
            cls.objects.filter(search_vector=query)
            .annotate(rank=SearchRank(models.F("search_vector"), query))
            .order_by("-rank")
        )

Pola 2: Pipeline Asynchronous Meilisearch

Pola integrasi Meilisearch membutuhkan isolasi proses melalui worker asynchronous untuk menjaga respons HTTP Django tetap cepat dan mencegah race condition sebelum transaksi database selesai di-commit.

# signals.py
from django.db import transaction
from django.db.models.signals import post_save, post_delete
from django.dispatch import receiver
from .models import Product
from .tasks import index_product_task, delete_product_task

@receiver(post_save, sender=Product)
def on_product_save(sender, instance, **kwargs):
    # Gunakan transaction.on_commit agar worker tidak membaca uncommitted read
    transaction.on_commit(
        lambda: index_product_task.delay(instance.id)
    )

@receiver(post_delete, sender=Product)
def on_product_delete(sender, instance, **kwargs):
    transaction.on_commit(
        lambda: delete_product_task.delay(instance.id)
    )
# tasks.py
import meilisearch
from celery import shared_task
from django.conf import settings
from .models import Product

client = meilisearch.Client(settings.MEILISEARCH_URL, settings.MEILISEARCH_API_KEY)
index = client.index("products")

@shared_task(autoretry_for=(Exception,), retry_backoff=True, max_retries=3)
def index_product_task(product_id: int):
    product = Product.objects.get(id=product_id)
    index.add_documents([{
        "id": product.id,
        "title": product.title,
        "description": product.description,
    }])

@shared_task(autoretry_for=(Exception,), retry_backoff=True, max_retries=3)
def delete_product_task(product_id: int):
    index.delete_document(str(product_id))

Sinkronisasi Data dan Failure Modes

Postgres FTS tidak memiliki failure mode sinkronisasi data karena data search tersimpan di tabel yang sama atau terikat langsung dengan foreign key. ACID menjamin konsistensi mutlak.

Arsitektur Meilisearch menghadapi masalah sinkronisasi klasik sistem terdistribusi:

  • Celery Task Failure: Jaringan terputus atau Meilisearch crash saat indexing menyebabkan data di engine tertinggal. Task harus menerapkan idempotent retry dan exponential backoff.
  • Order of Execution: Jika terjadi update berturut-turut pada ID yang sama, task Celery yang selesai tidak berurutan berpotensi menimpa data baru dengan data lama. Penanganannya membutuhkan tracking versi dokumen atau timestamp modifikasi pada payload.
  • Data Drift: Kesalahan unhandled exception pada worker akan membuat database dan Meilisearch desync. Diperlukan cron job rekonsiliasi (batch reindexing periodik) untuk memvalidasi ulang seluruh data index secara berkala.

Total Cost of Ownership (TCO) dan Operasional

PostgreSQL FTS memiliki TCO operasional rendah. Tim engineering tidak perlu memantau node baru, tidak memerlukan pipeline backup terpisah, dan tidak ada dependensi network tambahan antar instance. Monitoring resource terpusat pada metric PostgreSQL yang sudah ada.

Meilisearch menambahkan kompleksitas infrastruktur:

  • Membutuhkan dedicated compute instance dengan alokasi RAM yang cukup untuk memuat working set index ke memori.
  • Membutuhkan komponen message broker (seperti Redis atau RabbitMQ) dan worker process (Celery) yang harus dipantau availability-nya.
  • Membutuhkan strategi disaster recovery terpisah: backup snapshot Meilisearch secara rutin di luar WAL archive PostgreSQL.

Kriteria Pengambilan Keputusan

ParameterGunakan Postgres FTSGunakan Meilisearch
Volume Data< 500.000 records> 500.000 records
Throughput Search< 50 QPS> 50 QPS (skala ratusan hingga ribuan QPS)
Typo ToleranceTidak krusial atau cukup matching standarWajib ada (fitur e-commerce, UX search-first)
Search-as-you-typeTidak praktis (beban IO tinggi per keystroke)Fitur bawaan, latensi sangat rendah (< 20ms)
Kapasitas OperasionalTim kecil / DevOps terbatasTersedia kapabilitas memelihara cluster & worker
Konsistensi DataHarus real-time dan strictly consistentDapat mentolerir eventual consistency (100ms - bbrp detik)

Gunakan PostgreSQL FTS jika prioritas utama adalah kesederhanaan deployment, konsistensi data transaksional yang kaku, dan beban pencarian tidak mendominasi utilisasi database. Beralihlah ke Meilisearch ketika aplikasi membutuhkan fitur search-as-you-type yang instan, toleransi salah ketik yang granular, atau ketika profiling database menunjukkan degradasi metrik read/write IOPS akibat index GIN yang membengkak.