Рекомендации по проектированию таблиц и распределению данных

Рекомендации по проектированию таблиц

В зависимости от характеристик различных движков хранения вы можете гибко создавать разные типы таблиц, соответствующие требованиям конкретных сценариев. Ниже приведены примеры для справки.

1. Таблицы MARS3

Таблицы MARS3 зависят от расширения matrixts. Перед созданием таблицы с использованием этого движка хранения необходимо сначала создать расширение в целевой базе данных.

Примечание!
Расширение matrixts применяется на уровне базы данных. Его достаточно создать один раз на базу данных; повторное создание не требуется.

=# CREATE EXTENSION matrixts;

При создании таблицы используйте USING MARS3 для указания движка хранения и ORDER BY для определения ключа сортировки. Пример базового создания таблицы:

=# CREATE TABLE mars3(
    time timestamp with time zone,
    tag_id int,
    i4 int4,
    i8 int8
)
USING MARS3 ORDER BY (tag_id, time);

Дополнительно рассмотрим типичный пример сценария временных рядов в IoT:

=# CREATE TABLE vehicle_basic_data_mars3(
  daq_time timestamp ,
  vin varchar(32)  COLLATE "C" ,
  lng float ,
  lat float ,
  speed float ,
  license_template varchar(16) ,
  flag integer
)
USING MARS3
WITH (compresstype=zstd, compresslevel=3, compress_threshold=1200,
        mars3options='rowstore_size=64')
DISTRIBUTED BY (vin)
ORDER BY (vin, daq_time)
PARTITION BY RANGE (daq_time)
( START ('2022-07-01 00:00:00') INCLUSIVE
  END ('2022-08-01 00:00:00') EXCLUSIVE
  EVERY (INTERVAL '1 day')
,DEFAULT PARTITION OTHERS);

После успешного создания таблицы MARS3 рекомендуется создать индекс mars3_brin по ключу сортировки. Индекс mars3_brin является разреженным, оказывает минимальное влияние на объём диска и производительность вставки. В сочетании с упорядоченной природой ключа сортировки он обеспечивает оптимальную эффективность запросов по столбцам ключа сортировки.

Индексы mars3_brin также можно создавать по столбцам, не входящим в ключ сортировки. Однако без преимущества упорядоченности данных производительность запросов может быть несколько снижена. Добавляйте такие индексы по мере необходимости.

=# CREATE INDEX idx_mars3 ON vehicle_basic_data_mars3 USING mars3_brin(vin, daq_time);

Ниже приведены рекомендации, основанные на параметрах и операторах из примера выше:

Примечание!
Мы рекомендуем следовать предложенным принципам проектирования, но не советуем копировать их бездумно. Сценарии временных рядов сильно различаются; поэтому анализ каждого конкретного случая остаётся обязательным.

Выбор ключа сортировки

Проектирование таблицы должно учитывать выбор ключа сортировки. Цель ключа сортировки — обеспечить физическое совместное размещение данных в одном временном измерении или с похожими атрибутами. Это снижает количество операций поиска при вводе-выводе и повышает эффективность запросов. Следовательно, выбор ключа сортировки должен соответствовать основным шаблонам бизнес-запросов.

  • Если требуется точечный поиск по одному устройству, ключом сортировки должен быть идентификатор устройства (vin) в контексте временного ряда.
  • Если требуется детальный запрос, агрегация или запрос по нескольким устройствам в заданном временном диапазоне для одного устройства, ключом сортировки должен быть (vin, daq_time).

Рекомендуется выбирать поле уникального идентификатора субъекта данных. Если доступна метка времени, добавьте её перед полем идентификатора, чтобы сформировать ключ сортировки. В примере ключ сортировки — (vin, daq_time).

COLLATE "C"

Эта опция должна применяться только к полю кодировки устройства; другие поля в ней не нуждаются. Использование этой опции улучшает скорость сортировки и выполнения запросов для текстовых столбцов.

USING MARS3

Это фиксированный синтаксис, необходимый для создания таблицы MARS3. Не изменяйте это содержимое.

Параметры WITH (...)

Пример: WITH (compresstype=zstd, compresslevel=3, compress_threshold=1200, mars3options='rowstore_size=64, nbuckets = 2')

  • compresstype=zstd, compresslevel=3: Для этой таблицы рекомендуется использовать сжатие zstd с уровнем сжатия 3. В примере используется универсальный алгоритм сжатия. Для пользовательских схем сжатия см. Using Compression.
  • rowstore_size=64: Устанавливает размер L0 Run в 64 МБ. Порог сжатия установлен в 1200 строк.
  • nbuckets = 2: Устанавливает количество bucket в 2. Допустимые значения nbuckets — от 1 до 128. Значение по умолчанию — 1, что означает отсутствие bucketing. Подробности использования см. в разделе MARS3 Bucket Best Practices.

Примечание!
Параметр nbuckets применяется ко всем стратегиям распределения. Даже при случайном распределении bucketing может выполняться внутри segments.

Примечание!
Хотя значение nbuckets можно изменить после создания, изменение этого параметра вызывает полную перезапись таблицы. Чтобы предотвратить случайную потерю данных, текущая реализация предупреждает пользователя о необходимости перезаписи. Для фактического выполнения перезаписи необходимо включить специальный параметр GUC.

DISTRIBUTED BY (vin)

Используйте этот пункт для выбора ключа распределения. Ключ распределения определяет, как данные распределяются по узлам данных (Segments), что существенно влияет на производительность. Основные критерии выбора:

  1. Минимизация перекоса данных: Обеспечьте равномерное распределение данных для максимального использования параллельного выполнения запросов.
  2. Оптимизация соединений: При проектировании соединений между несколькими таблицами на этапе начального проектирования выравнивайте ключи соединения с ключами распределения, чтобы избежать ненужного перемещения данных.

В примере в качестве ключа распределения используется идентификатор устройства (vin). Это позволяет выполнять запросы к данным одного устройства локально, избегая накладных расходов на перераспределение данных между узлами.

PARTITION BY RANGE (daq_time)

Этот пункт указывает ключ секционирования для секционированной таблицы.

Рекомендуется использовать время в качестве ключа секционирования по двум причинам:

  1. Это позволяет автоматически удалять устаревшие данные при использовании Автоматического управления секциями.
  2. Это позволяет использовать pruning секций для фильтрации нерелевантных временных секций.

Размер секции должен составлять от одного до десяти миллионов строк. Слишком малое количество строк на секцию приводит к чрезмерному потреблению памяти для управления метаданными, а слишком большое — снижает эффективность pruning и усложняет удаление устаревших данных.

В примере в качестве ключа секционирования используется время сбора данных (daq_time). Большинство запросов включают фильтрацию по этому времени. Например, для запроса данных за последний день добавьте условие WHERE daq_time >= CURRENT_DATE - INTERVAL '1 day'. База данных быстро определит соответствующие подтаблицы, найдёт данные и эффективно выполнит запрос.

START ... END ... EVERY ... DEFAULT PARTITION OTHERS

  • START ('2022-07-01 00:00:00') INCLUSIVE END ('2022-08-01 00:00:00') EXCLUSIVE: Определяет диапазон для начальной секции: с 1 июля 2022 года в полночь (включительно) до 1 августа 2022 года в полночь (исключительно).
  • EVERY (INTERVAL '1 day'): Подсекции имеют временной интервал в один день. В зависимости от объёма данных также можно использовать интервалы hour, month или year.
    • Если сервер получает от 1 до 10 миллионов записей в день, оптимальным будет интервал 1 day.
    • Если ежедневный объём составляет сотни тысяч записей, подходит интервал 1 month.
    • Если ежедневный объём менее 10 000 записей, достаточно интервала 1 year.
  • DEFAULT PARTITION OTHERS: Определяет секцию по умолчанию. Все данные, выходящие за пределы явно заданных диапазонов, будут сохранены здесь.

Достижение оптимальной производительности запросов с помощью VACUUM / VACUUM FULL

  • Функциональность: Выполнение VACUUM преобразует неупорядоченные Runs на основе строк в упорядоченные Runs на основе столбцов. VACUUM FULL выполняет действия VACUUM и дополнительно объединяет несколько Runs в один, обеспечивая превосходную производительность запросов. Однако VACUUM FULL требует эксклюзивной блокировки целевой таблицы, запрещая одновременный доступ.
  • Время выполнения:
    • При непрерывной загрузке данных в таблицы MARS3 операции очистки обычно не требуются немедленно.
    • После стабилизации данных выполните операции очистки перед запуском запросов.
    • После значительных изменений данных в базе данных или конкретной таблице немедленно запустите операции очистки после обновления.

Режим уникальности для сценариев пакетной передачи данных

Если устройства передают данные пакетами с одинаковой меткой времени, MARS3 может объединять дублирующиеся записи (одинаковые daq_time и vin).

Чтобы включить эту функцию, необходимо вручную указать uniquemode=true при создании таблицы, так как значение по умолчанию — false.

  • При uniquemode=true: Если устройство 'A01' отправляет три записи в '2022-01-01 00:00:00', система сохраняет только последнюю запись, перезаписывая две предыдущие.
  • При uniquemode=false (по умолчанию): Все три записи сохраняются без изменений.

Примечание!
Если включён Unique Mode, первый столбец в предложении ORDER BY должен быть определён с ограничением NOT NULL. Текущая реализация Unique Mode в MARS3 не поддерживает удаление.

Пример создания таблицы с Unique Mode:

=# CREATE TABLE vehicle_basic_data_mars3_um(
  daq_time timestamp ,
  vin varchar(32)  COLLATE "C" NOT NULL,
  lng float ,
  lat float ,
  speed float ,
  license_template varchar(16) ,
  flag integer
)
USING MARS3
WITH (compresstype=zstd, compresslevel=3, compress_threshold=1200, uniquemode=true,
        mars3options='rowstore_size=64')
DISTRIBUTED BY (vin)
ORDER BY (vin, daq_time)
PARTITION BY RANGE (daq_time)
( START ('2022-07-01 00:00:00') INCLUSIVE
  END ('2022-08-01 00:00:00') EXCLUSIVE
  EVERY (INTERVAL '1 day')
,DEFAULT PARTITION OTHERS);

Рекомендации по использованию MARS3 Bucket

Подробнее о технических аспектах см. MARS3 Bucket Technical Principles.

Основное преимущество режима Bucket заключается в сохранении характеристик распределения данных при параллельном сканировании. Благодаря бакет-ориентированному параллельному сканированию исполнитель может избежать ненужных операций Motion, основываясь на свойствах данных.

Значение по умолчанию: 1
Допустимые значения: от 1 до 128
Рекомендуемые значения:

  • Максимальное значение не должно превышать (общее число ядер CPU на узле / количество segments на узле).
  • Убедитесь, что размер каждого бакета не слишком мал (< 2 МБ). Учитывайте это совместно с размером RowStore.

Примеры использования:

  • Метод 1 (глобальная настройка): задайте глобально с помощью команды set mars3.default_storage_options='mars3options="nbuckets=8"';.
  • Метод 2 (создание таблицы): используйте create table foo (c1 int) using mars3 with (mars3options='nbuckets=3');. Эта настройка переопределяет глобальное значение из метода 1.
  • Метод 3 (изменение таблицы): используйте alter table foo set (mars3options='nbuckets=5');. Не рекомендуется, так как это вызывает полную перезапись таблицы.

Примечания по использованию:

В YMatrix распределение данных таблицы определяется методом распределения. Например, в таблице с распределением HASH для данных вычисляется хеш-значение. Затем оно сопоставляется с конкретным Segment с помощью операции по модулю или согласованного хеширования перед записью. MARS3 поддерживает дополнительный механизм бакетинга внутри таблицы. Когда nbuckets > 1, система вычисляет hash_value % nbuckets и помещает данные в соответствующий бакет.

Поведение параллельного сканирования по умолчанию в режиме Bucket показано ниже:

bucket: 0 1 2 3 4      bucket: 0 1 2 3 4

Worker 0 reads: bucket {0, 3}
Worker 1 reads: bucket {1, 4}
Worker 2 reads: bucket {2}

Чтобы использовать устаревший режим параллельного сканирования для таблицы с Bucket, отключите параметр mx_enable_bucket_parallel_mode.

Важные предупреждения:

  • Рекомендуется указывать nbuckets только для больших таблиц, требующих параллельного сканирования.
  • Функции uniquemode и continuous view не поддерживают режим Bucket. Указание nbuckets для таких объектов не оказывает никакого эффекта.
  • Для таблиц, не использующих HASH-распределение, режим Bucket не имеет практического эффекта: все данные будут помещены в bucket 0.
  • Если параллельное сканирование отключено, информация о сортировке больше не предоставляется.

2. Таблицы HEAP

Таблицы HEAP являются движком хранения по умолчанию в YMatrix. Если вы не укажете явно движок хранения при создании таблицы, система по умолчанию создаст таблицу HEAP.

=# CREATE TABLE disk_heap(
    time timestamp with time zone,
    tag_id int,
    read float,
    write float
)
DISTRIBUTED BY (tag_id);

3. Таблицы AORO

Таблицы AORO — это append-only таблицы с ориентацией на строки.
AORO поддерживает сжатие на уровне таблицы, но не поддерживает сжатие на уровне столбца.

=# CREATE TABLE disk_aoro(
    time timestamp with time zone,
    tag_id int,
    read float,
    write float
)
WITH(appendonly=true, orientation=row, compresstype=zlib, compresslevel=4)
DISTRIBUTED BY (tag_id);

Рекомендации: выбор между HEAP и AO

Рекомендации по выбору между таблицами HEAP и AO следующие:

  • Используйте движок хранения HEAP для таблиц и секций, которые часто обновляются, удаляются или в которые выполняются вставки по одной строке.
  • Используйте движок хранения HEAP для таблиц и секций, подверженных одновременным обновлениям, удалениям и вставкам.
  • Используйте движок хранения AO для таблиц и секций, которые изначально загружаются и редко обновляются в дальнейшем, а последующие вставки выполняются только пакетными операциями. Никогда не выполняйте одиночные обновления, удаления или вставки по одной строке в таблицах AO. Поддерживаются одновременные пакетные вставки, но одновременные пакетные обновления или удаления не разрешены.
  • Пространство, занимаемое обновлёнными или удалёнными строками в таблицах AO, не освобождается и не переиспользуется так эффективно, как в таблицах HEAP. Поэтому движок хранения AO не подходит для часто обновляемых таблиц. Его цель — поддержка крупных таблиц с однократной загрузкой, редкими обновлениями и частыми аналитическими запросами.

Рекомендации по распределению данных

Хеш-распределение (HASH)

  • Оператор создания таблицы
    CREATE TABLE finance_voucher_main (
        subject_id INT NOT NULL PRIMARY KEY,
        company_id INT NOT NULL ,
        voucher_date DATE NOT NULL,
        amount DECIMAL(18,2) NOT NULL,
        currency_type VARCHAR(3) DEFAULT 'CNY',
        create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
        update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
    ) 
    USING MARS3
    DISTRIBUTED BY (company_id,subject_id);
  • Просмотр структуры таблицы
    postgres=# \d+ finance_voucher_main
                                                      Table "public.finance_voucher_main"
         Column     |            Type             | Collation | Nullable |         Default          | Storage  | Stats target | Description
    ---------------+-----------------------------+-----------+----------+--------------------------+----------+--------------+-------------
      subject_id    | integer                     |           | not null |                          | plain    |              |
      company_id    | integer                     |           | not null |                          | plain    |              |
      voucher_date  | date                        |           | not null |                          | plain    |              |
      amount        | numeric(18,2)               |           | not null |                          | main     |              |
      currency_type | character varying(3)        |           |          | 'CNY'::character varying | extended |              |
      create_time   | timestamp without time zone |           |          | CURRENT_TIMESTAMP        | plain    |              |
      update_time   | timestamp without time zone |           |          | CURRENT_TIMESTAMP        | plain    |              |
    Indexes:
        "finance_voucher_main_pkey" PRIMARY KEY, mars3_btree (subject_id)
    Distributed by: (company_id, subject_id)
    Access method: mars3

Случайное распределение (RANDOM)

  • Оператор создания таблицы
    CREATE TABLE etl_finance_temp (
        subject_id INT NOT NULL PRIMARY KEY,
        company_id INT NOT NULL ,
        voucher_date DATE NOT NULL,
        amount DECIMAL(18,2) NOT NULL,
        currency_type VARCHAR(3) DEFAULT 'CNY',
        create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
        update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
    ) 
    USING MARS3
    DISTRIBUTED RANDOMLY;
  • Просмотр структуры таблицы
    postgres=# \d+ etl_finance_temp
                                                        Table "public.etl_finance_temp"
         Column     |            Type             | Collation | Nullable |         Default          | Storage  | Stats target | Description
    ---------------+-----------------------------+-----------+----------+--------------------------+----------+--------------+-------------
      subject_id    | integer                     |           | not null |                          | plain    |              |
      company_id    | integer                     |           | not null |                          | plain    |              |
      voucher_date  | date                        |           | not null |                          | plain    |              |
      amount        | numeric(18,2)               |           | not null |                          | main     |              |
      currency_type | character varying(3)        |           |          | 'CNY'::character varying | extended |              |
      create_time   | timestamp without time zone |           |          | CURRENT_TIMESTAMP        | plain    |              |
      update_time   | timestamp without time zone |           |          | CURRENT_TIMESTAMP        | plain    |              |
    Indexes:
        "etl_finance_temp_pkey" PRIMARY KEY, mars3_btree (subject_id)
    Distributed randomly
    Access method: mars3

Реплицированное распределение (REPLICATED)

  • Оператор создания таблицы
    CREATE TABLE dict_subject (
        subject_id INT NOT NULL PRIMARY KEY,
        company_id INT NOT NULL ,
        voucher_date DATE NOT NULL,
        amount DECIMAL(18,2) NOT NULL,
        currency_type VARCHAR(3) DEFAULT 'CNY',
        create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
        update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
    ) 
    USING MARS3
    DISTRIBUTED REPLICATED;
  • Просмотр структуры таблицы
    postgres=# \d+ dict_subject
                                                          Table "public.dict_subject"
         Column     |            Type             | Collation | Nullable |         Default          | Storage  | Stats target | Description
    ---------------+-----------------------------+-----------+----------+--------------------------+----------+--------------+-------------
      subject_id    | integer                     |           | not null |                          | plain    |              |
      company_id    | integer                     |           | not null |                          | plain    |              |
      voucher_date  | date                        |           | not null |                          | plain    |              |
      amount        | numeric(18,2)               |           | not null |                          | main     |              |
      currency_type | character varying(3)        |           |          | 'CNY'::character varying | extended |              |
      create_time   | timestamp without time zone |           |          | CURRENT_TIMESTAMP        | plain    |              |
      update_time   | timestamp without time zone |           |          | CURRENT_TIMESTAMP        | plain    |              |
    Indexes:
        "dict_subject_pkey" PRIMARY KEY, mars3_btree (subject_id)
    Distributed Replicated
    Access method: mars3

Итог: рекомендации по проектированию таблиц и распределению данных

Движки хранения и стратегии распределения данных YMatrix предоставляют гибкие и эффективные решения для различных бизнес-сценариев.

Пользователи могут делать выбор на основе нескольких измерений: «Тип бизнеса (OLAP/OLTP/гибридный) + Характеристики данных (объём/частота обновлений) + Требования к запросам (измерения/методы соединения)»:

  • Выбор движка хранения: отдавайте предпочтение движку MARS3 для гибридных сценариев; выбирайте таблицы AO для чисто аналитических нагрузок; используйте HEAP для транзакционных нагрузок.
  • Стратегия распределения: применяйте хеш-распределение для основных бизнес-таблиц, реплицированное распределение — для небольших справочных таблиц и случайное распределение — для временных таблиц.

Благодаря научному подходу к выбору вы сможете максимально раскрыть производительность кластера YMatrix и добиться эффективного взаимодействия между хранением данных и обработкой запросов.