减少 clickhouse 客户端 75% 的内存

571 阅读1分钟

为了将数据高效率的写入 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% **的内存使用率。