– 沒有了
索引時的Boost
,但你還有我啊!
1. What is this?
Payload在Lucene中表述是一個任意 byte[],它可以(只是可以不是必須)爲它每個Term存儲額外的信息到倒排索引裏面。而且這些Payloads可以被用作Query的一部分來過濾,或者生成對應值用於打分,甚至替代文檔的返回值。
Payload能幹嘛呢
前面提到了,Payloads可以被用作Query的一部分來過濾,或者生成對應值用於打分,甚至替代文檔的返回值。這個非常強大的東西,你理解起來非常容易,你直接當她是一個Hive
中的JSON
類型即可,如果還是有點陌生的話,直接當她是一個Map
吧。即是可以key
找value
。
到這裏,你可能要問了。我們本來就是一個動態文檔,原本就是鍵值對的式子,那要你幹嘛呢?
其實,此時你應該把她當成一個nested document
。後面會舉這個例子,請繼續往下閱讀。
如果你應該理解上面說的,把她當成一個文檔的子文檔的話。其實她的功能應該很好理解,顯而易見了。
- 更人性化的文檔組織方式
- 用內嵌文檔(子文檔)的某個字段進行操作
- 排序
- Faceting
- 返回指定key的value
- 子文檔flatMap
2. Show Cases
這個例子有個名字,它叫Per-Store Pricing,在 lucidworks 多博客文出現過,感覺非常好,先借來改改先用着。原例子用的相對比較穩定的價格作爲示例,但我覺得用庫存這個動態的屬性作爲示例會更加容易解釋,但不需要過來多說明。
pre-setting :
這是一個在線連鎖店的中央倉存系統,它會記錄每個商品在每家的庫存。由於店鋪的位置和大小的原因,商品a不是所有店鋪都會銷售。
假定這是一個 Snapshot
有了上面這個大前提之後,我們繼續來設定一些假定的操作:
- 找出A店所有商品,並按庫存量排序
- 找出A店庫存在100-200之間的所有商品
- 想某些商品在某些店的庫存情況
針對上面設定和問題,我們嘗試組織文檔和定義操作。
- 再商品信息中加入庫存信息,每家店鋪都是一個獨立的字段
這種作法最大的問題是當然店鋪數量很多的時候,一個文檔的字段非常多,即是多少店鋪就得冗餘多少字段。 - 在庫存信息中加入商品信息,每家店鋪都是一個獨立的文檔
這種作法即是文檔數會很多,但是並不要緊,關鍵是想查商品的情況時,需要做groupBy
的操作,這是比較耗性能的操作。 - 採用payload的方式,在商品信息加入店鋪的庫存信息作爲payload
這就是我們要講的內容了。
- 再商品信息中加入庫存信息,每家店鋪都是一個獨立的字段
2. Payloads in Solr
a. 如何在Solr上啓用Payload
你把Payload說得這麼好,那我怎麼Solr裏把它上用起來呢,讓全世界都知道它的好。在Solr6.6
之後,我們知道它是隻是一個字段類型,那麼其實很好理解的,即是在schema.xml
多配一個字段類型咯,並把它加對應的字段即可了。
1. Change your schema.xml file
<fieldtype name="payloads" stored="false" indexed="true" class="solr.TextField" >
<analyzer>
<tokenizer class="solr.WhitespaceTokenizerFactory"/>
<filter class="solr.DelimitedPayloadTokenFilterFactory" encoder="integer"/>
</analyzer>
<similarity class="payloadexample.PayloadSimilarityFactory" />
</fieldtype>
<field name="stock_dpi" type="payloads" indexed="true" stored="true"/>
- 這裏
similarity
默認是SchemaSimilarity
,它決定payload_score
與Query得分
的關係。 - encoder 的取值
${float,integer}$
2. Add the payloaded term to the document
我們已經加了這麼一個字段類型和字段了,接下來我們需要把文檔帶着payload信息文檔提交給Solr進行索引。當然這也很簡單,它就是我們配置的一個字段類型嘛,那麼我們要求進行組織字段的值即可了。
按我們上面的配置(爲簡單起見我們就配置三個字段)
{
"id":"123",
"stock_dpi":"a|100 b|200",
"name":"cafe"
}
b. Payload神器,三把斧
正因爲這三把神器,從而真正牽引我來寫這篇博客。其實非常簡單,也非常實用。好了,我們開始吧。
1. payload() function
payload() 函數並不能作爲查詢條件,只能用於修改顯示的結構作爲僞字段出現在文檔中。即只用在fl
的條件,不能用於除fl之外
的場景。
payload是對word而言,它是有位置的。即是同一個詞在同個字段中的不同位置可以賦不同的payload值的。
payload()函數簽名:
payload(field, term [,default[,min|max|average|first]])
函數參數說明
- f 必填,字段名
- v 必填,需要轉化的Key
- def 選填, 當對應的key
沒有對應的value
時的默認
- func 選填,當有一個key
對應有多個value
時,進什麼計算,默認是average
Example
payloads = ‘a|1.0 a|2.0 a|3.0 b|0.1’
payload(payloads, a) = 2.0
payload(payloads, c, -1.0) = -1.0
payload(payloads, a, min) = 1.0
payload(payloads, c, -1.0, min) = -1.0
2. Query Function Parser
這裏我們來介紹一下兩個遲到Query Parser
,PayloadScoreParser和PayloadCheckParser兩個查詢解釋器。按理說它應該很早應該出現,即使不能跟Lucene4.x
一起出現,但實在不至於到現在Solr6.6
纔出生。當然,在此之前有過很多民間的實現。因此這兩個解釋器的出現,使得Payload
變得有意義、完整。
關於payload的索引存儲原理這裏我們只做簡單的解釋,不做深入的解析。
這些查詢解析器利用term|payload
在索引期間最終會翻譯成SpanQuery,SpanNearQuery或者SpanTermQuery。也就是Term與payload其實是成對存在於索引表上的,跟坡度查詢和短語查詢差不多意思。
a. PayloadScoceParser : payload_score
簡單的說,如果用某個子文檔的某個字段的值來參與評分呢?這就是我們接下來要討論的payload_score
了。
這些與
boost
有幾分相似了。多說一句,索引時boost已經Solr6.6
棄用了。爲何棄用Boost呢,我想你應該能理解,即是Boost的功能相當單一且不夠靈活。當然啦,也是因爲Payload的引用,使得Boost更加被弱化了。
我們已經payload字段是按一定的規則組織Term和Payload之間的關係的,如上例中我們是這樣的組織:
stock_dpi = a|100 b|200 c|300 a|101 a|102 a|103
- 其中
等號左邊
爲字段名,即是下面參數f
需要指定的字段名
。 - 等號右邊是字段值,它是一系列
鍵值對
。即這裏的每個鍵便是term
,值爲payload
參數說明:
- f 指定使用哪個payloads字段,必填。
v 拿哪個
key
(term
)來查詢,必填。相當於說,需要指哪個子文檔的哪個字段,它裏
v
即是這的哪個字段
。func 它的計算結果作爲
payload_score
的分數值,必填。payload function必須填,對payload的進行計算的函數。
includeSpanScore 是否把QueryFunction的查詢拼入一般的查詢條件中共同打分
是把 payload_score 的查詢函數作爲SpanQuery的條件,參與原查詢的打分規則。
如果是,
$score = score(Q_{org} + Q_{pay}) + pay$
;
否則的話,$score = score(Q_{org}) + pay$
Example
q={!payload_score f=my_field_dpf v=some_term func=max}
q={!payload_score f=stock_dpi v=a func=average}
對func而言,它僅對f
中的v
有多值時,纔會有意義。
1. {!payload_score f=payloads v=a func=min} = 1
2. {!payload_score f=payloads v=a func=max} = 3
3. {!payload_score f=payloads v=a func=average} = 2
b. PayloadCheckParser : payload_check
PayloadCheckParser提供的是一個針對Term-Payload檢索功能,即是通過指定鍵值
,搜索含有的所有文檔。這麼說可能不好理解,簡單的說,指定了子文檔的字段名和值,希望找到子文檔的該字段名下的值能匹配上的所有文檔。
因此,它跟普通的查詢一樣必須需要指定字段名,和對應的值。
參數說明
- f 指定字段名,必填。
payloads 指定payload, 支持多值,之間用空格分開。必填。
v 指定key,必填
Example
方式一:
{!payload_check f=words_dps payloads=”VERB NOUN”}searching stuff
方式二:
{!payload_check f=words_dps payloads=”VERB NOUN” v=searching stuff}
3. Faceting on numeric payloads
Faceting出生很早,是Lucene統計分析的基石,大家絕對不會陌生。Faceting在分析方面那麼好、那麼優秀,那麼她能不能延伸到PayloadQuery呢?
其實facet.range
只支持實際字段,而不支持僞字段。然而她又那麼好,不忍捨棄,所以在現在版本得用一些比較偏門的方法,即是facet.query
,對payload()再進行{!frange}
query function查詢來完成類facet.range
查詢。
後面版本可能會實現對
payload
進行facet.range
,等着吧。
- facet.query={!frange key=up_to_400 l=0 u=400}${computed_price} // includes price=400.00
- facet.query={!frange key=above_400 l=400 incl=false}${computed_price} // excludes price=400.00
3. 回顧例子,Payload On Solr
前面提到了,Payloads可以被用作Query的一部分來過濾,或者生成對應值用於打分,甚至替代文檔的返回值。
A. Solr下的原例1 per-store pricing
上面挖了坑,現在我們來把它填上。在Solr6.6
之後的Solr如何優雅解決這個問題的呢。前面我們討論per-store pricing
問題時,已經給出結論,但我們並沒有說明這個方案應當如何操作,接下來我們就來說說它應該如何操作。
在上面我們提出了這如下幾問題,如今是時候把他解決掉了。
1. 找出商品A所有商鋪,並按庫存量排序
2. 找出A店庫存在100-200之間的所有商品
3. 想某些商品在某些店的庫存情況
現在我們按商品信息進行組織文檔,然後把庫存信息作爲payloads字段類型加入到文檔中。即是
{
"id":"product00001"
"name":"product_a",
"price":100.0,
"stock_dpi":"stock_a|100 stock_b|200 stock_c|300",
...
}
現在我們可以這麼做
1. q=stock_dpi:stock_a&sort=payloads(stock_dpi, stock_a, 0, min) desc
2. facet.query={!frange key=up_to_400 l=0 u=400}payload(stock_dpi, stock_a, 0)