ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

TCLake+EMR实战:构建AI-Ready数据湖仓底座的架构与调优

TCLake+EMR实战:构建AI-Ready数据湖仓底座的架构与调优 AI 时代的数据底座到底应该长什么样这个问题我琢磨了很久也踩过不少坑。传统数仓太重数据湖又太散等到真要喂模型、跑训练、做 RAG 检索时才发现底层的存储和元数据根本扛不住大规模高并发访问。最近腾讯云把 TCLake 和 EMR 放到一起推了一套面向 AI 场景的“数据湖仓”方案定位是打造 AI-Ready 的数据底座。我第一时间在测试环境里完整跑了一遍从架构拆解到参数调优从数据入湖到权限管控把整个链路都摸了一遍。这篇文章就把我的实操过程、踩坑记录和思考整理出来给正在做数据湖仓选型或者准备给 AI 应用搭数据底座的朋友一个参考。1. 为什么数据湖仓突然要“AI-Ready”1.1 传统数据平台在 AI 场景下的三个“扛不住”我最早接触数据湖仓是在做离线数仓的时候那时候的思路很朴素数据先落到 HDFS再用 Hive 跑批最后同步到 ClickHouse 或者 Doris 供报表查询。这套架构支撑业务报表没问题但一旦转到 AI 场景问题就接踵而至。第一个扛不住是小文件风暴。AI 训练和特征工程产生的数据和传统 ETL 产生的数据形态完全不一样。特征工程动不动就产出几百万个小文件每个文件只有几十 KB。传统 HDFS 的 NameNode 面对海量小文件时性能直线下降元数据内存被撑爆集群整体可用性大打折扣。我见过最夸张的情况是一个特征表目录下堆了 800 多万个文件NameNode 的 Full GC 频繁到每分钟一次整个集群的作业全部处于等待状态。第二个扛不住是高并发元数据访问。AI 场景下多个训练任务、多个 Notebook、多组特征流水线会同时读取同一份数据集。传统 Hive Metastore 加上 HDFS NameNode 的组合在这种高并发读场景下就是瓶颈本身。NameNode 是单点Metastore 虽然可以做读写分离但一旦底层表的元数据量大起来查询响应时间会从毫秒级恶化到秒级再恶化到分钟级。第三个扛不住是存储与计算耦合带来的弹性问题。传统 HDFS 集群扩容缩容特别痛苦数据要重新平衡节点要停机维护。而 AI 训练任务有非常明显的波峰波谷特征白天做特征验证晚上跑大规模训练周末可能完全空闲。如果存储和计算绑在一起你就得为波峰时刻的存储需求去付全时段的计算节点费用这笔账怎么算都不划算。这三个问题叠加在一起指向一个共同的方向存储计算分离并且元数据服务要能独立弹性扩展。这正是 TCLake 这套方案要解决的核心问题。1.2 从“数据湖”到“数据湖仓”名字背后的架构变迁在讲 TCLake 之前得先把“数据湖仓”这个概念理清楚。数据湖和数据仓库本来是两条路线数据仓库强调 schema、事务、一致性适合做 BI 报表数据湖强调原始数据存储、灵活性、低成本适合做探索性分析。但 AI 场景把这两条路给拧到一起了你既需要数据湖那种“什么格式都往里扔”的灵活性又需要数据仓库那种“元数据可靠、权限可控、查询高效”的治理能力。数据湖仓Lakehouse就是把数据湖的存储灵活性和数据仓库的管理能力结合起来在低成本对象存储之上构建一套具备事务、索引、权限管控能力的数据平台。TCLake 在腾讯云的体系里就是干这件事的底层用 COS 对象存储承接海量数据中间层通过一套兼容 HDFS 协议的用户态文件系统让上层计算引擎无缝接入再加上一套弹性元数据服务来处理海量文件和高并发访问的元数据压力。这个架构的变化本质上是把原来 HDFS 的“目录 块”管理模式改成了“对象 元数据服务”的模式。目录结构变成了一种逻辑视图真正的物理存储是扁平的桶对象。理解这一点后面讲 TCLake 的技术细节就顺了。2. TCLake 到底做了什么三个关键层的技术拆解2.1 用户态 HDFS不用改代码就能迁移存储TCLake 最巧妙的设计是提供了一层兼容 HDFS 协议的用户态文件系统。这层文件的实现思路我在测试环境里研究了一下它本质上是一个跑在用户态的 FUSE 协议实现通过把 POSIX 文件操作转换为对象存储 API 调用让上层引擎看到的还是/user/hive/warehouse/xxx这种熟悉的 HDFS 路径。为什么要做“用户态”而不是直接改内核这里有个很实在的考虑。内核态文件系统比如原生 HDFS kernel driver开发和调试周期长兼容性难以保证而且一旦出问题影响的是整个节点。用户态实现的好处是可以用标准的 FUSE 接口对接软件更新升级都方便出了问题也只是挂载在这台机器上的文件系统失效不会波及内核。我在实践中的体会是这层兼容层最大的价值是迁移成本极低。原来在 HDFS 上跑的 Spark、Flink、Hive、Trino 任务几乎不需要改代码只需要把路径前缀从hdfs://namenode:8020/换成cosn://bucket/或者挂载后的本地路径再配一下 Hadoop 的core-site.xml就能无缝切过去。整个迁移过程可以用灰度方式做先切几个测试任务验证通过后再把生产任务批量切换。一个需要注意的点是用户态文件系统虽然用法接近 HDFS但底层毕竟是对象存储强一致性和文件语义上还是有差异。比如 HDFS 支持文件的 append 操作而对象存储通常不支持直接追加写。TCLake 的做法是对小文件先做本地缓存合并满足一定大小后再上传到 COS这样既解决了 append 语义问题也顺便缓解了小文件问题。理解这个机制对后面调优写入性能非常关键。2.2 弹性元数据服务把“单点”变成“集群”传统 HDFS 的 NameNode 是元数据服务的典型单点瓶颈。TCLake 的思路是把元数据服务独立出来做成一整套可以横向扩展的分布式服务。这套服务在功能上对标 Hive Metastore 加上 NameNode 的职责但底层变成了分布式 KV 存储加缓存层。这套元数据服务最关键的设计是区分了热元数据和冷元数据。热元数据比如近期频繁访问的表、分区、文件列表放在内存缓存里响应速度可以到毫秒级冷元数据比如几个月前的历史分区信息放在后端存储里按需加载。这样一来即使整个湖里有几亿个文件只有经常被访问的那部分会占用内存资源成本和性能都得到了平衡。在测试中我验证了一个很典型的高并发场景同时启动 20 个 Spark 任务读取同一张宽表每个任务并行度 1000也就是同时有 2 万个 executor 在访问元数据服务。在传统 Hive Metastore 架构下这个量级基本会把 thrift server 打到超时TCLake 的元数据服务因为有缓存层和水平扩展能力整体响应仍然稳定在几十毫秒级别这个差距非常直观。但要注意这并不意味着元数据服务是万能的。如果表的文件数量失控或者分区数爆炸任何元数据服务都会受影响。不同之处在于TCLake 给了你更多缓冲空间——原先 HDFS 在 100 万文件级别就告警现在这个数字可以推到千万级别甚至更高。不过小文件治理仍然是运维层面不能放松的事后面我会详细讲怎么做。2.3 存储加速缓存让“读取”不再成为瓶颈对象存储的硬伤是延迟比本地 HDFS 高尤其是随机读场景。AI 训练和特征读取的模式本身就是反复扫描大量数据如果每次都直接从 COS 拉取网络开销和请求延迟会很可观。TCLake 在 EMR 集群侧做了一个存储加速缓存层。简单说就是利用计算节点上的本地磁盘SSD 或者 NVMe把从 COS 读取的热数据缓存下来。第一次读取走网络后续读取直接命中本地磁盘命中率高的场景下读性能甚至能接近 HDFS 水平。这个缓存层的设计我做了一个类比就像开一家咖啡店供应商仓库在郊区对象存储你在店里设置了一个小冰柜本地缓存早上把最热卖的几款牛奶提前放进冰柜客人来了直接拿不用每次跑郊区。具体放多少在冰柜里取决于你的客流量规律和你愿意为冰柜付多少电费。实操中的两个关键配置项一个是缓存容量另一个是缓存策略。缓存容量建议给到数据总量的 10%~20%太少了命中率上不去太多了挤占 YARN 资源。缓存策略建议对训练集、特征集这类反复读取的数据开启全量缓存对临时表则关闭或者使用 LRU 淘汰策略。这块参数我在后面的实操章节会给出具体的配置示例。3. 从 0 到 1 的落地实践配置、流程与参数3.1 环境规划与集群搭建我先说一下我测试环境的整体规划大家可以根据自己的业务规模做缩放。我用的版本是腾讯云 EMR 3.x配套 TCLake 组件计算集群选择了 10 台 CVM 机型每台配置 32 核 128GB 内存本地挂载了 1 块 500GB 的 NVMe SSD 作为缓存盘。存储侧用的是 COS 标准存储开了一个独立的 bucket 用于 TCLake 数据湖。网络规划上有一个容易被忽略的点EMR 集群和 COS bucket 一定要在同一个地域最好是在同一个私有网络 VPC 内网互通。虽然走公网也能访问 COS但延迟和带宽完全不是一个量级。我有一次在做跨地域测试时读性能直接下降了一个数量级后来才定位到是地域不匹配的问题。集群搭建本身不复杂在控制台上几步就能完成。需要注意的是一开始就要把 TCLake 和元数据服务的组件勾上后面补装会比较麻烦。核心配置项我整理成了下面的表格配置项推荐值说明缓存持久化目录/data/tclake_cache建议单独挂载一块 SSD不要和系统盘共用最大缓存容量数据总量的 15%按实际工作集的命中率调整元数据服务 JVM 堆内存32GB 起步如果表数量超过 10 万张建议 64GB 以上COS 访问并发数默认 200可按 IOPS 调整过高会导致 COS 限流过低会浪费计算资源小文件合并阈值64MB小于该大小的文件会触发合并优化任务3.2 建库建表与数据入湖一个完整的操作示例环境搭好之后我完整地走了一遍“创建库表、数据入湖、查询验证”的流程。TCLake 兼容 Hive 语法所以建表的方式和 Hive 几乎一样核心是配置好 Location 指向 COS 路径。-- 创建数据库location 指向 COS CREATE DATABASE IF NOT EXISTS ai_feature LOCATION cosn://my-lakehouse-bucket/warehouse/ai_feature.db; -- 创建特征表使用 Hudi 格式支持增量更新 CREATE TABLE IF NOT EXISTS ai_feature.user_embedding ( user_id STRING, feature_version STRING, embedding ARRAYFLOAT, update_time TIMESTAMP ) USING HUDI TBLPROPERTIES ( primaryKey user_id,feature_version, preCombineField update_time, hoodie.bucket.index.num.buckets 50 ) LOCATION cosn://my-lakehouse-bucket/warehouse/ai_feature.db/user_embedding;这套建表方式的最大的好处是既保留了 Hive 生态的兼容性又引入了事务表的能力。primaryKey和preCombineField这两个属性是 Hudi 做 upsert 的关键我建议每个 AI 特征表都必须定义这两个字段否则后续做增量更新时要么全表重写要么数据重复调起来非常痛苦。数据入湖我这里用两套方式做了对比验证。第一套是离线批量入湖用 Spark 读取源数据然后写入 Hudi 表第二套是实时增量入湖用 Flink 消费 Kafka 数据流直接写入 TCLake。两条链路都跑通了但要注意的坑不一样。批量入湖的核心是并行度规划。AI 特征数据往往是多张表 join 后的结果写入目标表时要根据目标表的分桶数设计 Spark 的spark.sql.shuffle.partitions否则会产生大量小文件。我测试时源数据有 2TB目标表设置了 50 个 bucket把 shuffle 分区也调到 50写入结果就是每个 bucket 对应一个文件非常整齐。实时入湖的坑则主要在checkpoint 和幂等性上。Flink 写入 Hudi 表时消息重复消费场景下如果没有正确的 primaryKey 和 preCombineField就可能出现重复数据。另外Flink CDC 任务重启时要保证 Kafka offset 和 Hudi 表的写入状态一致否则数据就错乱了。我的建议是固定 checkpoint 路径并且不要频繁调整并行度否则状态恢复会比较麻烦。// Spark 批量写入示例 val df spark.read.parquet(/data/raw/user_behavior) .filter(dt 2024-06-01) df.write .format(hudi) .option(hoodie.datasource.write.recordkey.field, user_id) .option(hoodie.datasource.write.precombine.field, update_time) .option(hoodie.table.name, user_embedding) .mode(Append) .save(cosn://my-lakehouse-bucket/warehouse/ai_feature.db/user_embedding)3.3 关键参数调优这些配置决定性能上限跑通流程只是第一步真正决定生产环境性能的是参数调优。我在测试中反复调整了一批核心参数这里挑影响最大的几个展开讲。缓存参数这块EMR 的 TCLake 加速缓存是在core-site.xml里配置的。核心是缓存目录和缓存策略命中率是衡量配置是否合理的黄金指标。我在测试里给一张训练集表大约 800GB开启全量缓存后第二次执行的扫描任务耗时为首次执行的 35% 左右说明缓存命中带来的收益非常可观。如果你的任务大多数是单次扫描比如每天全量重跑的训练任务那么缓存的意义就没那么大可以适当调低缓存容量把资源让给计算。!-- core-site.xml 中 TCLake 缓存相关配置 -- property namefs.cosn.cache.dir/name value/data/tclake_cache/value /property property namefs.cosn.cache.max.size/name value150000/value !-- 单位 MB约 150GB -- /property property namefs.cosn.cache.policy/name valuelru/value /propertyJVM 和 GC 参数这块我吃过一次亏。EMR 上跑 Spark 作业时executor 的堆内存默认是 16GB但如果启用了 TCLake 的缓存一些元数据缓存和 shuffle 的临时数据会占用额外堆外内存。主要表现在作业跑到一半时executor 频繁 Full GC任务执行时间翻倍。后来我在 Spark 配置里把spark.executor.memoryOverhead从默认的 384MB 调到了 2GBFull GC 频率显著下降。如果你的任务主要是扫描大表建议直接把 overhead 调大。并发参数方面TCLake 访问 COS 的并发数直接影响了吞吐能力。在 EMR 控制台或者 core-site.xml 里可以配置 COS 的访问并发上限。这个值并不是越大越好并发过高会导致 COS 的请求队列堆积反而增加延迟。我测试下来对 32 核的节点单个节点并发在 100~150 之间表现最优超过 200 之后吞吐基本不再增长反而偶尔出现限流报错。3.4 权限与数据安全AI 场景下的“被忽视的基线”AI 场景的数据安全往往是被忽视的因为模型训练和特征工程看起来不像业务报表那样“正式”。但实际风险恰恰更高特征数据往往包含用户标签、行为偏好等敏感信息。TCLake 与 EMR 组合的权限体系底层是依托 COS 的 CAM 策略和计算引擎的 Ranger 策略。我在测试环境里建了三类角色数据工程师可读写、算法工程师只读特征表、审计人员仅查看权限。通过 Ranger 配置策略把表级别的读写权限分开并且对敏感字段如手机号、身份证设置了脱敏策略。这里有一个值得强调的经验权限策略宁可先收紧再逐步放开。AI 项目迭代快很多人会习惯性地授权“全部读写”出了问题才开始补漏洞。我的做法是初始化的时候就建好最小权限集合后续按需提权。另外建议大家开启 COS 的访问日志并定期做一次权限审计。4. 性能对比与收益量化用数据说话4.1 和传统 HDFS 方案的对照测试好话说了不少但选型最终还得看数据。我在测试环境里做了一系列对比测试用同一个 TPC-DS 数据集和同一组 SQL 查询分别跑在“传统 HDFS 标准 EMR”和“COS TCLake EMR”两套环境上。两套环境的计算规格保持一致都是 10 台 32 核 128GB 的节点避免因机器差异造成偏差。结果如下表所示测试场景HDFS 方案耗时TCLake 方案耗时差距全表扫描100GB 单表4 分 50 秒3 分 05 秒缓存命中提升约 36%复杂 JOIN 查询10 表关联12 分 20 秒11 分 08 秒缓存命中提升约 9%并发查询20 个查询同时跑16 分 40 秒11 分 25 秒提升约 31%数据入湖1TB 写入8 分 21 秒7 分 54 秒基本持平全表扫描之所以差别那么大核心原因就是缓存命中。第一次扫描时两套环境表现其实是持平的但第二次开始 TCLake 的本地缓存就发挥作用了。如果工作负载有反复扫描同一份数据的特点这个差距只会在迭代中被不断放大。但也要说一个实事求是的差距纯随机点查场景TCLake 的查询延迟大概是 HDFS 的 1.5~2 倍。原因也很简单对象存储的单个请求延迟本来就高于本地磁盘加上 FUSE 用户态层多一次上下文切换随机读的代价比 HDFS 高。所以如果你们的核心负载是大量随机点查比如在线服务直接查特征建议在 TCLake 之上再加一层 OLAP 引擎比如 Doris 或者 ClickHouse来承接点查流量把湖仓底座定位成“供数平台”而不是“在线查询引擎”。4.2 成本账存储与计算分离到底省多少钱做成本分析时传统的 HDFS 方案需要预留 3 倍存储空间做副本而且存储资源与计算节点强绑定没法独立缩容。TCLake 的数据放在 COS 标准存储上COS 本身自带多副本和跨可用区容灾不需要你手动配副本因子。我算了一笔账假设数据总量是 200TB净存储需求 200TB。HDFS 方案下按 3 副本算实际需要 600TB 物理存储按照当时测试机器磁盘的成本折算每月光是存储成本就相当于 12 台高性能存储型节点的费用。而且这台集群如果跑不满计算资源也在持续计费。TCLake 方案下200TB 数据放 COS 标准存储按实际用量计费初期成本大约是 HDFS 方案的 1/2 到 1/3。更重要的是计算集群可以在非训练时段缩容到 2 台节点甚至关机第二天训练前再扩容回来。这种弹性能力带来的边际节省在真正跑生产任务时会非常明显。成本收益还有一个隐性维度运维人力。HDFS 集群要处理 NameNode 元数据膨胀、磁盘坏道更换、节点数据均衡等日常操作这些工作每个月都会吃掉不少人天。而对象存储这些都由云厂商兜底了你只需要关注计算集群这一层运维复杂度大幅下降。5. 常见问题与排查技巧实录5.1 元数据性能瓶颈有一次我跑一个涉及 3 万张分区表的分析任务ETL 任务明明已经提交成功但一直卡在 pending 状态。检查发现是任务在获取表分区元数据时耗了过长的时间。TCLake 的元数据服务虽然有缓存但首次访问一张冷表时还是要从后端拉取全量分区信息如果分区数达到数万级别这个初始加载过程就会成为瓶颈。排查思路是先看元数据服务的监控指标确认是否存在缓存抖动再看表的文件分区数。如果是冷表首次访问可以提前执行一条MSCK REPAIR TABLE或者REFRESH TABLE温表把元数据预加载。如果表长期存在大量查询就要考虑把缓存内容调大。5.2 小文件问题从源头到治理小文件问题在数据湖场景里是个绕不开的话题。虽然 TCLake 的对象存储层面不太怕小文件但小文件会影响计算引擎的 task 调度效率和元数据服务的内存占用。我在实践中有两套解法一套是想办法避免产生小文件一套是定期做合并。源头避免上核心是控制写入并行度。Spark 写入时设置合理的spark.sql.shuffle.partitions让每个分区产出的文件尽量接近目标大小。Flink 写入时用 Hudi 的hoodie.parquet.small.file.limit参数控制小文件合并阈值并开启 clustering 异步任务。定期治理上TCLake 配套的 EMR 里可以跑一个合并作业。我定的策略是每天凌晨对前一天产生的增量数据做一次小文件合并阈值设为 64MB运行完检查文件数量和大小分布比原始状态改善非常明显。合并作业本身也会消耗计算资源建议配到比生产任务更低的优先级避免抢资源。5.3 数据倾斜与训练任务读取炸内存AI 场景的数据读取倾斜问题比传统 ETL 更隐蔽。特征数据天然就是长尾分布的比如热门用户的行为数据远超普通用户如果不做处理按 user_id 分区存表时某些分区会远超其他分区。训练任务读取时负责处理这些大分区的 executor 就会出现 OOM 或者严重的任务倾斜。我给用户行为特征表做 Hudi 分桶时选择把分桶键从 user_id 改成user_id feature_version并对热门 key 做加盐处理。加盐是给 key 拼上一个随机后缀后重新分桶读的时候再做聚合。这个方法对缓解训练数据读取倾斜非常有效代价是查询阶段需要多做一层去重聚合。不过训练任务本来就是全量扫描这点额外开销可以接受。5.4 权限同步与“幽灵表”还有一个容易踩坑的地方是权限同步。TCLake 的权限体系横跨 CAM 和计算引擎两者之间的同步有时会出现短暂的不一致。表现是用 Spark 作业读取一张表时用的是计算引擎的 Ranger 权限但直接通过 COS 控制台访问该表的数据文件用的却是 CAM 权限。如果两套权限配置不一致就可能出现“调整了 Ranger 策略但 COS 侧仍然拒绝访问”或者反过来。我的处理办法是把“配置基线”定在 CAM 层也就是先在 COS bucket 策略里定义好整体访问边界哪些子账号能访问哪个前缀然后业务引擎的 Ranger 策略在此基础上做细分。这样即使 Ranger 策略配错了底层还有一个 CAM 兜底不会出现越权的风险。另外要提一下“幽灵表”问题在 TCLake 中直接通过 COS API 删除文件后元数据服务里还保留着表信息查询时就会发现数据“找不到了”。这个我真实遇到过最后是通过修复元数据服务里的表定义才恢复。所以删除数据时一定要用 SQL 的方式DROP TABLE或者 TCLake 提供的删除接口不要绕过元数据服务直接去桶里删文件。6. 一些个人实操体会整套方案跑下来我的感受是TCLake EMR 的组合本质上是在“对象存储的弹性成本”和“HDFS 生态的兼容性”之间找到了一个平衡点。它不是把 HDFS 简单替换成对象存储而是把元数据、缓存、权限这些真正决定体验的中间层做了重构。在 AI 数据底座这个场景下这个重构成立而且效果明显。最后分享一个小技巧要不要上这套方案不要只听厂商的宣讲也不要只看测试报告。最好的办法是把你们最核心的一个训练数据集拿过来跑一次一个周末的对比验证——三件事一是算清楚存储成本差异二是对比缓存命中下的读取性能三是确认你们的计算引擎能不能无缝迁移。实践数据会告诉你答案。数据底座没有“放之四海而皆准”的最优解只有适不适合你们业务形态的解法。如果你们恰好也在评估 AI 数据底座的选型希望这篇文章的实操记录能帮你少走几步弯路。
返回列表