はじめに
去年からWebサーバをnginxからexpressにアップデートした。途中放置気味であったがようやくまとも動くようになったので、テストを兼ねてこの記事を書くことにする。約8か月振りのブログ更新となる。
静的サイト・ジェネレータ
私が今使っているServers Man VPSは月額467円のエントリープラン、メモリ1GB/HDD50GBのものを使用している。最初はメモリ256MBであったがだんだんとクラウド・ベンダ側で価格据え置きでスペックが勝手にアップされていき、今のこのスペックで固定となった。
スペックアップされたといってもサーバー・リソースは乏しい状況である。サーバーにはコンテンツのサーブにリソースを集中し、それ以外のことは作業用のクライアントPCで行いたい。コンテンツの生成は作業用PCで行い、それをサーバーにデプロイする静的サイト・ジェネレータを2年前に構築した。
当初のしくみ
当時の仕組みとしては以下の図の通りである。
独自拡張のMarkdown形式でコンテンツを書く。それをローカルPCにあるbuild-blog.jsでHTMLにレンダリングすると同時にアーカイブ・インデックスも更新してそれをgithubにpushする。webhookでvpsに通知する。vpsは通知を受け取りコンテンツをpullし、変更のあったコンテンツを.gzファイルに圧縮する。.gzに圧縮するのはレスポンスの際コンテンツをgzip圧縮転送し、転送コストを削減したいからである。さらに事前に圧縮しておくことで、レスポンス時の圧縮処理コストを削減している。
当時はnginxをフロントのwebサーバとしていて、node.jsで動かしているサービスはnginxから来たリクエストを裁くようにしていた。ただ私のサイトはアクセスが月に5,000PVあればいいほうなので、能力を持て余し気味であった。
nginx -> expressへの切り替え
でまあリソースを持て余し気味であれば、もうちょっとちゃんとしたCMS、例えばWordpressを復活してもよかったんだけど、そちらへのモチベーションはまるでない。どちらかというとそのリソースをnode.jsに振り向けたくなってきた。そう思ったとたんnode.jsですべてをやりたくなり、nginxからexpressに移行したのである。JSでWebサーバーのコンフィグレーションができるのはわかりやすくていいかなとも思ったしね。。
で今の環境は以下の図の通りである。
四苦八苦したが、ほぼnginxでやってたことをexpressに置き換えることができるようになった。今回ついでに過去に作ったnode.jsのウェブサービスもすべてルータ・モジュールとしてまとめている。
使用しているモジュール
移行にあたって使用した主要なモジュールは以下の通り。
HTTP/2
spdyモジュールを使用している。ネイティブHTTP/2モジュールは使用していない。まだexpressがサポートしていないので。
ただspdyはnode 11.1以降のバージョンを使うと不具合が出る問題がある。私は結局nodeをLTS版(10.x.x)に戻して対応した。顛末は以下のモーメントで確認してもらいたい。
11.x以降のバージョンでspdyモジュールが対応してない件
このモジュールのテスト結果は以下画面の通り。
gzip圧縮送信
リクエスト・レスポンスにおけるトラフィック・コストを下げるために、UAがAccept-Encoding: gzip
をサポートしていればgzip圧縮してコンテンツを送信している。
最近はzopfliという圧縮アルゴリズムも使用できるようだが。
zopfli で静的コンテンツの gzip 配信と Content/Transfer-Encoding について | blog.jxck.io
nginxはコンテンツの.gzファイルを用意しておけば、gzip圧縮送信時に圧縮済みのコンテンツを送信してくれる機能がある。
Module ngx_http_gzip_static_module
これと同じことを実現するために、以下のモジュールを使用した。
適用した結果は以下の通り。
webhookの'x-hub-signature'の処理
webhookは一応パスフレーズを設定しており、パスフレーズが一致しないwebhookは400を返すようにしている。パスフレーズは'x-hub-signature'ヘッダに入ってくる。これをcryptなどで解読し、パスフレーズが一致するか確認するだけだが、ここも安直に以下のモジュールを使って実装を端折ることにした。
注意事項として、このモジュールはルーティングの最初に入れなくてはならない。これでちょっとハマってしまった。
It needs to be one of the first and before bodyParser().
webhookをexpressルータに移植した際に、ペイロードに入ってくる更新情報が巨大な場合、受けきれずに413エラーが発生する。これに対応するには、bodyパーサのlimit値を増やす必要がある。
node/expressでpushで受け取るデータが大きい場合の対応
その他
.mdファイルから.htmlにレンダリングするツールのテンプレートも少しいじった。これは「PageSpeed Insights」のテスト結果に書かれてあったアドバイスを参考にして修正した。
scriptの非同期ローディング
<script>
タグにasync
属性を追加。
cssファイルのプリロード
<link rel="preload" >
の追加。注意事項として、リソースのプリロードをするだけであって、cssとして読み込むには別に読み込みのタグを描く必要がある。
<!-- preload -->
<link rel="preload" href="style.css" as="style">
<!-- 本体の読み込み -->
<link rel="stylesheet" href="style.css">
preload from:sfpgmr - Twitter検索
現状のパフォーマンス
PageSpeed Insightでパフォーマンスを計測したところ以下の結果であった。
PC版
モバイル版
モバイル版がなぜ遅いのかは原因究明中。。
課題など
webhookのペイロードの更新情報
webhookのペイロードには更新情報(どのコンテンツが追加・更新・削除されたか)が入っているのだが、なぜか更新されているはずのコンテンツの情報が洩れているものがある。この情報をもとにgzip圧縮やファイル削除の処理を行っているため、更新されているのにgzip圧縮作業が行われず、結果古いコンテンツがサーブされてしまう。なぜ更新情報が洩れるのかはわかっていない。代替策も含め検討中である。
configの整理・統合・秘匿化
もともと単一のモジュールであったものを統合してルータ化したため、設定情報の持たせ方などがバラバラになっている。設定ファイルから読み込んでいるものはまだいいが、ディレクトリなどがハードコードされているものもある。これを1つの設定ファイルにまとめ、統一した方法でアクセスするように変更したい。
今のところ、キーファイルなどはプライベートレポジトリ&パーミッションで絞り管理しているが、この中身についても暗号化を施すなどして秘匿性をさら強化したい。
モバイル版のスピードアップ
Pagespeed Insightsのモバイル版のパフォーマンスが悪いので、その改善を図りたい。どうもad系のスクリプトにまつわる部分にボトルネックが多そうである。
技術的なキャッチアップ
取りこみ可能な部分は取り込みたいと考えている。