積極的後進

後ろ向きに全力ダッシュ

Webを支える技術を読んだ

会社の本棚にあったものを読むことにした。

普段の生活や業務の中で息をするようにHTTPに触れているが、HTTPのことちゃんと理解してないのでは?と思い読み始めた。

RESTやHTTPの成り立ちや基本的なところのおさらい、特にヘッダーまわりについてはちゃんと意識することが無かったので新しい発見があった。一方で、流石に内容の陳腐化も進んでしまっていた。ATOMまわりはぼんやりしか知らなかったのでそれはそれで面白かったが。

以下読書メモ。

REST Webのアーキテクチャスタイル

  • アーキテクチャアーキテクチャスタイルは異なる
  • RESTはネットワークシステムのアーキテクチャスタイル
    • クライアント/サーバーに特に有効
      • まさにWeb
  • リソースの名前 = URI
    • リソース = Web上の情報
  • RESTは、複数のアーキテクチャスタイルを組み合わせて成り立っている
    • クライアント/サーバー
    • ステートレスサーバー
      • クッキーでのセッション管理はこれに反する
    • キャッシュ
    • 統一インターフェース
      • ここでのインターフェースは、GETやPOSTを指している @
    • 階層化システム
    • コードオンデマンド: プログラムをクライアントにダウンロードして実行する

URI

  • URIの構成
    • スキーム: http
    • ホスト名: hogefuga.com
    • パス
  • ベースURI
    • 相対URI時に、ホスト名までを指定しておく
    • リソースのURIをベースとするか、要素で指定することが出来る
  • %エンコーディングには、混乱を避けるためなるべくUTF-8を用いることが望ましい
  • URIとURLの違い
    • URI ≒ URLと思ってよさそう
    • URLには、ドメイン更新やサーバー変更等でアクセスできなくなる可能性がある
    • URIはリソースの所在を表す表記ルールの総称
      • 技術仕様等ではURI表記になることがある
    • URLはリソースの所在を表す

HTTP

  • TCP/IPとは
    • インターネットのプロトコルは階層型プロトコル
    • ネットワークインターフェース層
      • ケーブルとか
    • インターネット層
      • ネットワークで実際にデータをやり取りする部分
      • IPとは、インターネット層のプロトコルのこと
      • IPでは、送り出したデータが相手に届くかまでは保証しない
    • トランスポート層
      • IPが保証しなかったデータの転送、到達を保証する
      • TCPが接続先にコネクションを張り、データの抜け漏れをチェック
      • ポート番号は、TCPで接続したコネクションがどのアプリケーションに使われるかを決定する
    • アプリケーション層
      • メールやDNSHTTPSを実現する層
      • ソケットとは、TCPでプログラムを用いるときに使うライブラリ
    • リクエスト/レスポンス
      • クライアント
        • リクエスト・メッセージの構築、送信
        • レスポンスメッセージの受信、解析
        • その他、クライアントの目的に必要な処理
      • サーバー
        • リクエストの受信、解析
        • 適切なアプリケーションプログラムへの処理の委譲
        • アプリケーションプログラムから処理結果を受け取る
        • レスポンスメッセージの構築、送信
      • メッセージの構成要素
      • HTTPのステートレス性
        • ステートフルな通信(サーバーがクライアントのアプリケーション状態を管理する)のは、台数が増えるほど難しい
          • ステートレス一択
        • ステートレスのデメリット
          • パフィーマンスの低下
            • クライアントは必要な情報を毎回取得する必要がある
            • データ量の増加、認証などの負荷。。。
          • 通信エラーへの対応
            • 意図せずリクエストが繰り返されてしまった場合など

HTTPメソッド

  • POSTでPUT/DELETEを代用する
    • _methodパラメータ
      • formの中にtype=hiddenなinput要素を配置し、idを_methodとする
      • valueに書いたメソッドが使える
      • application/x-www-form-urlencodedの場合にのみ使える
    • X-HTTP-Method-Override
  • 条件付きリクエス
    • HTTPメソッドと更新日時などのヘッダを組み合わせて、メソッドの実行可否をキメられる
    • If-Modified-Sinceヘッダなど
  • 冪等性と安全性
    • 冪等性: ある操作を何回行っても結果が同じこと
    • 安全: 捜査対象のリソースの状態を変化させないこと
      • 変化を与える: 副作用

ステータスコード

  • 仕様で定められたステータスコードを正しく使いたい
  • 分類
    • 1xx: 処理中
    • 2xx: 成功
    • 3xx: リダイレクト
    • 4xx: クライアントエラー
    • 5xx: サーバーエラー

HTTPヘッダ

  • メタデータ
  • 認証やキャッシュなどHTTPの機能はヘッダで実現する
  • ヘッダの機能
    • 日時
      • Date
      • Expires
    • MIMEメディアタイプ
    • 言語タグ
      • Content-Langugage: ja-JP
    • コンテントネゴシエーション
      • Accept
        • クライアントが処理できるメディアタイプをサーバーに伝える
      • Accept-Charset
      • Accept-Language
    • Contente-Length
    • 認証
      • Authorization
    • キャッシュ
      • Pragma
        • キャッシュ禁止
      • Expires
      • Cache-Control
        • 詳細なキャッシュ方法の指定
        • 前述の指定はここでもできる
        • max-ageとか
      • 条件付きGET
        • If-Modified-Since

microformats

  • classやrel属性でメタデータを表す
    • さらに、rel="home" などの rel 属性を活用したメタデータの定義も推奨されていますが、これは HTML5 においては誤った構文となります。 rel 属性は、それを使える要素とその値が定められています。
  • wordpressでは採用されてる
  • microformats は競合する他のメタデータのフォーマットと比較して、古くから存在し、Wordpress などでも採用されていることから、 検索エンジンも採用したのだと考えられます。(他に Yahoo が一部取り入れているようです。)ただし、Googleschema.org の提唱するメタデータを採用するように推奨しています。 schema.org は、Google, Yahoo, Bing(Microsoft) などが共同で管理する団体です。 また schema.org が提唱するメタデータの記述形式、microdata や RDFa フォーマットは、W3C で制定されるフォーマットです。検索エンジンを提供する各社が microformats のサポートを直ぐに止めることは考えられませんが、 今後は schema.orgメタデータmicrodata フォーマットでマークアップ(記述)することがスタンダードになっていくと思われます。
  • SEOに寄与するかも微妙、標準化競争に負けたように見える

Atom

Web開発者のための大規模サービス技術入門を読んだ

会社の本棚にあったものを読むことにした。

業務ではWebフロントに注力していて、触るとしてもnginxやCDNまわりなのでサービス負荷について考える基準が欲しかったことがモチベーション。

少し古い本なので多少内容が陳腐化している箇所もあったものの、今でも使える基本的な知識や考え方について解説されている。(やむを得ないが)書籍中に出てくるコードはほぼPerlなので読んでなんとなく内容を察する程度で精一杯だった。

OSキャッシュやスケーリングの考え方はあまり詳しく知らなかったところなので良かった。業務外でサーバーを立てる時もスペックは割と適当だったりするので、この辺りの考え方を体得できると業務にも役立てることが出来そう。OSキャッシュの辺りは特に、言われてみれば当たり前だが今までさほど気にしたこともなかった。普段はブラウザ全体のメモリ消費量をなんとなく気にする程度なのだが、アプリケーションのパフォーマンスの観点でもメモリ消費量を気にする癖をつけたい。モニタリングが手軽に出来れば良いのだろうか。

DBまわりも通り一遍というかクエリの書き方を知っている程度なので、サービスに落とし込む際にどういったことを考えるかを知れてよかった。マスタースレーブ構成のクラスタにおいて、マスター間の遅延について考えたこともなかったので新鮮だった。また、これも初歩的な話だがストレージエンジンについて今までほぼシカトしてきていたので知ることができてよかった。

また、アルゴリズムにも興味があった。学生時代に講義で触った記憶はあるものの、ほぼ忘れ去っているからだ。せめてクイックソートくらいは諳んじて書けるようにしたい。結果、書籍中では具体的なアルゴリズムに踏み込んではいなかったが、きっかけにはなった。実践するには競技プログラミングあたりに踏み込むことになるのだろうか。

領域外の知識を吸収する足がかりに出来そうなので、定期的に読み返しておきたい。

以下読書メモ。

オリエンテーション

  • スケールアウト時の課題
    • ロードバランシング
    • データの同期
    • ネットワークレイテンシ
  • 大規模データ量への対処
    • ディスク、メモリ、キャッシュメモリ、CPUの順で速度差がある
    • データが小さいうちは処理をメモリで行えるが、大規模になるとそうはいかない
    • いかにデータを小さく持つか、複数サーバーに分散させるか、必要なデータを最小限の回数で読み取るか

大規模データ処理入門

  • 大規模データとは何か
  • 大規模すぎてメモリで処理できない
    • メモリはディスクの105 ~ 106倍早い
  • SSDは高速だが、バスの性能差故にメモリほどではない
  • 負荷の種類
    • CPU負荷
    • I/O負荷
  • ボトルネックを調べるには
  • CPU負荷のスケーリングはサーバー台数を増やす
  • DBなどI/Oの場合は、データ同期の観点から分散が難しい

OSのキャッシュと分散

  • ページ: OSが物理メモリを確保/管理する単位、仮想メモリの最小単位
  • プロセスは仮想メモリにしかアクセスできず、ディスクに直接アクセス出来ない
  • そのプロセスがアクセス済みになったデータもメモリに残しておく、これがページキャッシュ
    • 一度ディスクにアクセスすると、その後のアクセスが早くなる
  • LRU: Least Recently Used、一番古いものを破棄して新しいものを残す
  • Linuxはメモリがあいていればディスクをキャッシュし続ける
  • メモリを増やす = I/O負荷軽減
  • キャッシュがI/O対策の基本
    • 小さければメモリに載せきれる
  • データをキャッシュしきれない規模になったら、複数サーバーでのスケールを考える
    • CPUリソース(APIサーバー)が必要な場合は単純に増やせる
    • DBの場合は局所性が絡んでくる
  • メモリの増設ポイント
    • キャッシュ容量と、アプリケーションが扱う有効なデータ量を比較する
  • 局所性対策
    • アクセスパターンを考慮して分散させて、キャッシュできない領域をなくす
    • パーティショニングで実現することが多い
  • 負荷分散の学習のためには、OSの動作原理を知ると良い

DBのスケールアウト戦略

  • インデックスを正しく運用する
  • カラムの型が何で、どれくらいのバイト数で、全体通すとどれくらいのサイズになるのか計算する
  • B+木
    • O(n) to O(log n)
  • MySQLでは、where, order by , group byに指定されたカラムに対してインデックスが使われる
  • 明示的に作用したインデックス、プライマリーキー、UNIQUE制約がインデックスとして作用する
  • MySQLにおいては、複数カラムがインデックス利用の対象になった場合には、複合インデックスを使う必要がある
    • 単一のインデックスの場合は、どちらかのインデックスのみが使われる
  • explainコマンドで、インデックスが効いているかどうか確認できる
  • レプリケーション
    • マスタースレーブ
      • マスターが更新系、スレーブが参照系
      • 参照系はスケールするが、更新系はスケールさせない
      • どうしても更新系をスケールさせる際は、テーブル分割でテーブルサイズを小さくする
        • もしくはKVSを使う、RDBMSではなくする
    • ポーリング
  • MySQLでは、異なるサーバーにあるテーブルをJOINできない
    • クエリを分割して対応する方が良い
    • テーブル同士が密結合であれば、同じサーバーにおいてしまう方が良い
  • パーティショニングのデメリット
    • 運用が複雑になる
    • 故障率が上がる
    • 冗長化を考えるとマスター1スレーブ3の4台1セットにすると良い
      • 故障時のデータコピーのことを考える
    • パーティショニングはあくまで切り札として扱う方が良い

大規模データ処理[実践]入門

  • 全文検索や類似文書系探索、データマイニングRDBMSだと限界が来る
    • バッチ処理でデータを抽出し、別途インデックスサーバーを作る
    • WebアプリケーションからはAPIでインデックスサーバーにアクセスする
    • 用途特化型の参照用サーバー

アルゴリズムの実用化

大規模データ処理を支えるサーバー/インフラ入門

スケーラビリティの確保に必要な考え方

  • 多くのサービスはサーバー一台で動く
    • 4core CPU 8GB: 100万PV/月
    • 4core CPU 32GB: 200万PV/月
  • 負荷を測るための項目
    • ロードアベレージ
      • それらのプロセスがいつでも動ける状態なのにCPUが未割り当てで、待ち状態にあるプロセスの平均値
    • メモリの割当状態
  • 用途ごとのチューニング
    • レスポンスタイム、処理数など何に注力するのか
  • 用途が増え、細分化すると管理や異常検知が大変
    • だからkubeが人気になったんだよなあ

冗長性の確保、システムの安定化

  • 単一故障点の除去
  • APサーバーは、台数を並べるのが基本
    • ロードバランサのフェイルオーバー、フェイルオーバー
      • ロードバランサによるAPサーバーのヘルスチェック
      • 落ちたら外して、復帰したら戻す
  • サーバーが止まる理由は様々
    • 人為的な理由
    • 物理的な故障
    • メモリ異常
  • DBサーバー
    • マスタースレーブ方式で、一台二台落ちても大丈夫にする
    • マスターの冗長化にはマルチマスタ方式を用いる
      • 双方が双方のスレーブである
      • 片方への更新が、もう片方にも伝搬する
      • ミリ秒単位の遅延は避けられない(割り切るか、どうか
  • マルチマスタ
    • active/standby方式
    • 通常はactiveにのみ書き込み
    • activeに異常が生じたらstandbyがactiveに昇格する
  • ストレージサーバー
    • MogileFS: 分散ストレージサーバー
  • システムの安定化
    • トレードオフが基本
      • リソースをギリギリまで使うチューニングは、突発的なリクエスト増加に耐えられない
    • システムの不安定要因
      • アプリケーション/サービスレベル → 負荷増大
        • 機能追加
        • メモリリーク
        • 地雷
          • 特定のURLで応答が返ってこないなど
          • 機能によるバグと思ってよさそう
        • ユーザーのアクセスパターン
          • 人気のあるリクエストにリンクが貼られるなど
        • データ量の追加
        • 外部連携の追加
      • ハードウェア → 能力低下
        • メモリ・ストレージ障害
        • NIC障害
    • 実際の安定化対策
      • 適切なバッファの維持
        • 限界の7割運用
      • 不安定要因の除去
        • SQL負荷対策
          • どうしても重くなるSQLは、DBサーバーごと隔離して実行
        • メモリリークをへらす
        • 異常動作時の自律制御

Webサービスとネットワーク

  • サービスの成長とネットワークの分岐点

いまどきのWebサービス構築に求められる実践技術

  • ジョブキューシステム
  • ストレージの選択
    • 適切なストレージを選択することは難しい
    • アプリケーションからストレージを参照する際のアクセスパターン
      • 平均サイズ
      • 最大サイズ
      • 新規追加頻度
      • 更新頻度
      • 削除頻度
      • 参照頻度
    • ストレージの種類
      • RDBMS
        • 2つの機能ブロックから成る
          • SQLを解釈し実行するブロック
          • 実際にデータを保管するブロック
            • ストレージエンジン
        • MyISAM v.s. InnoDB
      • 分散key-valueストア
        • memcached
          • メモリ上で動作
          • キャッシュとして使う
          • redisと比べて、パフォーマンスに大きく差があるわけではない
          • redisに出来て、memcachedに出来ないこと
  • キャッシュシステム
    • フォワードプロキシ
      • クライアントが外部のサーバー・ネットワークに接続する際に挟むプロキシ
    • リバースプロキシ
      • 外部のクライアントが内部のサーバー・ネットワークに接続する際に挟むプロキシ
    • これらのプロキシでは、リクエストをキャッシュしておくことが出来る
    • キャッシュサーバーのメリット
      • 平常時に、APサーバーへのリクエストを軽減することが出来る
        • サーバーの台数削減に繋がる
      • アクセス増加時には、負荷増大によるシステム全体のダウンを予防することが出来る
        • アクセスが集中しているコンテンツをキャッシュして返す
    • 分散、冗長化も効果的
    • 容量が大きいファイルなどをキャッシュする場合は、キャッシュサーバーを二重化すると良い
      • リクエストを受け付けるサーバーと、キャッシュ本体を受けもつサーバーのクラスタ
      • 特定のファイルキャッシュはこのサーバー、などすると、キャッシュサーバーの台数が増えても効率的なキャッシュが出来るようになる
    • サーバー起動時にはキャッシュが全然ないから注意
      • activeにする前に、ウォームアップをしてキャッシュを溜めておこう
    • Squid
      • リバースプロキシキャッシュサーバー
      • 結構歴史がある
      • 代替にはnginx, varnishなど。。。
    • Varnish
      • リバースプロキシキャッシュに特化
      • Squidより性能が高い
      • VCLの柔軟さが特にポイント
      • 再起動でキャッシュが全て失われる
        • 数台以上のクラスタで運用する方が良さそう
  • 計算クラスタ
    • Hadoop
    • Redshift
    • 最近のトレンドは。。。?
      • BiqQuery?

アプリケーションアーキテクチャ設計パターン読書メモ

一連の読書シリーズはこれで最後。アプリケーションアーキテクチャ設計パターンを読んだ。

本書はアプリケーション全体、システム全体の設計について具体例を添えて書かれている。Javaを中心に様々なパターンについて触れられており、全部に触れていくとキリが無い。気になったワードや解釈を中心に触れていく。

気になる

  • アプリケーションの構造と処理方式を決めるのがアプリケーション設計の目的。
  • アプリケーションサーバーの中身の内訳についてはあまり意識したことが無かった。
    • プレゼンテーション層
    • ビジネス層
    • インテグレーション層
  • セッション管理や認証系についても詳細な解説があるのは、サーバー側の実装や知識がない僕にとっては嬉しい
  • バッチ処理やデータアクセス層の解説はもう何度か読み直したい
  • クライアントの実装、SPA等についても触れられている

まとめ

内容が多岐にわたることもあって簡単なワードやらを羅列する形でまとめているが、サーバー側の知識を中心に増やすことができた。これまでWebクライアント一本で進んできたので、繰り返し読み返して身につけたい。もう少し個人でもサーバーサイドのコードを書こうかなー。

CleanArchitecture読書メモ

僕がプログラムを一番最初に書いたのは17歳の時だったが、これまでに自分が書いたソースコードを全て見直しても、おおよそ設計と呼べるものが無かった。

設計思想、設計とは何かを学ぶためにCleanArchitectureを読んだのでメモを書く。

例の図

clean_architecture

いつも見る例のアレ。既にネットにある種々の解説を読んでみてもピンとこなかったが、本を読むとそれなりにしっくりきた気がする。自分なりにまとめてみる。

内容物

  • Entities: 企業全体の最重要ビジネスルールを司る。ビジネスルールとは、例えばローンの計算など、コンピューターで行うかどうかを問わないビジネス上の重要なルールのことを指す。このビジネスルールには、例えば利子のレートや支払いスケジュールなどのデータが紐づく。このデータを最重要ビジネスデータとよぶ。この図で表現されているEntitiesは、この最重要ビジネスデータそのものと、それらにアクセス、操作する関数群で成り立っている。このEntitiesは、システムの他の要素に依存せず独立しているようにしておく必要がある。
  • UseCases: 自動化されたシステムを使用する方法を記述したものであり、アプリケーション固有のビジネスルールを記述したオブジェクトである。例えばユーザーの入力やそれに対するインタラクション、入力後の処理などが相当する。ユースケースでは、エンティティに含まれる最重要ビジネスルールをいつ、どのように呼び出して使用するかを規定したルールが含まれている。ここで、ユースケースにはUIに関する記述は無いことに注意する。インターフェースを問わず、インターフェースが使用する処理が書かれている。ユースケースはアプリケーション固有である一方、エンティティはたとえアプリケーションが違ってもそのまま使用できる。そのため、ユースケースはエンティティに依存し、下位に位置する。
  • Interface Adapters: この層は、エンティティやユースケースと外部を繋ぐ部分となる。図に示されているように、ControllerやGatewayがこの部分に相当する。DBやWebへの接続はこの層から行う。Interface層やUseCases層を中心に、それぞれの層をまたいだデータのやり取りが発生することになる。その場合は、常に内側の層に対して便利な構造にしておくと良い。
  • Frameworks and Drivers: この層が、アプリケーションにとって最も外側の層となる。DBやUIが相当する。肝要なのは、これらは円の内側に依存するが、円の内側であるEntitiesやUseCasesはこれらに依存しないことだ。

依存方向のコントロールコンポーネント粒度

この本では、依存方向のコントロールと適切なコンポーネント粒度の重要性について語り口を変えて幾度も説かれている。アプリケーションやソースコードに依存が発生するのは避けられないが、依存の方向はコントロールすることが出来る。それが出来れば、依存先に重大な変更が加わった場合でも、依存している側への影響は最小限で済む。そのために、例えば必ずインターフェースを定義、用意しておくことでコントロールが容易になるなどの例が繰り返し紹介されている。

また、依存方向をコントロールするためには、適切なコンポーネント粒度である必要がある。コンポーネントが密結合すぎると、アーキテクチャの変更等アプリケーションにとって大きな変更が加わった場合に対応できなくなる。コンポーネントの分割粒度や可変/普遍など詳しくは書籍の中で触れられているが、それぞれのコンポーネントがどういう位置づけで、他のコンポーネントとどのように関わるのかは常に意識したい。

個人的には、こういったことを意識することでテスタブルなコードを書けることも嬉しい。

まとめ

本書を読むまで、上記の図すら理解出来なかったがまずはそこをクリア出来てよかった。クリアするためにはその前提である依存方向のコントロールコンポーネント粒度を理解しておかねばならず、その理解が無かった僕にとっては多くの収穫があった。本の中でも触れられているが、あくまで円の図は一例であるので、実際のアーキテクチャを考えるにあたっては完成はなく常に見直し続けることが大事らしい。そういった考えに至れたり、少しずつ自分の書くコードがまともになっている気がするのはほんの少し進歩したと思う。思いたい。

超速本読書メモ

僕がWebフロントエンドの世界に飛び込んでから数年経った。しかし、恥ずかしながらこれまでアプリケーションのパフォーマンスについてあまり深く考えたことがなかった。僕がWebに飛び込む前から、そして飛び込んだ後もWebページの高速化は日に日にその重要性を増してきており、最低限の知識や考え方を持っていないと成果を出すどころか同僚との議論にすら全くついていけなくなる有様だ。

パフォーマンスの世界への入り口として 超速本 を読了したので自分なりにまとめてみることにした。

基本戦略

とにかく計測から入る

推測するな、計測せよ は様々な領域で言われていることだが、改めてこの言葉を噛みしめることになった。

考え方としては、

  • ボトルネックを探して対応することが成果への近道
  • 手段から入るのは間違い

の2つが特に重要そう。

高速化のためのポイント

高速化と言っても、ボトルネックの正体やそれに合わせた対応を考えると様々である。

しかし、主に軽量化(取り扱うデータ量を削減する)や最適化(実行の順番やデータ経路などのチューニング)を意識することになる。

具体的には、下記のようなことを考えることになる。

  • ネットワーク処理
    • データの転送量をなるべく小さくすること
    • データの転送回数をなるべく少なくすること
    • データの転送距離をなるべく短くすること
  • レンダリング処理
    • UIの応答速度
      • 100ms以内を目指す
    • FPS
      • テレビ: 30fps, アニメ・映画: 24fps
      • Webでは60fpsを目標とする
  • スクリプト処理
    • 重いスクリプト
      • Bottom-Up タブ
      • setTimeoutを使って擬似的に非同期化できる
    • メモリリークの調査
      • GCの発生を抑制したい
    • 未解放のイベントリスナーとタイマーの調査と改善
      • performanceパネルで確認できる
      • 特にSPAでは気にしたい

計測手段

計測に用いるツールについて

ここまでで、Webのパフォーマンスアップに対する考え方や着目点を知ることが出来たが、重要なのは計測することである。計測手段はいくつかあるので、それぞれ見ていく。

Chrome DevTool

おそらく最も多くのWebフロントエンジニアに馴染み深いツール。DevToolの各タブで出来ることを見ていく。

Networkタブ

個人的には、DevToolの中でも使用頻度が高いタブ。ページ内の関連リソースの通信状況を確認できる。

Chrome/Devtool/Network tab

Yahoo!JAPAN でnetworkタブを開いてみるとこんな感じ。

ページ上部

ページ上部にはページ読み込み完了までのリソース読み込み状態が表示されている。

ここで、青と赤の赤線に注目したい。

  • 青い縦線がDOMContentLoadedが完了したタイミング
    • HTMLのパースが完了してDOMが構築されるタイミング
      • HTMLが巨大であったり、扱うDOMノードが大量。DOM構築をブロックするスクリプトが存在する。
      • コンテンツ量を減らしたりしてHTMLを軽量化する。同期的に読み込まれるJSファイルや、ベタがきされているスクリプトを排除する。
  • 赤い縦線がLoadが完了したタイミング
    • 関連するリソースの取得と解析が終了するタイミング
      • JSや画像などサブリソースが大量に存在する(またはレスポンスが遅い)。実行されるスクリプト処理やレンダリング処理が遅い。
      • 読み込むリソースの量・サイズ・経路などを見直す。強引な実装や非効率な処理を見直す。

上記のリソース読み込み状態の下には、Waterfallが表示されている。ここでは、リソースがダウンロードされる順番や、各リソースをダウンロードするのにかかった時間がわかる。それぞれのリソースの詳細も見られる。

Perfomanceタブ

Webサイトのパフォーマンスを計測できる。これまであまり使ったことがなかった。

Chrome/Devtool/Performance tab

AbemaTVのもの

  • FPS

    この値が小さいほど、より速く画面が更新され動くことを示す。

  • CPU

    このグラフが示すのはCPUの負荷の大きさで、色によってそれぞれ意味が異なる。

    • 青/Loading: HTTPリクエスト、パース
    • 黄/Scripting: JavaScriptでの処理
    • 紫/Rendering: スタイル評価、レイアウト算出
    • 緑/Painting: ペイント処理、画像のラスタライズ
    • 灰色/Other: その他
  • NET

    ネットワーク通信状態を示す。優先度の髙い/低いリソースへのアクセスを表す。

PageSpeed Insights

Googleが提供しているツール。URLを入力することで、そのWebページのPC/SPでの表示速度を計測できる。

PageSpeed Insights

久しぶりに自分のWebページに対してチェックをかけてみたら一見良さそうな値が出てきた。一見早く見えるが、単純にコンテンツが無いだけ。

複雑なWebページであればあるほどスコアは落ちてくるが、改善項目をサジェストしてくれるのでその項目を精査、改善するだけでも結構違う。

WebPagetest

合成モニタリングを行うサービス。実際に特定のリージョンからアクセスを実行し、ChromeDeveloperToolsのNetworkタブに相当するデータを取得できる。

こちらに詳しい。

Webpagetest

自分のWebページに対して実行してみた結果。有料の合成モニタリングサービスとしては、 SpeedCurve が有名。

表示速度の指標

上記のツールを用いてパフォーマンスの改善を図る場合に、どのタイミング/段階の数値を改善するのかを追いかける方がより良い改善ができる。表示速度においては、ナビゲーションからのパフォーマンスを示す複数の指標がある。

具体的な指標の数々

  • First Paint
    • ナビゲーション後に、ページ内の何かが表示され始めたタイミングのこと
    • クリティカルレンダリングパスの改善が有効
  • First Contentful Paint
    • ナビゲーション後に、ページ内のコンテンツが表示され始めたタイミングのこと
    • これら2つの指標は、サブリソースのロード状況やクリティカルレンダリングパスの状態などを示す
      • パフォーマンスの指標となりうる
      • ユーザー体験に直接影響する数値ではない
      • Paint Timingで取得可能
  • First Meaningful Paint
    • ユーザーにとって意味のある表示になったタイミング
    • あいまいな指標
      • 標準化が難しい
      • User Timingを利用して計測もしくは計測ツールでスクリーンキャプチャを撮って画像比較で判定
  • Time to Interactive
    • ロードが完了し、ユーザー操作に確実に応答できるようになったとき
    • RAILモデルのIdleを満たす状態
    • SPAのようにJSの初期化やAPIとのやり取りに時間がかかるアプリケーションなどで有効な指標
    • こちらも、標準化には至っていない
  • Speed Index
    • ATF(Above the fold)
      • スクロールせずに閲覧可能な画面領域
    • ATFでの表示性能を数値化
      • ファーストビューにおける描画量のスコア

こういった指標が挙げられ、それぞれに効果的な改善を模索、実行していくことになる。ここで意識しておきたいのは、 最も重要視するべき指標はプロダクトによって変わってくる ことである。プロダクトでの各指標がどうなっているかを継続的に計測すること、改善するために何が出来るかをチームで議論して実践することが何より大事っぽい。

その他、取り組みたいキーワード

  • Resource Hints
  • 通信内容の軽量化
    • brotli
      • googleが開発した圧縮アルゴリズム
      • gzipに比べて高圧縮率を誇り、かつ圧縮処理時間も短い
      • IE以外のモダンブラウザでは対応している
  • キャッシュ戦略
    • ServiceWorkerを用いたキャッシュ戦略
    • 優先度の髙いリソースをキャッシュしたりする
  • 画像の圧縮
  • フォントのサブセット化
    • Unicodeのコードポイントで分割
    • ブラウザが必要なフォントファイルのみロードすることを補助する

まとめ

いろいろ考えることがあって混乱してしまうのが正直なところだが、まずは計測する仕組みする仕組みを整えること、それぞれの指標/フェーズにおいて何が出来るのかを一つずつ明らかにしていくのが良さそう。

計測による可視化がなければ闇雲かつ場当たり的な対策しか出来ず、パフォーマンスを改善したとは言えない。特にWebは多種多様な環境でのアクセスが当然のように起こる世界なので、チーム/プロダクトで協力して様々な環境を用意し、定期的なモニタリングと評価、改善が肝要なのだと知ることが出来た。

参考

セッション認証付きAPIサーバーをnode.js(express)で作る

f:id:heimusu:20180518115114j:plain プライベートプロジェクトのサーバー実装を一新するべく,セッション認証とパスワードハッシュ化を付けたのでメモ.

本当はユーザー情報をDBに持たないと意味が無いけど,ユーザーは僕しか居ないからまあいいやと妥協した.

実際のコード

const express = require('express')
const oauth = require('oauth')
const http = require('http')
const path = require('path')
const crypto = require('crypto') // 暗号化
const bodyParser = require('body-parser')
const session = require('express-session')

const app = express();
app.use(express.static(__dirname));
app.set('port', process.env.PORT || 3000);
[f:id:heimusu:20180518115114j:plain]

// Cross Originを有効化
app.use((req, res, next) => {
  res.header('Access-Control-Allow-Origin', '*');
  res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept');
  res.header('Access-Control-Allow-Methods', 'GET, PUT, POST, DELETE, OPTIONS');
  next();
});

// Optionsも
app.options('*', (req, res) => {
  res.sendStatus(200);
});


// urlencodeとjsonの初期化
app.use(bodyParser.urlencoded({
  extended: true
}));
app.use(bodyParser.json());


// セッションの生存時間(分で指定)
const maxage = 1;

// セッション管理設定
app.use(session({ secret: 'keyboard cat', cookie: { maxAge: maxage * 60000 }}))



// セッション管理関数
const sessionCheck = (req, res, next) => {
  if (req.session.email) {
    next();
  } else {
    res.status(440).json({message: 'セッション切れです'})
  }
}


const email = 'email@email.com'
const password = 'hogehoge'

// sha-512で暗号化
const hashed = password => {
  let hash = crypto.createHmac('sha512', password)
  hash.update(password)
  const value = hash.digest('hex')
  return value;
}

// ログイン処理
app.post('/login', (req, res, next) => {
  const reqEmail = req.body.email
  const reqPass = req.body.password
  try {
    if(email === reqEmail && hashed(password) === hashed(reqPass)) {
      req.session.email = {name: req.body.email};
      res.send(200)
    }
    else {
      res.status(401).json({message: 'メールアドレス/パスワードが一致しません'})
    }
  }
  catch (error){
    res.status(500).json({message: 'error'})
  }
});

app.get('/', (req, res, next) => {
  sessionCheck(req, res, next)
  res.sendStatus(200)
})

app.listen(3000, function(){
  console.log('working');
});

リポジトリ

github.com

React.jsのsetStateが遅い問題

f:id:heimusu:20180510105852j:plain

散々言われ続けてきたことだが,自分の備忘録として残しておく.

this.stateを参照してDOMを書き換える

失敗する例

例えば下記のような場合

  constructor(props) {
    super(props);
    this.state = {
      isOpen: false
    }
  }

  componentDidMount() {
    this.setState({
      isOpen: true
    })
  }

  click(flg) {
    alert(flg)
  }


  render() {
    return({
      <div onClick={()=>this.click(this.state.isOpen)}>
    })
  }

DOMをクリックした場合に,1回目のクリックではfalse,2回目以降でtrueが表示される. 本当は1回目のクリックでtrueが表示されてほしいが…

成功する例

正しい対処法なのかどうかはさておいて,上記のコードを次のように変更すると良い.

  constructor(props) {
    super(props);
    this.state = {
      isOpen: false
    }
  }

  componentDidMount() {
    this.setState({
      isOpen: true
    })
  }

  click(flg) {
    alert(flg)
  }


  render() {
    const isOpen = this.state.isOpen

    return({
      <div onClick={()=>this.click(isOpen)}>
    })
  }

render()内でthis.state.isOpenを変数に外出しにして,その変数をDOMに渡す. こうすることで一回目のクリックでもtrueが表示されるはずだ.

私見

業務でReact.jsを扱う中でハマったので試行錯誤した結果この形になったが,原因や良い対処法についてはっきりしていない…

stateの変更タイミングが意図的に制御されているのだと思うが, そもそもreduxを使っていれば心配いらないのかもしれない.

原因やよりよい方法についてご存知であれば是非コメントにて.