为了将数据高效率的写入 clickhouse,通常我们需要在客户端缓冲数据,每 10w 条写入一次,这导致内存使用量非常高。
实现
当使用 clickhouse-go/v2 进行批量插入 []struct 时:
1, 将 []struct 通过 Append 转换成 Column 格式。
2,将 Column 转换成 Block 格式。
3, 将 Block 发送到 clickhouse server
4, 失败回退到 1 进行重试
batch, err := conn.PrepareBatch(context.Background(), "INSERT INTO benchmark")
if err != nil {
return err
}
for i := 0; i < 1_000_000; i++ {
err := batch.Append(
uint64(i),
"Golang SQL database driver",
[]uint8{1, 2, 3, 4, 5, 6, 7, 8, 9},
time.Now(),
)
if err != nil {
return err
}
}
return batch.Send()
}
clickhouse 一次插入耗时可能达到 6,7s。
而在这段时间内,[]struct 一直无法释放内存。
提前释放 []struct
使用底层的 github.com/ClickHouse/… api:
1, 将 []struct 转换为 proto.Col
2, 将 proto.Col 转换成 proto.Block。
3,将 proto.Block 发送到 clickhouse server。
4, 失败进入第二步进行重试。
var (
body proto.ColStr
)
// Append 10 rows to initial data block.
for i := 0; i < 10; i++ {
body.AppendBytes([]byte("Hello"))
}
input := proto.Input{
{Name: "ts", Data: ts}
}
if err := conn.Do(ctx, ch.Query{
// Or "INSERT INTO test_table_insert (body) VALUES"
// Or input.Into("test_table_insert")
Body: "INSERT INTO test_table_insert VALUES",
Input: input,
}); err != nil {
panic(err)
}
在执行完第一步后,[]struct 表示的内存就可以释放。
使用 net.Buffers.
不同的列,使用不同的 proto.Col 格式表示。
所有的 proto.Col 最后会拷贝到 proto.Block 发送,而 proto.Block 是 []byte, 因此实际上内存会重复的存在于内存中。
将 proto.Block 改造成 net.Buffers,使用 writev api 批量发送数据。
结果。
每 15w 行数据写一次,原先内存使用量 13GB,现在下降到 3.5 GB,降低了 **75% **的内存使用率。