TiDB 源碼閱讀系列文章(十)Chunk 和執行框架簡介

什麼是 Chunk

TiDB 2.0 中,我們引入瞭一個叫 Chunk 的數據結構用來在內存中存儲內部數據,用於減小內存分配開銷、降低內存占用以及實現內存使用量統計/控制,其特點如下:

  • 隻讀
  • 不支持隨機寫
  • 隻支持追加寫
  • 列存,同一列的數據連續的在內存中存放

Chunk 本質上是 Column 的集合,它負責連續的在內存中存儲同一列的數據,接下來我們看看 Column 的實現。1. Column

Column 的實現參考瞭 Apache Arrow,Column 的代碼在 這裡。根據所存儲的數據類型,我們有兩種 Column:

  • 定長 Column:存儲定長類型的數據,比如 DoubleBigintDecimal
  • 變長 Column:存儲變長類型的數據,比如 CharVarchar

哪些數據類型用定長 Column,哪些數據類型用變長 Column 可以看函數 addColumnByFieldType 。

Column 裡面的字段非常多,這裡先簡單介紹一下:

  • length

用來表示這個 Column 有多少行數據。

  • nullCount

用來表示這個 Column 中有多少 NULL 數據。

  • nullBitmap

用來存儲這個 Column 中每個元素是否是 NULL,需要特殊註意的是我們使用 0 表示 NULL,1 表示非 NULL,和 Apache Arrow 一樣。

  • data

存儲具體的數據,不管定長還是變長的 Column,所有的數據都存儲在這個 byte slice 中。

  • offsets

給變長的 Column 使用,存儲每個數據在 data 這個 slice 中的偏移量。

  • elemBuf

給定長的 Column 使用,當需要讀或者寫一個數據的時候,使用它來輔助 encode 和 decode。

1.1 追加一個定長的非 NULL 值

追加一個元素需要根據具體的數據類型調用具體的 append 方法,比如: appendInt64、appendString 等。一個定長類型的 Column 可以用如下圖表示:

我們以 appendInt64 為例來看看如何追加一個定長類型的數據:

  • 使用 unsafe.Pointer 把要 append 的數據先復制到 elemBuf 中;
  • 將 elemBuf 中的數據 append 到 data 中;
  • 往 nullBitmap 中 append 一個 1。

上面第 1 步在 appendInt64 這個函數中完成,第 2、3 步在 finishAppendFixed 這個函數中完成。其他定長類型元素的追加操作非常相似,感興趣的同學可以接著看看 appendFloat32、appendTime 等函數。

1.2 追加一個變長的非 NULL 值

而一個變長的 Column 可以用下圖表示:

47b0ad267023bc978d3b1f58813eaec9

我們以 appendString 為例來看看如何追加一個變長類型的數據:

  • 把數據先 append 到 data 中;
  • 往 nullBitmap 中 append 一個 1;
  • 往 offsets 中 append 當前 data 的 size 作為下一個元素在 data 中的起始點。

上面第 1 步在 appendString 這個函數中完成,第 2、3 步在 finishAppendVar 這個函數中完成。其他邊長類型元素的追加操作也是非常相似,感興趣的同學可以接著看看 appendBytes、appendJSON 等函數。

1.3 追加一個 NULL 值

我們使用 appendNull 函數來向一個 Column 中追加一個 NULL 值:

  • 往 nullBitmap 中 append 一個 0;
  • 如果是定長 Column,需要往 data 中 append 一個 elemBuf 長度的數據,用來占位;
  • 如果是變長 Column,不用往 data中 append 數據,而是往 offsets 中 append 當前 data 的 size 作為下一個元素在 data 中的起始點。

2. Row

18dadedcaa01895bdb6f2434c4f016a1

如上圖所示,Chunk 中的 Row 是一個邏輯上的概念:Row 中的數據存儲在 Chunk 的各個 Column 中,同一個 Row 中的數據在內存中沒有連續存儲在一起,我們在獲取一個 Row 對象的時候也不需要進行數據拷貝。提供 Row 的概念是因為算子運行過程中,大多數情況都是以 Row 為單位訪問和操作數據,比如聚合,排序等。

Row 提供瞭獲取 Chunk 中數據的方法,比如 GetInt64、GetString、GetMyDecimal 等,前面介紹瞭往 Column 中 append 數據的方法,獲取數據的方法可以由 append 數據的方法反推,代碼也比較簡單,這裡就不再詳細介紹瞭。

3. 使用

目前 Chunk 這個包隻對外暴露瞭 Chunk, Row 等接口,而沒有暴露 Column,所以,寫數據調用的是在 Chunk 上實現的對 Column 具體函數的 warpper,比如 AppendInt64;讀數據調用的是在 Row 上實現的 Getxxx 函數,比如 GetInt64。

執行框架簡介

1. 老執行框架簡介

在重構前,TiDB 1.0 中使用的執行框架會不斷調用 Child 的 Next 函數獲取一個由 Datum 組成的 Row(和剛才介紹的 Chunk Row 是兩個數據結構),這種執行方式的特點是:每次函數調用隻返回一行數據,且不管是什麼類型的數據都用 Datum 這個結構體來封裝。

de33362ab57f411ed559582bf346a93f

這種方法的優點是簡單、易用。缺點是:

  • 如果處理的數據量多,那麼框架上的函數調用開銷將會非常大;
  • Datum 占用的無效內存太大,內存浪費比較多(存一個 8 字節的整數需要 56 字節);
  • Datum 沒有重用,golang 的 gc 壓力大;
  • 每個 Operator 一次隻輸出一行數據,要進行更加緩存友好的計算、更充分的利用 CPU 的 pipeline 非常困難;
  • Datum 中的 interface 類型的數據,統計它的內存使用量比較困難。

2. 新執行框架簡介

在重構後,TiDB 2.0 中使用的執行框架會不斷調用 Child 的 NextChunk 函數,獲取一個 Chunk 的數據。

這種執行方式的特點是:

  • 每次函數調用返回一批數據,數據量由一個叫 tidb_max_chunk_size 的 session 變量來控制,默認是 1024 行。因為 TiDB 是一個混合 TP 和 AP 的數據庫,對於 AP 類型的查詢來說,因為計算的數據量大,1024 沒啥問題,但是對於 TP 請求來說,計算的數據量可能比較少,直接在一開始就分配 1024 行的內存並不是最佳的實踐( 這裡 有個 github issue 討論這個問題,歡迎感興趣的同學來討論和解決)。
  • Child 把它產出的數據寫入到 Parent 傳下來的 Chunk 中。

這種執行方式的好處是:

  • 減少瞭框架上的函數調用開銷。比如同樣輸出 1024 行結果,現在的函數調用次數將會是以前的 1/1024。
  • 內存使用更加高效。Chunk 中的數據組織非常緊湊,存一個 8 字節的整數幾乎就隻需要 8 字節,沒有其他額外的內存開銷瞭。
  • 減輕瞭 golang 的 gc 壓力。Chunk 占用的內存可以不斷地重復利用,不用頻繁的申請新內存,從而減輕瞭 golang 的 gc 壓力。
  • 查詢的執行過程更加緩存友好。如我們之前所說,Chunk 按列來組織數據,在計算的過程中我們也盡量按列來計算,這樣既能讓一列的數據盡量長時間的待在 Cache 中,減輕 Cache Miss 率,也能充分利用起 CPU 的 pipeline。這一塊在後續的源碼分析文章中會有詳細介紹,這裡就不再展開瞭。
  • 內存監控和控制更加方便。Chunk 中沒有使用任何 interface,我們能很方便的直接獲取一個 Chunk 當前所占用的內存的大小,具體可以看這個函數:MemoryUsage。關於 TiDB 內存控制,我們也會在後續文章中詳細介紹,這裡也不再展開瞭。

3. 新舊執行框架性能對比

采用瞭新的執行框架後,OLAP 類型語句的執行速度、內存使用效率都有極大提升,從 TPC-H 對比結果 看,性能有數量級的提升。

延展閱讀

(九)Hash Join

(八)基於代價的優化(七)基於規則的優化

(六)Select 語句概覽

(五)TiDB SQL Parser 的實現

(四)Insert 語句概覽

(三)SQL 的一生

(二)初識 TiDB 源碼

(一)序

发表回复

相关推荐

有关中国古代京剧常识

(一)京剧的形成 京剧是在18世纪下半叶经徽戏、秦腔、汉调的交融,并借鉴吸收昆曲、京腔之长而形成的。 徽剧是京剧的前身。 ...

· 4分钟前

营养不上火喝冬瓜汤, 教你做九道最受欢迎的冬瓜汤, 鲜嫩可口美味

冬瓜味甘、淡、性凉,入肺、大肠、小肠、膀胱经。有润肺生津,化痰止渴,利尿消肿,清热祛暑,解毒排脓。可用于暑热口渴,下 ...

· 5分钟前

易经的智慧:龙马负图

一、河图的内容 中华文化的根源在河洛,即黄河洛水,河洛文化的标志是“河图”“洛书”。“河图”指的是黄河出现的龙马负图,“洛书 ...

· 8分钟前

到底什么是当下?

真正的当下和“时间”、“空间”无关, 它是生命的“本来面目” 到底什么是当下? 对于头脑主义者来说,当下是指“此时”、“此地” ...

· 10分钟前

六位港片里著名“打星”,角色令人过目不忘,鲜有人知道他们真名

上世纪80-90年代,是香港电影黄金时代,当时香港作为亚洲电影中心,吸引了大批武术精英。

· 10分钟前