高频数据存储:HDF5、Parquet、InfluxDB时序数据库
做高频量化的人,迟早要面对一个灵魂拷问:数据存哪?
我刚开始做高频回测那会儿,傻乎乎地把所有Tick数据塞进MySQL。结果呢?一张表几亿行,查询一次要等半天。后来我才明白——高频数据的存储,不是存不存得下的问题,而是读不读得出来的问题。
今天咱们就聊聊三种主流方案:HDF5、Parquet、InfluxDB。它们各有各的脾气,选对了事半功倍,选错了……嗯,你会浪费大量时间在I/O等待上。
核心观点:高频数据存储没有银弹。本地分析用HDF5,跨平台协作用Parquet,实时监控用InfluxDB。别指望一个方案打天下。
一、HDF5:老牌劲旅,适合本地重度分析
HDF5(Hierarchical Data Format version 5)是科学计算领域的老将。它本质上是一个自描述的文件容器,可以存多维数组、表格、甚至图像。
我个人习惯把HDF5当作本地分析的“工作台”。每天收盘后,把当天的Tick数据追加到一个HDF5文件里,然后用Python的h5py或pandas的HDFStore直接读取分析。速度非常快。
我的经验:HDF5最适合存“一次性写入、多次读取”的数据。如果你需要频繁修改数据,它并不是好选择。
HDF5的核心优势
- 读取速度快:特别是按列读取时,比CSV快几十倍
- 支持压缩:默认的zlib压缩能把数据体积压到原来的1/5
- 层次结构:可以像文件系统一样组织数据,比如 /data/2024/01/01/tick
- 与Python生态无缝集成:pandas的HDFStore直接读写DataFrame
代码示例:用HDF5存储Tick数据
import pandas as pd
import numpy as np
# 模拟高频Tick数据
np.random.seed(42)
n = 1000000
df = pd.DataFrame({
'timestamp': pd.date_range('2024-01-01', periods=n, freq='1ms'),
'price': 100 + np.random.randn(n) * 0.5,
'volume': np.random.randint(1, 100, n),
'side': np.random.choice(['buy', 'sell'], n)
})
# 写入HDF5(使用固定格式,速度更快)
df.to_hdf('tick_data.h5', key='tick', mode='w', format='fixed', complevel=5)
# 读取指定时间范围的数据
query = 'timestamp > "2024-01-01 00:00:00" and timestamp < "2024-01-01 00:01:00"'
result = pd.read_hdf('tick_data.h5', 'tick', where=query)
print(f"查询到 {len(result)} 条记录")
避坑指南:我曾经用HDF5存了3年的Tick数据,结果文件超过100GB。HDF5单文件太大时,读写性能会急剧下降。建议按月份或季度拆分文件。
二、Parquet:列式存储的现代选择
Parquet是Apache Hadoop生态下的列式存储格式。它和HDF5最大的区别在于:Parquet天生为“大数据”而生,支持分布式处理,压缩率更高。
我为什么后来转向Parquet?因为团队需要把数据从本地搬到云端。HDF5在Spark、Flink这些框架里支持不太好,而Parquet是它们的“亲儿子”。
Parquet的核心优势
- 极高的压缩率:Snappy压缩下,高频数据能压到原始大小的1/10
- 列式存储:只读取需要的列,I/O开销极小
- 跨平台兼容:Spark、Presto、ClickHouse都能直接读
- 支持复杂嵌套结构:可以存JSON格式的订单簿快照
代码示例:用Parquet存储高频数据
import pandas as pd
import pyarrow.parquet as pq
import pyarrow as pa
# 同样模拟高频数据
df = pd.DataFrame({
'timestamp': pd.date_range('2024-01-01', periods=500000, freq='2ms'),
'bid': 99.5 + np.random.randn(500000) * 0.3,
'ask': 100.5 + np.random.randn(500000) * 0.3,
'bid_vol': np.random.randint(1, 50, 500000),
'ask_vol': np.random.randint(1, 50, 500000)
})
# 写入Parquet(使用Snappy压缩)
table = pa.Table.from_pandas(df)
pq.write_table(table, 'orderbook.parquet', compression='snappy')
# 只读取两列
df_bid_ask = pd.read_parquet('orderbook.parquet', columns=['timestamp', 'bid', 'ask'])
print(f"读取了 {len(df_bid_ask)} 行,仅包含3列")
我的建议:如果你需要和团队共享数据,或者数据要上云,优先选Parquet。它比HDF5更“通用”。
三、InfluxDB:专为时序数据设计的数据库
HDF5和Parquet都是文件格式,而InfluxDB是一个时序数据库。它专门为“时间戳+数值”这种数据结构做了深度优化。
我记得第一次用InfluxDB是在做实时监控系统的时候。当时需要每秒写入几万条行情数据,同时还要支持毫秒级的查询。MySQL扛不住,HDF5不支持并发写入。InfluxDB完美解决了这个问题。
InfluxDB的核心优势
- 超高写入性能:单机每秒可写入百万级数据点
- 自动数据保留策略:可以设置数据过期自动删除
- 内置降采样:自动将高频数据聚合为分钟级、小时级数据
- 类SQL查询:使用InfluxQL或Flux语言,上手快
代码示例:用InfluxDB存储实时Tick
from influxdb import InfluxDBClient
import time
import random
# 连接InfluxDB(默认端口8086)
client = InfluxDBClient(host='localhost', port=8086)
client.create_database('tick_db')
client.switch_database('tick_db')
# 模拟实时写入
for i in range(100):
json_body = [
{
"measurement": "tick",
"tags": {
"symbol": "BTCUSDT",
"exchange": "binance"
},
"time": int(time.time() * 1e9), # 纳秒时间戳
"fields": {
"price": 50000 + random.random() * 100,
"volume": random.randint(1, 10)
}
}
]
client.write_points(json_body)
time.sleep(0.01) # 模拟10ms间隔
# 查询最近1分钟的数据
results = client.query('SELECT * FROM tick WHERE time > now() - 1m')
print(f"查询到 {len(list(results.get_points()))} 条记录")
避坑指南:我曾经在生产环境遇到过InfluxDB的“写放大”问题——当数据量太大时,磁盘I/O会暴涨。解决方案是:合理设置shard duration,让数据按天或按小时分片。
四、三种方案对比:一张表说清楚
| 特性 | HDF5 | Parquet | InfluxDB |
|---|---|---|---|
| 存储方式 | 文件 | 文件 | 数据库 |
| 写入性能 | 中等(单线程) | 中等(批量写入快) | 极高(并发写入) |
| 读取性能 | 快(按列读取) | 极快(列式+谓词下推) | 快(按时间范围查询) |
| 压缩率 | 中等(zlib) | 高(Snappy/Zstd) | 低(默认不压缩) |
| 并发支持 | 差(单文件锁) | 差(只读并发好) | 好(多客户端写入) |
| 适用场景 | 本地分析、回测 | 数据交换、云端分析 | 实时监控、在线查询 |
| 学习成本 | 低(pandas即可) | 低(pyarrow/pandas) | 中(需学InfluxQL) |
五、我的实战建议
说了这么多,到底怎么选?我给出一个决策流程:
- 如果你只是个人做回测,数据量在几十GB以内 → 用HDF5。简单、直接、够用。
- 如果你需要和团队协作,或者数据要上云 → 用Parquet。它是大数据生态的通用语言。
- 如果你需要实时写入和查询,比如做盘中的信号监控 → 用InfluxDB。它天生为这种场景设计。
- 如果你全都要 → 混合使用。HDF5做本地分析,Parquet做数据交换,InfluxDB做实时监控。
最后说一句:存储方案只是工具,别在工具选择上内耗。高频数据的核心永远是信号的质量和策略的稳定性。存储只要不拖后腿,就是好方案。