Vespa: Mesin Serving Big Data untuk Vector Search, Ranking, dan Rekomendasi Skala Besar
Halo temen-temen, di tutorial kali ini aku mau ngajak kalian kenalan sama salah satu tools yang menurutku sering banget kelewat padahal powerful banget, namanya Vespa. Kalau kalian lagi bikin aplikasi yang butuh search, rekomendasi, atau ranking yang harus jalan cepat di atas jutaan bahkan miliaran dokumen, Vespa ini bakal jadi teman yang sangat berguna. Aku sendiri baru-baru ini banyak main sama Vespa buat kebutuhan hybrid search, dan jujur aja hasilnya bikin aku takjub. Jadi di artikel ini aku bakal jelasin dari konsep dasar sampai kita beneran deploy aplikasi pakai Python, biar kalian bisa langsung praktek sendiri.
Introduction
Sebelum masuk ke instalasi, aku mau cerita dulu Vespa itu sebenernya apa. Vespa adalah mesin serving big data yang open-source, awalnya dikembangin sama Yahoo dan sekarang dimaintain sama Vespa.ai. Yang bikin dia beda dari database biasa atau search engine biasa adalah dia dirancang khusus buat melayani query real-time di atas data yang gede banget, sambil ngelakuin komputasi yang berat kayak ranking pakai machine learning, sekaligus vector search buat semantic matching. Jadi bayangin aja temen-temen, dalam satu sistem kalian bisa gabungin pencarian teks tradisional pakai BM25, ditambah pencarian vektor pakai embedding, terus di-ranking pakai model ML yang kalian punya. Semua itu jalan dalam hitungan milidetik.
Nah biar kalian gak bingung, aku mau jelasin beberapa konsep inti di Vespa yang wajib kalian pahami dari awal:
Documents (Dokumen)
Di Vespa, satuan data paling dasar itu namanya document alias dokumen. Satu dokumen itu ibaratnya satu baris di database, misalnya satu artikel berita, satu produk, atau satu profil orang. Setiap dokumen punya ID unik dan berisi kumpulan field. Vespa nyimpen dokumen ini dan bisa nge-update, nge-hapus, atau nge-query-nya secara real-time tanpa harus reindex ulang semuanya.
Schema (Skema)
Schema itu semacam blueprint yang ngedefinisiin bentuk dokumen kalian. Di dalam schema kalian nentuin field apa aja yang ada, tipe datanya apa, field mana yang di-index buat search, dan gimana cara nge-ranking hasilnya. Satu aplikasi Vespa bisa punya satu atau lebih schema. Jadi schema ini penting banget karena dia yang nentuin kemampuan search dan ranking dari aplikasi kalian.
Fields (Field)
Field itu atribut di dalam dokumen. Misalnya sebuah artikel punya field title, body, dan embedding. Setiap field punya tipe, contohnya string buat teks, int buat angka, array buat list, dan tensor buat vektor. Yang menarik, kalian bisa ngatur atribut per field kayak apakah dia di-index (buat pencarian teks), apakah disimpan sebagai attribute (buat filtering dan sorting cepat), dan apakah pakai summary (buat ditampilin di hasil).
Rank Profiles (Profil Ranking)
Ini bagian favoritku. Rank profile itu ngedefinisiin gimana Vespa ngasih skor ke tiap dokumen yang cocok sama query. Kalian bisa nulis ekspresi ranking pakai fitur-fitur bawaan Vespa kayak bm25, closeness (buat vector similarity), atau bahkan manggil model machine learning kayak ONNX. Kalian juga bisa bikin ranking multi-fase, misalnya fase pertama pakai perhitungan murah buat nyaring kandidat, terus fase kedua pakai model yang lebih mahal buat rerank kandidat terbaik.
Hybrid Search: BM25 plus Vector (ANN)
Konsep yang paling sering aku pakai adalah hybrid search. Jadi gini temen-temen, pencarian teks tradisional pakai BM25 itu bagus buat nyari kecocokan kata kunci yang persis, tapi dia gak ngerti makna. Sementara vector search pakai embedding itu ngerti makna semantik, tapi kadang meleset di kata kunci spesifik kayak nama produk atau kode. Nah hybrid search itu gabungin keduanya, jadi kita ambil yang terbaik dari dua dunia. Buat vector search, Vespa pakai algoritma ANN alias Approximate Nearest Neighbor lewat struktur HNSW, biar pencarian vektor tetap cepat walaupun datanya jutaan.
Oke sekarang setelah kalian ngerti konsep dasarnya, ayo kita mulai praktek.
Instalasi
Buat mulai pakai Vespa, cara paling gampang menurutku adalah pakai Docker buat jalanin Vespa-nya, terus pakai library Python pyvespa buat ngedefinisiin dan deploy aplikasi. Aku bakal jelasin dua-duanya.
Menjalankan Vespa via Docker
Pertama-tama pastiin Docker udah keinstall di mesin kalian. Kalau belum, install dulu Docker Desktop atau Docker Engine sesuai OS kalian. Setelah itu, kita bisa langsung tarik dan jalanin image Vespa yang official. Vespa butuh memori lumayan, jadi pastiin Docker kalian dialokasiin minimal 4GB RAM, idealnya 8GB.
docker run --detach --name vespa --hostname vespa-container \
--publish 8080:8080 --publish 19071:19071 \
vespaengine/vespa
Nah aku jelasin ya port-nya. Port 8080 itu buat query dan feed dokumen, jadi ini yang bakal sering kita pakai. Port 19071 itu buat config server, yang tugasnya nerima deployment aplikasi kita. Buat mastiin kontainer udah jalan, kalian bisa cek statusnya:
docker exec vespa vespa-logfmt -l warning,error
Kalau kalian mau tunggu sampai config server bener-bener siap sebelum deploy, kalian bisa polling endpoint kesehatan config server-nya:
curl -s --head http://localhost:19071/state/v1/health
Kalau balasannya 200 OK, berarti config server udah siap nerima deployment.
Install Vespa CLI
Vespa punya CLI yang enak banget buat operasi cepat kayak deploy, feed, dan query dari terminal. Buat install di macOS pakai Homebrew:
brew install vespa-cli
Buat OS lain, kalian bisa download binary dari halaman rilis GitHub Vespa. Setelah keinstall, kalian bisa arahin CLI ke instance lokal kalian:
vespa config set target local
vespa status deploy --wait 300
CLI ini nanti berguna buat debugging dan operasi manual. Tapi buat alur kerja utama kita di tutorial ini, aku bakal fokus pakai Python.
Install pyvespa
Sekarang kita install library Python-nya. Aku saranin pakai virtual environment biar rapi.
python3 -m venv venv
source venv/bin/activate
pip install pyvespa
Library pyvespa ini keren karena kita bisa ngedefinisiin schema, rank profile, dan aplikasi secara programmatic pakai Python, terus deploy langsung ke instance Vespa. Jadi kita gak perlu nulis file XML dan schema manual, walaupun tetep bisa kalau kalian mau.
Basic Usage
Oke sekarang bagian yang seru. Ayo kita bikin aplikasi Vespa pertama kita pakai pyvespa. Skenarionya kita bikin mesin search buat artikel, di mana tiap artikel punya judul, isi, dan embedding buat semantic search.
Mendefinisikan Application Package
Di pyvespa, kita mulai dengan bikin sesuatu yang namanya ApplicationPackage. Ini semacam wadah yang ngumpulin semua schema dan konfigurasi aplikasi kita.
from vespa.package import (
ApplicationPackage,
Schema,
Document,
Field,
FieldSet,
RankProfile,
)
apppackage = ApplicationPackage(name="artikel")
apppackage.schema.addfields(
Field(name="docid", type="string", indexing=["attribute", "summary"]),
Field(
name="title",
type="string",
indexing=["index", "summary"],
index="enable-bm25",
),
Field(
name="body",
type="string",
indexing=["index", "summary"],
index="enable-bm25",
),
)
Coba perhatiin temen-temen, tiap Field punya parameter indexing yang isinya list instruksi. Kalau aku kasih index, artinya field itu di-index buat pencarian teks. Kalau attribute, artinya disimpan di memori buat filtering dan sorting cepat. Kalau summary, artinya field itu bakal ikut ditampilin di hasil query. Terus buat title dan body aku tambahin index="enable-bm25" supaya kita bisa pakai fitur ranking BM25 di field itu nanti.
Menambahkan FieldSet
FieldSet itu semacam grup field yang bisa kita cari sekaligus. Jadi daripada nyari cuma di title atau cuma di body, kita bisa bikin fieldset yang gabungin keduanya.
apppackage.schema.addfieldset(
FieldSet(name="default", fields=["title", "body"])
)
Dengan begini, kalau kita search pakai fieldset default, Vespa bakal nyocokin query kita ke title dan body sekaligus.
Menambahkan Rank Profile BM25
Sekarang kita definisiin cara nge-ranking hasilnya. Kita mulai dari yang sederhana dulu, yaitu ranking pakai BM25.
apppackage.schema.addrankprofile(
RankProfile(
name="bm25",
firstphase="bm25(title) + bm25(body)",
)
)
Ekspresi bm25(title) + bm25(body) itu artinya skor akhir sebuah dokumen adalah penjumlahan skor BM25 dari field title dan field body. Simpel banget kan? Kalian bisa kasih bobot juga misalnya 2 bm25(title) + bm25(body) kalau kalian pengen judul lebih berpengaruh ke ranking.
Deploy Aplikasi
Setelah aplikasi didefinisiin, sekarang kita deploy ke instance Vespa lokal yang tadi kita jalanin di Docker. pyvespa punya kelas VespaDocker buat ini.
from vespa.deployment import VespaDocker
vespadocker = VespaDocker(port=8080)
app = vespadocker.deploy(applicationpackage=apppackage)
print("Aplikasi berhasil di-deploy dan siap dipakai")
Kalau kalian udah punya kontainer Vespa yang jalan, pyvespa bakal connect ke situ. Kalau belum, dia bakal otomatis nyalain kontainer baru buat kalian. Proses deploy ini biasanya makan waktu beberapa menit di pertama kali karena Vespa nyiapin index dan konfigurasi. Sabar aja ya temen-temen.
Feed Dokumen
Setelah aplikasi jalan, kita masukin data. Proses masukin data ini namanya feeding. Aku bakal kasih contoh feed beberapa artikel.
dokumen = [
{
"docid": "1",
"title": "Panduan Belajar Machine Learning",
"body": "Machine learning adalah cabang AI yang belajar dari data.",
},
{
"docid": "2",
"title": "Resep Nasi Goreng Spesial",
"body": "Nasi goreng enak butuh nasi dingin dan api besar.",
},
{
"docid": "3",
"title": "Dasar Deep Learning dan Neural Network",
"body": "Neural network terinspirasi dari cara kerja otak manusia.",
},
]
for d in dokumen:
response = app.feeddatapoint(
schema="artikel",
dataid=d["docid"],
fields=d,
)
print(response.statuscode)
Buat data yang banyak, aku saranin pakai feediterable yang lebih efisien karena dia nge-feed secara paralel:
def datagenerator(dokumen):
for d in dokumen:
yield {"id": d["doc
id"], "fields": d}
app.feediterable(
iter=datagenerator(dokumen),
schema="artikel",
callback=lambda response, id: print(f"Feed {id}: {response.statuscode}"),
)
Query Dokumen
Nah sekarang yang paling ditunggu, kita query datanya. Kita pakai rank profile bm25 yang tadi kita bikin.
with app.syncio() as session:
response = session.query(
yql="select from artikel where userQuery()",
query="belajar machine learning",
ranking="bm25",
)
for hit in response.hits:
print(hit["fields"]["title"], "->", hit["relevance"])
Perhatiin ya, aku pakai YQL alias Vespa Query Language buat nulis query-nya. userQuery() itu artinya Vespa bakal nyocokin teks dari parameter query ke field yang di-index. Parameter ranking="bm25" nyuruh Vespa nge-ranking hasilnya pakai rank profile bm25 kita. Field relevance di tiap hit itu skor ranking-nya. Jadi hasil query bakal keurut dari yang paling relevan.
Advanced Usage
Oke, sampai sini kalian udah bisa bikin text search dasar. Sekarang aku mau naikin level ke hybrid search yang gabungin BM25 sama vector search. Ini bagian yang bikin Vespa beneran bersinar.
Menambahkan Field Embedding (Tensor)
Buat vector search, kita butuh nyimpen embedding tiap dokumen sebagai tensor. Tensor di Vespa itu tipe data khusus buat nyimpen array multidimensi kayak vektor embedding. Aku bakal pakai embedding berdimensi 384 sebagai contoh, misalnya keluaran dari model kayak all-MiniLM-L6-v2.
from vespa.package import HNSW
apppackage.schema.addfields(
Field(
name="embedding",
type="tensor(x[384])",
indexing=["attribute", "index"],
ann=HNSW(
distancemetric="angular",
maxlinkspernode=16,
neighborstoexploreatinsert=200,
),
)
)
Perhatiin tipe field-nya tensor, ini artinya vektor float berdimensi 384 dengan nama dimensi x. Terus aku pasang HNSW index dengan distance metric angular, yang cocok buat cosine similarity. HNSW ini yang bikin ANN search jadi cepat walaupun datanya banyak. Parameter maxlinkspernode dan neighborstoexploreatinsert itu ngatur trade-off antara kecepatan, akurasi, dan penggunaan memori.
Rank Profile Hybrid
Sekarang kita bikin rank profile yang gabungin BM25 sama vector similarity. Kita pakai fungsi closeness buat ngukur kedekatan vektor query sama vektor dokumen.
from vespa.package import RankProfile, Function
apppackage.schema.addrankprofile(
RankProfile(
name="hybrid",
inputs=[("query(qembedding)", "tensor(x[384])")],
functions=[
Function(
name="bm25score",
expression="bm25(title) + bm25(body)",
),
Function(
name="vectorscore",
expression="closeness(field, embedding)",
),
],
firstphase="bm25score + 100 vectorscore",
)
)
Aku jelasin ya. Bagian inputs ngedefinisiin bahwa query bakal ngirim tensor bernama qembedding berdimensi 384, yaitu embedding dari teks query. Terus aku bikin dua function pembantu: bm25score buat skor teks, dan vectorscore buat skor vektor pakai closeness. Fungsi closeness itu ngembaliin nilai antara 0 sampai 1, makin deket vektornya makin tinggi. Di firstphase aku gabungin keduanya. Aku kali vectorscore sama 100 buat nyeimbangin skala karena skor BM25 biasanya jauh lebih gede dari closeness. Angka bobot ini kalian harus tuning sendiri sesuai data kalian ya temen-temen, gak ada angka ajaib.
Feed Dokumen dengan Embedding
Sekarang pas nge-feed, kita ikutin embedding-nya. Di dunia nyata kalian generate embedding pakai model kayak sentence-transformers. Buat contoh, aku ilustrasiin gimana strukturnya.
from sentencetransformers import SentenceTransformer
model = SentenceTransformer("all-MiniLM-L6-v2")
dokumen = [
{"doc
id": "1", "title": "Panduan Machine Learning",
"body": "Machine learning belajar dari data historis."},
{"docid": "2", "title": "Resep Nasi Goreng",
"body": "Nasi goreng butuh nasi dingin dan bumbu lengkap."},
]
def datagenerator(docs):
for d in docs:
teks = d["title"] + ". " + d["body"]
vektor = model.encode(teks).tolist()
fields = dict(d)
fields["embedding"] = vektor
yield {"id": d["docid"], "fields": fields}
app.feediterable(
iter=datagenerator(dokumen),
schema="artikel",
callback=lambda response, id: print(id, response.statuscode),
)
Query Hybrid dengan Nearest Neighbor
Sekarang query-nya. Kita generate embedding dari teks query, terus kirim ke Vespa lewat input tensor, dan pakai operator nearestNeighbor buat ANN search.
teksquery = "cara belajar kecerdasan buatan"
q
vektor = model.encode(teksquery).tolist()
with app.syncio() as session:
response = session.query(
yql=(
"select
from artikel where "
"userQuery() or "
"({targetHits:100}nearestNeighbor(embedding, qembedding))"
),
query=teksquery,
ranking="hybrid",
body={"input.query(qembedding)": qvektor},
)
for hit in response.hits:
print(hit["fields"]["title"], "->", round(hit["relevance"], 4))
Coba perhatiin YQL-nya. Aku pakai userQuery() buat bagian teks BM25, terus di-OR sama nearestNeighbor(embedding, qembedding) buat bagian vektor. Anotasi {targetHits:100} nyuruh Vespa ambil sekitar 100 kandidat terdekat lewat HNSW. Karena aku pakai OR, dokumen yang cocok di salah satu jalur (teks atau vektor) bakal masuk kandidat, terus di-ranking bareng pakai rank profile hybrid. Inilah esensi hybrid search di Vespa.
Ranking Multi-Fase
Buat aplikasi produksi yang gede, kita sering pakai ranking dua fase biar efisien. Fase pertama pakai perhitungan murah buat nyaring, fase kedua pakai perhitungan mahal buat rerank kandidat terbaik.
apppackage.schema.addrankprofile(
RankProfile(
name="hybrid
duafase",
inputs=[("query(q
embedding)", "tensor(x[384])")],
firstphase="bm25(title) + bm25(body)",
secondphase={
"expression": "closeness(field, embedding)",
"rerank-count": 50,
},
)
)
Di sini fase pertama nyaring pakai BM25 yang murah, terus rerank-count: 50 nyuruh Vespa cuma nge-rerank 50 dokumen teratas pakai vector closeness di fase kedua. Ini strategi yang hemat komputasi banget buat data skala besar. Kalian gak perlu ngitung vector similarity buat semua dokumen, cukup buat kandidat terbaik dari fase pertama.
Filtering dan Pagination
Vespa juga jago filtering. Karena field kita ada yang disimpan sebagai attribute, kita bisa nyaring dengan cepat. Misalnya kalau kita punya field kategori:
with app.syncio() as session:
response = session.query(
yql=(
"select * from artikel where userQuery() "
"and kategori contains 'teknologi'"
),
query="machine learning",
ranking="bm25",
hits=10,
offset=0,
)
Parameter hits ngatur berapa hasil per halaman, dan offset buat pagination. Filtering pakai attribute ini cepet banget karena disimpan di memori.
Best Practices
Setelah cukup lama main sama Vespa, ada beberapa hal yang mau aku bagiin biar kalian gak jatuh di lubang yang sama kayak aku dulu.
Pilih Atribut Indexing dengan Bijak
Jangan asal kasih semua atribut ke semua field ya temen-temen. Setiap attribute itu disimpan di memori, jadi kalau kalian kasih attribute ke field body yang isinya teks panjang, memori Vespa bakal boros banget. Kasih index buat field yang mau dicari teksnya, attribute cuma buat field yang mau di-filter atau di-sort, dan summary buat field yang mau ditampilin di hasil. Hemat memori itu kunci performa di Vespa.
Tuning Bobot Hybrid Search
Kayak yang tadi aku bilang, gabungin skor BM25 sama vector similarity itu butuh tuning. Skala kedua skor ini beda jauh, jadi kalian harus eksperimen sama bobotnya. Cara yang bagus adalah normalisasi dulu masing-masing skor sebelum digabung. Vespa punya fitur normalisasi kayak normalizelinear yang bisa bikin penggabungan skor lebih adil. Aku saranin bikin dataset evaluasi kecil buat ngukur kualitas ranking sebelum dan sesudah tuning.
Perhatikan Parameter HNSW
Parameter HNSW kayak maxlinkspernode dan neighborstoexploreatinsert itu ngatur trade-off. Nilai yang lebih tinggi bikin akurasi ANN naik tapi memori dan waktu insert juga naik. Buat mulai, nilai default biasanya udah cukup bagus. Kalau kalian butuh recall yang lebih tinggi, naikin targetHits di query dan neighborstoexploreatinsert di schema secara bertahap sambil ngukur hasilnya.
Gunakan Feed yang Efisien
Buat nge-feed data banyak, jangan pakai loop feeddatapoint satu-satu karena lambat. Pakai feed_iterable yang nge-feed paralel. Kalau datanya jutaan, pertimbangin pakai Vespa feed client yang khusus dirancang buat throughput tinggi. Jangan lupa juga buat monitor response status di callback biar kalian tahu kalau ada dokumen yang gagal masuk.
Manfaatkan Ranking Multi-Fase
Buat produksi, aku hampir selalu pakai ranking multi-fase. Taruh perhitungan murah di fase pertama buat nyaring ratusan ribu kandidat jadi puluhan atau ratusan, terus taruh perhitungan mahal kayak model ML atau cross-encoder di fase kedua. Ini bikin latency tetap rendah tanpa ngorbanin kualitas ranking. Prinsipnya: filter kasar dulu yang murah, baru poles halus yang mahal di kandidat terbaik.
Uji di Lingkungan yang Mirip Produksi
Vespa punya banyak knob konfigurasi soal memori, jumlah node, dan alokasi resource. Perilaku di laptop kalian bisa beda jauh sama di cluster produksi. Jadi sebelum rilis, uji dulu dengan volume data dan pola query yang mirip produksi. Perhatiin metrik kayak latency query, penggunaan memori, dan recall dari ANN search.
Backup Schema di Version Control
Karena pakai pyvespa, semua definisi aplikasi kalian ada dalam kode Python. Manfaatin ini dengan nyimpen kode schema dan rank profile di git. Jadi setiap perubahan ranking bisa di-review, di-rollback, dan di-track. Ini praktik yang sering kelupaan tapi penting banget buat kolaborasi tim.
Conclusion
Nah temen-temen, itu tadi perjalanan kita dari nol sampai bisa bikin aplikasi search yang canggih pakai Vespa. Kita udah bahas konsep intinya mulai dari document, schema, field, sampai rank profile. Kita juga udah praktek install lewat Docker dan Vespa CLI, terus bikin aplikasi pakai pyvespa, feed dokumen, dan query pakai BM25. Yang paling seru menurutku, kita udah nyoba hybrid search yang gabungin kekuatan BM25 dan vector search lewat ANN, plus ranking multi-fase yang efisien buat skala besar.
Yang bikin aku suka sama Vespa adalah dia gak cuma sekadar vector database atau search engine biasa. Dia beneran mesin serving lengkap yang bisa handle text search, vector search, filtering, dan ranking berbasis ML dalam satu sistem yang terintegrasi. Buat kalian yang lagi bangun aplikasi search atau rekomendasi yang butuh skala dan fleksibilitas, aku sangat rekomendasiin buat kalian coba Vespa.
Saran aku, mulai dari yang kecil dulu. Bikin aplikasi sederhana kayak contoh di atas, mainin rank profile-nya, terus pelan-pelan tambahin embedding dan hybrid search. Setelah kalian nyaman, baru eksplor fitur lanjutan kayak model ONNX buat ranking, tensor operations yang lebih kompleks, dan deployment multi-node buat produksi. Dokumentasi resmi Vespa juga lengkap banget, jadi jangan ragu buat baca-baca di sana.
Oke segitu dulu dari aku. Semoga tutorial ini bermanfaat dan bikin kalian makin semangat ngulik Vespa. Kalau ada yang bingung atau mau diskusi, jangan sungkan buat tanya ya. Selamat ngoding dan sampai ketemu di tutorial berikutnya temen-temen. Semangat terus belajar teknologinya.