关于Ganos
Ganos是阿里云数据库产品事业部联合飞天数据库与存储实验室共同研发的新一代云原生位置智能引擎,它将时空数据处理能力融入了云原生关系型数据库PolarDB-PG、云原生多模数据库Lindorm、云原生数据仓库AnalyticDB-PG和云数据库RDS-PG等核心产品中。Ganos目前拥有几何、栅格、轨迹、表面网格、体网格、3D实景、点云、路径、地理网格、快显十大核心引擎,为数据库构建了面向新型多模多态时空数据的存储、查询、分析、服务等一体化能力。
本文介绍的全空间数据多态分层存储能力,依托阿里云云原生关系型数据库PolarDB for PostgreSQL产品建设输出。
关于多态分层存储
业务背景
随着物理世界数字化快速发展,各行业数字化业务催生出对大规模全空间多模多态数据的快速处理需求,传统基于中间件实现方式遇到了空间计算性能的严峻挑战。为了进一步解决大规模全空间检索效率问题,推动空间业务全面在线,Ganos原生支持了全空间数据存储类型与海量的计算算子,以“空间计算全面下推”的方式极大程度提升了查询计算速度。
在提升计算效率的同时,我们也发现空间数据的大规模增长、空间对象的日益增大可能会给客户带来愈发沉重的成本问题,客户往往需要在成本与效率方面做出取舍,部分业务不得不重新迁回线下或者用离线任务的方式完成,业务开发不得不在多种存储介质中来回切换,运维与研发成本大幅度上升。因此,寻找一种兼顾效率、成本与易用性的全空间数据管理方案成为Ganos团队历时两年所重点解决的问题,两年中Ganos团队聚焦以下三个核心问题开展能力规划与建设:
- 如何让用户拥有更为廉价的数据库存储介质(非外表方式),降低用户成本;
- 如何保障这类廉价存储的查询计算效率不会有大规模衰减;
- 如何让客户用更为透明的方式管理与使用多种存储介质;
在日前结束的2023云栖大会Ganos专场,Ganos团队发布了基于PolarDB for PostgreSQL 14数据库构建的全空间数据多态分层存储能力,它将OSS对象存储直接作为一种更为廉价的数据库存储介质,可以与块存储联合使用,它支持用户将整库、单表甚至是表内的一个字段存储在不同介质上,同时保持了增删改查的全部透明,并通过多级缓存保障了性能的最少衰减。多态分层存储是一种多方兼顾的全空间数据管理方案,同时它一样也可以面向通用的数据库类型字段(blob、text、json、jsonb、anyarray等)使用,本文重点介绍多态分层存储的各项能力与最佳实践。
功能简介
PolarDB for PostgreSQL 14多态分层存储是传统冷热分离存储功能的一个升级功能,它既可以让用户享受优良的读写性能,又能将存储成本降低到极致。首先它支持数据按冷热不同层次分层存储,其次它可以支持OSS、MinIO、HDFS等多种形态的对象存储介质,另外它针对大对象类型、全空间数据类型提供灵活多样的存储组合,简称多态分层存储,当前多态存储只支持OSS对象存储,所以本文介绍内容都以OSS存储为例,更多使用说明请参考产品文档《冷数据分层存储》。
基于多态分层存储功能,用户可以通过简便的sql操作将过期数据、大对象数据、全空间数据等转存在OSS上,享受OSS带来的弹性、低成本和数据高可靠优势;转存后用户可以完全透明的进行增删改查及表表联合等复杂分析操作,无需做任何SQL改动;另外当数据更新访问频率增加时,可以通过动态调整物化缓存以达到跟数据库云盘同水平的访问性能。
技术优势
PolarDB for PostgreSQL 14实现的多态分层存储具有以下几方面的技术优势:
- 成本:支持压缩,平均压缩率50%,部分可达20%,成本降为1/10甚至更低
- 易用:数据由冷到热分层存储,冷存后支持增删改查SQL完全透明
- 性能:物化缓存层加速冷存数据访问,性能衰减可控制在20%-80%
- 可靠:借助OSS的高可靠性,冷数据在不增加存储的前提下支持快照,数据具备恢复还原的能力
- 灵活:灵活的冷热分层存储模式,支持按表、按大字段、按子分区分别存储在OSS中
- 成本维度
当今时代数据呈爆炸式增长,尤其是带有时间、空间等多维度的数据,随着时间的推移,积累的数据轻松上TB甚至PB级别,同时对于过往的数据或辅助性数据,其访问频率比较低,此时存储降本往往是用户的核心诉求。在PolarDB for PostgreSQL冷热分层存储架构中,我们先将冷数据按规则切成小块,再对切好的块数据进行压缩,最后写入OSS,达到通用数据压缩率20%-40%,时空数据压缩率60%-70%,平均压缩率50%的效果,相当于将存储于数据库云盘中的数据转存在OSS中同时数据体量再砍掉一半,最终达到存储成本降为1/10甚至更低的水平。
我们结合PolarDB云盘的计费标准、OSS单位存储的成本,以及OSS中实现的压缩比例,用百GB数据每月存储费用来做一对比,以此来形成直观的降本感受:
最终的存储费用不到原始的1/10。
- 性能维度
OSS访问延迟是数据库云盘访问延迟的上百倍,如果直接访问存储在OSS中的数据,其读写性能会有较大幅度降低。PolarDB for PostgreSQL14多态存储功能利用数据库云盘实现了OSS数据的物化缓存层,根据数据块实际访问情况自动分层存储,读写操作可首先命中物化缓存,确保了访问性能,整体更新和插入性能达共享盘90%,点查性能达共享盘80%,数据在物化缓存中的生命周期由访问频率决定,这样可以达到降低存储成本的同时拥有优良的访问性能。
- 易用性维度
PolarDB for PostgreSQL实现的多态分层存储是一种完全透明的冷热分层存储方案,使用起来比较简单。首先,数据转存至OSS后,支持数据的增、删、改、查操作,支持索引扫描以及联合查询等各种复杂操作,SQL完全透明,无需有任何的改动;其次,执行数据转存的操作也做到了简单易用,比如分区表可按自定义规则自动转存至OSS、基表与索引一键转存至OSS等。可参考文章的最佳实践部分。
- 可靠性维度
OSS的持久性可达99.9999999999%,可用性达99.995%,同时拥有本地冗余/同城冗余,确保了数据存放在OSS上的可靠性,因此已经存储在OSS中的数据是不是就无需做备份还原了呢?答案是否定的。对于数据库中存储在OSS中的数据,虽然更新、访问频率比较低,但也支撑了业务场景中重要的查询分析、数据挖掘等功能,一旦误改、误删后也需要能够快速的还原、恢复。PolarDB for PostgreSQL多态分层存储功能支持冷数据的备份恢复,以极小的成本做到数据的真正可靠。
- 灵活性维度
PolarDB for PostgreSQL多态分层存储功能支持冷热灵活搭配,具体有以下几种搭配模式:
- 冷归档:将表、索引等全部数据存储在OSS
- 表与索引分层:将整表数据存储在OSS中,索引存储在云盘中,降本后还能有良好的访问性能
- 表内字段分层:将表中的大字段、辅助性字段独立存储在OSS中,其余字段存储在云盘中
- 分区表子分区分层:只将分区表中过期子分区存储在OSS中,热分区存储在云盘中,这是最经典的冷热分离模式。这里面还能衍生出好几种组合,比如冷分区数据与索引都存入OSS中,温分区数据存入OSS但索引保留在云盘中,而热分区全部在云盘,使得查询性能基本无衰减。
基于全空间数据多态分层存储的最佳实践
实践一:分区表过期子分区自动冷存
场景描述:轨迹数据采用分区表存储,并按月进行分区,随着时间的推移,三个月之前的轨迹数据访问频率大大降低(过期),为了降低存储成本,需要数据库自动将超过三个月的分区表进行冷存处理。
准备工具:pg_cron插件
操作步骤:
(1)数据准备
--创建分区表 CREATE TABLE traj( tr_id serial, tr_lon float, tr_lat float, tr_time timestamp(6) )PARTITION BY RANGE (tr_time); CREATE TABLE traj_202301 PARTITION OF traj FOR VALUES FROM ('2023-01-01') TO ('2023-02-01'); CREATE TABLE traj_202302 PARTITION OF traj FOR VALUES FROM ('2023-02-01') TO ('2023-03-01'); CREATE TABLE traj_202303 PARTITION OF traj FOR VALUES FROM ('2023-03-01') TO ('2023-04-01'); CREATE TABLE traj_202304 PARTITION OF traj FOR VALUES FROM ('2023-04-01') TO ('2023-05-01'); --往分区表中写入测试数据 INSERT INTO traj(tr_lon,tr_lat,tr_time) values(112.35, 37.12, '2023-01-01'); INSERT INTO traj(tr_lon,tr_lat,tr_time) values(112.35, 37.12, '2023-02-01'); INSERT INTO traj(tr_lon,tr_lat,tr_time) values(112.35, 37.12, '2023-03-01'); INSERT INTO traj(tr_lon,tr_lat,tr_time) values(112.35, 37.12, '2023-04-01'); --创建分区表索引 CREATE INDEX traj_idx on traj(tr_id);
(2)创建polar_osfs_toolkit插件
在当前database中创建polar_osfs_toolkit插件,很多工具类函数就可以被使用了,包括主表与索引一键转存OSS、分区表一键转存OSS等。
(2)创建polar_osfs_toolkit插件
在当前database中创建polar_osfs_toolkit插件,很多工具类函数就可以被使用了,包括主表与索引一键转存OSS、分区表一键转存OSS等。
create extension polar_osfs_toolkit;
create extension polar_osfs_toolkit;
CREATE OR REPLACE FUNCTION auto_alter_sub_partition_to_oss( parent_table_name text, partcount int) RETURNS VOID AS $$ DECLARE sql text; sql2 text; part_cnt integer; part_name text; rec record; BEGIN --查询多少个子分区存储在本地盘 sql = format('select count(*) from pg_class a, pg_tablespace b where a.relkind=''r'' and b.spcname=''oss'' and a.reltablespace!=b.oid and a.relname like ''%s_%%'';', parent_table_name); execute sql into part_cnt; --如果大于3表示有多余3个月的分区存储在本地盘 IF ( part_cnt > partcount ) THEN -- 查出这些分区表中创建时间最早的分区表 sql = format('select relname from pg_class a, pg_tablespace b where a.relkind=''r'' and b.spcname=''oss'' and a.reltablespace!=b.oid and a.relname like ''%s_%%'' order by a.oid limit 1;', parent_table_name) ; execute sql into part_name; -- 将该分区表冷存处理 sql = format('alter table %s set tablespace oss;', part_name); execute sql; -- 将该分区表的索引(如果有)也冷存处理,如果索引不希望冷存可注释本条sql sql = format('select i.relname as relname from pg_index x join pg_class c ON c.oid = x.indrelid join pg_class i ON i.oid = x.indexrelid where c.relname = ''%s'';', part_name); for rec in execute sql loop sql2 = format('alter index %s set tablespace oss;',rec.relname); execute sql2; end loop; END IF; END; $$ LANGUAGE plpgsql;
(3)创建pg_cron插件
如果当前database无法创建pg_cron,则需要使用高权限账户连接到 postgres 数据库中创建pg_cron插件,高权限账户可以在PolarDB for PostgreSQL 14控制台界面创建。
--注意只有高权限账户可以创建该插件,并且需要连接到postgres这个数据库中执行创建 CREATEN EXTENSION pg_cron;
(4)制定定时执行任务
使用高权限账户连接到 postgres 数据库中执行以下 SQL,为 db01 数据库创建一个名为 task1 的任务,任务为调用存储过程将超过分区3的历史分区表自动转入OSS存储,函数将返回任务 ID:
-- 每分钟执行 postgres=> SELECT cron.schedule_in_database('task1', '* * * * *', 'select auto_alter_sub_partition_to_oss(''traj'', 3);', 'db01'); schedule_in_database ---------------------- 1 -- 每天的 10:00am (GMT) 执行 postgres=> SELECT cron.schedule_in_database('task2', '0 10 * * *', 'select auto_alter_sub_partition_to_oss(''traj'', 3);', 'db01'); schedule_in_database ---------------------- 2 -- 每个月的 4号 执行 postgres=> SELECT cron.schedule_in_database('task3', '* * 4 * *', 'select auto_alter_sub_partition_to_oss(''traj'', 3);', 'db01'); schedule_in_database ---------------------- 3
(5)查看执行结果及历史执行记录
--任务执行的结果就是分区表转存至OSS中,查看分区表的存储位置 db01=> \d+ traj_202301 Table "public.traj_202301" Column | Type | Collation | Nullable | Default | Storage | Compression | Stats target | Description ---------+--------------------------------+-----------+----------+-------------------------------------+---------+-------------+--------------+------------- tr_id | integer | | not null | nextval('traj_tr_id_seq'::regclass) | plain | | | tr_lon | double precision | | | | plain | | | tr_lat | double precision | | | | plain | | | tr_time | timestamp(6) without time zone | | | | plain | | | Partition of: traj FOR VALUES FROM ('2023-01-01 00:00:00') TO ('2023-02-01 00:00:00') Partition constraint: ((tr_time IS NOT NULL) AND (tr_time >= '2023-01-01 00:00:00'::timestamp(6) without time zone) AND (tr_time < '2023-02-01 00:00:00'::timestamp(6) without time zone)) Replica Identity: FULL Tablespace: "oss" --已经存储在oss了 Access method: heap --查看定时任务历史执行记录 select * from cron.job_run_details ;
这样就实现了按自定义规则自动将过期分区表转为冷存了,冷存后的分区表不再占用云盘存储空间,大大降低了存储成本,同时增删改查操作也完全透明。
实践二:单表大字段分层存储
场景描述:大字段包括blob、text、json、jsonb、anyarray以及Ganos时空类型的字段,本实践介绍如何将一张表中的大字段分离出来并独立存储在OSS中,并实现访问透明,以达到单张表中按字段维度冷热分离降本的目的。
(1)创建包含大字段的表
这里以text类型为例,其余类型大字段使用方法类似。
CREATE TABLE blob_table(id serial, val text);
(2)设置大字段存储位置
alter table blob_table alter column val set (storage_type='oss');
注意事项:该语法计划在12月下旬上线,目前如果已经创建的表想使用该功能,可访问Ganos官网下方扫码加入技术支持群或提交工单进行技术支持处理。
(3)写入数据并查看存储
--插入数据,此时val字段完全存储在OSS中 INSERT INTO blob_table(val) VALUES((SELECT string_agg(random()::text, ':') FROM generate_series(1, 100000))); --查看val字段存储位置 WITH tmp as (select 'pg_toast_'||b.oid||'_'||c.attnum as tblname from pg_class b, pg_attribute c where b.relname='blob_table' and c.attrelid=b.oid and c.attname='val') select t.spcname as storage_engine from pg_tablespace t, pg_class r, tmp m where r.relname = m.tblname and t.oid=r.reltablespace; storage_engine ---------------- oss (1 row)
注意事项:只有先调用Alter column的SQL设置大字段的存储位置之后写入的数据才会真正存储至OSS,已有的数据表如果希望将某个大字段转存至OSS,可以先调用Alter column的SQL设置大字段的存储位置,然后执行vacuum full,将数据重新写入,如果数据量较大需要注意执行时间会比较久,执行过程中该数据表无法提供读写服务。
实践三:时空分析场景如何实现降本增效(高级进阶案例)
(本案例为高级进阶实践,用户可选择性的忽略中间的处理步骤直接关注最后的对比结果)
时空分析指利用数据库能力对全空间、时空、时序场景的数据进行数据挖掘、统计分析等功能,本小节以遥感影像统计分析为例,介绍时空分析场景如何利用多态分层存储功能降低存储成本,同时也能拥有良好的分析效率。
场景描述:遥感影像(栅格)数据在空间业务中应用越来越广泛,遥感数据数据体量规模又比较大,且往往存在影像浏览与分析统计等多种业务,针对分析统计类的非实时业务,存储成本的降低且易用性提升往往更具有吸引力,本实践介绍如何用低廉的OSS存储支撑遥感影像入库管理,并提供同样高效的统计分析功能。
(1)数据准备
准备四幅landset遥感影像数据(用户可自行准备此类数据)
- LC08_L1TP_114029_20200905_20200917_01_T1.tiff
- LC08_L1TP_114028_20191005_20191018_01_T1.tiff
- LC08_L1TP_113029_20191030_20191114_01_T1.tiff
- LC08_L1TP_113028_20190912_20190917_01_T1.tiff
(2)数据入库
--连接进入PolarDB for PostgresSQL,创建rastdb数据库 CREATE DATABASE rastdb; \c rastdb --创建Ganos Raster扩展 CREATE EXTENSION ganos_raster CASCADE; --导入影像数据 CREATE TABLE raster_table (id integer, rast raster); INSERT INTO raster_table VALUES (1, ST_ImportFrom('rbt','/home/postgres/LC08_L1TP_113028_20190912_20190917_01_T1.TIF')); INSERT INTO raster_table VALUES (2, ST_ImportFrom('rbt','/home/postgres/LC08_L1TP_113029_20191030_20191114_01_T1.TIF')); INSERT INTO raster_table VALUES (3, ST_ImportFrom('rbt','/home/postgres/LC08_L1TP_114028_20191005_20191018_01_T1.TIF')); INSERT INTO raster_table VALUES (4, ST_ImportFrom('rbt','/home/postgres/LC08_L1TP_114029_20200905_20200917_01_T1.TIF'));
(3)统计数据占用的存储空间
在Ganos中,Raster类型是一种元数据和块数据单独存储、统一管理的模式,基表中存储了影像的元数据,块表中存储了影像的块数据,为了方便统计块表中数据大小,这里需要手动创建存储过程:
CREATE OR REPLACE FUNCTION raster_data_internal_total_size( rast_table_name text, rast_column_name text) RETURNS int8 AS $$ DECLARE sql text; sql2 text; rec record; size int8; totalsize int8; tbloid Oid; BEGIN size := 0; totalsize := 0; --查询raster对象的块数据表 sql = format('select distinct(st_datalocation(%s)) as tblname from %s where st_rastermode(%s) = ''INTERNAL'';', rast_column_name, rast_table_name, rast_column_name); for rec in execute sql loop sql2 = format('select a.oid from pg_class a, pg_tablespace b where a.reltablespace = b.oid and b.spcname=''oss'' and a.relname=''%s'';', rec.tblname); execute sql2 into tbloid; if (tbloid > 0) then size := 0; else --统计每张数据表的大小 sql2 = format('select pg_total_relation_size(''%s'');',rec.tblname); execute sql2 into size; end if; totalsize := (totalsize + size); end loop; return totalsize; END; $$ LANGUAGE plpgsql;
创建完成后,执行统计,结果表示目前影像数据占用的数据库存储空间在1.2GB左右。
rastdb=# select pg_size_pretty(raster_data_internal_total_size('raster_table','rast')); pg_size_pretty ---------------- 1319 MB (1 row)
(4)统计NDVI
数据存储在云盘,我们先做一次NDVI统计,统计时需要嵌套将多幅影像数据进行mosaic,利用mosaic拼接的一幅完整影像进行NDVI计算,并统计耗时:
create table rast_mapalgebra_result(id integer, rast raster); rastdb=# INSERT INTO rast_mapalgebra_result select 1, ST_MapAlgebra(ARRAY(select st_mosaicfrom(ARRAY(SELECT rast FROM raster_table ORDER BY id), 'rbt_mosaic','','{"srid":4326,"cell_size":[0.005,0.005]')), '[{"expr":"([0,3] - [0,2])/([0,3] + [0,2])","nodata": true, "nodataValue":0}]', '{"chunktable":"rbt_algebra","celltype":"32bf"}'); INSERT 0 1 Time: 39874.189 ms (00:39.874)
(5)影像块数据冷存处理
将遥感影像的块数据做冷存处理存入OSS,遥感影像的元数据依旧存储在云盘中,为了方便处理,需要创建存储过程:
CREATE OR REPLACE FUNCTION raster_data_alter_to_oss( rast_table_name text, rast_column_name text) RETURNS VOID AS $$ DECLARE sql text; sql2 text; rec record; BEGIN --查询raster对象的数据表 sql = format('select distinct(st_datalocation(%s)) as tblname from %s where st_rastermode(%s) = ''INTERNAL'';', rast_column_name, rast_table_name, rast_column_name); for rec in execute sql loop sql2 = format('alter table %s set tablespace oss;',rec.tblname); execute sql2; end loop; END; $$ LANGUAGE plpgsql;
创建完存储过程后,执行冷存处理,冷存后再次统计块数据表占用的云盘存储空间:
select raster_data_alter_to_oss('raster_table', 'rast'); --统计冷存后块数据在共享盘占用的空间 rastdb=# select pg_size_pretty(raster_data_internal_total_size('raster_table','rast')); pg_size_pretty ---------------- 0 bytes (1 row)
结果显示,共享盘统计的存储空间为0,说明此时块数据都已经存储在OSS中了。
(6)重新统计NDVI值
数据块存储在OSS中后,重新做一次NDVI统计计算:
rastdb=# INSERT INTO rast_mapalgebra_result select 2, ST_MapAlgebra(ARRAY(select st_mosaicfrom(ARRAY(SELECT rast FROM raster_table ORDER BY id), 'rbt_mosaic','','{"srid":4326,"cell_size":[0.005,0.005]')), '[{"expr":"([0,3] - [0,2])/([0,3] + [0,2])","nodata": true, "nodataValue":0}]', '{"chunktable":"rbt_algebra","celltype":"32bf"}'); INSERT 0 1 Time: 69414.201 ms (01:09.414)
(7)存储成本及性能表现对比
对比项 |
云盘存储 |
OSS冷存 |
对比值 |
存储成本 |
1319MB 按1GB/月,费用为1.2元 |
1011.834MB 按0.13GB/月,费用为0.13元 |
10 : 1 |
NDVI统计耗时 |
39s |
69s |
1 : 1.76 |
对比结果显示,利用OSS冷存,费用只需共享盘的1/10,计算性能降低控制在1倍以内,这个性价比对于不追求RT的统计分析场景来说还是非常高的。
总结
目前,Ganos已经发展到了v6.0版本,支撑了数十个行业领域的数千个应用场景,稳定、成本、性能与易用性一直Ganos长期坚持的目标,全空间多态存储能力是Ganos在PolarDB-PG数据库上打造的内核级核心竞争力,它为全空间数据管理提供了真正兼顾成本、性能与易用性的方案,欢迎各位用户开通体验。