更高效能表現、更低資源佔用,高精度計算資料型別 DecimalV3 揭祕

語言: CN / TW / HK

數值運算是資料庫中十分常見的需求,例如計算數量、重量、價格等,為了適應多樣化運算場景,資料庫系統通常支援精準的數字型別和近似的數字型別,當我們需要精確地表示小數並計算小數時,通常會考慮使用 Decimal 資料型別。區別於浮點小數,Decimal 作為定點小數型別,可以支援高精度的小數運算,因此適用於各種高精度計算的場景,常見的應用場景有以下幾種:

  • 金融行業:在金融交易中經常涉及到小數,比如利息、金額的計算,金融場景對數字準確的要求極高,因此精確的小數運算是必要的。
  • 財務軟體:財務軟體通常需要進行復雜的財務計算,Decimal 型別可以提供精確的小數計算,避免計算過程中產生的舍入誤差。
  • 科學計算、工程計算等其他場景。

DecimalV3 功能介紹

Apache Doris 1.2.1 之前的版本中,我們已對 Decimal(precision, scale)(precision<=27) 資料型別進行了支援,隨著 Apache Doris 使用者的持續增長,銀行、證券、基金等金融領域的使用者也隨之快速增長,對高精度的小數計算場景也提出了更高的要求,舊的 Decimal 資料型別已無法滿足。因此,我們在 Apache Doris 1.2.1 推出了精度更高、速度更快的 DecimalV3(precision, scale)(precision<=38),實現了真正意義上的高精度定點數,相比於老版本中的 Decimal ,DecimalV3 有以下核心優勢:

  1. 可表示範圍更大。DECIMALV3 對 Precision 和 Scale 的取值範圍進行擴充。
  2. 記憶體佔用更低,效能更高。老版本的 Decimal 需要佔用 16 Bytes 的記憶體,而 DecimalV3 對記憶體可進行自適應調整,如下所示。
+----------------------+-------------------+
|     precision        | 佔用空間(記憶體/磁碟)|
+----------------------+-------------------+
| 0 < precision <= 8   |      4 bytes      |
+----------------------+-------------------+
| 8 < precision <= 18  |      8 bytes      |
+----------------------+-------------------+
| 18 < precision <= 38 |     16 bytes      |
+----------------------+-------------------+
  1. 更完備的精度推演。

精度推演規則

DECIMALV3 有一套很複雜的型別推演規則,針對不同的表示式,會應用不同規則進行精度推演,下面來介紹一下推演規則:

  1. 四則運算
  • 加法 / 減法:DECIMALV3(a, b) + DECIMALV3(x, y) -> DECIMALV3(max(a - b, x - y) + max(b, y), max(b, y)),即整數部分和小數部分都分別使用兩個運算元中較大的值。
  • 乘法:DECIMALV3(a, b) * DECIMALV3(x, y) -> DECIMALV3(a + x, b + y)
  • 除法:DECIMALV3(a, b) / DECIMALV3(x, y) -> DECIMALV3(a + y, b)
  1. 聚合運算
  • SUM / MULTI_DISTINCT_SUM:SUM(DECIMALV3(a, b)) -> DECIMALV3(38, b)。
  • AVG:AVG(DECIMALV3(a, b)) -> DECIMALV3(38, max(b, 4))(鑑於每個系統 AVG 的精度不同,且不同使用者對精度的需求也不一樣,經調研,決定選擇與 SQLServer 相同的策略,因此選擇“4”既能保證較好的效能,也不會有較大的精度損失。)
  1. 預設規則

除上述提到的函式外,其餘表示式都使用預設規則進行精度推演。即對於表示式 expr(DECIMALV3(a, b)),結果型別同樣也是 DECIMALV3(a, b)。

結果精度調整

上述幾種規則為當前 Doris 的預設行為,而不同場景對 DECIMALV3 的精度要求各不相同,遠超出以上幾種規則。當用戶有不同的精度需求,可以通過以下方式進行精度調整

  • 當期望的結果精度大於預設精度時,可通過調整入參精度來調整結果精度。例如使用者期望計算AVG(col)得到DECIMALV3(x, y)作為結果,其中col的型別為 DECIMALV3(a, b),則可以改寫表示式為AVG(CAST(col as DECIMALV3(x, y)))
  • 當期望的結果精度小於預設精度時,可通過對輸出結果求近似得到想要的精度。例如使用者期望計算AVG(col)得到DECIMALV3(x, y)作為結果,其中col的型別為DECIMALV3(a, b),則可以改寫表示式為ROUND(AVG(col), y)

使用演示

這裡我們採用 Bitcoin 的資料集對 DecimalV3 進行演示。

Bitcoin 的資料集部分示例如下:

  • Unix - 時間戳
  • Date - 時間
  • Symbol - 時間序列資料所指代的交易品種
  • Open - 該時間段的開盤價
  • High - 該時間段的最高價
  • Low - 該時間段的最低價
  • Close - 該時間段的收盤價
  • Volume BTC - BTC 金額
  • Volume USD - USD 金額

以下是在 Doris 中的建表儲存資料,其中小數的列分別用 DecimalV3 進行儲存:

CREATE TABLE `btc` (
  `unix` bigint(20) NOT NULL,
  `date` datetime NULL,
  `symbol` varchar(30) NULL,
  `open` decimalv3(8, 2) NULL,
  `high` decimalv3(8, 2) NULL,
  `low` decimalv3(8, 2) NULL,
  `close` decimalv3(7, 2) NULL,
  `Volume_BTC` decimalv3(10, 8) NULL,
  `Volume_USD` decimalv3(38, 30) NULL
) ENGINE=OLAP
DUPLICATE KEY(`unix`)
COMMENT 'OLAP'
DISTRIBUTED BY HASH(`unix`) BUCKETS 4
PROPERTIES (
"replication_allocation" = "tag.location.default: 1"
);

我們來計算一下 2022 年 1 月 1 日這一天的平均 Volume_BTC/Volume_USD 以及總的 Volume_BTC/Volume_USD:

mysql> select avg(Volume_BTC),avg(Volume_USD),sum(Volume_BTC),sum(Volume_USD) from btc where to_date(date)='2022-01-01';
+-------------------+--------------------------------------+-------------------+-----------------------------------------+
| avg(`Volume_BTC`) | avg(`Volume_USD`)                    | sum(`Volume_BTC`) | sum(`Volume_USD`)                       |
+-------------------+--------------------------------------+-------------------+-----------------------------------------+
|        0.51494486 | 24236.665942788256243957638888888888 |      741.52060313 | 34900798.957615088991299000000000000000 |
+-------------------+--------------------------------------+-------------------+-----------------------------------------+

通過 SQL 的執行結果可以看到,通過 DecimalV3,在 Volume_USD 這一列的平均結果和總和上,實現了保留 30 位的小數。而舊的 Decimal 型別在這個例子中只能實現保留不超過 20 位。

效能對比

我們採用 TPC-H Benchmark 100G 來對比 DecimalV3 與老版本 Decimal 的執行速度、儲存佔用、記憶體佔用等效能。

我們在兩個庫分別對新版 DecimalV3 和老版本 Decimal 進行建表。建表完成如下:

tpch1庫為DecimalV3

tpch2庫為老版本Decimal

執行速度

採用 TPC-H Benchmark 對執行速度進行測試:

SQL Q1

select /*+SET_VAR(exec_mem_limit=8589934592, parallel_fragment_exec_instance_num=16, enable_vectorized_engine=true, batch_size=4096, disable_join_reorder=false, enable_cost_based_join_reorder=false, enable_projection=false) */
    l_returnflag,
    l_linestatus,
    sum(l_quantity) as sum_qty,
    sum(l_extendedprice) as sum_base_price,
    sum(l_extendedprice * (1 - l_discount)) as sum_disc_price,
    sum(l_extendedprice * (1 - l_discount) * (1 + l_tax)) as sum_charge,
    avg(l_quantity) as avg_qty,
    avg(l_extendedprice) as avg_price,
    avg(l_discount) as avg_disc,
    count(*) as count_order
from
    lineitem
where
    l_shipdate <= date '1998-12-01' - interval '90' day
group by
    l_returnflag,
    l_linestatus
order by
    l_returnflag,
    l_linestatus;

tpch1庫(DecimalV3)的 SQL 執行結果為 6.38s

tpch2 庫(老版本 Decimal)的 SQL 執行結果為 8.13s

SQL Q1 所查詢的表是上述展示欄位的表 Lineitem,我們可以看到在 DecimalV3 的情況下,查詢 速度較老版本有 27.4% 的提升。

儲存佔用

tpch1庫(DecimalV3)的 Lineitem 表的儲存佔用為 18.475GB

tpch2 庫(老版本 Decimal)的 Lineitem 表的儲存佔用為 20.893GB

可以看到在有四個欄位由 Decimal 改為 DecimalV3 的情況下,儲存佔用有 13.1%的降低。

記憶體佔用

記憶體佔用測試我們同樣使用 Lineitem 表,採用自己改寫的一條 SQL

select count(*) 
from 
(   select l_quantity,l_extendedprice,l_discount,l_tax 
    from lineitem 
    where l_shipdate < '1995-01-01' 
    group by l_quantity,l_extendedprice,l_discount,l_tax
)tmp;

下圖的 Grafana 監控中可以看到執行測試前的 Doris 記憶體穩定為 12.2GB

分別在兩個庫執行上述 SQL

在 tpch1庫(DecimalV3)下執行,記憶體佔用峰值為 26.6GB

記憶體回落正常後,在 tpch2 庫(老版本 Decimal)下執行,記憶體佔用峰值為 30.8GB

從上方三張圖中可以看到,這條 SQL 在 DecimalV3 的情況下不僅記憶體佔用降低了 15.8%,執行時間也縮短了10s。

總結

Apache Doris 1.2.1 版本推出的 DecimalV3 實現了更高的精度,更高的效能,更完備的精度推演,使得 Doris 更加適用於金融財務、科學計算等有精確計算需求的應用場景,結合 Apache Doris 強大的分析計算效能,給相關使用者及行業提供了更準確、完善的資料服務。

接下來,社群還將實現 JDBC 外表對 DecimalV3 型別的支援,JDBC Catalog 可以通過標準 JDBC 協議,連線其他資料來源,連線後 Doris 會自動同步資料來源下的 Database 和 Table 的元資料,以便快速訪問這些外部資料。基於 JDBC 的通用性,結合 Apache Doris 的 高效能分析能力,實現對各類資料庫資料聯邦查詢的高精度計算。

本文作者:

鍾永康,SelectDB 生態研發工程師

李文強,SelectDB 資料庫核心研發工程師,Apache Doris Committer

「其他文章」