Semantic Search di VPS 1GB Tanpa Vector DB
Cosine pure-Python, vektor di postmeta MariaDB, embedding di mesin lokal. Fitur semantik tanpa service baru di server 1GB.
Ringkasan
VPS 1GB tak kuat menjalankan vector database plus model embedding, jadi bebannya dipecah. Embedding dihitung di mesin lokal via Ollama, vektor disimpan sebagai JSON di postmeta MariaDB, dan cosine similarity pakai pure Python tanpa numpy untuk deteksi duplikat. Skema ini efisien untuk puluhan artikel, tapi mulai jebol saat katalog ribuan atau QPS tinggi.
Pernah buka terminal, ketik htop, lalu terpaku pada angka RAM yang menyentuh 850MB dari total 1GB? Itulah momen "realita menampar" saya di server adityo.web.id.
Blog ini jalan di tumpukan ringan: FastAPI, MariaDB, dan Redis di backend, frontend Next.js di Vercel. Tantangannya sederhana: saya mau nambah fitur rekomendasi "artikel terkait" berbasis semantik plus dup-check otomatis, biar cron penulis AI tidak membuat topik yang sudah pernah dibahas.
Solusi di hampir semua tutorial modern? Pasang vector database (Qdrant, Milvus, atau pgvector), tambahkan numpy/scipy, dan jalankan model embedding langsung di server. Di VPS 1GB, skenario itu langsung tewas. Model embedding saja butuh ratusan MB sampai GB, numpy menambah puluhan MB RSS, dan satu service tambahan berarti memori tersisa buat FastAPI habis.
Jadi saya pilih arsitektur yang lebih pragmatis. Ini rinciannya.
Pisah beban: embedding lokal, cosine pure Python
Kuncinya memisahkan beban komputasi berat dari penyimpanan ringan.
Satu, embedding di mesin lokal. Vektor tidak dihitung di VPS. Saya hitung di mesin lokal pakai model bge-m3 via Ollama. Model ini multilingual (dense, sparse, colbert), dimensi 1024, max sequence 8192, jadi cocok untuk campuran bahasa Indonesia dan Inggris sekaligus. VPS cuma bertugas menyimpan vektor jadi JSON di tabel postmeta MariaDB.
Dua, cosine similarity murni Python. Tanpa numpy. Untuk skala 70 sampai 100 artikel dengan 1024 dimensi, kita cuma bicara sekitar 100 ribu operasi float per query. Selesai di bawah milidetik sampai puluhan milidetik pakai loop Python biasa. Tidak butuh indeks ANN untuk katalog sekecil ini.
def cosine_similarity(v1: list[float], v2: list[float]) -> float:
dot = sum(a * b for a, b in zip(v1, v2))
n1 = sum(a * a for a in v1) ** 0.5
n2 = sum(b * b for b in v2) ** 0.5
return dot / (n1 * n2) if n1 and n2 else 0.0
# Struktur di MariaDB postmeta:
# meta_key = 'embedding_vector', meta_value = '[0.012, -0.045, ..., 0.089]' (JSON)
Tiga, threshold dan proteksi. Similarity 0.90 ke atas dianggap duplikat kuat, 0.75 sampai 0.90 area abu-abu yang perlu kalibrasi manual, di bawah itu aman. Cron penulis mengecek topik baru ke vektor artikel existing lebih dulu. Untuk endpoint publik, ada rate limit dan cache jawaban di disk biar CPU dan RSS VPS terlindungi. Komputasi berat tidak boleh bisa dipicu sembarangan tanpa guard.
Kapan skema ini jebol
Jujur saja: pendekatan ini jebol ketika katalog Anda ribuan artikel, dimensi vektor membengkak, atau query per second jadi tinggi. Di titik itu, vector database sungguhan baru masuk akal.
Kalau Anda di ambang batas itu tapi masih mau jaga kesederhanaan, ada alternatif ringan: sqlite-vec, yang diklaim sebagai "An extremely small, 'fast enough' vector search SQLite extension that runs anywhere!" Contoh jalur tengah kalau tetap ingin pencarian vektor lewat extension, bukan service penuh yang rakus memori.
Sementara kalau database Anda memang PostgreSQL, pgvector jalan keluar yang elegan: "Open-source vector similarity search for Postgres". Tapi blog ini MariaDB; mengadopsi pgvector berarti migrasi database yang tidak perlu. Jadi kombinasi JSON di MariaDB plus pure Python tetap sweet spot-nya.
Stop over-engineering untuk blog kecil
Ada tren berbahaya di dunia pengembangan web: menganggap kosmetik "pakai teknologi kekinian" sebagai ukuran kualitas arsitektur. Itu bukan alasan valid buat menambah service baru di server 1GB.
Untuk blog berskala kecil sampai menengah, vector database itu over-engineering. Kompleksitas operasionalnya (monitoring, backup, konsumsi RAM) jauh lebih besar daripada nilai yang diberikan.
Keputusan akhir saya tetap: vektor disimpan di postmeta, perhitungan cosine di level aplikasi dengan Python murni, embedding berat digeser ke mesin lokal. Hasilnya, fitur semantik jalan, SEO terjaga karena konten tidak duplikat, dan VPS 1GB tetap bernapas lega.