使用 Postgres 的全文搜尋構建可擴充套件的事件驅動搜尋架構
需求:
搜尋由三個可搜尋欄位、標題和描述(使用全文搜尋)和文件 ID 組成的文件,能夠查詢包含文件 ID 的字串。
搜尋應該在不超過 200 毫秒的時間內執行超過 100 萬個文件。
PostgreSQL 支援全文搜尋。全文索引允許對文件進行預處理並儲存索引以供以後快速搜尋。
-- Create the Documents index table CREATE TABLE IF NOT EXISTS index."documents_index" ( "id" SERIAL, "created_on" bigint NOT NULL, "updated_on" bigint NOT NULL, "customer_id" character varying(150) NOT NULL, "document_id" character varying(255) NOT NULL, "document_type" character varying(50) NOT NULL, "document_title" text, "document_description" text, "words" text, "ts" tsvector GENERATED ALWAYS AS (to_tsvector('english',document_id || ' ' || document_title || ' ' || document_description)) STORED, "metadata" jsonb, CONSTRAINT "documents_index.primary_key" PRIMARY KEY ("customer_id", "document_id", "document_type")) -- Create GIN index on the ts (tsvector) column to improve search. CREATE INDEX IF NOT EXISTS documents_index_ts_idx ON index."documents_index" USING GIN (ts); -- Create Trigram Index on words column. (Requires installing pg_trgm Postgres extension) CREATE INDEX IF NOT EXISTS documents_index_trgm_idx on index."documents_index" USING GIN ("words" gin_trgm_ops); |
- 我們在表中新增了一個新的列ts,以儲存預處理的搜尋檔案(即詞庫列表)。ts是一個生成的列(Postgres 12的新列),自動與源資料同步。然後我們在ts列上建立了一個tsvector型別的GIN索引。
- 為了實現模糊搜尋,我們使用了pg_trgm Postgres擴充套件,並在表中新增了一個word列來儲存可搜尋文字。該列儲存的是可搜尋欄位的連線字串。
- 最後,pg_trgm擴充套件提供了GiST和GIN索引運算子類。該索引允許我們在單詞文字列上建立索引,以便進行快速的相似性搜尋。
import {BeforeInsert, BeforeUpdate, Column, Entity, Generated, PrimaryColumn} from 'typeorm'; @Entity({name: 'index', schema: 'documents_index'}) export class Document { @PrimaryColumn({name: 'id', nullable: false}) @Generated('increment') id: number; @Column({name: 'created_on', nullable: false}) createdOn: number; @Column({name: 'updated_on', nullable: false}) updatedOn: number; @Column({name: 'customer_id', nullable: false}) zoneId: string; @Column({name: 'document_id', nullable: false}) documentId: string; @Column({name: 'document_type', nullable: false}) documentType: string; @Column({name: 'document_title', nullable: false}) documentTitle: string; @Column({name: 'document_description', nullable: false}) documentDescription: string; @Column({name: 'words', type: 'text'}) words: string; @BeforeInsert() @BeforeUpdate() async calculateWords() { const fullText = this.documentId + ' ' + this.documentTitle + ' ' + this.documentDescription; const unique = Array.from(new Set(fullText.split(' '))); this.words = unique.join(' '); } @Column({name: 'metadata', type: 'jsonb'}) metadata: any; @Column({name: 'ts', type: 'tsvector'}) tsVector: any; } |
我們測試了下面的查詢,它使用pgbench工具返回 436 行:
SELECT id, document_id, document_title, document_description, COALESCE(similarity(words, 'management system'),0) + COALESCE(ts_rank_cd(ts, 'management & system'),0) as relevancy FROM "index"."documents_index" WHERE customer_id = '1' AND ( ts @@ to_tsquery('english', 'management & system') -- OR words ILIKE '%management system%' ) ORDER BY relevancy DESC, id ASC |
我們能夠實現每秒約 170 筆交易。
大量資料儲存在資料庫中,效能和擴充套件會隨著資料的增長而受到影響。分割槽透過將大表分成較小的表來解決這個問題,減少記憶體交換問題和表掃描,並提高效能。
-- Create the Documents index table CREATE TABLE IF NOT EXISTS index."documents_index" ( "id" SERIAL, "created_on" bigint NOT NULL, "updated_on" bigint NOT NULL, "customer_id" character varying(150) NOT NULL, "document_id" character varying(255) NOT NULL, "document_type" character varying(50) NOT NULL, "document_title" text, "document_description" text, "words" text, "ts" tsvector GENERATED ALWAYS AS (to_tsvector('english',document_id || ' ' || document_title || ' ' || document_description)) STORED, "metadata" jsonb, CONSTRAINT "documents_index.primary_key" PRIMARY KEY ("customer_id", "document_id", "document_type")) PARTITION by HASH("customer_id") -- Create GIN index on the ts (tsvector) column to improve search. CREATE INDEX IF NOT EXISTS documents_index_ts_idx ON index."documents_index" USING GIN (ts); -- Create Trigram Index on words column. (Requires installing pg_trgm Postgres extension) CREATE INDEX IF NOT EXISTS documents_index_trgm_idx on index."documents_index" USING GIN ("words" gin_trgm_ops); CREATE TABLE IF NOT EXISTS index."documents_index_part_1" partition of index."documents_index_part" for values with (MODULUS 10, REMAINDER 0); CREATE TABLE IF NOT EXISTS index."documents_index_part_2" partition of index."documents_index_part" for values with (MODULUS 10, REMAINDER 1); CREATE TABLE IF NOT EXISTS index."documents_index_part_3" partition of index."documents_index_part" for values with (MODULUS 10, REMAINDER 2); CREATE TABLE IF NOT EXISTS index."documents_index_part_4" partition of index."documents_index_part" for values with (MODULUS 10, REMAINDER 3); CREATE TABLE IF NOT EXISTS index."documents_index_part_5" partition of index."documents_index_part" for values with (MODULUS 10, REMAINDER 4); CREATE TABLE IF NOT EXISTS index."documents_index_part_6" partition of index."documents_index_part" for values with (MODULUS 10, REMAINDER 5); CREATE TABLE IF NOT EXISTS index."documents_index_part_7" partition of index."documents_index_part" for values with (MODULUS 10, REMAINDER 6); CREATE TABLE IF NOT EXISTS index."documents_index_part_8" partition of index."documents_index_part" for values with (MODULUS 10, REMAINDER 7); CREATE TABLE IF NOT EXISTS index."documents_index_part_9" partition of index."documents_index_part" for values with (MODULUS 10, REMAINDER 8); CREATE TABLE IF NOT EXISTS index."documents_index_part_10" partition of index."documents_index_part" for values with (MODULUS 10, REMAINDER 9); |
對 Index Storage 表進行分割槽後,我們實現了近 60% 的查詢效能提升。
術語
1、詞幹化Stemming
這是一個將一個詞還原為其詞幹的過程,該詞幹與字尾和字首或詞根相連,被稱為詞根,以確保該詞的變體在搜尋中與結果相匹配。例如,管理、經理、管理可以從一個詞Manag中提取詞幹,在搜尋manag這個詞時,將返回具有這個詞的任何變體的結果。線上詞幹工具
2、詞幹NGram
它就像一個在單詞上移動的滑動視窗--一個連續的字元序列,直到指定的長度。例如,單詞將變成{'w', 'wo, 'wor', 'ord', 'rd'}。NGram可以用來搜尋一個詞的各個部分,甚至從中間搜尋。最常用的NGram型別是Trigram 。
3、模糊性
模糊性 "指的是在比較兩個字串時,解決方案不尋求完美的、逐個位置的匹配。相反,它們允許一些不匹配(或'模糊性')。例如,對succesful這個詞的搜尋也會返回有success的結果。常見的應用包括拼寫檢查和垃圾郵件過濾。
4、相似性
兩個詞的相似性可以透過計算它們共有的卦數來衡量。這個簡單的想法對於測量許多自然語言中單詞的相似性非常有效。
5、排名
排名試圖衡量文件與特定查詢的相關程度,這樣當有許多匹配時,最相關的文件可以被首先顯示出來 Postgres支援排名和加權排名。通常情況下,加權是用來標記文件的特殊區域的詞,如標題或最初的摘要,以便它們可以比文件正文中的詞有更多或更少的重要性。
相關文章
- 可擴充套件的搜尋元件套件元件
- SharePoint 2013 開發——搜尋架構及擴充套件架構套件
- 指數級加速架構搜尋:CMU提出基於梯度下降的可微架構搜尋方法架構梯度
- 使用 Docker 和 Elasticsearch 構建一個全文搜尋應用程式DockerElasticsearch
- Laravel 之搜尋引擎elasticsearch擴充套件ScoutLaravelElasticsearch套件
- Elasticsearch——全文搜尋Elasticsearch
- Nebula 基於 ElasticSearch 的全文搜尋引擎的文字搜尋Elasticsearch
- 【譯】使用PHP和SQL構建可搜尋的加密資料庫PHPSQL加密資料庫
- 微軟推出必應程式碼搜尋擴充套件微軟套件
- tinysearch/tinysearch: 使用Rus和Wasm構建的靜態網站的微型全文搜尋引擎ASM網站
- SQLite中使用全文搜尋FTSSQLite
- 分散式搜尋引擎Elasticsearch的架構分析分散式Elasticsearch架構
- ElasticSearch全文搜尋引擎Elasticsearch
- Laravel xunsearch 全文搜尋Laravel
- oracle全文搜尋功能Oracle
- ✍ 寫一個擴充套件性較強的搜尋主頁套件
- Django與微服務架構:構建可擴充套件的Web應用Django微服務架構套件Web
- Spring Boot整合Postgres實現輕量級全文搜尋Spring Boot
- 有贊搜尋系統的架構演進架構
- Elasticsearch 的配置與使用,為了全文搜尋Elasticsearch
- 雅虎釋出API,構建自己的搜尋引擎API
- 用Elasticsearch構建電商搜尋平臺Elasticsearch
- 谷歌雲提出漸進式神經架構搜尋:高效搜尋高質量CNN結構谷歌架構CNN
- 使用Node,Vue和ElasticSearch構建實時搜尋引擎VueElasticsearch
- 搜尋引擎的體系結構
- 深度學習的未來:神經架構搜尋深度學習架構
- YouTube的架構擴充套件架構套件
- 構建可擴充套件的有態服務套件
- 構建可擴充套件的應用(一) (轉)套件
- 使用 Laravel Scout + ElasticSearch 實現全文搜尋LaravelElasticsearch
- 18. 使用MySQL之全文字搜尋MySql
- DoorDash使用 Kafka 和 Flink 構建可擴充套件的實時事件處理Kafka套件事件
- [外掛擴充套件]通過標題搜尋站外資訊套件
- Lucene : 基於Java的全文搜尋引擎Java
- 微信全文搜尋優化之路優化
- 如何全文搜尋oracle官方文件Oracle
- Mac上神奇的內建搜尋引擎——Spotlight(聚焦搜尋)Mac
- 深度解析搜尋引擎的原理結構