從未降級的搜索技術-天貓SKU搜索

jopen 10年前發布 | 8K 次閱讀 搜索技術

  前些天,五福老大的文章《從未降級的搜索技術》介紹了搜索雙 11 的 5 件新式武器,其中就包括天貓 SKU 搜索。本文就對此做一些更詳細的介紹:

  什么是 SKU

  SKU,Stock Keeping Unit,庫存單元,是商品庫存的最小單位。通俗的講,一種商品可能有各種規格的貨,每一種貨就是一個 SKU。比如,iphone6 有白色 16G、金色 16G、白色 64G、金色 64G、等多種 SKU;再比如商家售賣的某款T恤有白色S碼、黑色S碼、白色M碼、黑色S碼、等等 SKU。

  SKU 的概念在 tmall 這個平臺上其實已經存在很久了。在 tmall 上,當我們進入一個商品詳情頁時,會看到 SKU 相關的選項。比如進入某商家售賣的 iphone6 手機的商品詳情頁,你可以選擇具體規格的 iphone6 手機:顏色是白色、金色、還是黑色?內存大小是 16G、64G、還是 128G?如果某種規格的手機沒貨,比如金色 64G 無貨,那么頁面會禁止你同時選中”金色”和”64G”,或者在你選中這樣的組合之后提示你無貨。當你選定了一種規格,如果有貨,頁面會顯示對應規則的售價 (不同規則售價很可能是不一樣的哦),這時你才能對此商品下單。而此時,其實你是選中的就是一個確定的 SKU。

  不過很遺憾,一直以來,你只有進入商品詳情頁才能看到這些關于 SKU 的選項,才知道什么樣的 SKU 是有貨的,具體售價是多少。而在商品搜索頁面展示的結果只有商品維度的信息。

  SKU 搜索能帶來什么

  如果你對于精細化的搜索沒什么需求,只是想隨便逛逛,那么原先的搜索體驗對你來說應該問題不大。否則,你可能會遇到一些不方便,比如:

  • 你想買 64G 的 iphone6,想找一個價位合適一些的商家。但是搜索結果沒法給你展現 64G 的 iphone6 賣什么價錢(一般為了吸引眼球展現的都是最便宜的 16G 的價格,或者直接展示一個總的價格區間),你只能在茫茫多的搜索結果中逐個點擊進入商品詳情頁,然后再逐個比較。甚至不少商家可能暫沒有 64G 的貨,卻也堂而皇之的出現在搜索結果中;
  • 跟上一個場景比較類似,可能你看到茫茫多的搜索結果被嚇壞了,打算做一下價格過濾,只搜索售價低于 5000 塊的 64G iphone6。但是結果再一次讓你失望了,既然搜索結果沒法將 64G iphone6 準確的價格區間展現給你,那么價格過濾必定也不是你所期望的結果;
  • 類似的,選擇按價格排序的搜索結果也肯定讓你直搖頭;
  • 時間倒退半年,2014 巴西世界杯要開打了,你想買一件西班牙隊的球衣以表支持。Tmall 上的西班牙球衣或者相關T恤簡直五花八門,讓你挑花了眼。于是你打算加一些篩選條件:紅色、M號(可能你幾次點擊進入商品詳情后已經發現有些商品并沒有合 適你穿的M號)。可惜搜索又再一次令你失望了,很多商品并沒有紅色M號卻也混進了搜索結果中。這是 BUG 么?其實不是,細心的你也發現雖然那些商品并沒有你想要的紅色M號,卻有紅色S號和白色M號,或者紅色L號和黑色M號,諸如此類,反正紅色是有的,M號也 是有的,就是未必能湊在一起;

  這些問題的的根源在于,搜索引擎是以商品作為檢索單位,沒法提供更細粒度(SKU 粒度)的檢索功能。于是,為了提升用戶的搜索體驗,為了把搜索做得更好,搜索引擎需要支持 SKU 粒度的檢索。

  有了 SKU 粒度的搜索引擎,不僅能解決上面提到的這些關于精準搜索的問題,也給搜索結果的組織和排序提供了更多的玩法與可能性。比如:

  • SKU 信息披露:搜索結果依然按商品來展示,但是滿足搜索條件的 SKU 可以以小圖的形式展現在商品下面,在搜索結果頁面點擊這些小圖就能看到對應 SKU 的價格;有些類目下面,SKU 小圖可以做成兩個維度的。比如鞋子,小圖展現的是不同顏色的各個版本,點擊小圖之后會展現該顏色對應有貨的各種尺碼信息。選中顏色和尺碼再展現對應的價格 區間。另一方面,這些小圖也可以按其對應的 SKU 受歡迎程度等邏輯來進行排序;
  • 搜索結果組織:普通的 query 按商品粒度展示、命中標類(SPU)的 query 按標類聚合展示,這都是原來就有的組織方式。有了 SKU 引擎,當 query 更為具體(滿足條件的商品很少)時,可以以更細粒度來展示搜索結果,比如按子標類(CSPU)聚合展示、或直接按 SKU 展示;(關于子標類,舉一個簡單的例子,iphone6 是一個標類,白色 64G iphone6 就是一個子標類。當用戶搜索”手機”時,搜索結果可以按標類聚合展示;當用戶搜索”iphone6″時,如果依然按標類來聚合,那么搜索結果就只有 iphone6 自己孤零零的一個了,這時候更好的做法是按子標類聚合展示。)
  • 精準排序:現在既然引擎已經能認識到商品的某些 SKU 是不滿足搜索條件的,那么提供給算法用來計算排序分的信息就應該只包含滿足條件的那些 SKU 的信息,而不應該是整個商品的信息。這樣可以做到更精準的排序;

  有了 SKU 引擎,有了精準搜索,一個叫”尺碼個性化”的需求就信心滿滿地登上了臺面。用戶可以在 tmall 上設置自己以及家人朋友的各種尺碼,在搜索服飾類商品的時候可以方便的進行尺碼篩選。可以想象,在沒有精準搜索之前,尺碼個性化這樣的功能簡直就是自曝其 短(搜索結果不準確、價格展現也差強人意,用戶肯定罵聲一片)。

  SKU 引擎和尺碼個性化功能上線之后,取得了不錯的成績。經過數據分析,CSPU 聚合場景下 IPVUV/UV 增長率為 2.16%、整個沙發類目平均 ipvuv 價值增長率為 8.50%、文胸類目平均 ipvuv 價值增長率為 3.23%、男鞋類目平均 ipvuv 價值增長率為 1.26%、女鞋類目平均 ipvuv 價值增長率為 1.20%、等等。

  目前 SKU 引擎才剛剛上線,大幕才剛剛拉開,在此基礎上,后續必定會有更多的 feature 讓我們的搜索變得更好用,大家可以拭目以待。

  SKU 搜索的實現

  講了這么多 SKU 引擎的好處,為什么不早點把這個東西搞起來呢?其實實現 SKU 引擎的道路充滿了波折……

  第一個時期,可以稱為改良期。其實這個時侯并沒有什么 SKU 引擎,但是為了實現個別場景下的精準搜索,我們利用搜索引擎插件來做了一些處理。最典型的就是價格展現,并不是直接展現商品的最小 SKU 價格或是所有 SKU 的價格區間,而是通過插件來判斷哪些 SKU 是滿足搜索條件的,從而決定應該展現什么樣的價格區間。

  插件實現的精準搜索主要存在兩方面的問題:

  • 沒有統一的方案,各個改良點分散在不同的角落,難以維護;
  • 效率不盡如人意,一般需要在檢索過程之后額外增加一個 SKU 的判斷邏輯。特別是當 SKU 維度的搜索條件比較復雜時,插件判斷起來會很費勁。比如用戶搜索:64G、白色或金色的 iphone6,搜索條件是一個比較復雜的表達式,而不僅僅是一堆 AND 關系的條件疊加;

  第二個時期,是全面 SKU 化,將引擎數據由商品粒度改為 SKU 粒度。這是非常簡單易行的 SKU 方案,并且能夠很好的實現 SKU 引擎的各種需求。唯一,并且是致命,的問題是浪費太大。畢竟用于檢索的數據,絕大部分還是在商品這個維度上的。將檢索粒度直接改為 SKU 粒度,這意味著商品維度上的數據需要冗余到其對應的每一個 SKU 上。數據的冗余,造成了引擎索引量增大、造成了離線數據處理開銷增大、造成了各種在線歸并和聚合邏輯的開銷增大。細算下來,tmall 引擎需要增加 5 倍以上的機器才能支撐網站的全部流量,離線處理的機器資源也需要相應的增加,這實在是不可接受。

  所以這個時期的 SKU 引擎只存在于 bts,它的功績是驗證了 SKU 引擎的功能和效果,更堅定了我們要做 SKU 引擎的決心。

  第三個時期,才是目前的 SKU 引擎。既要支持 SKU 的檢索粒度,又要避免數據冗余,那么引擎就必須要支持兩個維度的數據共存。

  為此,作為搜索引擎內核的 isearch5 引擎引入了”子表”的概念。”主表”和”子表”都擁有完整的檢索能力,并且建立強一致的對應關系。商品作為主表、SKU 作為子表,檢索結果就表現為”一主帶多子”的結構。這些子表記錄都一定是滿足檢索條件的,并且當一個主表記錄對應 0 個子表記錄時,說明這個主表記錄不滿足檢索條件(回想之前”紅色M號”的例子)。后續的 RANK、字段展現、等等都在這樣的一主帶多子的數據結構上工作,每個環節都明確知道哪些 SKU 是滿足檢索條件的。

  “子表”這個概念跟引擎里面早已存在的”輔表”似乎有些相似。一般來說,我們可以將商家信息作為輔表來提供檢索,目的也是避免數據冗余。但是” 子表”與”輔表”還是有本質的不同:輔表并不提供查詢功能,我們只能實現查詢商品,然后從輔表中讀取商品對應的商家信息,而不能查商家(比如說,我們不能 查詢店鋪名稱含有”夕陽紅”的商家所售賣的標題含有”保暖內衣”的商品)。基于這一點,主表與輔表雖然是兩個表,但是引擎的處理邏輯還是一維的(主表維 度),并不存在一主帶多輔的概念(一般來說一個主表記錄只能對應到一個輔表記錄,就算數據結構是一對多,在進行取值的時候也只能保留一個而舍棄其他)。

  而”子表”則是真正的兩維概念,兩個表可以同時查詢(比如說,可以查詢標題含有”iphone6″的商品,且這些商品需要擁有包含”白色” 和”16G”兩個屬性的 SKU),并且檢索結果在引擎中表現為一主帶多子的兩維結構。這一點對 isearch 引擎來說沖擊是非常大的,整個查詢流程需要從一維數據結構變為兩維結構。具體來說主要有以下幾點:

  • 查詢過程支持主表與子表的混合查詢。了解 isearch 的同學都應該知道,引擎的查詢過程是由語法樹來描述的。比如查詢 64G、白色或金色的 iphone6,寫成查詢語法大概是這樣子:query=主表:iphone6 AND 子表:64G AND (子表:白色 OR 子表:金色)。查詢過程中涉及不同條件召回的結果集的合并,不過顯然主表與子表的結果是不能直接合并的。依賴于兩個表之間強一致的對應關系,它們的結果可 以相互轉換(比如 nid=123 轉換為 skuid=256,257,258;skuid=258 轉換為 nid=123),從而使得結果集合并成為可能;
  • 查詢過程合并后的結果集直接形成一主帶多子的二維結構。繼續上面的例子:query=主表:iphone6 AND 子表:64G AND (子表:白色 OR 子表:金色),假設各個條件分別查到如下結果:nid=123 AND skuid=256,257 AND (skuid=256 OR skuid=257,258),且有 nid=123 <==> skuid=256,257,258 的對應關系,那么合并后的結果就是:nid=123:skuid=256,257;
  • 查詢完成后,接下來是過濾、統計、排序、等過程。這些過程說白了都是在查詢到的結果的基礎上進行各種各樣的計算,而這些計算最典型的做法就是使用 屬性表達式。比如可能有這樣的過濾語句:filter=realprice (skuprice,discount_conf)<5000,通過 realprice 這個 function 插件來計算每個 sku 的實際售賣價,而其中 skuprice 是 sku 的原價,discount_conf 是商品維度的打折時間段配置。這里又是一個主表與子表混用的地方,引擎能識別 skuprice 字段來源于子表,discount_conf 來源于主表,并且自動推導出它們的計算結果應該落在子表這個維度上。那么假設 skuid=256,257 的 skuprice 取值分別為:5288,4988,當前折扣為1,則 skuid=256 這個結果不滿足售價<5000,將被過濾掉,最后剩下的結果是:nid=123:skuid=257;
  • 除了上面提到的 function 插件,引擎里面還有各種算分和排序插件,這些插件都有可能需要遍歷子表的結果,然后將運算結果寫在主表上。因為很多情況下,搜索引擎展示的結果是按商品聚 合的,需要把 SKU 維度的排序分整合到商品維度,實現商品之間的排序。為此,對于一個一主帶多子的兩維結果,引擎會給插件提供遍歷”多子”記錄的功能;
  • 在我們前面提到的搜索結果組織形式中,有按 SKU 展示,以及將 SKU 粒度聚合成 CSPU 展示的需求。為此,引擎實現了將”一主帶多子”拆散成”獨立多子”的功能,從而便于插件去實現子表維度的展示、打散及聚合等功能。這個拆散的過程其實并非 想象中的天翻地覆,假設有這樣的結果:nid=123:skuid=256,257,拆散后會變成兩個結果:nid=123:skuid=256 和 nid=123:skuid=257,其實描述查詢結果的數據結構并沒有發生變化,僅僅是將主表記錄 clone 了幾份,讓原來的一主帶多子變成多個一主帶一子;

  有了 isearch5 引擎的子表功能做為基礎,要打造 SKU 引擎還需要各個相關方的通力配合:

  • 離線:將原有的一維文檔結構變為兩維結構。其中有一個很麻煩的地方,因為并不是所有 tmall 商品都具有 SKU,而沒有 SKU 的商品會被在線引擎過濾掉(因為引擎沒法判斷到底是該商品的 SKU 都不滿足檢索條件,還是它本身就沒有 SKU。并且當需要使用 SKU 維度的字段時,你還不知道該怎么取值)。要解決這個問題,要么搭建兩套引擎,分別維護有 SKU 和沒有 SKU 的商品,但是這無疑增加了運維的復雜性;要么就給沒有 SKU 的這些商品偽造一個 SKU,使用商品上的對應字段來填充 SKU 的字段(比如 SKU 價格就是商品價格)。最后我們采用的是第二種方案,偽造 SKU 的重任就落在了數據離線處理的身上;
  • 算法:要支持 SKU 維度的打分模型。為了提高效率,算法對”一主帶多子”中的”一主”和多個”子”單獨打分,然后將最終得分整合到商品維度或者 SKU 維度上;
  • 插件:為了兼容有 SKU 和沒有 SKU 的各種應用場景、為了實現各種不同的聚合展現方式,各種臟活累活基本上都被插件干了。其中還有很重要的一點,目前 isearch5 引擎實現的子表功能并不是非常完備的,在結果展現(summary)階段并沒有子表的概念。這就得靠插件拿著”一主帶多子”的檢索結果,去識別各個商品具 體有哪些 SKU 滿足檢索條件,從而決定將哪些 SKU 納入結果展現中;
  • SP:為了將”一主帶多子”的對應關系帶到 summary 供插件做判斷,為了兼容搜索結果按子表拆散后 nid 可能重復的問題,SP 也是煞費苦心;

  可以看出,整個引擎團隊能一起啃下 SKU 引擎這塊硬骨頭,實屬不易。而后續我們也會把引擎做得更加完善和合理,為業務的持續發力打下堅實基礎。

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!