ClickHouse/ch-go

Issues when trying to do simple data copy between tables

Closed this issue · 4 comments

From telegram channel:

[...]

Я тут копирую данные из одной таблицы в другую. Структура идентичная, селект * с условиями, и инсерт куда следует. Копирование между базами, драйвер ch-go (golang). Более высокоуровневый драйвер работает по строкам неоптимально, я его не пользую т.к. зачем тогда это всё columnar db и проч?. А этот работает блоками и массивами. Это правильно. Но в нем проблема1 и проблема2. Поясняю.

проблема1) состоит в том, что невозможно нормально получить массив данных из селект и тут же отправить его в инсерт. Промежуточная структура данных для LowCardinality в указанном драйвере енумерирует (переводит из строки в инт) мне все значения из колонки во время дальнейшей сериализации. А вообще, хранит как вычисленные строки для потребления так и хештейбл (map) внутри себя. Ну и пусть бы. Проблема возникает по причине того, что я хочу отправить это всё в соседний тред для записи в другую базу, а следующий блок получить в свежее место. Драйвер написан так, что нет возможности дать ему новое место куда ложить следующий блок (или я не нашел в примерах?). Поэтому я хочу сделать копию полученных данных, пусть хоть с итерацией по строкам во время промежуточных енумераций (что позор), и отправить ее в saver thread. Обычный цикл внутри .Do очищает и засовывает новую порцию в те же буфера (Result), и по кругу, а копия уже ушла на запись. Неидеально, но пусть хоть так.

проблема2) состоит в том, что копию полученных данных делать не получается по-человечески, но только ковыряясь в кишках структур буферов, вдобавок с приватными полями: речь идет про LowCardinality тип данных, который уже я упомянул выше. Поэтому я подумал, а почему бы мне не сделать SerializeToBlock/DeserializeFromBlock как это делают все ленивые белые люди, для получения глубокой копии данных с использованием авторской методики. Баг в том, что там блок, до этого десериализованный из wire stream в Result, когда сериализуешь его снова, не чистит одно из своих полей, и при сериализации получается потеря данных (индексы смотрят не туда), поэтому далее снова десериализовать это уже не получается - приходит с потерянными данными.

Подробно:

func (c *ColLowCardinality[T]) Prepare()

делает

c.index.Append(v)

ошибочно предполагая что длина c.index == 0 в начале функции, потому что автор написал что var last int ( == 0) перед циклом, хотя у блока который только что десериализирован из протокола (и сериализируется опять) там не ноль, а уже что-то есть. Поэтому надо

if c.kv == nil {
	c.kv = map[T]int{}
	c.index.Reset()                       //  ++++++ добавить это
}

Более глобально, помимо этого глюка, драма состоит в том, что я-то хочу отправить на инсерт такой же блок, что мне и пришел, а в драйвере делается много ненужной работы, например переиндексируются LowCardinality.

Да, ColLowCardinality[T] написан не очень оптимально для случая, когда нужно копировать данные.

Если бы у протокола кликхауза был фрейминг, то мы бы обошлись чем-то вроде ColRaw, но что есть то есть.

До дженериков была вот такая имплементация LowCardinality:
https://github.com/ClickHouse/ch-go/blob/166e758d0ad4d9fa714c8ccdbfe37c5122aed300/proto/col_low_cardinality.go
Может она лучше подойдет для вашего случая и её стоит вернуть?

Ну, я по месту добавил строчку как написал выше, баг с сериализацией ушёл. Если она не противоречит замыслу, вы могли бы ее зарелизить в мастере, тогда я из своего проекта уберу ссылку на свой форк.

Что до перфоманса с ре-енумерацией, ну, я проживу 8)

Посмотрите, пожалуйста, #203

Я там вернул еще ColLowCardinalityRaw, с ним можно будет избежать такого оверхеда

Да, спасибо, я вижу. Когда в релиз?