Repository navigation
Linux 日更不再卡死,回补和日更只写改动的数据
CNEquity 0.22.0 已发布至 PyPI。开源 A 股本地数据湖,用于量化研究、回测与 AI Agent。
python -m pip install --upgrade cnequity==0.22.0- 升级步骤:
pip install -U cnequity后重启cne serve和已在运行的cne mcp。数据集契约没有变化,不需要迁移数据;配置不用改,[orchestrator]可选新增batch_stall_seconds(默认 600)。为绕过 #56 临时加的tdx_daily_backend = "thread"可以删掉,恢复 Linux 默认的进程池。 直接读取 Parquet 文件的外部程序要读分区目录下的所有文件:一个分区现在可以有多个文件(见下文)。 - 回补和日更只写改动的数据,不再重写整个分区:此前写入一个分区时,会把整个分区读出来、合并后整份重写成一个文件,写入成本取决于分区大小而不是改动多少。实测补 40 只股票一个月的日线要重写 159,192 行才写进 840 行;按年分区的行业指数每天追加约 1,200 行要重写约 29 万行;补一只股票的分钟线或逐笔要重写整天 170 万到 1,600 万行。现在一个分区可以有多个文件,各文件主键互不重叠:新增的键写成新文件,旧文件不读也不重写;修改只重写包含这些键的文件。同样的 40 只一个月补缺,现在只写 840 行(一个新文件),合并部分耗时 0.4 秒,新版本也只多出新写的文件,其余文件硬链接共享。分区越大、磁盘越慢,省下的时间越多;整理发布里与写入量无关的水位和覆盖检查仍要二三十秒。复权因子和行业指数的派生写入同样改为这种方式。
- 整理发布前不再把整个数据集复制一遍:此前每次整理发布和派生前,都会把当前版本整份复制到工作目录,在不支持克隆的文件系统(如 Linux 常用的 ext4)上是逐字节复制并对每个文件 fsync,与改动多少无关——
daily_bars453MB、7,201 个文件,minute_bars4.1GB,trade_ticks5.6GB。现在只复制和当前版本不一致的文件,正常情况下什么都不用复制。 cne run maintain每天把文件数达到 4 个的分区合并回一个文件,也可以手动运行新命令cne storage merge-files(不加--apply只报告)。合并不改变行内容时不再跑全湖发布审计(实测 21 个分区从 243 秒降到 5 秒);合并掉旧版遗留的重复键或清洗掉的行时照常审计。- 超过 20 万行的分区整份写入时按代码分桶成多个文件(如
part-b003-of-064.parquet):修改已有的键只重写所在的桶,不再重写整个分区——一天 1,400 万行的逐笔、170 万行的分钟线,以前改一只股票就要重写整天。20 万行以下的分区(如每天约 7,600 行的日线)仍是一个文件。分桶只决定文件布局,读取和查询不受影响。 - 整理发布中与写入量无关的固定开销大幅减少:判断日线覆盖到哪天、哪些股票有成交,改为按分区缓存结果,只重算文件有变化的分区,不再每次解码全部约 1,900 万行;北交所 ST 观察收据在输入数据集没有新版本时直接沿用;DuckDB 视图只在对应数据集的版本指针、配置或代码变化时重建,并且只重新定义变化的数据集;基金无成交证明的原始报文按文件指纹缓存解析结果。实测在同一个湖上,没有新数据的 40 只一个月回补从约 29 秒降到 10 秒;补缺口时,交易状态那一次整理发布从约 15 秒降到 2–4 秒,日线那一次仍需约 25–30 秒,主要花在重建依赖日线的视图、重新核验 ST 收据和更新水位上。
- 行为变化:整理发布只清洗它重写的文件。旧版遗留的非交易日行、同一文件里的重复键,此前在分区任何一次写入时都会被清掉;现在要等写入涉及这个文件(修改其中的键)或分区被合并时才清掉。一个分区内多个文件之间重复的键,在下一次写入该分区时就会合并。
- Linux 上
cne run daily不再卡在daily_bars:通达信日线的进程池此前用 fork 启动 worker(Python 3.13 及以下在 Linux 上的默认方式),而主进程在此之前已经用过 polars,子进程继承了 polars 线程池的状态却没有它的线程,第一次并行运算就永远等下去。worker 没有退出,进程池察觉不到,日志只剩每分钟一条still working,一个批次也不会完成(#56)。现在改用 forkserver(不支持时用 spawn)启动 worker,不需要改配置。macOS、Windows 和 Python 3.14 不受影响。 daily_bars的进程池卡住时不再一直等下去:此前 worker 挂住但没有退出,进程池察觉不到,只能手动中止。现在连续 10 分钟既没有批次完成、也没有 worker 心跳,就结束这些 worker,在当前进程内重跑未完成的批次,并在日志里写出卡住的批次号。时长由[orchestrator].batch_stall_seconds控制(默认 600,0关闭)。只作用于 Linux 默认的进程池,macOS 和 Windows 用的线程池不受影响。- 并行运行的 baostock 步骤不再把 30 秒的 socket 超时留给整个进程:此前每次 baostock 抓取都会改进程级的默认超时、结束时再还原,几个步骤同时运行时会按错误的顺序互相还原,实测 4 个并发抓取 200 轮里有 163 轮把超时留了下来,此后这次运行新开的所有 socket 都带着它。现在超时只加在 baostock 自己打开的 socket 上,其他代码不受影响。
- 每次日更和回补不再把仲裁改用 baostock 的股票重新抓一遍复权因子:判断缓存是否过期时,这些股票此前看的是从不更新的新浪缓存,所以每次都被当成过期,逐只登录 baostock 重抓。实测同一个湖上,复权因子派生从 200 秒降到 27 秒,每次少重写约 3.7 万行因子;40 只股票一个月的回补因此从约 285 秒降到约 2 分钟。日志里这类股票也不再写成“新浪不可用”,改为注明是仲裁指定。
- 等待文件锁的提示不再一律说“另一个进程持有”:同一次运行里的多个线程排队等 baostock 唯一的连接时也会出现这条提示,现在写成“另一个进程或本次运行的另一个线程持有”。
- macOS 上
cne serve取消任务后,任务不再在面板里长时间显示为“运行中”:任务进程退出后没有被及时回收,macOS 又无法像 Linux 那样从/proc认出已退出的进程。现在服务启动任务后会立即回收退出的进程。 - 卡住的运行能看出卡在哪里:
daily_bars运行期间,每条still working后面多一行,写出在跑的批次数、最久没有报告进度的批次、它停了多久和它包含的代码。Linux 和 macOS 上,cne命令和daily_bars的 worker 子进程收到kill -USR1 <pid>时会把所有线程的调用栈写到 stderr,不必先装 py-spy;第一条still working会提示这条命令。 macro_indicators新增usdcnh:美元兑离岸人民币日收盘,来自新浪外汇日 K(source=sina,2014-11-07 起),日更和回填都会写入。它是离岸收盘,不是在岸中间价,也不和东财的收盘拼接;新浪拒绝请求时只停这一列,国债和 Shibor 照常写入。cne backfill macro_indicators按区间回补时也会写入美国 10 年期国债收益率usbond_yield_10y(与中国 10 年期来自同一张东财报表),此前只有日更会写。