paranitips

Never stop learning! がモットーのゆるふわエンジニアブログ

Resource Hintsの対応をしてWebPageTestの点数を改善した

f:id:paranishian:20181102183456j:plain Photo by rawpixel on Unsplash

はじめに

いま開発チームでサイト速度改善に取り組んでいて、 その一環でResource Hintsの対応をしたので、調べたこととリリース後の効果をまとめます🙌

調べたこと

まずはResource Hintsについて
Resource Hints

ふむふむ。いろんな種類があるなぁ。🤔

DNS プリフェッチの制御 | MDN

特にモバイル環境においては、 DNS プリフェッチによりページの読み込みにかかる時間が劇的に改善されます。例えば、多数の画像が表示されるページにおいて、画像が要求される前に名前解決が行われている場合では読み込み時間が 5% 以上削減されるでしょう。

ほうほう、すばらしい!😆

DNS Prefetching - The Chromium Projects

注意点として、dns-prefetchは、HTTPSページではデフォルトで機能しない😇 けど、<meta http-equiv="x-dns-prefetch-control" content="on">と記述することでHTTPSページでも使えるようになるとのこと。

他ではどういう対応をしているのか見てみよう。

日経

preconnectを使っている。 2018-10-30 14 32 00

twitter

preconnect, dns-prefetchを併用している 👀 preloadも使ってるなぁ。 image

amazon

dns-prefetchのみ 2018-10-30 14 31 05

この記事によるとpreconnectdns-prefetchを併記したほうが良さそう👇
html - DNS-Prefetch and Preconnect - One, or Both? Fallback? - Stack Overflow

なるほどなるほど。

やったこと

いろいろ調べた結果、

  • 確実に必要なものにはpreconnect, dns-prefetchを併記
  • そうでないものにはdns-prefetchのみ記述
    • 広告系のタグとか

という方針で実装しました。

こんなかんじ。slimファイルです

head
  meta http-equiv='x-dns-prefetch-control' content='on'
  link rel='preconnect dns-prefetch' href='//cdn.sotoasobi.net'
  link rel='preconnect dns-prefetch' href='//www.googletagmanager.com'
  link rel='preconnect dns-prefetch' href='//www.google-analytics.com'
  link rel='dns-prefetch' href='//maps.google.com'
  link rel='dns-prefetch' href='//www.googleadservices.com'
  link rel='dns-prefetch' href='//googleads.g.doubleclick.net'
  ...

リリース後の効果

WebPageTestの点数が改善しました!🎉

start render speed index
1.900s → 1.500s 2.080s → 1.818s

dns lookup (緑の部分)が飛び飛びで先読みしていることがわかります😙

before after
2018-10-30 16 25 33 2018-10-30 16 24 18

この他にも、

  • アセットの軽量化
    • 画像の圧縮
      • lambdaでの自動化も考えている
    • css, jsの不要部分削除
      • 地味にしんどいやつ
  • 画像のlazyload

あたりを地道にやっていって、さらに改善中です😋

f:id:paranishian:20181101163913p:plain

まだまだこれから!チームでがんばるでぃ💪

iOSショートカットでちょっとしたライフハックをしてる話

f:id:paranishian:20181024100856p:plain

iOS12から登場したショートカットをつくるのが楽しすぎて時間が溶ける・・・🤤
自分は最近Google Mapを使うことが多いので、そのときに使えるショートカットをつくりました。

iCloudの共有リンクも置いてるので良ければ使ってみてください🙌

※ショートカットAppはプリインストールではないのでAppStoreからインストールする必要があります
ショートカットの使い方はこの記事が詳しいです👇
iOS 12で「ショートカット」アプリを使ってみよう:iPhone Tips - Engadget 日本版

お店の他の口コミを調べる

Google Mapでお店を見つけて、
Google以外の他のサイトの口コミも見てみたいなー🧐」
そんなときに使います。

Google Mapで、
共有 > ショートカット > 〇〇で調べる
で検索できます。

f:id:paranishian:20181023112237g:plain

ページを直接開くショートカットも作ってみたのですが、速度がなんとも遅かったのでシンプルにGoogle検索するだけにしました💡

バス停の場所を調べる

例えば渋谷駅。
「乗り換え案内でバス経由で表示されてもバス停の場所がわからん。。。🤮」
そんなときに使います。

f:id:paranishian:20181023112219g:plain

バス停の場所を調べる

ウィジェットからワンタップで

ウィジェットに表示するとさらに捗る。
いまんとここの2つを重宝してます😎 f:id:paranishian:20181023191202p:plain

目覚ましをセットする 嫁にLINE電話する
f:id:paranishian:20181023185520j:plain f:id:paranishian:20181023185525p:plain

LINE電話をショートカットに表示するには、まず一度LINEで電話すればショートカットAppに表示されるようになります👌

もっといろいろ種類が出てくるとおもしろいなぁ〜。

おしまい🤗

nginx.confを8万行削除した話

f:id:paranishian:20181010192436p:plain

強い気持ちでやりました😇

自分が関わる前からあったnginx.conf。

それはそれは大きく膨らんでおりました🐷

デプロイはchefで行っていたんですが、dry-runでも時間がかかりすぎて差分確認するのも一苦労、、という状態😢

少しでも快適な開発環境にすべく、やっていき!💪

TL;DR

大したことはやってなくて、アクセスログと照らし合わせて地道に削除していっただけなのであしからず🙇

削除の方針

nginx.confの中身を見ると、ほとんどがもはや機能しているかわからないリダイレクトのコードだったので、今回はそれらを削除していくことにしました。

今は使っていないURLでもどこかのブログやサイトで昔のURLが残っていることがあります 🤔💦

ただ、そのURLをどこまで面倒見るの?って話で、今回はプロダクトオーナーと相談して、

「直近3ヶ月でアクセス数がxxに満たない場合はSEO的にも価値がないので削除する」

という方針にしました。
(特に意味はないけど一応数字を伏せておいた😇)

アクセスログの保存と解析

サイトへのアクセスはすべてALBを経由してnginxに流れているので、ALBのアクセスログを見ていきます。

という流れでアクセスログデータを取得します。

ALBのアクセスログをS3に格納する

アクセスログのS3への格納作業自体はポチポチするだけで完了します👌
Application Load Balancer のアクセスログ - Elastic Load Balancing

(この作業を3ヶ月以上前にやってたので、現時点ですでにログデータがたまった状態です)

Athenaの連携とデータ取得

Athenaはこのへんのドキュメントとサイトを参考にしました👇

では、さっそくテーブルを作成していきます。

あらかじめパーティション(今回はdtにした)を設定して作成します。

CREATE EXTERNAL TABLE IF NOT EXISTS alb_logs (
    type string,
    time string,
    elb string,
    client_ip string,
    client_port int,
    target_ip string,
    target_port int,
    request_processing_time double,
    target_processing_time double,
    response_processing_time double,
    elb_status_code string,
    target_status_code string,
    received_bytes bigint,
    sent_bytes bigint,
    request_verb string,
    request_url string,
    request_proto string,
    user_agent string,
    ssl_cipher string,
    ssl_protocol string,
    target_group_arn string,
    trace_id string,
    domain_name string,
    chosen_cert_arn string,
    matched_rule_priority string,
    request_creation_time string,
    actions_executed string,
    redirect_url string,
    new_field string
    )
    PARTITIONED BY (dt string)
    ROW FORMAT SERDE 'org.apache.hadoop.hive.serde2.RegexSerDe'
    WITH SERDEPROPERTIES (
    'serialization.format' = '1',
    'input.regex' = 
'([^ ]*) ([^ ]*) ([^ ]*) ([^ ]*):([0-9]*) ([^ ]*)[:-]([0-9]*) ([-.0-9]*) ([-.0-9]*) ([-.0-9]*) (|[-0-9]*) (-|[-0-9]*) ([-0-9]*) ([-0-9]*) \"([^ ]*) ([^ ]*) (- |[^ ]*)\" \"([^\"]*)\" ([A-Z0-9-]+) ([A-Za-z0-9.-]*) ([^ ]*) \"([^\"]*)\" \"([^\"]*)\" \"([^\"]*)\" ([-.0-9]*) ([^ ]*) \"([^\"]*)\"($| \"[^ ]*\")(.*)')
    LOCATION 's3://<your-alb-logs-directory>/AWSLogs/<ACCOUNT-ID>/elasticloadbalancing/<region>';

(locationの<>部分はよしなに変更してください。)

Athenaでは各クエリでスキャンされるデータ量に基づいて課金されるので、対象を絞るためにパーティションつくっておきます。
今回は直近3ヶ月のデータだけで十分だったので手動でパーティションを作りました。
(なのでパーティションの自動化も今回はしていません)

ALTER TABLE alb_logs ADD
  PARTITION (dt='2018-07') LOCATION 's3://<your-alb-logs-directory>/AWSLogs/<ACCOUNT-ID>/elasticloadbalancing/<region>/2018/07'
  PARTITION (dt='2018-08') LOCATION 's3://<your-alb-logs-directory>/AWSLogs/<ACCOUNT-ID>/elasticloadbalancing/<region>/2018/08'
  PARTITION (dt='2018-09') LOCATION 's3://<your-alb-logs-directory>/AWSLogs/<ACCOUNT-ID>/elasticloadbalancing/<region>/2018/09'

パーティションの確認

show partitions alb_logs

リクエストの取得

パーティションを指定してデータ取得します。

SELECT count(*) AS request_count,
         request_url
FROM alb_logs
WHERE dt >= '2018-07'
        AND dt <= '2018-09'
GROUP BY  request_url
ORDER BY  request_count DESC

結果ファイルをcsvでダウンロードすると、以下のようなデータが確認できます。

"request_count","request_url"
"12345","https://sample.com:443/path/to/hoge"
"10000","https://sample.com:443/path/to/fuga"
"8000","http://sample.com:80/path/to/piyo"
...

あとは、grepsedなどのコマンドを駆使してリクエスト数を確認し、nginx.confと照らし合わせて削除していくだけです。(つらそう😇)

例えば、ポート番号が邪魔なのでこんなことをしてパスだけ確認したりとか。

egrep ':\d+\/path\/to\/' results.csv | sed -E "s/http.+:(443|80)\//\//g"

今回はじめてawksedを使いましたが、まだまだ使いこなせてない感😪

気の遠くなるような作業かと思いましたが、意外とトップレベルで削除できるものが多かったのでなんとかなりました。
(たとえば /path/to/hogegrepで引っかからなかったら/path/to/hoge/1/path/to/hoge/2など、配下のURLに関するコードも一気に削除できるよね、みたいな。)

地道な削除作業が完了し、QAを経て無事リリースできました🎉

リリース後は特に障害も起きず、chefでのデプロイもスムーズになったので頑張った甲斐がありました。よかった。

幸せに開発するためには自分で環境を変えて行かなあかんのや!という強い気持ち。

以上です🤗