2. 归档格式选型与设计原则
逐笔行情归档与普通K线存储有本质差异:逐笔数据是“事件流”,单只股票一天可能产生数万到数十万条委托与成交记录,全市场一天可达数十GB。归档格式的选择直接决定了三件事——写入时能否跟上实时落盘速度、存储成本能否承受、以及事后按不同条件检索时是否高效。本节围绕文本、二进制、列式存储三类格式展开对比,并给出归档文件结构、字段编码与版本兼容的具体设计方法。
三类格式的本质差异
文本格式(CSV、JSON Lines)最直观:一行一条记录,字段以分隔符或键值对表达。优点是无需专用工具即可查看、调试、跨语言交换,出问题时用 grep 就能定位异常记录。缺点是体积大——一个浮点价格写成“12.3400”占7字节,而二进制double只占8字节但文本还要加分隔符;解析时需反复做字符串到数值的转换,写入吞吐和读取吞吐都明显偏低。适合小规模、低频、以人工核对为主的场景,不适合全市场逐笔归档。
二进制行式格式(自定义定长/变长记录、Protobuf、FlatBuffers)按记录顺序紧凑排列,字段用固定宽度整数或浮点直接落盘。它的写入吞吐最高,因为几乎不需要序列化开销,追加写即可;读取单条记录也快。但行式布局的弱点在于“按列检索”:若只想统计某日所有记录的成交量总和,仍需把每条记录完整读入再取该字段,I/O放大严重。此外自定义二进制格式的可读性差,字段增删需要严格的版本管理。
列式存储(Parquet、ORC,以及基于它们的DuckDB/ClickHouse文件)把同一字段的连续值放在一起,天然适配“只读部分列”的分析型查询,且同列数据类型一致,压缩率显著高于行式。逐笔数据中交易所代码、证券代码、买卖方向等字段重复度极高,字典编码加RLE后压缩比常可达5~10倍。代价是写入需要缓冲与列块组织,随机单条写入不友好,更适合“批量落盘+批量分析”的模式。
按查询模式反推格式
选型不能只看“哪个先进”,而要先列出归档数据的真实查询模式。逐笔归档的典型查询大致分四类:一是按证券+日期回放,需要顺序读出某只股票某天的全部逐笔,用于策略回测;二是按时间窗口切片,如提取某日9:30:00~9:35:00全市场快照;三是按字段聚合统计,如统计每只股票当日主动买入笔数与金额;四是异常排查,定位某条价格异常或序号跳变的记录。
第一、二类查询是“窄范围、整行读取”,行式二进制表现很好;第三类只涉及少数列,列式优势明显;第四类需要可读性,文本或带索引的二进制更友好。实践中常见做法是分层归档:热数据用二进制行式按“交易所/日期/证券”分区落盘,保证回放速度;冷数据定期转成列式Parquet,供批量统计与长期低成本保存。这样既满足回放吞吐,又控制存储成本。
归档文件结构设计
无论选哪种格式,文件组织都应遵循“分区+元数据+数据块”的结构。以按日归档为例,目录可按 market=SH/date=20250612/ 分区,每个分区内再按证券代码哈希或前缀分桶,避免单文件过大。每个数据文件头部写入元数据区,记录字段schema、字段编码方式、记录数、时间范围、序号起止、压缩算法与版本号;数据区按记录顺序追加。文件尾部可写一个轻量索引,记录每个数据块的首条记录时间戳与文件偏移,支持按时间窗口快速定位。
字段编码上,逐笔数据有几处值得专门设计:证券代码用定长6字节ASCII而非变长字符串;交易所与买卖方向用1字节枚举;价格若以“分”为单位可用int32,避免浮点误差;时间戳用int64毫秒或微秒;序号用int64保证不溢出。这些选择能让单条记录从文本的百余字节压缩到约30~40字节,配合列式的字典编码与RLE,压缩比进一步提升。
版本兼容策略
归档文件往往要保存数年,schema演进不可避免。可靠的做法是:文件头固定写入format_version与schema_id,读取端先读版本号再选择对应解析器;新增字段一律追加在记录尾部并给默认值,绝不改变已有字段的顺序与宽度;删除字段只做逻辑标记而不物理移除。对于列式文件,Parquet本身支持schema evolution,但仍需在元数据中显式记录字段语义版本,避免“同名字段含义变化”这类隐性不兼容。每次格式变更都应保留至少一个旧版本样本文件用于回归测试,确保新解析器能正确读取历史归档。
总结来看,格式选型没有唯一答案:写入吞吐优先选二进制行式,分析检索优先选列式,调试与交换优先选文本,而工程上更常见的是组合使用。真正决定归档系统可用性的,是分区结构、字段编码与版本策略这些“设计原则”层面的工作,它们让格式选择落地为可长期维护的数据资产。