亚欧色一区w666天堂,色情一区二区三区免费看,少妇特黄A片一区二区三区,亚洲人成网站999久久久综合,国产av熟女一区二区三区

  • 發布文章
  • 消息中心
點贊
收藏
評論
分享
原創

PostgreSQL jsonb數據類型的應用和內核優化

2024-10-17 09:34:27
3
0

提綱

  1. PostgreSQL json數據類型介紹 及 與MongoDB 的對比
  2. Toast 設計原則和存在的問題
  3. json 場景下Toast設計缺陷的放大
  4. 內核中的2種優化思路及我們的選擇

PostgreSQL json數據類型介紹及與MongoDB 的對比

傳統關系型數據庫需要用戶事先定義表結構, 而事后修改表結構也比較麻煩。 在SaaS 場景下,不同的也有很多的用戶屬性就不同的, 此時jsonb 就可以有很好的應用場景

CREATE TABLE customer
(
  id int primary key,
  name varchar(50), 
  industry varchar(50),
  size int,
  location varchar(40),
  extra_info jsonb
)

通過這種方式, jsonb 列就可以存放任意的信息。 比如:

{
"partners": ["Company A", "Company B", "Company C"],
"customer_feedback": { "positive_reviews": 85, "negative_reviews": 5}
},
  
{
"annual_revenue": "$10 million",
"awards": ["Best Employer 2020", "Top Innovator Award"]
}

json 數據類型上面有非常靈活的操作符。 MongoDB也是以json數據類型而聞名的。

從用戶最明顯可感知的領域看, 最本質的區別在于: MongoDB 只能使用 json, 而PG 可以使用 jsonb 和 其他的數據數據類型。 明顯的差別是: 傳統表結構 可以有更好的網絡傳輸效率, 因為 數據描述信息 是固定的。 而 jsonb 不僅僅要發送Value, 也要發送Key.

從技術層面來看, 數據庫是一個極其復雜的產品(優化器、執行器,事務引擎,存儲引擎等), 產品需要大量的人力/時間/場景去磨練。 PG 的json 是在 既定框架下的一個小的改動, 這個改動和各個組件可以自由組合。 而 MongoDB 則是從零又開發了一套新的系統。 解決了一些問題, 也為這些便利做了很好地推銷,但也埋下了一些大坑需要長期去填補。

部分對比如下:

MongoDB PostgreSQL
豐富的操作符 Y Y
優化器 真實嘗試多種執行計劃 * 基于代價的* 完整的plan cache.
執行器 * 全表掃描* Index [Only]Scan* 有限的表連接 * 全表掃描* Index [Only]Scan* Bitmap Index Scan* BitmapAnd & BimapOr* 靈活的表連接和3種Join算子* 語句級別并行執行
建模能力 只能使用json類型 json 類型和其他數據類型混合使用 * 更高效的存儲效率* 更高效的提取效率* 更高效的網絡傳輸效率
SQL 兼容 不兼容, 需要新學一種語法。 有些聚合 分析相關的語法還很復雜 完全兼容
事務特性 4.2+ 支持部分事務特性 完整的ACID 支持
壓縮能力 較強地壓縮比 壓縮比很弱
Sharding 能力 自帶sharing 能力 TeledbX, Citus 等

上面僅僅是列了一些特性的對比, 我們可以發現 數據庫是一個非常復雜的系統,為了解決建模的靈活性問題。MongoDB 和PostgreSQL 做出了完全不同的選擇:

  • PostgreSQL 引入了一個【數據類型 + 與之對應的操作符】,共用了大部分 優化器,執行器, 存儲引擎上的積累。
  • MongoDB 開發了一個【全新的數據庫】,所有的核心組件都要重新開發,這需要大量的【人力/時間/場景的磨煉】。

而對于一個云公司來說, 我們也更應該去推薦PostgreSQL 數據庫, 因為:

  • PostgreSQL 是純社區運行, 協議友好的
  • MongoDB 背后有商業公司, 云企業需要支付昂貴的許可費。

測試數據: 10GB 的tpch 數據,將其中的lineitem表的數據以3種形式保存。

a). 傳統表結構 @ PostgreSQL

b). 單個jsonb 列 @ PostgreSQL

c). 單個jsonb 列 @ MongoDB

查詢了一個返回行數較多的Index Scan 的場景:

性能: a) 優于 c) 的4倍。 b) 優于 c) 的 1.4 倍。

存儲: c) 優于 a) 1.2 倍, c) 優于 b) 3+ 倍. MongoDB 的壓縮性能要比PostgreSQL 好太多了 .

Toast 設計原則和存在的問題

TOAST是“The Oversized-Attribute Storage Technique”(超尺寸屬性存儲技術)的縮寫, 即 【變長】數據類型。 如 int / float 的長度是固定的, 所以和 toast 無關。 但text, jsonb 的長度都是可變的, 和toast 有關。 簡單地說, toast 會將 完整的大的數據 存放到 在外部, heap 表內僅僅保留一個指針(chunk#, seq#), 在需要的時候 會去還原出完成的數據。 Toast 更嚴謹地介紹可以參考知乎文檔。 Toast 的設計主要是為了以下幾個場景做優化:

create table t1 (a int, bigtext text, c int); 
create table t2 (like t1); 
  1. 查詢對toast 列不感興趣, 比如 SELECT a, c FROM t1; ==> 減少IO.
  2. 查詢對toast 列感興趣,但有其他的過濾條件。 比如 SELECT * FROM t1 WHERE a > 10; ==》 減少IO
  3. toast 列不能被過濾,但它們需要被放入 Hash Table 或者 Sort Memory. ==> 減少內存占用

SELECT * FROM t1 JOIN t2 using(c);

Hash Join
    Seq Scan on t1
    Hash
      Seq Scan on t2

Sort Merge
    Sort
      Seq Scan on t1
    Sort
      Seq Scan on t2

如果仔細了解TOAST,我們可以知道 從一個 指針 組裝出來一個 完整的數據 的代價是很大, 這個和原始數據的大小正相關。 從指針組裝回完整的數據的過程我們稱為 detoast.

Toast系統設計的核心在于:

  1. 什么時候去 detoast (組裝回完整的數據)?
  2. 組裝后的數據存放到哪里?
  3. 組裝后的數據所占用的內存何時去釋放

PostgreSQL 的設計原則

  1. detaost 到了不得不做的時候才會進行。 比如 SELECT bigtext FROM t WHERE a > 3;
  2. 組裝后的數據 存放在對應函數的私有內存,使用后立馬釋放。 對于上述操作 就是 textout 函數內部
  3. 特定操作符使用完立馬釋放 / 至少外界不可訪問。

上述設計存在的問題: 當查詢語句中需要對同一個屬性進行多次detoast 時, 結果不能共享,從而導致性能浪費。

比如 SELECT bigtext FROM t WHERE length(bigtext) > 100; 在這里面 第一次detoast 發生在 length(bigtext) 操作符, 如果發現 length(bigtext) > 100, 第二個detoast 發生在 SELECT <span data-type="text">bigtext</span>的 text_out 操作。

json 場景下Toast設計缺陷的放大

對于jsonb 數據類型, 因為它內置了較多的屬性,通常大小比較大, 并且客戶更容易對里面的 1+ 個屬性進行過濾 或者 提取操作。 比如

SELECT jsoncol->'x', jsoncol->'y' FROM t WHERE jsoncol->'kind' = 'X' AND jsoncol->'size' > 100;

這個查詢理論上可能對 同一個數據 進行4次 detoast 操作。

內核中的2種優化思路及我們的選擇

有2種完全不同的思路來解決問題, 涉及到的模塊也完全沒有交集。 我們先看一下整體流程:

  1. 優化器生成計劃
  2. 執行器執行計劃。 迭代器將 TupleTableSlot 中的數據從最底層節點拉取到最上層。
  3. 當運行到特定操作符時, 特定操作符從 TupleTableSlot 中獲取數據,調用 toast 子系統進行 detoast.

方案1: Toast Cache

在Toast子系統里面維護一個 Hash Cache, 上層將它用作 Query 級別的Cache. Query 內生成, Query 接受釋放。 (chunk#, seq# -> detoast result) 的設計。 該設計的好處是 邏輯非常容易理解, 失效也無需處理, 主要改動都控制在了 Toast 子系統內。 但作為一個Cache系統,最大的問題在于不能感知數據的生命周期, 繼而有以下影響:

  1. Cache 大小的設置:
    1. 作為 Query 內Cache, 考慮到Query 的并發量, Cache 的大小不可能設置太大。
    2. Cache 的是用戶數據(vs 元數據), Cache 的大小很容易被寫滿。
    3. 寫滿以后,就會涉及到淘汰邏輯。

進一步帶來的運行期開銷包括:

  1. Cache 查找對應的性能開銷。
  2. Cache 尋找失效對象性能開銷。
  3. Cache 失效對象的準確性問題。

為什么不考慮【全局】【長生命周期的】Cache, 類似不同的 buffer cache: 大量數據會Cache 雙份 (buffer cache 和 toast cache), 占用的空間太大。

方案2: 在TupleTableSlot 層面中共享detoast 數據

如上所述,TupleTableSlot 是多個Plan 節點用于傳遞數據的結構,它有以下特點:

  1. 數量僅僅和Plan節點的個數有關,和用戶業務數據的大小無關。 簡單理解,下面的Plan 僅僅有4個TableTupleSlot (實際上更多一些,) 數據的生命周期在Plan 中非常清晰,這很好地控制了Cache 大小,失效開銷,失效準確性的問題。
  2. TupleTableSlot 是所有 操作符數據 獲取數據的入口,所以沒有任何的性能 查找開銷。
          QUERY PLAN         
-------------------------------
 Hash Join
   Hash Cond: (t1.c0 = t2.c0)
   ->  Seq Scan on t1
   ->  Hash
         ->  Seq Scan on t1 t2
(5 rows)

如果不假思索地將數據存放到 TupleTableSlot 中,就會破壞了 Toast 設計的 最初設計, 最終我們選擇:

在優化器生成 Plan以后, 判斷某個 toast 列是否需要被放到 Hash Table 或者 Sort 節點, 如果不需要,我們會在需要首次訪問detoast 屬性的節點上, 第一次填充這個屬性的時候, 會進行detoast 操作, 然后將detoast 版本寫入到 TupleTableSlot 內部。 最終方案2 涉及到了 優化器,執行器 , 表達式(解釋執行,JIT 執行), toast 模塊 等,能夠避免方案 a) 中的所有問題。

最終效果

  1. 對于非目標場景的影響可以忽略不計 (任何人為構造的案例都不能測試出可量化的性能回退)
  2. 對于目標場景,性能提升幅度和 可變長度數據 的大小有關, 和 同一個屬性 被訪問的次數有關。
  3. 真實的業務場景: 某IaaS 客戶存在大量 10MB ~ 40MB 的jsonb 數據,對同一個json 的不同屬性有多次訪問。 性能提升90%。
0條評論
作者已關閉評論
樊****輝
2文章數
0粉絲數
樊****輝
2 文章 | 0 粉絲
樊****輝
2文章數
0粉絲數
樊****輝
2 文章 | 0 粉絲
原創

PostgreSQL jsonb數據類型的應用和內核優化

2024-10-17 09:34:27
3
0

提綱

  1. PostgreSQL json數據類型介紹 及 與MongoDB 的對比
  2. Toast 設計原則和存在的問題
  3. json 場景下Toast設計缺陷的放大
  4. 內核中的2種優化思路及我們的選擇

PostgreSQL json數據類型介紹及與MongoDB 的對比

傳統關系型數據庫需要用戶事先定義表結構, 而事后修改表結構也比較麻煩。 在SaaS 場景下,不同的也有很多的用戶屬性就不同的, 此時jsonb 就可以有很好的應用場景

CREATE TABLE customer
(
  id int primary key,
  name varchar(50), 
  industry varchar(50),
  size int,
  location varchar(40),
  extra_info jsonb
)

通過這種方式, jsonb 列就可以存放任意的信息。 比如:

{
"partners": ["Company A", "Company B", "Company C"],
"customer_feedback": { "positive_reviews": 85, "negative_reviews": 5}
},
  
{
"annual_revenue": "$10 million",
"awards": ["Best Employer 2020", "Top Innovator Award"]
}

json 數據類型上面有非常靈活的操作符。 MongoDB也是以json數據類型而聞名的。

從用戶最明顯可感知的領域看, 最本質的區別在于: MongoDB 只能使用 json, 而PG 可以使用 jsonb 和 其他的數據數據類型。 明顯的差別是: 傳統表結構 可以有更好的網絡傳輸效率, 因為 數據描述信息 是固定的。 而 jsonb 不僅僅要發送Value, 也要發送Key.

從技術層面來看, 數據庫是一個極其復雜的產品(優化器、執行器,事務引擎,存儲引擎等), 產品需要大量的人力/時間/場景去磨練。 PG 的json 是在 既定框架下的一個小的改動, 這個改動和各個組件可以自由組合。 而 MongoDB 則是從零又開發了一套新的系統。 解決了一些問題, 也為這些便利做了很好地推銷,但也埋下了一些大坑需要長期去填補。

部分對比如下:

MongoDB PostgreSQL
豐富的操作符 Y Y
優化器 真實嘗試多種執行計劃 * 基于代價的* 完整的plan cache.
執行器 * 全表掃描* Index [Only]Scan* 有限的表連接 * 全表掃描* Index [Only]Scan* Bitmap Index Scan* BitmapAnd & BimapOr* 靈活的表連接和3種Join算子* 語句級別并行執行
建模能力 只能使用json類型 json 類型和其他數據類型混合使用 * 更高效的存儲效率* 更高效的提取效率* 更高效的網絡傳輸效率
SQL 兼容 不兼容, 需要新學一種語法。 有些聚合 分析相關的語法還很復雜 完全兼容
事務特性 4.2+ 支持部分事務特性 完整的ACID 支持
壓縮能力 較強地壓縮比 壓縮比很弱
Sharding 能力 自帶sharing 能力 TeledbX, Citus 等

上面僅僅是列了一些特性的對比, 我們可以發現 數據庫是一個非常復雜的系統,為了解決建模的靈活性問題。MongoDB 和PostgreSQL 做出了完全不同的選擇:

  • PostgreSQL 引入了一個【數據類型 + 與之對應的操作符】,共用了大部分 優化器,執行器, 存儲引擎上的積累。
  • MongoDB 開發了一個【全新的數據庫】,所有的核心組件都要重新開發,這需要大量的【人力/時間/場景的磨煉】。

而對于一個云公司來說, 我們也更應該去推薦PostgreSQL 數據庫, 因為:

  • PostgreSQL 是純社區運行, 協議友好的
  • MongoDB 背后有商業公司, 云企業需要支付昂貴的許可費。

測試數據: 10GB 的tpch 數據,將其中的lineitem表的數據以3種形式保存。

a). 傳統表結構 @ PostgreSQL

b). 單個jsonb 列 @ PostgreSQL

c). 單個jsonb 列 @ MongoDB

查詢了一個返回行數較多的Index Scan 的場景:

性能: a) 優于 c) 的4倍。 b) 優于 c) 的 1.4 倍。

存儲: c) 優于 a) 1.2 倍, c) 優于 b) 3+ 倍. MongoDB 的壓縮性能要比PostgreSQL 好太多了 .

Toast 設計原則和存在的問題

TOAST是“The Oversized-Attribute Storage Technique”(超尺寸屬性存儲技術)的縮寫, 即 【變長】數據類型。 如 int / float 的長度是固定的, 所以和 toast 無關。 但text, jsonb 的長度都是可變的, 和toast 有關。 簡單地說, toast 會將 完整的大的數據 存放到 在外部, heap 表內僅僅保留一個指針(chunk#, seq#), 在需要的時候 會去還原出完成的數據。 Toast 更嚴謹地介紹可以參考知乎文檔。 Toast 的設計主要是為了以下幾個場景做優化:

create table t1 (a int, bigtext text, c int); 
create table t2 (like t1); 
  1. 查詢對toast 列不感興趣, 比如 SELECT a, c FROM t1; ==> 減少IO.
  2. 查詢對toast 列感興趣,但有其他的過濾條件。 比如 SELECT * FROM t1 WHERE a > 10; ==》 減少IO
  3. toast 列不能被過濾,但它們需要被放入 Hash Table 或者 Sort Memory. ==> 減少內存占用

SELECT * FROM t1 JOIN t2 using(c);

Hash Join
    Seq Scan on t1
    Hash
      Seq Scan on t2

Sort Merge
    Sort
      Seq Scan on t1
    Sort
      Seq Scan on t2

如果仔細了解TOAST,我們可以知道 從一個 指針 組裝出來一個 完整的數據 的代價是很大, 這個和原始數據的大小正相關。 從指針組裝回完整的數據的過程我們稱為 detoast.

Toast系統設計的核心在于:

  1. 什么時候去 detoast (組裝回完整的數據)?
  2. 組裝后的數據存放到哪里?
  3. 組裝后的數據所占用的內存何時去釋放

PostgreSQL 的設計原則

  1. detaost 到了不得不做的時候才會進行。 比如 SELECT bigtext FROM t WHERE a > 3;
  2. 組裝后的數據 存放在對應函數的私有內存,使用后立馬釋放。 對于上述操作 就是 textout 函數內部
  3. 特定操作符使用完立馬釋放 / 至少外界不可訪問。

上述設計存在的問題: 當查詢語句中需要對同一個屬性進行多次detoast 時, 結果不能共享,從而導致性能浪費。

比如 SELECT bigtext FROM t WHERE length(bigtext) > 100; 在這里面 第一次detoast 發生在 length(bigtext) 操作符, 如果發現 length(bigtext) > 100, 第二個detoast 發生在 SELECT <span data-type="text">bigtext</span>的 text_out 操作。

json 場景下Toast設計缺陷的放大

對于jsonb 數據類型, 因為它內置了較多的屬性,通常大小比較大, 并且客戶更容易對里面的 1+ 個屬性進行過濾 或者 提取操作。 比如

SELECT jsoncol->'x', jsoncol->'y' FROM t WHERE jsoncol->'kind' = 'X' AND jsoncol->'size' > 100;

這個查詢理論上可能對 同一個數據 進行4次 detoast 操作。

內核中的2種優化思路及我們的選擇

有2種完全不同的思路來解決問題, 涉及到的模塊也完全沒有交集。 我們先看一下整體流程:

  1. 優化器生成計劃
  2. 執行器執行計劃。 迭代器將 TupleTableSlot 中的數據從最底層節點拉取到最上層。
  3. 當運行到特定操作符時, 特定操作符從 TupleTableSlot 中獲取數據,調用 toast 子系統進行 detoast.

方案1: Toast Cache

在Toast子系統里面維護一個 Hash Cache, 上層將它用作 Query 級別的Cache. Query 內生成, Query 接受釋放。 (chunk#, seq# -> detoast result) 的設計。 該設計的好處是 邏輯非常容易理解, 失效也無需處理, 主要改動都控制在了 Toast 子系統內。 但作為一個Cache系統,最大的問題在于不能感知數據的生命周期, 繼而有以下影響:

  1. Cache 大小的設置:
    1. 作為 Query 內Cache, 考慮到Query 的并發量, Cache 的大小不可能設置太大。
    2. Cache 的是用戶數據(vs 元數據), Cache 的大小很容易被寫滿。
    3. 寫滿以后,就會涉及到淘汰邏輯。

進一步帶來的運行期開銷包括:

  1. Cache 查找對應的性能開銷。
  2. Cache 尋找失效對象性能開銷。
  3. Cache 失效對象的準確性問題。

為什么不考慮【全局】【長生命周期的】Cache, 類似不同的 buffer cache: 大量數據會Cache 雙份 (buffer cache 和 toast cache), 占用的空間太大。

方案2: 在TupleTableSlot 層面中共享detoast 數據

如上所述,TupleTableSlot 是多個Plan 節點用于傳遞數據的結構,它有以下特點:

  1. 數量僅僅和Plan節點的個數有關,和用戶業務數據的大小無關。 簡單理解,下面的Plan 僅僅有4個TableTupleSlot (實際上更多一些,) 數據的生命周期在Plan 中非常清晰,這很好地控制了Cache 大小,失效開銷,失效準確性的問題。
  2. TupleTableSlot 是所有 操作符數據 獲取數據的入口,所以沒有任何的性能 查找開銷。
          QUERY PLAN         
-------------------------------
 Hash Join
   Hash Cond: (t1.c0 = t2.c0)
   ->  Seq Scan on t1
   ->  Hash
         ->  Seq Scan on t1 t2
(5 rows)

如果不假思索地將數據存放到 TupleTableSlot 中,就會破壞了 Toast 設計的 最初設計, 最終我們選擇:

在優化器生成 Plan以后, 判斷某個 toast 列是否需要被放到 Hash Table 或者 Sort 節點, 如果不需要,我們會在需要首次訪問detoast 屬性的節點上, 第一次填充這個屬性的時候, 會進行detoast 操作, 然后將detoast 版本寫入到 TupleTableSlot 內部。 最終方案2 涉及到了 優化器,執行器 , 表達式(解釋執行,JIT 執行), toast 模塊 等,能夠避免方案 a) 中的所有問題。

最終效果

  1. 對于非目標場景的影響可以忽略不計 (任何人為構造的案例都不能測試出可量化的性能回退)
  2. 對于目標場景,性能提升幅度和 可變長度數據 的大小有關, 和 同一個屬性 被訪問的次數有關。
  3. 真實的業務場景: 某IaaS 客戶存在大量 10MB ~ 40MB 的jsonb 數據,對同一個json 的不同屬性有多次訪問。 性能提升90%。
文章來自個人專欄
文章 | 訂閱
0條評論
作者已關閉評論
作者已關閉評論
0
0