昨年の夏の終わりくらいから人生初の格闘ゲームチャレンジとしてStreet Fighet VIを初めてみた。
今年の2月からはジュリをメインキャラにして地道に成長を続けているが、公式サイトであるBuckler’s Boot Campのバトルデータは100件を超えると古いものから捨てられてしまうため、スクレイピングをして自前のDBにデータをストアして可視化ツールとしてMatabaseを使ってダッシュボードを構築してみた話。
GWの個人開発としてある程度動くものができたのでここに記してみる。
構成
スクレイピングアプリケーション本体
- Golang
スクレイピングといえばPythonみたいなところがあるので、最初はPythonで構築していたのだがあまりにも自分とPythonとの相性が悪いので断念。
静的型付け言語が正義ということでGolangを選定してみた。
スクレイピングに用いたライブラリは github.com/go-rod/rod を今回は選定してみた。スクレイピング先が結構リッチなWebサイトのため、DevTools Protocolに準拠していてヘッドレスブラウザを動かすベースでできればとりあえず目的も達成できるだろうということで。
リポジトリはPrivateにしているので詳しいコード紹介は除くが、アプリケーションが立ち上がるとログイン(2FAも)を行って、バトルログのページをfetchしてデータベースに保存していく形。保存されていないデータが出てくるまでページも遡ってfetchする。初期fetch後は15分に1回fetchをするように time
パッケージのTickerを用いている。
SQLへのクエリは github.com/sqlc-dev/sqlc を用いてコード生成している。ドライバはpgx。ここらへんは本業での知見を活かした形で選定。
データベース
- PostgreSQL
これも特に強い意志はないが、本業での知見を活かす形で選定。SQL力低いけどクエリを書いた経験が多いということで採用。
前項でも記載したが、クエリは github.com/sqlc-dev/sqlc を用いて生成して触っている形。sqlcでのUUIDは github.com/gofrs/uuid を使っている。
BIツール
- Metabase
最初はGrafanaを用いて構築をしていたが、指標ごとのタイムレンジを付けることができない(ダッシュボード内で同じタイムレンジで指標が出る)ため、そもそもこういうデータのダッシュボード可視化には向いてないなと思い、別のツールを探したところOSSの中で出てきてUIもモダンだしかわいいので使ってみようということで選定。(Grafana自体をディスってるわけではなく、自分の今回の用途では向いてなかったということ)
直感的に使え、クエリを書いてビジュアライゼーションができるので用途にマッチしていた。加えて、Webページ画面内からDBのレコードを直接閲覧できるのも個人的に好きポイント。今回はクラウド上で動かしているのでテーブルの中身を簡単に閲覧できるのは嬉しい要素。
インフラ
- 某VPS
- Docker
- nginx
- Let’s Encrypt
- Route53
- mackerel
今回は某VPSサービスの2GBプランを使っていてコストとしては1,738円/月になっている。最初1GBプランを使っていたのだが、MetabaseがJavaアプリということで結構メモリを使うので2GBプランにアップグレード。動くのだが、動かし続けるとMetabaseがOOMで落ちる問題(後述)もあるが、個人サービスなのでコストを払えるギリギリラインということでこのプランで。
今回の一連のサービスに存在している様々なサービス(アプリケーション、DB、BIツール)はすべてそれぞれDockerのコンテナとして作っている。開発体験向上のためDocker Composeを用いて手元で開発していたが、めんどくさいのでVPS上でもそのままDocker Composeを使ってコンテナを立ち上げている。Metabaseも公式からDocker Imageが出ていたので用意にサービス構築をすることができて非常によかった。
別に自分で使うサービスなのでVPSのIPを直叩きすればサービスは使えたのだが、IPを覚えられないのでドメインを当てている。ドメインの管理自体はAWSのRoute53を使っていて普通にAレコードを設定している。VPS上にnginxを直接インストールして立ち上げており、80にリクエストが来るとローカルホストのMetabaseのポートにフォワードするようなリバースプロキシの設定を入れている。これでVPSのセキュリティ設定も80, 443だけ開けておけばよくなったので安心。
証明書自体はLet's Encryptで取得。nginxでその証明書を使ってリバースプロキシするようになっているので同じドメインの別サブドメインでHSTSの設定がされていてもSSLで接続できるようにした。(実際困った)
VPS自体の監視はmackerelを用いて監視している。主に監視しているのは今回はひたすらDBにデータを保存していくもののためファイルシステムの使用量、Metabaseが思ったより食いしん坊なのでメモリ、そして今回はDockerを用いてすべて立ち上げているためDockerのコンテナの稼働状況を見られるようにした。
死活監視も行っており、80, 443にリクエストを飛ばして200が返ってこなかったらDiscordに通知が来るようになっている。
話が脱線するが、DiscordのWebhook APIはSlack互換(DiscordのWebhook URLの末尾に /slack
を付けるとSlackに飛ばすのと同じリクエストで処理できる)なのでSlackは公式で対応しているが、Discordは対応していないなどのサービスでかなり便利だった。
できたもの
以下のようなダッシュボードを構築できた。
nullとか出ているのは、当日はJST管理なのでこれを書いているタイミングは未明なのでまだ今日は遊んでいないのでデータがないため。
今日何戦してて勝率いくつとか、24時間のポイント変動が見れると「着実に成長してるな〜」とか「今日調子悪いな……」とかがひと目でわかっていい。線グラフはトレンドを表示できるので「なんだかんだ緩やかに右肩上がりだからこのまま頑張ろう」とかになる。
キャラ別の勝率も出すことで自分の苦手なキャラを累計で可視化できるようになっている。にしてもデータ母数の割にザンギエフとマッチしすぎである。
対戦履歴も1レコードずつ出しており、「あ〜この時ランク格上の人とあたったけどいい感じで勝てたな」とかがパット見でわかる。そのリプレイが見たいときはリプレイIDも出しているのでゲーム内でID検索でリプレイを再生しやすい。
実装例として日別の勝率テーブル構築の裏側になっているが、こんな形でPostgreSQLのクエリを書いてデータをSelectしたあとにGUIからビジュアライゼーション設定ができる。つまり何でもできる。Metabaseはノーコードで色々できるっぽいけどそこは使えてない。
条件付き書式でセルの色等を変えられるのは嬉しい。勝率5割切ってる日は赤にするルールを入れている。
Metabaseはアカウントを作ってログインを要求されるので、自分のダッシュボードを公開したい相手に対してはアカウントを発行してこっそり自分のデータを覗いてもらって「おい!全然やってねえじゃねえか!」みたいな喝を入れてもらうことも可能。
mackerelのダッシュボードはこんな感じ。ファイルシステムは100GBあるのでしばらく問題ないがすぐ気付ける様にでっかく(アラートも設定している)。右上のDocker Running Stateの積み上げグラフはコンテナの稼働状況が0,1でメトリクスを取っている。4つコンテナがあるので4であるのが正常。抜け落ちて3っぽくなっているのはMetabaseのコンテナがOOMで死んだタイミング。寝てたので放置されていた。個人だけで使うサービスで本当に良かった。
Datadogも考えたが、個人で使うにはコストが高いので断念。mackerelはまだトライアルで請求が来ていないが、死活監視とダッシュボードをしっかり使っているのでフリープランでは収まらない。とはいえ1ホストなのでそこそこで収まるかな〜感。
困った話
AWSで構築してたらリクエストブロックされていた話
最初はAWSのEC2を使って構築していたが、アプリケーションを動かしてみてリクエストがブロックされている事に気づいた。流石にAWSやGCPなど有名どころのクラウドサービスはスクレイピング対策としてIPレンジ丸ごとWAFなどで弾いてるっぽかった。
最初絶望したが、いろんなVPSサービスを試したところリクエストが通るサービスがあったのでそこで落ち着いた。もしここもブロックされたらローカルで動かすしかなくなってしまって自鯖とか構築することになって厳しいのでこのまま安泰で動いてほしい。
Metabaseがメモリをめっちゃ食う話
Metabase自体がJavaアプリケーションということで結構メモリを食うっぽい。最初VPSを1GBのインスタンスを使っていたがそもそもMetabaseのコンテナが動かなかった。ログを見るとOOM。2GBにアップグレードしたら動いたので2GBプランを使っている。
しかし、Metabaseを動かし続けているとどんどんメモリを食っていき、最終的にリソースのリミットに達するとOOMで死ぬ(大体このVPSで動かしていると24時間で死ぬくらいじわじわ増加)問題がきつかった。
公式ドキュメントでJavaなので JAVA_OPT=-Xmx2g
などのヒープ最大サイズを環境変数として埋めると効くというのがあったので設定してみたが、結局設定値に達してOOMで死んでいた(なんでやねん)。メモリリークしてるんか?という気分。
今回は自分だけしか使わないサービスでもあるのでVPSのcronを使って24時間に1回Metabaseのコンテナを落として立ち上げ直す処理を入れている。入れてからは問題なく安定稼働してくれていそうだが、もしこれでもだめなら半日1回とか6時間に1回とかでコンテナ立ち上げ直す必要がでてくるので厳しい。
ちなみに最初選定していたGrafanaは1GBプランのVPSでもキビキビ動いてくれた。Metabaseは結構見た目も機能もリッチなので仕方ないとは思うがもうちょっと頑張ってほしい。
感想
そもそも勝率とか今のポイントとかどのキャラが苦手なのかという可視化がされたことによるメンタルに対する恩恵が大きかった。
「おー今日結構やれてるな」みたいなのがちゃんと見られるのは向上心につながって非常にいい。負け続けて沼った日でもこういうのがないと今日何敗してるかわからないけど、可視化されていると「あ〜今日流石に勝率やばすぎるからそろそろやめとくか」みたいな判断もできて良い(そういう判断方法が良いかは置いといて)
SQLはかなり苦手意識を持っていて(大学でデータベース入門という講義の単位を落としたことがある)、今回ダッシュボードの構築とか大丈夫かな〜って思ってたけどChatGPTにそこは全面的に助けてもらった。テーブルのCreateクエリをまずコンテキストで持たせておいて「こういう指標だしたい!」みたいなのを要求すると8割くらいそのまま使えるクエリが出来上がるので残った部分を手直しして今回のMetabaseのダッシュボードは構築できた。いい時代ですね。