Skip to main content

OpenSearch Index 教學:Mapping、textkeyword

本文件說明 thortron_one 全文檢索(Full-Text Search)如何建立 OpenSearch index,以及 mapping 裡 text / keyword 的差異與選用原則。

對應實作:internal/pkgs/utilities/fulltextsearch/opensearch/client.go


1. 這套 FTS 在存什麼?

每個 PDF 頁面會寫成一筆 OpenSearch document,大致欄位如下:

欄位OpenSearch type說明
document_idlong文件儲存系統的 document ID
filenametext + filename.keyword檔名(全文搜尋 + 精確值)
contenttext該頁擷取出來的文字
pageinteger頁碼
sourcekeyword來源識別,目前固定 thortron_one
indexed_atdate寫入時間(RFC3339)

Document _id 格式:thortron_one-{documentID}-p{page}


2. Index 什麼時候建立?

連線時由 Client.New 呼叫 ensureIndex

Client.New(url, index)
└─ ensureIndex
├─ Indices.Exists
│ ├─ 200 → index 已存在,不做任何事
│ └─ 404 → Indices.Create(套用下方 mappings)
└─ 連線成功後才能 IndexPages / Query

重點:

  • 第一次連到空的 OpenSearch 時,會自動建立 index 與 mappings。
  • Index 一旦存在就不會更新 mapping。若要改欄位型別(例如 keywordtext),必須刪除重建(見 RecreateIndex / migration 流程)。
  • DeleteByQuery 只能清資料,不能改 mapping。

3. 實際 Mapping

建立 index 時送出的 body(精簡版):

{
"mappings": {
"properties": {
"document_id": { "type": "long" },
"filename": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"content": { "type": "text" },
"page": { "type": "integer" },
"source": { "type": "keyword" },
"indexed_at": { "type": "date" }
}
}
}

搜尋時使用 multi_match,查詢欄位為:

  • content(text)
  • filename(text)

也就是:使用者關鍵字走全文檢索路徑,不是 filename.keyword 的精確比對。


4. textkeyword 差在哪?

兩者都是「字串相關」的型別,但索引與查詢行為完全不同。

對照表

textkeyword
主要用途全文搜尋(full-text search)精確比對、排序、聚合(filter / sort / agg)
寫入時會經過 analyzer 分詞整段字串當成一個 term,不分詞
查詢方式match / multi_matchterm / terms
典型例子內文、標題、可模糊搜的檔名ID、狀態碼、來源、標籤、固定枚舉值

用例子理解

假設寫入:

content = "Install the battery pack carefully"
filename = "User Manual Rev2.pdf"
source = "thortron_one"

content(text)

  • Analyzer 可能拆成:install, battery, pack, carefully
  • 搜尋 "battery" → 可以命中
  • 搜尋 "battery pack" → 也可以命中(依 analyzer / 查詢型態)

source(keyword)

  • 索引裡只有一個 term:thortron_one
  • 適合:term filter source = "thortron_one"
  • 若誤用 match 去查 "thortron",行為與預期常不一致(因為它本來就不是為全文搜尋設計)

filename(text + keyword multi-field)

同一份原始字串存成兩種索引:

欄位路徑型別行為
filenametext檔名可被分詞,給 multi_match
filename.keywordkeyword整段檔名精確值,可做 sort / aggregation / exact filter

ignore_above: 256 的意思:

  • 單位是 bytes(不是字元數)
  • 超過 256 bytes 的值 不會寫進 filename.keyword
  • filename 的 text 全文搜尋仍然可用
  • 目的:避免超長檔名把 terms dictionary 撐很大

5. 什麼時候該用哪一種?

text 若你需要

  • 使用者輸入關鍵字,要「包含某詞就命中」
  • 句子、段落、說明文字、可模糊搜的標題/檔名

本專案:contentfilename

keyword 若你需要

  • 完全相等才算命中(ID、狀態、來源、tag)
  • 依該欄位排序
  • 做 aggregation(例如依 source 分組統計)

本專案:source;以及 filename.keyword

兩種都要 → Multi-field(本專案 filename 的做法)

"filename": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
}

查詢時:

  • 全文搜 → 用 filename
  • 精確比對/排序 → 用 filename.keyword

6. 寫入與搜尋路徑(對應程式)

寫入(Index)

API IndexPDFs
→ fulltextsearch.IndexPDFs
→ 讀文件、確認是 PDF
→ pdf 擷取每頁文字
→ Client.IndexPages
→(可選)先刪除同 document_id 舊頁
→ bulk index:每頁一筆 document

搜尋(Query)

API Search
→ Client.Query
→ multi_match
query: <使用者關鍵字>
fields: ["content", "filename"]

注意:搜尋 沒有 打到 filename.keywordfilename.keyword 是為精確場景預留的子欄位。


7. 常見誤解

  1. 「字串就該用 keyword」
    錯。要全文搜尋就用 text;要精確值才用 keyword

  2. 「改 mapping 只要重新 index 資料就好」
    錯。既有 index 的 field type 不能就地變更;型別變更需要刪除重建 index(本專案有 RecreateIndex / migration)。

  3. ignore_above 會讓檔名搜不到」
    錯。它只影響 filename.keywordfilename(text)仍可搜。

  4. text 也能拿來做精確 filter」
    不建議。分詞後精確比對不可靠,精確場景請用 keyword(或 .keyword 子欄位)。


8. 快速決策流程

這個欄位要給使用者「打關鍵字搜內容」嗎?
├─ 是 → text
│ └─ 同時也要精確比對 / 排序 / 聚合?
│ └─ 是 → text + fields.keyword(multi-field)
└─ 否 → 只要完全相等 / 排序 / 聚合?
└─ 是 → keyword

套用到本專案:

  • PDF 內文 → content = text
  • 檔名要搜、也可能要精確處理 → filename = text + .keyword
  • 來源標記 → source = keyword

9. 相關程式位置

主題路徑
Index mapping / IndexPages / Queryinternal/pkgs/utilities/fulltextsearch/opensearch/client.go
業務:讀 PDF、批次 index、搜尋internal/pkgs/utilities/fulltextsearch/impl.go
Document 結構entity/utilities/fulltextsearch/v1/models/models.go
REST APIadapters/api/endpoints/restful/v1/fulltextsearch/impl.go
Index 重建遷移internal/pkgs/utilities/fulltextsearch/migration.go
本地 OpenSearchapp/infra/local/containers/opensearch/docker-compose.yml
遷移工具說明playground/thomas/full-text-search/README.md

10. 一句話記住

  • text:拆開來找(全文檢索)
  • keyword:整段比對(精確值 / 排序 / 聚合)
  • filename:兩種都要,所以做成 multi-field