【跨国数仓迁移最佳实践2】MaxCompute SQL执行引擎对复杂类型处理全面重构,保障客户从BigQuery平滑迁移

本系列文章将围绕东南亚头部科技集团的真实迁移历程展开,逐步拆解 BigQuery 迁移至 MaxCompute 过程中的关键挑战与技术创新。本篇为第二篇,跨国数仓迁移背后 MaxCompute 的统一存储格式创新。

注:客户背景为东南亚头部科技集团,文中用 GoTerra 表示。

业务背景和痛点

随着大数据与AI时代的快速发展,全球数据总量已经突破了百ZB量级,其中半结构化(Json等)和嵌套数据结构(PB等)占比超50%,传统技术处理此类数据,面临存储成本高,计算效率低等问题日益凸显,亟需高效的性能优化技术。当前主流大数据处理引擎框架普通采用复杂数据类型(struct、map、array)来灵活处理半结构化等数据,相关功能支持和处理性能成为现代数据架构技术竞争焦点。

Google BigQuery和阿里云MaxCompute作为全球领先的商业化数据处理平台,均历经十余年产品和技术迭代。其中MaxCompute日均处理EB级数据和数千万作业,服务覆盖了全球多行业客户,因此也一直在持续丰富复杂类型的功能完备和性能优化。在‌GOTO项目从BigQuery迁移至MaxCompute‌的过程中,‌复杂数据类型的处理能力对标‌成为关键任务,需确保功能和性能‌持平甚至超越‌BigQuery,以实现作业平滑迁移与资源成本优化。

技术现状

简单介绍三种典型复杂数据类型特征和使用场景:

  • Array类型: 一组同类型元素的集合(如array<bigint>),支持下标索引访问元素,广泛应用于存储和处理列表形式的数据,例如商品列表,多值属性等
  • Map类型:键值对集合(如map<bigint, string>),支持通过键直接访问值,典型场景包括动态属性、键值配置等
  • Struct类型: 包含多个字段的复合类型(如struct<id:bigint, value:string>),每个字段具有独立名称和类型,支持通过字段名访问元素,常用于用户画像标签、订单信息等

MaxCompute支持复杂类型的现有能力和瓶颈:

  • 存储层:支持复杂类型列式存储(Aliorc文件格式),显著降低存储成本,具体用法参考官网介绍。
  • 计算层: 支持行式复杂类型数据处理。尽管功能完备,但性能仍有较大优化空间。

由此可见,一条复杂类型数据处理,需经历列式读取,换转成行式计算,再转换成列式写入,多次转换开销巨大,并且大部分算子行式处理数据性能也较差,导致部分场景性能相比BigQuery存在一定的差距。

为彻底解决优化这些痛点,MaxCompute SQL执行引擎对复杂类型处理进行了全面重构,整体支持复杂类型列式的内存存储结构,对各个算子进行深度适配优化,整体处理性能实现质的飞跃,部分场景提升超10倍,基本追平Bigquery对复杂类型的计算处理性能,且在某些场景实现性能超越,最终保障了GOTO项目海量作业的平滑迁移,同时大幅节省计算资源

技术方案简述

这里主要介绍两个核心优化重构,一个是复杂数据类型的计算优化重构,另一个是Unnest with subquery的框架优化重构,这两项优化在复杂类型场景普遍都带来1-10倍以上的性能提升。

列式复杂数据类型的计算优化重构

优化分成两个阶段:

  1. 各算子基于当前行式复杂类型结构持续优化计算逻辑,减少不必要的拷贝和计算。
  2. 将行式结果彻底改造成列式结构,并适配各算子列式计算模式。

行式复杂类型浅拷贝优化

现有算子接收到行式复杂类型的输入数据,进行计算后输出,需全量数据深拷贝,对于数据量大的复杂类型,开销极高。因此数据结构和处理过程进行深入优化,支持绝大部份处理流程进行浅拷贝优化,只要数据不发生变更,只需拷贝数据引用即可,不涉及数据本身的拷贝,优化覆盖表达式/聚合/Join/Window等主要算子,部分场景提升高达100倍+,效果显著

复杂类型列式内存结构和计算优化重构

虽然行式优化取得了不少进展,但部分痛点依然存在,比如计算过程效率低下,无法高效适配向量化计算;计算过程中子元素列裁剪无法生效;每行数据的内存结构复杂,且需存储重复的辅助结构,严重制约整体计算性能。

为了显著提升性能,彻底改造复杂类型内存存储和计算框架,由行式转为列式内存结构,推动各个算子采用高效的列式计算思想适配优化,整体性能得到质的飞跃,部分场景提速超10倍。

列式内存结构重构

如上图所示,重构之后,每行复杂类型数据不再单独存储,而且采用类arrow结构,对于Batch(多行数据集合)中某一列复杂类型,相同位置的子元素连续内存存储,因此每个Batch仅需存储一份辅助结构(如列名等),有效节省内存空间,而且子元素数据连续存储,内存访问效率也显著提升。

算子适配列式复杂类型计算

列式结果改造完成后,各SQL算子需进一步适配优化,由于行式和列式计算方式差异显著,此部分优化近乎完全重构,并针对不同场景进行了精细化适配。

列式复杂类型计算模式可分为以下三类:

  • 数据透传模式: 数据从源表列式读取后,中间处理无变更,全程零拷贝传输,最后直接列式写入目标表,性能优化到极致。常见于数据搬迁,简单列裁剪数据清洗等场景。
  • 数据追加模式: 算子一次性处理复杂类型数据后顺序输出到列式结构中,不会再次修改,可有效利用内存缓存优化和向量化处理提升整体性能。常见于表达式/Window/Join等主要算子。
  • 数据修改模式: 算子需多次随机修改复杂类型数据,此模式不太适合列式内存结构,其存储的多行数据内存是连续的,如果随机修改中间某一行数据,可能破坏后继其他行数据的内存结构,因此此场景需退化成行式处理。常见于聚合函数处理等少数算子。

Unnest with subquery 的框架优化重构

BigQuery的Unnest(array)操作极为常见,输入参数通常是一个Array复杂类型,负责把Array中每一个子元素展开为一个单独行进行输出,GOTO项目大量任务使用此操作,迁移到MaxCompute之后,由于缺乏原生支持,需自动转换成等效的Lateral View + Explode操作来执行。但当Unnest嵌套于SQL子查询等较为复杂用法时,MaxCompute处理会变得极其复杂,性能急剧下降。

下面举个例子:

create table src(a bigint, b array<struct<c:bigint, d:string>>);select(select max(c) from unnest(b)),a+100,(select collest_list(d) from unnest(b) where d='test')
from src; 

上述SQL示例中,MaxCompute执行时会转换成如下Plan来执行(示意图):

可见此Plan主要存在如下问题:

  • 同一源表多次读取
  • 相同unnest操作重复执行
  • 需多次复杂Join拼接数据

因此如需大幅提升性能,需在语法解析,Plan构建,算子计算等各流程对此场景完全重构优化。

SQL Plan优化

为了优化上述三个问题,重构优化后的Plan如下图所示(示意图),

  1. 仅读取一次源表,按需把读取数据列推送至各Subquery计算
  2. 定义新的CorrelatedJoin操作,负责把每个Subquery定义成一个单独的internal subplan,然后按行对齐和拼接每个Subquery的计算输出列,整行输出,消除计算量很重的多次Join操作
  3. 定义新的internal subplan结构,本质是一棵OperatorTree, 并做针对性的Plan优化改造,如消除shuffle操作
  4. 对于多个subquery中相同Unnest操作,支持子树合并,消除冗余的unnest操作(在优化中)。

经过上述优化后,基本可解决之前的性能痛点,贴近性能最佳的计算Plan,后续还需要SQL执行层进行计算适配。

SQL执行层优化

主要负责执行物理Plan,并发处理数据,输出结果,优化重构主要分以下几步:

  1. 适配优化后的新物理Plan,解析为可执行算子
  2. 实现新CorrelatedJoin算子,负责驱动internal subplan执行,并且把各subplan执行的结果按行对齐和高效拼接,然后输出整行结果到后续算子执行
  3. 实现新的internal subplan数据处理框架,需支持按照每行复杂类型进行一次性处理和输出的语义,跟通用算子执行框架差异显著。

上线效果和业务价值

提升效果案例

列式复杂类型重构优化性能提升案例

上图示例展示的是用户某个作业的其中一个Stage M6的处理过程,TableScan1从源表中读取了多层嵌套的复杂类型数据,经过了TableScan / Project / TableFunction / Shuffle等算子处理,基于行式复杂类型结构进行处理,M6 Stage整体耗时超过5分钟,切换到优化后的列式复杂类型处理,整体Stage耗时缩短到31秒,速度提升了10倍+,效果极为显著。

Unnest with subquery重构优化性能提升案例

上图示例展示了用户某个作业经过Unnest With subquery重构优化后,整体作业性能提升了80%+,由于整体sql比较复杂,这个优化只涉及到其中部分stage,其中命中优化的算子提速3倍+,效果极为显著。

业务价值

GOTO整体项目,包含复杂类型的表数量超4万张,日均处理复杂类型的SQL作业超20万+,通过上述优化重构之后,大部分SQL性能提升20%~10倍+,日均减少2000+ Cpu Core消耗,极大的助力了GOTO项目按时保质的从Bigquery平滑迁移到MaxCompute,实现降本增效的目标。

后续会持续完善打磨列式复杂类型在各个场景的数据处理优化,并全面推广到MaxCompute平台所有业务场景中,预计全域上百万作业将显著受益,大幅节省业务计算成本,技术普惠到所有用户。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/news/915474.shtml
繁体地址,请注明出处:http://hk.pswp.cn/news/915474.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

react(基础篇)

React由Meta公司研发&#xff0c;用于构建Web和原生交互界面的库。 React 官方中文文档 查看JSX &#xff08;一&#xff09;React组件 用户界面的一部分&#xff0c;通俗的来讲&#xff0c;最小的元素组成的单元&#xff0c;可以实现部分逻辑与功能 房子的门就可以看成一个…

数据结构-哈希表(一)哈希函数、哈希表介绍、优缺点

哈希表 哈希函数哈希表使用了哈希函数来完成key到地址的快速映射&#xff0c;所以在了解哈希表之前&#xff0c;需要先明白哈希函数的概念和特点。 哈希函数的定义 哈希函数 哈希函数是一种将任意长度输入的数据&#xff0c;转换成固定长度输出的算法哈希函数H可以表示为yH(x) …

Shader开发(一)什么是渲染

前言在现代游戏开发和计算机图形学领域&#xff0c;渲染技术是连接虚拟世界与视觉呈现的关键桥梁。无论你是刚接触图形编程的新手&#xff0c;还是希望深入理解渲染原理的开发者&#xff0c;掌握渲染的核心概念都是必不可少的第一步。什么是渲染&#xff1f;渲染&#xff08;Re…

策略模式+工厂模式(案例实践易懂版)

最近,可以说这2025年度,自己更文的次数都大大减少,主要最近大环境不景气,自己职业也受到波及,学习的东西也是因为AI而变得更多, 没办法,你不学,总有人会学,关于AI的我也准备出个专辑,相信绝对帮助到大家 额,好像说多了,言归正传,我们看一下今天的主题:策略模式工厂模式 本文主要…

【NLP舆情分析】基于python微博舆情分析可视化系统(flask+pandas+echarts) 视频教程 - snowNLP库实现中文情感分析

大家好&#xff0c;我是java1234_小锋老师&#xff0c;最近写了一套【NLP舆情分析】基于python微博舆情分析可视化系统(flaskpandasecharts)视频教程&#xff0c;持续更新中&#xff0c;计划月底更新完&#xff0c;感谢支持。今天讲解snowNLP库实现中文情感分析 视频在线地址&…

大根堆,小根堆,双指针

码蹄集OJ-大约 #include<bits/stdc.h> using namespace std; priority_queue<int>max2,maxDel; priority_queue<int,vector<int>,std::greater<int>>min2,minDel; const int N1e51; int n,result0,a[N]; int main( ) {cin>>n;for(int i1…

RS485和Modbus

UART协议中&#xff0c;空闲状态为高电平&#xff0c;也就是1,R25和R27&#xff0c;485收发器特性MAX485 (美信)SSP485 (国产替代)AZRS3080 (安格)供电电压5V5V3.3V ~ 5.5V静态电流300μA (接收模式)120μA (接收模式)150μA (接收模式)传输速率2.5Mbps10Mbps20Mbps总线负载能力…

【Android】交叉编译faiss库 | 问题解决

目录 一 解决 FAISS 交叉编译到 Android 时的 BLAS/MKL 依赖问题 二 交叉编译faiss ■禁用 BLAS并交叉编译faiss ■使用 OpenBLAS 的 Android 移植版本并交叉编译faiss 三 报错处理 ■报错 ■SWIG 一 解决 FAISS 交叉编译到 Android 时的 BLAS/MKL 依赖问题

《使用 IDEA 部署 Docker 应用指南》

使用 IDEA 部署 Docker 应用的详细步骤 一、创建 Dockerfile 配置文件 在项目根目录下创建Dockerfile文件&#xff0c;配置内容如下&#xff1a; # 使用官方的OpenJDK镜像作为基础镜像 FROM openjdk:17-jdk-slim# 设置维护者信息(可选) LABEL maintainer"三木豪"# 设…

【Docker#3】Window 和 Linux 上 docker安装 相关知识

前置了解&#xff1a; X86 高并发&#xff1a;基于 x86 架构的处理器&#xff0c;在高负载下处理大量并发请求的能力。ARM &#xff1a;使用 ARM 架构处理器的移动设备&#xff0c;具有低功耗和高性能的特点。 操作系统&#xff1a; CentOS&#xff1a;基于 Red Hat Enterprise…

一次 POI 版本升级踩坑记录

前言 结论先行。 开发过程中由于可能涉及到二次开发&#xff0c;若原系统开发时间久远&#xff0c;没有达成一致规范设计&#xff0c;导致风格各异&#xff0c;确实满足当时开发场景&#xff0c;但增大了后续的更新的难度&#xff0c;容易出现俄罗斯套娃现象&#xff0c;新的更…

硬件设计学习DAY13——电源缓冲电路设计全解

每日更新教程&#xff0c;评论区答疑解惑&#xff0c;小白也能变大神&#xff01;" 目录 一.缓冲电路介绍 1.1缓冲电路的作用 1.2寄生参数的来源 1.3缓冲电路的类型 1.4常见缓冲电路设计 1.5设计原则 二.吸收与缓冲 2.1吸收与缓冲的核心作用 2.2电压尖峰与吸收措…

鸿蒙搜狐新闻如何在Native调用ArkTS方法

01前言鸿蒙作为一款新兴的智能操作系统&#xff0c;现在适配鸿蒙系统的应用越来越多&#xff0c;同时会面临三端兼容问题&#xff0c;如同一产品功能&#xff0c;需要维护iOS、Android、鸿蒙三端代码。拿文件上传、下载功能场景举例&#xff0c;同时要适配iOS、Android、鸿蒙三…

Java行为型模式---中介者模式

中介者模式基础概念中介者模式&#xff08;Mediator Pattern&#xff09;是一种行为型设计模式&#xff0c;其核心思想是通过一个中介对象来封装一系列对象之间的交互&#xff0c;使各对象不需要显式地相互引用&#xff0c;从而降低耦合度&#xff0c;并可以独立地改变它们之间…

Python爬虫实战:研究Korean库相关技术

一、引言 1.1 研究背景与意义 随着韩流文化在全球的传播,韩语网页内容急剧增加。韩国在科技、娱乐等领域的信息具有重要研究价值。然而,韩语独特的黏着语特性(如助词体系、词尾变化)给信息处理带来挑战。传统爬虫缺乏对韩语语言特点的针对性处理,本研究旨在开发一套完整…

表单校验--数组各项独立校验

写需求时遇到一个这样的问题&#xff0c;就是校样项是多个的&#xff0c;但是其字段名称相同这时我们可以这样校验&#xff0c;注意字段之间的关联性<div v-for"(item,index) in formData.hospitalDoctorList" :key"item.key || index"><el-form-…

基于SpringBoot和leaflet-timeline-slider的历史叙事GIS展示-以哪吒2的海外国家上映安排为例

目录 前言 一、哪吒2的海外之路 1、海外征战历程 2、上映国家空间查询 二、后端接口的实现 1、模型层的实现 2、上映时间与国家 3、控制层的实现 三、基于leaflet-timeline-slider的前端实现 1、时间轴控件的引入及定义 2、时间轴绑定事件 3、成果展示 四、总结 前言…

tar 解压:Cannot change ownership to uid 1000, gid 1000: Operation not permitted

tar 解压 tar.gz 压缩包报错&#xff1a; # tar xzf $INPUT_FOLDER/archive.tar.gz -C /mnt/test-nas/[..] tar: xx.jpg: Cannot change ownership to uid 1000, gid 1000: Operation not permitted原因是用普通用户执行的解压缩脚本&#xff0c;用root用户执行tar解压缩&…

腾讯客户端开发面试真题分析

以下是针对腾讯客户端开发工程师面试问题的分类与高频问题分析&#xff08;基于​​105道问题&#xff0c;总出现次数118次​​&#xff09;。按技术领域整合为​​7大类别​​&#xff0c;按占比排序并精选高频问题标注优先级&#xff08;1-5&#x1f31f;&#xff09;&#x…

线上问题排查之【CPU飙高100%】

目录 案例 发现问题 排查问题 步骤一 步骤二 步骤三 案例 import java.util.concurrent.TimeUnit;/*** 简单写一个CPU飙高的案例*/ public class CpuLoadUp {// 这里定义了一个标识private volatile static int flag 0;public static void main(String[] args) {// 执行…