溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

ClickHouse是如何批量寫入的

發(fā)布時間:2021-12-22 17:16:03 來源:億速云 閱讀:501 作者:柒染 欄目:云計算

這篇文章將為大家詳細講解有關(guān)ClickHouse是如何批量寫入的,文章內(nèi)容質(zhì)量較高,因此小編分享給大家做個參考,希望大家閱讀完這篇文章后對相關(guān)知識有一定的了解。

簡介

批量寫入又稱為bulk write,對于單表插入多條數(shù)據(jù)的場景,可以減少插入請求數(shù)量,提高吞吐量和效率。clickhouse官方Golang驅(qū)動clickhouse-go[1]支持該關(guān)鍵特性,但是文檔的介紹不是很詳細,只有一句:

Bulk write support : begin->prepare->(in loop exec)->commit
 

并沒有詳細介紹用法和原理,筆者在開發(fā)業(yè)務時使用的庫是sqlx[2],sql也支持clickhouse-go驅(qū)動。參考了官方樣例代碼[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是每次執(zhí)行都發(fā)送寫請求到數(shù)據(jù)庫嗎?這個問題其實我不敢肯定,官方文檔也說得不明確??紤]到嚴謹性,讓自己的PR更有說服力,自己去翻看了相關(guān)源代碼。

這里需要指出,如果利用編輯器里的代碼跳轉(zhuǎn)功能會跳到database/sql庫中的Exec函數(shù)實現(xiàn),實際上我們要看的代碼是clickhouse-go中的實現(xiàn),至于編輯器跳轉(zhuǎn)到database/sql中的原因,書寫此文時筆者也沒弄清楚,先挖個坑吧。

 

核心實現(xiàn)

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
}
 

上面的代碼不多,非常清晰,當執(zhí)行Exec時,stmt.ch.block.AppendRow(args)會先把sql參數(shù)附加到本地緩存block中,然后(stmt.counter % stmt.ch.blockSize)判斷本地緩存大小是否到達閾值,到達則執(zhí)行Flush(),將數(shù)據(jù)寫入遠端。綜上,clickhouse-go中的核心實現(xiàn)邏輯是:

  1. 底層維護一個緩存block,同時設(shè)置block_size控制緩存大小
  2. 執(zhí)行stmt.Exec時,不會直接寫入遠程ClickHouse中,而是將插入?yún)?shù)Append到block中
  3. 每次Append后,判斷block的size和block_size的關(guān)系,如果正好整除,則刷新block(即寫入clickhouse)

因此block_size這個參數(shù)很重要,它表示本地緩存的上限,如果很大的話,程序會占用一些內(nèi)存。筆者起初設(shè)置為100000,在調(diào)試日志中看不到stmt.ch.logf("[exec] flush block")打印的log,設(shè)置小后就看到下面的輸出:

...
[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
....
   

關(guān)于ClickHouse是如何批量寫入的就分享到這里了,希望以上內(nèi)容可以對大家有一定的幫助,可以學到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI