存储诊断工具 datainspect

datainspect 是 MARS2 自带的存储诊断工具,它可以提供底层数据洞察,以精确地优化你的数据的存储和查询性能。

存储优化需要关注数据在物理文件中的实际内容,与 PostgreSQL 的 Pageinspect 类似,你可以使用 datainspect 来方便的提取 MARS2 物理存储中的数据段来做进一步分析。
此外,datainspect 整合了 MARS2 中相关的索引和元信息,以提供 NULL 分布和 minmax 等基本信息,从而辅助 I/O 扫描过程的优化。

注意!
此工具只适用于 MARS2 表。

1.1 安装

datainspect 是 MARS2 中内置的系统函数,在正确安装 MARS2 后即可使用。MARS2 表依赖 matrixts 时序扩展,在建表前,首先需要你在使用该存储引擎的数据库中创建扩展。

注意!
matrixts 扩展为数据库级别,一个数据库里面创建一次即可,无需重复创建。

=# CREATE EXTENSION matrixts;

1.2 函数功能介绍

首先,创建一张测试表,示例中命名为 tb1,我们将以此测试表为用例介绍四个相关函数。

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

创建 MARS2 索引。

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

构造 24000 条测试数据。

=# 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;

1.2.1 desc_ranges

desc_ranges 函数整合了 MARS2 底层元信息及索引,以提供如 minmax 索引信息和空值信息等,还支持对数据底层存储空间占用情况进行精确监测。

  • 语法

    SELECT <* / column1,column2,...> FROM matrixts_internal.desc_ranges(<'tablename'>)
  • 参数

字段 说明 必选
tablename 表名,分区表时为分区表名
  • 返回
字段 说明
segno Segment 编号,从 0 开始
attno Attribute Number,列编号,从 0 开始
forkno 物理文件分片的编号。是数据库底层的编号,可以认为它对应一个具体的文件,默认从第一个 fork 开始
offno RANGE 在物理文件中的偏移量,单位字节。默认从 0 开始。在 MARS2 中数据按批存储入库,以设置好的 compress_threshold(压缩阈值参数)为单位形成一个存储 RANGE,以在物理文件中的偏移量形成自身的位置标识
nbytes RANGE 在物理文件中占用的实际空间,单位字节
nrows 建表时设置的 compress_threshold,默认 1200compress_threshold 即压缩阈值。用于控制单表多少元组(Tuple)进行一次压缩,是同一个单元中压缩的 Tuple 数上限
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)
  1. 给定一张表,看某一个 Segment 上的空值和数据分布规律。
=# 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)

1.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,查看这个 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)

    1.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 导出的文件名。导出完毕后,不同的 Segment 会产生以不同编号标识的独立后缀。例如,数据文件命名为 tb1-f2.bin,导出后在 Segment1 上显示为 tb1-f2.bin-seg1
forkno 物理文件分片编号
offno 数据在物理文件中的偏移量,单位字节
limits 默认为 100,以 forknooffno 指定的位置为起始位置来确定限制的 RANGE 个数

注意!
此处 limits 只限制单个 Segment 上的导出的 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 条结果,这是因为导出是 Segment 独立执行的,这里面每一条都代表下游一个 Segment 上的导出结果,随后在相应的目录会出现;其中有 2 条结果的 nbytes0,是因为 Segment 上没有满足过滤条件的数据。

执行完成后每个 Segment 所在主机都会生成在原有后缀后又添加独立后缀标识 .-seg<no> 的二进制文件。

1.2.4 desc_compress

desc_compress 针对 MARS2 列存中选中的一段连续的物理数据做两种用户指定的压缩算法的比较。

  • 语法
    =# SELECT <* / column1,column2,...> FROM matrixts_internal.desc_compress(
    tablename text, 
    attno int4, 
    <method1> text, 
    <method2> int4,
    forkno int4, 
    offno int4,
    limits int4
    );
  • 参数
字段 说明 必选
tablename 表名,分区表时为分区表名
attno 列编号
压缩算法名一 如 zstd,lz4,deltadelta 等
压缩算法名二 如 zstd,lz4,deltadelta 等
forkno 物理文件分片编号
offno 数据在物理文件中的偏移量,单位字节
limits 默认为 100,以 forknooffno 指定的位置为起始位置来确定限制的 RANGE 个数
  • 返回
字段 说明
segno Segment 编号,从 0 开始
attno Attribute Number,列编号,从 0 开始
forkno 物理文件分片的编号。是数据库底层的编号,可以认为它对应一个具体的文件,默认从第一个 fork 开始
offno RANGE 在物理文件中的偏移量,单位字节。默认从 0 开始。在 MARS2 中数据按批存储入库,以设置好的 compress_threshold(压缩阈值参数)为单位形成一个存储 RANGE,以在物理文件中的偏移量形成自身的位置标识
nbytes RANGE 在物理文件中占用的实际空间,单位字节
nrows 建表时设置的 compress_threshold,默认 1200compress_threshold 即压缩阈值。用于控制单表多少元组(Tuple)进行一次压缩,是同一个单元中压缩的 Tuple 数上限
nrowsnotnull RANGE 内非空条数
mmin 该列如果支持 minmax 索引,就是显示该 RANGE 内的最小值,如果不存在为 NULL
mmax 该列如果支持 minmax 索引,就是现实该 RANGE 内的最大值,如果不存在为 NULL
decompresstime0 解压当前列原有压缩算法使用的时间,单位时钟周期(CPU Cycle)
compressedsize1 采用 <method1> 压缩此段数据得到的数据大小,单位字节
compressedtime1 采用 <method1> 压缩此段数据的时间,单位 CPU Cycle
selectiontime1 如果 <method1> 为系统匹配的自适应编码(Auto),那么此参数的意思即分析数据特征所用的时间(CPU Cycle)
decompressedtime1 采用 <method1> 解压此段数据的时间,单位 CPU Cycle
iscompressible1 采用 <method1> 是否可以压缩。当压缩算法压缩完的结果与原始数据接近,甚至更大,建议放弃压缩
dataloss1 采用 <method1> 压缩数据后是否会存在精度损失
compressedsize2 采用 <method2> 压缩此段数据得到的数据大小,单位字节
compressedtime2 采用 <method2> 压缩这段数据的时间,单位 CPU Cycle
selectiontime2 如果 <method2>自适应编码,那么此参数的意思即分析数据特征所用的时间(CPU Cycle)
decompressedtime2 采用 <method2> 解压这段数据的时间,单位 CPU Cycle
iscompressible2 采用 <method2> 是否可以压缩。当压缩算法压缩完的结果与原始数据接近,甚至更大,建议放弃压缩
dataloss2 采用 <method2> 压缩数据后是否会存在精度损失
  • 示例
  1. 已知本文示例表格采用 lz4 压缩算法,现在我们通过 desc_compress 来分析 f1 列数据采用什么压缩算法能得到更高的压缩率。
    =# SELECT compressedsize1,compressedsize2,dataloss1,dataloss2,compressedtime1,compressedtime2,iscompressible1,iscompressible2 
            FROM matrixts_internal.desc_compress(
                'tb1', 0, 'lz4', 'deltadelta') limit 10;
    compressedsize1 | compressedsize2 | dataloss1 | dataloss2 | compressedtime1 | compressedtime2 | iscompressible1 | iscompressibl
    e2
    -----------------+-----------------+-----------+-----------+-----------------+-----------------+-----------------+--------------
    ---
             4835 |             975 | NO        | NO        |          275976 |          171902 | YES             | YES
             4848 |            1041 | NO        | NO        |          241742 |          111632 | YES             | YES
             4843 |             995 | NO        | NO        |          161580 |          123520 | YES             | YES
             4843 |             976 | NO        | NO        |          180704 |          118966 | YES             | YES
             4846 |            1009 | NO        | NO        |          157268 |          123994 | YES             | YES
             4844 |            1025 | NO        | NO        |           93118 |           70050 | YES             | YES
             4846 |            1018 | NO        | NO        |           91896 |           64120 | YES             | YES
             4843 |             997 | NO        | NO        |           89732 |           64062 | YES             | YES
             4845 |            1013 | NO        | NO        |           95010 |           71106 | YES             | YES
             4561 |             975 | NO        | NO        |           84664 |           82220 | YES             | YES
    (10 rows)

    此表中显示的实际上是选定的数据段中的每个 RANGE 的对比数据。可见 deltadelta 算法在 f1 列的压缩率远高于 lz4,同时压缩时间也相对较短,因此比起 lz4,在 f1 列使用 deltadelta 算法显然是更优选择。