共計 2333 個字符,預計需要花費 6 分鐘才能閱讀完成。
這篇文章將為大家詳細講解有關 ClickHouse 是如何批量寫入的,文章內容質量較高,因此丸趣 TV 小編分享給大家做個參考,希望大家閱讀完這篇文章后對相關知識有一定的了解。
簡介
批量寫入又稱為 bulk write,對于單表插入多條數據的場景,可以減少插入請求數量,提高吞吐量和效率。clickhouse 官方 Golang 驅動 clickhouse-go[1]支持該關鍵特性,但是文檔的介紹不是很詳細,只有一句:
Bulk write support : begin- prepare- (in loop exec)- commit
并沒有詳細介紹用法和原理,筆者在開發業務時使用的庫是 sqlx[2],sql 也支持 clickhouse-go 驅動。參考了官方樣例代碼[3]:
...
tx, err := connect.Begin()
checkErr(err)
stmt, err := tx.Prepare(INSERT INTO example (country_code, os_id, browser_id, categories, action_day, action_time) VALUES (?, ?, ?, ?, ?, ?) )
checkErr(err)
for i := 0; i 100; i++ {
if _, err := stmt.Exec(
RU ,
10+i,
100+i,
[]int16{1, 2, 3},
time.Now(),
time.Now(),
); err != nil {
log.Fatal(err)
}
}
...
我寫的 bulk write 類似上面的代碼,但是提交給同事 review 時,他提出了疑問:stmt.Exec 是每次執行都發送寫請求到數據庫嗎?這個問題其實我不敢肯定,官方文檔也說得不明確。考慮到嚴謹性,讓自己的 PR 更有說服力,自己去翻看了相關源代碼。
這里需要指出,如果利用編輯器里的代碼跳轉功能會跳到 database/sql 庫中的 Exec 函數實現,實際上我們要看的代碼是 clickhouse-go 中的實現,至于編輯器跳轉到 database/sql 中的原因,書寫此文時筆者也沒弄清楚,先挖個坑吧。
核心實現
stmt.Exec 的核心代碼如下[4]:
func (stmt *stmt) execContext(ctx context.Context, args []driver.Value) (driver.Result, error) {
if stmt.isInsert {
stmt.counter++
if err := stmt.ch.block.AppendRow(args); err != nil {
return nil, err
}
if (stmt.counter % stmt.ch.blockSize) == 0 {
stmt.ch.logf([exec] flush block )
if err := stmt.ch.writeBlock(stmt.ch.block); err != nil {
return nil, err
}
if err := stmt.ch.encoder.Flush(); err != nil {
return nil, err
}
}
return emptyResult, nil
}
if err := stmt.ch.sendQuery(stmt.bind(convertOldArgs(args))); err != nil {
return nil, err
}
if err := stmt.ch.process(); err != nil {
return nil, err
}
return emptyResult, nil
}
上面的代碼不多,非常清晰,當執行 Exec 時,stmt.ch.block.AppendRow(args)會先把 sql 參數附加到本地緩存 block 中,然后 (stmt.counter % stmt.ch.blockSize) 判斷本地緩存大小是否到達閾值,到達則執行 Flush(),將數據寫入遠端。綜上,clickhouse-go 中的核心實現邏輯是:
底層維護一個緩存 block,同時設置 block_size 控制緩存大小執行 stmt.Exec 時,不會直接寫入遠程 ClickHouse 中,而是將插入參數 Append 到 block 中每次 Append 后,判斷 block 的 size 和 block_size 的關系,如果正好整除,則刷新 block(即寫入 clickhouse)
因此 block_size 這個參數很重要,它表示本地緩存的上限,如果很大的話,程序會占用一些內存。筆者起初設置為 100000,在調試日志中看不到 stmt.ch.logf([exec] flush block )打印的 log,設置小后就看到下面的輸出:
...
[clickhouse][connect=1][begin] tx=false, data=false
[clickhouse][connect=1][prepare]
[clickhouse][connect=1][read meta] - data: packet=1, columns=6, rows=0
[clickhouse][connect=1][exec] flush block
[clickhouse][connect=1][exec] flush block
....
關于 ClickHouse 是如何批量寫入的就分享到這里了,希望以上內容可以對大家有一定的幫助,可以學到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。