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

Обзор MARS3

MARS3 — это проприетарный движок хранения на основе LSM-дерева, разработанный компанией YMatrix. Он использует архитектуру гибридного строчно-колоночного хранения. В дополнение к традиционной модели LSM, MARS3 внедряет двухэтапный путь записи «сначала строки, затем столбцы». Такой подход наследует преимущества строкового хранения для операций записи и сохраняет высокопроизводительные аналитические возможности колоночного хранения.

Ключевые особенности:

  • Сжатие с помощью цепочек кодирования (encoding chain).
  • Поддержка обновлений и удалений данных.
  • Управление многоверсионным параллелизмом (MVCC).
  • Индексация BRIN.
  • Гибридное строчно-колоночное хранение.

MARS3 разработан для удовлетворения требований как аналитической обработки (AP), так и транзакционной обработки (TP).

Движок поддерживает операции обновления и удаления через cláusулы UPDATE (за исключением режима Unique) и DELETE. Также поддерживаются добавление и удаление столбцов, операции COPY и pg_dump.

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

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

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

Run

Данные в MARS3 хранятся в упорядоченном виде. Непрерывный сегмент упорядоченных данных называется Run.

Существует два типа Run:

  • Rowstore Run: Оптимизирован для высокоскоростной ingestии. Входящие данные изначально хранятся в этом формате.
  • Columnstore Run: Оптимизирован для чтения и сжатия. Rowstore Run со временем конвертируются в этот формат.

Каждый Run имеет ограничение по размеру:

  • Параметр уровня таблицы max_runsize указывает максимальный размер одного Run при создании таблицы. Максимальное значение составляет 16384 МБ.
  • Значение по умолчанию — 4096 МБ.
  • Для просмотра файлов расширений и delta-файлов таблиц MARS3 можно использовать функцию matrixts_internal.mars3_files.
SELECT * FROM matrixts_internal.mars3_files('test');

Основные типы файлов:

  • DATA: Основной файл данных, хранящий пользовательские данные.
  • FSM (Free Space Map): В YMatrix обновления и удаления реализуются через MVCC, а не путем изменения на месте. Это создает «мертвые кортежи» (версии, невидимые для всех транзакций). FSM отслеживает это освобождаемое пространство для эффективного перераспределения.
  • LINK: Поддерживает отношения цепочки версий (восходящие/нисходящие) для кортежей во время уплотнения (compaction) в сценариях обновления/удаления.
  • DELTA: Хранит маркеры удаления (например, информацию XMAX). MARS3 не изменяет данные на месте; вместо этого используются DELTA-файлы и информация о версиях для скрытия старых данных и управления видимостью.
  • INDEX и INDEX_1_TOAST: Хранят структуры индексов. MARS3 в настоящее время поддерживает индексы BRIN и B-Tree.

Level

MARS3 организует данные по модели LSM-дерева. Файлы Run организованы в Уровни (Levels), от L0 до L9 (максимум 10 уровней).

Уплотнение (compaction) запускается, когда:

  • Количество Run на уровне превышает пороговое значение.
  • Общий размер Run на уровне превышает пороговое значение.

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

Структура уровней

Фоновые процессы слияния в YMatrix периодически обнаруживают состояние таблиц и выполняют уплотнение. Служебная функция matrixts_internal.mars3_level_stats предоставляет информацию о статусе каждого уровня таблицы MARS3:

SELECT * FROM matrixts_internal.mars3_level_stats('test') LIMIT 10;

Эта операция критически важна для оценки здоровья таблицы, например, для проверки слияния Run в соответствии с ожиданиями, выявления избыточного количества невидимых Run и обеспечения того, чтобы количество Run оставалось в нормальных пределах.

Эмпирические правила здоровья:

  • Уровень 0: Нездоровое состояние, если количество Run > 3.
  • Уровень 1: Нездоровое состояние, если количество Run > 50.
  • Уровень > 1: Нездоровое состояние, если количество Run > 10.

Range и Stripe

Данные Columnstore записываются и читаются напрямую, без буферного слоя, такого как Shared Buffers или сброс страниц.

  • Range: Логическое окно строк. По умолчанию каждые compress_threshold (по умолчанию 1200) строк образуют Range.
  • Stripe: Физический блок данных для конкретного столбца в пределах Range (содержащий compress_threshold значений).

Если данные столбца внутри Stripe особенно велики, они разбиваются на фрагменты по 1 МБ. Чтение не обязательно выбирает весь объем compress_threshold за один раз.

RUN 
 └── Range (Разделение по строкам, по умолчанию 1200 строк на range)
      ├── Stripe столбца 1 (1200 данных)
      ├── Stripe столбца 2 (1200 данных)
      ├── Stripe столбца 3 (1200 данных)
      └── ...
  • Range: Логическое окно строк; внутри хранится в колоночном формате и сжимается по столбцам.
  • Stripe: Физический блок столбца; непрерывное хранилище для конкретного столбца в пределах диапазона из 1200 строк.
  • Datum: Минимальная единица значения (значение одной ячейки, нативное для ядра PostgreSQL).

Rowstore и Columnstore

MARS3 использует стратегию хранения «Сначала строки, затем столбцы». Данные поступают в систему в формате, оптимизированном для записи (rowstore), и постепенно конвертируются в формат, оптимизированный для анализа (columnstore), посредством фонового управления. Это обеспечивает непрерывную запись и бесперебойный анализ.

  • Rowstore: Оптимизирован для ingestии и доступа к свежим данным. Он быстро принимает новые данные и подходит для чтений небольшого масштаба или детального поиска, особенно сразу после вставки до начала уплотнения.
  • Columnstore: Оптимизирован для сканирования и агрегации. После организации columnstore значительно повышает пропускную способность сканирования и эффективность сжатия, делая крупномасштабные агрегации и запросы с фильтрацией более ресурсоэффективными.

Преимущества по сравнению с прямой колоночной записью:

  • Более быстрая ingestия для высокочастотной записи небольшими пакетами.
  • Сниженные требования к памяти для кэширования данных.
  • Обеспечение равномерного количества кортежей в блоках данных.

Ingestия данных

  • Данные вставляются в память через INSERT, затем записываются в Rowstore Run Уровня 0 (L0).
  • YMatrix поддерживает три режима записи, определяемых параметрами уровня таблицы prefer_load_mode и rowstore_size (см. Конфигурация):
    1. Normal: Режим по умолчанию. Новые данные записываются в Rowstore Run L0. После достижения rowstore_size данные сбрасываются в Columnstore Run L1. По сравнению с режимом Bulk, это требует одной дополнительной операции ввода-вывода, а конвертация из строк в столбцы происходит асинхронно. Подходит для высокочастотной записи небольшими пакетами при достаточной мощности ввода-вывода и высокой чувствительности к задержкам.
    2. Bulk: Режим пакетной загрузки. Данные записываются напрямую в Columnstore Run L1. По сравнению с режимом Normal, это сокращает одну операцию ввода-вывода, а конвертация происходит синхронно. Подходит для низкочастотной записи большими пакетами при ограниченной мощности ввода-вывода и низкой чувствительности к задержкам.
    3. Single: Данные вставляются напрямую в rowstore. Кортежи размещаются непосредственно в Shared Buffers.

Дополнительные сведения см. в разделе Путь записи.

Индексы MARS3

MARS3 в настоящее время поддерживает индексы BRIN и B-Tree.

  • B-Tree: Идеален для транзакционных систем, требующих «точного поиска». Использует указатели на уровне строк для быстрого позиционирования.
  • BRIN: Идеален для крупномасштабных аналитических систем, где преобладают «диапазонные сканирования». Использует сводки блоков для значительного сокращения ненужного ввода-вывода.

Примечание!
Одна таблица MARS3 поддерживает максимум 16 индексов (независимо от типа столбца или индекса).

Индексы BRIN

  • MARS3 поддерживает создание индексов mars3_brin и mars3_default_brin. Поддерживаются создание и удаление.
  • Каждый Run при создании генерирует свой собственный независимый файл индекса BRIN.
  • Default BRIN — это ключевая функция, обеспечивающая автоматическую поддержку BRIN на уровне таблицы без необходимости ручного создания. В отличие от стандартного CREATE INDEX USING BRIN (который приносит пользу только сканированию по индексу), Default BRIN также улучшает последовательное сканирование (Seq Scan), значительно повышая эффективность запросов. Важно отметить, что Default BRIN не занимает слот индекса. Даже при включенном Default BRIN можно создать до 16 дополнительных индексов.
Функция mars3_brin mars3_default_brin
Создание Ручное Автоматическое (ручные действия не требуются)
Поддержка запросов Фильтрация данных только при сканировании по индексу Фильтрация данных как при сканировании по индексу, так и при последовательном сканировании
Версия технологии brinV2 brinV2
Параметризованный запрос Поддерживается (param-IndexScan) Поддерживается (param-SeqScan)

Индексы B-Tree

B-Tree — это индекс общего назначения, основанный на сбалансированной многопутевой древовидной структуре. Он организует узлы индекса по значению ключа, обеспечивая быстрое и точное позиционирование отдельных строк или небольших диапазонов. Сложность запроса остается стабильной на уровне O(logN). Он эффективно поддерживает проверки на равенство, диапазонные сканирования и операции сортировки. Благодаря независимости от физического распределения данных, B-Tree обеспечивает исключительную стабильность в сценариях транзакций с высокой конкуренцией. Это выбор по умолчанию для первичных ключей, уникальных ограничений и запросов с высокой селективностью. Однако он не подходит для столбцов с низкой селективностью или широких диапазонных сканирований больших таблиц.

mars3btree — это специализированная реализация B-Tree для MARS3. Внутренние страницы остаются стандартными страницами B-Tree. Поддерживаются два типа:

  • NORMAL: Стандартный строково-ориентированный B-Tree (для RowStore), без сжатия.
  • COMPRESSED: Колоночно-ориентированный сжатый B-Tree (для ColumnStore), со сжатием.

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

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

  • Правильно выбранные ключи сортировки создают сильную локальность данных внутри Run и на более высоких уровнях. Условия фильтрации запросов с большей вероятностью будут соответствовать непрерывным диапазонам, делая операции пропуска (skip-scans) более эффективными.
  • Неправильно выбранные ключи сортировки приводят к разрозненному распределению данных. Условия фильтрации не могут сузить диапазон сканирования, заставляя систему вести себя так, как будто выполняется полное сканирование таблицы, несмотря на наличие индексов.
  • MARS3 хранит данные в отсортированном порядке. Порядок сортировки должен быть указан через столбцы сортировки (допускается несколько столбцов) при создании таблицы. Эти поля составляют ключ сортировки.
  • Ключ сортировки можно указать только один раз. Его нельзя изменить, добавить к нему столбцы или удалить после создания.
  • Для максимизации выигрыша в производительности от упорядочивания выбирайте поля, часто используемые в фильтрах и обладающие высокой селективностью. Например, используйте временную метку события и ID устройства для таблиц мониторинга оборудования.
  • Если ключ сортировки текстовый и допустима сортировка по байтовому порядку, использование COLLATE C для этого столбца может ускорить сортировку.

Подробные принципы выбора см. в разделе Ключи сортировки и локальность данных.

Сжатие

О влиянии на производительность см. раздел Сжатие и производительность.

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

  • Удаление: MARS3 записывает удаления в Delta-файл соответствующего Run. Физическое удаление данных происходит только во время уплотнения Run.
  • Обновление: MARS3 реализует обновления путем логического удаления исходных данных и вставки новой записи.
  • Режим Unique: Поддерживает DELETE. Для обновлений не требуется явная клаузула 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.

Технические подробности см. в разделе Обновления и удаления.

Уплотнение и освобождение места

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

Технические подробности см. в разделе Фоновое управление.

Поддержка MVCC

  • MVCC (Multiversion Concurrency Control), или многоверсионность, в основном обрабатывает обновления, модификации и удаления данных.
  • При многоверсионности обновления и удаления не изменяют данные на месте. Вместо этого создается новая версия, старая версия помечается как недействительная, и добавляются новые данные. Сохраняется несколько версий данных, каждая из которых несет информацию о версии.
  • MARS3 полагается на Delta-файлы и информацию о версиях для скрытия старых данных, управляя видимостью, а не изменяя данные на месте.
  • Примечание: Непрерывные обновления или удаления одного и того же Run увеличат физическое пространство, занимаемое его Delta-файлом. Однако рост прекратится, как только все данные в Run будут удалены. Уплотнение MARS3 автоматически очищает Dead (мертвые) данные. Кроме того, можно запланировать выполнение VACUUM для профилактической очистки Dead данных.

Поддержка сегментов (Buckets)

MARS3 Bucket — это механизм оптимизации параллельного выполнения на уровне хранилища, разработанный YMatrix для сценариев параллельного сканирования в архитектуре MPP. Организуя данные в несколько логических сегментов (buckets) на основе хеша ключа распределения на этапе записи, он гарантирует, что данные с одинаковым ключом распределения будут обрабатываться одним и тем же воркером во время параллельных сканирований. Это сохраняет семантику распределения данных (локус), позволяет избежать ненужного перераспределения данных (Motion) и обеспечивает скачок производительности от «более быстрого сканирования» к «более локальным вычислениям».

create table foo (c1 int, c2 int) using mars3 with (mars3options='nbuckets = 2');

Допустимые значения для nbuckets находятся в диапазоне от 1 до 128. Значение по умолчанию равно 1, что указывает на использование единого сегмента (т.е. сегментирование не выполняется).

Дополнительные технические сведения см. в разделе Технический обзор MARS3 Bucket.

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

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

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

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

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

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

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

Параметры для контроля размера Run L0 (косвенно влияют на более высокие уровни):

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

Параметры порога сжатия (регулируют коэффициент сжатия и эффективность чтения):

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

Параметры режима загрузки данных:

Параметр Единица По умолчанию Диапазон Описание
prefer_load_mode - normal normal / bulk / single Режим загрузки данных.normal: Сначала запись в Rowstore L0, затем сброс в Columnstore L1 после достижения rowstore_size. Асинхронная конвертация; подходит для высокочастотной записи небольшими пакетами при достаточном вводе-выводе.bulk: Прямая запись в Columnstore L1. Синхронная конвертация; подходит для низкочастотной записи большими пакетами при ограниченном вводе-выводе.single: Прямая вставка в rowstore (Shared Buffers).

Параметры усиления размера уровня:

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

Следующий параметр определяет количество сегментов (buckets):

Параметр Единица Значение по умолчанию Диапазон Описание
nbuckets 1 1 – 128 Количество сегментов. Управляет числом сегментов для оптимизации производительности запросов. Рекомендации по использованию см. в разделе Лучшие практики использования MARS3 Bucket в рамках раздела «Лучшие практики проектирования таблиц и распределения данных».

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

CREATE EXTENSION matrixts;
CREATE TABLE t(
    time timestamp with time zone,
    tag_id int,
    i4 int4,
    i8 int8
)
USING MARS3
WITH (compresstype=zstd, compresslevel=3, compress_threshold=1200,
        mars3options='rowstore_size=64,prefer_load_mode=normal,level_size_amplifier=8, nbuckets=2')
DISTRIBUTED BY (tag_id)
PARTITION BY RANGE (time)
(
    START ('2026-02-01 00:00:00+08')
    END   ('2026-03-01 00:00:00+08')
    EVERY (INTERVAL '1 day')
)
ORDER BY (time, tag_id);

Служебные функции

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

Обзор HEAP

HEAP — это движок хранения по умолчанию в YMatrix, унаследованный от PostgreSQL. Также известный как кучное хранение (heap storage), он поддерживает только строковое хранение и не поддерживает колоночное хранение или сжатие. Он основан на механизме MVCC и подходит для сценариев с частыми обновлениями и удалениями.

Механизм MVCC

В рамках MVCC таблицы HEAP не удаляют данные физически во время операций обновления или удаления. Вместо этого они полагаются на информацию о версиях для скрытия старых данных (управление видимостью). Следовательно, обширные обновления или удаления приводят к непрерывному росту физического пространства, занимаемого таблицей HEAP. Требуется регулярное выполнение VACUUM для освобождения места от старых данных.

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

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

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

Обзор AORO

AORO (Append-Only Row-Oriented) — это парадигма организации хранения, разработанная для аналитических баз данных. Данные записываются непрерывно в режиме «только добавление» (append-only) по строкам. Он не поддерживает обновления или удаления на месте. Версионирование поддерживается через временные метки или идентификаторы транзакций, балансируя пропускную способность записи, эффективность запросов и согласованность MVCC. AORO поддерживает строковое хранение.

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

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