1. 课程导入
在金融数据工程中,我们面对的不是“几张表、几百万行”的常规业务数据,而是行情快照、逐笔成交、委托队列、因子矩阵、风险敞口、回测中间结果等高频、宽表、多版本、强时序的数据集合。一个中型量化研究平台每天新增的原始行情可能达到数十GB,因子计算过程中还会产生数倍于原始数据的中间列。此时,传统基于行式存储与逐行对象处理的技术栈会迅速暴露瓶颈:磁盘I/O放大、内存占用失控、CPU缓存命中率低、跨语言传输开销高。本课程的第一节不急于写代码,而是先建立一张“技术地图”:金融数据工程到底难在哪里,列式存储与内存计算为什么能解决问题,Parquet与Arrow各自扮演什么角色,以及我们接下来的学习路径如何与实战目标对齐。
一、金融数据工程的三类核心挑战
金融数据工程与通用大数据处理的最大区别在于“时间、精度、宽度”三个维度同时施压。时间维度上,行情数据具有严格的时序性和事件驱动特征,乱序、重复、缺失、交易所时钟差异都会影响后续计算;精度维度上,价格、收益率、持仓、保证金等字段对浮点误差和舍入方式极其敏感;宽度维度上,因子研究和风险模型往往需要同时加载数百甚至数千列,而每次计算只涉及其中少数列。这三类挑战直接决定了存储格式与内存模型的选择。
- I/O放大:行式存储按整行读取,计算单列因子时不得不把无关列全部读入,造成大量无效I/O。
- 内存膨胀:Python对象逐行存储的元数据开销远大于数据本身,百万行数据可能占用数GB内存。
- 序列化开销:跨进程、跨语言传递数据时,反复序列化与反序列化会吃掉大量CPU时间。
- 版本与回溯:回测需要可复现的数据快照,格式必须支持Schema演进与分区裁剪。
二、列式存储与内存计算的价值
列式存储的核心思想是:同一列的数据在物理上连续存放。这样带来三个直接收益。第一,按需读取:查询只涉及某几列时,只需读取对应列的数据块,I/O量大幅下降。第二,高压缩比:同一列数据类型一致、取值分布集中,字典编码、游程编码、Delta编码等更容易生效。第三,向量化计算:连续内存布局让CPU可以批量处理数据,减少分支预测失败和缓存未命中。
内存计算则进一步把“列式”从磁盘延伸到内存。Arrow定义了一套语言无关的列式内存格式,使Python、Java、C++、Rust等不同运行时可以共享同一块内存,而不需要复制或转换。对于金融场景,这意味着行情数据从C++撮合引擎写出后,Python研究环境可以直接零拷贝读取;因子计算结果可以交给Rust做高性能聚合,再返回给Notebook可视化。列式存储解决“怎么存”,内存计算解决“怎么算”,两者结合才构成完整的数据工程链路。
三、Parquet与Arrow的定位差异
很多初学者会把Parquet和Arrow混为一谈,认为“都是列式,选一个就行”。实际上它们位于数据生命周期的不同阶段,解决的问题也不同。Parquet是磁盘上的列式文件格式,强调压缩、编码、分区和长期存储;Arrow是内存中的列式数据格式,强调零拷贝、跨语言和向量化计算。二者不是竞争关系,而是上下游协作关系:Parquet负责把数据高效地放在磁盘上,Arrow负责把数据高效地放在内存中并交给计算引擎。
| 对比维度 | Parquet | Arrow |
|---|---|---|
| 主要定位 | 磁盘列式存储格式 | 内存列式数据格式 |
| 核心优势 | 高压缩比、列裁剪、分区裁剪、Schema演进 | 零拷贝、跨语言、向量化、低序列化开销 |
| 典型使用场景 | 行情归档、因子快照、回测数据湖 | Notebook分析、跨进程传输、实时计算 |
| 与计算引擎关系 | 作为数据源被读取 | 作为内存载体被直接计算 |
| 常见误区 | 认为Parquet可以直接原地计算 | 认为Arrow可以替代持久化存储 |
四、学习路径与实战目标
本课程围绕“存得省、读得快、算得准”三条主线展开,共30章。前段建立Parquet与Arrow的核心心智模型,中段进入金融场景的实战:行情数据清洗、因子矩阵构建、回测数据快照、跨语言传输与性能调优,后段覆盖分区策略、压缩参数、内存布局、流式处理与常见故障排查。每一章都尽量给出可运行的代码片段和可观测的性能指标,而不是停留在概念介绍。
- 基础认知:理解列式存储、内存计算、Parquet与Arrow的定位与协作关系。
- 读写实战:使用PyArrow读写Parquet,掌握Schema定义、分区写入与列裁剪。
- 金融场景:处理行情快照、逐笔成交、因子矩阵与回测快照。
- 性能调优:压缩编码、Row Group大小、内存池、零拷贝与向量化。
- 工程化:数据版本、Schema演进、跨语言服务与故障排查。
五、常见误区与复盘
在正式进入后续章节之前,先澄清几个高频误区。第一,“列式存储一定比行式快”:如果查询总是需要整行数据,列式反而可能因为多次寻址而变慢。第二,“Parquet文件越小越好”:过度压缩会增加CPU解压开销,金融低延迟场景需要权衡压缩率与解压速度。第三,“Arrow就是Pandas”:Pandas底层可以使用Arrow,但Pandas的索引、对象类型和Arrow的列式模型并不完全等价。第四,“零拷贝等于没有成本”:零拷贝依赖内存对齐和生命周期管理,跨进程共享时仍需注意内存所有权和释放时机。
# 一个典型的金融数据读取对比(示意)
# 行式CSV:读取全部列,再筛选
import pandas as pd
df = pd.read_csv("tick.csv") # 所有列进入内存
df = df[df["symbol"] == "IF2406"] # 过滤发生在读取之后
# Parquet + Arrow:先列裁剪,再过滤
import pyarrow.parquet as pq
table = pq.read_table(
"tick.parquet",
columns=["symbol", "price", "volume"], # 只读需要的列
filters=[("symbol", "=", "IF2406")] # 分区/行组裁剪
)
本章的目标不是让你立刻成为Parquet与Arrow专家,而是先建立一张清晰的技术地图:金融数据工程的挑战来自时间、精度与宽度;列式存储与内存计算分别解决“怎么存”和“怎么算”;Parquet与Arrow是上下游协作关系,而非二选一。接下来,我们将从Parquet的文件结构开始,逐步进入可运行的实战环节。