Ускорение COUNT(DISTINCT) и Join с помощью AUTO INCREMENT и глобального словаря
В этой теме описывается, как ускорить вычисление COUNT(DISTINCT) и операции Join с использованием столбцов AUTO INCREMENT и глобального словаря.
Сценарии использования
-
Сценарий 1: Предположим, вам нужно выполнить т очную дедупликацию на массивных данных (например, розничные заказы или заказы на доставку). Однако столбец для дедупликации имеет тип STRING, что может привести к неоптимальной производительности при подсчёте. Например, в таблице
ordersстолбецorder_uuid, представляющий идентификатор заказа, имеет тип STRING, обычно размером от 32 до 36 байт, генерируемый функциейUUID()или аналогичными. В этом случае COUNT(DISTINCT) по STRING-столбцуorder_uuidс запросом видаSELECT count(DISTINCT order_uuid) FROM orders WHERE create_date >= CURDATE();может не обеспечить удовлетворительную производительность. Использование INTEGER-столбца для точной дедупликации значительно повысит производительность. -
Сценарий 2: Предположим, вы хотите ускорить точную дедупликацию в многомерном анализе с использованием bitmap-функций. Функция
bitmap_count()требует INTEGER на входе, но если столбец для дедупликации имеет тип STRING, вам нужно использовать функциюbitmap_hash(). Это может привести к приблизительным, немного заниженным результатам дедупликации, а также может снизить производительность запросов и уве личить требования к хранилищу, поскольку INTEGER-значения, генерируемые bitmap_hash(), более рассеяны по сравнению с последовательно присвоенными INTEGER-значениями. -
Сценарий 3: Предположим, вам нужно запросить количество заказов с коротким промежутком времени между размещением и оплатой заказа, где время размещения и оплаты заказа могут храниться в разных таблицах, поддерживаемых разными бизнес-командами. Вам может потребоваться объединить эти таблицы по идентификатору заказа, а затем выполнить дедупликацию заказов. Например:
SELECT count(distinct order_uuid)
FROM orders_t1 as t1 JOIN orders_t2 as t2
ON t1.order_uuid = t2.order_uuid
WHERE t2.payment_time - t1.create_time <= 3600
AND create_date >= CURDATE();Однако использование STRING-столбца
order_uuidдля соединений менее эффективно, чем использование INTEGER-столбца.
Подход к оптимизации
Для решения проблем в вышеуказанных сценариях подход к оптимизации включает загрузку данных заказов в целевую таблицу и установление сопоставления между STRING- и INTEGER-значениями. Последующий анализ запросов будет основан на INTEGER-столбце. Этот подход можно разделить на следующие этапы:
-
Этап 1: Создание глобального словаря и установление сопоставления между STRING- и INTEGER-значениями. В этом словаре ключевой столбец имеет тип STRING, а столбец значений — тип AUTO INCREMENT INTEGER. При загрузке данных система автоматически генерирует уникальный идентификатор для каждого STRING-значения, создавая сопоставление между STRING- и INTEGER-значениями.
-
Этап 2: Загрузка связи между данными заказов и глобальным словарём в целевую таблицу.
-
Этап 3: Использование INTEGER-столбца из целевой таблицы для точной дедупликации или сое динений при последующем анализе запросов, что может значительно повысить производительность.
-
Этап 4: Для дальнейшей оптимизации производительности можно использовать bitmap-функции на INTEGER-столбце для ускорения точной дедупликации.
Решение
До версии v1.5.2 этап 2 мог быть реализован двумя способами:
- Использование внешней таблицы или внутренней таблицы в качестве промежуточной таблицы для соединения с таблицей словаря для получения соответствующего идентификатора словаря перед загрузкой.
- Использование таблицы Primary Key для загрузки данных, а затем использование оператора UPDATE с операцией JOIN для обновления идентификатора словаря. Однако этот процесс загрузки данных может быть неудобным и имеет много ограничений.
Начиная с версии v1.5.2, Selena представила функцию dict_mapping(), позволяющую определять столбец идентификатора словаря в целевой таблице как вычисляемый столбец с использованием выражения dict_mapping(). Последующие задачи загрузки данных обрабатываются как обычная загрузка данных, без необходимости использования операторов UPDATE с операциями JOIN для записи идентификаторов словаря. Во время загрузки данных система автоматически связывает исходную таблицу с таблицей словаря и вставляет соответствующий идентификатор словаря, что значительно упрощает процесс загрузки данных с глобальной таблицей словаря, независимо от типов таблиц и с поддержкой различных методов загрузки.
Бизнес-сценарий
В следующем примере используются два примера CSV-файлов, batch1.csv и batch2.csv, каждый из которых содержит два столбца: id и order_uuid.
-
batch1.csv1, a1
2, a2
3, a3
11, a1
11, a2
12, a1 -
batch2.csv1, a2
2, a2
3, a2
11, a2
12, a101
12, a102
13, a102
Процесс
Первый этап
Создайте таблицу глобального словаря и загрузите значения столбца идентификатора заказа из CSV-файлов для установления сопоставления между STRING- и INTEGER-значениями.
-
Создайте таблицу Primary Key для использования в качестве глобального словаря. Определите Primary Key
order_uuid(тип STRING) и столбец значенийorder_id_int(тип AUTO INCREMENT INTEGER).к сведениюФункция
dict_mappingтребует, чтобы таблица глобального словаря была таблицей Primary Key.CREATE TABLE dict (
order_uuid STRING,
order_id_int BIGINT AUTO_INCREMENT -- Автоматически присваивает идентификатор каждому значению order_uuid.
)
PRIMARY KEY (order_uuid)
DISTRIBUTED BY HASH (order_uuid)
PROPERTIES("replicated_storage" = "true"); -
Используйте Stream Load для пакетной загрузки столбца
order_uuidиз двух CSV-файлов в столбецorder_uuidтаблицы словаряdict. Убедитесь, что вы используете частичное обновление в режиме столбцов.curl --location-trusted -u root: \
-H "partial_update: true" \
-H "format: CSV" -H "column_separator:," -H "columns: id, order_uuid" \
-T batch1.csv \
-XPUT http://<fe_host>:<fe_http_port>/api/example_db/dict/_stream_load
curl --location-trusted -u root: \
-H "partial_update: true" \
-H "format: CSV" -H "column_separator:," -H "columns: id, order_uuid" \
-T batch2.csv \
-XPUT http://<fe_host>:<fe_http_port>/api/example_db/dict/_stream_load
ПРИМЕЧАНИЕ
Если новые данные добавляются в источник данных до перехода к следующему этапу, все новые данные должны быть загружены в таблицу словаря для обеспечения существования сопоставления.
Второй этап
Создайте целевую таблицу, включающую столбец идентификатора словаря с атрибутом dict_mapping. При загрузке данных заказов в целевую таблицу система автоматически свяжет их с таблицей словаря и вставит соответствующий идентификатор словаря.
-
Создайте таблицу
dest_table, включающую все столбцы из CSV-файлов. Вам также нужно определить INTEGER-столбецorder_id_int(обычно BIGINT) для сопоставления со STRING-столбцомorder_uuid, имеющий атрибут столбцаdict_mapping. Будущий анализ запросов будет основан на этом столбцеorder_id_int.-- В целевой таблице определите BIGINT dict_mapping столбец `order_id_int` для сопоставления со STRING-столбцом `order_uuid`.
CREATE TABLE dest_table (
id BIGINT,
order_uuid STRING, -- Этот столбец записывает STRING-идентификатор заказа.
batch INT comment 'Используется для различения разных пакетов загрузки',
order_id_int BIGINT AS dict_mapping('dict', order_uuid) -- Столбец dict_mapping идентификатора словаря соответствует `order_uuid`.
)
DUPLICATE KEY (id, order_uuid)
DISTRIBUTED BY HASH(id); -
Загрузите данные в целевую таблицу с помощью Stream Load или любого другого доступного метода. Поскольку столбец
order_id_intимеет атрибутdict_mapping, система автоматически получит идентификатор словаря изdictво время загрузки.curl --location-trusted -u root: \
-H "format: CSV" -H "column_separator:," -H "columns: id, order_uuid, batch=1" \
-T batch1.csv \
-XPUT http://<fe_host>:<fe_http_port>/api/example_db/dest_table/_stream_load
curl --location-trusted -u root: \
-H "format: CSV" -H "column_separator:," -H "columns: id, order_uuid, batch=2" \
-T batch2.csv \
-XPUT http://<fe_host>:<fe_http_port>/api/example_db/dest_table/_stream_load
Третий этап
Во время анализа запросов вы можете выполнять точную дедупликацию или соединения по INTEGER-столбцу order_id_int, что значительно повышает производительность по сравнению с использованием STRING-столбца order_uuid.
-- Точная дедупликация на основе BIGINT-типа order_id_int.
SELECT id, COUNT(DISTINCT order_id_int) FROM dest_table GROUP BY id ORDER BY id;
-- Точная дедупликация на основе STRING-типа order_uuid.
SELECT id, COUNT(DISTINCT order_uuid) FROM dest_table GROUP BY id ORDER BY id;
Вы также можете использовать bitmap-функции для ускорения точной дедупликации.
Использование bitmap-функций для ускорения точной дедупликации
Для дальнейшего ускорения вычислений вы можете вставить INTEGER-значения столбцов таблицы словаря непосредственно в bitmap-столбец после создания глобального словаря. Впоследствии вы можете использовать bitmap-функции на этом bitmap-столбце для точной дедупликации.
Способ 1
Если вы создали глобальный словарь и уже импортировали данные заказов в dest_table, выполните следующие шаги:
-
Создайте Aggregate-таблицу
dest_table_bitmapс двумя столбцами: BITMAP-сто лбецorder_id_bitmapдля агрегации с использованием функцииbitmap_union()и INTEGER-столбецid. Эта таблица не включает исходный STRING-столбец, иначе каждый bitmap содержал бы только одно значение, что сводит на нет преимущество ускорения.CREATE TABLE dest_table_bitmap (
id BIGINT,
order_id_bitmap BITMAP BITMAP_UNION
)
AGGREGATE KEY (id)
DISTRIBUTED BY HASH(id) BUCKETS 6; -
Вставьте данные в
dest_table_bitmap. Вставьте данные из столбцаidтаблицыdest_tableв столбецid, а INTEGER-столбецorder_id_intиз таблицы словаряdict(обработанный функциейto_bitmap()) — в столбецorder_id_bitmap.INSERT INTO dest_table_bitmap (id, order_id_bitmap)
SELECT id, to_bitmap(dict_mapping('dict', order_uuid))
FROM dest_table
WHERE dest_table.batch = 1; -- Указывает разные пакеты.
INSERT INTO dest_table_bitmap (id, order_id_bitmap)
SELECT id, to_bitmap(dict_mapping('dict', order_uuid))
FROM dest_table
WHERE dest_table.batch = 2; -
Используйте функцию
BITMAP_UNION_COUNT()на BITMAP-столбце для точной дедупликации.SELECT id, BITMAP_UNION_COUNT(order_id_bitmap) FROM dest_table_bitmap
GROUP BY id ORDER BY id;
Способ 2
Если вам не нужно сохранять конкретные данные заказов после создания глобального словаря и вы хотите загружать данные непосредственно в таблицу dest_table_bitmap, выполните следующие шаги:
-
Создайте Aggregate-таблицу
dest_table_bitmapс двумя столбцами: BITMAP-столбецorder_id_bitmapдля агрегации с использованием функцииbitmap_union()и INTEGER-столбецid. Эта таблица не включает исходный STRING-столбец, иначе каждый bitmap содержал бы только одно значение, что сводит на нет преимущество ускорения.CREATE TABLE dest_table_bitmap (
id BIGINT,
order_id_bitmap BITMAP BITMAP_UNION
)
AGGREGATE KEY (id)
DISTRIBUTED BY HASH(id) BUCKETS 6; -
Вставьте данные в Aggregate-таблицу. Вставьте данные из столбца
idCSV-файлов в столбецid, а INTEGER-столбецorder_id_intиз таблицы словаряdict(обработанный функциейto_bitmap()) — в столбецorder_id_bitmap.curl --location-trusted -u root: \
-H "format: CSV" -H "column_separator:," \
-H "columns: id, order_uuid, order_id_bitmap=to_bitmap(dict_mapping('dict', order_uuid))" \
-T batch1.csv \
-XPUT http://<fe_host>:<fe_http_port>/api/example_db/dest_table_bitmap/_stream_load
curl --location-trusted -u root: \
-H "format: CSV" -H "column_separator:," \
-H "columns: id, order_uuid, order_id_bitmap=to_bitmap(dict_mapping('dict', order_uuid))" \
-T batch2.csv \
-XPUT http:///<fe_host>:<fe_http_port>/api/example_db/dest_table_bitmap/_stream_load -
Используйте функцию
BITMAP_UNION_COUNT()на BITMAP-столбце для точной дедупликации.SELECT id, BITMAP_UNION_COUNT(order_id_bitmap) FROM dest_table_bitmap
GROUP BY id ORDER BY id;