Двигатель хранения

В этом документе представлены сведения о движке хранения и инструментах диагностики хранилища, поддерживаемых MatrixDB, в основном включая следующие разделы:

Движок хранения является основой системы баз данных. База данных использует движок хранения для создания, запросов, обновления и удаления данных. В зависимости от ваших потребностей различные движки хранения обеспечивают различные механизмы хранения и проектирования по таким измерениям, как физическая структура, тип индекса и уровень детализации блокировок.
В настоящее время система базы данных MatrixDB поддерживает использование следующих движков хранения: HEAP на основе PostgreSQL, MARS2, разработанный MatrixDB, а также столбцовый движок AOCO и строковый движок AORO.
Таблицы HEAP — это движки хранения, предоставляемые традиционными базами данных PostgreSQL, также известные как кучные таблицы. Этот тип таблиц поддерживает большое количество одновременных операций чтения и записи, транзакции, индексы и другие функции.
Таблицы MARS2 уменьшают количество обращений к I/O за счет своего метода физического упорядоченного слияния, тем самым повышая производительность запросов к данным в таблице. Кроме того, MARS2 поддерживает сжатие, хранение в столбцах, автоматическое архивирование, предварительную агрегацию и другие функции, что делает его особенно эффективным в сценариях работы с временными данными.
На данный момент MARS2 не поддерживает операции обновления и удаления.


1 Создание таблиц под разными движками хранения

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

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

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

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

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

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

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

=# CREATE EXTENSION matrixts;

При создании таблицы используйте USING MARS2, чтобы указать движок хранения:

=# CREATE TABLE vehicle_basic_data_mars2(
daq_time timestamp  encoding (minmax),
vin varchar(32)  COLLATE "C" encoding (minmax),
lng float encoding (minmax),
lat float encoding (minmax),
speed float ,
license_template varchar(16) ,
flag integer
)
USING MARS2
WITH (compresstype=zstd, compresslevel=3)
DISTRIBUTED BY (vin)
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 );

Рассмотрим конкретные параметры и выражения в таблице vehicle_basic_data_mars2:

  1. encoding (minmax)
    Этот параметр используется для повышения вычислительной эффективности. Если вы планируете выполнять агрегированные запросы, такие как min, max, avg, sum, count, на основе этого поля или использовать условие фильтрации WHERE. Необходимость добавления этой опции должна определяться исходя из бизнес-сценария. Не следует добавлять её ко всем полям бездумно, так как это приведет к чрезмерному расходу ресурсов CPU и дискового I/O при записи данных.
  2. COLLATE "C"
    Эта опция добавляется только в поле кодировки устройства; для других полей она не требуется. Эта операция позволяет повысить скорость сортировки и поиска текстовых типов данных.
  3. USING MARS2
    Это фиксированная форма записи при использовании таблицы MARS2, изменять содержимое нельзя.
  4. WITH (compresstype=zstd, compresslevel=3)
    Указывает алгоритм сжатия zstd, а также поддерживаются алгоритмы rle_type, zlib, lz4. В данной таблице рекомендуется использовать zstd, а уровень сжатия — 3. Подробные параметры производительности сжатия см. в таблице ниже.
  5. DISTRIBUTED BY (vin):
    Используется для выбора ключа распределения. Мы рекомендуем использовать Device Encoding Field в качестве ключа распределения, чтобы данные одного и того же устройства можно было запрашивать и выполнять связанные вычисления. Это позволяет избежать потерь производительности из-за перераспределения данных между узлами.
  6. PARTITION BY RANGE (daq_time)
    Определяет ключ секционирования для секционированной таблицы. Мы рекомендуем использовать Time of data acquisition by the device в качестве ключа секционирования. Обычно большинство запросов фильтруют данные по времени сбора. Например, когда вы хотите запросить данные за день и выполнить соответствующие вычисления, нужно добавить условие фильтрации WHERE daq_time >= CURRENT_DATE - INTERVAL '1 day', тогда база данных быстро определит, в какой подтаблице секции находятся данные, и быстро найдет их.
  7. ( START ('2022-07-01 00:00:00') INCLUSIVE
    END ('2022-08-01 00:00:00') EXCLUSIVE
    EVERY (INTERVAL '1 days')
    ,DEFAULT PARTITION others);
    Этот SQL-оператор означает, что будет использовано выражение START...END вместе с ключевыми словами INCLUSIVE и EXCLUSIVE для создания подразделенной таблицы, начиная с 2022-07-01 00:00:00 до 2022-08-01 00:00:00.
  8. EVERY (INTERVAL '1 day')
    Временной интервал подразделения составляет 1 день. Помимо использования day, можно также использовать hour, month, year и т.д., выбирая в зависимости от объема данных. Например, если ваш сервер ежедневно получает от 1 до 10 миллионов записей, то интервал 1 day будет оптимальным выбором. Если суточный объем данных составляет лишь десятки или сотни тысяч, то лучше выбрать 1 month; если суточный объем всего несколько десятков тысяч, то подразделение можно делать раз в 1 year.
  9. DEFAULT PARTITION others
    Указывает раздел по умолчанию. Если соответствующая подтаблица для данных не найдена в других разделах, данные будут помещены в этот раздел.

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

Параметры производительности сжатия таблицы MARS2, упомянутые выше:

Название параметра Значение по умолчанию Минимальное значение Максимальное значение Описание
compress_threshold 1200 1 8000 Порог сжатия. Используется для контроля количества кортежей (tuples), сжимаемых в одной таблице, и представляет собой верхний предел числа кортежей, сжимаемых в одной единице.
compressiontype lz4 -- -- Алгоритм сжатия, поддерживается:
1. zstd
2. zlib
3. lz4
compresslevel 1 1 -- Уровень сжатия. Обычно чем меньше значение, тем ниже степень сжатия, но быстрее процесс сжатия; чем больше значение, тем выше степень сжатия, но медленнее сжатие. Разные алгоритмы имеют разные диапазоны допустимых значений:
zstd: 1–19
zlib: 1–9
lz4: 1–20

Примечание!
Как правило, чем выше уровень сжатия, тем выше степень сжатия и ниже скорость. Однако это не абсолютное правило.

После успешного создания таблицы MARS2 необходимо дополнительно создать индекс типа mars2_btree для нормального чтения и записи данных. Цель использования индекса сортировки — максимально физически сблизить данные одной размерности или схожих характеристик, чтобы уменьшить количество обращений к I/O и повысить эффективность запросов. Поэтому выбор ключей сортировки должен соответствовать основным характеристикам бизнес-запросов.
Например, если требуется точечный запрос по одному устройству, то ключ сортировки — номер устройства (vin) в сценарии временных данных. Если требуется детальный запрос, агрегированный запрос или запрос по нескольким устройствам в определенный период времени, то ключ сортировки — номер устройства и временная метка (vin,daq_time).

=# CREATE INDEX idx_mars2 ON vehicle_basic_data_mars2 
USING mars2_btree(vin, daq_time);

Примечание!
Когда данные с одного временного интервала с устройства передаются пакетом, MARS2 может объединять данные с одного и того же устройства (значение daq_time в этой таблице) в одно и то же время (значение vin в этой таблице). Для использования функции слияния необходимо вручную указать uniquemode=true при создании индекса, поскольку значение по умолчанию для этой опции — false. Например, если вы укажете uniquemode=true, то устройство A01 отправит обратно 3 данных в момент 2022-01-01 00:00:00, и в итоге первоначальные две записи будут перезаписаны последней, останется только одна запись; однако если оставить значение по умолчанию uniquemode=false, то обе 3 записи, отправленные устройством 2022-01-01 00:00:00, будут сохранены без обработки.


2 Инструмент диагностики хранения datainspect

datainspect — это встроенный инструмент диагностики хранения в MARS2, который обеспечивает глубокий анализ данных на нижнем уровне, позволяя точно оптимизировать производительность хранения и запросов к вашим данным.
Оптимизация хранения требует внимания к фактическому содержимому данных в физических файлах. Аналогично Pageinspect в PostgreSQL, вы можете использовать datainspect для простого извлечения сегментов данных из физического хранилища MARS2 для дальнейшего анализа. Кроме того, datainspect интегрирует соответствующую информацию об индексах и метаданных в MARS2, предоставляя базовую информацию, такую как распределение NULL и minmax, что помогает оптимизировать процесс сканирования I/O.

Примечание!
Этот инструмент доступен только для таблиц MARS2.

2.1 Установка

datainspect — это системная функция, встроенная в MARS2, и может использоваться после правильной установки MARS2. Таблицы MARS2 зависят от расширения matrixts для временных данных. Перед созданием таблицы необходимо сначала создать расширение в базе данных, используя движок хранения.

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

=# CREATE EXTENSION matrixts;

2.2 Введение в функции

Сначала создадим тестовую таблицу с именем tb1 в примере. Мы будем использовать эту тестовую таблицу как пример для демонстрации трех связанных функций.

=# CREATE TABLE tb1(
f1 int8 encoding(minmax), 
f2 int8 encoding(minmax), 
f3 float8 encoding(minmax), 
f4 text
) USING MARS2;

Создадим индекс MARS2.

=# CREATE INDEX ON tb1 USING mars2_btree(f1);

Сгенерируем 24 000 тестовых данных.

=# INSERT INTO tb1 SELECT 
generate_series(1, 24000), 
mod((random()*1000000*(generate_series(1, 1200)))::int8, (random()::int8/100 + 100)), 
(random() * generate_series(1, 24000))::float8, 
(random() * generate_series(1, 24000))::text;

2.2.1 desc_ranges

Функция desc_ranges интегрирует нижележащие метаданные и индекс MARS2, предоставляя такую информацию, как индекс minmax и информация о значениях NULL, а также поддерживает точный мониторинг занятого пространства хранения на нижнем уровне.

  • Синтаксис

    SELECT <* / column1,column2,...> FROM matrixts_internal.desc_ranges(<tablename TEXT>)
  • Параметры

  • tablename: имя таблицы, при секционировании — имя секционированной таблицы. (Обязательно)

  • Возвращаемые значения

Поля Описание
segno Номер сегмента, начинается с 0
attno Номер атрибута, номер столбца, начинается с 0
forkno Количество фрагментов физического файла. Это внутренний номер базы данных, который можно считать соответствующим конкретному файлу. По умолчанию начинается с первого fork
offno Смещение RANGE в физическом файле, в байтах. По умолчанию начинается с 0. В MARS2 данные хранятся пакетно, и хранилище RANGE формируется в единицах заданного compress_threshold (параметр порога сжатия), образуя свою идентификацию позиции со смещением в физическом файле.
nbytes Фактический объем пространства, занимаемый RANGE в физических файлах, в байтах
nrows Значение, установленное при создании таблицы, по умолчанию 1200. compress_threshold — это порог сжатия. Используется для контроля количества кортежей (tuples), сжимаемых в одной таблице, и представляет собой верхний предел числа кортежей, сжимаемых в одной единице.
nrowsnotnull Количество непустых строк в RANGE
mmin Если столбец поддерживает индекс minmax, отображает минимальное значение в RANGE; если нет, значение NULL
mmax Если столбец поддерживает индекс minmax, отображает максимальное значение в RANGE; если нет, значение NULL.
  • Примеры
  1. Для заданной таблицы просмотреть использование пространства хранения каждого столбца во всем кластере.
    =# SELECT attno, sum(nbytes)/1024 as "Size in KB" 
     FROM matrixts_internal.desc_ranges('tb1') GROUP BY attno ORDER BY attno;
    attno |      Size in KB      
    -------+--------------------------------------------------------------------------------------------------------------------------
      0 |  94.9062500000000000
      1 |   7.8203125000000000
      2 | 187.3437500000000000
      3 | 386.3515625000000000
    (4 rows)
  2. Для заданной таблицы просмотреть наличие значений NULL и правила распределения данных в определенном сегменте.
    =# SELECT * FROM matrixts_internal.desc_ranges('tb1') WHERE segno = 1;
    segno | attno | forkname | forkno | offno  | nbytes | nrows | nrowsnotnull |        mmin        |        mmax        
    -------+---------+-------+-------+---------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
      1 |     0 | data1    |    304 |      0 |   4848 |  1200 |         1200 | 15                 | 7240
      1 |     1 | data1    |    304 |  16376 |    856 |  1200 |          199 | 0                  | 99
      1 |     2 | data1    |    304 |  17712 |   9072 |  1200 |         1200 | 1.4602231817218758 | 704.8010557110921
      1 |     3 | data1    |    304 |  50024 |  20272 |  1200 |         1200 | NULL               | NULL
      1 |     0 | data1    |    304 |   4848 |   4856 |  1200 |         1200 | 7243               | 14103
      1 |     1 | data1    |    304 |  17232 |    160 |  1200 |            0 | NULL               | NULL
      1 |     2 | data1    |    304 |  26784 |   9760 |  1200 |         1200 | 705.0931003474365  | 1372.9018354549075
      1 |     3 | data1    |    304 |  70296 |  19680 |  1200 |         1200 | NULL               | NULL
      1 |     0 | data1    |    304 |   9704 |   4856 |  1200 |         1200 | 14125              | 21417
      1 |     1 | data1    |    304 |  17392 |    160 |  1200 |            0 | NULL               | NULL
      1 |     2 | data1    |    304 |  36544 |   9760 |  1200 |         1200 | 1375.043496121433  | 2084.906658862494
      1 |     3 | data1    |    304 |  89976 |  19792 |  1200 |         1200 | NULL               | NULL
      1 |     0 | data1    |    304 |  14560 |   1816 |   445 |          445 | 21429              | 23997
      1 |     1 | data1    |    304 |  17552 |    160 |   445 |            0 | NULL               | NULL
      1 |     2 | data1    |    304 |  46304 |   3720 |   445 |          445 | 2086.0748374078717 | 2336.065046118657
      1 |     3 | data1    |    304 | 109768 |   7576 |   445 |          445 | NULL               | NULL
    (16 rows)

2.2.2 show_range

Функция show_range выбирает фрагмент физических данных в MARS2 и отображает этот фрагмент в виде читаемых данных. Поддерживаемые в настоящее время типы данных: int2, int4, int8, float4, float8, timestamp, date, text.

  • Синтаксис
    SELECT <* / column1,column2,...> FROM matrixts_internal.show_range(
     tablename text, 
     attno int4, 
     forkno int4, 
     offno int4, 
     nbytes int4
    )
  • Параметры
    • tablename: имя таблицы, для секционированной таблицы — имя подтаблицы секции. (Обязательно)
    • attno: номер столбца; при определении таблицы столбцы нумеруются последовательно с 0. (Обязательно)
    • forkno: номер физического файлового шарда. (Обязательно)
    • offno: смещение данных в физическом файле, в байтах. (Обязательно)
    • nbytes: фактический объём данных в физических файлах, в байтах. (Обязательно)

Примечание!
Подробности по указанным выше параметрам см. в описании возвращаемых значений функции desc_ranges, приведённой выше.

  • Возвращаемые значения
Поля Описание
rowno номер строки. Этот номер определяется относительным смещением в RANGE, а не абсолютным смещением всей таблицы
val фактическое содержимое

Примечание!
Возможны ошибки при отображении val в данных с плавающей точкой.

  • Пример
  1. При наличии физически хранимого диапазона RANGE просмотреть его содержимое.
    =# SELECT * FROM matrixts_internal.show_range('tb1', 1, 304, 16176, 808) LIMIT 20;
    rowno | val 
    -------+--------
      1 | 4
      2 | 36
      3 | 81
      4 | 58
      5 | 17
      6 | 75
      7 | 11
      8 | 84
      9 | 60
     10 | 78
     11 | 69
     12 | 0
     13 | 87
     14 | 40
     15 | 72
     16 | 58
     17 | 17
     18 | 48
     19 | 70
     20 | 6
    (20 rows)

2.2.3 dump_range

Функция dump_range распаковывает выбранные физические данные из MARS2 в двоичный файл для последующего анализа.

  • Синтаксис
    =# SELECT <* / column1,column2,...> FROM matrixts_internal.dump_ranges(
    tablename text, 
    attno int4, 
    outfile text, 
    forkno int4, 
    offno int4,
    limits int4
    );
  • Параметры
    • tablename: имя таблицы, если таблица секционирована — имя подтаблицы секции. (Обязательно)
    • attno: номер столбца. (Обязательно)
    • outfile: имя экспортируемого файла. После завершения экспорта на разных сегментах будут созданы независимые файлы с разными числовыми суффиксами. Например, если имя файла данных — tb1-f2.bin, то после экспорта на Segment1 файл будет называться tb1-f2.bin-seg1. (Обязательно)
    • forkno: номер физического файлового шарда. (Необязательно)
    • offno: смещение данных в физическом файле, в байтах. (Необязательно)
    • limits: по умолчанию 100; количество диапазонов RANGE определяется по умолчанию, начиная с позиции, указанной параметрами forkno и offno. (Необязательно)

Примечание!
Здесь параметр limits ограничивает только количество экспортируемых диапазонов RANGE на одном сегменте.

Примечание!
Подробности по указанным выше параметрам см. в описании возвращаемых значений функции desc_ranges, приведённой выше.

  • Возвращаемые значения

    • nbytes: объём экспортированных данных на каждом узле Segment, в байтах.
  • Пример

  1. Экспорт фрагмента физических данных в двоичный файл
    =# SELECT * FROM matrixts_internal.dump_ranges('tb1', 1, '/data/demo/tb1-f2.bin', 304, 16176, 1) LIMIT 20;
    nbytes 
    --------
       0
       0
    1480
    1632
    1704
    1592
    (6 rows)

    В примере после выполнения dump_ranges было получено 6 результатов. Это связано с тем, что экспорт выполняется независимо на каждом сегменте. Каждый результат представляет собой результат экспорта на соответствующем нижестоящем сегменте и будет находиться в соответствующей директории. При этом значение nbytes в двух результатах равно 0, поскольку на этих сегментах нет данных, удовлетворяющих условиям фильтрации.

После выполнения на каждом хосте сегмента будет создан двоичный файл с исходным суффиксом и уникальным идентификатором-суффиксом .-seg<no>.