公司动态

不起 Spark,把 CSV/Excel 直接写成 Iceberg 表:10 万行 2~5 秒

📅 2026/8/21 8:36:00
不起 Spark,把 CSV/Excel 直接写成 Iceberg 表:10 万行 2~5 秒
数据平台有一类被低估的需求:用户手上的维表、字典表、名单,本来就不在任何数据源里——它们躺在 Excel 里。「导入数据源的表」接不上,离线集成作业也接不上,最后往往是用户找工程师手工建表灌数。「我的数据空间」(datastudiohappy.cn)为此做了一条独立通道:本地表格文件直接变成 Iceberg 湖表,且出来的表与平台建的任何表没有区别——可查询、可授权、有血缘、能配 TTL、能进语义模型。这篇讲这条通道的核心选型:装载为什么不经 Spark,以及不经 Spark 之后正确性怎么守住。一、为什么不丢给 Spark直觉方案是收到文件后起一个 Spark 作业去装载,但账算不过来:这条通道的输入天生有上限——HTTP 上传,文件不会大到哪里去;spark-submit光冷启动(申请资源、拉容器、起 JVM)就要30~60 秒,用户传一个 2MB 的字典表要等一分钟,体验是荒谬的;后端用 Iceberg Java API 直写 Parquet,10 万行 2~5 秒出表。代价是资源要自己框住:没有了 Spark 的资源隔离,解析和写入发生在服务进程里,必须给这条通道立架构边界而非经验值——单文件 200MB、500 万行、512 列、并发装载数 2,超限显式拒绝并引导走离线集成,绝不静默截断。「这条路是为小文件设计的」要成为一个被代码强制的事实,而不是一句注释。二、正确性上的四个刻意选择不经引擎,类型系统和事务语义就得自己对齐,这里有四个容易做错的点:1. 类型推断与文本解析必须是同一套规则。推断阶梯为boolean → 整数 → decimal/double → date → timestamp → string,关键在于:推断某列是不是某类型,判据就是「用装载时的那个解析器去解析样本」。两套逻辑哪怕差一个大小写、一个空白符的处理,就会出现「预览正常、导到第 8 万行炸」——预览说它是日期,装载器解析不了。2. 定点小数推decimal(p,s),不推double。CSV 里的小数大多是金额和比率,double 一进来就带上了浮点尾差,后续所有聚合都不再精确。精度 p/s 按样本实测取;装载时小数位超过声明精度报错,不静默四舍五入——错要错得响。3. 中文表头规范化,原文一个字不丢。中文列名在下游(SQL、BI、血缘)到处要转义。非法字符转下划线,整段没剩下合法字符的退化成col_N,原始表头完整落到列注释——规范化解决工程问题,注释保住业务语义。4. 要么全进,要么全不进。数据分批写 Parquet,但一次性提交为单个 Iceberg 事务;中途失败清掉已写的孤儿文件;覆盖装载同样是单事务切换;新建表失败则补偿删表,不给用户留一张空壳表。行宽和声明列数对不上且多出的内容非空,报错并带上行号——这多半是分隔符选错了,静默丢一列数据是最恶劣的失败方式。三、一个容易做错的架构细节:暂存区跟谁走文件解析前要有个暂存落点。它的存储坐标(endpoint/凭证/桶)跟着目标 catalog 自己的连接属性走,不单独配一套——不同 catalog 可以指向不同的对象存储,后端若再存一份「暂存区在哪」,就是第二个真值,迟早和 catalog 的配置漂移打架。由此也定下交互顺序:上传时就要先选定 catalog 和库。装载完成后,这张表在元数据侧与任何平台表一视同仁:四、三句话总结输入天生有上限的通道,不值得付 Spark 冷启动的税——但省掉引擎,就要用显式的架构边界把资源框死,超限拒绝而非截断;类型推断和装载解析必须同一套规则,decimal 不换 double,错误要带行号大声报;装载是单事务:要么全进要么全不进,失败清孤儿、不留空表。这条上传建表通道是「我的数据空间」的一部分——一套可私有化部署的数据平台,支持 OEM 合作。产品介绍:https://datastudiohappy.cn/。