Blob Storage(大value存储优化)

更新时间:
复制 MD 格式

Hologres V5.0.0及以上版本支持Blob Storage。通过表属性blob_columns将TEXT、BYTEA列中的大value单独存放,在不改变表结构和查询语义的前提下,降低点查IO与Compaction开销。本文为您介绍Blob Storage的原理、使用方法和使用限制。

背景信息

在Hologres列存表中,长文本、文档内容、日志大字段等超大value会带来以下问题:

  • 文件数增多:大value占用大量空间,单个数据文件能容纳的行数骤降,产生大量小文件。

  • 点查IO放大:即使只点查一行,也需要读取该行所在数据块中的全部大value。

  • Compaction写放大:Compaction过程中需要反复读写、搬运大value。

Blob Storage将超过阈值的大value单独存放,原列中只保留指向该value的引用,从而在不改变表结构、不改变查询语义的前提下,优化大value的存储组织以及读取、合并效率。

适用场景

Blob Storage适用于包含大value列、且以写入和点查为主的场景,例如:

  • 存储大文本或文档内容,如大模型语料、对话内容。

  • 存储日志中的超宽列,如单行内容达数十KB以上的日志字段。

  • 按主键或高选择性条件点查整行数据(包含大value列)。

需要频繁对大value列做全量扫描的OLAP场景不适合开启Blob Storage,详情请参见使用限制。

工作原理

  • 按行自适应存放:对于开启了Blob Storage的列,系统逐行判断value大小。达到阈值的value压缩后单独存放,原列只保留引用;较小的value仍直接存放在原列中。同一列中可以同时存在两种存放方式的行。

  • 按需读取:只有在查询真正需要返回某个大value时,系统才会读取它。被WHERE条件过滤掉的行、查询中未涉及的列,都不会读取单独存放的大value。

  • Compaction不重写大value:合并文件时默认只搬运引用,不读取、不重写单独存放的大value。仅在回收已删除数据、无效数据占比过高或文件碎片化较严重时,才会重新整理大value。

配套能力:Blob Column Lazy Seek

在高选择性点查场景(如WHERE k = x)中,Lazy Seek会在过滤得到最终命中行后,只读取命中行对应的大value,而不是将整个row group(行组)中的大value全部读出并解压。

  • 控制参数:由GUC参数hg_experimental_enable_blob_column_lazy_seek控制,默认值为on。

  • 生效条件:同一row group仅命中1行,且Blob Storage列为直接输出列。不满足条件时,系统自动回退为读取整列,查询结果不受影响。

功能优势

  • 点查IO大幅降低:点查只读取命中行的大value,无需读取整个row group中的大value。配合Lazy Seek,读取量和解压量显著下降。

  • Compaction写放大显著降低:Compaction默认只搬运引用,不重写大value,耗时明显缩短。

  • 文件数减少:大value单独存放后,数据文件变小、单个文件可容纳的行数增加,小文件随之减少。

  • 对业务透明:现有TEXT、BYTEA列可直接开启,无需使用特殊的列类型,也无需修改SQL。

说明

Blob Storage不以节省总存储空间为目标。大value单独存放后仍然占用存储,以pg_relation_size统计时,开启与未开启Blob Storage的表总存储占用基本持平(均执行Full Compaction后对比)。Blob Storage的收益体现在点查IO、Compaction效率和文件数量上,而非存储空间。

使用限制

  • 仅Hologres V5.0.0及以上版本支持Blob Storage。

  • 仅支持TEXT、BYTEA类型的列,且仅列存表和行列共存表支持,行存表不支持。

  • 开启Blob Storage的列不支持字典编码和Bitmap:即使为该列设置了dictionary_encoding_columns或bitmap_columns,系统也会自动忽略(视为关闭)。其他索引(包括全文倒排索引)不受影响,可正常使用。因此,不建议对需要等值过滤的低基数列开启Blob Storage,否则Bitmap被忽略后,等值过滤会退化为全表扫描并需读取单独存放的大value,查询会显著变慢。

  • 对大value列做全量扫描的OLAP查询会变慢:全量扫描时需逐条读取并解压大value,比未开启Blob Storage时更慢。如果业务需要频繁对大value列做全表扫描,请评估后再开启。

  • 存储占用与数据特征相关,开启后不一定节省空间:较小且重复度高(低基数)的值开启后,存储占用可能更大;较大且重复度低的值开启后,存储占用可能更小。对比存储占用前,请先执行Full Compaction。

  • 分区表:blob_columns会从父表继承到子分区。执行ATTACH PARTITION时,要求父表和子表的blob_columns设置一致,否则执行失败。

  • 对于存储长度恰好为32字节的纯二进制数据的列,存在极低概率被误识别为大value引用。如果需要存储此类数据,建议对该列关闭Blob Storage。

开启Blob Storage

通过set_table_property设置表属性blob_columns,指定哪些列开启Blob Storage。语法如下。

CALL set_table_property('<TABLE_NAME>', 'blob_columns', '<COLUMN_NAME>:{on|auto|off}[,...]');

参数说明如下。

参数

说明

TABLE_NAME

表名称。

COLUMN_NAME

列名称,仅支持TEXT、BYTEA类型的列。多个列之间使用英文逗号(,)分隔,例如'col1:on,col2:auto'。

blob_columns默认值为空字符串(''),未配置的列始终存放在原列中。

on

当前列强制开启Blob Storage,大value单独存放。

auto

由系统按阈值自动判断value是否单独存放。

off

当前列关闭Blob Storage。

blob_columns支持动态修改。修改后不会立即重写历史文件,而是与bitmap_columns、dictionary_encoding_columns一样,在后台通过Compaction异步逐步生效。

-- 为payload列强制开启Blob Storage
CALL set_table_property('<TABLE_NAME>', 'blob_columns', 'payload:on');
-- 由系统按阈值自动判断
CALL set_table_property('<TABLE_NAME>', 'blob_columns', 'payload:auto');

验证功能是否生效

  1. 确认配置已生效:执行以下语句查看表的DDL,输出中包含blob_columns属性即表示配置成功。

    SELECT hg_dump_script('<TABLE_NAME>');
  2. 对比点查IO:开启执行统计信息并清理缓存后,对同一条高选择性点查执行EXPLAIN ANALYZE,对比结果中se_read_statistic的decompress_out_bytes和total_read_bytes。

    SET hg_experimental_show_execution_statistics_in_explain = on;
    SELECT hg_admin_command('freecache', 'table_name=<TABLE_NAME>');
    EXPLAIN ANALYZE SELECT id, payload FROM <TABLE_NAME> WHERE k = 5000;

    开启Blob Storage且Lazy Seek为on时,命中单行的点查仅解压该行的数据量;关闭Lazy Seek或未开启Blob Storage时,会解压整个row group。

  3. 查看存储占用:先执行Full Compaction,再使用pg_relation_size或hologres.hg_relation_size查看。未执行Full Compaction时,未合并的碎片文件会导致对比结果失真。

    SELECT hologres.hg_full_compact_table('<SCHEMA_NAME>.<TABLE_NAME>');
    SELECT pg_relation_size('<TABLE_NAME>');
    -- 或者
    SELECT hologres.hg_relation_size('<SCHEMA_NAME>.<TABLE_NAME>', 'data');

升级与降级兼容性

  • 升级:兼容。实例升级到Hologres V5.0.0及以上版本不受影响。

  • 降级:不支持。开启了Blob Storage的表不支持降级到Hologres V5.0.0以下版本。低版本无法识别单独存放的大value,会将原列中的引用当作普通值返回,导致数据不正确。

  • 降级前处理:需先清除单独存放的大value,以下方式任选其一。

    • 删除已开启Blob Storage的表。

    • 将blob_columns中相关列设置为off,再对该表执行VACUUM或hologres.hg_full_compact_table并等待Compaction完成,然后执行以下语句确认已无单独存放的大value。返回f(false)时,表示可以安全降级。

      SELECT EXISTS (
          SELECT 1 FROM <TABLE_NAME>
          WHERE hg_file_meta IS NOT NULL
            AND COALESCE((hg_file_meta ->> 'blob_file_count')::bigint, 0) > 0
      ) AS has_blob_file;

使用示例

以下示例创建列存表docs,为大value列payload强制开启Blob Storage,并执行点查和存储占用查询。

-- 建表并为大value列payload强制开启Blob Storage
BEGIN;
CREATE TABLE docs (
    id INT PRIMARY KEY,
    k INT,
    payload TEXT
)
WITH (orientation = 'column');
CALL set_table_property('docs', 'clustering_key', 'k:asc');
CALL set_table_property('docs', 'blob_columns', 'payload:on');
COMMIT;

-- 写入大value数据并落盘
INSERT INTO docs SELECT g, g, repeat(md5(g::text), 3200) FROM generate_series(1, 10000) g;
SELECT hg_admin_command('flush', 'table_name=docs');

-- 高选择性点查:命中单行,配合Lazy Seek仅读取该行的大value
SELECT id, payload FROM docs WHERE k = 5000;

-- 查看存储占用(务必先执行Full Compaction再对比)
SELECT hologres.hg_full_compact_table('public.docs');
SELECT pg_size_pretty(pg_relation_size('docs'));