「先把 tick 數據全存下來,以後要用再說」——這句話在項目早期聽起來很穩妥,但它會變成一筆持續增長的賬單。tick 數據的體積增長是標的數量 × 行情活躍度 × 時間的三重乘積,沒有任何一項會自己變小。
這篇文章用一組明確的假設算一遍:1 萬個標的、存一年,到底要多少磁盤、花多少錢,以及怎麼把這個數字壓下來。
先定義假設
為了避免結論含糊,先把假設寫清楚。這些是行業裡常見的取值,你可以按自己的情況替換:
| 參數 | 取值 | 說明 |
|---|---|---|
| 標的數量 | 10,000 | 覆蓋多市場組合監控的規模 |
| 每標的 tick 頻率 | 2 條/秒 | 活躍標的的平均水平,冷門標的遠低於此 |
| 每條 tick 字段 | 標的、時間、最新價、買一、賣一、成交量 | 對應行情接口的 tick 字段集 |
| 保留週期 | 365 天 | 全年不間斷 |
| 對象儲存單價 | $0.023 / GB / 月 | 主流雲廠商標準儲存的常見檔位 |
第一步:算出一年的 tick 總量
先把量級算出來,後面才有討論的基礎:
- 每秒 tick 數:10,000 標的 × 2 條 = 20,000 條/秒
- 每日 tick 數:20,000 × 86,400 秒 = 17.28 億條/天
- 每年 tick 數:17.28 億 × 365 = 約 6,307 億條/年
6,307 億條。這個數字本身沒有意義,有意義的是它乘以「每條多少字節」之後的結果。
第二步:三種儲存格式的體積差異
同樣 6,307 億條數據,用不同格式存下來,體積能差 7 倍以上:
| 格式 | 每條約 | 一年總體積 | 說明 |
|---|---|---|---|
| JSON 文本 | 180 字節 | 約 113.5 TB | 字段名重複儲存,浮點數以文本表示 |
| 定長二進制 | 48 字節 | 約 30.3 TB | 省掉字段名,數值按定長編碼 |
| 列式 + 壓縮 | 24 字節 | 約 15.1 TB | 字典編碼 + 時間戳增量編碼 + 壓縮 |
差距的來源很清楚:JSON 把字段名和引號重複寫了 6,307 億次,而定長二進制與列式儲存不需要。列式儲存還能利用時間戳的單調性和價格的低波動性做增量與字典編碼,進一步壓縮。
第三步:換算成錢
把體積乘以對象儲存單價($0.023/GB/月):
| 格式 | 月儲存成本 | 年儲存成本 |
|---|---|---|
| JSON 文本(113.5 TB) | 約 $2,611 | 約 $31,334 |
| 定長二進制(30.3 TB) | 約 $696 | 約 $8,356 |
| 列式 + 壓縮(15.1 TB) | 約 $348 | 約 $4,178 |
換句話說,僅僅因為選擇了 JSON 落庫,一年的儲存賬單就多出兩萬多美元。而這還只是儲存費用,沒有算寫入吞吐、查詢算力和備份。
需要說明的是,這裡的單價是標準儲存檔位。如果改用低頻訪問或歸檔檔位,單價可以顯著下降,但取回延遲和取回費用會上升——這就是下一節要講的取捨。
為什麼 JSON 落庫是最貴的選擇
JSON 在 API 傳輸層是完全合理的選擇:可讀、調試方便、各語言原生支持。但把它直接當作持久化格式會帶來三重成本:
- 儲存體積:字段名與結構符號被重複儲存,是純粹的開銷。
- 寫入吞吐:解析 JSON 比解析定長二進制慢一個量級,在每秒兩萬條的寫入壓力下,CPU 會成為瓶頸。
- 查詢算力:列式儲存可以只讀需要的列;JSON 必須整行解析,回測查詢的掃描量成倍增加。
合理做法是:傳輸用 JSON,落庫前轉成列式格式。 在寫入管道里加一個轉換層,成本遠低於長期多付的儲存與算力費用。
分層儲存:不是所有數據都需要在線
另一個常見的浪費是「把全部歷史都放在最快的儲存上」。實際訪問模式是有規律的:
- 近 7 天:高頻訪問,用於實時看板與近期回測,放在低延遲儲存。
- 近 90 天:中頻訪問,用於策略迭代,放在標準對象儲存。
- 90 天以上:低頻訪問,主要用於歷史回測復現,放在歸檔檔位。
按這個分層,只有約 20% 的數據落在最貴的檔位,整體賬單能顯著下降。代價是歸檔數據的取回需要等待,所以分層策略必須和你的回測頻率匹配——如果團隊每週都要跑一年期回測,歸檔檔位就不合適。
更根本的做法:不要全存
最後也是最有效的辦法:先聚合,再決定存什麼原始數據。
- 按需求決定粒度:大部分策略只需要分鐘線,不需要 tick。先存 1 分鐘 K 線,體積能下降兩個數量級。
- 選擇性存原始數據:只對核心標的保留 tick,其餘存聚合結果。
- 原始 tick 設保留期:例如只保留 90 天原始數據,之後降級為聚合數據。
在動手擴容之前,先問一句:這些 tick 數據裡,實際被查詢過的比例是多少?很多團隊會發現答案低於 5%。
用 Python 算你自己的數字
TICKS_PER_SEC = 2
SECONDS_PER_DAY = 86_400
DAYS = 365
STORAGE_USD_PER_GB_MONTH = 0.023
# 每條約佔用字節數:JSON 文本 / 定長二進制 / 列式壓縮
BYTES_PER_TICK = {"json": 180, "binary": 48, "columnar": 24}
def annual_cost(symbols: int, ticks_per_sec: int = TICKS_PER_SEC) -> None:
total_ticks = symbols * ticks_per_sec * SECONDS_PER_DAY * DAYS
print(f"標的 {symbols:,} 個 → 年 tick 總量 {total_ticks / 1e8:.0f} 億條")
print(f"{'格式':<10}{'體積':>12}{'月成本':>12}{'年成本':>14}")
for name, size in BYTES_PER_TICK.items():
tb = total_ticks * size / 1e12 # 十進制 TB
monthly = tb * 1000 * STORAGE_USD_PER_GB_MONTH # 1 TB = 1000 GB
print(f"{name:<10}{tb:>10.1f} TB{monthly:>11,.0f} ${monthly * 12:>13,.0f} #34;)
annual_cost(10_000)
# 標的 10,000 個 → 年 tick 總量 6307 億條
# 格式 體積 月成本 年成本
# json 113.5 TB $2,611 $31,330
# binary 30.3 TB $696 $8,354
# columnar 15.1 TB $348 $4,177
把 symbols 和 ticks_per_sec 換成你的真實取值,就能得到自己的賬單預估。建議在數據管道設計階段就跑一遍這個計算——它的成本是幾分鐘,而選錯格式的代價是持續數年的賬單。
常見問題
tick 數據到底要不要全量保留?
大多數團隊不需要。先明確哪些策略真正依賴 tick 級數據,只對這部分標的保留原始數據,其餘存分鐘級聚合結果即可。
為什麼列式儲存能壓到 24 字節一條?
因為利用了數據的結構性:時間戳單調遞增可以做增量編碼,標的代碼和價格檔位可以字典編碼,數值列連續儲存便於壓縮。這些手段在逐條 JSON 裡都無法使用。
用數據庫直接存 tick 合適嗎?
取決於規模。每秒兩萬條的寫入壓力下,通用行式數據庫通常撐不住,需要時序數據庫或列式文件格式配合對象儲存。數據管道的整體設計可參考行情數據庫實踐。
歸檔儲存能省多少?
單價通常比標準儲存低一個數量級,但取回需要等待且可能產生取回費用。適合歷史回測復現這類低頻場景,不適合每週都要跑全量回測的團隊。
不同市場的 tick 量差異大嗎?
很大。加密市場 7×24 連續交易,tick 頻率可能非常高;而成交清淡的股票每分鐘可能只有幾筆。建議按市場分別估算,不要用統一的平均值——總量通常由最活躍的那部分主導。
壓縮會不會影響回測精度?
不會。列式壓縮是無損的,解碼後與原始值完全一致。影響精度的因素是還原處理與數據對齊,而不是儲存格式。
下一步
先算清自己的數字,再決定格式與分層策略。tick 數據的字段定義見 tick 數據,落庫方案可參考 行情數據庫實踐,回測場景的數據準備流程見 回測數據準備指南。如果你的方案是「歷史數據靠 API 按需拉取、只在本地保留近期數據」,那 套餐額度 會直接影響可行規模——流式接口的推送不按條計費,適合持續採集。