私はこれまで 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 点に集約されます。

アーキテクチャ全体像

採用したのは「時系列パーティショニング + 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日ストレージ
単一テーブル + 単発 INSERT12,0007804.1 GB1.20 TB
単一テーブル + COPY 5秒バッチ180,0001204.1 GB1.20 TB
時系列パーティション + COPY + BRIN210,000832 KB0.10 TB(圧縮後)
時系列パーティション + COPY + B-tree のみ205,00045620 MB0.10 TB

BRIN の効果は劇的で、索引サイズを 1/130,000 に削減しつつ時間範囲スキャンを 15 倍高速化しました。ストレージ全体でも native LZ4 圧縮により 12:1 の圧縮比を達成しています。

HolySheep AI 実機レビュー(1 ヶ月運用)

NL→SQL 変換と運用ログ解析の 2 用途で HolySheep AI を 1 ヶ月間使い込みました。以下の 5 軸で評価します。

評価軸スコア計測値・所感
遅延(latency)4.8 / 5p50 38ms、p99 96ms。公式ドキュメントの <50ms を実測でも確認
成功率(success rate)4.9 / 51 ヶ月間で 384,210 リクエスト中 99.71% が 200 応答、リトライ込みで実質 100%
決済のしやすさ5.0 / 5WeChat Pay / Alipay 対応で、日本円口座からのチャージが 30 秒で完了
モデル対応4.7 / 5GPT-4.1、Claude Sonnet 4.5、Gemini 2.5 Flash、DeepSeek V3.2 を切替可能
管理画面 UX4.5 / 5API キー発行、使用量ダッシュボード、モデル別メータリングが直感的

総合スコア: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.008.00¥292.0
Claude Sonnet 4.5約 22.5015.00¥547.5
Gemini 2.5 Flash約 3.752.50¥91.3
DeepSeek V3.2約 0.630.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")

向いている人・向いていない人

向いている人

向いていない人