データは配列で送られてくる
DBに配列は入れられない
データの個数は280件/秒
★10分程度スナップショット+差分の件数を自前で計測:約280件/秒の平均と出た。データが送られてくる回数や通信量は今回問題ではないので内部の { price, size }
で1件とカウントしている。
以上から
データを配列で入れるのは非効率なため1件ごとに分割して記録する必要があると考える。
この場合データ登録にDBが追いつけるのかを調査する。
昨日記事を書いた後に公式ドキュメントを精査していたところHardware sizing guidelinesというページがあり性能に対してどれぐらいのデータが記録できるのかの目安とできそうだった。
まずは次の表を参考にする。
一番上の最小構成を見てほしい。Writes per secondは5000未満となっており、今回私のデータが余裕であることが分かる(1件あたりのデータ量も少ない)
では、最小構成はどんなPCだろうか。
家庭で常時稼働させられるLinux PCといえば現時点ではRaspberry Pi 4が最適だろう。
Raspberry Pi 4のスペックはCPU4コア、メモリ4Gで最低スペックをクリアしている。IOPSに関して言えばSSDが安物でも数万と言われているので余裕である。
しかし気になる項目がある。Unique series < 100,000 とはなんだろうか。
公式のドキュメント( Seriesの説明 )を読む必要がある。
A logical grouping of data defined by shared measurement, tag set, and field key.
この説明では短すぎてSeriesとは何かが分かりにくいが、InfluxDB key conceptsというページにもう少し詳しく具体例が乗っていた。
TagとFieldの違いについてはここでは説明しないがTagにはよくアクセスするデータを入れて検索を高速化させる役割があるためこのあたりのコストは注意が必要だ。
例えば今回のデータ { price:値, size:値 }
で考えればFieldが2個なのでUnique seriesは「2」で良いはずだが、ここにtag=1
の { price:値, size:値 }
tag=2
の { price:値, size:値 }
とすると倍になり「4」になる。
つまりtagの数だけ指数関数的に増えると考えている。
結論:いずれにせよ今回のパターンではデータの数自体は多いものの項目は極めて限定的であり「Raspberry Pi 4 + SSD」のようなマシンでも十分データをストアできると考える。
以前の謎のチャート製作時に使用したJSON形式をそのままInfluxDBに入れる場合は危険だ。
謎のチャートのデータ構造は { price:値, size:値 }
を変換し { 価格(キー):サイズ(値) }
にしている。
例えば10万円のBTCに0.5の指値が入っていた場合は { 100000 : 0.5 } というデータになる構造だ。
この場合データ量としてはスマートだがInfluxDBの指標に換算するとFieldsの数=∞(100万幅は軽い)ため簡単にUnique seriesの目安を超えてしまう可能性がある。
注意しておきたい。
まずは送られてきたデータの配列構造を分割しInfluxDB用に変換する。
schema: [
{
measurement: 'lightning_board_FX_BTC_JPY', //差分もスナップショットも一つに纏める
fields: {
price: Influx.FieldType.INTEGER,
size: Influx.FieldType.FLOAT,
},
tags: [
'type', //差分とスナップショットはTagを使って分ける
'bid_or_ask' //bidかaskか
'uniq' //重要:後述
]
}
]
今回はTagsを使って差分かスナップショットかどうか(type
)、bidかaskか(bid_or_ask
)を明示しておく。これによりクエリを発行するときにパフォーマンスが向上すると思われる。
uniq
については後述(重要)
余談:tagsはstring:stringのobject限定なのでFieldTypeを設定できないらしく配列にキー名を入れるのみ(自動的にStringと認識)
データ登録は「バッチ(batch)」という概念で行う。
これは{ price:値, size:値 }
を1件として、複数同時にレコード登録する方法だ。
writePoints()関数にオブジェクトを配列として送る事で一度に複数のレコードが記録される。
//スナップショットを登録するコード
//socket.ioにて接続
socket_bF_client.on("lightning_board_snapshot_FX_BTC_JPY", (message) => {
const asks_data = message.asks.map((asks, index) => {
return {
measurement: 'lightning_board_FX_BTC_JPY',
tags: {
type: 'snapshot',
bid_or_ask: 'ask',
uniq: index
},
fields: {
price: asks.price,
size: asks.size
}
}
})
const bids_data = message.bids.map((bids, index) => {
return {
measurement: 'lightning_board_FX_BTC_JPY',
tags: {
type: 'snapshot',
bid_or_ask: 'bid',
uniq: index
},
fields: {
price: bids.price,
size: bids.size
}
}
})
influx.writePoints([...asks_data, ...bids_data]).then(() => {
console.log("ok")
}).catch(err => {
console.error(`Error saving data to InfluxDB! ${err.stack}`)
})
})
公式ドキュメント:How does InfluxDB handle duplicate points?
ここに重複したレコードを上書きする原則について書かれている。バッチで一括登録する場合これがとても重要である。
バッチにて登録する場合、全てのレコードに同じタイムスタンプが入る。
時系列DBは時間そのものが最も重要なので時間が重複しなおかつメジャーメントもタグも同じものであった場合重複と判断し上書きされる。
これの回避のために1レコードづつ違ったタグをいれてやる必要がある。ここではシンプルに初めの配列のインデックスを入れてやるだけでよい。
また、公式では1ナノ秒単位でタイムスタンプをずらす力技も公開されている(タイムスタンプがずれていれば上書きされない)。ケースに合わせて使っていきたい。
Chronografで読み込む場合「Groub by」が指定されていないと上の対策がしてあってもタグが無視され正しく 抽出できなかった。このあたりはデータベースのスペシャリストではないため詳しく説明できない。色々試してみてほしい。
snapshotだけだが、テスト動作させてみた。bidとaskを取得しsumにより板の厚みを可視化している。
一切グラフの描画関係をいじることなくスタイリッシュに出力された。どちらの板が厚いのかが瞬時に判断できる。
30分スナップショットのみを記録(おおよそ秒間に120件)行い、デフォルトのデータから実際にどれぐらいデータ量が膨らんだかを確認したところ差が+118MB。そこから更に35分記録を続けた所+10MB。
この10MBから計算すると差分を入れると倍にはなるので1時間あたり40MBといった試算となる。1日1GB近くなる計算だ。
ここに約定データなど記録するとなると、それなりに容量のあるディスクを積まないと記録を続けるのは難しいかもしれない。
とりあえず板情報を記録する準備はおおよそ整った。既に学習範囲が複雑になり始めているため、この先は覚悟が必要そうだ。