最近の砂場活動その13: Vue.js + Nuxt.jsで構築したアプリケーションをTypeScript化する

趣味プロジェクトであるML-Newsのフロントを整備したのは約一年前。その頃はVue.jsのTypeScript対応も微妙な時期(?)だった。

ちょいちょいTypeScriptサポート対応したよという声も(個人的に)見るようになった気がして、自分もやってみることにした。趣味プロジェクトのように忙しいときは結構触らない期間があるプロジェクトでは、型が分かったりlinterで怒ってくれるサポートがあるのはありがたいのである(普段はScala / Golan /Python with mypyで生活しているので)。

やったこと

困ったこと

導入するためにちょい困ったりはしたけど、得られた恩恵のほうが大きいかなという印象です。

Nuxt.jsビギナーズガイド―Vue.js ベースのフレームワークによるシングルページアプリケーション開発

Nuxt.jsビギナーズガイド―Vue.js ベースのフレームワークによるシングルページアプリケーション開発

最近の砂場活動その12: Data Studioを使ってGoogle Analyticsと任意のデータを紐付けてデータ分析する

仕事でデータ分析をやる機運が高まっていて、Google Data Studioをぼちぼち使っていこうとしてる。仕事のデータは分析する前のデータパイプラインなどあれこれ考える必要があるので、ひとまず趣味プロジェクトのML-Newsを題材にして遊んでみる。こういうときに雑に遊べる砂場プロジェクトは最高。

Data Studioの大雑把な概念

Data Source

Data StudioにはData Sourceという概念がある。Data Sourceは名前の通りデータソースで分析をかける対象データを指す。例えば

  • Google Spreadsheet
  • Google Analitics
  • BigQuery
  • Cloud Spanner
  • Search console

などなど様々なものをデータソースとして指定できる。手元のcsvをアップロードするFile Uploadなどもある。

Reports / Explore

同じようにグラフが書けて似ているなって感じだけど、こういう使い分けかな。

  • Reports: みんなで定期的に見るダッシュボードを整理する用
  • Explore: データ分析担当者が探索的に条件等を指定して分析速度重視でやる用

綺麗にやるのは後でやるとして、Data Studioで何ができるか知りたいので、Exploreを先に触った。ここでは中心となるData SourceをGoogle Analyticsとする。

ページタイトル毎のPVなどをテーブルに吐き出したりグラフに描画できて、便利は便利であるけど、これ単独ではGoogle Analyticsでできることとあまり変わらない。ちょいちょい触った結果、威力を発揮しそうなのは複数のデータソースを統合する時かなと思った。SQLでいうjoinを相当ができると思うとよい。

複数のData Sourceを統合して分析

ML-NewsのURIはこういう感じになっていて、以下の数字の箇所がDB内のexample_idというものに対応している。

このexample_idに関連するtweet一覧やブコメ一覧をDBの中に持っている。ちなみにDBではtweetのスキーマを以下のように持っている。

go-active-learning=> \d tweet
                                             Table "public.tweet"
      Column       |            Type             |                         Modifiers
-------------------+-----------------------------+------------------------------------------------------------
 id                | integer                     | not null default nextval('tweet_id_seq'::regclass)
 example_id        | integer                     | not null default nextval('tweet_example_id_seq'::regclass)
 created_at        | timestamp without time zone | not null
 id_str            | text                        | not null
 full_text         | text                        | not null
 favorite_count    | integer                     | not null
 retweet_count     | integer                     | not null
 lang              | text                        | not null
 screen_name       | text                        | not null
 name              | text                        | not null
 profile_image_url | text                        | not null
 label             | integer                     | not null default 0
 score             | double precision            | not null default 0.0
Indexes:
    "tweet_pkey" PRIMARY KEY, btree (id)
    "example_id_id_str_idx_tweet" UNIQUE, btree (example_id, id_str)
    "example_id_idx_tweet" btree (example_id)
Foreign-key constraints:
    "tweet_example_id_fkey" FOREIGN KEY (example_id) REFERENCES example(id) ON DELETE CASCADE

よって、example_idを軸にjoinしてあげれば、PVと言及があるtweet数などの関係を解析することができる。これはあくまで一つの例であって、他にも例えば

  • 顧客の満足度(別のアンケートが手元にある想定)とWebサイト上での使われ方(Google Analiticsが持ってる)
  • 顧客毎の売り上げとWebサイト上での使われ方(Google Analiticsが持ってる)

といったことが業務関連だとできそう。

複数のData Sourceを統合するにはData SourceをBlended Dataというものにする必要がある。

tweetの元データはAWS上のRDSに存在する。業務で真面目にやる場合は何らかのデータパイプラインを組んでBigQueryに定期的に流す、といったことが必要になる(この辺はあまり詳しくない...)と思う。しかし、今回はアドホックな分析がしたいだけなので雑にやる。

  • SQLを叩いてcsvとして手元に落としてくる
  • そのcsvをData Sourceの一つとして選択できるFile Upload経由でGCSに保持

雑にこういった感じのコマンドでcsvにする。

psql -U postgres -h hogehoge -d go-active-learning -p 5431 -F "," -A --pset footer -c 'SELECT example_id, favorite_count, retweet_count, screen_name FROM tweet;' > tweets.csv

統合されたData Sourceの完成系はこういう感じになる。Join keysを元にどう繋ぐかを決定する。統合後に分析対象としたいものはDimensionsMetricsに入れておく。左側がGoogle Analitics、右側がDBから抽出したcsvをData Sourceに指定したもの。

Google Analitics側はどの情報を元にexample_idとすればよいか当然知らないので、指定する必要がある。Google Analiticsのデータソースは、関数を使って自分が必要な列を持たせることができる。今回必要な情報はPage Pathというところにあるので、それを正規表現を使って抽出する。

Google Analiticsで持っている各行のデータには今回の解析の対象外(個別のページではなくカテゴリ別のページ、など)としたいものも存在する。そういったものはFilterを使って抽出する。今回はPathの情報とタイトルの情報を使ってフィルターを書いた。

あとは分析しまくる

ここまでくれば後は簡単。やりたい分析をやるだけ。棒グラフや散布図を書いて自分が欲しい情報を抽出してくればよい。Google Analiticsだけで分からない分析の例として、Page Viewsとretweetされた数の総数の関係を散布図で書いてみた。

各点のページタイトルもしゅっと分かるので、深掘りなどもすぐできる。といっても深掘りするのもめちゃくちゃ簡単で、散布図中の深掘りしたい点をクリックしてやると他のテーブルや図もその条件に勝手に絞ってくれる。

感想

Data Studioを使ってみる前はデータソースをあれこれ指定してダッシュボードで可視化できる君くらいに思っていたが、join相当のことができると知って印象ががらっと変わった。join keysのことを意識しつつ、何らかの形でデータソースに上げることができれば分析可能になるので、エンジニアとしては(ひとまず)データを上げるところまでを考えれば使ってもらえる形にできる。分析者視点としてはデータソースにまで上がっていれば、複数のデータソースを統合してより詳細な分析をExploreでアドホックにやったり、みんなで見るダッシュボードを整備するといったことができる。かなり便利であることが分かったので、もっと使い込んでみようと思います。

参考

Machine Learning Casual Talks #10でMackerelのロール内異常検知について発表しました

メルカリさんのオフィスで開かれたMachine Learning Casual Talks (MLCT) #10に「教師なし学習によるMackerelの異常検知機能について 〜設計/運用/評価の観点から〜」というタイトルで登壇してきました。

MLCTは機械学習をサービスで運用していく知見を共有する勉強会です。YouTube等で動画配信を積極的にしてくださっていて、はてなの京都オフィスでも鑑賞会と称してランチタイムに同僚と発表を見させてもらっていました。普段から勉強させてもあっていた勉強会に、登壇という形でちょっとはお返しできているとうれしいです。登壇させて頂き、ありがとうございました!

私の発表資料はこちらです。スライド46枚ありますが、発表は15分だったので本番はこれの短縮バージョンで発表させてもらいました。

f:id:syou6162:20190530065428j:plain

‪@chezou‬さんのこういうTweetもありましたが、開発者の僕自身結構胸熱(?)で、発表の見所としてはMackerelというプロダクトの概念にフィットするものを、運用が大変にならないようにいかにシンプルに作るか、というところです。具体的にはこういう感じ。

  • どの単位で異常検知モデルを作るか
    • 全体で一つだと大きすぎる、ホストに一つだと細かすぎる
    • Mackerelというサービスの根幹にある「ロール」という概念をうまく利用する
      • ロールは例えば、はてなブログのDBサーバーというようなグルーピングのことです
    • 異常検知のために特別な設定をしてもらうというより、Mackerelの概念に沿って使ってもらえば、異常検知も自然とうまく動作するような設計を目指した
    • プロダクトが先、機械学習が後
  • ホストを異常と判定したときに、その根拠をどうやって提示するか
    • 世の中的には、ブラックボックスのモデルを人間が解釈可能な近似したモデルを用意する(例: LIME)のが主流になりつつある
    • しかし、運用観点では管理しないといけない機械学習モデルが増えて厳しい。予測のlatencyの増加という観点からも望ましくない
    • 異常判定に使っている混合ガウス分布の条件付き確率分布を計算し、どのメトリックが異常かを判定している
      • 混合ガウス分布は性質のよい確率モデルがベースにあるので、条件付き確率も閉じた形で書き表わせる
      • 根拠提示用にもう一つモデルを用意する必要がない
    • 混合ガウスを使っていたとしても、混合数が大きかったり次元数が高いとlatencyなどの観点では厳しいが、ここでも「ロールに対してモデルを作る」ということが効いてくる
      • ロール内では状態数はそこまで多くならないので、混合数はそこまで大きくする必要がない
      • 判定に必要な次元数を抑えるため、ドメインエキスパート(つまり監視のプロである社内のSRE)にどの特徴量は削っても監視には影響なさそうかヒアリングしにいった
  • 誤検知を防ぎたいが、教師なしであるモデルの挙動を人間の直感とどう合わせるか
    • ほぼ動きがないメトリックがほんの少し動いた場合、モデルは異常と判定するが人間はそれを誤報と感じる
    • なるべく人間の直感に合うように振る舞わせたいが、教師ラベルを整備して制御といったことは教師なしではできない...
    • 混合ガウス分布を選択したことがここで生きる
    • 混合ガウス分布は由緒正しき(?)確率的生成モデルであるので、分散の事前分布に「memoryで全体のX%くらい変動するのはまあありえるよね」といった人間のドメイン知識を埋め込む
  • 継続的な改善
    • 自動で取れる数値はダッシュボードにまとめているが、手動でのアノテーションでのシステム評価にそれなりに力を入れている
    • ほぼ全ての異常検知によるアラートに目を通し、正しいアラートか誤報かのアノテーションをしている
      • 誤報率を時系列でトラッキング
    • 誤報の中でカバレッジの大きいものに関しては改善の施策を実施、施策の実施後には再度アノテーションをして実際に改善しているか確認、というのをひたすら繰り返している

発表に対する反応

合わせて読みたい

超交流会に登壇しました

ハカルスの染田さんと同僚のid:daiksyさんと一緒に登壇(パネルディスカッション)してきました。アルゴリズムがどうこうというより、実際にユーザに使ってもらえるプロダクト・サービスにするのはどうすればよいか、それを運用していくために必要な苦労・工夫などざっくばらんに話しました。立ち見(座り見?)が出たり、パネル後も質問してくださる方がいらっしゃるなど、大変盛り上がってよかったです。

アジェンダとしては以下のものを用意していたのですが、時間内ではとても語り切れなかったので、続きはMACHINE LEARNING Meetup KANSAI #5で!!!

  • 技術的話題
    • 機械学習の精度どこまでやる?
    • ノートブックのレビューってどうしてる?
    • 精度 vs 解釈性
  • 運用的話題
    • アプリケーションチームとの境界、役割分担
    • 引き継ぎ/ドキュメント化
    • アノテーションのルールの運用
  • ビジネス的話題
    • リリース日どう決めてる?
    • コストと儲かり

Splatoon2のウデマエがやっとXに到達した

一番好きなルールのガチエリアでウデマエXに到達したので、結構うれしい。

記録ではSplatoon2でS+に到達したのは約一年半前ということなので、ウデマエXに上がるのに大分苦戦したことが分かる。なお、ガチヤグラとガチホコのウデマエは当時と一緒というのが笑うポイントです。

デュアルスイーパーカスタムとか他のブキもあれこれ使ったけど、最終的にSplatoon 1の頃から愛用していたわかばシューターが一番成績がよいというのも面白い。打ち合いは苦手な一方、デスせず塗ることやボムでのキルは得意という自分の性質を生かそうとすると、わかばシューターを選ぶのは納得感はある。

ウデマエXに上がるまでにやったこと

当たり前のことを当たり前にやると、少なくともガチエリアに関してはウデマエXには行けた。

  • うまい人の動画を見る
    • 例: https://www.youtube.com/watch?v=NnL4qaijaQ0
      • この方の解説動画は多分全部かつ何回も見て参考にさせてもらいました
    • エイムがはちゃめちゃうまい人の動画は真似できないので、立ち回りがうまい人の動画を見つけて参考にするのがよかった
  • 味方と相手の編成をしっかり確認する
    • 味方に塗り要因が全然いないからキルより塗りを優先しようとか、編成事故で相手にしか長射程がいないのに何も考えないと一方的にキルされるので、ボムの牽制を意識的に多めにしようといったことを意識する
    • 余裕があれば敵のギアも見ておく。イカニンジャがいるとかステジャンがいると頭に入れておくと、多少は警戒できる
  • スペシャルはとにかく味方と合わせる
    • Splatoon 2では1の頃と比べるとスペシャルが弱くなっているので、一人で打開しに行くのは無謀
    • カウント取り替えすまで多少時間がかかっても、味方の復帰を待ってからスペシャルを吐くようにしよう。打開の成功の確率がぐっと上がる
    • わかばのスペシャルはアーマーなので、自分がリードしているときは無闇にスペシャルを使わず、敵のマルチミサイルやハイパープレッサーに対してカウンター気味に発動すると味方の生存率が高まって敵の打開を阻止しやすい
  • 立ち回りでカバーできないところはギアでカバー
    • 自分の場合、どうしても長射程に対してメインを使って距離を詰めるというのが下手でカバーしきれなかった
    • サブ性能アップを積んでボムの飛距離を伸ばし、長射程がいても対抗できるようになり、目に見えて成績が上がった
  • 常に味方と敵の生存人数を確認する
    • 勝ってても味方が2人落ちたから少し前線を下げようとか、敵が3落ちしたから一気に前線を上げようといったことを考える
  • 暇があったらとにかくマップを見る
    • マップのインクの動きで裏取りにきてるなとか、味方はこっちから打開しようとしているから合わせようとか相手の姿を目視する前にできることはたくさんある
    • サウンドプレイとかは自分はうまくできなかった
  • 味方のカバーをなるべく早くする
    • S+の上位になってくると、とにかくカバーが早い。味方が仕留め損なった敵をボムで追撃するとかそういったことをやって、局所的に数的優位な状況を作り出す
    • 一人で無双はできるものじゃないので、ツーマンセルを組んで動くようなイメージ
  • 壁は左に置く
    • イカちゃんは右利き。相手と対面する場合は壁を左に置いておくと一方的に有利に射撃することができる

AWS Fargate上でMackerelのプラグインを実行 & 監視する

特定のホストの紐付かないメトリックをMackerelのプラグイン経由で監視する場合、AWSであればEC2上に監視専用のホスト(いわゆるmonitoringホスト)を立てることが多いです。これは便利な一方で、以下のような問題点があります。

  • EC2ホストの面倒を見ないといけない
  • 趣味で使う用途だとEC2のホスト台を毎月あまり払わずに済むとうれしい

特に一番最初の面倒を見るコストが一番大きいわけですが、サーバーレス/コンテナ時代なので、いいソリューションがあればそれに乗りたいわけです。先行研究がすでに存在していて、AWS Lambdaで試されている方がおられます。

自分でも似たようなことをAWS Fargate上でやっているのですが、せっかくなのでどんな感じでやっているか簡単に書いておきます。

mkrおよびプラグインが入ったdockerイメージを作る

mkrやMackerelプラグインを入れるDockerfileを書きます。

FROM centos:centos7

RUN yum install -y curl sudo
RUN curl -fsSL https://mackerel.io/file/script/setup-yum-v2.sh | sh
RUN yum install -y mackerel-agent mackerel-agent-plugins mackerel-check-plugins mkr
RUN mackerel-agent init -apikey='XXXXXXXXXXXXXXXX'

# install plugin
RUN mkr plugin install tjinjin/mackerel-plugin-aws-billing
RUN mkr plugin install mackerelio/mackerel-plugin-aws-batch
RUN mkr plugin install mackerelio/mackerel-plugin-aws-step-functions

WORKDIR /app
ADD monitor /app/monitor
CMD /app/monitor

Fargate上で実行するshellスクリプトを用意

プラグインを実行して、その出力をmkrを使ってMackerelにメトリックとして投稿します。サービスメトリックとして投稿しても、ホストメトリックとして投稿してもよいと思います。

#!/bin/bash
ACCESS_KEY_ID="XXXXXXXX"
SECRET_ACCESS_KEY="YYYYYYYYYY"
MACKEREL_HOST_ID="ZZZZZZZZZ"

/opt/mackerel-agent/plugins/bin/mackerel-plugin-aws-billing -access-key-id=${ACCESS_KEY_ID} -secret-access-key=${SECRET_ACCESS_KEY} | mkr throw --host ${MACKEREL_HOST_ID}
/opt/mackerel-agent/plugins/bin/mackerel-plugin-aws-batch -access-key-id=${ACCESS_KEY_ID} -secret-access-key=${SECRET_ACCESS_KEY} -job-queue=go-active-learning -region=us-east-1 | mkr throw --host ${MACKEREL_HOST_ID}

まだ存在しないmonitoringホストを指定したい場合、API経由で新規ホストとして登録するとよいでしょう。

以上をbuildして、ECRにイメージを登録します。

タスクを定期的に実行する

先ほど書いたshellスクリプトを対象にしたタスクを定期的に走らせます。私の場合はCloudwatchイベントで定期的に走らせています(趣味のものなので、例えば1時間に1回とかの頻度で特に困らない。どうせすぐには対応できないことも多いので...)。

そもそも論として

Mackerel公式のAWS/Azureインテグレーションが充実すれば、ユーザーがこういったmonitoringホストを立てる工夫をする必要がなくなるという話があって、それはその通りだと思います(著者はMackerelの開発者の一人です)。インテグレーションも最近どんどん増やしていっていますが、インテグレーションで対応しきれないすごくニッチなメトリックを取りたいというケースは今後もmonitoringホスト相当のものが必要なケースはあると思います。そういったときはAWS Lambdaや今回紹介したようなFargateを使った方法を試してもらえるといいかもしれません。

2018年の砂場活動振り返り

インフラやミドルウェアにとにかく苦手意識があるが、仕事的にいつまでもそう言ってられない。そこで、最悪全部ぶっ壊れても大丈夫な砂場を作り、そこを土台に活動をするというのを2018年の目標に設定していた。

結構な時間をかけたこともあり、それなりの砂場と活動ができて、自分としても勉強になってよかった点が多かったので振り返りを書きます。一個一個ちゃんとエントリ書いていたので、振り返りが楽で助かった。

完成系はML Newsだけど、2018年1月時点では

  • そもそもWebアプリですらなくCLIアプリだった
  • データの管理もデータベースではなくテキストファイル

という素朴な作りだった。

インフラ編

最初はCLIアプリをWebアプリにする活動をやったが、その後はAWS上にインフラ部分の構築を進めた。

次に一台のEC2をAWSコンソールから立てて、sshでログインしてyumコマンドを打って...という10年前くらいのサーバー構築をやった。しかし、EC2のスペックを変えて作り直すのはあまりにもダルかったので、ansibleでミドルウェアのインストールや必要な設定をやらせるように。

一台のEC2にWebアプリもredisもpostgresも同居してという状況だと辛い状況(公開する関係でWebアプリはpublicサブネットに置きたいけど、DBもpublicサブネットに置いてる...)になるのは目に見えていたので、managedサービスが存在するものはそれを使うようにした。AWS上でのリソース構築は会社でCloudFormationを使うことが多かったので、自分も使うことにした。

CloudFormationはまあ書くだけという感じだけど、VPCやサブネット、セキュリティグループをどう設定するとよいかをイチから自分で考えるのは普通に勉強になったので、やってよかったですね。CloudFormationのテンプレートはどういう粒度で分割すればいいのかは未だに分からない。CloudFormationのyamlを編集するのも辛くなってきたのでaws-cdkも試しましたが、こちらはまだまだ開発中という感じだったのでしばらく放置しています。

WebアプリをEC2上に立てていたが、世の中コンテナ化が進んできていてどういうよいところ悪いところがあるのか知りたい自分も知りたい、仕事的にも知っておく必要があるということでコンテナ化を進めた。

最初は自分でEC2を立ててECSのクラスタにjoinさせる、という形を取っていたが「いい加減EC2の管理をしたくないなぁ...」と思ってきたため、途中からFargateを使うように。FargateだとEC2を立てておかなくてもdesired-countを変えればアプリケーションサーバーの個数も変えられるので楽になった一方、EC2より若干お高いとかログ周りが微妙(fluentdとの連携とか)、Fargateの起動は若干もたつくなどが試して分かってきました。この辺から必要なリソースをある程度自由に変更できるようになってきたため、砂場感が出てきました。

コンテナ化したご利益でAWS Batchも使えるようになりました。普段のWebアプリのワークロードと異なる機械学習のモデルの学習などはAWS Batchに投げ込むことができるようになったため、バッチが走っている間レイテンシーが落ちる...といったことを心配しなくてよくなりました。AWS Batchは仕事でも使っていますが、便利ですね。

AWS Batchで複数のジョブを管理していると、そのバッチの順序依存関係をcron職人芸ではなくもっと賢い方法で管理したくなりました。同僚が使っていたStep Functionsが便利そうだったので導入したのもこの頃でした。試した内容はLTで発表も行ないました。

機械学習をやるためにデータ基盤周りのことを考えることが増えてきましたが、その辺も試してみようということでCloudWatch LogsからKinesisを通してS3にログを転送して、Athenaで分析するといったこともやりました。やってみたのはよかったですが、ログの流量がまだまだ少ないのでどういうところで困るかはまだまだ見えてこないという状況ではありました...。

ミドルウェア編

インフラ編である程度下周りは整備したので、パフォーマンス改善の活動を通してミドルウェアのことを知る活動も行ないました。ISUCON勢には当たり前すぎるところから、という感じではあります。当たり前のことを当たり前にできるようになっていきたい。

チューニングをした結果、改善したのかそうでないのかを判断するためのモニタリングをやれるようにしたり、DBで必要なインデックスを張るとか、コネクションプールを適切に設定するとか、そもそもボトルネックきちんと把握できてる??といったことを少しずつやっていきました。AWSの課金額が順調に増えてきてしまったのもこの辺だ😇

チューニングではないけれど、Nginxの設定をあれこれいじったりということもやっていました。

フロントエンド編

砂場活動のメインの目的はインフラ方面の知識および経験の強化ですが、Webアプリを作る過程でフロントエンドでも勉強になりました。Vue.jsを導入してSPAにしたり、Cognitoを使って認証させたりといったことをやっていました。以前は趣味でjavascriptを書く機会が皆無だったので、趣味でもjavascriptを書く機会ができたのはよかったなと思います。当初は特に予定がなかったサーバーサイドレンダリングも導入成功していた。

まとめ

2018年の砂場活動を振り返りました。正直、よい設計に落し込むまでめちゃくちゃ遠周りしているし、AWSへの月々の課金も無視はできない金額になっています(現実的なところに落と込んだら課金も減りつつある)。しかしながら、以下のような大きなメリットがありました。

  • 遠回りするとどのアプローチがどうダメだったかが身を持って分かる
    • 多少痛い目に合わないとどれくらい困るかがイメージが湧かない性格...
  • 多少データがぶっ飛ぼうが困るのは自分だけなので、思い切ってあれこれ試せる
    • 精神的な障壁を低くするという砂場活動の目的を十分果たせていたと思います

インフラ/ミドルウェアの苦手意識はまだまだ払拭できてはいませんが、2018年に作った砂場を足場に2019年には多少の武器にしていけるようにしていきたいなと思います。

飽きっぽい自分としては、年始に立てた目標を年末までちゃんと継続的に取り組めていたことに驚いていますが、エンジニア1on1で適切にフィードバックをくれたid:shiba_yu36さんの力が大きかったなぁと思います。ありがとうございます!

Amazon Web Services 基礎からのネットワーク&サーバー構築 改訂版

Amazon Web Services 基礎からのネットワーク&サーバー構築 改訂版