火山引擎云数据库 veDB 在字节内部的业务实践( 三 )


在MySQL里面 , 复杂查询基本都是单线程执行 , 而单线程的任务一般比较重 , 包括解析、优化、数据读取和执行 , 计算存储分离会导致跨网络读取页面的时延大大增加 。 如果复杂查询出现catchmiss , 即在缓冲区找不到数据 , 就需要从存储区拉取数据 。 这种情况频繁发生 , 性能会变差 。 另外 , 单库容量也大大增加了 , 数据容量从原来的几T变成了现在的几十T , 也加大了复杂查询的负担 。解决方案
计算操作(部分)直接下推存储层:当执行任务时 , 团队会去查询B+数的倒数第二层节点 , 得到需要分发的页面ID情况 , 并且把任务并行分发给存储层;并行任务分发:团队引入了并行执行查询算子 , SQL层完成分发之后 , 可以通过算子收集结果 , 汇聚并返回给计算层;回表查询页面精准预取:对于回表查询 , 比如查完二级索引之后 , 需要去主键索引上查询 , 团队会做预取 。 火山引擎云数据库 veDB 在字节内部的业务实践
文章图片
基于以上方案 , veDB也取得了不错的效果 。 比如对于count(*)操作 , 在不增加计算层CPU负担的情况下 , veDB实现了5倍到100倍左右的提升;同时 , 以TPCH(100G)的数据集测试为例 , 对于缓冲区命中率低和命中率高的两种情况而言 , 从最坏到最好的情况 , 会有2倍到10倍的提升 。秒杀场景优化
在类似于秒杀、限时抢购、抢红包等场景下 , 大量用户需要在极短时间内请求商品或者红包 , 此时数据库将面临单行记录大并发更新的老大难问题 。 通常 , 这会导致系统活跃线程增加、TPS降低、时延增加、系统吞吐降低 。 针对这种情况 , 目前有两种解决方案 , 其一是不依靠数据库 , 在应用层进行处理 , 但是这种方案较为复杂;其二就是依靠数据库来解决问题 。
解决方案
SQL语句更新列热点标识:对于带有热点更新标识的SQL , 团队会在数据库内部维护一个哈希表 , 会将相同数据的SQL放在哈希表的一个队列里;批量处理:经过一段时间之后 , 团队会针对队列里的SQL进行合并处理;语法糖:为了更好地满足业务需求 , veDB支持打开Binlog , 以及语法糖 , 比如:Auto_Logic_Commit_Rollbackhint , 是指成功就自动提交 , 否则Rollback;另外veDB扩展Update..returning , 支持语句返回 。 火山引擎云数据库 veDB 在字节内部的业务实践
文章图片
基于以上操作 , 实施效果如何呢?以一个20c的虚拟机为例 , 单行更新性能能够达到9.3wQPS , 多行更新性能能够达到14.7wQPS 。 同时 , 团队目前已经上线了电商直播、抖音的本地生活 , 在业务直播带货过程中 , 即使热点商品QPS突然猛增到1.5万 , veDB数据库也能够提供足够的支持 。
计算引擎写能力如何扩缩容?
对于一写多读的架构 , 是如何做到多写呢?团队结合了字节内部的分库分表的中间件与veDB , 提供Multimaster支持 。 但是分库分表的Multimaster在写流量较大的场景时(例如618或双11活动)会面临集群扩容的问题 , 活动结束后又会面临集群缩容的问题 。 传统基于中间件的分库分表方案扩容需要复制数据 , 会面临两个问题:其一是耗时与数据量成正比 , 其二是还需要一倍的冗余空间 , 因此需要扩容 。 另外 , 分库分表目前没有一个有效的在线缩容方案 , 会导致在大型网络活动结束后 , 没有办法及时缩容 。
解决方案由于计算存储架构分离 , 数据都在存储层 。 veDB引入共享表的概念 , 共享表可以在不同的数据库实例之间进行共享 , 而不同的实例在MySQL都有相同的spaceID 。 同时 , 共享表的所有权(包括读和写)在任何时候都属于一个实例 , 它可以在实例之间动态切换 , 团队仅需要变更元数据的信息 , 无需移动数据 。