[TOOLS] 13 分鐘閱讀OraCore 編輯部

Qdrant 讓 RAG 先過濾再找相似

我拆 Raunaq 的向量資料庫比較,整理出 filter-first RAG 的選型邏輯與可直接貼上的設計模板。

分享 LinkedIn
Qdrant 讓 RAG 先過濾再找相似

我把 Raunaq 的向量資料庫比較拆成一套 filter-first RAG 做法,直接給你可抄的設計模板。

我做 RAG 做到後面,最煩的不是 embedding 不準,而是系統明明看起來都對,實際一加條件就開始鬧脾氣。你要 tenant、要時間區間、要來源限定,結果檢索不是少給,就是給錯,還常常是那種「數學上沒錯、產品上很爛」的輸出。我以前也以為這只是 top-k 調一調的事,後來才發現不是。問題根本在架構:你到底是先找相似,再補過濾,還是先把範圍縮對,再找相似。

這次我看的來源是 Raunaq 的 Vector Databases for RAG,他把 pgvectorChromaDBQdrant 放在同一個問題底下看:當你的向量查詢一定要尊重 metadata 時,誰是真的能打,誰只是 demo 好看。他沒有講虛的,這點我很買單。

真正難的不是相似度,是子集合相似度

訂閱 AI 趨勢週報

每週精選模型發布、工具應用與深度分析,直送信箱。不定期,不騷擾。

不會寄垃圾信,隨時可取消。

“A real-world vector search isn’t just ‘find similar vectors’ — it’s ‘find similar vectors within a subset.’”

翻譯一下就是:你在 production 裡做的,從來不是純向量搜尋,而是「在某個限制範圍內找最像的內容」。我如果做客服助理,不會想要整家公司最像的工單,我要的是這個 tenant、這個專案、這段時間、這種文件類型裡最像的那幾段。範圍才是主角,相似度只是第二步。

Qdrant 讓 RAG 先過濾再找相似

Raunaq 這個切法很重要,因為它把很多 RAG 的假問題直接拆穿。很多團隊在 notebook 裡看起來都很順,一上線就開始怪 embedding、怪 chunking、怪模型。其實不是。真正的問題是你把「語意排序」跟「權限/範圍控制」混在一起了。向量索引懂語意,metadata 懂條件,這兩個東西本來就不是天生會合作的。

我之前做過一個多租戶知識庫,早期偷懶,直接一張向量表加一個 tenant_id 欄位就上了。小流量時還行,等到某個 tenant 的文件量暴增,檢索品質就開始飄。明明應該回 10 段,結果有時候只回 3 段,有時候回 10 段但一半沒用。最煩的是 log 看起來完全正常,因為資料庫真的只是照你的規則在跑。

實操寫法很簡單:在選向量庫之前,先把「子集合規則」寫清楚。不是先決定 embedding model,也不是先挑 chunk size,而是先回答這幾件事:

  • 你有哪些必須套用的 filter?
  • 這些 filter 的選擇性高不高?
  • 這些條件會不會常變?

如果你答不出來,就先別急著選產品。你是在盲選儲存架構,不是在做技術決策。

先查再過濾,最容易把 recall 偷偷弄爛

pgvector 這條路我很熟,也不是不能用。它的優點很直白:你本來就跑 Postgres,資料、權限、交易、運維都在同一套裡,簡單到讓人舒服。問題是很多人把它拿去做有嚴格 filter 的 RAG,然後還期待結果穩定,這就有點想太多了。

Raunaq 講的 post-filtering 很關鍵:先做向量 top-k,再把 metadata filter 套上去。聽起來很省事,因為你吃到了向量索引的速度;但只要 filter 夠窄,這招就開始漏。你要 10 筆,結果 top-k 裡 8 筆被 filter 掉,最後只剩 2 筆。系統不會跳警告,它只會很安靜地交出一份不完整答案。

也就是說,post-filtering 最大的坑不是慢,而是「看起來正常」。你會以為是 embedding 不夠好、chunk 切太大、模型太爛,然後開始亂調參數。其實根本不是模型問題,是你的候選池沒有為 filter 留空間。這種錯很貴,因為你會在錯的地方花很多時間。

我之前看過一個內部搜尋專案,團隊一直嫌召回差,最後才發現是 tenant filter 太窄,卻還用一般 top-k 當標準。這種情況下,調再多 embedding 都沒用。你是在用錯的檢索策略硬撐。

實操寫法:

  • 如果你一定要 post-filter,就先 oversample,再看過濾後還剩多少。
  • 不要只看 top-k,要看「requested k vs returned k」。
  • 拿最窄的 tenant、最小的來源集、最嚴的日期區間來測。

如果最差情境都穩,才輪得到你談模型品質。

先過濾再找,才是真的尊重條件

ChromaDB 走的是另一條路:先過濾,再在過濾後的集合裡找相似。Raunaq 的描述很清楚,metadata 先走 SQLite,拿到符合條件的 ID,再在這個範圍內做向量搜尋。這不是什麼花俏做法,但它有一個很重要的優點:結果是誠實的。

Qdrant 讓 RAG 先過濾再找相似

翻譯一下就是,如果你的 filter 只命中一小塊資料,系統就只在那一小塊裡找,不會先拿一堆看起來很像、其實不合格的候選來湊數。對 local 開發、原型驗證、小型知識庫來說,這種 predictable 行為其實很舒服。我自己也用過這種設計,因為我當時要的是「先能工作」,不是「先把 infra 搞到很漂亮」。

但它的代價也很直接:如果過濾後的集合很大, brute force 會開始痛。你一旦不是在小集合裡找,速度就不會太好看。說白了,Chroma 的強項是簡單和正確,不是大規模 selective query 的效率。

Raunaq 也提到 embedded mode 的運作方式很方便,但在某些 crash 窗口裡,metadata 跟向量索引的狀態可能會讓你不太安心。這種事平常不會發生在 demo,通常都發生在你最不想重跑資料的時候。很煩,但這就是實務。

實操寫法:

  • 當你要的是 local dev、概念驗證、或小資料集,先用 filter-first 很合理。
  • 當你的 filtered subset 會長到很大,就不要硬撐 brute force。
  • 如果你很在意查詢結果一定要符合條件,先過濾通常比先向量化更踏實。

Qdrant 之所以順,是因為它從一開始就把 filter 當主角

我看 Raunaq 那篇最有感的地方,是他把 Qdrant 講得很像一個真的有在想 production 的系統。它不是把 metadata 當附屬品,而是把 payload index 跟 vector index 一起設計。這件事聽起來很小,實際上差很多。

也就是說,Qdrant 不是先假設「先找相似再說」,而是直接承認 query 本來就是混合型的:你既要語意相似,也要條件符合。它會先用 payload index 找出符合條件的 ID,再讓向量檢索只在這些 ID 裡面跑。這個方向我很認同,因為它不是在補洞,是在一開始就把洞補好。

我特別在意 Raunaq 提到的 filter bitmap。這種東西看起來像底層細節,實際上就是檢索正不正確的分水嶺。當 filter 很窄時,系統能不能先把可用候選縮出來,決定了你會不會拿到一堆不該出現的 chunk。這不是「優化」,這是基本功。

Qdrant 還有一個我很吃的點:它不是只給你一種儲存姿勢。in-memory、memory-mapped、on-disk 都有,這表示你可以依資料量跟機器條件調整,不必每次都把 RAM 當信仰。我個人會先偏向 mmap,讓 OS 幫你做快取,少一點自己手動照顧記憶體的痛苦。

實操寫法:如果你的產品有多租戶、ACL、每人不同可見範圍、或很明確的 metadata 條件,從一開始就把 filter-aware engine 放進候選。不要等到系統長歪了才補。重構 retrieval schema 比你想像中更煩,尤其是已經有使用者在上面跑的時候。

分段儲存讓 Qdrant 比較不像紙糊的

Raunaq 的圖我也很喜歡,因為它把 Qdrant 的 segmented storage 講得很清楚。這不是什麼簡報上好看的架構圖而已,它直接影響你在大量寫入下,系統會不會開始抖。

翻譯一下就是:新資料會先進到可追加的 segment,背景再慢慢做優化、合併、重建,不需要整個 collection 停機重來。這跟那種「先停機再重建索引」的做法差很多。我真的很不喜歡後者,因為它把日常營運變成維護排程,然後大家還得假裝這很正常。

分段儲存的價值不只是在寫入快,而是它讓系統可以邊長大邊維持可用。對 RAG 來說,資料不是固定不動的。文件會一直進來、刪掉、更新、重排。如果你的檢索層在這種情況下容易失真,那你再會調 embedding 也沒用。

我之前碰過一個文件平台,白天一直 ingest,晚上還要被搜尋。結果一到重建索引時間,整個 retrieval 就像踩到香蕉皮。後來我才真正理解,RAG 的問題不是只看 query latency,而是看它在「持續變動」下還能不能維持正常。

實操寫法:

  • 問清楚資料庫怎麼處理 writes、compaction、index rebuild。
  • 如果答案聽起來像要排維護窗口,先小心。
  • 把「10M inserts、delete、tenant churn、bad deploy」都拿來想一遍。

你會發現,很多系統不是不能用,是一碰到真實營運就開始露餡。

選型不要看品牌,要看你的 filter 痛點

Raunaq 這篇最有用的地方,不是幫你選出唯一答案,而是逼你承認:不同資料庫對 filter 的態度真的不一樣。你如果把這件事看成「哪個向量庫比較紅」,那你大概會選錯。

我自己的簡化版判斷是這樣:

  • pgvector:你本來就重度依賴 Postgres,filter 很鬆,想把運維壓力壓到最低。
  • ChromaDB:你在做 local dev、原型、或小型資料集,想先把流程跑通。
  • Qdrant:你的檢索本身就高度依賴 filter,多租戶、權限、條件查詢是日常,不是邊角料。

我不是在說 pgvector 不行。相反,我覺得它在很多場景都很合理,尤其是你本來就活在 Postgres 世界裡的時候。但如果你的產品核心就是「在一堆條件裡找對內容」,那你就不要再拿一個只是在資料庫外面順手補向量能力的方案硬撐。那通常會變成後面幾個月的技術債。

實操寫法:先寫你的 95th percentile query。不要寫抽象需求,直接寫真實 query:

  • 這個 tenant 有多少文件?
  • 這個 filter 平均命中多少比例?
  • 最窄的條件會縮到多小?

如果你連這些都沒算過,就先不要選庫。你不是在選工具,你是在賭未來會不會被自己打臉。

可抄的模板

# Filter-first RAG 設計模板(可直接貼到 design doc)

## 1. 先定義子集合,不要先談 embedding
- tenant_id:必填 / 選填
- source:必填 / 選填
- date_range:必填 / 選填
- acl_rule:必填 / 選填
- selectivity:
  - broad:> 50% corpus
  - medium:5%–50%
  - narrow:< 5%

## 2. 依 filter 選檢索策略
- broad filter:post-filtering 可接受
- medium filter:先 oversample,再驗證 recall
- narrow filter:優先 filter-first / filter-aware retrieval

## 3. 每個 chunk 都要帶 metadata
{
  "id": "chunk_123",
  "tenant_id": "tenant_a",
  "source": "docs",
  "doc_id": "doc_456",
  "created_at": "2026-06-02T00:00:00Z",
  "tags": ["billing", "policy"],
  "embedding": [/* vector */],
  "text": "chunk text here"
}

## 4. 檢索合約
Input:
- query_text
- filters
- top_k

Output:
- results 必須符合 filters
- 系統可內部 oversample
- 若不足 top_k,允許回傳較少結果,但要明確標示

## 5. 資料庫選型規則
- pgvector:已經在用 Postgres、filter 較鬆、想少一套運維
- ChromaDB:local prototyping、小資料集、要快點跑起來
- Qdrant:selective filters、多租戶、ACL、production retrieval

## 6. 必測案例
- no filter
- single tenant filter
- highly selective tenant filter
- date range filter
- tenant + source filter
- worst-case tenant with smallest corpus

## 7. 要記錄的 metrics
- requested_top_k
- returned_count
- filter_selectivity
- latency_ms
- recall_on_test_set
- fallback_used

## 8. Prompt / 生成規則
Only pass chunks that satisfy the filter.
If fewer chunks are available, do not invent missing context.

## 9. 決策備註範例
- "We chose Qdrant because our retrieval is tenant-scoped and filters are often selective."
- "We chose pgvector because our data already lives in Postgres and filters are broad."
- "We chose ChromaDB for local prototyping and offline document search."

這段我會直接貼進自己的設計文件裡,因為它逼團隊先把檢索條件講清楚,再來談工具。這樣至少不會一開始就把問題做歪。

原始來源是 https://raunaqness.substack.com/p/vector-databases-for-rag。我上面對 pgvector、ChromaDB、Qdrant 的整理是衍生解讀;模板與實作寫法是我根據同一個 filter-first 觀念重寫成可直接拿去開 design review 的版本。