目次
オーダーブックデータ保存戦略
📊 現在の保存方式分析
テーブル構造と保存内容
1. orderbook_snapshots テーブル
- 目的: 集約されたスナップショット保存
- 保存項目:
```sql - timestamp, exchange, symbol
- best_bid_price, best_bid_size
- best_ask_price, best_ask_size
- mid_price, spread, spread_bps
- depth_imbalance, bid_liquidity, ask_liquidity
- weighted_mid_price, bid_levels, ask_levels
``` - データサイズ: ~200バイト/レコード
2. orderbook_levels テーブル
- 目的: 個別価格レベルの詳細保存
- 保存項目:
```sql - timestamp, exchange, symbol
- side (bid/ask), level (0=best)
- price, size
``` - データサイズ: ~80バイト × 20レベル = 1,600バイト/レコード
- デフォルト設定: 上位10レベル保存
3. orderbook_metrics テーブル
- 目的: 時系列集約メトリクス
- 保存項目: 1分間統計、5レベル深度メトリクス、流動性スコア等
- データサイズ: ~250バイト/レコード
データ量推定
1秒1更新、4シンボルの場合:
- 日次: ~590MB
- 月次: ~17.7GB
- 年次: ~212GB
🤔 全レベル保存 vs 集約データ保存
トレードオフ分析
| アプローチ | メリット | デメリット | 推奨用途 |
|---|---|---|---|
| 全レベル保存 | • 完全な市場深度 • 詳細バックテスト可能 • マイクロストラクチャー分析 |
• 高ストレージコスト • クエリ性能低下 • 管理複雑性 |
HFT戦略開発、研究 |
| 集約データ保存 | • 低ストレージコスト • 高速クエリ • 管理簡単 |
• 詳細情報喪失 • 限定的バックテスト • 深度分析制限 |
リアルタイム監視、基本分析 |
🎯 推奨:階層型ストレージアーキテクチャ
時間軸による階層化
ホットティア(0-24時間):
ストレージ: インメモリ(Redis/QuestDB キャッシュ)
データ: 全レベル(最大50レベル)
アクセス: < 1ms
用途: リアルタイム取引、HFT
ウォームティア(1-7日):
ストレージ: QuestDB(NVMe SSD)
データ: 上位20レベル + 集約メトリクス
アクセス: < 10ms
用途: 短期分析、戦略調整
コールドティア(7-30日):
ストレージ: QuestDB(圧縮)+ S3
データ: 上位5レベル + 集約メトリクス
アクセス: < 100ms
用途: パフォーマンス分析
アーカイブティア(30日以上):
ストレージ: S3 Glacier
データ: 集約メトリクスのみ
アクセス: 分単位
用途: 長期トレンド分析
データライフサイクル自動化
-- 自動データ移行ルール
CREATE TABLE data_lifecycle_rules (
tier_name TEXT,
retention_days INT,
compression TEXT,
levels_to_keep INT,
action TEXT
);
INSERT INTO data_lifecycle_rules VALUES
('hot', 1, 'none', 50, 'keep_all_levels'),
('warm', 7, 'snappy', 20, 'reduce_levels'),
('cold', 30, 'zstd', 5, 'reduce_levels_compress'),
('archive', 365, 'zstd_max', 0, 'aggregate_only');
🚀 段階的実装アプローチ
Phase 1: 監視と分析(2週間)
// storage_monitor.rs
impl StorageMonitor {
pub async fn analyze_usage_patterns(&self) -> UsageReport {
// アクセスパターン分析
let access_frequency = self.analyze_access_frequency().await?;
// レベル別使用率
let level_usage = self.analyze_level_usage().await?;
// 時間帯別クエリパターン
let query_patterns = self.analyze_query_patterns().await?;
UsageReport {
frequently_accessed_levels: level_usage.top_10(),
peak_hours: query_patterns.peak_times(),
recommended_hot_levels: calculate_optimal_levels(access_frequency),
}
}
}
Phase 2: インデックス最適化(1週間)
-- パフォーマンス最適化インデックス
CREATE INDEX idx_orderbook_symbol_time
ON orderbook_snapshots(symbol, timestamp DESC)
WHERE timestamp > NOW() - INTERVAL '7 days';
CREATE INDEX idx_levels_symbol_time_side
ON orderbook_levels(symbol, timestamp DESC, side, level)
WHERE level <= 10;
-- パーティショニング
ALTER TABLE orderbook_levels
PARTITION BY RANGE (timestamp);
CREATE TABLE orderbook_levels_2024_01
PARTITION OF orderbook_levels
FOR VALUES FROM ('2024-01-01') TO ('2024-02-01');
Phase 3: スマートサンプリング実装(3週間)
// smart_sampler.rs
pub struct MarketAwareSampler {
volatility_threshold: f64,
normal_interval_ms: u64, // 1000ms
high_vol_interval_ms: u64, // 100ms
max_levels_normal: usize, // 10
max_levels_volatile: usize, // 50
}
impl MarketAwareSampler {
pub fn determine_sampling(&self, market_state: &MarketState) -> SamplingConfig {
if market_state.volatility > self.volatility_threshold
|| market_state.is_news_time() {
// 高ボラティリティ/ニュース時
SamplingConfig {
interval: self.high_vol_interval_ms,
levels: self.max_levels_volatile,
compression: CompressionLevel::None,
}
} else {
// 通常時
SamplingConfig {
interval: self.normal_interval_ms,
levels: self.max_levels_normal,
compression: CompressionLevel::Snappy,
}
}
}
}
💡 高頻度取引向け最適化
リアルタイムアクセス最適化
// cache_manager.rs
pub struct OrderbookCache {
redis_client: RedisClient,
cache_ttl: Duration,
}
impl OrderbookCache {
pub async fn get_latest_orderbook(&self, symbol: &str) -> Option<OrderbookSnapshot> {
// L1: Redisキャッシュ(<1ms)
if let Some(cached) = self.redis_client.get(symbol).await? {
return Some(cached);
}
// L2: QuestDB最新データ(<10ms)
let latest = self.questdb_client
.query_latest_orderbook(symbol)
.await?;
// キャッシュ更新
self.redis_client.setex(symbol, latest, self.cache_ttl).await?;
Some(latest)
}
}
差分圧縮による効率化
// delta_compression.rs
pub struct DeltaCompressor {
last_snapshots: HashMap<String, OrderbookSnapshot>,
}
impl DeltaCompressor {
pub fn compress(&mut self, snapshot: OrderbookSnapshot) -> CompressedData {
let symbol = &snapshot.symbol;
if let Some(last) = self.last_snapshots.get(symbol) {
// 差分のみを保存
let delta = OrderbookDelta {
timestamp: snapshot.timestamp,
symbol: symbol.clone(),
changed_levels: self.calculate_changed_levels(last, &snapshot),
};
self.last_snapshots.insert(symbol.clone(), snapshot);
CompressedData::Delta(delta)
} else {
// 初回は完全スナップショット
self.last_snapshots.insert(symbol.clone(), snapshot.clone());
CompressedData::Full(snapshot)
}
}
}
📊 コスト削減効果
ストレージコスト比較
| 保存方式 | 日次容量 | 月次容量 | 年次容量 | 相対コスト |
|---|---|---|---|---|
| 全レベル保存(50レベル) | 2.9GB | 87GB | 1.04TB | 100% |
| 現在の方式(10レベル) | 590MB | 17.7GB | 212GB | 20% |
| 推奨階層型 | 400MB | 12GB | 144GB | 14% |
| スマートサンプリング | 300MB | 9GB | 108GB | 10% |
実装による改善効果
ストレージ削減: 60-70%
クエリ速度向上: 200-300%
リアルタイムアクセス: <1ms(キャッシュヒット時)
バックテスト速度: 150%向上
🔧 実装優先順位
即座に実装可能(1週間)
- バッチサイズ最適化(100→500)
- インデックス追加
- 基本的な監視ダッシュボード
短期実装(2-4週間)
- スマートサンプリング
- Redisキャッシュ層
- 基本的な階層化
中期実装(1-2ヶ月)
- 完全な階層型ストレージ
- 差分圧縮
- 自動ライフサイクル管理
まとめ
オーダーブックデータの保存において重要なのは:
- 階層型アーキテクチャで時間軸に応じた最適化
- スマートサンプリングで市場状況に適応
- 差分圧縮でストレージ効率化
- キャッシュ活用でリアルタイム性確保
- 自動化でメンテナンスコスト削減
この戦略により、HFT要件を満たしながらコストを60-70%削減できます。