1. 程式人生 > >圖資料庫 Nebula Graph TTL 特性

圖資料庫 Nebula Graph TTL 特性

導讀

身處在現在這個大資料時代,我們處理的資料量需以 TB、PB, 甚至 EB 來計算,怎麼處理龐大的資料集是從事資料庫領域人員的共同問題。解決這個問題的核心在於,資料庫中儲存的資料是否都是有效的、有用的資料,因此如何提高資料中有效資料的利用率、將無效的過期資料清洗掉,便成了資料庫領域的一個熱點話題。在本文中我們將著重講述如何在資料庫中處理過期資料這一問題。

在資料庫中清洗過期資料的方式多種多樣,比如儲存過程、事件等等。在這裡筆者舉個例子來簡要說明 DBA 經常使用的儲存過程 + 事件來清理過期資料的過程。

儲存過程 + 事件清洗資料

儲存過程(procedure)

儲存過程是由一條或多條 SQL 語句組成的集合,當對資料庫進行一系列的讀寫操作時,儲存過程可將這些複雜的操作封裝成一個程式碼塊以便重複使用,大大減少了資料庫開發人員的工作量。通常儲存過程編譯一次,可以執行多次,因此也大大的提高了效率。

儲存過程有以下優點:

  • 簡化操作,將重複性很高的一些操作,封裝到一個儲存過程中,簡化了對這些 SQL 的呼叫
  • 批量處理,SQL + 迴圈,減少流量,也就是“跑批”
  • 統一介面,確保資料的安全
  • 一次編譯多次執行,提高了效率。

以 MySQL 為例,假如要刪除資料的表結構如下:

mysql> SHOW CREATE TABLE person;
+--------+---------------------------------------------------------------------------------------------------------------------------------+
| Table  | Create Table                                                                                                                    |
+--------+---------------------------------------------------------------------------------------------------------------------------------+
| person | CREATE TABLE `person` (
  `age` int(11) DEFAULT NULL,
  `inserttime` datetime DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8 |
+--------+---------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)

建立一個名為 person 的表,其中 inserttime 欄位為 datetime 型別,我們用 inserttime 欄位儲存資料的生成時間。

建立一個刪除指定表資料的儲存過程,如下:

mysql> delimiter //

mysql> CREATE PROCEDURE del_data(IN `date_inter` int)
    -> BEGIN
    ->   DELETE FROM person WHERE inserttime < date_sub(curdate(), interval date_inter day);
    -> END //

mysql> delimiter ;

建立一個名為 del_data 的儲存過程,引數 date_inter 指定要刪除的資料距離當前時間的天數。當表 person 的 inserttime 欄位值(datetime 型別)加上引數 date_inter 天小於當前時間,則認為資料過期,將過期的資料刪除。

事件(event)

事件是在相應的時刻呼叫的過程式資料庫物件。一個事件可呼叫一次,也可週期性的啟動,它由一個特定的執行緒來管理,也就是所謂的“事件排程器”。
事件和觸發器類似,都是在某些事情發生的時候啟動。當資料庫上啟動一條語句的時候,觸發器就啟動了,而事件是根據排程事件來啟動的。由於它們彼此相似,所以事件也稱為臨時性觸發器。事件排程器可以精確到每秒鐘執行一個任務。

如下建立一個事件,週期性的在某個時刻呼叫儲存過程,來進行清理資料。

mysql> CREATE EVENT del_event  
    ->     ON SCHEDULE 
    ->     EVERY 1 DAY 
    ->     STARTS '2020-03-20 12:00:00'
    ->     ON COMPLETION PRESERVE ENABLE
    ->     DO CALL del_data(1);

建立一個名為 del_event 的事件,該事件從 2020-03-20 開始,每天的 12:00:00 執行儲存過程 del_data(1)。

然後執行:

mysql> SET global event_scheduler = 1;

開啟事件。這樣事件 del_event 就會在指定的時間自動在後臺執行。通過上述的儲存過程 del_data 和事件 del_event,來達到定時自動刪除過期資料的目的。

TTL(Time To Live) 清洗資料

通過上述儲存過程和事件的組合可以定時清理資料庫中的過期資料。圖資料庫 Nebula Graph 提供了更加簡單高效的方式--使用 TTL 的方式來自動清洗過期資料。

使用 TTL 方式自動清洗過期資料的好處如下:

  1. 簡單方便
  2. 通過資料庫系統內部邏輯進行處理,安全可靠
  3. 資料庫會根據自身的狀態自動判斷是否需要處理,如果需要處理,將在後臺自動進行處理,無需人工干預。

TTL 簡介

TTL,全稱 Time To Live,用來指定資料的生命週期,資料時效到期後這條資料會被自動刪除。在圖資料庫 Nebula Graph 中,我們實現 TTL 功能,使用者設定好資料的存活時間後,在預定時間內系統會自動從資料庫中刪除過期的點或者邊。

在 TTL 中,過期資料會在下次 compaction 時被刪除,在下次 compaction 之前,query 會過濾掉過期的點和邊。

圖資料庫 Nebula Graph 的 TTL 功能需 ttl_col  和 ttl_duration 兩個欄位一起使用,到期閾值是 ttl_col 指定的屬性對應的值加上 ttl_duration  設定的秒數。其中 ttl_col 指定的欄位的型別應為 integer 或 timestamp,ttl_duration 的計量單位為秒。

TTL 讀過濾

針對 tag / edge,Nebula Graph 在 TTL 中將讀資料過濾邏輯下推到 storage 層進行處理。在 storage 層,首先獲取該 tag / edge 的 TTL 資訊,然後依次遍歷每個頂點或邊,取出 ttl_col 欄位值,根據 ttl_duration 的值加上 ttl_col 列欄位值,跟當前時間的時間戳進行比較,判斷資料是否過期,過期的資料將被忽略。

TTL compaction

RocksDB 檔案組織方式

圖資料庫 Nebula Graph 底層儲存使用的是 RocksDB,RocksDB 在磁碟上的檔案是分為多層的,預設是 7 層,如下圖所示:

SST檔案在磁碟上的組織方式

Level 0 層包含的檔案,是由記憶體中的 Memtable flush 到磁碟,生成的 SST 檔案,單個檔案內部按 key 有序排列,檔案之間無序。其它 Level 上的多個檔案之間都是按照 key 有序排列,並且檔案內也有序,如下圖所示:

非Level 0 層的檔案資料劃分

RocksDB compaction 原理

RocksDB 是基於 LSM 實現,但 LSM 並不是一個具體的資料結構,而是一種資料結構的概念和設計思想,具體細節參考LSM論文。而 LSM 中最重要部分就是 compaction,由於資料檔案採用 Append only 方式寫入,而對於過期的資料,重複的、已刪除的資料,需要通過 compaction 進行逐步的清理。

RocksDB compaction 邏輯

我們採用的 RocksDB 的 compaction 策略為 Level compaction。當資料寫到
RocksDB 時,會先將資料寫入到一個 Memtable 中,當一個 Memtable 寫滿之後,就會變成 Immutable 的 Memtable。RocksDB 在後臺通過一個 flush 執行緒將這個 Memtable flush 到磁碟,生成一個 Sorted String Table (SST) 檔案,放在 Level 0 層。當 Level 0 層的 SST 檔案個數超過閾值之後,就會與Level 1 層進行 compaction。通常必須將 Level 0 的所有檔案 compaction 到 Level 1 中,因為 Level 0 的檔案的 key 是有交疊的。

Level 0 與 Level 1 的 compaction 如下:

Level 0 與 Level 1 的 compaction

其他 Level 的 compaction 規則一樣,以 Level 1與 Level 2 的 compaction 為例進行說明,如下所示:

Level 1 與 Level 2 的 compaction

當 Level 0 compaction 完成後,Level 1 的檔案總大小或者檔案數量可能會超過閾值,觸發 Level 1 與 Level 2 的 compaction。從 Level 1 層至少選擇一個檔案 compaction 到 Level 2 的 key 重疊的檔案中。compaction 後可能會觸發下一個 Level 的 compaction,以此類推。

如果沒有 compaction,寫入是非常快的,但這樣會造成讀效能降低,同樣也會造成很嚴重的空間放大問題。為了平衡寫入、讀取、空間三者的關係,RocksDB 會在後臺執行 compaction,將不同 Level 的 SST 進行合併。

TTL compaction 原理

除了上述預設的compaction操作外(sst檔案合併),RocksDB 還提供了CompactionFilter 功能,可以讓使用者自定義個性化的compaction邏輯。Nebula Graph 使用了這個CompactionFilter來定製本文討論的TTL功能。該功能是 RocksDB 在 compaction 過程中,每讀取一條資料時,都會呼叫一個定製的Filter 函式。TTL compaction 的實現方法就是在 Filter 函式中實現 TTL 過期資料刪除邏輯,具體如下:

  1. 首先獲取 tag / edge 的 TTL 資訊
  2. 然後遍歷每個頂點或邊資料,取出 ttl_col 列欄位值
  3. 根據 ttl_duration 的值加上 ttl_col 列欄位值,跟當前時間的時間戳進行比較,然後判斷資料是否過期,過期的資料將被刪除。

TTL 用法

在圖資料庫 Nebula Graph 中,edge 和 tag 實現邏輯一致,在這裡僅以 tag 為例,來介紹 Nebula Graph 中 TTL 用法。

建立 TTL 屬性

Nebula Graph 中使用 TTL 屬性分為兩種方式:

create tag 時指定 ttl_duration 來表示資料的持續時間,單位為秒。ttl_col 指定哪一列作為 TTL 列。語法如下:

nebula> CREATE TAG t (id int, ts timestamp ) ttl_duration=3600, ttl_col="ts";

當某一條記錄的 ttl_col 列欄位值加上 ttl_duration 的值小於當前時間的時間戳,則該條記錄過期,否則該記錄不過期。

  • ttl_duration 的值為非正數時,則點的此 tag 屬性不會過期
  • ttl_col 只能指定型別為 int 或者 timestamp 的列名。

或者 create tag 時沒有指定 TTL 屬性,後續想使用 TTL 功能,可以使用 alter tag 來設定 TTL 屬性。語法如下:

nebula> CREATE TAG t (id int, ts timestamp );
nebula> ALTER TAG t ttl_duration=3600, ttl_col="ts";

檢視 TTL 屬性

建立完 tag 可以使用以下語句檢視 tag 的 TTL 屬性:

nebula> SHOW CREATE TAG t;
=====================================
| Tag | Create Tag                  |
=====================================
| t   | CREATE TAG t (
  id int,
  ts timestamp
) ttl_duration = 3600, ttl_col = id |
-------------------------------------

修改 TTL 屬性

可以使用 alter tag 語句修改 TTL 的屬性:

nebula> ALTER TAG t ttl_duration=100, ttl_col="id";

刪除 TTL 屬性

當不想使用 TTL 屬性時,可以刪除 TTL 屬性:

可以設定 ttl_col 欄位為空,或刪除配置的 ttl_col 欄位,或者設定 ttl_duration 為 0 或者 -1。

nebula> ALTER TAG t1 ttl_col = ""; -- drop ttl attribute

刪除配置的 ttl_col 欄位:

nebula> ALTER TAG t1 DROP (a); -- drop ttl_col

設定 ttl_duration 為 0 或者 -1:

nebula> ALTER TAG t1 ttl_duration = 0; -- keep the ttl but the data never expires

舉例

下面的例子說明,當使用 TTL 功能,並且資料過期後,查詢該 tag 的資料時,過期的資料被忽略。

nebula> CREATE TAG t(id int) ttl_duration=100, ttl_col="id";
nebula> INSERT VERTEX t(id) values 102:(1584441231);

nebula> FETCH prop on t 102;
Execution succeeded (Time spent: 5.945/7.492 ms)

注意:

  1. 當某一列作為 ttl_col 值的時候,不允許 change 該列。
         必須先移除 TTL 屬性,再 change 該列。
  2. 對同一 tag,index 和 TTL 功能不能同時使用。即使 index 和 TTL 創建於不同列,也不可以同時使用。

edge 同 tag 的邏輯一樣,這裡就不在詳述了。

TTL 的介紹就到此為止了,如果你對圖資料庫 Nebula Graph 的 TTL 有改進想法或其他要求,歡迎去 GitHub:https://github.com/vesoft-inc/nebula issue 區向我們提 issue 或者前往官方論壇:https://discuss.nebula-graph.io/ 的 Feedback 分類下提建議