MangaRank(仮) を支える技術

はじめに

先日紹介記事を書いた、はてなブログのマンガ感想記事を集計して作成したランキングサイト『MangaRank(仮)』。

tnakamura.hatenablog.com

「技術的な内容について後日記事を書く」と宣言してからだいぶ時間が空いてしまったけど、 今回の開発ではかなり紆余曲折ありながら試行錯誤したので、その記録を残しておくことにする。

開発方針

Web サイトを開発するにあたって、『無料で運用できること』にとことんこだわった。 無料で運用を続けられるなら、たとえ利用者が自分しかいなくても、「とりあえずこのまま動かしておこう」と思える。

また、ユーザーが触る部分、フロントエンドの速度にもかなりこだわった。 パフォーマンスは重要。遅いのはもはやバグ。

あと、無料で運用しつつ、万が一、億が一アクセスが増えた場合でもスケールするように、一応考えて開発した。

全体の構成

MangaRank(仮)の構成は、バックエンドとフロントエンドに分かれている。

Web スクレイピングでマンガの感想記事をダウンロードしたり、Amazon からマンガの情報をダウンロードして集計するバックエンド。

そして、集計したデータを表示するフロントエンドに分かれている。フロントエンドは静的サイト。今で言うところの JAMStack。

バックエンド

.NET Core のコンソールアプリケーション

Web スクレイピングしたり、Amazon Product Advertising API を呼んだり、集計したりするアプリケーションを、 .NET Core のコンソールアプリケーションで作成した。よって言語は C#。最近のトレンドでは、この手のアプリケーションには Go を使うんだろうけど、自分が一番慣れている言語を選択したことになる。

Web スクレイピングVisual Studioデバッグ実行で確認しながらだと捗った。HTML の解析には AngleSharp を利用。

tnakamura.hatenablog.com

Web スクレイピングAmazon Product Advertising API で取得した情報は SQLite に書き込み、 集計して JSON に出力。 その JSONGoogle Cloud Storage にアップロードしている。 あと、Google Cloud Storage には SQLite データベースのバックアップもアップロードしている。

tnakamura.hatenablog.com

コンソールアプリケーションを動かすサーバー

.NET Core 製コンソールアプリケーションは Google Compute Engine の F1 micro インスタンスで動かしている。 理由は当然、F1 micro インスタンスなら無料で使い続けることができるから。

Web スクレイピングではアクセス先に負荷を与えないように適切にインターバルを入れているので、 今のところ CPU とメモリをそれほど必要としていない。 ただただ時間がかかるだけ。 そのため F1 micro インスタンスでも全然余裕。

ストレージサイズも 30 GB あるので、SQLite データベースの置き場として使っている。 データベースのサイズはまだ 100 MB いってないので、こちらも余裕だ。

ロギング

Web スクレイピング等でエラーが発生した場合に調査できるよう、ログをちゃんと仕込んでいる。 ログの保存先だけど、GCE インスタンスのストレージだと見るためにわざわざ SSH で入らなきゃいけない。 それはちょっと面倒なので、Google StackDriver Logging に保存している。

ASP.NET Core 用の公式パッケージがあったので、.NET Core コンソールアプリケーションに組み込むのは簡単だった。

tnakamura.hatenablog.com

余談

最初はデータの保存先に Firestore を使おうとした。 Web スクレイピングで集めたデータではなく、集計したデータだけど。 フロントエンドは読み取りだけの予定だったし。 ただ、途中でオーバースペックなことに気づいてしまい、Firestore を使うのはやめてしまった。

tnakamura.hatenablog.com

フロントエンド

GatsbyJS で静的サイト生成

フロントエンドの速度にこだわると決めていたので、検討の結果、 静的サイトとして書き出して CDN で配信するのが最速という結論に至った。

静的な Web サイトの作成には GatsbyJS を採用。 GatsbyJS は Blazing fast をうたっていて、爆速な Web サイトを作成できる。 React をテンプレートに使うのは違和感なかったのに対し、 そのテンプレートに渡すデータを GraphQL で取得するのは慣れるまでに時間がかかった。 今となっては、データソースを GraphQL で抽象化して統一的に扱うのは上手いやり方だと思う。 こういった風な GraphQL の使い方は覚えておきたい。

マンガの一覧と詳細の各ページは、Google Cloud Storage からダウンロードした集計データの JSON ファイルから生成している。

その他の工夫としては、一覧ページは表示する画像が多いため、 gatsby-image を使って画像の取得を遅延させている。

Web サイトは Netlify で公開

Firebase Hosting や S3 だと、自前でビルドしたものをデプロイする必要がある。 GatsbyJS のビルドはマシンパワーを結構喰うので、バックエンドを動かしている F1 micro インスタンスでは力不足。 とくにメモリが。

Netlify は Web サイトのホスティングだけでなく、ビルドまでやってくれるので、ケチケチ個人開発の強い味方。 今のところメモリ不足などのエラーでビルドが失敗したことはない。 フォーム機能も提供してくれているので、ロックイン上等で使っている。

余談2

最初、フロントエンドは Nuxt で開発していた。 というのも、開発を始めたばかりの頃 Google App Engine の Standard Environment が Node に対応したというニュースがあったためで、これは Nuxt で実装して Server Side Rendering するしかないと思った。 そして、実のところほとんど出来上がっていた。 しかし、GAE にデプロイしたら SSR で頻繁にコケて使い物にならなかった。 完成間際で、GAE 無料枠のインスタンスでは力不足というのが判明するとは…。

普通はここでインスタンスをスケールアップするところだけど、今回は無料にこだわっていたので断念。 あと、当初 Firestore を使おうとしていたので、併せて Firebase Hosting + Cloud Functions for Firebase で Nuxt の SSR も試みたが、こちらもインスタンスをスケールアップしないと使い物にならなかったので断念。

デザイン

デザインには Bootstrap 4 を使った。 GatsbyJS は React をベースにしているので Material UI とか使えるが、 ほぼ表示だけのサイトに重厚な UI フレームワークは不要と判断。 UI フレームワークを使うのは Web サイトではなく Web アプリを作るときになるだろうな。 Bootstrap は手に馴染んでいて Web サイト制作に丁度いい。 ただ Bootstrap 臭を消すのが大変で、まだまだ漂っているのでなんとかしたい。

アイコンはみんな大好き FontAwesome。 FontAwesome は svg で使えるようになっていたので嬉々として試してみたら、 Lighthouse のスコアが落ちたので、Web フォントで使っている。

まとめ

余談が結構なボリュームになってしまった気がしないでもないけど、最終的に使った技術をまとめると次の通り。

  • .NET Core
  • Google Compute Engine
  • Google Cloud Storage
  • Google Stackdriver
  • GatsbyJS
  • Bootstrap4
  • FontAwesome
  • Netlify

現在の構成に辿り着くまでに、かなり回り道をしてしまった。 Nuxt や Firebase の知見も多少溜まったので、まったくの無駄ではなかったと思う。 Firebase はアプリを開発するときにはお世話になるだろう。

今回初めて Google Cloud Platform を使ってみたが、開発体験が思いのほか良かった。 Stackdriver はログを集めるのに重宝したし、Compute Engine は Web ブラウザ上から SSHVM インスタンスにアクセスできたしと、 わかってるな〜という印象。 .NET Core は Linux で動かせるから、Microsoft Azure にこだわる理由もない。 個人開発では Firebase と Google Cloud Platform の出番が増えそうだ。