对于数据库系统而言,存储容量和数据安全是影响用户系统的成本及信息安全的重要因素。TDSQL PG版通过支持压缩来减少磁盘空间使用以此来节省成本,同时支持对表内容加密来保证用户数据的安全性。
探索前沿研究,聚焦技术创新。本期DB·洞见由腾讯云数据库高级工程师丁艳飞从工程创新的角度,为大家介绍TDSQL PG版透明压缩和透明加密的相关实现,主要包括整体架构介绍、压缩方法简介、透明压缩实现、加密脱敏介绍四个部分。
观看视频,获取完整版讲师分享内容
公众号福利:本期讲师课件获取方式,腾讯云数据库公众号后台回复“洞见丁艳飞课件”即可。
TDSQL PG 版整体架构
TDSQL PG版是一款分布式数据库,在内核层面由GTM、CN、DN节点组成。其中GTM是事务管理器,负责协调整个集群的事务,并管理全局的对象。CN节点是协调节点,是业务访问入口,每个节点之间相互对等,都能对外提供一致性视图。DN节点是数据节点,业务运行过程中产生的数据都会在DN节点上进行存储。
数据库内核配合OSS系统可以对外提供指标监控、运维管理、实时告警、安全审计、数据治理等运维管控能力。随着业务的不断进行,所产生的数据会越来越多,对DN节点的磁盘存储也造成越来越大的压力。数据节点的存储容量也越来越成为制约整个IT系统部署密度、影响系统成本的主要因素。所以目前大多数客户都会要求数据库系统具备数据压缩存储的能力。
压缩方法简介
针对压缩需求,在数据库领域出现了一些特定的压缩方法,其中一种就是在存储介质中进行压缩。这种方式会在原有的SSD内部引入一个计算引擎。数据按照常规方式来写入SSD,盘内的计算引擎可以对写入的数据进行压缩处理,再把压缩后的数据写入到存储物理介质中。整个压缩过程对业务透明,上层业务基本无感知,应用方面也可以不进行修改。此外,这种方式相当于零拷贝技术,不需要做额外的数据传输,当数据量增长较多时,也可以很方便地增加多块盘来实现压缩能力的线性扩展。
传统的关系型数据库(使用B-Tree数据模型,如Oracle/MySQL/SQL Server/PostgreSQL)可以直接部署在这种智能存储介质上使用,且不存在占用业务cpu的问题,使用方便,对性能影响小。但目前而言,很少有客户使用,主要原因是这些存储介质的价格昂贵,整体部署成本大,基本上很难实现大批量使用。
另一种常用的压缩方法是使用数据编码。数据库关系表中存放的一般是结构化数据,同一列中字段具有相同的数据类型且有明确的数据边界。对于不同的列,如果它们之间有相互关系,在这些列之间可能也会存在较多相似的数据。所以我们可以利用数据明确的字段边界和丰富的类型信息,采用一定的编码技术来实现数据库的压缩。
下表中罗列了部分常见的编码方式,包括字典编码、RLE编码、常量编码、差值编码、前缀编码等。不同的编码有不同的适配场景,字典编码是较为通用的编码方式,RLE编码则主要用于连续相等的数据,常量编码主要针对近似性常量化的数据,差值编码适用于在小值域范围内分布均匀的整数型数据,前缀编码则适用于前缀相同的字符型数据。
右边是一个数据库使用数据编码的简单例子,是用字典编码对页面进行压缩的一个实例。当页面满或者页内行的数量达到阈值后,通过数据编码进行压缩,较为常用的做法是把重复性较高的数据进行去重,将去重后的数据建立字典存储在页面固定区域,而把原来存放数据的地方存成指向特定字典下标的引用。但这种方案最大的问题是频繁更新对性能影响较大。频繁更新会导致页级字典的频繁重建,与此对应的页面数据的引用也需要频繁变动。除此之外,在实现上还需要在页内增加字典区域,对现有页面冲击大,实现复杂。
第三种方法是使用通用的压缩算法对数据库中的数据进行压缩。常见的压缩算法会把输入的数据当作连续的字节流进行处理,再基于一些经典的编码算法比如串编码、嫡商编码来实现对信息的压缩。下表中列举了部分常见的压缩算法,不同的压缩算法追求的目标不同,比如lz4算法追求快速的压缩和解压的速度,而zstd算法则追求更高的压缩比。
在数据库中,buffer里存储的原始页面一般具有固定大小。这种方式的优点是,在将页面写入到磁盘中时能较好地计算每一个页面在文件中的偏移位置,以方便对这个页面读或者写。但对页面引入压缩算法后就会出现另一种情况,因为不同的页面存储的文件内容不同,所以采用压缩算法后,实际压缩后的大小是不固定的。即使对于同一个页面,随着增删改的进行,压缩之后的数据大小也有可能变化。所以当引入压缩后,再重新去磁盘上读取相同的页面时,页面的偏移位置就会发生变化,这样会对整个数据库层面的读写产生巨大的冲击。
为了解决这个问题,通常在数据库领域会新增一个新的压缩页面类型。这个新的压缩文件里每个页面的大小都是固定不变的,一般会配置成原始页面的二分之一、四分之一或者八分之一,以此来存储压缩之后的数据。但这种实现方式也存在问题,它的压缩主要受限于压缩页面大小的配置。如果压缩页面配置较大,当前的数据量比较小,压缩之后所占用的空间也比较小,但由于压缩页面比较大,所以写入到磁盘文件中时还是要占一个压缩页面大小,这就达不到节省磁盘空间的目的。
但如果把压缩页面设置的比较小,则会存在另外一个问题。原有的页面有可能需要多个压缩页面进行存储,所以一个原始的页面会分裂成多个压缩页面,需要额外的原数据信息进行额外的存储。在读取时,需要在原有的压缩页面解压后,再把多个页面重新拼装成为原始页面,因此又增加了实现上的复杂度。
透明压缩实现
我们的目标是实现数据文件的压缩以此来节省存储空间,且方案实现要简单且适合OLTP场景。通过前文的介绍,我们先把智能存储介质排除,因为它属于磁盘层面的压缩方案,不属于数据库本身,暂时不作考虑。同时我们也可以排除数据编码,它不适合于频繁更新的场景,且会对现有页面的结构造成较大的冲击,不满足代码低侵入性的要求,因此我们把实现基调定在通用压缩方法上。
通用压缩方法存在前文提到的问题,即页面压缩后大小不固定,因此偏移也不固定。如果存在一种方法能使页面压缩后,还能按照原页面固定大小进行偏移的查找,又能把压缩后节省的空间释放出来,整个方案的实现会简单很多。这时我们可以采用文件系统本身提供的打洞能力。文件系统的打洞能力是保证文件其他属性不变的情况下,告知文件系统在指定偏移的位置上有一段空间不再需要,可以主动释放该文件所占用的物理空间。
找到这种方式后,我们把最终的压缩方案定为使用通用压缩加文件打洞来实现。整个方案以页为压缩粒度,在页面落盘时先使用压缩算法对页面进行压缩,压缩完后按照原有的页面偏移的计算方式找到页面应该写入的位置,将压缩后的数据写到固定的位置中。对于固定页面的大小,除去压缩数据后剩余部分所占用的空间,我们可以利用打洞工具将剩余磁盘空间释放。在读取页面时,从磁盘上读取后先进行数据的解压,再将解压后的页面放入buffer中。因此整个压缩解压在buffer层向上完全透明,在代码实现层面满足低入侵要求。
为了提高压缩速度,在刷脏页时我们采用多进程并行的方式进行压缩。在syncbuffer时,轮询压缩进程,将脏页id加入待压缩队列,压缩进程的主循环发现队列中有数据,就会对相应页面进行压缩,再将压缩后的页面存储在slot中。Syncbuffer将脏页id加入待压缩队列后不会等待压缩完成,而是去访问已压缩完成队列,将队列里对应的页面加入buffer id list中,再对list中的页面进行刷盘。因此页面压缩采用多线程并行,而压缩和落盘可以看作是一个pipeline的过程,以此来加快速度。
除此之外,我们也对Vacuum整理页面时的压缩进行了优化。因为元组更新时不是原位更新,会在原有的元组上做删除标记,再插入一个新的元组。这样在页面里就会存在dead tuple,因此auto Vacuum的进程会定期对页面进行整理,将页面中有效的元组按照原有的组织方式从页面尾部进行排列和插入。对其他的dead tuple,则会在line pointer上做标志,表示它们可以重用。在页面整理后,没有用的空闲空间可以进行置0操作,以此提高整个页面中的数据重复度。
右图是一个简单的测试。我们对表进行不同程度的更新,从20%逐渐到100%。我们发现在全表更新的情况下,优化后比优化前可以节省30%的磁盘空间。
为了让备机也支持压缩,我们也做了相应适配。备机的信息来源渠道主要是主机的日志,因此备机获取压缩信息的场景之一就是FULL PAGE IMAGE。在页面第一次做修改前,在FULL PAGE WRITE的机制下,我们会把页面记录到日志里。备机在应用到这种类型的日志时,可以获取到页面上记录的压缩信息,在备机页面落盘前根据压缩信息进行页面的压缩,再进行打洞处理。
另一种场景是备机在回放其他日志时可能产生一些新的页面。假如在回放一个插入操作时,当前页面空间不足,需要新增一个页面来进行插入,这时备机会初始化一个新的页面。或者当备机在回放新建索引日志、索引分裂、元数据初始化的操作时,都会涉及到页面初始化动作。备机本身没有压缩信息,因此要想得到压缩信息,就需要在主机里对相应日志类型增加压缩信息,给备机进行应用。这样备机在初始化页面后,就可以把压缩信息设置在页面的页面头上,后续就可以按照正常流程进行操作,在实际的落盘前再根据页面头上的压缩信息进行压缩处理。
除了备机外,我们也需要对数据库相对应的配套做压缩的支持。
一是备机重建。备机重建是使用pg_basebackup工具,读取对应的主节点上的数据文件,再把该数据文件备份到指定目录下。因为数据文件经过打洞处理,在读取这个数据文件时,它会把每个页面对应的压缩信息读取上来,对空洞部分进行零的填充,这样一来,备份好的文件每个页面的大小就等于没有压缩前的大小。为了应对这种重建的压缩情况,在获取每个表文件后,我们会把该表文件的每个页面读取上来,根据页面上记录的压缩后的大小,找到空洞对应的偏移位置,在指定的偏移位置上重新进行打洞处理。为了提高处理的速度,我们对多个文件采取了并行处理。
二是压缩信息查看。为了方便查看压缩信息,我们对pageinspect进行了相应的适配。它能够读取指定页面上的信息。在读取时先将文件读取,再读到buffer里进行解析和显示。对压缩而言,我们不需要buffer里的信息,我们真正需要的是磁盘中实际存储压缩的信息,因此我们在插件里增加了一些函数,可以支持从磁盘中直接把相应页面读上来,再去解析页面头,再显示对应的压缩信息。
三是压缩率获取。因为我们通过文件系统的打洞功能来实现压缩,而文件经过打洞后,使用普通的stat或者ll命令读取的大小是包含空洞大小的,而真正存储所占用的大小则不包括空洞大小,因此可以利用这两个的实际比值来获取压缩比。
四是参数设置。我们可以对表和索引分别指定不同的压缩算法,同时也可以查看整个表的信息。在信息显示里,我们的option选项能列出压缩的情况。如果想要查看每个页面的情况,我们也提供了函数支持,可以列出对应页面里压缩算法和压缩后页面的大小。如果想要查看整个表的压缩概述,我们也提供了函数支持,可以看出当前表有多少页面是压缩的,有多少页面没有压缩。
我们对压缩和非压缩情况做了tpcc、pgbench测试,可以对比压缩前、压缩开启后产生的影响。我们在测试过程中把shared buffer设置得比较小,构造出页面频繁换入换出的场景,在这种情况下对页面做频繁的压缩和解压操作。从测试结果可以看到,在tpcc模型下压缩比可以达到2.3,ptmC有15%的新的下降。在pgbench模型下,压缩比能达到6.8,tpcB有约17%的下降。以我们目前采取的压缩策略来看,采用通用压缩算法,本质上是以时间换空间,因此性能劣化在我们预期之内。
加密脱敏介绍
TDSQL PG版整体的安全体系是以数据库三权分立为基础的。我们把传统的DBA角色分解成安全管理员、审计管理员、数据管理员。在安全规则方面,我们针对安全管理员增加了安全规则和数据透明加密、脱敏的规则;在审计方面,我们结合业界的审计标准和业务的场景需要,增加对象审计、用户审计、SQL审计;数据管理员则履行之前DBA数据权限管理和数据库运维的职能。
通过三个角色的划分,我们从根本上杜绝了系统的安全死角。安全管理员负责整体安全规则的制定,通过这种方式来约束系统中所有的角色。审计管理员则负责制定审计规则、设计系统中包括审计管理员在内的所有角色,做到系统的所有操作都具有可追溯性。数据管理员负责数据库的日常运维。这三个管理员之间相互约束,相互监督。
在这部分我们主要分享数据透明加密、脱敏的规则。加密主要针对数据文件本身的泄露问题。目前常见的数据库的存储格式基本属于半公开状态,只要拿到了数据文件,再配合相应的数据程序,他人就可以解出里面对应的数据。在这种情况下,我们需要通过数据中心对一些有保密要求的文件进行加密,防止泄漏,因此需要把存储的文件进行加密。
TDSQL PG版支持表级加密和列级加密两种方式。通过下图,我们可以看到表级加密的流程和压缩的流程非常相似。一般是在页面进行落盘前,先进行加密处理,再进行落盘,在磁盘里存储的都是经过加密后的文件。当需要读取时,我们会从磁盘上读取后先进行解密,再把解密后的数据放在buffer里,所以用户在读取时可以直接从buffer里获取明文数据。列加密的不同之处在于,列加密会在一行数据进行插入时将这一列值先进行加密,加密完后再插入到buffer的页面中,所以buffer里是加密的状态,磁盘里存的也是加密的文件。当用户需要使用这一列或这个页面时,我们就会对相应的这列先进行解密,再给用户进行明文显示。
脱敏对应的使用场景是,在访问数据时,部分用户没有权限看到这些数据的明文,但为了进行运维操作或其他工作需要访问这些数据,但他们不需要知道这些数据真正的存储结果。针对这种场景,我们可以在磁盘里存储正常的明文数据,在用户进行读写时进行处理。如果在用户读取时,发现是授权用户,就可以直接从buffer里拿到正常的数据进行显示;但如果发现是非授权用户,就需要先对数据进行脱敏处理,把脱敏后的结果展示给非授权用户。
加密和脱敏之间互不冲突,可以灵活地搭配使用,既可以对存储内容进行加密,也可以针对不同的用户,采用用户自己设置的脱敏规则来保证数据访问的隔离性。
下图是加密和脱敏的简单示例。在这个例子中,非授权用户是分公司经理和人力资源经理,授权用户是董事长。对同样的表格而言,董事长能看到所有信息的原始状态,薪酬和家庭信息也能正常显示。但对非授权的分公司经理而言,在薪酬和家庭信息方面,他查看到的都是脱敏后的结果,例如他只能看到薪酬为0,家庭信息为空。通过这样的方式,系统里不同等级的用户对同样的数据会看到不同的视图,以此达到隔离的效果。
加密和压缩流程相似,因此我们也支持加密和压缩的同时使用。在这种情况下,我们整体处理逻辑不变,在原始页面进行落盘前先进行压缩和加密处理,在读取时进行解密和解压的处理。但需要注意的是,我们要保证先进行压缩再进行加密。这主要考虑到压缩对于结构化的数据能够达到较好的效果,而数据页面本身存储的就是相对结构化的数据,如果把原页面作为输入源,压缩效果会相对较好。但如果把原页面先进行加密,加密后得到的数据是相对杂乱的数据,再把相对杂乱的结果作为压缩的输入源,压缩效果就会大打折扣。因此为了保证压缩和加密的正常使用,我们在处理时需要先进行压缩再进行加密。与此相对应,读取时也是从磁盘读取后先对页面进行解密,再把解密后的数据进行解压,再放入到buffer里,以此保证数据正常的读和写。
TDSQL PG版起源于技术成熟、功能强大的PostgreSQL,在此基础上腾讯云数据库构造和发行了功能更丰富、稳定性更好、兼容性更广、安全性更高、性能更强、扩展性极好的分布式数据库TDSQL PG版产品。
腾讯公司对TDSQL PG版具有完全自主知识产权,实现安全可控, 具备在中高端市场规模化替代国外数据库的能力,在数据库基础软件层面有力支撑了国家安全可控战略发展。当前TDSQL PG版已经在金融、保险、通信、税务、公安、消防、政务等多个行业的核心交易系统上线运行,为众多行业客户提供优质服务。
一键预约下期洞见
7月20日(周三)19:00 - 20:00,DB·洞见带大家学习向量化执行,本期邀请到腾讯云数据库高级工程师胡翔为大家介绍向量化执行的最新技术创新、基本原理以及向量化引擎的相关实现。快来预约学习吧!
﹀
﹀
﹀
-- 更多精彩 --
基于LSM-Tree 的分布式组件化 KV 存储系统 | DB·洞见回顾
↓↓点击阅读原文,了解更多优惠