🚴

ツールド東北リアルタイムマップを支える技術【バックエンド・インフラ編】🚴

2022/12/22に公開約6,700字

Mapboxはデジタル地図のSDKやWebサービスを提供する米国企業です。筆者はMapboxの東京オフィスでデジタル地図向けの広告配信プラットフォームのバックエンドをRustで開発しています。2022年9月17日に行われたツール・ド・東北 2022 (以下、ツールド東北と表記します)にてMapbox有志でリアルタイムマップを開発しましたので、技術紹介や開発の裏側を話したいと思います。

ツールド東北リアルタイムマップ

https://www.youtube.com/watch?v=tHqoRQnJoqU

ツールド東北リアルタイムマップは、ツールド東北にライダーとして参加された方達へのWebアプリで、以下の機能を提供します。

  • Mapboxによる美しい3D地図
  • ライダーのリアルタイム位置表示
  • ライダー検索
  • 走行リプレイ
  • コース周辺の見どころ表示
  • ツイート一覧表示
  • 押すと花火が上がる応援ボタン

2022年12月時点ではアーカイブモードになっており上記の機能は一部制限されていますが、大会当日の走行リプレイをみたり、ツイートを見ることができます。
https://realtimemap.jp/#/

Webアプリの詳しい機能はツールド東北リアルタイムマップを支える技術【フロントエンド編】を参照ください。

https://zenn.dev/yukinakanaka/articles/6d2f15304b8a42

プロジェクトのタイムライン

タイムラインはだいたい以下の通りでした。PMさんが優秀だったので筆者は開発に集中できましたが、実質開発期間2ヶ月半でエンジニア2人で、フロントエンド、バックエンド、インフラを作るのはさすがに骨が折れました。

  • 5月末: エンジニアリングチーム・キックオフ
    企画,設計,技術調査,検証

  • 6月末: PoC評価
    開発

  • 8月8日: 東京テストラン
    開発

  • 9月2日: 宮城女川現地テストラン
    開発/テスト

  • 9月17日: 本番
    アーカイブモード開発

  • 10月: アーカイブモード公開

開発体制

Mapboxからエンジニアだけでなくマーケやオペレーション等多くの人がボランティアとして関わっていました。エンジニアリングチームはだいたい以下のような体制でした。@yukinakanakaにはフロント、バック、インフラ全部に関わってもらいました、というか筆者がインフラ周りもやろうとしたんですが、配信部分の開発だけで手一杯で結果いろいろお願いすることになってしまいました。本当に感謝 🙏

  • PM: 2名
  • デザイナー: 1名
  • フロントエンド: @yukinakanaka
  • バックエンド:
  • インフラストラクチャー: @yukinakanaka
  • ビデオストリーミング: 1名

要件と技術選定

要件

  • ツイートを表示したい
  • ニックネームでライダーを検索したい
  • リプレイを表示したい
  • Mapboxライダーにつけたカメラから走行動画をビデオストリーミングをしたい
  • 最大ライダー数1500人を表示できる
  • Webアプリの最大同時接続5000人

上記の要件以外に、エンジニアで以下も付け加え技術選定を行いました。

  • 大会終了後にOSSとしてコードを公開するので、特定のクラウドに依存しない構成にする
  • エンジニア2人しかいないので割り切るべきところは割り切る
  • フロントエンドで頑張り過ぎない、バックエンドでやる部分はバックエンドで
  • 業務で使っていないテクノロジーを触ってみたい

技術選定

技術 選定理由
クラウド AWS いつも使っているので
コンテナ基盤 EKS on Fargate いつもEKS on EC2を使っているのでFargate試してみたかった
プログラミング言語: TypeScript/NodeJS フロント、バックエンドが同じ言語の方がOSSとしての魅力が上がると考えたため
データベース InfluxDB, Redis 時系列データベース使ってみたかった
その他 gRPC, gRPC Web[1] スキーマ駆動開発したい。gRPC Webを使ってみたかった。フロントエンドにポーリングはさせたくなかった

割と普通な技術選定に見えますが、筆者は普段Rustでバックエンドエンジニア、@yukinakanakaはインフラ・データエンジニアとして働いており、React/TypeScript/NodeJS業務経験ほぼなしで、開発期間も短いので、自分達に取ってはチャレンジングでした。

アーキテクチャ

アーキテクチャは以下のようになりました。

(図が大きいのでクリックして拡大してみてください)

  • tdt-front: Reactとmapbox-gl-jsを使ったフロントエンド
  • tdt-api-server: TdTのgRPC APIサーバ、フロントエンドが必要なAPIを提供する。InfluxDBからはリプレイのためにデータをReadする。tdt-injeterからリアルタイムデータを受信し、フロントエンドにstreamingする。grpcwebproxyをSideCarしてgRPCをブラウザが解釈できるHTTP 1.1に変換する
  • tdt-injester: ライダー位置座標を提供する外部のサーバに対して15秒に一回ポーリングし、gRPC Server-side Streamingでtdt-api-serverにデータをfowardする
  • tdt-pss-proxy/tdt-image-converter: 外部サーバが返すアイコンを画像処理します。tdt-injesterからみて透過的にしたかったのでtdt-pss-proxyはHTTPプロキシとして動作する
  • tdt-tweet-collector-stream/tdt-tweet-collector-job: Twitter APIをコールしてTweets取得、InfluxDBに保存して、tdt-api-serverにStreamingで返す
  • influxdb: ライダー位置座標、ツイートを保存する
  • redis: 応援カウントを保存する

リアルタイム&リプレイ配信

先ほ全体図からリアルタイム&リプレイ配信部分だけ抜き出しました。

この設計では外部サーバから位置座標を取得する以外はポーリングは全く使用しておらず、全てgRPC Server-side Streamingでデータのやりとりをしています。ポーリングをしなくなることによって、サーバーサイドで更新タイミングを調節したり、フロントエンド・バックエンドで更新データを管理しやすくなったり等さまざまなメリットがありました。詳しくはツールド東北リアルタイムマップを支える技術【フロントエンド編】/更新タイミング制御を参照ください。

冗長性・スケーラビリティ

システムの各コンポーネントが単一障害点にならないこと、落ちても最小限のデータ欠損で済むように設計しました。本来はtdt-injesterinfluxdbも多重化すべきですが、tdt-injesterinfluxdbが落ちた場合のデータ欠損が数秒程度で済みそうだったので、シングルインスタンスで乗り切ることにしました。

インフラ

インフラはAWSとKubernetesを採用し、なるべく手間をかけないようにしました。

インフラ管理

  • 静的コンテンツ: AWS S3
  • バックエンドサービス: AWS on Fargate
    • eksctlを用いてサクッと構築
  • Database = AWS EKS on Fargate & AWS EFS

サービス管理

  • 高負荷時の自動スケールアウト
  • 異常終了した際の自動再起動
  • 定期的なJobの実行

位置補正・予測アルゴリズム

GPS位置座標はライダーさんがスマホアプリ内で30秒、1分、2分のどれかを選べることになっており、リアルタイムマップのバックエンドが15秒毎に外部サーバへ座標を問い合わせるため、最大で2分15秒遅れる可能性があることが分かっていました。また、GPS座標はさまざまな要因でズレることがあるため、スムーズな走行軌跡を表示するためには位置補正と予測アルゴリズムが必要不可欠です。

位置補正アルゴリズム

  1. 前回座標と速度から以下の式でコース上での到達可能パスを計算する
    • 到達可能パス = 前回座標 + 速度 x 経過時間 + α
  2. TurfJSというライブラリのnearestPointOnLineを使ってRaw座標から到達可能パスに最も近い点を補正座標とする

なぜ到達可能パスの計算が必要かというと、単純にコースに対してnearestPointOnLineを使って補正すると、折り返し地点のようなコースにおいて本来進んでいる方向とは逆側に補正されてしまうのを防ぎたかったからです。

位置予測アルゴリズム

位置予測は単純に前回座標から速度x経過時間分コース上を進めました。

本番当日まで

負荷試験

Mockサーバー上にランダムに速度を割り当てた擬似ライダー1500人を走らせた状態で、アプリのセッションを増やし続ける負荷テストを行いました。負荷試験の結果、ログイン時のInfluxDBへのクエリが重くて途中でInfluxDBがOOM(Out Of Memory)で死んでしまうことがわかりました。バックエンドサービス起動時に一度だけInfluxDBにクエリしてメモリにキャッシュするようにして解決しました。


負荷試験の様子

Fake GPSを使った擬似走行試験

AndroidのMock Locationsという位置偽装アプリを使ってひたすら走行試験をしました。Mock Locationsを使うと位置座標や走行速度やを自由に変更できるので自宅に居ながら東北のコース上で走行試験をすることができました。このツールがなかったら位置補正・予測アルゴリズムのデバッグに数倍は時間がかかったと思います。


Mock LocationsのPlayStoreのスクリーンショット

本番当日

@yukinakanakaは現地へ行き、筆者は自宅で監視・サポートをしましたが、これがとてもよかったです。@yukinakanakaが現地で起こったトラブルを切り分けし、リアルタイムで報告してもらいながら筆者がログの調査をしたり出来ました。

その他、小さなトラブルはありましたが、無事本番を乗り切ることができました。


リアルタイムマップ当日の様子

(が、実は大会当日に弊社CEO高田の走行リプレイがバグってたのはここだけの秘密です😂)

良かったこと

  • 最初にgRPCでIFを決めMockサーバを作ったのでfront担当の手が止まることなく開発できた
  • 事前に負荷試験をして性能の問題を修正できた
  • マイクロサービスにして良かった
    • 走行試験中に一部を何度か修正・デプロイしたがRiderへの影響はなかった
  • 大会当日、大きなトラブルはなかった

反省点

  • フロント・バックエンドでほぼコードの共有はなかった。無理にTypeScriptで共通化するより、より手に馴染んだRustで書いた方が品質も開発生産性も良かったかも?

終わりに

ツールド東北2022運営に関わって頂いた方々、参加してくれたライダーの方達に感謝申し上げます。来年もより良いリアルタイムマップにできるように頑張りたいと思います。

脚注
  1. ブラウザはgRPCに必要な機能を完全にサポートしていないため直接gRPCを使えます。gRPC WebはgRPCのサーバがブラウザが通信できるようにHTTP1.1ベースの通信に変換する技術です。UnaryとServer-sideストリーミングがサポートされています。 ↩︎

GitHubで編集を提案

Discussion

ログインするとコメントできます