天天看點

行為、審計日志 (實時索引/實時搜尋) - 最佳實踐

postgresql , es , 搜尋引擎 , 全文檢索 , 日志分析 , 反向索引 , 優化 , 分區 , 分片 , 審計日志 , 行為日志

在很多系統中會記錄使用者的行為日志,行為日志包括浏覽行為、社交行為、操作行為等。

典型的應用例如:資料庫的sql審計、企業内部的堡壘機(行為審計)等。

行為、審計日志的量與業務量或者操作量有關,為了滿足企業實時查詢的需求,通常需要建構搜尋引擎,比如使用es或者使用postgresql的全文檢索功能來實作。

如果使用postgresql來建構,有幾個優勢,可以滿足多個需求:

1. 明細存儲的需求,除了需要建立索引的字段,明細字段也可以存儲在postgresql中。

2. 索引的需求,即建立日志行為字段的全文索引。

3. 多元度索引的需求,除了日志行為字段的索引,還可以建立其他字段的索引,例如時間次元,屬性次元的索引。這些索引可以組合使用,滿足多個次元的搜尋需求。

4. 不需要同步到搜尋引擎,滿足了實時搜尋的需求。

磁盤,使用空間大、廉價的sata盤,使用一塊ssd作為bcache寫緩存。

目錄規劃,每塊盤一個目錄

建立12個資料庫叢集,對應到每一塊磁盤。可以充分利用磁盤的io。

将資料庫執行個體綁定到不同的cpu核

4個字段,分别存儲pk(對應原始明細資料的pk),時間,使用者id,使用者行為(tsvector字段)。

檢索時可能按照時間區間,使用者id,以及分詞條件進行檢索。

日志保留一段時間(例如1個月)後清除。

每個叢集中,建立若幹個分區表,例如本例使用了12個分區表。

如果條件允許,建議每個小時一個分區表,這樣的話可以不建時間索引,查詢時間區間的資料使用分區即可。

如果單個使用者的資料量很龐大,那麼建議按uid再建立哈希或list分區,這樣的話,按照uid查詢,不需要使用索引(可以省去在uid建立索引,甚至省去存儲uid這個字段)。

行為字段,全文索引。

使用者id,b-tree索引。

時間字段,brin塊級索引。

時間,時序産生。

使用者id,在一個範圍内随機産生。

使用者行為資料,長約512字元的字元串,拆分成若幹個token,例如本例為40個長度不等的token。

灌入測試資料,例如每張表插入2億,一個資料庫插入24億(約6tb),總共插入288億(約72tb)。

每10條一批灌入。

查詢測試資料如下,資料非常随機,每條記錄的content約40個元素,長度限定在512字元。

使用者全文檢索請求,輸入4個查詢條件,流式傳回pk。

建議使用流式傳回接口,因為結果集可能非常大。

壓測

cpu基本耗盡,磁盤的寫入也非常的充分

cpu大部分為user的開銷,後面使用perf看一下

大部分的開銷是postgres程序消耗的,建議使用以下開關重新編譯一下.

<a href="https://github.com/digoal/blog/blob/master/201611/20161129_01.md">《postgresql 源碼性能診斷(perf profiling)指南》</a>

換算成單機的寫入,約6.5萬行/s。

寫入性能基本上取決于tsvector字段的元素個數,散列程度,本例每條記錄約40個元素。如果元素個數下降一半,性能将提升一倍左右。

1. 全文檢索索引條目

每條記錄約40個元素,當插入的tps=6.5萬時,建構的全文檢索條目數約 260萬/s。

2. uid索引條目,較小,忽略不計。

3. ts索引條目,使用brin塊級索引,忽略不計。

性能影響最大,資源消耗最多的就是全文檢索索引條目的建構。

舉例

1. 寫入tps

7萬/s ,建構的全文檢索條目數約 280萬/s。

性能比較平穩。

7.5萬/s ,建構的全文檢索條目數約 300萬/s。

1. 查詢聚合

由于日志資料打散分布在多個叢集,多個表内,建議使用plproxy進行查詢的聚合。

參考

<a href="https://github.com/digoal/blog/blob/master/201110/20111025_01.md">《a smart postgresql extension plproxy 2.2 practices》</a>

<a href="https://github.com/digoal/blog/blob/master/201512/20151220_04.md">《阿裡雲apsaradb rds for postgresql 最佳實踐 - 4 水準分庫 之 節點擴充》</a>

<a href="https://github.com/digoal/blog/blob/master/201512/20151220_03.md">《阿裡雲apsaradb rds for postgresql 最佳實踐 - 3 水準分庫 vs 單機 性能》</a>

<a href="https://github.com/digoal/blog/blob/master/201512/20151220_02.md">《阿裡雲apsaradb rds for postgresql 最佳實踐 - 2 教你rds pg的水準分庫》</a>

2. 寫入分片

寫入分片,可以在業務層完成,随機打散寫入。

實際應用時,可以根據需要,切分成更多的分區。

3. 主要的開銷是postgres的開銷,如果需要詳細的分析,建議重新編譯postgres

4. gin索引的優化

<a href="https://www.postgresql.org/docs/9.6/static/sql-createindex.html">https://www.postgresql.org/docs/9.6/static/sql-createindex.html</a>

gin_pending_list_limit的目的是延遲合并,因為一條記錄中可能涉及較多的gin key,如果實時更新,gin索引的寫入量會非常大,性能受到影響。

本例gin_pending_list_limit設定為2mb,tps比較平緩,如果設定過大,當cpu資源不足時,抖動會比較嚴重。

使用者可以根據實際測試,設定合理的gin_pending_list_limit值。

5. 如果把postgresql完全當成索引庫使用,并且允許資料丢失,那麼可以使用fsync=off的開關,(檢查點fsync對io的影響比較大,本例使用的是sata盤,将會導緻較大的性能抖動)。

如果有ha的話,丢失的風險又會更小。(但是伺服器crash後,需要重建備庫,這麼大的量,還是挺恐怖的。)

建議用更多的資料庫執行個體,每個執行個體的大小可控(例如 &lt; 2tb),重建的時間也相對可控。

6. 為了達到更好的響應速度(rt),建議明細和索引分開存放,明細要求寫入rt低,索引可以存在一定的延遲。 并且索引與明細資料的可靠性要求也不一樣。