溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

Lucene查詢原理是什么

發(fā)布時間:2021-12-23 09:24:49 來源:億速云 閱讀:170 作者:iii 欄目:開發(fā)技術(shù)

本篇內(nèi)容介紹了“Lucene查詢原理是什么”的有關(guān)知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領(lǐng)大家學(xué)習(xí)一下如何處理這些情況吧!希望大家仔細閱讀,能夠?qū)W有所成!

Lucene查詢原理

本節(jié)主要是一些Lucene的背景知識,了解這些知識的同學(xué)可以略過。

Lucene的數(shù)據(jù)結(jié)構(gòu)和查詢原理

Elasticsearch的底層是Lucene,可以說Lucene的查詢性能就決定了Elasticsearch的查詢性能。

Lucene查詢原理

Lucene中最重要的就是它的幾種數(shù)據(jù)結(jié)構(gòu),這決定了數(shù)據(jù)是如何被檢索的,本文再簡單描述一下幾種數(shù)據(jù)結(jié)構(gòu):

  1. FST:保存term字典,可以在FST上實現(xiàn)單Term、Term范圍、Term前綴和通配符查詢等。

  2. 倒排鏈:保存了每個term對應(yīng)的docId的列表,采用skipList的結(jié)構(gòu)保存,用于快速跳躍。

  3. BKD-Tree:BKD-Tree是一種保存多維空間點的數(shù)據(jù)結(jié)構(gòu),用于數(shù)值類型(包括空間點)的快速查找。

  4. DocValues:基于docId的列式存儲,由于列式存儲的特點,可以有效提升排序聚合的性能。

組合條件的結(jié)果合并

了解了Lucene的數(shù)據(jù)結(jié)構(gòu)和基本查詢原理,我們知道:

  1. 對單個詞條進行查詢,Lucene會讀取該詞條的倒排鏈,倒排鏈中是一個有序的docId列表。

  2. 對字符串范圍/前綴/通配符查詢,Lucene會從FST中獲取到符合條件的所有Term,然后就可以根據(jù)這些Term再查找倒排鏈,找到符合條件的doc。

  3. 對數(shù)字類型進行范圍查找,Lucene會通過BKD-Tree找到符合條件的docId集合,但這個集合中的docId并非有序的。

現(xiàn)在的問題是,如果給一個組合查詢條件,Lucene怎么對各個單條件的結(jié)果進行組合,得到最終結(jié)果。簡化的問題就是如何求兩個集合的交集和并集。

1. 對N個倒排鏈求交集

上面Lucene原理分析的文章中講過,N個倒排鏈求交集,可以采用skipList,有效的跳過無效的doc。

2. 對N個倒排鏈求并集

處理方式一:仍然保留多個有序列表,多個有序列表的隊首構(gòu)成一個優(yōu)先隊列(最小堆),這樣后續(xù)可以對整個并集進行iterator(堆頂?shù)年犑壮龆?,隊列里下一個docID入堆),也可以通過skipList的方式向后跳躍(各個子列表分別通過skipList跳)。這種方式適合倒排鏈數(shù)量比較少(N比較小)的場景。

處理方式二:倒排鏈如果比較多(N比較大),采用方式一就不夠劃算,這時候可以直接把結(jié)果合并成一個有序的docID數(shù)組。

處理方式三:方式二中,直接保存原始的docID,如果docID非常多,很消耗內(nèi)存,所以當doc數(shù)量超過一定值時(32位docID在BitSet中只需要一個bit,BitSet的大小取決于segments里的doc總數(shù),所以可以根據(jù)doc總數(shù)和當前doc數(shù)估算是否BitSet更加劃算),會采用構(gòu)造BitSet的方式,非常節(jié)約內(nèi)存,而且BitSet可以非常高效的取交/并集。

3. BKD-Tree的結(jié)果怎么跟其他結(jié)果合并

通過BKD-Tree查找到的docID是無序的,所以要么先轉(zhuǎn)成有序的docID數(shù)組,或者構(gòu)造BitSet,然后再與其他結(jié)果合并。

查詢順序優(yōu)化

如果采用多個條件進行查詢,那么先查詢代價比較小的,再從小結(jié)果集上進行迭代,會更優(yōu)一些。Lucene中做了很多這方面的優(yōu)化,在查詢前會先估算每個查詢的代價,再決定查詢順序。

結(jié)果排序

默認情況下,Lucene會按照Score排序,即算分后的分數(shù)值,如果指定了其他的Sort字段,就會按照指定的字段排序。那么,排序會非常影響性能嗎?首先,排序并不會對所有命中的doc進行排序,而是構(gòu)造一個堆,保證前(Offset+Size)個數(shù)的doc是有序的,所以排序的性能取決于(Size+Offset)和命中的文檔數(shù),另外就是讀取docValues的開銷。因為(Size+Offset)并不會太大,而且docValues的讀取性能很高,所以排序并不會非常的影響性能。

各場景查詢性能分析

上一節(jié)講了一些查詢相關(guān)的理論知識,那么本節(jié)就是理論結(jié)合實踐,通過具體的一些測試數(shù)字來分析一下各個場景的性能。測試采用單機單Shard、64核機器、SSD磁盤,主要分析各個場景的計算開銷,不考慮操作系統(tǒng)Cache的影響,測試結(jié)果僅供參考。

單Term查詢

ES中建立一個Index,一個shard,無replica。有1000萬行數(shù)據(jù),每行只有幾個標簽和一個唯一ID,現(xiàn)在將這些數(shù)據(jù)寫入這個Index中。其中Tag1這個標簽只有a和b兩個值,現(xiàn)在要從1000萬行中找到一條Tag1=a的數(shù)據(jù)(約500萬)。給出以下查詢,那么它耗時如何呢:
請求:
{  "query": {    "constant_score": {      "filter": {        "term": {          "Tag1": "a"
        }
      }
    }
  },  "size": 1}'
響應(yīng):
{"took":233,"timed_out":false,"_shards":{"total":1,"successful":1,"skipped":0,"failed":0},"hits":{"total":5184867,"max_score":1.0,"hits":...}

這個請求耗費了233ms,并且返回了符合條件的數(shù)據(jù)總數(shù):5184867條。

對于Tag1="a"這個查詢條件,我們知道是查詢Tag1="a"的倒排鏈,這個倒排鏈的長度是5184867,是非常長的,主要時間就花在掃描這個倒排鏈上。其實對這個例子來說,掃描倒排鏈帶來的收益就是拿到了符合條件的記錄總數(shù),因為條件中設(shè)置了constant_score,所以不需要算分,隨便返回一條符合條件的記錄即可。對于要算分的場景,Lucene會根據(jù)詞條在doc中出現(xiàn)的頻率來計算分值,并取分值排序返回。

目前我們得到一個結(jié)論,233ms時間至少可以掃描500萬的倒排鏈,另外考慮到單個請求是單線程執(zhí)行的,可以粗略估算,一個CPU核在一秒內(nèi)掃描倒排鏈內(nèi)doc的速度是千萬級的。

我們再換一個小一點的倒排鏈,長度為1萬,總共耗時3ms。

{"took":3,"timed_out":false,"_shards":{"total":1,"successful":1,"skipped":0,"failed":0},"hits":{"total":10478,"max_score":1.0,"hits":...}

Term組合查詢

首先考慮兩個Term查詢求交集:

對于一個Term的組合查詢,兩個倒排鏈分別為1萬和500萬,合并后符合條件的數(shù)據(jù)為5000,查詢性能如何呢?
請求:
{  "size": 1,  "query": {    "constant_score": {      "filter": {        "bool": {          "must": [
            {              "term": {                "Tag1": "a"  // 倒排鏈長度500萬
              }
            },
            {              "term": {                "Tag2": "0" // 倒排鏈長度1萬
              }
            }
          ]
        }
      }
    }
  }
}
響應(yīng):
{"took":21,"timed_out":false,"_shards":{"total":1,"successful":1,"skipped":0,"failed":0},"hits":{"total":5266,"max_score":2.0,"hits":...}

這個請求耗時21ms,主要是做兩個倒排鏈的求交操作,因此我們主要分析skipList的性能。

這個例子中,倒排鏈長度是1萬、500萬,合并后仍有5000多個doc符合條件。對于1萬的倒排鏈,基本上不進行skip,因為一半的doc都是符合條件的,對于500萬的倒排鏈,平均每次skip1000個doc。因為倒排鏈在存儲時最小的單位是BLOCK,一個BLOCK一般是128個docID,BLOCK內(nèi)不會進行skip操作。所以即使能夠skip到某個BLOCK,BLOCK內(nèi)的docID還是要順序掃描的。所以這個例子中,實際掃描的docID數(shù)粗略估計也有幾十萬,所以總時間花費了20多ms也符合預(yù)期。

對于Term查詢求并集呢,將上面的bool查詢的must改成should,查詢結(jié)果為:

{"took":393,"timed_out":false,"_shards":{"total":1,"successful":1,"skipped":0,"failed":0},"hits":{"total":5190079,"max_score":1.0,"hits":...}

花費時間393ms,所以求并集的時間是多于其中單個條件查詢的時間。

字符串范圍查詢

RecordID是一個UUID,1000萬條數(shù)據(jù),每個doc都有一個唯一的uuid,從中查找0~7開頭的uuid,大概結(jié)果有500多萬個,性能如何呢?
請求:
{  "query": {    "constant_score": {      "filter": {        "range": {          "RecordID": {            "gte": "0",            "lte": "8"
          }
        }
      }
    }
  },  "size": 1
}
響應(yīng):
{"took":3001,"timed_out":false,"_shards":{"total":1,"successful":1,"skipped":0,"failed":0},"hits":{"total":5185663,"max_score":1.0,"hits":...}
查詢a開頭的uuid,結(jié)果大概有60多萬,性能如何呢?
請求:
{  "query": {    "constant_score": {      "filter": {        "range": {          "RecordID": {            "gte": "a",            "lte": "b"
          }
        }
      }
    }
  },  "size": 1
}
響應(yīng):
{"took":379,"timed_out":false,"_shards":{"total":1,"successful":1,"skipped":0,"failed":0},"hits":{"total":648556,"max_score":1.0,"hits":...}

這個查詢我們主要分析FST的查詢性能,從上面的結(jié)果中我們可以看到,F(xiàn)ST的查詢性能相比掃描倒排鏈要差許多,同樣掃描500萬的數(shù)據(jù),倒排鏈掃描只需要不到300ms,而FST上的掃描花費了3秒,基本上是慢十倍的。對于UUID長度的字符串來說,F(xiàn)ST范圍掃描的性能大概是每秒百萬級。

字符串范圍查詢加Term查詢

字符串范圍查詢(符合條件500萬),加上兩個Term查詢(符合條件5000),最終符合條件數(shù)目2600,性能如何?
請求:
{  "query": {    "constant_score": {      "filter": {        "bool": {          "must": [
            {              "range": {                "RecordID": {                  "gte": "0",                  "lte": "8"
                }
              }
            },
            {              "term": {                "Tag1": "a"
              }
            },
            {              "term": {                "Tag2": "0"
              }
            }
          ]
        }
      }
    }
  },  "size": 1
}
結(jié)果:
{"took":2849,"timed_out":false,"_shards":{"total":1,"successful":1,"skipped":0,"failed":0},"hits":{"total":2638,"max_score":1.0,"hits":...}

這個例子中,查詢消耗時間的大頭還是在掃描FST的部分,通過FST掃描出符合條件的Term,然后讀取每個Term對應(yīng)的docID列表,構(gòu)造一個BitSet,再與兩個TermQuery的倒排鏈求交集。

數(shù)字Range查詢

對于數(shù)字類型,我們同樣從1000萬數(shù)據(jù)中查找500萬呢?
請求:
{  "query": {    "constant_score": {      "filter": {        "range": {          "Number": {            "gte": 100000000,            "lte": 150000000
          }
        }
      }
    }
  },  "size": 1
}
響應(yīng):
{"took":567,"timed_out":false,"_shards":{"total":1,"successful":1,"skipped":0,"failed":0},"hits":{"total":5183183,"max_score":1.0,"hits":...}

這個場景我們主要測試BKD-Tree的性能,可以看到BKD-Tree查詢的性能還是不錯的,查找500萬個doc花費了500多ms,只比掃描倒排鏈差一倍,相比FST的性能有了很大的提升。地理位置相關(guān)的查詢也是通過BKD-Tree實現(xiàn)的,性能很高。

數(shù)字Range查詢加Term查詢

這里我們構(gòu)造一個復(fù)雜的查詢場景,數(shù)字Range范圍數(shù)據(jù)500萬,再加兩個Term條件,最終符合條件數(shù)據(jù)2600多條,性能如何?
請求:
{  "query": {    "constant_score": {      "filter": {        "bool": {          "must": [
            {              "range": {                "Number": {                  "gte": 100000000,                  "lte": 150000000
                }
              }
            },
            {              "term": {                "Tag1": "a"
              }
            },
            {              "term": {                "Tag2": "0"
              }
            }
          ]
        }
      }
    }
  },  "size": 1
}
響應(yīng):
{"took":27,"timed_out":false,"_shards":{"total":1,"successful":1,"skipped":0,"failed":0},"hits":{"total":2638,"max_score":1.0,"hits":...}

這個結(jié)果出乎我們的意料,竟然只需要27ms!因為在上一個例子中,數(shù)字Range查詢耗時500多ms,而我們增加兩個Term條件后,時間竟然變?yōu)?7ms,這是為何呢?

實際上,Lucene在這里做了一個優(yōu)化,底層有一個查詢叫做IndexOrDocValuesQuery,會自動判斷是查詢Index(BKD-Tree)還是DocValues。在這個例子中,查詢順序是先對兩個TermQuery求交集,得到5000多個docID,然后讀取這個5000多個docID對應(yīng)的docValues,從中篩選符合數(shù)字Range條件的數(shù)據(jù)。因為只需要讀5000多個doc的docValues,所以花費時間很少。

“Lucene查詢原理是什么”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識可以關(guān)注億速云網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實用文章!

向AI問一下細節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI