By Andrew Dugan
Senior AI Technical Content Creator II

When your application needs search, the obvious choices are tools you may already run, such as OpenSearch for full-text search or PostgreSQL with the pgvector extension for applications already built on relational data. Both can store and query vectors, but neither was designed with vectors as the primary data structure. OpenSearch added vector search as a plugin on top of an inverted-index engine. pgvector adds nearest-neighbor search as a SQL operator, which is useful when a vector is one column among many, but it is not built for semantic search at the center of your product.
Weaviate takes the opposite stance. The HNSW (Hierarchical Navigable Small World) vector index is the core data structure, and BM25 keyword search and hybrid search are built on top of it rather than added afterward. The practical result is that semantic search, keyword search, and the blend of both are each a single API call. DigitalOcean Managed Weaviate provisions, backs up, and patches the cluster for you, so you get those capabilities without the operational overhead.
To make the comparison concrete, this article loads the same corpus of podcast transcripts into all three DigitalOcean managed services and compares them on what actually distinguishes them for search. Those dimensions are the quality of the results (vector vs keyword vs hybrid retrieval), the amount of search logic that lives in your application, ingest speed, and index size. Raw query speed is not a focus. At this scale all three respond comparably fast, fast enough that speed is not a deciding factor.
For the dataset, we used a corpus of podcast-episode transcripts with 4,886 unique episodes and chunked into ~500-token passages, producing 100,000 documents, each carrying the passage text plus episode metadata. Embeddings were generated once with OpenAI’s text-embedding-3-small (1,536 dimensions) and cached, so the embedding step is identical for all three systems.
All three ran as DigitalOcean Managed Databases, queried from a DigitalOcean Droplet.
| Service | Engine / Index |
|---|---|
| Managed Weaviate | HNSW (vector) + BM25 (keyword), native hybrid |
| Managed OpenSearch | k-nearest neighbors (k-NN) Lucene HNSW + inverted index |
| Managed PostgreSQL + pgvector | HNSW (vector_cosine_ops) + GIN tsvector |
Two kinds of index appear throughout this table and the rest of the article. HNSW (Hierarchical Navigable Small World) is a graph-based index for vector search. It links each embedding to its near neighbors in a layered graph, so the engine finds the vectors closest to a query by hopping through the graph rather than comparing against all 100,000, which makes it fast but approximate. An inverted index is the classic keyword-search structure. It maps every word to the list of passages that contain it, much like the index at the back of a book maps terms to pages, and BM25 is the standard formula for ranking those matches by how rare a word is and how concentrated it is in a passage. Postgres fills the same two roles with pgvector’s HNSW implementation for vectors and a GIN (Generalized Inverted Index) over tsvector, its native full-text type, for keywords.
This is not a controlled hardware benchmark. The instances were not spec-matched, so it deliberately avoids head-to-head speed claims. The focus is result quality and the engineering differences that persist regardless of hardware, namely how each system retrieves and how much search logic you must write.
To measure retrieval quality, we randomly sampled 200 passages from the dataset and had an LLM (large language model) generate a natural-language question specific to each one. We then used those generated questions as our search queries to see whether the passage that produced each question appeared in the top ten results. A hit means the source passage came back in the top ten (hit@10), and we also recorded its mean reciprocal rank (MRR), which scores higher the closer the passage is to the top of the list. Recall@10 is measured against an exact brute-force nearest-neighbor search over the same vectors. Index size is the on-disk footprint after the full load.
For index configuration, Weaviate and pgvector use HNSW with default parameters, while OpenSearch uses the k-NN plugin with the Lucene HNSW engine and cosine similarity. Vectors are pre-computed and identical across systems. To keep the engines comparable, all three were configured the way a practitioner would configure each for English text. Weaviate’s keyword operator is technically BM25F, which can weight multiple fields, but searching a single content field reduces it to standard BM25, the same algorithm OpenSearch runs. OpenSearch uses the english analyzer (stopword removal and stemming, matching what Postgres’s english config and Weaviate’s tokenization already do) and was force-merged after loading, per the k-NN plugin’s guidance. The HNSW candidate-list depth (ef_search) was aligned at 100 for all three engines, which is Weaviate’s default.
Embeddings were pre-computed, so loading performed the same job in all three systems, inserting 100,000 passages and building a vector index plus a keyword index over them. Since the instances were not spec-matched, the exact times don’t transfer to your hardware, but the ordering (Weaviate fastest, then Postgres, then OpenSearch) follows from how each engine builds its indexes, and that does transfer.
The pattern to take away is that the difference isn’t raw insert speed, it’s where the index-building work happens, whether inline and invisible (Weaviate), as a deliberate post-load step (Postgres), or amortized across the load itself (OpenSearch).
Hybrid search, which blends semantic (vector) and keyword (BM25) relevance, is both the highest-quality approach (as the quality section shows) and where the architectural differences between these systems are most visible. The real cost is what your application has to own.
Weaviate runs hybrid search natively in one call. It queries the HNSW and BM25 indexes together, fuses them with Reciprocal Rank Fusion (RRF), and returns one ranked list. The alpha parameter controls the blend (0 = pure keyword, 1 = pure vector).
results = collection.query.hybrid(
query=query_text,
vector=query_vector,
alpha=0.5,
limit=10,
)
OpenSearch has no native hybrid query in this configuration. You issue a vector query and a keyword query, then fuse the ranked lists in your application.
knn = client.search(index="passages", body={
"size": 50, "query": {"knn": {"embedding": {
"vector": query_vector, "k": 50,
"method_parameters": {"ef_search": 100}}}}})
bm25 = client.search(index="passages", body={
"size": 50, "query": {"match": {"transcript_text": query_text}}})
def rrf(*result_lists, k=60): # you write, test, and maintain this
scores = {}
for results in result_lists:
for rank, hit in enumerate(results):
scores[hit["_id"]] = scores.get(hit["_id"], 0) + 1 / (k + rank + 1)
return sorted(scores, key=scores.get, reverse=True)[:10]
results = rrf(knn["hits"]["hits"], bm25["hits"]["hits"])
pgvector has no hybrid primitive either, so the same two-ranking fusion is expressed in SQL.
WITH v AS (SELECT guid, row_number() OVER (ORDER BY embedding <=> %(qv)s) rk
FROM passages ORDER BY embedding <=> %(qv)s LIMIT 50),
k AS (SELECT guid, row_number() OVER (ORDER BY ts_rank(tsv, to_tsquery('english', %(q)s)) DESC) rk
FROM passages WHERE tsv @@ to_tsquery('english', %(q)s) LIMIT 50)
SELECT p.guid,
COALESCE(1.0/(60+v.rk),0) + COALESCE(1.0/(60+k.rk),0) AS rrf_score
FROM passages p LEFT JOIN v USING (guid) LEFT JOIN k USING (guid)
WHERE v.guid IS NOT NULL OR k.guid IS NOT NULL
ORDER BY rrf_score DESC LIMIT 10;
| System | How Hybrid Works | Network Round Trips | Fusion Logic You Maintain |
|---|---|---|---|
| Weaviate | Native, one call | One | None (alpha parameter) |
| OpenSearch | Two queries + client merge | Two | RRF function in app code |
| pgvector | Two rankings + merge in SQL | One | RRF expression in every query |
Beyond round trips, the two-ranking approach creates a maintenance surface. The fusion logic lives in your code, must be tested, and must stay consistent across every service and language that queries search. With Weaviate, tuning the blend is one float. Everywhere else it is a smoothing constant inside a function you own.
Using the 200 generated questions described in the methodology, each engine was scored on whether the source passage appeared in its top 10 (hit@10) and on its mean reciprocal rank (MRR).
| Engine | Vector (hit@10 / MRR) | Keyword | Hybrid |
|---|---|---|---|
| pgvector | 0.575 / 0.376 | 0.550 / 0.357 | 0.665 / 0.446 |
| Weaviate | 0.600 / 0.399 | 0.660 / 0.465 | 0.725 / 0.504 |
| OpenSearch | 0.580 / 0.382 | 0.635 / 0.475 | 0.685 / 0.450 |
Three findings stand out.
Postgres full-text search scored 0.04 with plainto_tsquery, which requires every word in a question to match. Rewriting the query to OR its terms (to_tsquery with |) lifted it to 0.550. OpenSearch and Weaviate behave well by default, but with Postgres you must construct the text query deliberately.
Aggregate scores are abstract, so here is what the systems actually return for a real question, “how to handle rejection in sales”. Passages are mid-conversation transcript chunks, so they start mid-sentence.
Vector search returned identical top threes on all three engines because they hold the same embeddings. The strongest answer comes from an interview with Daniel Pink about his sales book To Sell Is Human:
“…gonna get rejected, there’s no question about it. So one of the qualities that I talk about is a quality called buoyancy… every day I face an ocean of rejection. Okay, that’s what sales is like… So buoyancy is how do you stay afloat in that ocean of rejection?”
Your choice of database does not change what semantic search finds. The embedding model does. The database only affects how reliably it fetches the right vectors (recall).
Keyword search agrees at the top for this query, with all three engines ranking the same two passages first, a sales veteran’s story about starting out cold calling door to door and an Art of Charm (Jordan Harbinger) listener question about cold-call anxiety. The divergence shows at the third position, where each engine fills the slot differently, though all stay on topic.
| Engine | Third Keyword Result | |
|---|---|---|
| Weaviate | the Daniel Pink buoyancy passage above | on topic ✓ |
| pgvector | 30 Minutes to President’s Club, an SDR (Sales Development Representative) cold-call training story | on topic ✓ |
| OpenSearch | a solo episode on avoiding new work for fear of rejection | on topic ✓ |
The divergence is not always that gentle. On another of the 200 eval questions, “What should a presenter do if their audience starts leaving during their session?”, only Weaviate returned the source passage the question was generated from, and the other two engines agreed on the same miss.
| Engine | Top Keyword Result | |
|---|---|---|
| Weaviate | The Art of Charm, a speaking coach handling exactly that. “…with everybody leaving, I would probably say to the organizer… do we need to reschedule my session? Is there something we need to do to handle this?…” | the source passage ✓ |
| pgvector | a musician on when to stop playing a song that isn’t landing with the crowd | wrong domain ✗ |
| OpenSearch | the same musician passage | wrong domain ✗ |
Field weighting and analyzer choices, not the BM25 algorithm, decide whether keyword search stays on topic. Keyword search counts words instead of understanding them, so what you let it count matters. Hybrid search fuses the two signals and, for this query, lands all three engines on the same top three, the sales veteran’s cold-calling story, the Daniel Pink buoyancy passage, and the cold-call anxiety question. That is the pattern the aggregate table shows. Vector search is consistent across engines, keyword search is where they diverge, and hybrid is the most reliable for a real query.
On-disk footprint after loading all 100,000 documents with 1,536-dimensional embeddings plus passage text and metadata:
| System | Index Size | Notes |
|---|---|---|
| PostgreSQL + pgvector | 1.87 GB | 781 MB HNSW + 42 MB GIN full-text + table |
| OpenSearch | 2.91 GB | k-NN graph + inverted index + raw vectors stored in _source |
| Weaviate | not exposed | Managed Weaviate does not surface on-disk size via API |
pgvector is the most compact, a genuine advantage when vectors are one column in an existing table. OpenSearch’s larger footprint comes partly from retaining the raw embedding array in each document’s _source alongside the k-NN graph. (Weaviate maintains an HNSW graph plus an inverted index, but the managed service does not currently expose a disk figure, so it is omitted rather than estimated.)
Weaviate is the right choice when semantic search or retrieval-augmented generation (RAG) is the primary workload. Native hybrid search, the highest ingest throughput, and the best retrieval quality in this comparison make it the least-maintenance option when search is central to the product. The alpha parameter replaces an entire fusion function and the application logic around it. DigitalOcean Managed Weaviate provisions in minutes with no index management in your code.
OpenSearch is the right choice when you already run an OpenSearch cluster for full-text search or log analytics and want to add vector search incrementally, or when you need its mature faceting, aggregations, and horizontal scale. It performs respectably, but hybrid search is client-side orchestration, and its k-NN engine choice meaningfully affects recall and tuning, so get that decision right early.
PostgreSQL + pgvector is the right choice when vectors are secondary to a relational schema, such as a passages or products table where ORDER BY embedding <=> $1 is one condition among many WHERE clauses and JOINs, inside a transaction. It was the most compact here, and it keeps vectors consistent with the data they describe. Its trade-offs are keyword search that needs deliberate query construction and hybrid fusion you express yourself in SQL.
All three systems store and query vectors competently, and at this scale they answer about equally fast. The differences that matter show up in the quality of what comes back and in how much search logic you have to build. Hybrid retrieval is the highest-quality approach across the board, pgvector needs deliberate query construction to make keyword search work, OpenSearch needs deliberate analyzer and recall configuration to compete, and Weaviate delivers the top quality with the least code and the best defaults.
For applications where search is a feature alongside relational data, pgvector avoids a new service and keeps vectors close to the data they describe, at the cost of building keyword and hybrid behavior yourself. For applications where search is the product, the operational overhead of implementing hybrid search on OpenSearch or pgvector (two query paths, a fusion function, consistent tuning across every client) adds up, and Weaviate replaces all of it with one call and one parameter, while delivering the best retrieval quality in this comparison.
Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.
Andrew is an NLP Scientist with 8 years of experience designing and deploying enterprise AI applications and language processing systems.
This textbox defaults to using Markdown to format your answer.
You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!
Get paid to write technical tutorials and select a tech-focused charity to receive a matching donation.
Full documentation for every DigitalOcean product.
The Wave has everything you need to know about building a business, from raising funding to marketing your product.
Stay up to date by signing up for DigitalOcean’s Infrastructure as a Newsletter.
New accounts only. By submitting your email you agree to our Privacy Policy
Scale up as you grow — whether you're running one virtual machine or ten thousand.
From GPU-powered inference and Kubernetes to managed databases and storage, get everything you need to build, scale, and deploy intelligent applications.