В этом руководстве предполагается, что пользователи уже знакомы с концепцией Incremental Materialized Views и загрузкой данных с помощью табличных функций, таких как S3 и GCS. Мы также рекомендуем ознакомиться с нашим руководством по оптимизации производительности вставки из Объектного хранилища: приведенные в нем рекомендации можно применять к вставкам на протяжении всего этого руководства.
Демонстрационный набор данных
pip.
Например, это подмножество охватывает один день — 2024-12-17 — и находится в открытом доступе по адресу https://datasets-documentation.s3.eu-west-3.amazonaws.com/pypi/2024-12-17/. Вы можете выполнить запрос с помощью:
Полный набор данных PyPI, включающий более 1 триллиона строк, доступен в нашей публичной демо-среде clickpy.clickhouse.com. Подробнее об этом наборе данных, в том числе о том, как в демо используются materialized views для повышения производительности и как данные ежедневно загружаются, см. здесь.
Сценарии дозагрузки
- Дозагрузка данных при существующей ингестии данных - Загружаются новые данные, и необходимо дозагрузить исторические. Эти исторические данные уже определены.
- Добавление materialized view к существующим таблицам - Нужно добавить новые materialized view в конфигурацию, где исторические данные уже загружены, а новые данные продолжают поступать.
Использование дублирующих таблиц и представлений
pypi и materialized view, вычисляющий количество загрузок для каждого Python-проекта:
{101..200}. Хотя можно выполнить вставку непосредственно в pypi, эту дозагрузку можно провести изолированно, создав дублирующие таблицы.
Если дозагрузка завершится неудачей, основные таблицы останутся нетронутыми — достаточно выполнить TRUNCATE для дублирующих таблиц и повторить попытку.
Чтобы создать новые копии этих представлений, можно использовать конструкцию CREATE TABLE AS с суффиксом _v2:
pypi_v2 и pypi_downloads_v2 и повторить загрузку данных.
Теперь, когда загрузка данных завершена, мы можем переместить данные из наших таблиц-дубликатов в основные таблицы с помощью оператора ALTER TABLE MOVE PARTITION.
Имена партицийВ приведённом выше вызове
MOVE PARTITION используется имя партиции (). Оно обозначает единственную партицию этой таблицы (которая не разбита на партиции). Для таблиц, разбитых на партиции, потребуется выполнить несколько вызовов MOVE PARTITION — по одному для каждой партиции. Имена текущих партиций можно получить из таблицы system.parts, например: SELECT DISTINCT partition FROM system.parts WHERE (table = 'pypi_v2').pypi и pypi_downloads содержат все данные. pypi_downloads_v2 и pypi_v2 можно безопасно удалить.
MOVE PARTITION одновременно является и малозатратной (за счёт использования жёстких ссылок), и атомарной, то есть либо завершается ошибкой, либо выполняется успешно без какого-либо промежуточного состояния.
Мы активно используем этот процесс в описанных ниже сценариях дозагрузки.
Обратите внимание, что в рамках этого процесса нужно выбрать размер каждой операции вставки.
Чем крупнее вставки, то есть чем больше строк, тем меньше потребуется операций MOVE PARTITION. Однако это нужно уравновешивать затратами на восстановление в случае сбоя вставки, например из-за прерывания сети. Чтобы снизить риск, этот процесс можно дополнить разбиением файлов на батчи. Это можно сделать либо с помощью диапазонных запросов, например WHERE timestamp BETWEEN 2024-12-17 09:00:00 AND 2024-12-17 10:00:00, либо с помощью glob-шаблонов. Например,
ClickPipes использует этот подход при загрузке данных из объектного хранилища: автоматически создает дубликаты целевой таблицы и связанных с ней materialized views, избавляя пользователя от необходимости выполнять описанные выше шаги. Кроме того, благодаря использованию нескольких воркер-потоков, каждый из которых обрабатывает разные подмножества данных (через glob-шаблоны) и имеет собственные таблицы-дубликаты, данные можно загружать быстро и с семантикой «ровно один раз». Дополнительные подробности см. в этом блоге.
Сценарий 1: Дозагрузка данных при существующей ингестии данных
- Определите контрольную точку — либо временную метку, либо значение столбца, начиная с которого необходимо дозагрузить исторические данные.
- Создайте дубликаты основной таблицы и целевых таблиц для materialized views.
- Создайте копии всех materialized views, указывающих на целевые таблицы, созданные на шаге (2).
- Выполните вставку в дубликат основной таблицы, созданный на шаге (2).
- Переместите все партиции из таблиц-дубликатов в их исходные версии. Удалите таблицы-дубликаты.
2024-12-17 09:00:00. Используя описанный ранее процесс, мы создаем таблицы-дубликаты и представления и загружаем подмножество данных, применяя фильтр по временной метке.
Фильтрация по столбцам с временными метками в Parquet может быть очень эффективной. ClickHouse будет читать только столбец с временной меткой, чтобы определить полные диапазоны данных для загрузки, сводя к минимуму сетевой трафик. Индексы Parquet, такие как min-max, также могут использоваться движком запросов ClickHouse.
Просто используйте ClickPipes в ClickHouse CloudЕсли вы используете ClickHouse Cloud, для восстановления исторических резервных копий следует использовать ClickPipes, если данные можно изолировать в отдельном бакете (и фильтр не требуется). Помимо сокращения времени загрузки за счет распараллеливания между несколькими воркерами, ClickPipes автоматизирует описанный выше процесс и создает таблицы-дубликаты как для основной таблицы, так и для materialized views.
Сценарий 2: Добавление materialized view к существующим таблицам
Избегайте POPULATEМы не рекомендуем использовать команду
POPULATE для дозагрузки materialized view нигде, кроме как на небольших наборах данных, когда приём приостановлен. Этот оператор может пропустить строки, вставленные в исходную таблицу, поскольку materialized view создаётся только после завершения хеширования POPULATE. Кроме того, POPULATE выполняется по всем данным и на больших наборах данных уязвим к прерываниям и ограничениям по памяти.Доступна временная метка или монотонно возрастающий столбец
- Создать materialized view с фильтром, который учитывает только строки со значениями больше некоторого произвольного момента времени в ближайшем будущем.
- Выполнить запрос
INSERT INTO SELECT, который вставляет данные в целевую таблицу materialized view, читая их из исходной таблицы с использованием агрегирующего запроса представления.
SELECT clause, добавляя фильтр, который учитывает только строки со временем больше некоторого произвольного момента в ближайшем будущем — в данном случае мы предполагаем, что 2024-12-17 09:00:00 наступит через несколько минут.
INSERT INTO SELECT. Например, для приведённого выше представления:
В приведённом выше примере наша целевая таблица — SummingMergeTree. В этом случае мы можем просто использовать исходный запрос агрегации. Для более сложных сценариев, в которых используется AggregatingMergeTree, для агрегаций следует использовать функции
-State. Пример можно найти в этом руководстве по интеграции.pypi_downloads_per_day_v2, выполнить вставку в неё, а затем Attach её итоговые партиции к pypi_downloads_per_day.
Часто запрос materialized view может быть более сложным (что неудивительно, иначе пользователи не использовали бы view!) и потреблять ресурсы. В более редких случаях ресурсы, необходимые для запроса, превышают возможности сервера. Это подчёркивает одно из преимуществ materialized views в ClickHouse — они инкрементальны и не обрабатывают весь набор данных за один проход!
В этом случае у пользователей есть несколько вариантов:
- Измените запрос, чтобы выполнить дозагрузку по диапазонам, например:
WHERE timestamp BETWEEN 2024-12-17 08:00:00 AND 2024-12-17 09:00:00,WHERE timestamp BETWEEN 2024-12-17 07:00:00 AND 2024-12-17 08:00:00и т. д. - Используйте движок таблицы Null, чтобы заполнить materialized view. Это воспроизводит типичное инкрементальное заполнение materialized view, выполняя её запрос по блокам данных (настраиваемого размера).
Использование движка таблицы Null для заполнения materialized views
/dev/null в мире движков таблиц). Хотя это может показаться противоречивым, materialized views всё равно будут выполняться для данных, вставляемых в этот движок таблицы. Это позволяет создавать materialized views без сохранения исходных данных, избегая IO и связанного с ним хранилища.
Важно, что любые materialized views, подключённые к этому движку таблицы, всё равно выполняются над блоками данных по мере их вставки, отправляя результаты в целевую таблицу. Размер этих блоков можно настраивать. Хотя более крупные блоки потенциально эффективнее (и обрабатываются быстрее), они потребляют больше ресурсов, главным образом памяти. Использование этого движка таблицы позволяет строить materialized view инкрементально, то есть по одному блоку за раз, не удерживая в памяти всю агрегацию.
Рассмотрим следующий пример:
pypi_v2, для приема строк, которые будут использоваться для построения нашего materialized view. Обратите внимание, что мы ограничиваем схему только нужными нам столбцами. Наш materialized view выполняет агрегацию по строкам, вставляемым в эту таблицу (по одному блоку за раз), и отправляет результаты в нашу целевую таблицу pypi_downloads_per_day.
Здесь мы использовали
pypi_downloads_per_day в качестве целевой таблицы. Для дополнительной отказоустойчивости пользователи могут создать таблицу-копию pypi_downloads_per_day_v2 и использовать ее как целевую таблицу представления, как показано в предыдущих примерах. После завершения вставки партиции из pypi_downloads_per_day_v2, в свою очередь, можно переместить в pypi_downloads_per_day. Это позволит восстановиться в случае, если вставка завершится неудачей из-за проблем с памятью или сбоев сервера, то есть мы просто выполняем TRUNCATE для pypi_downloads_per_day_v2, корректируем настройки и повторяем попытку.pypi_v2 соответствующие данные из pypi для дозагрузки.
639.47 MiB памяти.
Настройка производительности и ресурсов
- Параллелизм чтения — количество потоков, используемых для чтения. Управляется через
max_threads. В ClickHouse Cloud это значение определяется размером инстанса и по умолчанию равно числу vCPU. Увеличение этого значения может повысить производительность чтения, но приведёт к большему использованию памяти. - Параллелизм вставки - Количество потоков вставки, используемых для выполнения вставки. Управляется через
max_insert_threads. Примечание: Это значение ограничиваетсяmax_threads, поэтому фактический параллелизм вставки равенmin(max_insert_threads, max_threads). В ClickHouse Cloud он определяется размером инстанса (от 2 до 4), а в OSS установлен в 1. Увеличение этого значения может повысить производительность ценой более высокого использования памяти. - Размер блока вставки - данные обрабатываются в цикле: их считывают, разбирают и формируют в находящиеся в памяти блоки вставки на основе ключа партиционирования. Затем эти блоки сортируются, оптимизируются, сжимаются и записываются в хранилище как новые части данных. Размер блока вставки, задаваемый настройками
min_insert_block_size_rowsиmin_insert_block_size_bytes(в несжатом виде), влияет на использование памяти и дисковый ввод-вывод. Более крупные блоки требуют больше памяти, но создают меньше частей, снижая I/O и количество фоновых слияний. Эти настройки задают минимальные пороги (сброс происходит при достижении первого из них). - Размер блока materialized view — Помимо описанного выше механизма основной вставки, перед вставкой в materialized view блоки также укрупняются для более эффективной обработки. Размер этих блоков определяется настройками
min_insert_block_size_bytes_for_materialized_viewsиmin_insert_block_size_rows_for_materialized_views. Более крупные блоки обеспечивают более эффективную обработку, но ценой большего использования памяти. По умолчанию эти настройки принимают значения настроек исходной таблицыmin_insert_block_size_rowsиmin_insert_block_size_bytesсоответственно.
Совет для простых запросов INSERT SELECT: Для простых запросов
INSERT INTO t1 SELECT * FROM t2 без сложных преобразований можно включить optimize_trivial_insert_select=1. Этот параметр (по умолчанию отключён начиная с версии 24.7) автоматически подстраивает параллелизм SELECT под max_insert_threads, снижая потребление ресурсов и количество создаваемых частей. Это особенно полезно при массовой migration данных между таблицами.min_insert_block_size_bytes_for_materialized_views и min_insert_block_size_rows_for_materialized_views для повышения производительности не требуется. Если эти параметры всё же изменяются, следует руководствоваться теми же рекомендациями, что и для min_insert_block_size_rows и min_insert_block_size_bytes.
Для снижения потребления памяти можно поэкспериментировать с этими настройками. Это неизбежно приведёт к снижению производительности. Используя приведённый ранее запрос, рассмотрим примеры ниже.
Снижение max_insert_threads до 1 уменьшает накладные расходы памяти.
max_threads до 1.
min_insert_block_size_rows в 0 (это отключает его как фактор, определяющий размер блока) и min_insert_block_size_bytes в 10485760 (10 MiB).
Нет временной метки или монотонно возрастающего столбца
- Приостановите вставки в основную таблицу.
- Создайте дубликат основной целевой таблицы с помощью синтаксиса
CREATE AS. - Присоедините партиции из исходной целевой таблицы к дубликату с помощью
ALTER TABLE ATTACH. Примечание: Эта операция attach отличается от использованного ранее перемещения. Хотя она также опирается на жёсткие ссылки, данные в исходной таблице сохраняются. - Создайте новые materialized view.
- Возобновите вставки. Примечание: Вставки будут обновлять только целевую таблицу, а не дубликат, который будет ссылаться только на исходные данные.
- Выполните дозагрузку materialized view, применив тот же процесс, который описан выше для данных с временными метками, используя дубликат таблицы как источник.
pypi_downloads_per_day (предположим, что мы не можем использовать временную метку):
pypi_downloads_per_day, используя простой подход INSERT INTO SELECT, описанный ранее. Его также можно улучшить, используя подход с таблицей Null, описанный выше, а при необходимости — и дублирующую таблицу для повышения отказоустойчивости.
Хотя для этой операции действительно требуется приостановить вставки, промежуточные действия обычно выполняются быстро, что сводит любые перерывы в поступлении данных к минимуму.