Обзор движка хранения


1 Обзор MARS3

Движок хранения MARS3 построен на основе MARS2 с рядом оптимизаций. Он использует гибридный формат хранения строк-столбцов, что обеспечивает как высокую скорость ввода данных, так и агрессивное сжатие.

MARS3 поддерживает обновление и удаление данных с помощью ключевых слов UPDATE и DELETE (за исключением режима Unique).

MARS3 поддерживает добавление и удаление столбцов, а также операции COPY и pg_dump.

1.1 Внутренняя архитектура

Каждая таблица MARS3 использует внутреннюю структуру LSM-дерева (Log-Structured Merge Tree). LSM-дерево — это многоуровневая, упорядоченная, ориентированная на диск структура данных. Её основная идея заключается в использовании производительности диска за счёт пакетных последовательных записей, что значительно превосходит производительность случайных записей.

Внутренняя архитектура MARS3 представлена ниже:

Рассмотрим эту диаграмму по уровням.

1.1.1 Ключ сортировки

  • В MARS3 данные хранятся в отсортированном порядке. При создании таблицы необходимо указать один или несколько столбцов сортировки для определения порядка. Эти столбцы называются ключом сортировки.
  • Ключ сортировки может быть задан только один раз. Его нельзя изменять, добавлять или удалять после создания.
  • Для максимального использования преимуществ упорядочивания данных выбирайте в качестве ключа сортировки часто запрашиваемые столбцы с высокой селективностью фильтрации. Например, в таблице мониторинга устройств используйте временну́ю метку события и идентификатор устройства в качестве ключа сортировки.
  • Если ключ сортировки имеет текстовый тип и допустимо байтовое упорядочивание, применение COLLATE C к этому столбцу ускорит сортировку.
  • Ключевое слово SQL для указания ключа сортировки: ORDER BY.

1.1.2 Rowstore и Columnstore

  • MARS3 поддерживает хранение данных в формате сначала строки, затем столбцы, либо непосредственно в формате columnstore. Режим загрузки задаётся параметром prefer_load_mode. См. Параметры конфигурации ниже.
  • В гибридном режиме входящие данные сначала записываются в формате rowstore. После накопления достаточного объёма они преобразуются в формат columnstore.
  • По сравнению с прямой загрузкой в columnstore, этот подход имеет ряд преимуществ:
    • Более высокая производительность записи при частых, мелких вставках
    • Меньшее потребление памяти для буферизации
    • Обеспечение равномерного количества кортежей в каждом блоке данных

1.1.3 Run, delta-файлы и метаданные

  • На основе ключа сортировки данные в MARS3 хранятся в отсортированном порядке. Непрерывный сегмент отсортированных данных называется Run.
  • Runs бывают двух типов: для быстрого ввода данных вставленные данные сначала хранятся как rowstore Run; позже, для эффективного чтения и сжатия, они преобразуются в columnstore Run.
  • Каждый Run имеет собственный набор delta-файлов. Помимо основных данных (Data file), существуют Toast-файлы для больших значений, Delta-файлы для записей удаления, Index-файлы для индексации и Link-файлы для отслеживания слияний (типы delta-файлов немного различаются между rowstore и columnstore Runs).
  • Кроме того, поддерживается метаданные для отслеживания расположения файлов: пути к файлам, их размеры, количество кортежей и статус сжатия.
  • Размер Run (настраивается через параметр rowstore_size) можно оптимизировать под различные рабочие нагрузки.

1.1.4 Слияние и сборка мусора

  • Перекрывающиеся диапазоны данных между Runs вызывают усилённое чтение и снижают производительность запросов. Когда количество Runs на диске превышает пороговое значение, MARS3 выполняет слияние: несколько Runs сортируются и объединяются в один Run. Этот процесс называется слиянием.
  • Во время слияния данные остаются доступными для чтения и записи:
    • Чтения используют только входные файлы слияния
    • Записи не мешают процессу слияния
    • Операции чтения, записи и слияния не блокируют друг друга
  • После завершения слияния исходные Runs помечаются как устаревшие на основе идентификаторов транзакций и отмечаются для сборки мусора.

1.1.5 Уровни

  • Для обеспечения схожести размеров входных файлов при слиянии (избегая слияния больших и малых файлов) Runs организованы в уровни, до 10 уровней: L0, L1, L2, ..., L9.
  • Когда количество Runs на уровне достигает порога или их суммарный размер превышает лимит, они сливаются в один Run и продвигаются на следующий более высокий уровень.
  • В рамках одного уровня разрешено одновременное выполнение нескольких задач слияния для ускорения продвижения Runs.

1.1.6 BRIN-индекс MARS3

  • MARS3 поддерживает создание, удаление и добавление BRIN-индексов.
  • Каждый Run при создании генерирует собственный независимый файл BRIN-индекса.
  • Пример: CREATE INDEX brin_idx ON t1 USING mars3_brin(time,tag_id);

1.1.7 Сжатие

  • По умолчанию все столбцы в MARS3 сжимаются с использованием lz4.
  • Можно задавать пользовательские цепочки кодеков сжатия на уровне таблицы или отдельного столбца.

1.1.8 Поддержка MVCC

  • MVCC (Multi-Version Concurrency Control), также известная как многоверсионное управление, в основном обрабатывает обновления, изменения и удаления данных.
  • В многоверсионном управлении обновления и удаления не изменяют данные на месте. Вместо этого создаётся новая версия, старые данные помечаются как устаревшие, а новые данные добавляются в новую версию. Данные существуют в нескольких версиях, каждая из которых имеет метаданные версии, и исторические версии сохраняются.
  • MARS3 не выполняет ин-плас обновления или удаления. Вместо этого он использует Delta-файлы и видимость версий для маскировки старых данных.
  • Примечание: Постоянное обновление или удаление данных внутри одного Run увеличивает физический размер его Delta-файла. Этот рост прекращается, когда все данные в Run удалены. MARS3 автоматически удаляет Dead данные во время слияний. Также можно периодически выполнять VACUUM для ручной очистки Dead данных.

1.1.9 Ввод данных

  • Данные сначала записываются в память через INSERT, затем сбрасываются в Run уровня L0.
  • Размер Runs в L0 настраивается. См. Параметры конфигурации ниже.

1.1.10 Обновление и удаление

  • MARS3 использует DELETE для удаления. Удаления записываются в Delta-файл соответствующего Run и физически удаляются во время слияния.
  • MARS3 использует UPDATE для обновления. Обновление сначала удаляет старую строку, а затем вставляет новую.
  • В режиме Unique удаление не поддерживается. Обновления не требуют явного указания UPDATE; достаточно просто использовать INSERT, что автоматически выполнит операцию. Чтобы обновить строку с определённым уникальным ключом (т.е. значением ключа сортировки, заданного при создании таблицы), вставьте новую строку с тем же уникальным ключом. Например: CREATE TABLE mars3_t(c1 int NOT NULL, c2 int) USING MARS3 WITH (uniquemode=true) ORDER BY (c1, c2);, где уникальный ключ — (c1, c2).

Примечание!
При включении режима Unique первое поле в предложении ORDER BY должно иметь ограничение NOT NULL.

1.2 Использование MARS3

1.2.1 Создание таблицы MARS3

После создания расширения matrixts простейший способ создать таблицу MARS3 — добавить предложение CREATE TABLE с USING и ORDER BY. Для расширенных примеров см. Лучшие практики проектирования таблиц.

=# CREATE TABLE metrics (
    ts              timestamp,
    dev_id          bigint,
    power           float,
    speed           float,
    message         text
) USING MARS3 
  ORDER BY (dev_id,ts);

Примечание!
Таблицы MARS3 поддерживают BRIN-индексы, но они не обязательны. Однако при создании таблицы MARS3 обязательно необходимо использовать предложение ORDER BY для определения ключа сортировки.


1.2.2 Параметры конфигурации

Примечание!
Это параметры на уровне таблицы. Они могут быть заданы только при создании таблицы с помощью предложения WITH(mars3options='a=1,b=2,...') (за исключением compress_threshold, который также поддерживается MARS2 и может быть задан напрямую через WITH). После установки их нельзя изменить. Эти параметры применяются к одной таблице. Подробнее см. Параметры таблиц базы данных.

Следующий параметр управляет размером Runs в L0, косвенно влияя на размеры Runs на более высоких уровнях.

Параметр Единица Значение по умолчанию Диапазон Описание
rowstore_size МБ 64 8 ~ 1024 Управляет моментом переключения L0 Run. Новый Run создаётся, когда объём данных превышает это значение.

Следующий параметр задаёт порог сжатия, балансируя эффективность сжатия и производительность чтения. Слишком низкое значение снижает эффективность сжатия; слишком высокое увеличивает потребление памяти.

Параметр Единица Значение по умолчанию Диапазон Описание
compress_threshold Кортежи 1200 1 ~ 100000 Порог сжатия. Максимальное количество кортежей в одном столбце, сжимаемых за одну партию.

Следующий параметр определяет режим загрузки данных в MARS3.

Параметр Единица Значение по умолчанию Диапазон Описание
prefer_load_mode normal normal / bulk Режим загрузки данных. normal означает нормальный режим: новые данные сначала записываются в rowstore Runs уровня L0, затем перемещаются в columnstore Runs уровня L1 после достижения rowstore_size. Это требует дополнительного I/O по сравнению с bulk, а преобразование в столбцы выполняется асинхронно. Подходит для частых, мелких записей, когда пропускная способность I/O достаточна, а задержка критична. bluk означает режим массовой загрузки: данные записываются непосредственно в columnstore Runs уровня L1. Это снижает I/O по сравнению с normal, а преобразование в столбцы выполняется синхронно. Подходит для редких, крупных пакетных записей, когда пропускная способность I/O ограничена, а задержка менее критична.

Следующий параметр задаёт коэффициент увеличения размера уровня.

Параметр Единица Значение по умолчанию Диапазон Описание
level_size_amplifier 8 1 ~ 1000 Коэффициент увеличения размера уровня. Порог для запуска слияния уровня: rowstore_size * (level_size_amplifier ^ level). Более высокое значение замедляет чтение, но ускоряет запись. Выбирайте на основе рабочей нагрузки (запись vs чтение, коэффициент сжатия и т.д.). Убедитесь, что количество Runs на уровне не становится слишком большим, иначе это может ухудшить производительность запросов или заблокировать новые вставки.

Пример конфигурации:

=# CREATE TABLE metrics (
    ts              timestamp,
    dev_id          bigint,
    power           float,
    speed           float
) USING MARS3
WITH (compress_threshold=1200,mars3options='rowstore_size=64',compresstype=zstd, compresslevel=1)
DISTRIBUTED BY (dev_id)
ORDER BY (dev_id,ts)
PARTITION BY RANGE (ts)
( START ('2023-07-01 00:00:00') INCLUSIVE
  END ('2023-08-01 00:00:00') EXCLUSIVE
  EVERY (INTERVAL '1 day')
,DEFAULT PARTITION OTHERS);

1.2.3 Вспомогательные функции

  • matrixts_internal.mars3_level_stats: Просмотр состояния каждого уровня в таблице MARS3. Полезно для оценки состояния таблицы, например, выполняется ли слияние Runs по ожиданиям и находится ли количество Runs в пределах нормы.
  • matrixts_internal.mars3_files: Просмотр состояния файлов таблицы MARS3, включая вспомогательные и delta-файлы (Data, Delta, Index и т.д.), для проверки ожидаемого состояния файлов.
  • matrixts_internal.mars3_info_brin: Просмотр состояния конкретного BRIN-индекса на таблице MARS3.


2 Обзор MARS2

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

2.1 Внутренняя архитектура

Как и MARS3, MARS2 использует структуру LSM-дерева для хранения.

Внутренняя архитектура MARS2 представлена ниже:

Рассмотрим эту диаграмму по уровням.

2.1.1 Ключ сортировки

  • В MARS2 данные хранятся в отсортированном порядке. При создании таблицы порядок сортировки определяется созданием индекса. Столбцы, участвующие в этом порядке, называются ключом сортировки.
  • Ключ сортировки может быть задан только один раз и не может быть изменён или удалён.
  • Для максимального использования преимуществ упорядочивания данных выбирайте в качестве ключа сортировки часто используемые столбцы с высокой селективностью. Например, в таблице мониторинга устройств используйте временную метку события и идентификатор устройства.
  • Если ключ сортировки имеет текстовый тип и допустимо байтовое упорядочивание, применение COLLATE C к этому столбцу ускорит сортировку.

2.1.2 Run и метаданные

  • На основе ключа сортировки данные в MARS2 хранятся в отсортированном порядке. Непрерывный сегмент отсортированных данных называется Run.
  • Метаданные записываются для отслеживания расположения каждого Run.
  • Метаданные также содержат минимальные и максимальные значения Run, что позволяет эффективно фильтровать данные при запросах.
  • При вставке данные сортируются в памяти, поэтому размер Run ограничен доступной памятью для сортировки.

2.1.3 Слияние

  • Перекрывающиеся диапазоны данных между Runs вызывают усилённое чтение и снижают эффективность запросов. Когда количество Runs на диске превышает пороговое значение, MARS2 загружает несколько Runs в память, сортирует их и выводит один Run. Это называется слиянием.
  • Во время слияния данные остаются доступными для чтения и записи:
    • Чтения используют только входные файлы слияния
    • Записи не мешают процессу слияния
    • Операции чтения, записи и слияния не блокируют друг друга

2.1.4 Уровни

  • Для обеспечения схожести размеров входных файлов при слиянии (избегая слияния больших и малых файлов) Runs организованы в 3 уровня: L0, L1, L2.
  • Когда объединённый Run превышает порог размера, он продвигается на более высокий уровень:
    1. Новые вставки направляются в L0. Когда количество Runs достигает порога (настраивается через mars2_automerge_threshold), запускается слияние L0, объединяющее все Runs L0 в один.
    2. Если результирующий Run превышает 25 МБ (настраивается через level0_upgrade_size), он продвигается в L1.
    3. Если суммарный размер Runs L1 превышает 1000 МБ (настраивается через level1_upgrade_size), запускается слияние L1, объединяющее все Runs L1 в один.
    4. Если результирующий Run превышает 1000 МБ (настраивается через level1_upgrade_size), он продвигается в L2.
    5. Runs в L2 не сливаются дальше.

2.1.5 Columnstore

  • MARS2 использует формат columnstore. При доступе к диску читаются только необходимые столбцы, что снижает I/O.
  • Данные в одном столбце имеют однородный тип, что позволяет добиться лучшего сжатия и экономии дискового пространства.
  • Columnstore также оптимизирован для векторизованного выполнения, что значительно ускоряет производительность запросов. См. Движок векторизованного выполнения.

2.1.6 Фильтрация MINMAX

  • Как упоминалось, метаданные Run хранят минимальные и максимальные значения для фильтрации запросов.
  • Поскольку фильтрация использует метаданные без загрузки фактических данных, накладные расходы I/O ниже, чем при последовательном сканировании (которое сначала загружает данные, а затем фильтрует), что обеспечивает более быструю фильтрацию.
  • MINMAX по умолчанию отключён в MARS2. Его необходимо включить явно.

Сначала создайте расширение matrixts:

=# CREATE EXTENSION matrixts ;

Затем явно включите MINMAX:

=# CREATE TABLE metrics (
    ts              timestamp   ENCODING(minmax),
    dev_id          bigint      ENCODING(minmax),
    power           float,
    speed           float,
    message         text
) USING MARS2;

Создайте индекс mars2_btree:

=# CREATE INDEX ON metrics
USING mars2_btree (ts, dev_id);
  • MINMAX может быть задан как для ключей сортировки, так и для не-ключей. Наиболее эффективен на ключах сортировки. MINMAX может записываться как в верхнем, так и в нижнем регистре.
  • Максимум 32 столбца могут иметь включённый MINMAX.

2.1.7 Btree-индекс MARS2

  • Помимо MINMAX, MARS2 имеет встроенный индекс (создаётся при создании таблицы).
  • Можно определить только один индекс на таблицу, то есть разрешён только один глобальный порядок сортировки.
  • В отличие от стандартных Btree-индексов, Btree-индекс MARS2:
    • Является разреженным, поэтому занимает меньше места
    • Избегает случайных I/O при сканировании индекса, поскольку базовые данные уже отсортированы

2.1.8 Сжатие

То же, что и в MARS3.

  • По умолчанию все столбцы сжимаются с использованием lz4.
  • Можно задать пользовательские цепочки кодировок сжатия на уровне таблицы или столбца.

2.1.9 Ввод данных

  • Данные записываются в память через INSERT, затем обрабатываются в Run.
  • Если вводимые данные превышают объем памяти для сортировки, создаётся несколько Run.
  • Размер памяти для сортировки настраивается. См. «2.2.2 Параметры конфигурации».

2.1.10 Обновление и удаление

  • MARS2 в настоящее время не поддерживает операции обновления и удаления.
  • Обновления можно эмулировать с помощью режима Unique.
  • Удаление возможно только путём удаления таблиц или использования TRUNCATE на партиционированных таблицах.

2.2 Использование MARS2

2.2.1 Создание таблицы MARS2

После создания расширения matrixts простейший способ создать таблицу MARS2 — использовать ключевое слово USING в CREATE TABLE и создать индекс. Подробные примеры см. в разделе Лучшие практики проектирования таблиц.

=# CREATE TABLE metrics (
    ts              timestamp,
    dev_id          bigint,
    power           float,
    speed           float,
    message         text
) USING MARS2;

=# CREATE INDEX ON metrics
USING mars2_btree (ts, dev_id);

2.2.2 Параметры конфигурации

Примечание!
Параметры на уровне таблицы можно задавать только при создании таблицы с помощью ключевого слова WITH, они применяются к одной таблице и не могут быть изменены позже. Глобальные параметры можно задавать на уровне сессии или системы. Изменения на уровне системы требуют mxstop -u для применения. Подробнее см. Параметры таблиц базы данных.

Следующие параметры управляют поведением слияния (см. раздел «Уровни» выше).

Параметр управления слиянием Единица Значение по умолчанию Диапазон Описание
mars2_automerge_threshold run 32 10 - 2048 Управляет всеми таблицами MARS2: количество L0 Run, при достижении которого запускается слияние. Глобальный параметр. Переопределение на уровне таблицы: level0_merge_threshold
level0_merge_threshold run 32 1 - 2048 Управляет порогом слияния L0 для каждой таблицы. Параметр на уровне таблицы
level0_upgrade_size MB 25 1 - 10000 Управляет размером перехода L0 → L1 для каждой таблицы. Если объединённый Run превышает этот порог, он повышается до L1. Параметр на уровне таблицы
level1_upgrade_size MB 1000 1 - 10000 Управляет размером перехода L1 → L2 для каждой таблицы. Если объединённый Run превышает этот порог, он повышается до L2. Параметр на уровне таблицы
  • Если частые небольшие вставки (каждая создаёт Run) вызывают частые слияния, что неэффективно, увеличьте mars2_automerge_threshold / level0_merge_threshold, чтобы снизить частоту слияний.
  • Если объединённые Run в L0/L1 не достигают порога повышения уровня, они позже объединяются повторно, что вызывает усилительную запись. Чтобы избежать этого, уменьшите level0_upgrade_size / level1_upgrade_size, чтобы раньше повышать Run.

Следующий параметр управляет поведением сжатия.

Параметр управления сжатием Единица Значение по умолчанию Диапазон Описание
compress_threshold Tuples 1200 1 - 100000 Порог сжатия. Количество кортежей на таблицу, сжимаемых за одну партию. Максимальное количество кортежей в единице сжатия. Параметр на уровне таблицы

Следующие параметры управляют памятью для сортировки. При вставке в несколько партиционированных таблиц каждая получает mars2_sort_mem_core; общее потребление не превышает mars2_sort_mem.

Параметр памяти для сортировки Единица Значение по умолчанию Диапазон Описание
mars2_sort_mem KB 2097152KB (2 ГБ) 128KB - 2147483647KB (~2048 ГБ) Память для сортировки на одну вставку. Для партиционированных таблиц — разделяется между партициями. Глобальный параметр
mars2_sort_mem_core KB 16384KB (16 МБ) 128KB - 2147483647KB (~2048 ГБ) Минимальная память для сортировки на одну партиционированную таблицу. Глобальный параметр

Пример конфигурации на уровне таблицы:

=# CREATE TABLE metrics (
    ts              timestamp,
    dev_id          bigint,
    power           float,
    speed           float,
    message         text
) 
USING MARS2
WITH (compress_threshold=1200,level0_merge_threshold=32);

=# CREATE INDEX ON metrics
USING mars2_btree (ts, dev_id);

Пример глобального параметра на уровне сессии:

=# SET mars2_sort_mem TO 2097152;

Пример глобального параметра на уровне системы:

=# gpconfig -c mars2_sort_mem -v 2097152
=# \q
$ mxstop -u


3 Обзор HEAP

HEAP — это стандартный движок хранения в YMatrix, унаследованный от PostgreSQL. Он поддерживает только формат rowstore и не поддерживает columnstore или сжатие. Использует MVCC и подходит для рабочих нагрузок с частыми обновлениями и удалениями.

3.1 Механизм MVCC

При использовании MVCC таблицы HEAP не физически удаляют данные при обновлениях или удалениях. Вместо этого старые данные маскируются с помощью видимости версий. В результате частые обновления и удаления увеличивают объём физического хранилища. Рекомендуется периодически выполнять VACUUM для освобождения места.

3.2 Использование HEAP

Для создания таблицы HEAP в YMatrix используйте следующий SQL:

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


4 Обзор AO

Таблицы, использующие движки хранения AOCO или AORO, совокупно называются AO-таблицами (Append-Optimized). Они поддерживают операции вставки, обновления и удаления, а также сжатие.
AORO поддерживает rowstore; AOCO поддерживает columnstore.

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

Для AO-таблиц с частыми обновлениями и удалениями требуется регулярная очистка. Инструмент очистки vacuum должен сбрасывать битмап и сжимать физические файлы, что обычно медленнее, чем очистка HEAP через vacuum.

Примечание!
Для подробной информации о движках хранения, их использовании и лучших практиках см. Лучшие практики проектирования таблиц.