索引是加速數據庫查詢的重要手段,Lindorm除了提供高性能的二級索引外,同時支持搜索索引 (SearchIndex),主要面向復雜的多維查詢場景,并能夠覆蓋模糊查詢、聚合分析、排序、分頁等場景。本文主要介紹SearchIndex的技術原理和核心能力。
背景
在海量數據存儲的背景下,伴隨著云原生、5G/IoT時代的到來,新的業務模型在不斷涌現,除了簡單的主鍵查詢和范圍查詢外,簡單分析、多維檢索成為業務的基本需求。常見的一些查詢需求如下:
多維查詢。即席查詢 (adhoc),一般是不固定的列隨機組合。
count計數。獲取數據表的總行數,或者返回一次查詢命中的數據條數。
指定列排序。按照指定列降序或升序,比方說按照訂單時間降序輸出結果。
分詞檢索。支持文本字段的分詞檢索,返回相關性較高的結果數據。
統計聚合。按照某個字段進行聚類統計,求取sum/max/min/avg等,或者返回去重后的結果集。
模糊查詢。查詢以'阿里'開頭的數據,可以匹配出'阿里云'的結果集,類似MySQL的like語法。
諸如此類對海量數據低成本存儲和檢索多樣化的需求,成為越來越多業務的基本訴求。Lindorm系統持續探索如何在Lindorm系統本身的高擴展、低成本的優勢之上,同時去支撐業務的多樣化查詢場景。
Lindorm SearchIndex 設計思路
為了在有限的資源下盡可能高效的滿足業務復雜查詢的訴求,Lindorm期望設計一種新的引擎,以數據庫特性的方式即開即用,幫助業務解決海量數據下的復雜查詢問題。索引通常用來加速查詢,可以通過增加一種新的索引類型來解決海量數據的復雜查詢問題,Lindorm作為一個多模數據庫,原生支持搜索引擎,天然具備全文索引能力。因此,通過融合搜索引擎,Lindorm寬表增加了SearchIndex,使得業務在不用感知底層的多個引擎以及數據流轉的情況下,通過申請一個新的索引即可解決復雜的查詢問題,就像使用Lindorm二級索引一樣方便快捷。SearchIndex重點構建以下能力:
統一元數據 多套系統運維復雜的根因是各自的元數據不統一,需要使用各自專用的命令才可以操作,例如在一個系統中建完表,還需要在另外一個系統中建索引。通過維護統一的分布式元數據,我們可以屏蔽掉不同引擎間的Schema差異,提供統一的命令完成DDL類的操作。
統一接口 多個系統間的接口存在差異,通過實現一套專有的統一接口可以有效降低開發的復雜度,但這也需要業務學習和理解新的接口,應用開發成本沒有明顯的降低。SQL作為眾多數據庫系統的開發語言,使用和學習成本都較低,Lindorm SearchIndex原生支持類SQL接口:CQL,業務開發過程中不感知索引的存在,在使用體驗上與原始的寬表訪問保持一致。
強一致性 數據在多個引擎間流轉必然會涉及到一致性問題,通常只能提供最終一致性的語義,數據的正確性和訪問延遲無法有效保障。Lindorm SearchIndex提供了最終一致性和強一致性兩種語義,對于訪問量大、數據延遲性要求不高的場景采用最終一致性,可以提供非常高的吞吐和可用性,而業務訪問延遲敏感的業務可以選擇強一致性模型,數據寫入成功后,索引立即可查。
資源隔離 異構系統對資源的使用各不相同,必須建立有效的隔離機制確保資源使用最大化,Lindorm通過
存儲和索引
分離的模式來保障系統的健壯和彈性。寬表引擎負責存儲原始數據,具備極低的存儲成本,搜索引擎負責索引和檢索,兩個引擎可以配置不同的CPU、內存資源,并且可以獨立擴縮容。
Lindorm SearchIndex 功能解析
使用舉例
使用SearchIndex創建索引表時只需要枚舉出索引列名即可,查詢時不需要感知索引表的存在。以下示例介紹如何使用Lindorm CQL操作SearchIndex。
CQL是Cassandra的查詢語言,Lindorm無縫兼容Cassandra API。
原始表
CREATE TABLE myTable ( id bigint, name text, age int, sex text, city text, address text, PRIMARY KEY (id) ) WITH compression = {'class': 'ZstdCompressor'};
索引
對姓名 (name)、年齡 (age)、性別 (sex)、城市 (city)、地址 (address) 建立全文索引。
CREATE SEARCH INDEX myIndex ON myTable WITH COLUMNS (name, age, sex, city, address);
說明索引列的先后順序不影響,即索引列 (c3, c2, c1)與索引列 (c1, c2, c3)最終的效果是一致的。
查詢
標準查詢語句
模糊查詢:SELECT * FROM myTable WHERE name LIKE '小%' 多維查詢排序:SELECT * FROM myTable WHERE city='杭州' AND age>=18 ORDER BY age ASC 多維查詢翻頁:SELECT * FROM myTable WHERE name='小劉' AND sex=false OFFSET 100 LIMIT 10 ORDER BY age DESC
高級查詢語句
多維查詢排序:SELECT * FROM myTable WHERE search_query='+city:杭州 +age:[18 TO *] ORDER BY age ASC' 文本檢索:SELECT * FROM myTable WHERE search_query='address:西湖區'
適用場景
SearchIndex在阿里內部已經成功應用多個業務場景,當前該特性在公有云上已經發布,支持的重要功能列表如下:
多維查詢:多個條件任意組合的精確查詢、范圍查詢等。
通配符查詢:* 代表任意個字符;? 代表任意單個字符。
統計聚合:求最小值、求最大值、求和、求平均值、統計行數。
排序分頁:任意索引列的排序輸出。
文本分詞:支持中文/英文分詞,分隔符分詞,拼音分詞等。
地理位置:距離查詢、長方形/多邊形范圍查詢。
有了這些功能,可以很容易的將Lindorm應用到多樣化的業務場景中,經典的使用場景主要有以下幾個:
訂單詳情,例如物流訂單、交易賬單,支持訂單的多維查詢、排序等。
標簽畫像,例如基于商家對買家進行標簽圈選,定向投遞信息。
文本搜索:例如日志分析,異常信息檢索等。
實現原理
Lindorm作為一款多模數據庫,同時支持多種模型,將搜索引擎與寬表模型深度融合,對外提供簡單易用的SearchIndex,整體的分層架構如下:
查詢接入
由多個QueryProcessor節點組成,主要負責查詢接入,進行SQL解析,基于RBO自動選擇合適的索引。
索引預處理
基于索引列的元信息將新插入或者更新的原始數據轉換為索引數據,并且針對不同的場景可以選擇與之匹配的Mutability屬性,比較典型的例如日常監控,數據寫入后不更新,可以選擇Immutable模式,直接生成索引原始數據;而那些有狀態的數據,大多需要局部更新,此時通過回讀歷史數據組裝成索引原始數據,并且能夠支持業務自定義時間戳的寫入,確保數據不亂序。
索引同步
對于最終一致模式(默認),LTS (Lindorm Tunnel Service)作為Lindorm生態的數據同步服務,具備高效的實時同步和全量遷移能力。可以實時監聽WAL的變化,將索引原始數據轉換后寫入到搜索引擎,同時支持一讀多寫,即一份WAL可以同步到多個索引表中,極大提升同步效率;對于強一致模式,索引原始數據構建完畢后同步寫入到搜索引擎,由搜索引擎實時生成全文索引,為業務提供寫入即可查的強一致體驗。
索引引擎
由多個節點組成的分布式Lucene集群,數據按照Hash或者Range來劃分為多個Shard,對外提供全文檢索能力。
索引存儲
索引數據存儲在分布式文件系統Lindorm DFS上,存算分離的架構具有極好的擴展性,同時存儲層的透明壓縮和智能冷熱分離可以顯著降低索引的存儲成本。
核心特性
Online DDL Operations
作為一個分布式數據庫,Lindorm可以橫向擴展支持高達億次每秒的處理能力,如果索引DDL需要阻塞DML,對高并發的業務應用影響將會被放大。借助Lindorm的分布式元數據管理,SearchIndex通過合理的擴展,可以支持在線DDL操作,并且不會破壞數據的完整性。
動態增加、刪除索引列 在寬表的應用場景中,列可能不會固定,尤其是標簽畫像場景,需要經常性的增加或刪除索引列。SearchIndex提供Java API/CQL接口來動態操作索引列。
在線變更索引列屬性 每個索引列支持多種屬性:indexed(是否索引,默認true)、stored(是否存儲原始值,默認false)、docvalues(是否維護正排索引,默認true)、分詞類型等。例如某個索引列起初并未設置stored,那么在索引表中是不會存儲原始值的,服務端會自動回查Lindorm寬表獲取原始數據。此時,可以通過接口變更索引列的stored為true。
動態修改壓縮 Lindorm寬表在創建時可以設置壓縮算法(例如ZSTD、Snappy),也可以創建表后動態修改。SearchIndex是一個獨立的索引表,底層依賴Lucene,僅支持LZ4和ZLIB兩種壓縮算法,為了保證原始主表與索引表的屬性統一,我們通過修改Lucene,讓其支持ZSTD、Snappy壓縮算法。因此,在修改原始主表壓縮算法時,也會聯動修改SearchIndex,有效降低索引的存儲大小。
動態修改TTL 為了自動淘汰歷史數據,Lindorm支持動態修改表的TTL,例如設置TTL=30days,代表從此刻起30天前的數據將會被淘汰,并且無法查詢。我們在Lucene的基礎上實現了行級TTL能力,可以與原始主表的TTL聯動,自動淘汰過期數據,確保主表和索引表的數據一致。
動態修改索引表狀態 索引表的狀態主要有三種:DISABLED(不可寫,不可查)、BUILDING(可寫不可查)、ACTIVE(可寫可查)。在創建、刪除索引或者對歷史數據構建索引時,我們經常需要動態變更索引的狀態,此時也不能夠影響到運行中的DML請求。
多一致性
如同Lindorm寬表,在設計SearchIndex時,Lindorm針對不同的業務場景提供了多一致性的支持。
一致性等級 | 讀寫一致性保障 | 可用性 |
最終一致性(EC) | 數據寫入后,需要等待一段時間可讀(秒級)。 | 讀寫可規避任何hang及毛刺; 宕機讀寫恢復10毫秒。 |
強一致性(SC) | 數據寫入后,100%可以立即讀到。 | 只有主副本提供讀寫服務; 主副本宕機恢復一般在秒級。 |
可選的索引構建成本
索引可以加速查詢,助力業務進一步挖掘數據的價值,但會帶來寫入成本和存儲成本的增加。一方面,Lindorm通過多種高效的壓縮算法顯著降低索引的存儲體積;另一方面,通過提供可選的索引構建方式降低索引構建對寫入吞吐的影響。索引WAL的構建
快慢將直接影響到原始數據的寫入性能。Lindorm寬表是一個KV數據庫,天然支持更新部分列,但是搜索引擎Lucene只能整行更新,不能夠局部更新。因此,在構建索引時需要回讀原表獲取歷史數據,才能夠拼接出完整的索引WAL。Lindorm將這個回讀操作按照業務場景進行分類,支持不同的選擇。
構建方式 | 適用場景 | 屬性 |
IMMUTABLE (成本最低) | 數據只增不刪的場景(可以TTL淘汰數據)。 例如監控數據/日志數據,數據寫入后不會更新和刪除。 | 索引構建非常高效,不需要回查舊數據,直接依據當前的數據生成索引。 |
MUTABLE_LATEST(成本中等) | 通用場景(除UDT)。 | 假設索引列為c1,c2,第一次寫入c1列,第二次寫入c2列。那么在第二次寫入c2的值時,需要讀出原始的c1值,才能夠拼接出完整的索引數據c1,c2。 |
MUTABLE_ALL (成本最高) | 寫入數據時,業務自定義時間戳(User-Defined Timestamp)。 例如:全量任務和增量并存的場景,往往需要在全量任務時攜帶時間戳,這樣可以確保不會覆蓋增量寫入的數據。 |
|
高效同步
在最終一致性的模型中,索引數據同步依賴LTS服務。LTS服務作為Lindorm生態的數據通道,具備高效的實時同步和全量遷移能力,寫入寬表的數據,可以在毫秒內感知到,快速的同步到搜索引擎中。
同步可視化
LTS提供Web訪問,可以查看到索引同步的詳細信息。例如同步的耗時、索引表信息、同步的數據量等。另外,LTS可以將這些信息對外吐出監控指標,對接告警體系,實時監測同步鏈路的健康度。
同步高效率
LTS內部通過高并發的生產者/消費者模式,支持快速消化大量的數據,一份WAL只需要讀取一次。并且支持橫向擴展,新加入的節點可以快速加入到同步鏈路中,加速索引數據的同步。
WAL保序
通過隱藏的時間戳屬性,保證在寬表中先寫入的數據先寫入搜索,后寫入的數據后寫入搜索,確保寬表和搜索的數據一致性,徹底解決LilyIndexer存在的數據錯亂問題。
全量構建快
對于已有的歷史數據,可以借助LTS的全量任務運行機制,高效的從寬表中獲取原始數據生成索引,TB級別的數據量分鐘內即可完成索引構建。
數據快速校驗
支持對已有數據的比對校驗,可以快速篩選出不一致的索引數據,幫助業務及時發現問題。
索引實時可見(RealTime Search)
寫入成功后的索引數據可以立即可查,即為索引的實時可見,是一種強一致的模型。SearchIndex底層依賴Lucene,Lucene有一個明顯的"缺陷":數據寫入后不能立即可查,必須要顯示的執行Flush或者Commit操作才可以查詢到。這導致基于Lucene的服務無法應用到實時業務場景,只能適用于監控、日志等弱實時的場景。在業界,基于Lucene的分布式搜索引擎Elasticsearch/Solr為了緩解這個問題,提供近實時查詢(NRT)
功能,可以確保索引數據在某個時間范圍內(通常在秒級)一定可查,但還是達不到實時性的要求。
為了解決寫入的數據無法立即可查
的問題,Lindorm基于Lucene實現了一種索引實時可見的方案,通過精細化的數據結構設計和動態的內存管理機制,可以保證索引數據一旦寫入成功后可以立即查詢到,真正做到實時性。
CQL API
CQL是Cassandra的官方查詢語言,是一種適合NoSQL數據庫特點的SQL方言,由于Lindorm無縫兼容Cassandra,因此默認推薦使用CQL來訪問SearchIndex。
DDL
創建索引
CREATE SEARCH INDEX index_name [ IF NOT EXISTS ] ON [keyspace_name.]table_name | [ WITH [ COLUMNS (column1,...,columnn) ] | [ WITH [ COLUMNS (*) ]
刪除索引
DROP SEARCH INDEX [IF EXISTS] ON [keyspace_name.]table_name;
重構索引
REBUILD SEARCH INDEX [IF EXISTS] ON [keyspace_name.]table_name;
修改索引
ALTER SEARCH INDEX SCHEMA [IF EXISTS] ON [keyspace_name.]table_name ( ADD column_name | DROP column_name) ;
DML
標準查詢,WHERE后面緊跟具體的條件。
search_query查詢
SELECT selectors FROM table WHERE (indexed_column_expression | search_query = 'search_expression') [ LIMIT n ] [ ORDER BY column_name ]
當標準的查詢語法無法滿足檢索需求時,可以考慮通過search_query
來直接從搜索引擎中檢索數據,使用的語法也是Lucene的語法。例如,下面的用例中,相當于檢索city為'hangzhou',并且age包含1到18的數據。
SELECT name,id FROM myTable WHERE search_query = '+city:hangzhou +age:[1 TO 18]';
詳細的CQL語法可參考CREATE SEARCH INDEX。
案例介紹
訂單場景
對于物流、第三方支付和移動出行等業務場景,訂單數據的存儲是核心需求。而且訂單數據往往有其特殊的天然屬性。
高增長:數據可能隨時會爆發式的增長,例如雙11或大促節日。
低成本:訂單數據一般不會直接產生經濟效益,是業務對外呈現的附加價值,需要低成本存儲。
多維查詢:對于C端用戶而言,往往會從不同角度對訂單進行分類、標記以及查看和過濾自己的訂單。
在之前,面對上面的訴求,一般的解決方案是MySQL+搜索引擎。業務雙寫到兩個系統中,或者借助binlog進行實時同步,查詢時分別從不同的系統中獲取結果。隨著數據量的增長,可能會演變為MySQL熱數據+Lindorm冷數據+搜索引擎的架構,基于多套系統可以有效解決業務問題,但需要面臨多套系統維護的時間成本和人力成本。
現在,Lindorm可一一站式解決上述問題,不用關心數據的流轉,統一的API訪問。
通過冷熱分離、壓縮優化等手段顯著降低存儲成本。
橫向彈性擴展適應海量數據的寫入。
SearchIndex CQL提供豐富的查詢語法。
用戶畫像
用戶畫像的數據一般有兩種:一個是基礎數據,另一個是經過分析得到的標簽數據。這些數據可以被應用到營銷、推薦等場景中,可以助力企業營收快速增長。畫像數據的主要痛點如下:
數據量大:畫像數據與用戶基數強相關,往往在千萬甚至億級別,而且數據維度非常高,在我們服務的客戶中,有的場景支持的標簽數在5000個以上。
高并發。畫像數據通常需要全量刷新,需要在基線的時間內完成才能有效輔助后續的推薦、廣告投放等。
動態列。數據維度在不斷的變化中,因此需要支持動態增/刪列。
多維查詢。面向不同的業務需求,畫像數據查詢需求也會有差異,運營人員通常會統計任意一個維度的數據。
畫像場景一般沒有強事務需求,而是大數據量、高并發讀寫的場景,關系數據庫不太適合。Lindorm作為一款NoSQL數據庫,非常適合這樣的場景。
多列族、動態列、TTL等特性,適合表結構不固定,經常需要進行變更的業務場景。
高性能吞吐。
SearchIndex CQL支持任意維度的查詢和統計。
日志檢索
日志的來源非常廣泛,例如系統日志、數據庫審計日志、用戶行為日志等,這些數據在互聯網公司中通常存儲于開源Elasticsearch(ES)中,借助ELK體系構建一站式的日志平臺。但ES的存儲成本是非常高的,而且存儲和計算往往需要同機部署,在海量數據下系統運維面臨非常多的挑戰,數據遷移、節點擴縮容等都需要人工介入。多模數據庫Lindorm的SearchIndex是日志檢索場景的更優選擇,通過寬表引擎存儲海量數據降低成本,搜索引擎構建合適的索引加速查詢,統一的API操作進一步降低業務開發成本。