1. 初めに
こんにちは、LINEフィナンシャル開発センター、開発1室の姚 剣楠です。現在はLINE証券の開発を担当しています。
LINE証券はスマホに特化した証券サービスですが、2019年8月開業以来、1株から取引可能な「いちかぶ」や現物取引、信用取引など様々なサービスを順次リリースしました。2021年10月末時点で100万以上の口座向けにサービスを提供しています。
ではこれから、LINE証券のサービスについて、株の現値、気配版、株価ローソクチャートなどの様々な情報をタイムリーに提供しているプライシング・システムを紹介したいと思います。よろしくお願いいたします。
2. プライシング・システムについて
2.1 システム目標
プライシング・システムは、LINE証券のユーザーに株価や気配情報、ローソク足チャートなどのデータをリアルタイムで提供するために構築されたものです。
株価の変動は激しいもので、お客様の立場からすれば、なるべく最新の相場情報を基づいて売買の判断をしたいわけです。情報の提供は数秒でも遅れると、お客様は他のプラットフォームのユーザーと比較して相対的に不利な状況になり、提供した情報は意味のないものになってしまうかもしれません。また、お客様にとっては、古い相場よりも、システム障害によって誤った株価を提供されたり、価格更新が停止されたりすることの方が受け入れがたいことです。要約すると、リアルタイム性と高可用性を兼ねるプライシング・システムを構築することは最大の目標です。
システム設計前に、処理すべき株価(東証1部の全銘柄、取扱予定の国内・海外指数、先物)情報データの規模をテストで見ました。表に示すように、前場オープン(朝9時)時のデータ量は、一番多いです。つまり、朝オープン時のデータ量をうまくハンドリングできれば、システムの目標に満たすことができます。
前場オープン(09:00) | 後場オープン(12:30) | 後場終了(15:00) | 通常 | |
データ件数 | 25,000 ~ 30,000 件/s | 15,000 ~ 20,000 件/s | 15,000 ~ 20,000 件/s | 5,000 ~ 8,000 件/s |
インバウンド | 30 ~ 35 MB/s | 15 ~ 20 MB/s | 15 ~ 20 MB/s | 5 ~ 8 MB/s |
2.2 システムアーキテクチャ
まずは、システムの全体像を理解してもらうために、システム構成図を説明させていただきます。
LINE証券の開発はマイクロサービスのアーキテクチャを採用しております。図に示すように、プライシング・システムは下記の五つのサービスからなって、それぞれ異なる機能を持っています。まず、株価情報ハンドリングサービスは、Refinitiv社から東証一部全銘柄のFeed情報をサブスクライブ(sub)します。そして、受信した内容をKafka経由ですべての関連サービスに配信しています。あとは障害時のことを考慮してBlue/GreenのActive-Activeモードを採用しています。例えば、Greenに障害が出たときはロードバランサ側の制御で、すべてのリクエストをBlueの側に振れるような設定となっています。
機能概要:
- 株価情報ハンドリング(Feed handler service): RefinitivのRealtime Market Data(以下、EMS)をPub・Sub方式でリアルタイム株価情報を受信し、Kafka経由で関連サービスに配信するアプリケーション。
- 株価情報インジェクター(Price info injector): Kafkaで配信されるマーケットデータ(値段、気配など)を格納するアプリケーション。
- 株価ローソクチャート生成(Chart generator service): Kafkaで配信されるマーケットデータをベースで、ローソクチャートデータ(Tick、分・時間足から、日・週・月足まで)を生成するサービス。
- 株価情報検索インジェクター(Price search injector): いろいろな条件(例えば、出来高・値段変動など)で株をランキング・検索するため、Kafkaで配信されるマーケットデータをElasticSearchに蓄積・Indexingするサービス。
- 株価情報APIサービス(Price pool web service): ユーザ向けの株価情報を照会するAPIサービス。
3. システムの特徴
このシステムは主に2つの特徴があります。
- WebFluxを使って、すべてのIO処理はノンブロッキングI/Oであること。
- Kafkaを中心にPub/Subシステムであること。
3.1 Spring WebFluxの導入
Spring WebFluxは、Spring 5.0 から提供しているリアクティブスタック Web フレームワークです。完全にノンブロッキングで、バックプレッシャもサポートし、Netty・Undertowなどの高機能なNIOサーバー上で実行できます。図に示すように、非同期なノンブロッキングのスレッドモデルは、サーバーリソースを最大限活用できますが、より少ないリソースで多くの処理を捌けます。
- ノンブロッキング: リクエストやレスポンスを処理するために少数のスレッドが用意されます。1つのスレッドで沢山のリクエストを処理できます。
- ブロッキング: 1つのリクエストに対して1つのスレッドが割り当てられます。
本システムはデータ集約的なシステムであり、Feed情報更新頻度結構高いです。そして、最新銘柄値段情報をタイムリーにお客様に提供する必要があり、APIサーバへのQPSも高いです。ハードウェアのリソースを効率よく利用し、システムのスループット、スケーラビリティを向上するため、WebFluxの導入を決まりました。
3.2 Kafka中心のPub/Subシステム
下記の非機能要件に満たすアーキテクチャを設計する必要がありますが、要約すると、優れた拡張性・スケーラビリティ・可用性が求められています。
- 複数のコンシューマ・サービスは、プロデューサ・サービス(株価情報ハンドリング)からFeed情報を受信し、今後新しいコンシューマサービスサービスの追加も考慮する必要があります。
- 株市場の状況によって、Feed情報量の急激な増加を考慮し、負荷急増を耐えられるアーキテクチャを考える必要があります。
- ただ1つのFeedデータの処理失敗・欠損で銘柄のローソクチャートを正しく作成できなくなるので、高いデータの可能性を確保する必要があります。
当初は、Socket通信でサービス間のデータを連携する案も検討しましたが、通信速度が速いですが、下記の欠点があります。
- プロデューサ・サービスは同じデータを同時に複数のサービスに送信するので、負荷が高いです。かつ、新規プロデューサ・サービス追加時、負荷テストをする手間がかかります。
- Socket接続は想定外の原因で、一瞬切れる場合、再接続できるまでデータはもらえない問題点があります。
対照的に、メッセージキューをプロデューサとプロデューサの間でのデータを受け渡すためのハブとして活用すれば、上記の案の欠点を回避できます。それで、検討した結果、メッセージキューの導入に至りました。メッセージキューも幾つの選択肢(Kafka、Rocketmq、Rabbitmq)がありますが、LineのプロジェクトはよくKafkaを使っています。Kafkaはスケーラビリティに優れた分散メッセージキューであり、スループットが高く、IOの処理速度が速い、高可用性のなどの特徴を持っています。
図に示す通り、1つのTopicは複数のPartition(図の例はPartition三つ)に分かれており、複数のPartitionにメッセージを並列的に振り分けながらプロデューサします。Topicに送信したデータは保存され、読み出されてもすぐには削除されません。そのため、同じTopicのデータを複数のアプリケーションから読み出すことができます。また、Partition 内のデータはBroker間で複製されるため、一部のBrokerノードに障害が発生してもデータが失われにくい仕組みとなっています。
※ KafkaにはTopicとPartitionという概念があります。 Topicはメッセージを整理するためのカテゴリーを示す概念です。Topic内のメッセージはPartitionという単位で分散させています。
4. おわりに
今回はLINE証券の株価情報を提供しているプライシング・システムの設計を紹介しました。
1取引日の7時間(09:00 ~ 15:00)のFeed配信・処理は例えるならテレビの生放送のようなものです。なんらかの原因(ネットワーク不安定など)によるFeed処理の失敗(ユーザに提示した値段表示、生成したローソクが誤った)は「放送事故」となります。テレビ局では「放送事故」を起こさないように、多くの努力が払われています。
それはLINE証券でも同じで、システムの可用性向上に日々たくさんの力が注がれています。例えば、各サービスのクラスター化、障害時自動フェイルオーバー、Blue/Greenのバルクヘッドパターン(※)の導入など、いろいろ工夫しました。それでも、テレビで生放送中に予期せぬことが起きるかわからないのと同じで、LINE証券でも1取引日の7時間の中で何が起こるかは誰にもわかりません。何かあったときになるべくすぐに対応できるように、サービス がリリースしてから数か月間もプレッシャーを感じながら、対応のシナリオを考えていました。毎日朝マーケットオープン時のサービス状況を監視し、迅速的にFeedを処理できたことを見て達成感を感じています。各取引日の1億ぐらいのFeedメッセージをもらって、その中の96.9パーセントのデータは基本20ミリ秒(Refinitivからメッセージを受信してから各Consumer serviceの処理完了までの時間)以内に処理できました。これはベータ環境の実績ですが、本番環境はもっと良いパフォーマンスが見込まれています。
※ バルクヘッド パターン:障害を許容するアプリケーション設計の一種です。 アプリケーションの要素をプールに分離し、1つの要素で障害が発生しても、他の要素は引き続き機能できるようにします。
最後に、一緒にプライシング・システムの設計と開発を手掛けたLai Rongchangさんにも厚く御礼を申し上げます。