Kursus
Terkadang, pencarian teks penuh sederhana atau pencarian vektor saja tidak cukup untuk melakukan kueri pada database dan mendapatkan hasil yang Anda cari. Kombinasi keduanya sangat baik saat pengembang menangani sejumlah besar data multimodal dan tidak terstruktur yang akan diuntungkan dari kedua jenis pencarian. Ini dikenal sebagai pencarian hibrida, dan menawarkan solusi hebat bagi pengembang untuk tantangan yang sulit.
Apa Sebenarnya Pencarian Hibrida Itu?
Untuk memahami pencarian hibrida dengan benar, kita perlu memahami terlebih dahulu apa itu pencarian teks penuh.
Pencarian teks penuh adalah metode pencarian yang mencocokkan istilah literal dari kueri Anda terhadap dokumen Anda. Jenis pencarian tradisional ini sebenarnya sudah sangat akrab bagi banyak pengembang.
Sebagai contoh, jika Anda mencari “kafe lucu dengan tempat duduk luar ruangan,” mesin pencari akan mencari kata-kata persis tersebut di dalam database. Sederhananya, pencarian teks penuh sangat presisi dan efisien, tetapi tidak bekerja dengan baik jika Anda berharap mendapatkan hasil yang sama saat mencari sinonim, parafrase, atau bahkan jika ada salah ketik dalam kueri Anda.
Di sisi lain, pencarian vektor mengonversi semua data menjadi angka, atau embedding. Jadi, alih-alih mencocokkan kata-kata yang persis sama, pencarian vektor membandingkan makna semantik dari kueri Anda dengan dokumen yang tersimpan di database Anda.
Mencari “kafe lucu dengan tempat duduk luar ruangan” mungkin menampilkan “kue dan kopi di luar,” meskipun tidak menggunakan kata-kata yang sama persis. Pencarian vektor tidak hanya semantik; ia juga sangat fleksibel, tetapi terkadang dapat mengembalikan hasil yang terlalu luas dibandingkan kueri yang ditentukan.
Jadi, di mana peran pencarian hibrida? Pencarian ini menggabungkan pencarian teks penuh dan pencarian vektor. Artinya, pengembang dapat memanfaatkan kecerdasan semantik dari vektor sekaligus mempertahankan fitur penyaringan yang sangat presisi dari pencarian teks penuh. Inilah benar-benar yang terbaik dari keduanya. Ini sangat berguna bagi pengembang saat bekerja dengan kumpulan data besar yang tidak terstruktur.
Mengapa Pencarian Hibrida Penting
Pencarian hibrida bermanfaat dalam berbagai aplikasi dunia nyata, termasuk (namun tidak terbatas pada) e-niaga, layanan kesehatan, dan bahkan rekrutmen.
Dalam e-niaga, bayangkan Anda mengakses situs favorit dan mencari “kursi meja yang nyaman di bawah $100.” Dengan pencarian vektor, Anda akan melihat item yang secara semantik serupa (seperti kursi kantor ergonomis), sementara pencarian teks penuh berfokus pada kata “chair” dan membantu menegakkan batas harga.
Dalam rekrutmen, jika seorang perekrut menelusuri resume untuk “insinyur dengan pengalaman NLP,” pencarian hibrida dapat menangkap “natural language processing” sekaligus kata kunci persis “engineer”.
Ini berarti Anda dapat menerima hasil yang lebih relevan dan andal dibandingkan menggunakan salah satu pendekatan saja.
Pencarian Hibrida di MongoDB
Mari membahas cara melakukan pencarian hibrida di MongoDB Atlas. Agar berhasil mengikuti tutorial ini, Anda memerlukan beberapa prasyarat:
Dalam tutorial ini, kita akan berfokus pada koleksi embedded movies di database sample_mflix, dan kita akan mengikuti tutorial pencarian hibrida $rankFusion, dengan beberapa penyesuaian.
Walaupun $rankFusion operator baru tersedia di klaster versi 8.1+, dan membuat pencarian hibrida menjadi jauh lebih mudah di MongoDB, fitur ini masih dalam Public Preview dan klaster free tier otomatis berada pada versi 8.0. Jadi, kita akan mengikuti tutorial ini, mengubah beberapa hal, dan menuntaskannya dengan baik.
Provision klaster Anda
Saat melakukan provisioning klaster, pastikan Anda mengunduh dataset sample_mflix. Ini adalah dataset yang akan kita gunakan sepanjang tutorial. Kita berfokus pada koleksi sample_mflix.embedded_movies.

Periksa cepat untuk memastikan Anda telah memasang mongosh.
mongosh --version
Untuk tutorial ini, saya menggunakan 2.4.2.
Sekarang, sambungkan ke klaster MongoDB Atlas Anda:
mongosh "mongodb+srv://<yourclusterhere>.mongodb.net/" --apiVersion 1 --username <yourusername>
Anda akan diminta memasukkan kata sandi, dan setelah tersambung, tampilannya akan seperti ini:

Sekarang, kita bisa tersambung ke database kita. Jalankan perintah:
use sample_mflix
Keluaran yang akan muncul:
switched to db sample_mflix
Buat indeks Anda
Kita bisa langsung membuat kedua indeks yang diperlukan untuk tutorial ini. Yaitu indeks vektor dan indeks pencarian teks penuh. Ingat bahwa kita membuat kedua indeks ini pada koleksi embedded_movies.
Indeks vektor:
db.embedded_movies.createSearchIndex(
"hybrid-vector-search",
"vectorSearch",
{
fields: [
{ type: "vector", path: "plot_embedding_voyage_3_large", numDimensions: 2048, similarity: "dotProduct" }
]
}
)
Indeks pencarian teks penuh:
db.embedded_movies.createSearchIndex(
"hybrid-full-text-search",
"search",
{ mappings: { dynamic: true } }
)
Periksa kembali dan pastikan di dalam klaster MongoDB Atlas Anda bahwa indeksnya sudah siap:

Kueri database kita
Sekarang, kita ingin mengueri data di koleksi sample_mflix.embedded_movies untuk “star wars” pada bidang plot_embedding_voyage_3_large.
Mengikuti tutorial, karena kita tidak perlu menggunakan API apa pun, kita dapat menyimpan semua embedding yang diperlukan ke file terpisah bernama query_embeddings.js.

Sekarang kita bisa memuat embedding untuk digunakan dalam kueri. Jalankan perintah berikut:
load('/Users/<PATH NAME>/query_embeddings.js')
const queryVec = STAR_WARS_EMBEDDING
Untuk memastikan embedding berhasil dimuat, jalankan perintah:
STAR_WARS_EMBEDDING.length

Keluaran 2048 adalah benar.
Pipeline agregasi
Sekarang, kita dapat membuat pipeline agregasi untuk pencarian hibrida. Harap diingat bahwa ada beberapa keterbatasan dan alasan mengapa kita harus menyusun pipeline agregasi dengan cara ini.
Seperti disebutkan di atas, yang pertama adalah meskipun ada $rankFusion di MongoDB Atlas, fitur ini saat ini dalam Public Preview, dan Anda memerlukan klaster 8.0 atau lebih tinggi. Dengan klaster free tier, Anda belum dapat menggunakan $rankFusion saat ini. Hal ini sedikit mempersulit karena MongoDB Atlas tidak akan menggabungkan peringkat teks dan vektor secara native untuk kita. Ini berarti kita harus melakukan fusi sendiri, atau perpaduan berbobot dari dua daftar peringkat tersebut.
Penempatan tahap dalam pipeline agregasi juga ketat:
$searchharus menjadi tahap pertama dari sebuah pipeline.$vectorSearchharus berada di awal pipeline dan tidak boleh berada di dalam$facet.
Solusinya? $vectorSearch dapat menjadi tahap pertama dari subpipeline kita di dalam $unionWith, karena subpipeline ini dianggap sebagai pipeline tersendiri.
Karena itu, kita harus memulai dengan $search untuk hasil pencarian teks penuh agar mempertahankan skornya, lalu gunakan $unionWith sebagai mini-pipeline kedua yang dimulai dengan $vectorSearch sehingga juga mempertahankan skornya.
Kemudian kita perlu melakukan pengelompokan berdasarkan _id agar tidak ada duplikasi dan agar kita menggunakan yang terbaik dari masing-masing istilah $search dan $vectorSearch.
Setelah itu, kita perlu menghitung pencarian hibrida, yaitu yang terbaik dari $search, yang terbaik dari $vectorSearch, kemudian dikalikan dengan bobot pilihan kita (saya memilih 0,35), dan diurutkan dari sana.
Bobot menentukan seberapa besar pengaruh pencarian vektor dibandingkan pencarian teks penuh dalam hasil akhir.
Rumus hibrida kita adalah: hybrid_score = text_score + (vector_score x weight). Jadi, bobot yang lebih tinggi (misalnya, 0,7–1,0) berarti pencarian vektor lebih dominan, dan pada gilirannya memprioritaskan kesamaan semantik.
Sebaliknya, bobot yang lebih rendah (0,1–0,3) berarti pencarian teks penuh lebih dominan dan membantu memprioritaskan kecocokan kata kunci yang persis sama.
Salin dan tempel pipeline agregasi ini ke jendela terminal Anda:
const WEIGHT = 0.35;
db.embedded_movies.aggregate([
// Stage 1: full-text search
{ $search: { index: "hybrid-full-text-search", text: { query: "star wars", path: ["title","plot"] } } },
{ $set: { t: { $meta: "searchScore" } } },
{ $project: { _id: 1, title: 1, plot: 1, t: 1 } },
// Stage 2: Union with vector search
{ $unionWith: {
coll: "embedded_movies",
pipeline: [
// Vector search subpipeline
{ $vectorSearch: {
index: "hybrid-vector-search",
path: "plot_embedding_voyage_3_large",
queryVector: queryVec,
numCandidates: 200,
limit: 150
}},
{ $project: { _id: 1, title: 1, plot: 1, v: { $meta: "vectorSearchScore" } } }
]
}},
// Stage 3: Combine and rank results
{ $group: { _id: "$_id", title: { $first: "$title" }, plot: { $first: "$plot" }, t: { $max: "$t" }, v: { $max: "$v" } } },
{ $set: { h: { $add: [ { $ifNull: ["$t", 0] }, { $multiply: [ { $ifNull: ["$v", 0] }, WEIGHT ] } ] } } },
{ $sort: { h: -1 } },
{ $limit: 20 }
]).toArray()
Berikut keluaran kita:
{
_id: ObjectId('573a139af29313caabcf124d'),
title: 'Star Wars: Episode III - Revenge of the Sith',
plot: 'As the Clone Wars near an end, the Sith Lord Darth Sidious steps out of the shadows, at which time Anakin succumbs to his emotions, becoming Darth Vader and putting his relationships with Obi-Wan and Padme at risk.',
t: 4.957413673400879,
v: 0.756283700466156,
h: 5.2221129685640335
},
{
_id: ObjectId('573a13a6f29313caabd17d08'),
title: 'Star',
plot: 'The Driver now carries an arrogant rock star who is visiting a major city (not Pittsburgh as earlier believed). Played by Madonna, this title character wants to get away from her bodyguards...',
t: 5.151784420013428,
v: null,
h: 5.151784420013428
},
{
_id: ObjectId('573a1397f29313caabce8cdb'),
title: 'Star Wars: Episode VI - Return of the Jedi',
plot: 'After rescuing Han Solo from the palace of Jabba the Hutt, the rebels attempt to destroy the second Death Star, while Luke struggles to make Vader return from the dark side of the Force.',
t: 4.726564407348633,
v: 0.7782549858093262,
h: 4.998953652381897
},
{
_id: ObjectId('573a13aef29313caabd2da15'),
title: 'Star Runner',
plot: 'Get ready for the ultimate martial arts competition, where anything goes and lives are bought and sold. Tank is the celebrated Champion Star Runner and is deemed invincible among the ...',
t: 4.702453136444092,
v: 0.696668267250061,
h: 4.9462870299816135
},
{
_id: ObjectId('573a13c0f29313caabd62f62'),
title: 'Star Wars: The Clone Wars',
plot: 'Anakin Skywalker and Ahsoka Tano must rescue the kidnapped son of Jabba the Hutt, but political intrigue complicates their mission.',
t: 4.537533760070801,
v: 0.7561323642730713,
h: 4.802180087566375
},
{
_id: ObjectId('573a1397f29313caabce6f53'),
title: 'Message from Space',
plot: 'In this Star Wars take-off, the peaceful planet of Jillucia has been nearly wiped out by the Gavanas, whose leader takes orders from his mother (played a comic actor in drag) rather than ...',
t: 4.401333808898926,
v: 0.7913960218429565,
h: 4.678322416543961
},
{
_id: ObjectId('573a139df29313caabcfa90b'),
title: 'Message from Space',
plot: 'In this Star Wars take-off, the peaceful planet of Jillucia has been nearly wiped out by the Gavanas, whose leader takes orders from his mother (played a comic actor in drag) rather than ...',
t: 4.401333808898926,
v: 0.7913960218429565,
h: 4.678322416543961
},
{
_id: ObjectId('573a1398f29313caabce9851'),
title: 'Gymkata',
plot: 'Johnathan Cabot is a champion gymnast. In the tiny, yet savage, country of Parmistan, there is a perfect spot for a "star wars" site. For the US to get this site, they must compete in the ...',
t: 4.284864902496338,
v: 0.7193170189857483,
h: 4.53662585914135
},
{
_id: ObjectId('573a1397f29313caabce68f6'),
title: 'Star Wars: Episode IV - A New Hope',
plot: "Luke Skywalker joins forces with a Jedi Knight, a cocky pilot, a wookiee and two droids to save the universe from the Empire's world-destroying battle-station, while also attempting to rescue Princess Leia from the evil Darth Vader.",
t: 2.9629626274108887,
v: 0.7987099885940552,
h: 3.242511123418808
},
{
_id: ObjectId('573a139af29313caabcf0f5f'),
title: 'Star Wars: Episode I - The Phantom Menace',
plot: 'Two Jedi Knights escape a hostile blockade to find allies and come across a young boy who may bring balance to the Force, but the long dormant Sith resurface to reclaim their old glory.',
t: 2.9629626274108887,
v: 0.7771433591842651,
h: 3.2349628031253816
},
{
_id: ObjectId('573a1397f29313caabce77d9'),
title: 'Star Wars: Episode V - The Empire Strikes Back',
plot: 'After the rebels have been brutally overpowered by the Empire on their newly established base, Luke Skywalker takes advanced Jedi training with Master Yoda, while his friends are pursued by Darth Vader as part of his plan to capture Luke.',
t: 2.7174296379089355,
v: 0.7810181975364685,
h: 2.9907860070466996
},
{
_id: ObjectId('573a139af29313caabcf1258'),
title: 'Star Wars: Episode II - Attack of the Clones',
plot: 'Ten years after initially meeting, Anakin Skywalker shares a forbidden romance with Padmè, while Obi-Wan investigates an assassination attempt on the Senator and discovers a secret clone army crafted for the Jedi.',
t: 2.7174296379089355,
v: 0.7400004267692566,
h: 2.9764297872781755
},
{
_id: ObjectId('573a1394f29313caabcdf65b'),
title: 'Ugetsu',
plot: 'A fantastic tale of war, love, family and ambition set in the midst of the Japanese Civil Wars of the sixteenth century.',
t: 2.858368396759033,
v: null,
h: 2.858368396759033
},
{
_id: ObjectId('573a13a4f29313caabd1137f'),
title: 'S1m0ne',
plot: "A producer's film is endangered when his star walks off, so he decides to digitally create an actress to substitute for the star, becoming an overnight sensation that everyone thinks is a real person.",
t: 2.842216730117798,
v: null,
h: 2.842216730117798
},
{
_id: ObjectId('573a13b8f29313caabd4c3c3'),
title: 'Star Trek',
plot: "The brash James T. Kirk tries to live up to his father's legacy with Mr. Spock keeping him in check as a vengeful, time-traveling Romulan creates black holes to destroy the Federation one planet at a time.",
t: 2.577816963195801,
v: 0.7295459508895874,
h: 2.8331580460071564
},
{
_id: ObjectId('573a13b5f29313caabd42e99'),
title: 'Sars Wars',
plot: "The fourth generation of the virus SARS is found in Africa! It's more dangerous and causes the patients to transform into bloodthirsty zombies. The virus quickly lands to Thailand, Dr. ...",
t: 2.826810598373413,
v: null,
h: 2.826810598373413
},
{
_id: ObjectId('573a13b5f29313caabd42e1b'),
title: 'Sars Wars',
plot: "The fourth generation of the virus SARS is found in Africa! It's more dangerous and causes the patients to transform into bloodthirsty zombies. The virus quickly lands to Thailand, Dr. ...",
t: 2.826810598373413,
v: null,
h: 2.826810598373413
}
As we can see, we have some results that are identical to our query, “star wars,” and other results that are clearly off of meaning.
Conclusion
Congratulations! You have successfully completed hybrid search in MongoDB. While $rankFusion will simplify this process, this method shows a workaround while the operator is still in Public Preview. Through this tutorial, we have successfully incorporated both full-text search and vector search into one pipeline to retrieve the most optimal results for our given query. For more information on hybrid search in MongoDB, please refer to the MongoDB documentation. If you’re still getting up to speed with MongoDB, I recommend the Introduction to MongoDB in Python course.
MongoDB Hybrid Search FAQs
Do I need MongoDB 8.1 and `$rankFusion` to do hybrid search
No. On 8.0, you can actually fuse the scores yourself. Do this by running $search and $vectorSearch in two pipelines and combine them with $unionWith + $group and a weighted formula. On 8.1+, $rankFusion will do this for you.
What is hybrid search?
It’s combining full-text search (exact words) and vector search (meaning) for the most optimal results possible from a given query.
When should I use hybrid search?
Use hybrid search when your text is varied (synonyms, paraphrases, typos) but you still want exact terms.
How do I pick the weight I want to use?
It’s best practice to begin around 0.2-0.5. The lower is more for full-text search influence, and higher is for semantic influence. It’s important to tune the weight after testing and viewing the results provided.
Can hybrid search work with image and audio data?
Yes! As long as your data can be turned into vector embeddings and you can combine them with any specific wording constraints, you can perform hybrid search on a dataset.
