实习生没有错,错的是3500万个JSON文件

我们是大模型团队的前置数据组,工作任务和数据有关:清洗、整理、归档,把合适的样本交给下游训练团队。 昨天团队简会时,新来的小陈领取了 3500 万条 brainly 数据归档的任务

前天新到的 Brainly 原始数据,逻辑上是 3500 万个独立 JSON 文件并且均匀装进 350 个 zip 压缩包,每个包大约 10 万条。每个 JSON 对应 1 组题目-解析-答案

下游团队挑数据

午睡刚醒,下游的训练团队的王宇在飞书沟通群里发信息说要一组大学的 STEM (数理化、工程、科学类)数据投入到增强训练,问我们晚上吃饭之前能不能给他们,这样他们可以早点挂起来训练,早点下班回家。

我看任务看板上 brainly 数据归档任务的状态是已完成,时间上相当充裕,就应承下来了:"可以的 哥,晚饭刷你的卡"

"GET",王宇在飞书上用表情回了我

数据交过去了吗

两点五六分的时候,我创建了个任务给小陈,跟他大概说了一声,让他具体跟王宇那边对接。

"好的 师兄,我尽快完成",小陈看起来挺开心

下午我参加了几个会,有点困顿。准备散场的时候收到小陈的信息"师兄,感觉来不及,现在才跑了不到一半,怎么办?"

我看小陈发这个,有点不好的感觉

" ??? 筛选数据不就是一会儿的事,怎么花这么长时间?",好在会议内容不关我的事,我可以分心回几句。

小陈发来一条新消息:"3500 多万个文件,我不知道为什么这么慢"

我一下清醒了

让小陈给我发了简化的文件 tree,再看了他筛选数据的代码,看来晚饭要刷小陈的卡了

python 复制代码
# 小陈数据筛选代码示意|已脱敏
from pathlib import Path
from zipfile import ZipFile
import json

zip_dir = Path("/data/brainly")
unpack_dir = Path("/tmp/brainly-unpack")

for zip_path in sorted(zip_dir.glob("*.zip")):
    with ZipFile(zip_path) as archive:
        for member in archive.infolist():
            if member.is_dir() or not member.filename.endswith(".json"):
                continue

            # 先把 ZIP 内的 JSON 解压出来
            archive.extract(member, unpack_dir)
            json_path = unpack_dir / member.filename

            # 解压完成后,再打开 JSON 文件读取
            with open(json_path, "r", encoding="utf-8") as f:
                record = json.load(f)

            # 业务筛选逻辑
            question_images = record.get("question_images", [])
            answer_images = record.get("answer_images", [])
            image_count = (len(question_images) + len(answer_images))
            if image_count >= 3:
                save_result(record)

为什么处理速度很慢

数据到我们这里的时候,这 3500 万条数据是放到 350 个 zip 压缩包的,每个压缩包定量 10 万个文件。小陈没有进行改动也没建立索引 ,直接将这几百个压缩包原封不动 push 到了内网的存储。

当前小陈的解法在筛选数据前得经历解压、文件读取、JSON 反序列化、提取指定字段、符合的数据写入新文件,最费时间就是文件内容读取和 JSON loads,可偏偏还写了个解压到新文件后再读...

海量小文件的读写会有大量的 open 和 close,磁盘 I/O 会被打满,怪不得这小子一下午跑不完

现在怎么办

临时补救吧,好在他程序处理日志打印了已处理文件清单,我让他整理好给我。

我这边给大模型下了需求:

1、将小陈的单线程 Python 改为多 worker 并行处理的 rust;

2、先跳过已处理的文件,但是记录下来;

3、优化操作路径,将解压完-逐一读取文件-JOSN反序列化-取值-判断-逐个写入的流程,优化为流式读取压缩包内容-基于字符串位置拿到ID-去重/跳过-JSON反序列化取值-判断-放到内部队列-额外线程定量/定时写入;

4、在写入的时候不仅是挑选王宇他们要的 STEM,原本的数据也按我的字段规划拆好,定量封片到 Parquet 文件;

5、第 3 步去重跳过的数据是为了快速交付给下游团队,但是我们自己要封片 Parquet 还是要完整数据的。

rust 复制代码
// 大模型写的多 Worker 实时处理示意
rayon::ThreadPoolBuilder::new()
      .num_threads(8) // 8 个 Worker
      .build_global()?;

  zip_paths.par_iter().try_for_each(|zip_path| {
      let file = File::open(zip_path)?;
      let mut zip = ZipArchive::new(file)?;

      for i in 0..zip.len() {
          let mut item = zip.by_index(i)?;
          let id = get_id_from_filename(item.name());
          let record: BrainlyRecord =
              serde_json::from_reader(&mut item)?;

          // 先判断并交付下游需要的数据
          if !delivered.contains(&id)
              && seen.insert(id.clone())
              && is_stem(&record)
              && image_count(&record) >= 3
          {
              delivery_tx.send(record.clone())?;
          }
          // 完整记录进入 Parquet 归档队列
          parquet_tx.send(to_parquet_row(&id, &record))?;
      }

      Ok::<_, anyhow::Error>(())
  });

到这里,两条处理路径不是换个编程语言,而是从读取方式、并行粒度到输出目标都发生了变化,请看对比图

先把下游要的数据交出去,同时把完整记录写入 Parquet,不要为了快速交付而跳过长期归档

经过小样运行测试、王宇和我俩人的双检,确定数据可用,然后找了台 CPU 性能强并且磁盘 I/O 能力强的机器来跑这批数据。

多了一个全量 Parquet 环节

全量数据 Parquet 操作看起来是多余的,因为王宇他们只要他们想要的那部分。

小陈的归档工作其实并没有真的完成,既然是我操作,那必不可能跑两轮。

对于少写、多读的文本类数据,我们会以 Parquet 格式归档存储。

对,既不是 csv,也不是 zip,更不是独立多个 JSON,对需要频繁按字段过滤的文本数据来说,即使已经定量写成大 JSON,也仍然不如 Parquet 适合查询

为什么是 Parquet

被使用才有价值,数据一定要用起来 - 这是我们老板早些年的理念,也是现实场景和需求的归纳。

大模型训练不可能一次就完成,同一份数据可能会在不同时期投入到不同的训练里。那么数据需要被快速访问、快速挑选、快速写入、快速传输和快速使用。

在我们的场景里,Parquet 相比于 csv、zip、jsonl、txt 或者是数据库这种存储形式更贴近需求,Parquet 文件内部按列组织 ,并且以 Row Group 保存统计信息。基于它自身配合列裁剪谓词下推合理分片 的特性,天然支持我们的筛选和归档场景,也天然支持并发读取,像这种千万级的数据过滤,只需要几秒钟

再看 csv、zip、jsonl、txt,除非你专门维护一套索引,否则对于这种字段多、文本长的 JOSN 数据处理,每次都要耗费几个小时

索引的问题很明显,你无法确保索引跟原始数据任何时候都一致,万一文件被改动,很有可能出现货不对版的尴尬事。

简单复盘

数据如约交给下游的王宇,归档工作也并行完成了

晚饭刷王宇的卡,宵夜是小陈点的烧烤。生活似乎还不错!

吃烧烤的时候和队长简单聊了聊今天这事,小陈有点粗心,过程里没有按照部门规则处理数据,也没有在规划好方案后让我们给他确认一下

些许波折,都在可控范围内,算不得什么。小陈就算不给我发求助信息,队长到下午四点也会问他数据交付进度

烧烤是好吃,但小陈只能请这一次

相关推荐
小五传输32 分钟前
【一文解读】汽车制造业协同,跨网跨区域文件传输如何落地?
大数据·运维·安全
长谷深风11144 分钟前
Agent 的 Context 里,到底应该放什么?
大数据·人工智能·prompt工程·ai agent·智能体·context工程·systemprompt
贝格前端工场1 小时前
AI在生产制造企业的六大成熟落地场景(可量产、可验收、非噱头)
大数据·ui·前端开发
Elastic 中国社区官方博客1 小时前
机构如何统一智慧城市数据以改善公共服务?
大数据·人工智能·物联网·elasticsearch·搜索引擎·全文检索·智慧城市
Raas1001 小时前
MAI Gateway(魔芋企业级AI网关)详解:企业为什么需要AI网关,一文读懂企业AI流量治理
大数据·人工智能·gateway·api·ai网关·mai gateway
大任视点1 小时前
《怪兽引擎:能源转型的驯化与共生》开幕 以艺术思辨锚定后化石时代的文明坐标
大数据·人工智能
linweidong1 小时前
阿里Lazada Java面经及参考答案
大数据·高并发·反射·拦截器·session·幂等性·redis性能
衡石科技1 小时前
HENGSHI SENSE 6.2发布,超越ChatBI的数据智能底座
大数据·科技·数据分析
上海蓝色星球1 小时前
数智重构造价全流程|蓝色星球造价机器人,驱动行业标准化智能化升级
大数据·人工智能