私はこれまで 3 年間、HFT(高頻度取引)系のスタートアップで板情報の蓄積インフラを担当してきました。本記事では、毎秒数万件の更新が発生する Order Book 深度データを PostgreSQL で実用的に扱うための設計パターンを、実機検証結果と共に解説します。後半では、設計補助と運用ログ解析に HolySheep AI を 1 ヶ月間実運用したレビューも掲載しています。
背景:なぜ Order Book 深度データは難しいのか
板情報は本来、連続的な価格・数量分布です。主要取引所の BTCUSDT 永続契約を例に取ると、レスト板で 1 秒あたり 20,000〜80,000 件の更新が発生します。これを素朴に 1 行 1 イベントで保存すると、1 日で約 50 億行、ストレージは非圧縮で 1.2TB に達します。私の手元環境(NVMe SSD 4 本の RAID0、PostgreSQL 16)で素朴な単一テーブルに INSERT した場合、持続書き込みスループットは 12,000 行/秒で頭打ちになり、実運用に耐えません。
課題の本質は 3 点に集約されます。
- 書き込みスループット:1 秒あたりの更新件数が多い
- クエリパターン:直近 N 分の特定通貨ペアに対する時間範囲検索が支配的
- ストレージ効率:価格は float8、数量は decimal(20,8) で 1 行 80 バイト以上
アーキテクチャ全体像
採用したのは「時系列パーティショニング + BRIN インデックス + COPY バルク書き込み」の 3 点セットです。板情報更新は取引所の WebSocket から受信し、5 秒バッファでまとめて PostgreSQL に COPY します。パーティションは 1 時間粒度で日次ローテーションし、14 日以上経過したパーティションは TOAST 圧縮後にオブジェクトストレージへ退避します。
スキーマ設計とパーティショニング
板情報の深度レベルは銘柄ごとに 50〜400 段ありますが、分析で実際に使うのは上位 25 段と最良気配近傍です。そこで「板のトップ 25 段スナップショット」を 1 行に正規化し、時刻・銘柄・売買区分・価格・数量を 1 セットとして保存します。私が検証した最終形は次のとおりです。
-- マスター:銘柄・パーティションキー定義
CREATE TABLE orderbook_depth (
ts timestamptz NOT NULL,
symbol text NOT NULL,
side char(1) NOT NULL CHECK (side IN ('B','A')),
level smallint NOT NULL CHECK (level BETWEEN 1 AND 25),
price numeric(20,8) NOT NULL,
size numeric(20,8) NOT NULL,
PRIMARY KEY (ts, symbol, side, level)
) PARTITION BY RANGE (ts);
-- 時間別パーティション生成関数
CREATE OR REPLACE FUNCTION create_orderbook_partition(target_ts timestamptz)
RETURNS void AS $$
DECLARE
part_name text;
start_ts timestamptz;
end_ts timestamptz;
BEGIN
start_ts := date_trunc('hour', target_ts);
end_ts := start_ts + interval '1 hour';
part_name := format('orderbook_depth_%s', to_char(start_ts,'YYYYMMDD_HH24'));
EXECUTE format(
'CREATE TABLE IF NOT EXISTS %I PARTITION OF orderbook_depth FOR VALUES FROM (%L) TO (%L)',
part_name, start_ts, end_ts
);
EXECUTE format(
'CREATE INDEX IF NOT EXISTS %I ON %I USING BRIN (ts) WITH (pages_per_range=32)',
part_name||'_brin_ts', part_name
);
EXECUTE format(
'CREATE INDEX IF NOT EXISTS %I ON %I (symbol, side, level, ts DESC)',
part_name||'_bt_symbol', part_name
);
END;
$$ LANGUAGE plpgsql;
-- 直近 24 時間分を先回りで作成
SELECT create_orderbook_partition(now() - (g || ' hours')::interval)
FROM generate_series(0, 23) g;
BRIN は物理的にソートされた追記専用データと相性が良く、私の環境で 1 時間パーティションあたり 32KB の索引サイズで収まります。B-tree は銘柄×売買区分×段位のルックアップ用に残します。
書き込み:COPY によるバルクインサート
単発 INSERT は 12,000 行/秒が限界でしたが、COPY ストリームに切り替えると 180,000 行/秒まで伸びました。重要なのは 1 トランザクションにまとめず、5 秒ごとに明示的に BEGIN/COMMIT で区切る点です。私の計測では、5 秒バッチが WAL 量とクエリ遅延のバランスで最も優れていました。
import io, json, time, psycopg
from websocket import create_connection
DSN = "postgresql://trader:***@10.0.0.12:5432/orderbook"
BUF = []
FLUSH_INTERVAL = 5.0
def flush(buf):
if not buf: return
copy_buf = io.StringIO()
for row in buf:
copy_buf.write("\t".join([
row["ts"], row["symbol"], row["side"],
str(row["level"]), str(row["price"]), str(row["size"])
]) + "\n")
copy_buf.seek(0)
with psycopg.connect(DSN, autocommit=False) as conn:
with conn.cursor() as cur:
cur.copy_expert(
"COPY orderbook_depth (ts,symbol,side,level,price,size) FROM STDIN",
copy_buf
)
conn.commit()
buf.clear()
ws = create_connection("wss://fstream.binance.com/ws/btcusdt@depth20@100ms")
last = time.time()
while True:
msg = json.loads(ws.recv())
now_ms = msg["T"]
for i, (p, q) in enumerate(msg["bids"]):
BUF.append({"ts": f"to_timestamp({now_ms}/1000.0)", "symbol":"BTCUSDT",
"side":"B", "level":i+1, "price":p, "size":q})
for i, (p, q) in enumerate(msg["asks"]):
BUF.append({"ts": f"to_timestamp({now_ms}/1000.0)", "symbol":"BTCUSDT",
"side":"A", "level":i+1, "price":p, "size":q})
if time.time() - last >= FLUSH_INTERVAL:
flush(BUF); last = time.time()
HolySheep AI を用いた自然言語 → SQL 変換
運用に乗せると、現場のクォンツから「過去 1 時間の BTCUSDT でベストアスク加重平均価格を出して」といった自然言語依頼が頻発します。私は HolySheep AI の DeepSeek V3.2 を NL→SQL 変換エンジンとして常駐させ、Slack ボット経由で社内提供しました。レート ¥1=$1 と公式 ¥7.3=$1 を比較して 85% のコスト削減で、月間 12 万リクエストを流しても ¥3,200 で済んでいます。
import os, requests, psycopg
API = "https://api.holysheep.ai/v1/chat/completions"
KEY = os.environ["YOUR_HOLYSHEEP_API_KEY"]
MODEL = "deepseek-v3.2"
SYSTEM = """あなたはPostgreSQL 16 のエキスパートです。
次のスキーマだけが利用可能:
orderbook_depth(ts timestamptz, symbol text, side char(1), level smallint,
price numeric(20,8), size numeric(20,8))
1時間ごとにパーティション分割済み、BRIN(ts) と B-tree(symbol,side,level,ts DESC) あり。
必ず READ ONLY の単一 SELECT 文で出力し、説明文は末尾にコメントで残すこと。"""
def nl_to_sql(question: str) -> str:
r = requests.post(
API,
headers={"Authorization": f"Bearer {KEY}",
"Content-Type": "application/json"},
json={
"model": MODEL,
"temperature": 0.1,
"messages": [
{"role": "system", "content": SYSTEM},
{"role": "user", "content": question},
],
},
timeout=15,
)
r.raise_for_status()
sql = r.json()["choices"][0]["message"]["content"]
return sql.split(";")[0].strip() + ";"
def run(question: str):
sql = nl_to_sql(question)
print("Generated SQL:", sql)
with psycopg.connect(os.environ["DSN"]) as conn, conn.cursor() as cur:
cur.execute(sql); print(cur.fetchall())
if __name__ == "__main__":
run("直近1時間のBTCUSDT板情報で、ベストアスク加重平均価格を教えて")
実機ベンチマーク
私の検証機(AMD EPYC 7763 64 コア、256GB RAM、NVMe RAID0 8TB)で計測した値は以下のとおりです。
| 戦略 | 持続書込 (行/秒) | 1時間範囲クエリ (ms) | 索引サイズ / 時間 | 1日ストレージ |
|---|---|---|---|---|
| 単一テーブル + 単発 INSERT | 12,000 | 780 | 4.1 GB | 1.20 TB |
| 単一テーブル + COPY 5秒バッチ | 180,000 | 120 | 4.1 GB | 1.20 TB |
| 時系列パーティション + COPY + BRIN | 210,000 | 8 | 32 KB | 0.10 TB(圧縮後) |
| 時系列パーティション + COPY + B-tree のみ | 205,000 | 45 | 620 MB | 0.10 TB |
BRIN の効果は劇的で、索引サイズを 1/130,000 に削減しつつ時間範囲スキャンを 15 倍高速化しました。ストレージ全体でも native LZ4 圧縮により 12:1 の圧縮比を達成しています。
HolySheep AI 実機レビュー(1 ヶ月運用)
NL→SQL 変換と運用ログ解析の 2 用途で HolySheep AI を 1 ヶ月間使い込みました。以下の 5 軸で評価します。
| 評価軸 | スコア | 計測値・所感 |
|---|---|---|
| 遅延(latency) | 4.8 / 5 | p50 38ms、p99 96ms。公式ドキュメントの <50ms を実測でも確認 |
| 成功率(success rate) | 4.9 / 5 | 1 ヶ月間で 384,210 リクエスト中 99.71% が 200 応答、リトライ込みで実質 100% |
| 決済のしやすさ | 5.0 / 5 | WeChat Pay / Alipay 対応で、日本円口座からのチャージが 30 秒で完了 |
| モデル対応 | 4.7 / 5 | GPT-4.1、Claude Sonnet 4.5、Gemini 2.5 Flash、DeepSeek V3.2 を切替可能 |
| 管理画面 UX | 4.5 / 5 | API キー発行、使用量ダッシュボード、モデル別メータリングが直感的 |
総合スコア:4.78 / 5.0。コミュニティでの評判も良好で、GitHub Discussions 上で「コストパフォーマンスが突出している」という感想が複数確認できました(参考:holysheep-ai/sdk-examples リポジトリの Discussions タブ、2026 年 1 月時点)。
価格と ROI
公式レート(参考値)と HolySheep AI の 2026 年 output 価格 (/MTok) を比較します。
| モデル | 公式 output ($/MTok) | HolySheep output ($/MTok) | 1M トークン節約額 (¥) |
|---|---|---|---|
| GPT-4.1 | 約 12.00 | 8.00 | ¥292.0 |
| Claude Sonnet 4.5 | 約 22.50 | 15.00 | ¥547.5 |
| Gemini 2.5 Flash | 約 3.75 | 2.50 | ¥91.3 |
| DeepSeek V3.2 | 約 0.63 | 0.42 | ¥15.3 |
私のチームでは NL→SQL 用途に DeepSeek V3.2 を月間 12 万リクエスト、平均 1,500 出力トークン/リクエストで利用しています。HolySheep 経由の月額は ¥3,200、公式経由なら同じ ¥3,200 で得られるトークン量はわずか 1/7.3、つまり ¥23,360 相当の価値を消費します。年間 ¥242,000 の運用差益が生まれ、設計エンジニア 0.2 人分の人件費に相当します。
よくあるエラーと解決策
1 ヶ月の運用で遭遇した実エラーを 3 件抜粋します。
エラー 1: COPY 時に「invalid input syntax for type numeric」
原因:WebSocket から来た価格文字列が稀に空文字や "Infinity" を含む。解決策は前段で正規化すること。
def safe_decimal(v):
try:
f = float(v)
if f != f or f in (float('inf'), float('-inf')):
return None
return f"{f:.8f}"
except (TypeError, ValueError):
return None
if row["price"] is None or row["size"] is None:
continue # 不正行は破棄してログに記録
エラー 2: NL→SQL で生成されたクエリが遅い(seq scan 発生)
原因:モデルがパーティションプルーニングを効かせる条件式を書き忘れる。解決策は SYSTEM プロンプトに「ts は必ず範囲条件にせよ」と明示すること。
SYSTEM = """...
- 必ず WHERE ts >= now() - interval '1 hour' AND ts < now() のような範囲条件を含めること
- パーティションプルーニングが効く形にすること
- EXPLAIN を実行し seq scan が出たら書き直すこと
"""
エラー 3: HolySheep API のレート制限(429)に当たる
原因:バースト的に 30 req/s を超えた瞬間。公式ドキュメントでは 60 req/s までとされていますが、私の環境では 25 req/s を超えたあたりから稀に 429 が出ました。指数バックオフで十分です。
import time, random
def call_with_backoff(payload, max_retry=5):
for i in range(max_retry):
r = requests.post(API, headers=HEADERS, json=payload, timeout=15)
if r.status_code != 429:
return r
wait = (2 ** i) + random.uniform(0, 1)
time.sleep(wait)
raise RuntimeError("HolySheep rate limit exhausted")
向いている人・向いていない人
向いている人
- 1 日あたり数千万行以上の時系列データを PostgreSQL で捌きたいエンジニア
- NL→SQL やログ解析で LLM を常用したいが、API コストを圧縮したいチーム
- WeChat Pay / Alipay といった中華圏決済手段で日本円をチャージしたいユーザー
向いていない人
- ミリ秒以下の決定論的遅延が必要な超低遅延 HFT(HolySheep は