ClickHouse原始碼筆記1:聚合函式的實現
阿新 • • 發佈:2020-06-02
>由於工作的需求,後續筆者工作需要和開源的OLAP資料庫ClickHouse打交道。ClickHouse是Yandex在2016年6月15日開源了一個分析型資料庫,以**強悍的單機處理能力被稱道**。
筆者在實際測試ClickHouse和閱讀ClickHouse的原始碼過程之中,對"戰鬥民族"開發的資料庫十分欣賞。**ClickHouse不僅是一個很好的資料庫學習材料,而且同時應用了大量的CPP17的新特性進行開發,也是一個大型的Modern CPP的教導資料。**
筆者接下來會陸續將閱讀ClickHouse的部分心得體會與通過原始碼閱讀筆記的方式和大家分享,坦白說,這種原始碼閱讀筆記很難寫啊。(**多一分繁瑣,少一分就模糊了~~**)
第一篇文章,我們就從聚合函式的實現開始聊起~~ 上車!
### 1.基礎知識的梳理
##### **什麼是聚合函式?**
聚合函式: **顧名思義就是對一組資料執行聚合計算並返回結果的函式。**
這類函式在資料庫之中很常見,如:```count, max, min, sum```等等。
##### ClickHouse的實現介面
* **IAggregateFunction介面**
在ClickHouse之中,定義了一個統一的聚合函式介面:**IAggregateFunction**.(在ClickHouse之中,所有的介面類都是以大寫的**I**開頭的。) 上文筆者提到的聚合函式,則都是作為抽象類IAggregateFunction的子類實現的。其中該介面最為核心的方法是下面這5個方法:
* add函式:最為核心的呼叫介面,將**對應AggregateDataPtr指標之中資料取出,與列columns中的第row_num的資料進行對應的聚合計算**。(這裡可以看到ClickHouse是一個純粹的列式儲存資料庫,所有的操作都是基於列的資料結構。)
* merge函式:將兩個聚合結果進行合併的函式,通常用在併發執行聚合函式的過程之中,需要將對應的聚合結果進行合併。
* serialize函式與deserialize函式:序列化與反序列化的函式,**通常用於spill to disk或分散式場景需要儲存或傳輸中間結果的。**
* addBatch函式:這是函式也是非常重要的,雖然它僅僅實現了一個for迴圈呼叫add函式。它通過這樣的方式來減少虛擬函式的呼叫次數,並且增加了編譯器內聯的概率。(**虛擬函式的呼叫需要一次訪存指令,一次查表,最終才能定位到需要呼叫的函式上,這在傳統的火山模型的實現上會帶來極大的CPU開銷。**)
```
/** Adds a value into aggregation data on which place points to.
* columns points to columns containing arguments of aggregation function.
* row_num is number of row which should be added.
* Additional parameter arena should be used instead of standard memory allocator if the addition requires memory allocation.
*/
virtual void add(AggregateDataPtr place, const IColumn ** columns, size_t row_num, Arena * arena) const = 0;
/// Merges state (on which place points to) with other state of current aggregation function.
virtual void merge(AggregateDataPtr place, ConstAggregateDataPtr rhs, Arena * arena) const = 0;
/// Serializes state (to transmit it over the network, for example).
virtual void serialize(ConstAggregateDataPtr place, WriteBuffer & buf) const = 0;
/// Deserializes state. This function is called only for empty (just created) states.
virtual void deserialize(AggregateDataPtr place, ReadBuffer & buf, Arena * arena) const = 0;
// /** Contains a loop with calls to "add" function. You can collect arguments into array "places"
* and do a single call to "addBatch" for devirtualization and inlining.
*/
virtual void addBatch(size_t batch_size, AggregateDataPtr * places, size_t place_offset, const IColumn ** columns, Arena * arena) const = 0;
```
* **抽象類IColumn**
上面的介面IAggregateFunction的函式使用到了ClickHouse的核心介面IColumn類,這裡也進行簡要的介紹。 IColumn 介面表達了所有資料在ClickHouse之中的用記憶體表達的資料結構,其他帶有具體資料型別的如```ColumnUInt8、ColumnArray``` 等, 都實現了對應的列介面,並且**在子類之中具象實現了不同的記憶體佈局。**
IColumn的子類實現細節很瑣碎,筆者這裡就暫時不展開講了,筆者這裡就簡單講講涉及到聚合函式呼叫部分的IColumn介面的對應方法:
這裡```columns```是一個二維陣列,通過```columns[0]```可以取到第一列。(這裡只有涉及到一列,為什麼columns是二維陣列呢?因為處理array等列的時候,也是通過對應的介面,而array就需要應用二維陣列了. )
注意這裡有一個強制的型別轉換,column已經轉換為ColVecType型別了,這是模板派生出IColumn的子類。
然後通過```IColumn```子類實現的```getData方法```獲取對應```row_num```行的資料進行add函式呼叫就完成了一次聚合函式的計算了。
```
void add(AggregateDataPtr place, const IColumn ** columns, size_t row_num, Arena *) const override
{
const auto & column = sta