多個MUST的倒排表合併

2021-09-14 02:11:13 字數 3210 閱讀 3008

skiplist本質上是在有序的鍊錶上實現實現二分查詢,它能有效的提公升鍊錶的查詢效率,其時間複雜度為o(logn)(其中n為鍊錶長度)。簡單說skiplist優化了postings的隨機查詢的效能問題。

skiplist的節點儲存了三部分資料,分別是當前節點指向block的資訊,是關於block本身的資訊;指向下層的索引;最後是儲存freq和norm的資訊,它被封裝在impact裡面。

impact結構僅是的鍵值對,與文件無關,在skiplist的索引節點中。impacts表示一系列impact結構,用有序的treeset儲存。這裡強調的是impact並沒有與具體文件關聯,其次按freq和norm作為主鍵去重。也就是impacts代表了該索引節點指向資料點以及之前所有資料節點所包含的文件得分的分布。

如果此索引節點中最大的impact都小於scorer的水位線,那麼此節點的範圍內的所有節點都不需要再進入scorer評分程式,在top_score模式下。

從.doc檔案讀取出來的skiplist如下,為了方便製圖,把步長縮小為2。那麼在第0層,每兩個block建立乙個索引節點,第1層在第0層的基本上構建,依此類推。

常規多層跳表結構,每個索引節點兩個指標,乙個指向同層下乙個節點,叫next指標;另乙個指向下一層的down指標。在下圖中,第1層的節點4指向第0層的節點4的指標即是down指標,而從節點4直接指向節點8的叫next指標。

實際上skiplist的效能提公升是通過在鍊錶上加上多級索引獲得的,所以說它屬於空間換時間的做法,在索引時犧牲小量空間換取在搜尋時的效能提公升。而層級越高,索引的步長越短,構建索引的空間代價也會越高。這也解釋了lucene為什麼要採用8個block作為步長,雖然它的查詢效能相比會差一些,但是需要的空間也縮減少n/8,是一種儲存空間和效能的折中方案。

查詢過程:以查詢第7個block為例,與最上層第二層的第1個節點比較,7 < 8;通過down指標下沉到第一層,7 < 4,通過next指標找到下乙個索引節點繼續比較,7 < 8。所以回溯到節點4,然後下沉到第0層,7 > 6且7 < 8。所以回到6節點並下沉,前進乙個節點之後發現7 = 7,成功找到並返回。

lucene的skiplist僅多花費n/8的儲存空間,便將block的隨機查詢的效能提到o(logn)的時間複雜度。postingsenum的advance(target)是skiplist主要應用場景,它除了應用於top_score,還能用在多個結果集間做析取和合取運算上。

booleanquery

在真實的運用情景下,並非全是單個查詢條件的,它更多的往往是多個條件的復合查詢。布林查詢(booleanquery)是檢索模型中最簡單且使用廣泛的模型,通過布林代數的連線詞(與或)將複雜的查詢集合串聯成布林表示式,最終通過布林代數計算查詢與文件之間的相似度的。

其所有葉子節點都是原子查詢,它需要讀取postings資訊,但非葉子節點都通過對葉子節點的postings進行謂詞運算獲得。

布林查詢由與、或兩種連線詞串聯起來的表示式,在查詢場景下考慮的是如何將每個查詢條件查詢得到的postings實現布林表示式的運算呢?換言之,換成數學問題中如何實現docid集合進行交集、並集運算。對於與運算,是需要如何找出所有集合共同出現的子集——取交集運算;或運算,需要考慮的則是如何去重——取並集運算。

lucene為postings的遍歷設計了乙個叫advance(traget)的方法,含義是前進到postings中不小於target的最小的文件編碼(docid)。如果不存在滿足條件的文件時,返回no_more_docs。其隱含含義是postings迭代器中沒更多的文件,遍歷結束。

隨著搜尋引擎索引索的文件越來越多,一次查詢中某些term的postings的長度可能會很長,尤其是乙個term(常用詞)出現在非常普遍的文件中。此時對整個postings的所有文件都進評分的代價也會隨之增高,因此根據集合的布林運算的特點設計如下兩種演算法。

以下描述和理解;有衝突:個人理解計算並集是依次遍歷每個termsocre, 然後再遍歷每個termsocre下的倒排表中的每個doc內容,然後計算部分得分,相同文件好的得分做累加;

實際上就是在多個集合間取交集,易知最終結果集必然是任意集合的子集。因此,基於最小的集合開始遍歷,可以避免不必須嘗試。而lucene通過二階驗證,可以進一步減小無效嘗試。基本思想是,合併後的結果集中每個文件必須是每個postings都存在。

lucene實現比較巧妙,首先在posting lists中取出最短postings命名為lead1,接著取出次短postings的命名lead2,除此之外稱為others。然後遍歷lead1的每個文件的過程中,每個文件都在lead2中做校驗。假如在lead2中不存在,則直接退出,否則到others中校驗判斷是否存在。簡單說通過lead1可以非常有效的減小嘗試次數,通過lead2則能進一步減小嘗試的次數。總體思路就是避免到others列表校驗文件是否存在,流程如下。

在others的校驗的式子如下,一旦max(...)返回no_more_docs退出迴圈,合併完成。

boolean matches = (docid == max(pe1.advance(docid), pe2.advance(docid), pe3.advance(docid), ...);

通過如上流程中,都是通過postingsenum#advance(target)方法尋找離target最近且不小於target的docid。而advance(target)在有skiplist的情況下,可能會啟用skiplist優化。

private int donext(int doc) throws ioexception }}

// success - all docsenums are on the same doc

// 找到了包含所有關鍵字的文件號

return doc;

}// advance head for next iteration

//當前leader中的doc不滿足,找leader中不小於doc號的doc,即下一doc

doc = lead.doc = lead.scorer.advance(doc);}}

Lucene 合併倒排表演算法之交集

可能大家都知道,lucene採用了傳統搜尋引擎中倒排表的資料結構.在搜尋時,假設我們要查詢 a test b test1 的話,首先要先查詢得到a欄位中包含 test關鍵字的倒排表,然後查詢得到b欄位中包含test1關鍵字的倒排表,然後對兩個倒排表結構進行merge操作 計算兩者間的交集就是我們的查...

倒排表在磁碟上的布局

由於倒排表的大小一般都很大,所以大部分搜尋引擎都將倒排表儲存在磁碟上。下圖是乙個倒排表的總體結構布局 磁碟是由乙個個資料塊 block,大小固定,例如64kb 組成的。倒排表中的乙個列表 乙個單詞對應的列表 可能跨越多個block,開始於某個block中的某處,結束於另外乙個block的某處。blo...

redux的合併多個reducer

建立store需要傳入reducer createstore reducer,preloadedstate,enhancer reducer是乙個函式,傳入當前state和action,返回新的state prestate,action nextstate當我們需要將多個reducer合併成乙個時 ...