時序資料庫連載系列:當SQL遇到時序TimescaleDB

焦先發表於2019-02-19

1.概述

TimescaleDB是Timescale Inc.(成立於2015年)開發的一款號稱相容全SQL的 時序資料庫 。它的本質是一個基於 PostgreSQL(以下簡稱 PG )的擴充套件( Extension ),主打的賣點如下:

  • 全SQL支援
  • 背靠PostgreSQL的高可靠性
  • 時序資料的高寫入效能

下文將對TimescaleDB這個產品進行解讀。如無特殊說明,這裡所說的TimescaleDB均是指Github上開源的單機版TimescleDB的v1.1版本。

2.資料模型

由於TimescaleDB的根基還是PG,因此它的資料模型與NoSQL的時序資料庫(如我們的阿里時序時空TSDB,InfluxDB等)截然不同。

在NoSQL的時序資料庫中,資料模型通常如下所示,即一條資料中既包括了時間戳以及採集的資料,還包括裝置的後設資料(通常以 Tagset 體現)。資料模型如下所示:

influxdb_data_model.png

但是在TimescaleDB中,資料模型必須以一個二維表的形式呈現,這就需要使用者結合自己使用時序資料的業務場景,自行設計定義二維表。

在TimescaleDB的官方文件中,對於如何設計時序資料的資料表,給出了兩個正規化:

  • Narrow Table
  • Wide Table

所謂的 Narrow Table 就是將metric分開記錄,一行記錄只包含一個 metricValue – timestamp 。舉例如下:

metric名 屬性1 屬性2 屬性3 metric名 時間戳
free_mem 裝置1屬性1值 裝置1屬性2值 裝置3屬性3值 xxxxxxx timestamp1
free_mem 裝置2屬性1值 裝置2屬性2值 裝置2屬性3值 xxxxxxx timestamp1
free_mem 裝置3屬性1值 裝置3屬性2值 裝置3屬性3值 xxxxxxx timestamp1
temperature 裝置1屬性1值 裝置1屬性2值 裝置3屬性3值 △△△△△△△ timestamp1
temperature 裝置2屬性1值 裝置2屬性2值 裝置2屬性3值 △△△△△△△ timestamp1
temperature 裝置3屬性1值 裝置3屬性2值 裝置3屬性3值 △△△△△△△ timestamp1

而所謂的 Wide Table 就是以時間戳為軸線,將同一裝置的多個metric記錄在同一行,至於裝置一些屬性(後設資料)則只是作為記錄的輔助資料,甚至可直接記錄在別的表(之後需要時通過 JOIN 語句進行查詢)

時間戳 屬性1 屬性2 屬性3 metric名1 metric名2 metric名3
timestamp1 裝置1屬性1值 裝置1屬性2值 裝置1屬性3值 metric值1 metric值2 metric值3
timestamp2 裝置2屬性1值 裝置2屬性2值 裝置2屬性3值 metric值1 metric值2 metric值3

基本上可以認為: Narrow Table 對應的就是 單值模型 ,而Wide Table對應的就是 多值模型

由於採用的是傳統資料庫的關係表的模型,所以TimescaleDB的metric值必然是強型別的,它的型別可以是PostgreSQL中的 數值型別 , 字串型別 等。

3.TimescaleDB的特性

TimescaleDB在PostgreSQL的基礎之上做了一系列擴充套件,主要涵蓋以下方面:

  1. 時序資料表的透明自動分割槽特性
  2. 提供了若干面向時序資料應用場景的特殊SQL介面
  3. 針對時序資料的寫入和查詢對PostgreSQL的 Planner 進行擴充套件
  4. 面向時序資料表的定製化並行查詢

其中 3 和 4 都是在PostgreSQL的現有機制上進行的面向時序資料場景的微創新。因此下文將主要對上述的 1 和 2 稍加展開說明

透明自動分割槽特性

在時序資料的應用場景下,其記錄數往往是非常龐大的,很容易就達到 數以billion計 。而對於PG來說,由於大量的還是使用B+tree索引,所以當資料量到達一定量級後其寫入效能就會出現明顯的下降(這通常是由於索引本身變得非常龐大且複雜)。這樣的效能下降對於時序資料的應用場景而言是不能忍受的,而TimescaleDB最核心的 自動分割槽 特性需要解決就是這個問題。這個特性希望達到的目標如下:

  • 隨著資料寫入的不斷增加,將時序資料表的資料分割槽存放,保證每一個分割槽的索引維持在一個較小規模,從而維持住寫入效能
  • 基於時序資料的查詢場景,自動分割槽時以時序資料的時間戳為分割槽鍵,從而確保查詢時可以快速定位到所需的資料分割槽,保證查詢效能
  • 分割槽過程對使用者透明,從而達到Auto-Scalability的效果

TimescaleDB對於自動分割槽的實現,主要是基於PG的表繼承機制進行的實現。TimescaleDB的自動分割槽機制概要可參見下圖:

trans_partition.png

在這個機制下, 使用者建立了一張普通的時序表後,通過TimescaleDB的介面進行了hyper table註冊後,後續的資料寫入和查詢操作事實上就由TimescaleDB接手了。上圖中,使用者建立的原始表一般被稱為“主表”(main table), 而由TimescaleDB建立出的隱藏的子表一般被稱為“chunk”

需要注意的是,chunk是伴隨著資料寫入而自動建立的,每次建立新的chunk時會計算這個chunk預計覆蓋的時間戳範圍(預設是 一週 )。且為了考慮到不同應用場景下,時序資料寫入速度及密度都不相同,對於建立新分割槽時,新分割槽的時間戳範圍會經過一個自適應演算法進行計算,以便逐漸計算出某個應用場景下最適合的時間戳範圍。與PG 10.0

自適應演算法的詳細實現位於TimescaleDB的chunk_adaptive.cts_calculate_chunk_interval(),其基本思路就是基於歷史chunk的 時間戳填充因子 以及 檔案尺寸填充因子 進行合理推算下一個chunk應該按什麼時間戳範圍來進行界定。

藉助 透明化自動分割槽 的特性,根據官方的測試結果,在同樣的資料量級下,TimescaleDB的寫入效能與PG的 傳統單表 寫入場景相比,即使隨著數量級的不斷增大,效能也能維持在一個比較穩定的狀態。

timescale-vs-postgres-insert-1B.jpg

注: 上述Benchmark測試結果摘自Timescale官網

面向時序場景的定製功能

TimescaleDB的對外介面就是SQL,它100%地繼承了PG所支援的全部SQL特性。除此之外,面向時序資料庫的使用場景,它也定製了一些介面供使用者在應用中使用,而這些介面都是通過 SQL函式(標準名稱為 User-defined Function)予以呈現的。以下列舉了一些這類介面的例子:

  • time_bucket()函式

    該函式用於 降取樣 查詢時使用,通過該函式指定一個時間間隔,從而將時序資料按指定的間隔降取樣,並輔以所需的聚合函式從而實現降取樣查詢。一個示例語句如下:

    SELECT time_bucket(`5 minutes`, time)
      AS five_min, avg(cpu)
      FROM metrics
      GROUP BY five_min
      ORDER BY five_min DESC LIMIT 10;
    

    將資料點按5分鐘為單位做降取樣求均值

  • 新增的聚合函式

    為了提供對時序資料進行多樣性地分析查詢,TimescaleDB提供了下述新的聚合函式。

    • first() 求被聚合的一組資料中的第一個值
    • last() 求被聚合的一組資料中的最後一個值
    • histogram() 求被聚合的一組資料中值分佈的直方圖

    注: 新增的聚合函式在非時序場景也可以使用

  • drop_chunks()
    刪除指定時間點之前/之後的資料chunk. 比如刪除三個月時間前的所有chunk等等。這個介面可以用來類比 InfluxDB 的 Retention Policies 特性,但是目前TimescaleDB尚未實現自動執行的chunk刪除。若需要完整的 Retention Policies 特性,需要使用系統級的定時任務(如 crontab)加上drop_chunks()語句來實現。

    drop_chunks()的示例語句如下。含義是刪除conditions表中所有距今三個月之前以及四個月之後的資料分割槽:

    SELECT drop_chunks(older_than => interval `3 months`, newer_than => interval `4 months`, table_name => `conditions`);
    

除此之外,TimescaleDB定製的一些介面基本都是方便資料庫管理員對後設資料進行管理的相關介面,在此就不贅述。包括以上介面在內的定義和示例可參見官方的API文件

4.TimescaleDB的儲存機制

TimescaleDB對PG的儲存引擎未做任何變更,因此其索引資料和表資料的儲存都是沿用的PG的儲存。而且,TimescaleDB給chunk上索引時,都是使用的預設的B+tree索引,因此每一個chunk中資料的儲存機制可以參見下圖:

pg_storage.png

關於這套儲存機制本身不用過多解釋,畢竟TimescaleDB對其沒有改動。不過考慮到時序資料庫的使用場景,可以發現TimescaleDB的Chunk採用這套機制是比較合適的:

  • PG儲存的特徵是 只增不改 ,即無論是資料的插入還是變更。體現在Heap Tuple中都是Tuple的Append操作。因此這個儲存引擎在用於OLTP場景下的普通資料表時,會存在表膨脹問題;而在時序資料的應用場景中,時序資料正常情況下不會被更新或刪除,因此可以避免表膨脹問題(當然,由於時序資料本身寫入量很大,所以也可以認為海量資料被寫入的情況下單表實際上仍然出現了膨脹,但這不是此處討論的問題)

  • 在原生的PG中,為了解決表膨脹問題,所以PG記憶體在 AUTOVACUUM 機制,即自動清理表中因更新/刪除操作而產生的“Dead Tuple”,但是這將會引入一個新的問題,即AUTOVACUUM執行時會對錶加共享鎖從而對寫入效能的影響。但是在時序資料的應用場景中,由於沒有更新/刪除的場景,也就不會存在“Dead Tuple“,因此這樣的Chunk表就不會成為AUTOVACUUM的物件,因此INSERT效能便不會受到來自這方面的影響。

至於對海量資料插入後表和索引增大的問題,這正好通過上述的 自動分割槽 特性進行了規避。

此外,由於TimescaleDB完全基於PG的儲存引擎,對於 WAL 也未做任何修改。因此TimescaleDB的高可用叢集方案也可基於PG的流複製技術進行搭建。TimescaleDB官方也介紹了一些基於開源元件的HA方案

5.小結

綜上所述,由於TimescaleDB完全基於PostgreSQL構建而成,因此它具有若干與生俱來的 優勢 :

  • 100%繼承PostgreSQL的生態。且由於完整支援SQL,對於未接觸過時序資料的初學者反而更有吸引力
  • 由於PostgreSQL的品質值得信賴,因此TimescaleDB在質量和穩定性上擁有品牌優勢
  • 強ACID支援

當然,它的 短板 也是顯而易見的

  • 由於只是PostgreSQL的一個Extension,因此它不能從核心/儲存層面針對時序資料庫的使用場景進行極致優化。
  • 當前的產品架構來看仍然是一個單機庫,不能發揮分散式技術的優勢。而且資料雖然自動分割槽,但是由於時間戳決定分割槽,因此很容易形成I/O熱點。
  • 在功能層面,面向時序資料庫場景的特性還比較有限。目前更像是一個 傳統OLTP資料庫 + 部分時序特性 。

不管怎樣,TimescaleDB也算是面向時序資料庫從另一個角度發起的嘗試。在當前時序資料庫仍然處於新興事物的階段,它未來的發展方向也是值得我們關注並借鑑的。


相關文章