メディアシステム開発部の野崎です。 メディアシステム開発部では、「 auWebポータル 」や「 auスマートパス 」といった、サービスを担当しています。 弊社では一部のサービスでアクセスログなどをTreasure Dataに貯めています。 今後はこのデータを分析活用し、より良いサービスを提供していきたいと考えています。 その一歩として、今回はTreasure Data内で使える機械学習ライブラリhivemallを利用してユーザレコメンドを試してみました。 はじめに 今回は、DACさんのブログを参考にさせていただきました。 「HivemallでMinhash!〜似てる記事を探し出そう。〜」 ※ Treasure Dataのブログにも英訳されている、非常にわかりやすい記事です! この参考記事をなぞる形ですが、サンプルデータではなく、実サービスのデータを入力とします。 あるサービスのユーザごとのジャンル別の閲覧数を入力とし、 あるユーザに閲覧傾向が似ているユーザをレコメンドしてみます。 Minhashとは minhashは、乱択アルゴリズムと呼ばれます。 大量のユーザデータや文書データを比較したい場合に利用され、 類似しているユーザや文書を推定することができます。 ある2つの集合(ユーザや文章)の類似度は、 Jaccard係数で表すことができます。 Jaccard係数は、積集合を和集合で割った値で表されます。 参考記事にあるように、 2つの集合の要素にあるhash関数を適用しその結果のうち最小値が一致する確率と Jaccard係数は等しくなる性質があります。 この際に使うhash関数をk個用意すれば、 最小値が一致した個数をkで割ればJaccard係数を推定できる。 つまり、2つの集合の類似度を推定できるということになります。 利用する環境 今回利用した環境は以下の通りです。 hive : 0.13.0 参考 hivemall : 0.4.2-rc.4 利用する関数の確認 minhashを使います。 関数の定義は、 hivemallのユーザガイド を参考にしながら hiveの DESCRIBE FUNCTION を使って確認するのがわかりやすいです。 (minhash) 第一引数に、 item 、第二引数に、 features を取ります。 また、オプションとしてnを取ります。 今回は、itemをuser_idとします。 返り値として、一つのuser_idに対して、n個のhash値が出力されます。 次に、featuresを用意します。 入力データの用意 ユーザガイドのinput ページを参考にします。 featuresは、特徴量ベクトルで、indexのみ、またはindexとweightをセットで表します。 hivemallでは、これらをダブルクオーテーションで括った形式で表します。 上記のfeature関数を使います。 とあるサービスの、 user_idに対し、ジャンルごとの閲覧数を合計した テーブル userid_by_genre があるので、それを元にします。 user_id genre_id_1 genre_id_2 genre_id_3 1 0 2 10 2 1 4 12 3 0 8 1 4 10 4 0 5 9 7 12 ※記載してあるデータはサンプルです。 このテーブルに対して、 以下のようなクエリでfeature関数を適応します。 (type:hive) SELECT user_id AS user_id, ARRAY(FEATURE(1, genre_id_1), FEATURE(2, genre_id_2), FEATURE(3, genre_id_3)) AS feature FROM userid_by_genre 以下のような、 user_idとfeatureのデータができます。 これを、別テーブル user_feature に書き出しておきます。 hashの作成 このテーブルに対して、minhash関数を適応していきます。 n=10とし、一つのuser_idに対して、10個のhash値を生成します。 (type:hive) SELECT minhash( user_id, feature, "-n 10" ) AS( clusterId, rowid ) FROM user_feature 以下のような、hash値が生成されます。 この結果を、別テーブル user_hash に書き出しておきます。 類似ユーザの集計 テーブル user_hash の 異なるuser_idからhash値がある個数一致するuser_idを集計します。 今回は、9個以上のものを対象とします。hash関数は10種類あるのでJaccard係数が0.9以上のものとなります。 SELECT J.Rowid, Collect_set(rid) AS Related_userid FROM ( SELECT L.Rowid, R.Rowid AS rid, COUNT(*) AS cnt -- minhashが一致する数 FROM user_hash l LEFT OUTER JOIN user_hash r ON ( L.Clusterid = R.Clusterid ) -- minhashの値が一致するレコードのみをjoin WHERE L.Rowid != R.Rowid --同じuser_idは除外 GROUP BY l.rowid, r.rowid HAVING cnt > 8 -- Jaccard係数が0.8以下のものは除外 ) J GROUP BY J.Rowid ORDER BY J.Rowid ASC ここでは、実データはお見せできませんが、 以下のような形式の結果が得られます。 user_id:1に対して、user_id:2,4,5のユーザの閲覧が似ていることになります。 user_id Related_userid 1 [2,4,5] 3 [7,8] 4 [10,11] この評価に関しても、本記事では詳細を扱いませんが ほぼ同様の閲覧傾向にあるユーザをレコメンドすることができました。 まとめ 参考記事をなぞる形になりましたが、実際のサービスのデータを用いて Treasure Dataのhivamallを用いて、ユーザのレコメンドを試してみました。 どのようなデータを特徴量とするかどうやって特徴量を作るかあたりが戸惑いましたが、 Treasure Data上に生データやhivemallの実行環境が用意されていることで、手軽に試すことができました。 メディアシステム開発部では、Treasure Dataなどを利用してサービスに貢献できるエンジニアを募集しています。 ご興味ある方は、 募集職種一覧 などをご確認ください。
こんにちは。デザイナーの大村です。 Adobe XD CC や InVision Studio が気になりつつも Sketch をバージョン管理できる、設計チームで一緒に作業するためのプラットフォーム Abstract (0.63.5) を使ってみたところとても便利だなと思ったので紹介します。 Abstract ってどんなもの? デザインデータのバージョン管理ができる 最新データがどれなのかバージョン管理で透明化できる 複数人での並行作業を簡単に一本化できる ファイルを開くことなくプロジェクトの変更点を確認できる https://www.goabstract.com/ 月額費用:Business $16.67 Starter $10 メリット バージョン管理された過去データでもローカルにも落とせる 過去に遡れるのでパーツがなぜ生まれたかコメントを流れで追うことができ、理解しやすい Slack 連携で誰が現在何やってるか〝見える化〟できる Abstract で管理している Sketch ファイルは Contributor(課金利用している人) なら誰でも編集して Commit することもできる コメントのやりとりでデザインレビューできる(Viewer で招待すれば誰でも無料で参加可能) Business であれば 無制限 にバージョン管理ができる(Starter は250GBまで) デメリット Sketch 以外のファイルは管理できない コミニュケーションロスが減ってストレスも軽減 「作って」→「どっかあげて」→「見てねって何かでお知らせして」→「お返事もらって」→「読んで理解して」→「修正して」→「修正したよってお知らせして」→「見てもらう」 みたいなフローが 「Branch 切って」→「作って」→「見てねってお知らせして」→「お返事もらって」→「お返事見ながら修正して」→「Commit =お知らせして」→「見てもらう」 となります。 この「どっかあげて」と言うのが重要で、弊社では Share Point (チームによっては社内の共有サーバや InVision や Prott ) に格納してその場所をお知らせしてレビューのやりとりを行うのですが、人為的な作業なのであげ忘れや上げ漏れがあって「上がってないよ!」「反映されてないよ!」というタイムロスが無くなるのは大きく違うと思います。 またバージョン管理しているので「このデザインってどこの関連だっけ~?」とデザインデータのファイル管理も減り、少し楽になりました。(配置する画像は Photoshop と Illustrator なのでそのデータは別で管理します。) 一番嬉しいことはひとりで悩む時間が減ったこと。 「こんなデザイン思いついたけど、解決方法にマッチしてないかも。でもこう考えるとマッチしてるのかも・・・(悩)とっておこうか捨てようか。」と悩んだ時に、考えた事をそのまま Commit して Comment でメンバーに聞けば、「解決にならないならいらないよ」とか「こういう考え方するとそのデザインで合ってると思う」とか、客観的な意見がその時点で聞けるのでデザインを作りながらひとりで悩む時間が減りました。 コメントはこんな感じで書けます。 @でメンションする(される)と右上のベルとアプリアイコンにバッジがついてお知らせされます。 Slack 連携して「見える化」 Slack をプロジェクトチームで使ってる場合、連携して Commit したことが「見える化」できるので流れが見えて安心です。 Commit の粒度を一定にして自分以外の人も分かりやすくできるとレビューが捗りそうです。 Slack への連携方法 ホームの Organization Setting の Integrations から可能です。 ※Organization Setting は Web からの設定になります。 お知らせしたい Active Project を選択 Organization Setting の Integrations から通知したい Channel を選択 デザイン作業とバージョン管理の親和性 デザインを進める工程で、選択が間違っていたことに気がついても、保管されている Sketch データを開いて過去のデザインデータを拾うことができるので、「後で使うかもしれないから・・」といらないデータを取っておく必要もなく、取捨選択しながらとにかく進めることができます。 別ページや別ファイルでゴミ(いつか使うかもしれない)データを残しておかなくても良いので、いつでも整理されていて見やすく、第三者からレビューもしやすいです。 また、確定しているデザインは常に Master にあるので改修中のデザインと分けて見ることができ安心して他者に展開できます。 Master にあるデータは一覧で確認できる Sketch ファイルを開くことなくプレビューで確認できるので、パソコンの負荷も少なく時短が期待できます。フットワークが軽くなるのも一つの魅力です。 Commit 単位で変更したもの変更してないものが一目でわかる こんな風に見れるので、 Commit の単位は「なぜその変更をしたのか」変更の目的単位にする必要があります。 複数人での並行作業を簡単に一本化できる 自分以外の人が編集した差分データを自分の Branch に取り込んでマスターに Merge することができます。複数人で案を出して部分でいいとこ取りも可能です。また、パーツの過不足や誤字脱字のようなちょとした修正ならフォローし合ってデザインデータの間違えを正していけそうです。 さいごに INVITE で招待メールを送れば、誰でもビューアーになってコメントが書けるので、Design レビューのツールとしても便利です。デザインはユーザーとの接点なので企画側、開発側、ビジネス側全ての観点からレビューを頂いて、届けたい人に届くデザインを作りたいと思っています。
初めまして。フロントエンドエンジニアの謝花(ジャハナ)です。 入社して8ヶ月ほど経ちましてこうして初めてブログを書きます。 沖縄を離れて長い年月が経ちますが、東京では「ジャハナ」が人名だと認識されず未だに苦しんでおります。(電話ごしだと100%聞き取ってもらえません) そんなこんなで楽しくお仕事させていただいているのですが、最近とあるプロジェクトでHTTP/2を取り入れ、モバイルサイトのページ表示速度の高速化に取り組みましたので、そのお話をフロントエンドの視点からお話します。 表示速度が遅い! 改善したい! と思っていても、実際どこから手をつけていいかいまいち分からないと言う方もいらっしゃるかと思います。 少しでもそんな方の参考になればと思っております。 Webページの表示速度の改善はなぜ必要? もちろん表示速度は速いに越したことはないと誰しもお思いでしょうが、 具体的に表示が遅いとどういったことが起こるのでしょうか? ユーザの約50%が2秒以内のページ表示を期待し、読み込み速度が3秒以上かかると40%のユーザが離脱する 引用元 : https://blog.kissmetrics.com/loading-time/?wide=1 操作開始時間が3秒のサイトは1秒のサイトに比べ、CVRは38%低下、直帰率は50%上昇する 引用元 : http://web-tan.forum.impressrd.jp/e/2014/07/08/17757 んー、どんなにいいサービスやコンテンツが揃っていても表示が遅いとユーザーは離れていってしまうということですね。 ページの表示が遅いというのはまさに百害あって一利なしということが分かります。 表示速度が遅くていい人なんていないですよね。イライラしますもんね。 HTTP1.1の問題点 HTTP1.1では、1つのリクエストが完了するまで、次のリクエストを送ることができなくなっています。 リソースが2つあった場合は、1つ目の読み込みが完了してから2つ目のリソースのリクエストを開始いたします。 引越しで例えるならば、引越し先まで荷物を1つずつしか運べないのと同じ状況になります。 100個の荷物があれば100往復しなければなりません。相当時間がかかりそうですね。 このようにHTTP1.1では、1ホストごとに1つのリクエストしかできないので、リソースが多ければ多いほど往復する数が増え、ページの表示に時間がかかってしまいます。この方法は明らかに非効率です。 これを回避するために、ほとんどのモダンブラウザは1ドメインに対し複数同時接続を行うことで、ある程度通信の多重化を図っています。 次の図は、弊社のWebサイト(HTTP1.1)をChromeブラウザのデバッグツールで確認したものになります。 Chromeが同時に送信するリクエストは最大6つまでである(7つ目以降はブロックする)ことが分かります。 HTTP/2の多重化処理とは HTTP1.1の1つのリクエストが完了するまで、次のリクエストを送ることができないという問題点をHTTP/2ではリクエストを並列に送信することでクリアにしています。 上の図のように、HTTP/2では1つのコネクション上で複数並列に扱うことが可能になりました。 この問題が解決されると次の図のように、ページが読み込み完了(onLoad)されるまでの時間が短縮されます。 さらに、次の図は、弊社が運営している、あるWebサイトのHTTP/2でのリソースのリクエストの様子です。 HTTP1.1と比較していただくとより効率よく、1度にいくつものリソースがリクエストされてるかがお分かりいただけるかと思います。 レンダリングパス最適化について HTTP/2の特徴がなんとなく分かったかと思いますので、ここからは表示速度の向上のため、より効率的なHTML + CSSの設計について考えていきます。 HTTP1.1時代のCSS設計 HTTP/1.1の場合は一度に通信できる量が限られているので、以下のサンプルのようにリクエスト数を減らすためなるべくまとめてリクエストをするのが主流でした。 <html> <head> ... <!-- リクエスト数削減のため一つにまとめたcssファイル --> <link href=“all.css”> </head> <body> ... </body> </html> 他にもCSSスプライトなどリクエスト数を極力減らすような設計をしたことあるのではないでしょうか? しかし、HTTP/2の場合だと先述したように並列でのリクエストが可能になったため、このような設計は効果的ではありません。 リクエストが並列で行われるということは 無理にリソースを1つにまとめる必要がなくなりました。 むしろ無理に1つにまとめ、ファイルサイズを大きくしてしまうとレスポンスが返ってくる時間が長くなり逆に表示速度が遅くなってしまいます。 HTTP/2時代のCSS設計 以上を踏まえて、実際にHTTP/2を使用したページのCSSについてです。 <html> <head> ... <!-- ヘッダのcssファイル --> <link href=“header.css”> <!-- メインコンテンツのcssファイル --> <link href=“main.css”> <!-- フッタのcssファイル --> <link href=“footer.css”> </head> <body> ... </body> </html> 上記のように1つにまとめるのではなく、なるべく細かく分けることがポイントです。 先述してるように、リソースを無理にまとめなくてよくなったというのももちろんですが、 コンポーネント指向という観点や、キャッシュを持たせるという意味でもCSSの細分化は必要と言えます。 結果 上記を踏まえて実際にページの表示速度はどう変わるのかChromeのデバッグツールで検証してみました。 ①HTTP1.1 ②HTTP/2 着目すべきは描画が開始されるまでの時間です。 ①の 1.89s に対し、②は 901ms となっております。 時間が短くなるにつれ、ユーザの待機時間が減りますので表示の体感速度は上昇してると言えるでしょう。 今回はそこまでリソースが多くないページでの検証になりましたが、リソースが比較的多い大規模なページであれば、より差がつくはずです。 ※この検証は差を出すため、意図的に遅めのネットワーク環境で試しております。 しかし、なんでもかんでもHTTP/2にすれば表示速度が速くなるかと言われるとそうではありません。 HTTP/2はリクエスト数が多ければ多いほど効果を発揮しますので、 そもそもリクエスト数やリソースが少ないサイトではそれほど効果は見られない でしょう。 まとめ HTTP1.1は1ホストごとに1つのリクエストしかできない Chromeなどのモダンブラウザでは同時に送信するリクエストは6つまで HTTP/2は複数並列にリクエストすることが可能 HTTP/2ではリクエストが並列でなされるため、なるべくリソースを細分化してファイルサイズを小さくすると効果的 リクエスト数やリソースの少ないサイトでは効果は薄い また、本日は触れておりませんが、HTTP/2ではリクエストのプライオリティも指定できます。 ですので、優先度の高いリソースから先にリクエストすることが可能になりました。 HTTP/2のプライオリティ制御について 加えて、ページの表示速度を向上させるにはCSSをいかに早く返してもらえるかが鍵となります。 ですので外部ファイルを読み込むのではなく、HTML内に直接CSSをインライン化し、レンダリングまでの時間をなくしてしまうのも効果的です。 インライン化する際は、しっかり設計をしていないとメンテナンスが大変になりそうですが、そういうWebサイトも今後増えてくるのではないでしょうか。 おまけ Chrome Canaryの chrome://flags のランタイムフラグの中に Experimental Web Platform features というのがあり、 そのフラグをONにすればCSS in body を試すことができます。 CSS in bodyにした時のメリットは以下。 無駄なCSSの読み込みを待たずともレンダリングが可能になるのでとても効率的 全てのCSSを読み込んでから一気にレンダリングということがなくなり、準備ができたものから少しずつ表示されるので表示の体感速度は向上する <html> <head> ... </head> <body> <!-- ヘッダのcssファイル --> <link href=“header.css”> <header> ... </header> <!-- メインコンテンツのcssファイル --> <link href=“main.css”> <main> ... </main> <!-- フッタのcssファイル --> <link href=“footer.css”> <footer> ... </footer> </body> </html> 上記の設計であれば、それぞれ必要なCSSが読み込まれた直後にレンダリングが開始されます。 不要なCSSの読み込みを無駄に待ったりしませんので、表示速度が飛躍的に向上します。 そのCSS in Bodyを以下のHTMLで実際に検証してみました。 ・HTML <html> <head> ... </head> <body> <link href=“reset.min.css”> <link href=“definition.min.css”> <!-- ヘッダのcssファイル --> <link href=“header.min.css”> <header> ... </header> <!-- メインコンテンツのcssファイル --> <link href=“top_main.min.css”> <main> <img src=“101.png”> <img src=“211.png”> <img src=“600.png”> <img src=“100.png”> <img src=“500.png”> <img src=“200.png"> </main> <!-- weeklyのcssファイル --> <link href=“weekly.min.css”> <div> <img src=“202.png”> <img src=“101.png”> <img src=“201.png”> </div> </body> </html> ・リクエストの様子 top_main.min.css を読み込んだ後に、 <main> の中の画像を読み込んでいますね。 さらに、 <main> のレンダリングが終われば次にある weekly.min.css を読み込んでいますので、必要なCSSを読み込んだ後にレンダリングがされているのが分かります。 ・描画の様子 まとめてレンダリングされることはなく、 <header> から優先してレンダリングされているのが確認できますので、レンダリングパスの最適化がされているといえるでしょう。 本日は以上となります。ありがとうございました。 参考 : https://jakearchibald.com/2016/link-in-body/
こんにちは。制作部の苅部です。 今回は、サービス横断でのWebパフォーマンス改善を1年間続けた中で指標としてSpeed Indexを採用した振り返りを書き残しておこうと思います。 Speed Indexとは 時間ごとの描画面積で算出される値で、体感速度の指標として参考にすることができます。 UX向上としてのWebパフォーマンス改善を考える時に、他の指標よりも役に立ちます。 DOMContentLoadedやwindow.onload、First Paintといったいくつもの指標はあくまで説明変数で、Speed Indexが目的変数になると考えています。 体感速度における目的変数が明確になることで、実施すべき施策(クリティカルレンダリングパスなど)にフォーカスすることができるようになります。 ※Speed Indexの詳しい算出方法については以下ページが参考になります。 Speed Index - WebPagetest Documentation Speed Index – how it works and what it means - NCC Group 改善の進め方 1. 数値を定量化する Speed Indexを統計的な定量データにした上で、時系列の軸で他のデータと比較できるようにします。 これによって「数値」を「ファクト」として扱えるようになります。 数値の妥当性を計るためにもヒストグラムも合わせて確認できるようにすると良いと思います。 2. 比較のためのベンチマークをとる 速度とは相対的な値ですので、比較のために同じ条件でベンチマークを複数取り相対評価をできるようにします。 これで速い・遅いの判断ができるようになります。 3. 改善して効果を測る ベンチマークと比較した結果遅ければ、ファーストビューにフォーカスして描画をブロックする要因がないかを調べ、そのボトルネックに対して改善を行います。 その後、定量データ(統計量)を前後比較して施策の評価を行います。 この繰り返しでPDCAサイクルを回していきます。 Speed Indexの理想値 計測条件や計測対象のUIあるいは計測時のネットワーク影響によって差が出てしまうため厳密な絶対値は出せませんが、いくつか参考になる値はあります。 ・HTTP Archive Big QueryのPublicなデータセットにHTTP Archiveのデータが保存されているため、数十万件の計測データから任意の統計量を取得することができます。 例えば以下のようなクエリを叩くことで、Speed Indexの中央値が取得できます。 クエリ SELECT NTH(501, quantiles(SpeedIndex,1001)) pages_mobile_speedindex_median, FROM [httparchive:runs.2017_09_01_pages_mobile] SELECT NTH(501, quantiles(SpeedIndex,1001)) pages_speed_index_median, FROM [httparchive:runs.2017_09_01_pages] 実行結果 2017年9月1日の計測(46万件)におけるSpeed Indexの中央値はモバイルサイトで8,025、デスクトップサイトで3,800となりました。 モバイルサイトの方がSpeed Indexが高くなる傾向にあるので、目標とする値はViewportで分けて考える必要がありそうです。 ※ HTTP Archiveは世界中のサイトを計測しているため、ネットワーク(RTT)の影響を受けて数値が高い状態になっていると思います。 参考URL: HTTP Archive + BigQuery = Web Performance Answers - igvita.com ・Google Adsense, Double Click by Google AdsenseのブログやDouble Clickの資料ではSpeed Indexへの言及があり、目指すべき数値として3,000以下と記載されています。 Webpagetest provides a Speed Index that indicates the average time at which visible parts of the page are displayed. Aim for a Speed Index of 3,000 or less and load time of 3 seconds or less — ideally 1-3 seconds.2 5 steps to improve Page Speed and boost page performance ・モバイルサイト認定資格 Google Partnersのモバイルサイト認定資格には Speed Indexのスコアの目標値に関する問題が出てきます。 試験対策ガイドには 私たちの目標は、スコアが 3,000を下回ることです。 2.1.3 目標値の設定 - Google Partners ヘルプ と記載されていて解答欄にも 5,000未満という選択項目が用意されていたので、最低でも5,000未満、理想は3,000未満という解釈ができそうです。 ・書籍:パフォーマンス向上のためのデザイン設計 オライリー・ジャパンから出版されている" パフォーマンス向上のためのデザイン設計 (Designing for Performance)“では、Speed Indexの数値について言及があります。 Table 5-1. Example responsive web design budget Measure Goal Speed Index 1,000 Designing for Performance "Example"となっているので、数値に根拠はないと思いたいです・・。 ・NCC Groupのベンチマーク セキュリティ企業のNCC Groupの記事によれば、英国の小売店(上位50位)のベンチマークは中央値が3,106(帯域:8Mbps)だった とのことです。 in a recent test of top 50 UK retailer home pages (tested in Performance Analyser with Internet Explorer 11 at 8Mbps), the best Speed Index score was 819. The average was 3,658 (median 3,106), while the poorest had a score of 8,582 Speed Index – how it works and what it means ・Lighthouse Chrome Developer ToolsのAuditsにてパフォーマンス診断をするとLighthouseを使った分析が可能です。 実行結果としてSpeed Indexの値が表示されますが、目標の数値としては [< 1,250] となっています。 Chromeのソースコードには以下のようなコメントアウトの記述もあり、中央値を5,500と想定している模様です。 // 10th Percentile = 2,240 // 25th Percentile = 3,430 // Median = 5,500 // 75th Percentile = 8,820 // 95th Percentile = 17,400 参考URL: https://github.com/GoogleChrome/lighthouse/blob/v2.4.0/lighthouse-core/audits/speed-index-metric.js#L62 ・mediba運営のサービス 私たちが計測している複数のモバイルサイトのベンチマークは以下のようになりました。(2016年12月〜2017年8月) 計測概要 項目 内容 ツール WebpageTest 地点 EC2 north-west1 Viewport iPhone5c 帯域/RTT Mobile3GFast(1.6Mbps/768Kbps 150msRTT) 数値の算出方法 1日12回計測した上で月単位での中央値を取得 計測結果 計測対象 毎月の中央値の平均 A 3,452 B 4,276 C 4,808 D 5,330 E 5,756 F 6,098 ※D・Eは同期的なA/Bテストツールの読み込み(HTMLパースのブロッキング)があり、FはファーストビューにカルーセルUIが存在しているため、それを反映して数値が高くなっています。 感覚的にもレンダリングをブロックする要素がなければ(ボトルネックがなければ)、5,000以下の数値が出せる という印象です。 そしてLighthouseの5,500という中央値も肌感覚としては妥当に感じます。 そのため、medibaでは5,000を閾値としてパフォーマンス改善に取り組んでいます。 6,000を超えているような状況では「体感速度が遅く、改善の余地がある」といったざっくり判断をしています。 Speed Indexを取り入れるポイント 1. ざっくり捉える Speed Indexは描画の面積で算出するため、ファーストビューに自動送りのカルーセルが存在していたりバナー広告があったりする場合は不利になる事があります。 逆に画像の少ないテキストベースのデザインであれば有利になります。 ただ体感速度という意味ではある意味妥当ですし、他のベンチマークと比較はできないとしても同一計測対象での比較としては有用だと思います。 そのため、厳密さを求めずある程度の「ざっくり感」をもって取り入れると良いと思います。 2. 他の指標を使って改善する Speed Indexはあくまで結果としての数値(目的変数)なので、何かアクションを起こすためには、TTFBやfirstPaint、DOMContentLoadedなどの他の指標(説明変数)が必要になります。 またonLoadは古い指標と言われたりする事もありますが、onLoadが遅い場合はブラウザのインジケータの表示時間が増えるため、体感速度へのマイナス影響もあります。 そのため投機的な読み込みや不要なサードパーティ絡みのリクエストの削除など、onLoadの最適化も必要だと思います。 Speed Indexは他の指標を代替するわけではなく、相互的に補完していくイメージです。 3. ビジュアライズする Speed Indexの改善幅を数値で共有しても、なかなか相手に意図が伝わりづらいと思います。 そのため、数値(中央値)が近いもの同士の比較動画をWebPagetest上で作成し、数字と合わせて展開すると視覚的にもわかりやすく、コミュニケーションもスムーズになると思います。 速度改善のベネフィット パフォーマンス改善にかかるコストに対して、どういったベネフィットが期待できるかの説明に悩むことは常にあると思います。 「速い方がいい」のは間違いないですが、それだけでは協力を得る事が難しいです。 多くの人が納得できる説明が必要になるのですが、その一つとして「パフォーマンス改善は帯域の品質が低いほど効果が大きい」ということが挙げられると思っています。 最近のデータ契約は容量に上限(と超えた場合の通信制限)があることがほとんどです。 MVNOの市場シェアも伸びてきていますし、事業者によっては節約モード(下り制限)のスイッチを用意しているケースもあります。 つまり国内の通信環境にも多様性があり、ナローバンドも珍しくないので「通信速度が速いから大丈夫」というわけでもなさそうです。 というわけで、ナローバンドを考慮したときに、UXとしてのエンゲージメント貢献や事業指標への期待ができると思っています。 ※ rtt attribute がモバイルのChromeにも実装されたら、RUMとして収集するのも面白そうです。 おわりに Speed Indexを使ってボトルネックにフォーカスすることで、パフォーマンス改善をシンプルに考えることができると思います。 例えば、ボトルネックには以下のようなものが挙げられます。 CSSファイルの肥大化 同期的なScript読み込みによるHTMLパースのブロック 圧縮が有効になっていない 巨大なCSS Sprite画像の読み込み HTTP/1.1での同時接続数制限 キャッシュの設計の問題 どれも難しいことではなく解決のためのコストも低いですが、サードパーティのリソースも含めて、このいずれかが課題として見つかる事が多いです。 そしてどれもネットワーク(RTT)影響を大きく受けるため、RTT的に不利なモバイルネットワークではわかりやすく体感速度を上げることができました。 Speed Indexは精度が高くないかもしれませんが、[体感速度が遅いかどうか]を把握し次のアクションに繋げる事ができるため、より良いモバイルUXを提供できる指標だと思っています。
こんにちは、AWS Lambda と戯れる日々を過ごしているインフラストラクチャー部の沼沢です。 みなさん、AWS Lambda 使っていますか? Lambda では CloudWatch Logs に /aws/lambda/関数名 というロググループ名でログを出力することができますね。 Lambda@Edge でも同じようにログを出力しようと思ったのですが、ロググループが見つからず困ったので同じように困ってる人の助けになれば幸いです。 結論 以下の公式ドキュメントを見るとちゃんと書いてあります。 Lambda 関数の CloudWatch メトリクスと CloudWatch Logs - Amazon CloudFront 以下引用です。 Lambda は、関数が実行される場所に最も近い CloudWatch Logs リージョンで CloudWatch Logs ログストリームを作成します。各ログストリームの名前の形式は、/aws/lambda/us-east-1.function-name です。 つまり、日本で CloudFront にアクセスして Lambda@Edge を動作させた場合は東京リージョン近辺のエッジロケーションにアクセスされるので、東京リージョンの CloudWatch Logs に出力されます。 Lambda@Edge では関数自体はバージニアリージョンに作成する必要があるため、てっきりログもバージニアリージョンの CloudWatch Logs に出力されるもとの思い込んでいました。 そのため、最初は Lambda@Edge ではログが出せないのかと思ってしまいましたが、無事に発見することができました。 困ってからドキュメントを見るという癖が付いているため、少しハマったというお話でした。 公式ドキュメントはちゃんと見ましょう。(自戒)
こんにちは、インフラストラクチャー部の沼沢です。 以前、 Lambda@Edge を使ってデバイス判定をする記事を書きましたが、最近 Lambda@Edge が正式リリースされたので、正式版での検証も実施してみます。 以前書いた記事はこちら Lambda@Edge でデバイス判定をする | mediba Creator × Engineer Blog 概要 今回も、前回と同じように以下の判定をできるようにします。 iPhone iPad Android 上記以外(Other) CloudFront の設定も前回と同じく、 Viewer Request に Lambda@Edge を定義します。 やってみる オリジンサーバー相当の EC2(nginx) を用意 前回と同様の手順なので割愛します。 nginx のアクセスログをカスタマイズ こちらも前回と同様の手順なので割愛します。 ただし、今回は “X-Custom-Device” というヘッダー名にしているためそこだけ変更しましょう。 Lambda ファンクションを用意 Lambda@Edge のファンクション作成についてはこちらの公式ドキュメントが参考になります。 AWS Lambda@Edge - AWS Lambda Preview の時は東京リージョンで作っても動作しましたが、正式版の Lambda@Edge は、 バージニアリージョンで作成する必要があるため、必ずバージニアリージョン(us-east-1)で作成しましょう。 Lambda 関数の作成 設計図の選択: ブランク関数 トリガーの設定: (何も変更せず) 次へ 関数の設定 名前: device_judge_test 説明: device judge ランタイム: Node.js 6.10 (←ここはPreview時は Edge Node.js 4.3 でした) Lambda 関数のコード コード エントリ タイプ: コードをインラインで編集 以下のコードを入力 'use strict'; exports.handler = (event, context, callback) => { const customHeaderName = 'X-Custom-Device'; const uaHeaderName = 'User-Agent'; const request = event.Records[0].cf.request; const headers = request.headers; if (headers[uaHeaderName.toLowerCase()]) { const device = { "key": customHeaderName, "value": headers[uaHeaderName.toLowerCase()][0]['value'].match(/(Android|iPhone|iPad)/)? RegExp.$1: 'Other' }; headers[customHeaderName.toLowerCase()] = [ device ]; } callback(null, request); }; Lambda 関数ハンドラおよびロール ハンドラ: index.handler ロール: テンプレートから新しいロールを作成 ロール名: lambda_edge_execute_role ポリシーテンプレート: 基本的な エッジ Lambda のアクセス権限 関数のテストは、サンプルイベントテンプレートの “CloudFront AB Test” を選択して、User−Agent の値だけ書き換えて実施すると良い 作成完了後、以下の手順で Lambda 関数の新しいバージョンを発行 CloudFront Web Distribution を用意 以下の通り設定していきます。 作成後、Status が Deployed になるまで待ち、CloudFront の Domain Name (xxxx.cloudfront.net) にアクセスした際に nginx のデフォルトページが表示されれば準備は OK です。 動作検証 今回の検証で期待する動作は、「X-Custom-Device が同じリクエストは、30 秒間(Age が 30 になるまで)はオリジン側の nginx にアクセスは来ず、CloudFront がキャッシュを返すこと」です。 これを確認するため、以下のコマンドを流して確認します。(MacOS 向け) ua_list=("{判定させたいUA文字列1}" "{判定させたいUA文字列2}" "{判定させたいUA文字列3}" "{判定させたいUA文字列4}" ...) i=0 v=0 while : do curl -i -s -H "User-Agent:${ua_list[$v]} ${i}" http://**************.cloudfront.net/ | egrep "^(HTTP|X-Cache|Age)" echo "" sleep 0.85 i=$(( i + 1 )) if [ $v == `expr ${#ua_list[*]} - 1` ]; then v=0 else v=$(( v + 1 )) fi done このコマンドに各 User-Agent を設定してアクセスし、nginx のアクセスログを確認します。 インクリメントした数字を最後に付与しているのは、User-Agent が変わってもデバイス判定結果( X-Custom-Device )が同じ場合にはオリジンにアクセスが来ないことを確認するためです。 “その他” 判定 “その他” を判定させるため、Google Chrome の User-Agent (以下)でアクセスしてみます。 User-Agent:Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36 コマンド実行結果は以下。 HTTP/1.1 200 OK X-Cache: Miss from cloudfront ← 初回アクセスなので Miss HTTP/1.1 200 OK Age: 1 X-Cache: Hit from cloudfront ← 2回目以降は User-Agent が異なっても iPhone 判定なので Hit 〜〜〜中略〜〜〜 HTTP/1.1 200 OK Age: 29 X-Cache: Hit from cloudfront ← Age: 29 までは Hit HTTP/1.1 200 OK X-Cache: RefreshHit from cloudfront ← Age: 30 を迎える頃に RefreshHit HTTP/1.1 200 OK Age: 1 X-Cache: Hit from cloudfront ← RefreshHit 以降はまた Age:30 になるまで Hit 記録された nginx アクセスログは以下。 ***.***.***.*** - - [07/Aug/2017:16:25:02 +0900] "GET / HTTP/1.1" 200 3770 "-" "Amazon CloudFront" "***.***.***.***" Other ***.***.***.*** - - [07/Aug/2017:16:25:32 +0900] "GET / HTTP/1.1" 304 0 "-" "Amazon CloudFront" "***.***.***.***" Other 無事に末尾に “Other” が記録され、最初に “Other” 判定されたアクセスから30秒間は、"Other" 判定される他のアクセスは nginx 側には来ませんでした。 “iPhone” 判定 “iPhone” を判定させるため、以下の 4 つのバージョンの User-Agent で順番にアクセスを繰り返してみます。 iOS 10 Mozilla/5.0 (iPhone; CPU iPhone OS 10_3_2 like Mac OS X) AppleWebKit/603.2.4 (KHTML, like Gecko) Version/10.0 Mobile/14F89 Safari/602.1 iOS 9 Mozilla/5.0 (iPhone; CPU iPhone OS 9_3_5 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13G36 Safari/601.1 iOS 8 Mozilla /5.0 (iPhone; CPU iPhone OS 8_4_1 like Mac OS X) AppleWebKit/600.1.4 (KHTML, like Gecko) Version/8.0 Mobile/12H321 Safari/600.1.4 iOS 7 Mozilla/5.0 (iPhone; CPU iPhone OS 7_1_2 like Mac OS X) AppleWebKit/537.51.2 (KHTML, like Gecko) Version/7.0 Mobile/11D257 Safari/9537.53 コマンド実行結果は “その他” 時と同様なので割愛します。 記録された nginx アクセスログは以下。 ***.***.***.*** - - [07/Aug/2017:17:33:36 +0900] "GET / HTTP/1.1" 200 3770 "-" "Amazon CloudFront" "***.***.***.***" iPhone ***.***.***.*** - - [07/Aug/2017:17:34:06 +0900] "GET / HTTP/1.1" 304 0 "-" "Amazon CloudFront" "***.***.***.***" iPhone 無事に末尾に “iPhone” が記録され、最初に “iPhone” 判定されたアクセスから30秒間は、"iPhone" 判定される他のアクセスは nginx 側には来ませんでした。 “iPad” 判定 “iPad” を判定させるため、以下の 4 つのバージョンの User-Agent で順番にアクセスを繰り返してみます。 iOS 10 Mozilla/5.0 (iPad; CPU OS 10_3_2 like Mac OS X) AppleWebKit/603.2.4 (KHTML, like Gecko) Version/10.0 Mobile/14F91 Safari/602.1 iOS 9 Mozilla/5.0 (iPad; CPU OS 9_3_5 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13G36 Safari/601.1 iOS 8 Mozilla/5.0 (iPad; CPU OS 8_4_1 like Mac OS X) AppleWebKit/600.1.4 (KHTML, like Gecko) Version/8.0 Mobile/12H321 Safari/600.1.4 iOS 7 Mozilla/5.0 (iPad; CPU OS 7_1_2 like Mac OS X) AppleWebKit/537.51.2 (KHTML, like Gecko) Version/7.0 Mobile/11D257 Safari/9537.53 コマンド実行結果は “その他” 時と同様なので割愛します。 記録された nginx アクセスログは以下。 ***.***.***.*** - - [07/Aug/2017:17:58:10 +0900] "GET / HTTP/1.1" 200 3770 "-" "Amazon CloudFront" "***.***.***.***" iPad ***.***.***.*** - - [07/Aug/2017:17:58:40 +0900] "GET / HTTP/1.1" 304 0 "-" "Amazon CloudFront" "***.***.***.***" iPad 無事に末尾に “iPad” が記録され、最初に “iPad” 判定されたアクセスから30秒間は、"iPad" 判定される他のアクセスは nginx 側には来ませんでした。 “Android” 判定 “Android” を判定させるため、以下の 4 つのバージョンの User-Agent で順番にアクセスを繰り返してみます。 Android 7 Mozilla/5.0 (Linux; Android 7.0; SCV36 Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.91 Mobile Safari/537.36 Android 6 Mozilla/5.0 (Linux; Android 6.0.1; SOV34 Build/39.0.C.0.282) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.81 Mobile Safari/537.36 Android 5 Mozilla/5.0 (Linux; Android 5.1.1; SOV32 Build/32.0.D.0.282) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.133 Mobile Safari/537.36 Android 4 Mozilla/5.0 (Linux; Android 4.4.4; SOL26 Build/23.0.C.0.296) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.141 Mobile Safari/537.36 Android 2 Mozilla/5.0 (Linux; U; Android 2.3.5; ja-jp; IS12F Build/FGK600) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1 コマンド実行結果は “その他” 時と同様なので割愛します。 記録された nginx アクセスログは以下。 ***.***.***.*** - - [07/Aug/2017:18:11:28 +0900] "GET / HTTP/1.1" 200 3770 "-" "Amazon CloudFront" "***.***.***.***" Android ***.***.***.*** - - [07/Aug/2017:18:11:58 +0900] "GET / HTTP/1.1" 304 0 "-" "Amazon CloudFront" "***.***.***.***" Android 無事に末尾に “Android” が記録され、最初に “Android” 判定されたアクセスから30秒間は、"Android" 判定される他のアクセスは nginx 側には来ませんでした。 あるデバイスのキャッシュがある状態で別デバイス判定のアクセスをする 例えば “iPhone” のキャッシュがある状態で “Android” のアクセスをした場合に、"iPhone" のキャッシュを返さず、オリジンにアクセスが来るか、を検証します。 “iPhone” 判定のコマンドを先に開始し、15秒後に “Android” 判定のコマンドを開始して検証。 記録された nginx アクセスログは以下。 ***.***.***.*** - - [07/Aug/2017:18:22:00 +0900] "GET / HTTP/1.1" 304 0 "-" "Amazon CloudFront" "***.***.***.***" iPhone ***.***.***.*** - - [07/Aug/2017:18:22:15 +0900] "GET / HTTP/1.1" 304 0 "-" "Amazon CloudFront" "***.***.***.***" Android ***.***.***.*** - - [07/Aug/2017:18:22:30 +0900] "GET / HTTP/1.1" 304 0 "-" "Amazon CloudFront" "***.***.***.***" iPhone ***.***.***.*** - - [07/Aug/2017:18:22:45 +0900] "GET / HTTP/1.1" 304 0 "-" "Amazon CloudFront" "***.***.***.***" Android 15秒後に “Android” 判定のアクセスがあり、それぞれが30秒ごとにオリジンにアクセスが来ることが確認できました。 あとがき 今回行った CloudFront の設定上は、適当にクエリパラメータを付けたり、Cookie を変更したりしても、1つの Path に対してはデバイス判定結果( X-Custom-Device ) が異ならない限り同じキャッシュが返ります。 これらの検証結果までは載せていませんが、このことを意識すると、オリジンで判定処理等をすること無く CloudFront の Edge 上でキャッシュの単位をコントロールできる、と考えられます。 ただし、本記事執筆時点で Lambda@Edge は 1秒 or 3秒のタイムアウト(変更不可) があるため、あまり大変な処理はできないので、なるべく簡単な処理に留めることが大事です。 ちなみに、今回のコードではおおよそ1ミリ秒以内で処理できました。 参考: Lambda@Edge の制限 また、今回の検証では X-Custom-Device ごとのレスポンス出し分けまではしませんでしたが、この仕組みを使えば User-Agent を通さずにデバイスごとにレスポンスを出し分けられるため、キャッシュヒット率の向上=オリジンサーバーの負荷軽減、レスポンス速度改善が見込めます。 Lambda@Edge は無事に正式リリースされたので、mediba でもどんどん導入していく予定です。
こんにちは、最近 WAF WAF しているインフラストラクチャー部の沼沢です。 WAF(Web Application Firewall) といえば、SQL インジェクションなどの Web アプリケーションへの攻撃をブロックするためのファイアウォールです。 そんな用途とは少し異なり、AWS WAF を使って CloudFront や Application Load Balancer へのアクセス制限をする機会が多いのですが、自分自身、WAF というものにあまり縁が無かったからなのか、AWS WAF の設定が少々ややこしくてわかりにくかったので、自分なりに if 文に例えてみました。 なお、あくまで理解するためだけの例えとなりますので、正確には以下の公式ドキュメント等を参考にしていただければと思います。 ルールの使用 - AWS WAF と AWS Shield アドバンスド ウェブ ACL の使用 - AWS WAF と AWS Shield アドバンスド Condition まずは最小単位の Condition からです。 Condition は if 文の1つの条件と捉える if (ipaddress == "xxx.xxx.xxx.xxx"){ // アクセス元の IP Address が "xxx.xxx.xxx.xxx" だったら OK! return "Access OK"; } 上記の if 文で言うと、 ipaddress == "xxx.xxx.xxx.xxx" が Condition(IP addresses 相当) にあたります。 Rule Rule は if 文の条件 (Condition) の塊と捉える if (ipaddress == "xxx.xxx.xxx.xxx" && method == "POST") { /* アクセス元の IP Address が "xxx.xxx.xxx.xxx" かつ * * HTTP Method が "POST" だったら OK! */ return "Access OK"; } 上記の if 文を Rule と仮定するとして、 以下の2つの Condition の塊が Rule ipaddress == "xxx.xxx.xxx.xxx" (IP addresses 相当) hostHeader == "hoge.com" (String matching 相当) このような、 1つ以上の Condition の組み合わせが Rule です。 1つの Rule 内では Condition は 全て AND で繋がります 。 1つでも異なるとその Rule ではマッチしません。 Web ACL Web ACL は if 文本体と捉える if (!url.startsWith("/hoge/") && userAgent == "hogehoge") { /* URL の Path が "/hoge/" で前方一致 かつ * * User-Agent が "hogehoge" だったら OK! */ return "Access OK"; } else if (ipaddress == "xxx.xxx.xxx.xxx") { // アクセス元の IP Address が "xxx.xxx.xxx.xxx" だったら OK! return "Access OK"; } else { // それ以外は NG! return "Access NG"; } 上記の if 文全体を Web ACL と仮定するとして、上から 以下の2つの Condition を含む Rule が 優先順位 1 の Rule (最初の if 文) !url.startsWith("/hoge/") (String matching 相当) userAgent == "hogehoge" (String matching 相当) 上記2つとも一致すれば許可 以下の1つの Condition を含む Rule が 優先順位 2 の Rule (次の else if 文) ipaddress == "xxx.xxx.xxx.xxx" (IP addresses 相当) 上記に一致すれば許可 全ての Rule にマッチしなかった(else ブロックに入った)ものの挙動を定義(Web ACL のデフォルト動作相当) 全ての Rule にマッチしなかったリクエストを拒否 なお、Rule 同士は 全て OR で繋がります 。 (else if で繋がると考えた方がわかりやすいかも?) そのため、Rule は 優先順位ごとに先勝ちで評価されます 。 まとめ 今回紹介したように普段使い慣れている(?) if 文に例えてみるとなかなか分かりやすかったので、皆様の参考になれば幸いです。 if 文同様、あまり複雑な条件にすると他の人が解けない難解な条件となり、後々とても後悔することになるので、なるべくシンプルな組み合わせを心がけたいですね。
こんにちは、制作部 フロントエンドエンジニアの武田です。 入社して5ヶ月経ちました。 ECMAScript の推し proposal は Cancellation API です。 今回は開発には切っても切れない Babel 、そのプリセットである babel-preset-env についてお話します。 このプリセットは Browserslist 1 の記述で compat table 2 を利用し、指定された環境にあったトランスパイルができるプリセットです。 使い方についての記事はすでに多くありますので今回は丁寧に触れません。 babel-preset-env を試す – アカベコマイリ 最新版で学ぶwebpack 3入門 - ICS MEDIA ピンと来ない方は、JavaScript をとりまくビルド環境のスケーリングに寄与しそう、と大きく理解していただいてよいです。 対象となりそうな人 Babel を使ったトランスパイル環境を作ったことがある人 babel-preset-env をすでに使っている人、知っている人 Babel 沼にハマったことがある人 ES2017 以降も Babel に付随するパッケージをアップデートしながらやっていきな人 書くこと そんな便利なプリセットの alpha 版である @2.0.0 を試しに使ってみて 2.0 で何が変わるか 2.0 以降、babel-preset-env を使ったトランスパイルがどう変わりそうか それらを踏まえて、今後の Babel の動向をほんのり少しだけ について書いていきます。 この記事を書いている時点の2系は alpha 版となっておりそれらを利用して書いていますので、 プロダクション環境への導入は安定版となってから十分に吟味した上で利用ください。 サンプル https://github.com/mediba-takeda/babel-env-sandbox master ブランチ = 2系、feature/preset-env@v1.x ブランチ = 1系 です。 ※ 切り替えで試してみる際は node_modules を一旦消してくださいね。 v2 でトランスパイルしたものを gh-page に上げてみました。 https://mediba-takeda.github.io/babel-env-sandbox/ ES2015, 2016, 2017 の構文を拾い環境を変えながらトランスパイルしています。 書かないこと サンプルで使用しているコードそのものについて バンドラーとして使用した webpack について babel-preset-env v1 => v2 における大きな変更点 v2になって何が変更されるかというと、大きなところでは useBuiltIns 設定の値が変更されます。 { "presets": [ ["env", { "targets": { "browsers": [ "ios >= 9" ] }, // v1 まで は true | false "useBuiltIns": true // v2 からは "usage" | "entry" | false "useBuiltIns": "usage" }] ] } useBuiltIns って何の設定ですか A way to apply babel-preset-env for polyfills (via babel-polyfill). です。要は babel-polyfill を引き込むかどうかの設定になります。 1系ではコード上に @import 'babel-polyfill' の記述が必要です。 1系で "useBuiltIns": true に設定しトランスパイルした、コンソールへのデバッグ表示が下記になります。 Using plugins: ブロックでこのブラウザターゲットに必要なものがわかります。 その後に Using polyfills: ブロックでさらに穴埋めする Polyfills 。なんですが… …便利ですけどこんなに要らないです…。使ってないものの方が多い…。 これは babel-preset-env を使用していなくても抱える悩みですね。 @import 'babel-polyfill' の全マシで要らないものまで引き込みたくない気持ちです。 v2 は余計なもの引き込まない "useBuiltIns": "usage" という v2 の新しい設定をしてトランスパイルしてみます。 2系ではこの設定を有効にした場合 @import 'babel-polyfill' の記述は必要ありません。 Using polyfills: の箇所がちょっと変わっていますね。 デバッグログだけだと少しわかりづらいですが、静的解析を行いコード上の使用に応じて穴埋めしています。 これで不要に Polyfill だけで肥大化することはなくなります。 ちなみに "useBuiltIns": "entry" の使用はエントリーファイルが1つの場合のみとして v1 時の true と同等の動きをします。 それでもハマりそうな Babel 沼 ただケースによっては問題が出てきます。 3 Object.assign のような静的なメソッドは変換や解析がうまくいきますが、 String.prototype.includes Array.prototype.includes のようなインスタンス/プロトタイプメソッドが、コンテキスト上で arr.includes('foo') のように利用される場合、 arr の型判定をした上で変換、ということができません。 これについてはすでに issue にあがっておりまだ落としてどころがないように見えます。 4 型推論しようとすれば TypeScripts, Flow のようなアノテーションか型定義ファイルへのリファリングをしてから変換する必要があります。 現状どうするのかといえば、それ用に別の Polyfill ライブラリで埋めるか、明示的に @import 'core-js/modules/es7.array.includes' するかどちらかしかないでしょう。 v2 のその先、Babel の動向を少しだけ v2 でこの大きな変更は v1 がリリースされてすぐに issue で議論され 5 、4月段階でマージされたあとに v2.0.0-alpha.6 としてリリースされています。 6 次のロードマップとしては transform-runtime を機能的にこちらに移す話も上がっています。 7 日付としてはちょっと古いミーティングですが、下記のような記載も見受けられます。 8 Move babel-preset-env into core (or a package (like babel) that includes it by default) コアに入るような入らないような話もちらほら出ているということですね。 We released babel-preset-env v1.6 yesterday with support for node 8 and browserslist’s chromeandroid target! 2.0 is coming soon! #babeljs — Brian Ng (@existentialism) 5 July 2017 コアコミッターの Tweet を信じ、カミングスーンな安定版の 2.0 を待つことにします。 babel-preset-env v2 まとめ v2 で Polyfill がスマートになる ただ課題はまだありそうだから慎重にいったほうが良さそう v2 以降の動向を watch しておくと良さそう babel-preset-env が必ずしも銀の弾丸ではありませんが、ECMAScript の策定・採択とブラウザ実装に追従しながら、環境・コードともにスケールできると一番ステキですね。 本日は以上です。最後まで読んでいただき、ありがとうございました。 補足 Web プラットフォーム = ブラウザに限った話でしたが、babel-preset-env は Node.js のバージョン、Electron のバージョンにも有効です。 ES2015 〜 のスクリプトに関しては、UglifyJS が有効ではありません。 harmony ブランチである uglify-es では有効ですが、Babel の推奨する babili を使用してminifyしています 。 リポジトリでは webpack を使用していますが、もちろんエントリーファイルが 1 ファイルであれば babel-cli も有効です。 レガシーな端末での動作を確実にするものではありません。トランスパイルによるファイルの肥大化・端末自身の処理速度によってはパフォーマンスが著しく落ちることもあります。 サンプルの検証について サンプルで作ったものは下記端末、環境で確認しています。 ※ 多端末までは確認していませんのでご注意ください。 ※ development, recent(=last 2 version) 環境については特に検証の対象としていません。 環境指定 端末 OK android>=2 Android2.2 on IS05 ◯ android >= 4 Android4.1.2 on Xperia UL SOL22 ◯ ie >= 10 IE10 on Win7(x86) 9 ◯ ios >= 9 iOS9 on iPhone5S ◯ chrome >= 59 Chrome60 on Mac Sierra ◯ https://github.com/ai/browserslist ↩︎ https://github.com/kangax/compat-table ↩︎ サンプルリポジトリの Array.prototyp.includes参照 ↩︎ https://github.com/babel/babel-preset-env/issues/284 ↩︎ https://github.com/babel/babel-preset-env/issues/84 ↩︎ https://github.com/babel/babel-preset-env/pull/241 ↩︎ https://github.com/babel/babel-preset-env/issues/246 ↩︎ https://github.com/babel/notes/blob/master/2017-04/april-08.md ↩︎ Microsoft 配布の VM イメージ ↩︎
こんにちは!制作部 デザイナーの森本です。 最近は、スマートフォンなどの端末の解像度が上がってきているため、アイコンであっても大きな画像が必要になりますが、多用するとページの描画速度の低下にも繋がってしまいます。 そこで、画像を多用せずとも高解像に対応できる「アイコンフォント」を簡単に作る方法をご紹介いたします。 今回は最近使ってみて一番シンプルで使いやすいと感じたジェネレーターサイト「IcoMoon」を使い、 SVGからアイコンフォントを作成していきます。 IcoMoonとは? SVG形式のアイコンデータをアップロードするだけで自作のオリジナルアイコンを作成できる便利なWebサービスです。 また、商用でも無料で使用可能なアイコンが豊富に提供されています。 ※ IcoMoon(Library内)の各アイコンには複数のライセンス形態がありますので、使用する前によく確認しましょう。 アイコンフォントを使うメリット・デメリット メリット ビットマップではなくベクターとして扱えるので、解像度に依存せず拡大縮小が可能 1つのフォントファイルにまとめる事ができるので、画像と比較するとHTTPリクエストを減らせる トータルの容量を画像よりも抑えることができる 大きさや色をCSSでカスタマイズできる 導入が簡単でメンテナンスの効率もアップ! デメリット 一部古いブラウザではアイコンフォントが使用できない場合がある(参考 : Can I use web font? ) アイコンフォントが無効な環境の場合、アイコンが表示されなかったり、意図しない文字や記号が表示されたりしてしまう アイコンフォントの作成の流れ 1. IllutratorでSVG形式のデータを作成する パスでアイコンを描く アートボードは正方形にする(好きなサイズでOK) 線やフォントはアウトライン化した上で、必ずパスファインダーで結合する 余計なアンカーポイントを削除しておくことでアイコンデータが多少軽くなります SVGで書き出し保存する 「ファイル」→「別名で保存」→「ファイル形式」を「SVG(svg)」に変更して保存 2. IcoMoonを使って自作のSVGデータをアイコンフォントへ変換 IcoMoonはこちら http://icomoon.io/ 無償で使えるアイコンは、IconMoon-Freeで現在 490種類あります 2-1.右上の「IconMoon App」というボタンをクリック 2-2.たくさんのアイコンが表示されるページに遷移します このページの「IcoMoon - Free」の中のアイコンについては無料で利用できるようです。 2-3.左上の「Import Icon」ボタンをクリックし、先ほど作ったSVGデータを登録する 2-4.登録できました 2-5.アイコンデータを選択する 自分で作ったアイコン以外にも使いたいものがあればここで一緒に選択する。 2-6.右下の「Generete Font」ボタンをクリック 2-7.右下の「Download」ボタンをクリックしてデータをダウンロード! この画面でファイル名の変更などちょっとした編集を行うことができる 3. HTMLとCSSに反映する IcoMoonでダウンロードしたデータを解凍すると以下のようになっています demo-files demo.html fonts Read Me.txt selection.json style.css アイコンフォントを表示させるのに必要なファイル index.html(作成する) style.css fontフォルダ内の4つのフォントファイル icomoon.eot icomoon.svg icomoon.ttf icomoon.woff ※ 4つすべてアップしないと環境によってアイコンが表示されない場合があります index.htmlを作成し、タグを記述する index.html内でアイコンを表示させたい場所の要素にCSSクラスをつける <!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <title>IcoMoonを使ってSVGからアイコンフォントを作る</title> <link rel="stylesheet" href="style.css"> </head> <body> <!-- アイコン --> <span class="icon-webiconfont-01"></span> <span class="icon-webiconfont-02"></span> <span class="icon-headphones"></span> </body> </html> IcoMoonのこちらの画面からソースコードをコピーすることもできます htmlのソースコードがでてきました 4. では表示してみましょう! アイコンのカスタマイズをする場合は style.css を編集します [class^="icon-"], [class*=" icon-"] { font-family: 'icomoon' !important; speak: none; font-style: normal; font-weight: normal; font-variant: normal; text-transform: none; line-height: 1; /* アイコンのカスタマイズ */ font-size: 300px; color: #44aac1; } カスタマイズ後はこちら 大きくしてもキレイですね! さいごに 大きさや色の変更が発生した際にも画像と比べてカスタマイズが簡単にできるため、とても効率的だと感じました。 簡単に実装が可能で学習コストもかからないので、単色のシンプルなアイコンであれば取り入れるととても便利だと思います。 また、ブラウザ中でベクター画像を扱うには、SVGを直接記述する方法もあります。 ブラウザ対応状況の違いやメンテナンス性の違いなど、それぞれメリットやデメリットがありますので、一概にどれが正解と言えるものではないですが、色々な手段を知っておくことで選択肢が広がり適宜対応できるのではないかと思います! 最後までご覧いただきありがとうございました!
こんにちは。広告システム開発部の竹谷です。 私のチームのサービスで、Laravelのバージョンを5.2から5.3にあげたときの話を書きます。 すでに5.4も出ていますが、段階的にまずは5.3にあげました。 PHPのバージョンは7.0.9です。 ドキュメント ドキュメントはこちらになります。 https://laravel.com/docs/5.3/upgrade#upgrade-5.3.0 composer.jsonの修正からやってみる まずは、composer.json を修正して、 $ composer install をやり直してみます。 - "laravel/framework": "5.2.*", + "laravel/framework": "5.3.*", "laravelcollective/html": "~5.0", "webpatser/laravel-uuid": "2.*", "aws/aws-sdk-php": "3.*", @@ -16,8 +16,8 @@ "fzaninotto/faker": "~1.4", "mockery/mockery": "0.9.*", "phpunit/phpunit": "~4.0", - "symfony/css-selector": "2.8.*|3.0.*", - "symfony/dom-crawler": "2.8.*|3.0.*", + "symfony/css-selector": "2.8.*|3.1.*", + "symfony/dom-crawler": "2.8.*|3.1.*", エラーがでます。 > php artisan optimize [ErrorException] Declaration of App\Providers\EventServiceProvider::boot(Illuminate\Contracts\Events\Dispatcher $events) should be compatible with Illuminate\Foundation\Support\Providers\EventServiceProvider::boot() これは、ドキュメントの Application Service Providers という項目にあるとおり、下記の3つのファイルのboot関数が変わったことが原因のようです。 boot関数の引数を消します。 app/Providers/AuthServiceProvider.php app/Providers/EventServiceProvider.php app/Providers/RouteServiceProvider.php AuthServiceProvider.php - public function boot(GateContract $gate) + public function boot() { - $this->registerPolicies($gate); + $this->registerPolicies(); EventServiceProvider.php - public function boot(DispatcherContract $events) + public function boot() { - parent::boot($events); + parent::boot(); RouteServiceProvider.php - public function boot(Router $router) + public function boot() { - parent::boot($router); + parent::boot(); もう一度、 $ composer install すると今度は通りました。 動かしてみる とりあえず動かしてみます。 エラーが出ます。 FatalErrorException in Controller.php line 13: Trait 'Illuminate\Foundation\Auth\Access\AuthorizesResources' not found ドキュメントの The AuthorizesResources Trait という項目に 「AuthorizesResourcesは、AuthorizesRequestsに統合したから削除しておきましょう。」というような記載があるので削除します。 app/Http/Controllers/Controller.php use Illuminate\Foundation\Auth\Access\AuthorizesRequests; -use Illuminate\Foundation\Auth\Access\AuthorizesResources; class Controller extends BaseController { - use AuthorizesRequests, AuthorizesResources, DispatchesJobs, ValidatesRequests; + use AuthorizesRequests, DispatchesJobs, ValidatesRequests; } 再度動かしてみると、別のエラーが出ます。 ReflectionException in Container.php line 749: Class App\Http\Controllers\Auth\LoginController does not exist ドキュメントの Authentication Scaffolding という項目に記載があります。 https://github.com/laravel/laravel/tree/5.3/app/Http/Controllers/Auth にある4つのコントローラーをコピペして新しく置きます。 AuthController は不要になります。 AuthController でやっていたカスタマイズは各コントローラーに引きつぎましょう。 routes.phpに以下も追加。 Auth::routes(); 私たちのサービスで困った点 私たちのサービスで困った点としては、次のようなことがありました。 5.2では以下のような感じでログインに使うパラメータを指定していたのですが、その指定の方法が変わっていました。 protected $username = 'login_id'; 5.3だと、username関数をオーバーライドする。 public function username() { return 'login_id'; } 他のprotected メンバ変数もオーバーライドするように変更になったようです。 ここまでで、一通り動くようになりました。 ドキュメントの変更点を確認 あとは、ドキュメントの変更点を読んで、サービスに影響がないかを確認します。 インターフェイスが変わっていたり、動作が変わっているところは要注意ですね。 まとめ そんなにひっかかるところもなくスムーズにバージョンアップができました。 なるべく早く5.4にアップグレードしたいと思います。
制作部でデザイナーをしている渡邉と申します。 昨年、2016年の4月に入社して6月の頭で配属1年になります。 学生時代は現代アートを専攻する科でカリキュラムにあらがって写真とか撮ってました。 さて今回は、私が新米Webデザイナーとして配属されてからのたくさんの学びの中から、つまずいた点や重要だと感じた点について紹介し、2年目にどう繋げていくべきかを書こうと思います。 自分のすぐ後を歩いている学生さんやWebデザイナーを目指している方の参考になれば嬉しいです。 また初心を忘れないための備忘録も兼ねて、書いていきます。 では早速。。。 目次 こういうところに注意しよう(デザイン編) こういうところに注意しよう(コーディング編) これらを身につけた上で2年目に挑戦すること 最後に こういうところに注意しよう(デザイン編) 作業中はセルフツッコミを入れ続けよう デザイン=ビジュアルを整えること、と捉えがちですが、デザインは問題解決の手段です。「誰が抱えてるどんな問題を解消するために、何ができるのか」「果たしてこの一手は適切なのか」を考えながら作業すると自ずと論理立てられたデザインができると思います。 いいデザインを見つけてどんどん真似しよう 同じような配色だったり近い文字量のバナーを参考するといいです。引き出しが増えて確実にパワーアップします。(私は学生時代に学んだ著作権の知識がブレーキをかけてしまってましたが、作ってるうちに全然違う見た目になっていくのでパクリだと不安になることはないです。) また、参考資料を探すときは英語で検索するとオシャレなアイデアが見つかりやすいです。 “一番手こずった場合"を想定してスケジューリングしよう フィードバックでは自分が想定してなかった点を指摘される訳なので、修正は自分になかったアイデアを具現化していく作業になります。思いのほか時間がかかるので、早めに提出できるスケジュールを組みましょう。 フォルダ・ファイル・レイヤーは全てにきちんと命名し整理整頓しよう 会社で作ったデータは他の人に渡したり使い回すことがあります。誰に渡しても使いやすいように職場でのユーザビリティにも気を配りましょう。「長方形3のコピーのコピー」とか「背景写真の候補2(あとで消す)」とかナシです。 ちなみに複製したレイヤーに「〜のコピー」を表示させない方法は こちら 、 レイヤーの名前を一括で管理できるプラグインについては こちら です。 引き算のデザインで迷走を防ごう 引き算のデザインとは情報量のピークから削っていって完成系はシンプルにする方法です。まず必要そうな情報をとにかく盛り込み情報量をMAXにします。羅列した情報に優先順位をつけて画面の左上から順番に配置していきます。書かなくても伝わる要素、キャンバスからはみ出た要素は容赦なく捨てちゃいましょう。「順序立てた要素を」「どうシンプルに組むか」を意識しましょう。 完璧を求めずまずは終わらせよう デザイナーたるもの人が気付かないような細部までこだわることはとても大切。文字のエフェクトに悩んでいたら暗くなっていたなんてこともありましたが、納期に間に合わなかったら元も子もありません。 自分の力で考えることが非常に大事ですが、先に進めなさそうな時はすぐに先輩に相談しましょう。間違った方向で進めるよりもよほどいいです。決してネガティブなニュアンスではなく「絶対に間に合う範囲でどこまでできるかを考える」ことも大切だとも思いました。 デザインのフローと意識すること スケジューリング(複数回の再提出も考慮して早め早めの予定を組む) ↓ プランニング(ターゲットやテーマやゴールを明確にする) ↓ エスキース(必要そうな要素をとにかく書き出す) ↓ ラフスケッチ(本当に必要な情報のみキャンバスに並べる) ↓ レイアウト(他の作品を参考にしつつ、重要度順に並べる) ↓ 調整(行間等細部の調整。画面から離れたりセルフツッコミを入れて制作物を客観視する) ※後々コーディングするデザインの場合は奇数や小数のオブジェクトがないか注意する ↓ 提出(迷走し出したら早めに相談。レビューを考慮し余裕を持って提出) ↓ 再編集(成果物を客観視しユーザー目線で改善策を模索。参作をよく観察して編集) ↓ 入稿(納品データは軽く軽く) こういうところに注意しよう(コーディング編) マークアップは"レイアウト"じゃない 最初全く馴染めなかったのですが、マークアップはデザイン案通りにパーツを並べさせることではなく、文章に適切な意味づけをして人間語の構造を機械に認識させていくことです。デザインカンプ内に並んだ各パーツをよく読み解き、「これが小見出しに当たるからh3で…」「ここは補足情報だからasideで…」などと適切に目印(マーク)を振りましょう。 コーディングを始める前に設計図を書こう 上の書いた作業をするときは構造を紙にザクザク書き込んでみましょう。要素の親子関係を図にしたり動きを絵で描いたりするとかなり頭が整理できてエディターでの作業が早くなります。もし適当なクラス名でもって見切り発車的に作り始めてしまうと、後で修正が面倒になりますしいらないケアレスミスを招いたりもします。 編集に強く、汎用性の高いマークアップを心がけよう デザインデータ同様、他の方がコーディングファイルを触ることがあります。なので"このページに足りるコーディング"ではなくて、全てのページに通用するルールに則ってコーディングをしましょう。具体的には、仕様変更に対応しやすいように各要素にクラス名を与えておくことや、社内ルールに従って命名することが大切です。 プルリク前にセルフコードレビューをしよう 余計な編集を加えていないか、リンクが機能しているか、仕様漏れはないかなど、自分で気づけるレベルのミスはレビュー前に潰しておきましょう。せっかく先輩方に確認をしていただくのに、しょうもない部分の指摘に時間を取らせてしまうのは賢明でないです。(長く同じ画面を見ていると目が慣れてしまい些細なミスに気づけないこともあります。エディターの画面だけでなく実機のプレビューでもしっかりと確認しましょう。) リリース完了まで責任を持とう どの端末でも正しく表示されているか、正しくリンク先に遷移するかなどを確認し、旅立つ子供を見えなくなるまで送る親のように、手を離れたページを見守りましょう。自分の成果物に責任を持ち、リリース完了の確認がディレクターからも取れるまでは、対応できる状態でいましょう。 ネットサーフィンはデバックモードで楽しもう たまにやってみるとかなりコーディングの勉強になります。問題と解答が一枚のページに載っている状態なのでとてもいい教材です。「よく見るレイアウトだけどこんな手法もあったのか」とか「この手のパーツにはこんなクラス名がいいんだな」とか、発見が沢山ありますw これらを身につけた上で2年目に挑戦すること 次に、上に書いた諸々を身につけたうえで、入社2年目としてやるべきことについて書きます。 自立してできる仕事を増やすこと 与えられた仕事をこなすだけでなく、自立してできる仕事を増やしたいです。そのためにまず、仕事を円滑進めるために何をするべきかを考えながら行動します。これを実践していくことで、チームで効率的に動ける(動かせる)力を身につくと思っています。また、チーム内で進行している別の仕事やチーム内の他のメンバーが持っているタスクを把握することで、必要とされる場所に自分から飛び込める人間になります。 提案すること いいものを作っていくために、よりディレクターチームと連携し企画段階から関わっていきたいです。 提案するためには自分の考えを持っている必要があり、自分の考えを持つためは知見を持っている必要があるはずです。なので提案できる人間になるために、まずはサービズの仕組みを理解したり、数値を読み解けるようになったり、日々の実務を通じてより深い部分にまで関心を向けていこうと思います。 脱"新人"を意識すること 後輩がぞくぞくと入ってきます。新卒で入社した人材として、先輩から受け継いだことを自分で磨き後輩に継承していくことが求めらるはずです。新入社員としてではなく先輩社員として求められるものは何かを考えると、技術や業務についてもっと深く知ってなければいけないと気づけます。 その他これから挑戦していきたいこと 自分の力でJSを動かせるようにする(パズルゲームの一種だと思うと勉強しやすい。動くと超楽しい) UIについて学ぶ(デザイン思考を身につけて制作するときの武器を増やしたい) 英語に慣れる(エラーメッセージやコンソールをちゃんと読む。英語の技術ブログや取説も読む努力をする) 新しいものに興味を持つ(現状は最新技術や世の中の流行りにうとい) 映像制作について学ぶ(学生時代に少しやってた動画撮影や映像制作についてもう少し学び、仕事でチャンスをつかみたい) 最低限ターミナルを使えるよになる(画像データを扱うのでFinderの方が使用機会が多いが、ターミナルでコマンド制御できたら早いしカッコイイ!) 仕事直結じゃないインプットも大切にする(本、雑誌、映画、舞台など) 最後に 自分を含めた新人デザイナーさんや、学生さんに向けて書きます。 満を持して社会に出ても初めは教えていただくことばかりで、心のどこかで学生気分を引きずってしまいがちです。しかし、私はもう自分の成果物の対価としてお給料を頂いています。会社に入ってこの活動でお給料をもらっているということは"プロ"になったということなのです。 私はまだまだ先輩方のアドバイスが必要で、自分をwebデザイナーと名乗るのはおこがましいと思ってしまいますが、そんな気持ちで遠慮していて大きなヒヨコになってはいけないです。なので今すぐにもでもプロ意識を持って仕事に取り組むべきだと思います。一緒に頑張っていきましょう!
メディアシステム開発部の野崎です。 メディアシステム開発部では、「 au Webポータル 」や「 au スマートパス 」といった、 多くのユーザ様にご利用頂いているサービスを担当しています。 このようなシステムでは新規開発や機能追加時には負荷試験を実施することは必須となります。 そこで今回は、Webシステムの負荷試験について 負荷を生成する環境にフォーカスして、 これまで行ってきたノウハウをまとめてみます。 はじめに 負荷試験と言われるものには幾つか種類があります。 性能試験、耐久試験、限界試験、ロードテストなどがありますが、 今回は簡単のために、以下の目的の試験をまとめて負荷試験と呼ぶことにします。 非機能要件で定義した性能を担保できるか? システムの性能の限界はどのくらいなのか? 長時間連続して負荷をかけて、サービスイン後状況を再現し、期待通りに動作するか? 生成する負荷としては、秒間数百から数千リクエストを想定しています。 システム前提条件 以下のような環境である前提で話を進めます。 負荷試験対象のシステムはAWSで構築されている。 負荷生成側のシステムもAWSにてEC2上に構築する。 負荷生成側のサーバはGatlingを使用する。(OSは、Amazon Linux) 負荷生成の指示はローカルのPCから行う。 レポートの生成はローカルのPC上で行う。 Gatlingは、Async, Netty, Akkaを用いたScala製でHTTPリクエストを対象とした負荷試験ツールです。 個人的に、構築が容易なところと結果レポートが見やすいところが気に入っています。 公式サイト と、 ソースコード が以降の説明で、参考になります。 準備 負荷試験の準備段階で、計画や環境をどのようにそろえるかを記載していきます。 準備1 - 試験の計画 試験を計画する際に注意したことです。 ◯ 新規開発時には、すべてのURLを対象とした試験計画にしましょう。 「このURLは大した機能でないから試験範囲から外そう」と思いがちですが、 バグが有ったり、想定外の負荷がかかったりして、結果的に全体に影響することもあります。 サービスイン後は全体の試験を行うことは難しいので、新規開発時にやっておくことが望ましいです。 ◯ 負荷試験の項目をすべて行える日程を複数回計画しましょう。 特に新規開発の場合ですが、往々にして期待どおりの性能を発揮できることはありません。 結果をレビューし、原因を特定修正し、再度負荷試験実施できる期間を設けましょう。 ◯ キャッシュが有り無しのパターンをテストケースに含めましょう 多くのユーザリクエストを受けるシステムの場合、何らかのキャッシュ機構を組み込むことが多いと思います。 その際キャッシュが有る場合、無い場合を想定しテストケースを作成しましょう。 アプリケーションにキャッシュを使わないモード実装が必要な場合もあります。 ◯ 別システムと連結している場合の考慮 オンラインでバックエンド側から別システムへ連結している場合、 別システムも含めた試験の範囲にするか確認しましょう。 範囲外であれば、スタブの作成などを検討しましょう。 準備2 - 負荷生成サーバ準備 負荷試験生成サーバの準備を行っていきます。 GatlingサーバはEC2上に構築します。構築時はインスタンスタイプは適当で大丈夫です。 ◯ インストール こちら を参考にGatlingをインストールしましょう。 JDK8のインストールとGatlingのパッケージを解凍し、適当なディレクトリに設置します。 ◯ OSのチューニング こちら を参考に、EC2のディスクリプタやカーネルパラメータを変更します。 ここを怠ると、あとで多くの台数のサーバが必要になったりします。 ◯ 1台のGatlingサーバがある程度の負荷を掛けられるかを確認 ある程度のオーダーの負荷が掛けられるかを確認しておきましょう。 もし期待する負荷を生成出来ない場合は、「OSのチューニング」を見直しましょう。 それでも負荷生成が十分でない場合は、インスタンスタイプを徐々にあげてみましょう。 場合によってはこの確認をするために、後述の申請が必要になったりします。 ◯ Gatlingのテストケースの作成 テストケースを作成し、構築したGatlingサーバに配置しましょう。 ※テストケースに関しては多くの情報がWeb上にあることもあり、今回書き方は説明しません。 ※GatlingDSLの チートシート を参照すれば調べやすいです。 ◯ Gatlingサーバを複数台用意する ここまで来たら、1台での負荷生成性能を元に、 複数台Gatlingサーバを作成しておきましょう。 後述の申請と関連するためで、2台以上あると試験当日に期待する負荷が生成出来ない場合でも、 インスタンスタイプを上げていけば必要な負荷を生成出来るでしょう。 ◯ AWSへの負荷試験の申請 ELBなどのAWS上のリソースに対して負荷をかけるときは、AWSへ事前の申請が必要です。 試験内容によってどういったことを申請する必要があるかはサポートなどで確認しましょう。 ※ 2017年4月時点での申請においては、負荷生成サーバのインスタンスIDが必要でした。(ということは、申請後にサーバ台数を増加できない・・!?) 準備3 - Gatilngサーバのスケーリングアウト ところで、GatilngにはJMeterの用にクラスタを構築する機能は提供されていないようですが、 こちら にある、スケーリングアウトの方法を取れば、 複数台のGatilngサーバから同時に負荷を生成することが出来ます。 今回はこちらの方法を取ってみます。 準備4 - ローカルPCの準備 ローカルPCはMacとして、以下の準備をします。 ◯ Gatilngのインストール 後述するレポートの作成をするために、ローカルPCにもGatilngを設置しておきます。 負荷生成サーバのインストールと同じ方法で大丈夫です。 (ローカルPC上で負荷生成はしないので、OSのチューニングは不要です。) ◯ csshx or i2cssh のインストール homebrew などで csshx や i2cssh をインストールしておきましょう。 これらは、ターミナルアプリをクラスタリングするアプリケーションで 1度のコマンド操作で、複数のサーバを同時に操作出来たりします。 実施 以下図の流れで試験を実施します。 Gatlingサーバ2台あるとし、gatling01,gatling02というホスト名とします。 $GATLING_HOME をインストールしたディレクトリとします。 1.1 ローカルPCからGatlingを実行指示 # ローカルPCのからGatlingサーバへログイン $ csshx gatling01 gatling02 (ターミナル.appの複数ウィンドウが立ち上がる以降はサーバ内操作) # 2台同時に実行指示 $ cd $GATLING_HOME $ ./bin/gatling.sh \ -s mediba.SampleCluster \ -nr \ -rd "cluster test" #オプション説明 -s : シナリオの指定 -nr : レポート出力しない -rd : 説明の記載 (実行中は以下のようになります。) 1.2 負荷生成 テストケースの実行が終わるまで待ちましょう。 csshx を利用した理由は、負荷生成の実行状況がGatlingサーバそれぞれにて確認できるからです。 Gatlingサーバのリソース不足が合った場合は、実行状況から確認できます。実行を止めて、インスタンスタイプを上げるなどしましょう。 レポート作成 負荷試験が無事終わったら、レポートを作成しましょう。 2.1 サーバごとの実行ログをローカルPCにDLする # ローカルPC $ cd $GATLING_HOME # 適当なディレクトリをresults以下に作成 $ mkdir -p ./results/samplecluster # Gatlingサーバからsimulation.logをDLする $ scp gatling01:/$GATLING_HOME/results/{実行ID}/simulation.log simulation_gatling01.log $ scp gatling02:/$GATLING_HOME/results/{実行ID}/simulation.log simulation_gatling02.log 2.2 サーバごとのログからレポートを作成する。 ./results/samplecluster/ 配下のログファイルを読み込んで、レポートを生成します。 $ sudo ./bin/gatling.sh -ro samplecluster # オプション説明: -ro : ログからレポートの作成のみ行う。 GATLING_HOME is set to /opt/gatling-charts-highcharts-bundle-2.2.3 Parsing log file(s)... Parsing log file(s) done Generating reports... ================================================================================ ---- Global Information -------------------------------------------------------- > request count 40 (OK=40 KO=0 ) > min response time 26 (OK=26 KO=- ) > max response time 113 (OK=113 KO=- ) > mean response time 39 (OK=39 KO=- ) > std deviation 14 (OK=14 KO=- ) > response time 50th percentile 37 (OK=37 KO=- ) > response time 75th percentile 40 (OK=40 KO=- ) > response time 95th percentile 47 (OK=47 KO=- ) > response time 99th percentile 100 (OK=100 KO=- ) > mean requests/sec 2 (OK=2 KO=- ) ---- Response Time Distribution ------------------------------------------------ > t < 800 ms 40 (100%) > 800 ms < t < 1200 ms 0 ( 0%) > t > 1200 ms 0 ( 0%) > failed 0 ( 0%) ================================================================================ Reports generated in 0s. Please open the following file: /opt/gatling-charts-highcharts-bundle-2.2.3/results/samplecluster/index.html こちら に有るような、 見やすいHTMLのレポートが生成されます。 このレポートをS3などにアップすれば、複数人での結果のレビューも行いやすく、 次のアクションも取りやすいと思います。 まとめ AWS環境でのGatlingを使った負荷試験について主に環境周りについてまとめてみました。 負荷試験の本来の目的は、試験対象システムを評価することです。 負荷試験生成の環境は、今回の手順などを参考に楽に構築し自由に負荷生成を行える様にしておけば、スムースに試験を実行できるかと思います。 今回の記事が皆さんの開発の参考になれば幸いです。 以上となります。
こんにちは、インフラストラクチャー部の沼沢です。 今回は、2016年の re:Invent で発表された Lambda@Edge を使って、リクエスト元のデバイス判定を実装してみます。 Lambda@Edge といえば、 CloudFront の Edge ロケーション上で Lambda が実行できる 画期的なサービスです。 現在は Limited Preview 中で、General Availability を待ち望んでいるサービスの1つです。 Lambda@Edge についてはこちら → AWS Lambda@Edge (プレビュー) 本投稿時点ではまだ Preview ですので、この記事の内容を試したい場合は こちら から事前に利用申請をしておきましょう。 re:Invent 2016 で発表された各サービスについては以下をご参考まで。 AWS re:Invent 2016 新サービスまとめ 1 | mediba Creator × Engineer Blog AWS re:Invent 2016 新サービスまとめ 2 | mediba Creator × Engineer Blog デバイス判定は既に簡単にできる機能がある デバイスタイプに基づいてオブジェクトをキャッシュするように CloudFront を設定する 上記に記載の通り、以下の4タイプの判定で事足りる場合は、このヘッダをそれぞれオリジンサーバーに転送し、オリジンサーバー側でこのヘッダを読み取ってレスポンスを分けたりする方法が良いと思います。 CloudFront-Is-Desktop-Viewer CloudFront-Is-Mobile-Viewer CloudFront-Is-SmartTV-Viewer CloudFront-Is-Tablet-Viewer しかし、この4タイプでは足りない場合(例えば iOS, Android でデザインを分けたい等)には、この方法は使えません。 その場合、User-Agent をオリジンサーバーに転送してアプリケーション側で判定する方法を取ることで解決できますが、そうすると CloudFront のキャッシュ効率が悪くなってしまうというデメリットがあります。 そこで、Lambda@Edge を使ってこの課題を解決してみたいと思います。 概要 今回は、以下の判定をできるようにします。 iPhone iPad Android 上記以外(Other) CloudFront にアクセスに来た際に、Lambda@Edge を Viewer Request で起動するように設定し、Request Header にカスタムヘッダを付けて nginx でログに出力するところまでを実装します。 Viewer Request とする理由は、CloudFront のエッジ上の動作が行われる前に、デバイス判定→ヘッダ追加を行いたいためです。 やってみる EC2(nginx) を用意 詳細な手順は割愛しますが、以下の手順で nginx がインストールされた EC2 インスタンスを用意します。 EC2 インスタンスをローンチ ローンチした EC2 インスタンスに EIP を付与(Public IP でも可) ローンチした EC2 インスタンスに ssh ログインし、nginx をインストール nginx のアクセスログをカスタマイズ /etc/nginx/nginx.conf の log_format を以下のように修正し、nginx を再起動します。 log_format main '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for" ' '$http_custom_device '; # 追加 Lambda ファンクションを用意 Lambda 関数の作成 設計図の選択: ブランク関数 トリガーの設定: (何も変更せず) 次へ 関数の設定 名前: device_judge 説明: device judge ランタイム: Edge Node.js 4.3 Lambda 関数のコード コード エントリ タイプ: コードをインラインで編集 以下のコードを入力 'use strict'; # User-Agent からデバイスを判定し、"Custom-Device" ヘッダを追加 exports.handler = (event, context, callback) => { const request = event.Records[0].cf.request; const headers = request.headers; if (headers['User-Agent']) { headers['Custom-Device'] = headers['User-Agent'][0].match(/(Android|iPhone|iPad)/)? RegExp.$1: 'Other'; } callback(null, request); }; Lambda 関数ハンドラおよびロール ハンドラ: index.handler ロール: “カスタムロールの作成” で作成される “lambda_basic_execution” を指定 CloudFront Web Distribution を用意 以下の通り設定していきます。 作成後、Status が Deployed になるまで待ち、CloudFront の Domain Name (xxxx.cloudfront.net) にアクセスした際に nginx のデフォルトページが表示されれば準備は OK です。 動作検証 ブラウザで、各 User-Agent でアクセスし、nginx のアクセスログを確認します。 “その他” 判定 “その他” を判定させるため、Google Chrome で User-Agent を変更せずアクセスしてみます。 ***.***.***.*** - - [17/Apr/2017:17:29:08 +0900] "GET / HTTP/1.1" 200 3770 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36" "***.***.***.***" Other 無事に末尾に “Other” が記録されました。 “iPhone” 判定 “iPhone” を判定させるため、Google Chrome で User-Agent を “iPhone 6 Plus” に変更し、アクセスしてみます。 ***.***.***.*** - - [17/Apr/2017:17:47:41 +0900] "GET / HTTP/1.1" 200 3770 "-" "Mozilla/5.0 (iPhone; CPU iPhone OS 9_1 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13B143 Safari/601.1" "***.***.***.***" iPhone 無事に末尾に “iPhone” が記録されました。 念のため、"iPhone 5" にしてアクセスしてみても、同じように末尾に “iPhone” が記録されました。 “iPad” 判定 “iPad” を判定させるため、Google Chrome で User-Agent を “iPad” に変更し、アクセスしてみます。 ***.***.***.*** - - [17/Apr/2017:17:55:54 +0900] "GET / HTTP/1.1" 200 3770 "-" "Mozilla/5.0 (iPad; CPU OS 9_1 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13B143 Safari/601.1" "***.***.***.***" iPad 無事に末尾に “iPad” が記録されました。 念のため、"iPad Pro" にしてアクセスしてみても、同じように末尾に “iPad” が記録されました。 “Android” 判定 最後に、"Android" を判定させるため、Google Chrome で User-Agent を “Galaxy S5” に変更し、アクセスしてみます。 ***.***.***.*** - - [17/Apr/2017:17:59:28 +0900] "GET / HTTP/1.1" 200 3770 "-" "Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Mobile Safari/537.36" "***.***.***.***" Android 無事に末尾に “Android” が記録されました。 念のため、"Nexus 6P" にしてアクセスしてみても、同じように末尾に “Android” が記録されました。 まとめ 今回は検証のため Header を全てオリジンサーバーへ渡しましたが、実際には CloudFront の設定でオリジンサーバーへ渡すヘッダを指定( “Forward Headers” の Whitelist で “Custom-Device” を指定 ) することで、指定した Header の値ごとにキャッシュを持たせることができるようになります。 また、今回は Lambda@Edge をデバイス判定に利用しましたが、他にもいろんな用途で利用できると思います。 まだ Preview なのが残念ですが、今後のシステム設計の参考になれば幸いです。
こんにちは、広告システム開発部の菅原です。 今回は構文解析のお話です。構文解析は、コンパイラ、自然言語処理(テキストマイニング)、AST(抽象構文木)、AltJS(Alternatives to Javascript) などのベースとなる重要な技術の一つです。本記事では、実例としてコンパイラを交えて説明しつつ、パーサコンビネータを使って簡単に構文解析を行えることを紹介していきます。 構文解析が使われている技術について 文字列が関わる様々な技術の根底に、構文解析は関与しています。構文解析がどのように関わっているか、その実例としてコンパイラの仕組みを簡単に見ていきます。 コンパイラ(Compiler)とは、人間が理解しやすい高級言語で記述されたプログラムを、ハードウェア寄りの低級言語(機械語もしくは中間言語)に変換するプログラム 1 です。 一言で「変換」と表記しましたが、このプロセスは、大きく分けて次の四つの機構から成ります。 字句解析(Lexical Analysis) ソースコードを読み込んで、トークン(字句)に分解する工程 構文解析(Syntactic Analysis) 分解したトークン列をもとに構文木を構築する工程 最適化(Optimization) 構築した構文木を効率の良いものに変換する工程 コード生成(Code Generation) 構文木からオブジェクトコードを生成する工程 構文解析と構文解析器 コンパイラの場合は、二番目の機構に構文解析が含まれています。構文解析を行う機構のことを「構文解析器(Parser)」と呼びます。後述するパーサジェネレータ(パーサコンビネータ)のライブラリの最近の実装では、字句解析器 2 と構文解析器の機構も併せて持っていることが珍しくなく、トークン分割から構文木構築までカバーできるものがほとんどです。 パーサジェネレータ(パーサコンビネータ) 構文解析器を生成するプログラムを「パーサジェネレータ(Parser Generator)」と言います。パーサジェネレータそのものは構文解析器ではありません。また、パーサジェネレータの中でも、演算子と関数を組み合わせて構文解析器を生成できるプログラムを「パーサコンビネータ(Parser Combinator)」と呼びます。 構文解析器の手続き 構文解析器では、構文木に対する手続きによって、トップダウン構文解析とボトムアップ構文解析の二つに大分類されます。トップダウン構文解析は、最上位の根から最下位の葉へ向かって書き換えていく手続きであり、一方のボトムアップ構文解析では、最下位の葉から最上位の根に向かって書き換えていく手続きです。 トップダウン構文解析とボトムアップ構文解析 トップダウン構文解析器とボトムアップ構文解析器には、主たる構文解析法が下表のように分類されます。 構文解析法 分類される解析器 LL parser Recursive descent parser Packrat parser トップダウン構文解析器 LR parser(SLR parser / LALR parser / GLR parser) Earley parser Cocke-Younger-Kasami parser ボトムアップ構文解析器 パーサコンビネータを使うための準備 構文解析の簡単な概要について把握できたところで、パーサコンビネータを使う準備を整えていきます。実行環境は、macOS Sierra 10.12.4 で、パッケージ管理システムは、 Homebrew を使い、コーディングには Sublime Text 3 を使う前提です。使用する言語は、パーサコンビネータのライブラリがあれば、どの言語でも構わないのですが、文字列操作に優れた Haskell を選択します。 Haskell のインストール まず始めに、Homebrew で Haskell(Glasgow Haskell Compiler) をインストールします。 $ brew install ghc Cabal-install のインストール 続いて、Haskell のパッケージ管理システム cabal をインストールします(Homebrew でのパッケージ名は、cabal-install になります)。 $ brew install cabal-install インストールが完了したら、cabal のパッケージリストの更新を行います。 $ cabal update パッケージリストが最新化されたところで、新しいバージョンの cabal-install をインストールします。 $ cabal install --global cabal-install hsdev のインストール cabal-install を更新した後、Sublime Text 3 上でコード補完等を行ってくれるパッケージ hsdev をインストールします。 $ cabal install hsdev SublimeHaskell の導入 Sublime Text 3 上で、Shift+Controll+P のショートカットキーを押してコマンドパレットを起動させます。この状態で「install」と入力すると、ドロップダウンの予測一覧が表示されますので、その中の「Package Control:Install Pacakge」の項目にフォーカスが当たった状態で「Enter」キーを押下します。すると、パッケージ検索パレットに切り替わるので、「SublimeHaskell」を検索してインストールします。 SublimeHaskell の設定 メニューバーから、Preferences > Package Settings > SublimeHaskell の順にメニューを辿って行き、「Settings - User」の項目を押下します。下記のように、hsdev のパスを記述し、Haskell のコマンドオプションを警告の表示・非表示を自分の好みで設定します。コマンドオプションについては、 こちら が参考になります。 { "enable_hdevtools": false, "auto_completion_popup": true, "add_to_PATH": [ "~/.cabal/bin/hsdev" ], "ghc_opts": [ "-fno-warn-type-defaults", "-fno-warn-missing-signatures", "-fno-warn-incomplete-patterns", "-fwarn-unused-binds" ] } パーサコンビネータを使ってみる Haskell には、Parsec という LL parse の非常に優れたパーサコンビネータライブラリがあります。このライブラリを使って四則演算ができる簡易計算機を作ってみようと思います。 インタラクティブモードでソースコードを実行する 簡易計算機を作る前にインタラクティブモードでソースコードを読み込んで実行する手順を示します。 Haskell でプログラミング入門恒例の hello world を書いて hello.hs というファイル名で保存します。 main = do print "hello world" このソースコードを保存した場所に cd コマンドで移動した後、ghci コマンドでインタラクティブモードにしてから、「:load hello.hs」でソースコードを読み込み、main で実行します。 $ ghci Prelude> :load helo.hs *Main> main "hello world" 数値を読み取れるようにする まずは、数値を読み取れるようにしてみます。1文字以上読み取りたいので many1 を、その上で数値を読み取るので digit の関数を組み合わせて使います。正規表現だと「[0-9]+」と同じようなイメージで考えてください。しかし、このままの構文解析器だと文字列出力になってしまうので、Int 型に変換します。 import Text.Parsec -- bind での書き方 number = many1 digit >>= \x -> return (read x :: Int) -- do で bind する書き方 number2 = do x <- many1 digit return (read x :: Int) main = do parseTest number "123" -- 123 parseTest number2 "456" -- 456 足し算をできるようにする 数値を読み取れるようになったので、今度は足し算をできるようにしてみましょう。足し算だけの計算式は、「a+b+…+z」の書式で表せることから、「数値」と「プラス記号と数値のセットの繰り返し」であることがわかるので、0回以上の繰り返しを表す many 関数と、数値を読み取るための自作関数 number を組み合わせます。そして、プラス記号が計算時には不要になるため、アプリカティブの「*>」(右辺のみ残す)を使用します。foldl 関数は、ここでは計算式の項を左から順に畳み込むために使用します。 import Text.Parsec import Control.Applicative ((*>)) add = do a <- number b <- many $ (char '+' *> number >>= \y -> return (+ y)) return $ foldl (\x f -> f x) a b number = do x <- many1 digit return (read x :: Int) main = do parseTest number "123" -- 123 parseTest add "1+2" -- 3 parseTest add "1+2+4" -- 7 引き算もできるようにする 足し算だけでなく引き算もできるようにしてみます。引き算そのものをできるようにするのは特に難しくありませんので、足し算と同じような内容で構文解析器を組んで行きます。ただし、Haskell の仕様上、マイナス記号が単項演算子として扱われてしまうため、subtract 関数を使うことで引き算を実現します。なお、足し算と引き算の演算優先度は同じなので、足し算と引き算の両方を演算できるようにするためには、選択「<|>」を使います。 import Text.Parsec import Control.Applicative ((*>)) expr = do a <- number b <- many $ (char '+' *> number >>= \y -> return (+ y)) <|> (char '-' *> number >>= \y -> return (subtract y)) return $ foldl (\x f -> f x) a b number = do x <- many1 digit return (read x :: Int) main = do parseTest number "123" -- 123 parseTest expr "1+2" -- 3 parseTest expr "3-1" -- 2 parseTest expr "2+3-1" -- 4 さらに掛け算と割り算もできるようにする 掛け算と割り算もできるようにしてみましょう。これらの計算は、足し算・引き算よりも演算の優先度が高いので、正しく計算するためには、同じ関数 expr の中で選択を使うことができません。そのため、掛け算と割り算用の関数 term を用意します。 import Text.Parsec import Control.Applicative ((*>)) eval a b = foldl (\x f -> f x) <$> a <*> b expr = eval term $ many $ (char '+' *> term >>= \y -> return (+ y)) <|> (char '-' *> term >>= \y -> return (subtract y)) term = eval number $ many $ (char '*' *> number >>= \y -> return (* y)) <|> (char '/' *> number >>= \y -> return (`div` y)) number = do x <- many1 digit return (read x :: Int) main = do parseTest number "123" -- 123 parseTest expr "2+4/2-1*3" -- 1 四則演算ができる簡易計算機として完成させる 四則演算ができるようになりましたが、これだけでは不完全です。計算式の空白があっても正しく計算できるように spaces 関数、アプリカティブを使いこなしてパーレン内の項を優先して計算できるようにした factor 関数を作ります。 import Text.Parsec import Control.Applicative ((<$>), (<*>), (*>), (<*)) eval a b = foldl (\x f -> f x) <$> a <*> b expr = eval term $ many $ (char '+' *> term >>= \y -> return (+ y)) <|> (char '-' *> term >>= \y -> return (subtract y)) term = eval factor $ many $ (char '*' *> factor >>= \y -> return (* y)) <|> (char '/' *> factor >>= \y -> return (`div` y)) factor = spaces *> (char '(' *> expr <* char ')' <|> number) <* spaces number = do x <- many1 digit return (read x :: Int) main = do parseTest number "123" parseTest expr "123" parseTest expr "1 + 20 + 300" -- 321 parseTest expr "100 - 20 - 3" -- 77 parseTest expr "3 * 60 + 4 / 2" -- 182 parseTest expr "3 * 30 - 50 / 2" -- 65 parseTest expr "3 * (60 + 4) / 2" -- 96 parseTest expr "3 * (30 - 50) / 2" -- -30 Haskell 以外の言語のパーサコンビネータ Haskell 以外のプログラミング言語のパーサコンビネータライブラリの一例を紹介します。なお、ここに挙げた以外もパーサコンビネータの実装はありますので、下表のライブラリが特別良いというわけではありません。 言語名 ライブラリ名とリンク PHP loco Javascript Esprima Python Parsec.py Ruby rsec Java jparsec Go goparsec Scala scala.util.parsing.combinator.Parsers Elixir Combine C# Sprache C++ boost Spirit Visual Basic VBParserCombinator 最後に たった数十行程度で四則演算が出来る計算機を作成できたことから、パーサコンビネータを使うと簡単に構文解析を行えることがわかるかと思います。本記事では紹介していませんが、この簡単さ具合で、テキストマイニングや抽象構文木などもより簡単にすることができます。 なお、本記事で作成した簡易計算機を発展させていくと、最終的には、プログラムのコンパイラにも、AltJS のように入力したソースコードから Javascript を出力するコンパイラにもすることができます。俺様プログラミング言語を作る夢があるなら、実現も不可能ではありません。 コンパイラはいくつかの種類があり、C 言語や C++ などの言語で、機械語へと直接一括コンパイルするコンパイラを「ネイティブコンパイラ」、C# や Java のなどの言語で、中間言語にコンパイルするコンパイラを「Ahead-Of-Time コンパイラ」と言います(中間言語から機械語にコンパイルするコンパイラは「Just-In-Time コンパイラ」と呼びます)。 ↩︎ 字句解析を行う機構を「字句解析器(Lexical Analyzer)」と呼びます。 ↩︎
フロントエンジニアの苅部です。 medibaシステム本部では一部サービスのA/BテストをGoogle Optimize(以下Optimize)で実施しております。 先日Optimizeの一般利用が可能になったようですので、これから初めてA/Bテストを実施する方に向けて、使用感を共有できたらと思います。 Optimizeの特徴 1. 無償版でも十分使える 無償版でもほとんどの機能が利用できるため、予算のないプロジェクトでもすぐに実践的なA/Bテストが開始できます。 実際にOptimizeを使ってA/Bテストを運用していますが、無償版で機能が制限されているというよりも、有償版にすることでGoogle Analyticsのオーディエンスデータを活用できる という印象を受けました。 ツールが評価できない段階で数十万円/月の予算を確保するのはなかなか難しいと思うので、いったん無償版で小さく始められる事は大きいと思います。 今のところ、ヒット数の制限もありません。(2017年5月現在) ・Optimize vs. Optimize 360 - Optimize Help ・Optimize - Free Beta Hit Limit - The Google Advertiser Community 2. 表示遅延を最小限に抑えられる JavaScriptを使って非同期的なA/Bテストを実施する場合には、表示速度へのマイナス影響を考慮する必要があります。 サードパーティーのJavaScriptを同期的に読み込む場合 DNSLookup, 3wayHandshake, Responseの流れが完了するまではレンダーツリーの構築をブロッキングすることになります。つまり描画がネットワークのレイテンシに左右されるため、RTTの長いモバイルネットワークでは顕著に体感速度に影響がでます。 この点で、Optimizeでは以下のような配慮がされています。 - 非同期読み込み JavaScriptを非同期に読み込み、テストデータの返却 or タイムアウトでHTMLの透過を解除する処理になるため、レンダーツリー構築への影響を最小限に抑えます。 - TCPコネクションの節約 Google Analyticsのプラグインのため、google-analytics.comと同一のTCPコネクションでテスト内容も返却することになります。 - JavaScriptの最適化 JavaScriptのレスポンスは動的生成されるため、返却される内容が最適化されています。 例えばテスト対象外のユーザーには、テストデータをレスポンスに含めることはないのでオーバーヘッドが最小限に抑えられています。 A/Bテストツールの体感速度へのマイナス影響を考えると、クリティカルレンダリングパスやネットワークでのボトルネックを減らせる事はメリットが大きいと思います。 実際に他のプラットフォームの同期Scriptと比較してみたところ、SpeedIndex値の中央値で10%ほどの差が確認できました。 (パフォーマンス計測では webpagetestを利用 ) ・オプティマイズとページの読み込み速度 - Optimize ヘルプ ・クリティカル レンダリング パスのパフォーマンスを分析する | Web | Google Developers 3. Google Analyticsの延長で利用できる - 汎用性や将来の拡張性 Google Analyticsを使いこなしている人は多く、Google Analyticsの延長で使えるOptimizeは、学習コストやコミュニケーションの面でメリットがあると感じています。 インターフェイスは他のGoogle製品とも統一されているため、慣れた操作感で扱いやすいです。 そして今後はDataStudioとの接続など、他のGoogle製品との連携も期待できるのではないでしょうか。 - 実装の手軽さ すでにGoogle Analyticsを利用している場合には新たなJS読み込みをしなくても、以下のような形でプラグインの指定を1行追加して任意でJavaScriptのスニペットを追加するだけでOptimizeと連携できます。 <script> (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) })(window,document,'script','https://www.google-analytics.com/analytics.js','ga'); ga('create', 'UA-NNNNNNNN-N', 'auto'); // 以下のrequireの1行を追加する ga('require', 'GTM-NNNNNN'); // ------------------------ ga('send', 'pageview'); </script> - イーコマース連携 Google Analytics側でイーコマース機能を使っている場合には追加設定なくコンバージョン連携できます。 テスト作成画面でのゴール設定では、TransactionとRevenueが選択できるようになります。 そしてレポート画面では個々のテスト結果をトランザクション・収益ベースで比較できます。 Google Analyticsとは異なりOptimizeではコンバージョンに統計モデルを利用していて、95%信頼区間・50%信頼区間・中央値が表示されます。 信頼区間は、どのくらいの確率でどのくらいの効果が見込まれるかが判断できるため、P値を使って帰無仮説を却下する検定方法よりも得られる情報が多いと思います。 A/Bテストの成果として重視されるのはクリック数が増加したことよりも[KPIに貢献できたかどうか]だと思っています。 そういう意味で、コンバージョンとしてトランザクション数や収益額を用いることで、より本質的なA/Bテストが可能になるのではないでしょうか。 ※ 備考 ・トランザクションをコンバージョンとして実施するテストで統計的に有意な差を出すためには、相当数のコンバージョンか、明らかに大きな差のあるテストを実施する必要があると思われます。 ・オプティマイズレポートとアナリティクスレポートの違い - Optimize ヘルプ - オーディエンスデータを利用したターゲティング(Optimize360) 有償版(Optimize360)に限定されますが、Google Analytics上でユーザーリストを作成して、そのユーザーリストを対象にターゲティングする事ができます。 例えば、 特定の商品カテゴリをよく見ている人 コンバージョンの回数が[N]の人 コンバージョンへの近さが[N]の人( セッションの品質 ) といったセグメントをGoogle Analyticsで作成することで、ユーザーごとに最適なバリエーションでテストできるようになります。 [どのバリエーションに割り振られたか]といった値も自動的にディメンションに入りますので、 バリエーションごとでセグメントを分けて深く分析できる ようになります。 つまりOptimize360とGoogle Analyics360を連携させることで、A/Bテストでのターゲティングと、レポーティングでのセグメンテーションをセットで考える事ができるようになります。 ツール導入やA/Bテスト運用のポイント 1. デザインの反映はフロントエンジニアで A/Bテストプラットフォームを利用する事のメリットに[スピード感]があると思いますが、デザイン反映にあたってはCSS/JavaScriptへの理解・動的な値/非同期処理への配慮、また実機での確実なテスト確認が求められます。 そのためデザイン実装はフロントエンジニアが行い、スピード感と共に安全性を担保する必要があります。 誰でも簡単に編集できるという事は、誰でも簡単に壊せるということでもありますので、細心の注意を払って作業した方がいいと思います。 2. A/Aテストを実施する テスト対象のページにおいては一度、[同じデザインパターンを複数]用意して2週間程度A/Aテストを実施することをオススメします。 例えばテスト対象が想定していない偏り方をすることもあると思いますし、ツールの問題で有意差が正しく判定できないかもしれません。 そのため、A/Aテストを実施して数値がどの程度の期間で平均に回帰していくか、数値の誤差がどの程度想定されるかを事前に把握できると良いと思います。 3. OptimizeはTagManagerからは配信しない TagManager経由でOptimize利用することも可能ですが、表示遅延の問題があるためGoogleは推奨していないようです。 例えばTagManager経由でanalytics.jsを配信する場合、 1) TagManagerへリクエスト 2) TagManagerからanalytics.jsのタグが返却 3) analytics.jsをリクエスト といった流れで1)〜2)がレイテンシとなり、描画に関わるOptimizeが遅延することで表示遅延につながります。 そのためTagManagerを経由せず、可能な限り早い段階(headタグ付近)でanaltyics.jsの記述を書いた方がよさそうです。 ・Serve Optimize via Google Tag Manager - Optimize Help 4. オリジナルデザインの描画に注意する A/Bテストのデータは非同期で渡されるため、レスポンスが遅延すると先にオリジナルのデザインが反映される事があります。 これを避けるため、Optimizeでは以下のようなPageHidingSnippetが用意されています。 <style scoped="scoped">.async-hide { opacity: 0 !important} </style><script>(function(a,s,y,n,c,h,i,d,e){s.className+=' '+y;h.start=1*new Date; h.end=i=function(){s.className=s.className.replace(RegExp(' ?'+y),'')}; (a[n]=a[n]||[]).hide=h;setTimeout(function(){i();h.end=null},c);h.timeout=c; })(window,document.documentElement,'async-hide','dataLayer',4000, {'GTM-XXXXXX':true});</script> このSnippetによってテスト返却されるまで or タイムアウトになるまではhtmlタグの透過度が100%となり、オリジナルの描画を防ぐ事ができます。 ※プログレッシブな描画ではなくなるめ、体感速度は若干落ちるかもしれません。 5. ツールの理解を深める Googleやリセラーと契約せず、無償版で利用する場合は自身で問題を解決するしかありません。 また社内からの問い合わせも集中してしまいますので、本格的に進める場合にはOptimizeやGoogle Analyticsの深い理解が必要になると思います。 Googleだけあってヘルプやフォーラムが充実していますので、大抵の問題は自身で解決する事はできると思います。 日本語でもヘルプが充実しています。 Help - Optimize ヘルプ 英語版ヘルプの方が情報が多いです。 Optimize Help フォーラムも用意されています。(英語) Google Optimize - The Google Advertiser Community YouTubeのGoogleAnalyticsアカウントにも動画が上がっています。(英語) Google Analytics - YouTube GoogleAnalyicsの学習にはGAIQ取得がオススメです。 Google アナリティクス個人認定資格(IQ)について OptimizeにはFeedBack機能もあるので、万が一問題が発生した場合にはこれを使ってスクリーンショット付きで報告するのも良いと思います。 6. データ分析力を身につける A/Bテストでは[勝ち/負け]といった言葉が使われる事がありますが、A/Bテストの結果はあくまで統計的な確率であり[100%の勝ち or 100%の負け]ではないと思っています。 ミスリードを防いだり説明責任を果たすためにも、基礎的な統計やデータ分析を理解して、ツールが出した数値を鵜呑みにせず、最終的に人が判断できる状態が望ましいと思います。 私自身はというと、Optimize・Google Analytics・有意差検定の説明はできますが、ベイズ統計の概要だったりOptimizeの統計モデルを把握できていないので、ざっくりでも理解していきたいなと。。 実際に導入・運用まで進めてみた感想 テストの運用までこぎ着けても、A/Bテストで成果を出すには多くの知見(技術/ノウハウ/文化)が必要だと感じています。 A/Bテストの実施を検討する場合にはツールありきではなく、KPIと目的を明確にして、そのために何が必要か考えてからツール選定すると良いと思います。 A/Bテスト自体は目的ではなく手段であって、KPIへ繋がる成果を出すことが目的ですし、グロースハックの文化を熟成していくことも大事です。 ただ細かい事を考えていても何も始まらないので、7割いけると思ったら、ツール先行でまずは走り出してしまうのがいいと思っています。 A/Bテストには[ツール導入/理解][データ分析力][成果]の3つの壁があると思っていて、1つめの壁は確実にOptimizeに優位性があると思います。 そしてGoogle Analyticsと連携できる無償のツールというところで、今A/Bテストを始めるならOptimizeをオススメしたいです。
みなさん、Golang書いてますか? お久しぶりです。メディアシステム開発部の森竹です。 前回は Docker for Macを使ってRuby on Rails開発環境を構築する を紹介させて頂きました。 今回はTravis CIを使用したGolangビルドを紹介させて頂きます。 ビルド Travis CI でGolangビルドを実行し、AWS S3へPUTします。ビルドサーバーでGolangビルドする案もありましたが、下記の観点を鑑み、Travis CIを選択しました。 Travis CI ビルドサーバー 運用コスト 月額固定( https://travis-ci.com/plans ) サーバー費用など 管理コスト 小(Golangバージョンアップは .travis.yml の変更のみ) 大(ビルドサーバー自体を管理する必要があり、Golangバージョンアップはサーバー全体に影響する) 成果物 AWS S3 ビルドサーバーのストレージなど .travis.yml は下記のように定義しています。 .travis.yml dist: trusty language: go go: - 1.7.5 env: global: - TZ=Asia/Tokyo before_install: - make glide # vendoringツールのGlideをインストールします - make awscli # aws-cliをインストールします install: - make deps # glide instalを実行し、パッケージをインストールします before_script: - make fmt # gofmtを実行します - make imports # goimportsを実行します - make lint # golintを実行します - make vet # go vetを実行します - make mysql # go testで使用するDBのcreate、migrateを実行します - make test # go testを実行します script: - make -f development.mk # Development環境のgo buildを実行します - make -f staging.mk # Staging環境のgo buildを実行します - make -f production.mk # Production環境のgo buildを実行します addons: apt: packages: - mysql-server-5.6 - mysql-client-core-5.6 - mysql-client-5.6 artifacts: s3_region: "ap-northeast-1" paths: - $(git ls-files -o --exclude-standard | tr "\n" ":") debug: true target-paths: $TRAVIS_REPO_SLUG/$TRAVIS_BRANCH bucket: "xxxxx-golang-artifacts" AWS S3へのPUTは、Travis CIの addons である travis-ci/artifacts を使用しています。上記のように定義すると、 s3://xxxxx-golang-artifacts/repository_fqdn/repository_path/branch/au_web_portal.production.linux.amd64.gz のような構成で成果物が配置されます。 AWS S3へのPUTには ARTIFACTS_KEY と ARTIFACTS_SECRET が必要です。こちらはTravis CIの Settings から Environment Variables に定義すると、 .travis.yml に定義する必要がありませんのでお勧めです。 基本的にコードフォーマット( gofmt 、 goimports )、静的解析( golint 、 go vet )、単体テスト( go test )が通らないとGolangビルド( go build )は実行されません。 Travis CIで実行するコマンドなどは Makefile に定義し、 .travis.yml から make する方針としました。 Production環境の go build を実行する Makefile は下記のように定義しました。 production.mk .PHONY: build clean NAME := au_web_portal ENV := production GOOS := linux GOARCH := amd64 HASH := $$(git rev-parse --verify HEAD) DATE := $$(date '+%Y/%m/%d %H:%M:%S %Z') GOVERSION = $$(go version) build: $(NAME).$(ENV).$(GOOS).$(GOARCH).gz $(NAME).$(ENV).$(GOOS).$(GOARCH): GOOS=$(GOOS) GOARCH=$(GOARCH) \ go build -tags=$(ENV) \ -o $(NAME) \ -ldflags "-X main.Hash=$(HASH) \ -X \"main.Date=$(DATE)\" \ -X \"main.GoVersion=$(GOVERSION)\"" \ ./au_web_portal mv $(NAME) $@ $(NAME).$(ENV).$(GOOS).$(GOARCH).gz: $(NAME).$(ENV).$(GOOS).$(GOARCH) gzip -f $< clean: rm -rf $(NAME).$(ENV).$(GOOS).$(GOARCH).gz rm -rf $(NAME).$(ENV).$(GOOS).$(GOARCH) 環境毎に異なる値は Build Constraints を使用し、各環境(Production、Staging、Development)の go build を実行します。 また ldflags オプションを指定し、Golangバイナリに情報を埋め込みます。開発段階ではGitHubへのpush後すぐ( go build が実行される前)にデプロイを実行し、変更が反映されていない状況があったりしました。そのような時に、Golangバイナリがどのような状態で動作しているのかを確認するのに便利です。 デプロイ デプロイは Capistrano を使用しました。AWS S3から各環境に対応したGolangバイナリを取得し、対象サーバーへデプロイします。 下記のようなコマンドを実行します。 $ bundle exec cap branch=master production deploy おわりに Travis CIを使用したGolangビルドを紹介させて頂きました。 今回はTravis CIが実行されると各環境(Production、Staging、Development)に対応したGolangビルドが実行されますが、GitHubブランチルールを策定し、各ブランチに対応した環境のGolangビルドを実行するようにしても良いかもしれません。 例) master ブランチの場合、Production環境のGolangビルドを実行する release/99999999 ブランチの場合、Staging環境のGolangビルドを実行する feature/xxxxxxxx ブランチの場合、Development環境のGolangビルドを実行する またGolangバイナリのAWS S3へのPUTですが、 aws-cli を使用し、下記のように実行しても良かったと思っています。 $ aws s3 cp au_web_portal.production.linux.amd64.gz s3://xxxxx-golang-artifacts/repository_fqdn/repository_path/branch/ 今後はGolangバージョンアップなど、継続的にアップデートや改善をして行きたいと思います。 参考 Travis CIを利用した継続的な成果物の配置
こんにちは、制作部 松本です。 私はこれまでCSSレイアウトで display プロパティ用いる際、 inline-block や table 、 最近では flexbox を使用してきました。 今でも float を多用している方はあまりいないのではないかと思います。 本記事では、よりフレキシブルなレイアウトが実現できる grid について紹介します。 grid は複雑なため、まだ体系的に全ての機能を説明できるほど私の理解も追いついていないので、今回はざっくりとした紹介となります。 対応ブラウザ 2017年4月現在、すでにほとんどのブラウザで grid が対応可能になっています。 残るはAndroid Browserのみ。 Can I Use CSS Grid Layout gridで何ができるのか 一言で言えば、 flexbox 、 table 同様にマルチカラムレイアウトすることができるプロパティです。 flexbox が一次元的なレイアウトなのに対して、 grid は二次元的なレイアウトが可能(らしい)です。 gridの概要 グリッドコンテナ display:grid; を指定された要素。 グリッドアイテム display:grid; を指定された要素の直下の子要素。 孫要素はグリッドアイテムとみなされない。 グリッドトラック grid の column(列) と row(行) の総称。 html の table タグをイメージしてもらうとわかりやすいです。 グリッドライン グリッドトラック間の線。 グリッドセル グリッドラインによって分けられた個々のスペース。 グリッドエリア 複数のセルをまとめたグリッド内の特定の領域。 gridのプロパティ グリッドコンテナのプロパティ grid 要素をグリッドコンテナとして定義する。 grid-template-columns グリッドトラック(列)のサイズを指定する。 grid-template-rows グリッドトラック(行)のサイズを指定する。 grid-template-areas グリッドエリアの名前を参照して、グリッドテンプレートを定義する。 grid-template grid-template-columns 、 grid-template-rows 、 grid-template-areas を指定できるショートハンド。 grid-column-gap グリッドトラック(列)の間を指定する。 grid-row-gap グリッドトラック(行)の間を指定する。 grid-gap grid-column-gap 、 grid-row-gap を指定できるショートハンド。 justify-items グリッドアイテムの行方向の整列。 align-items グリッドアイテムの列方向の整列。 justify-content グリッドトラックの行方向の整列。 align-content グリッドトラックの列方向の整列。 grid-auto-columns 自動的に生成されたグリッドトラックのサイズを指定する。 grid-auto-rows 自動的に生成されたグリッドトラックのサイズを指定する。 grid-auto-flow 明示的に配置されていないグリッドアイテムの配置を指定する。 グリッドアイテムのプロパティ grid-column-start グリッドアイテム(列)の開始位置を指定する。 grid-column-end グリッドアイテム(列)の終了位置を指定する。 grid-row-start グリッドアイテム(行)の開始位置を指定する。 grid-row-end グリッドアイテム(行)の終了位置を指定する。 grid-column grid-column-start 、 grid-column-end を指定できるショートハンド。 grid-row grid-row-start 、 grid-row-end を指定できるショートハンド。 grid-area グリッドセルに名前を付けて grid-template-areas プロパティで参照できるようにする。 grid-column 、 grid-row のショートハンドでもある。 justify-self グリッドアイテム内のコンテンツを行方向に整列する。 align-self グリッドアイテム内のコンテンツを列方向に整列する。 サンプル 上図のようなレイアウトを実現する場合、HTML/CSSの構造はとても単純です。 HTML <div class="container"> <div class="item item-a">A</div> <div class="item item-b">B</div> <div class="item item-c">C</div> <div class="item item-d">D</div> <div class="item item-e">E</div> <div class="item item-f">F</div> </div> CSS .container { display: grid; grid-template-columns: 100px 100px 100px; grid-template-rows: 100px 100px; grid-gap: 10px; color: #444; } .item { background: #bbb; color: #fff; padding: 10px; font-size: 150%; } .item:nth-child(odd) { background: #333; } このコードを図解すると↓のようになります。 grid-template-columns: 100px 100px 100px; grid-template-rows: 100px 100px; この記述は嚙み砕くと、 width100pxのグリッドアイテムを3列 height100pxのグリッドアイテムを2行 ということです。 ちなみにショートハンドで書くとこんな感じです。 grid-template:100px 100px / 100px 100px 100px; grid-template-columns を以下のように変更します。 grid-template-columns: 100px 100px 100px 100px; すると width100pxのグリッドアイテムを4列 になるので、Dのグリッドアイテムが1行目に移動します。 グリッドアイテムの数が多い場合、 repeat を使うと便利です。 grid-template: repeat(5, 100px) / repeat(5, 100px); これは↓と同じ意味です。 grid-template-columns: 100px 100px 100px 100px 100px; grid-template-rows: 100px 100px 100px 100px 100px; Aのグリッドアイテムだけ、横幅を広げたい場合、 .item-a { grid-column:span 4; } これは table タグの colspan="4" と同様の動きをします。 このとき、Fのグリッドアイテムの height が100px以下になっていますが、 それは grid-template-rows: 100px 100px; の記述で、2行目までしか height を指定していないためです。 次に、 grid でもっとも重要と思われる grid-area について説明します。 この機能を使うためには、まず テンプレートに定義したい要素 に名前をつけます。 .item-a { grid-area: area-a; /* .item-aにarea-aという名前をつける */ } .item-b { grid-area: area-b; } .item-c { grid-area: area-c; } .item-d { grid-area: area-d; } .item-e { grid-area: area-e; } .item-f { grid-area: area-f; } グリッドコンテナにテンプレートを定義します。 .container { display: grid; grid-template-columns: 100px 100px 100px; grid-template-rows: 100px 100px; grid-template-areas:"area-a area-b area-c" "area-d area-e area-f"; grid-gap: 10px; color: #444; } grid-template-areas の値はそのままグリッドアイテムの並びと連動します。 ・グリッドアイテムの順番を変える場合 grid-template-areas:"area-e area-c area-b" "area-f area-a area-d"; ・グリッドアイテムの要素を広げる場合 grid-template-areas:"area-e area-c area-c" "area-e area-a area-f" "area-b area-d area-d"; ・グリッドエリア内に空白部分が欲しい場合 grid-template-areas:"area-e area-c ......" "area-e area-a area-f" "area-b area-d area-d"; 視認性を考え他のエリア名と文字数を揃えていますが、「.」はひとつでも構いません。 ・複雑なレイアウトの場合 grid-template-areas:"area-a ...... ...... area-c" "...... area-e area-f area-c" "area-b area-b area-f area-c" "...... area-d ...... area-c"; こんな面倒くさそうなレイアウトもとても簡単です! grid特有の単位 グリッドアイテムのサイズには px 、 em 、 rem 以外に fr(flex fraction) が指定できます。 grid-template-columns: 200px 2fr 1fr; grid-template-rows: 200px 200px; fr は、グリッドアイテムの配置可能な領域を占める割合を表します。 flexbox の flex-grow と同様の動きをすると考えるとわかりやすいです。 2fr は 1fr の2倍の幅になります。 まとめ ざっくりとした紹介にはなりましたが、いかがでしたでしょうか? 恐らく grid の機能の10分の1程度しか紹介できていませんが、もっと色々な機能を使いこなせるようになれば最強の武器になると思います。 Androidでも対応可能になるその日までに、少しでも理解を進めておいて損はないのではないでしょうか!
こんにちは、お久しぶりです。mediba広告システム開発部の原です。 前回はpython+TensorFlowで画像から顔認識と分類をする簡単なモデルについて書きました。 機械学習で芸能人の顔を分類してみよう! で、今回ですが、やっぱり流行りのアレ。 流行ってますよね、pix2pix! ということで、pix2pixを使うのに必要な学習素材を動画から簡単に作れますよ、今すぐ始められますよ、という内容です。 開発環境 最初に環境の話です。 本記事の作成・検証環境は以下のとおりです。 Mac OS X 10.12.3 (10.10.5とかでも動作すること確認済み) Python 2.7.10 OpenCV2 2.4.12 概要 pix2pix pix2pixとは、簡単には画像と画像の間の関係性/2画像間の変換パターンの特徴を学習、DNNで表現してしまおう、というプログラムです。 例えば地図から航空写真に変換するための学習を行い、架空の町並みを作ってみたり、線画からカラー画像を作ったりすることができます。 GAN (Generative Adversarial Network)という仕組みを基にしているわけですが、画像から画像への変換という仕組みは数あれど、pix2pixはとにかく簡単に始められて、しかも精度がすごい、と最近話題になってます。 参考: pix2pix 作る学習モデルについて 今回はシンプルに、モノクロ画像を彩色するというモデルを作ってみようと思います。 pix2pixでの学習に用いるのは変換前後それぞれ256×256ピクセルの画像を左右に結合した、512×256の画像(便宜上、これを素材タイルと呼びます)になります。 今回は、弊社メディアである Z TOKYO のプロモーション動画から、学習に必要な素材タイルを作ってみたいと思います。 素材タイル作成 元動画について 今回、素材に用いる動画のスペックは下記のとおりです。 MP4フォーマット 1920 × 1080 30FPS 60sec 動画を読み込み1フレームずつ画像として処理 さて、上記の動画を1フレームずつ処理すれば、1920 × 1080の画像が約1800枚出来ることになります。 ※実際には60秒を少し越えていたので、1875フレームありました これでは多すぎるので、あとで適当な枚数に減らすとして、まずはこの操作を書いていきます。 また、今回は彩色モデルを作るので、併せて画像をグレースケールに変換します。 #coding=utf-8 import cv2 # 入力する動画パスを指定 cap = cv2.VideoCapture("sample.mp4") counter = 0 while(cap.isOpened()): ret, frame = cap.read() if ret == True : counter = counter + 1 gray2 = cv2.cvtColor(frame, cv2.COLOR_RGB2GRAY) # 変換するとカラー情報が落ちるので、一度ファイルに保存して開き直す cv2.imwrite('./gray.png', gray2) gray = cv2.imread('./gray.png') print(frame.shape) print(gray2.shape) print(gray.shape) if ret == False and counter != 0 : break cap.release() cv2.destroyAllWindows() これだけです。 デバッグも兼ねて各フレームのshape(高さ、幅、チャネル数)を表示しています。 これを実行すると、 (1080, 1920, 3) (1080, 1920) (1080, 1920, 3) という表示がフレーム数分表示されます。 上から元のフレーム/グレースケール変換/変換後のファイルをファイルから読み直したものの、それぞれのshapeになります。 単純なグレースケール変換ではチャネル数のデータが落ちていることがわかると思います。 あとで素材タイルに結合する際に、データ形式が違うと実行エラーが発生してしまいますので、ここで一旦ファイル保存を経由してデータ形式を揃えています(もっと効率がいい方法があるかもしれませんので、ご存じの方はこっそりおしえてください) 各画像から一部を切り出してタイルに結合 さて、上記処理で作ったframeおよびgrayは、どちらも幅1920×高さ1080の画像になっています。 素材タイルは256pix × 256pixの正方形なので次の図のように位置を指定して切り出していくことにします。 frameおよびgrayは色を除けば同じ画像ですので、それぞれから同じ位置の画像を切り抜けば、彩色有無だけが違った正方形の画像データを取り出すことが出来ます。 このやり方であれば、1フレームごとに28枚の画像を切り出すことが出来ます。 pix2pixの標準トレーニングデータは全体で600枚くらいですので、そのくらいのタイルセットが取れればとりあえずは十分だと思います。 今回は特にデータのスクリーニングを行いませんが、例えばほとんど一色のタイルなどは学習時にゴミになりえます。 そこで、この時点では少し多めにデータを用意することにして、70フレームに一度、この処理を行うことにしました。 (1800 / 70 ) * 28 = 720枚のタイルセットが出来ることになります。 pythonで書くとこうなります。 # 切り出す画像の縦横幅定義 height = 256 width = 256 # 切り出す位置の初期定義 defX = 28 # 縦位置(pixel)初期値 defY = 64 # 横位置(pixel)初期値 # 縦方向ループ for numX in range(4): # 横方向ループ for numY in range(7): cutX = defX + (numX * height) cutY = defY + (numY * width) cutImg = frame[cutX:cutX+256, cutY:cutY+256] cutGray = gray[cutX:cutX+256, cutY:cutY+256] train/test/valに画像振り分け さて、720枚のデータセットができたところで、最後に素材タイルへと結合していきます。 また、pix2pix標準のデータセット600枚は以下のような内訳になっています。 train(学習用データ)400枚程度 test(テスト用データ)100枚程度 val(モデルの検証用データ)100枚程度 これに併せてtrain:test:valを4:1:1になるようにランダムで振り分ける仕組みもついでに実装すれば、動画から素材タイルを作り出すプログラムの完成です。 で、これまで書いたものとあわせて出来上がったものがこちら。 #coding=utf-8 import cv2 from numpy.random import * # 入力する動画パスを指定 cap = cv2.VideoCapture("sample.mp4") counter = 0 dataset_counter = 0 while(cap.isOpened()): ret, frame = cap.read() if ret == True : counter = counter + 1 if counter % 70 == 0 : #70フレームに一度画像処理を行う # グレースケールに変換 gray2 = cv2.cvtColor(frame, cv2.COLOR_RGB2GRAY) # 変換するとカラー情報が落ちるので、一度ファイルに保存して開き直す cv2.imwrite('./gray.png', gray2) gray = cv2.imread('./gray.png') # 切り出す位置の初期定義 defX = 28 # 縦位置(pixel)初期値 defY = 64 # 横位置(pixel)初期値 # 切り出す画像の縦横幅定義 height = 256 width = 256 # 縦方向ループ for numX in range(4): # 横方向ループ for numY in range(7): dataset_counter = dataset_counter+1 cutX = defX + (numX * height) cutY = defY + (numY * width) cutImg = frame[cutX:cutX+256, cutY:cutY+256] cutGray = gray[cutX:cutX+256, cutY:cutY+256] # 画像の結合 imgAdd = cv2.hconcat([cutImg, cutGray]) # train:test:val = 4:1:1になるように保存ディレクトリ振り分け key = = rand() * 6 if key < 4: saveDir = 'train' elif key < 5: saveDir = 'test' else: saveDir = 'val' cv2.imwrite('./datasets/sample/%s/%d.png' % (saveDir, dataset_counter), imgAdd) if ret == False and counter != 0 : break cap.release() cv2.destroyAllWindows() これで720枚の素材を振り分けることが出来ました。 学習・結果評価 データ移動 作成したトレーニングデータをpix2pixのdatasets以下に移動します。 $ cp -a datasets/sample ~/pix2pix/datasets/movie トレーニング 移動したデータを下にpix2pixのトレーニングを実施します。 $ DATA_ROOT=./datasets/movie \ name=movieDump2 which_direction=BtoA \ gpu=0 cudnn=0 batchSize=20 \ save_epoch_freq=5 save_latest_freq=10 \ continue_train=0 niter=10 th train.lua パラメータについて以下の設定をしています。 gpu=0 cudnn=0:GPUおよびCUDNNを利用しない save_epoch_freq=5:5回学習毎にモデルを保存する save_latest_freq=10:10回回学習毎に「最新学習モデル」を保存する continue_train=0:前回の「最新学習モデル」を利用して継続学習する ※「最新学習モデル」がないと利用できない niter=10:学習ループ回数定義(デフォルトは200回) pix2pixによる彩色の実施 最後に、ここで作成したモデルで彩色を実施します。 $ DATA_ROOT=./datasets/movie \ name=movieDump2 which_direction=BtoA \ phase=val gpu=0 cudnn=0 th test.lua ここでもGPUおよびCUDNNは利用しません。 評価 実行した結果がこちらになります。 左:グレースケール変換した画像 中:pix2pixが機械的に彩色した画像 右:元動画から切り出した(正解の)画像 今回は10回ループでしたが、これでも結構学習してくれているのがわかると思います。 一方で、 こんな状況になっている画像もありました。 幾つか原因はあると思うのですが、 元動画には上下に黒帯が入っているのに除去しなかったこと 機械的に学習タイルを作成したので、元のタイルが全体的にマットで均一色彩の場合はほぼ効果が出ない(むしろじゃまになりかねない) ということなどが考えられます。 これらは切り出す位置を工夫するとか、ヒストグラムを作って均一な色合いのタイルを予め除去しておくなどの対応が取れると思います。 せっかく多めの素材タイル用意したんだからやっておけよ、という話 このように課題も見つかりましたが、簡単にpix2pixの学習素材が作れることはわかっていただけたかと思います。 それにしても、GANすごいなあ…… まとめ GANすごい GPUない環境でやるもんじゃない Z TOKYO 見に来てね!
こんにちは、制作部デザイナーの福島です。 大規模な案件になると、ヘッダーやフッターなどの共通パーツをチームのメンバーと共有して制作を進めたい、というときがありますよね。 そこで、今回Creative Cloudライブラリという、Adobe Creative Cloudの制作ツールに搭載されている機能を使ってみました。 実際に使ってみて、チームでデザイン作業を進めるのにとても役立つ機能であると感じました。 今回は、チームでデザインを進める際に役立つCreative Cloudライブラリの機能を中心に紹介したいと思います。 Creative Cloudライブラリとは アセットをクラウド上に登録して使用できるサービスで、使用するにはAdobe Creative Cloudのアカウントが必要です。 Photoshop / Illustrator / InDesign / After EffectsなどAdobe Creative Cloud制作ツールで使用する事ができ、画像やパスなどのグラフィック/ カラー情報/文字スタイル/レイヤースタイルなど、様々なアセットを登録することができます。 ライブラリに登録されたアセットは、登録したAdobe Creative Cloud制作ツール以外からでも使用可能です。 (例えばPhotoshopで登録したアセットを、Illustratorでも使用する…といったような使い方もできます) ライブラリフォルダを作成する では実際にライブラリを作成してみましょう。 今回はPhotoshopを例に紹介していきたいと思います。 ウインドウ→ライブラリからライブラリパネルを開き、右上のメニューから「新規ライブラリを」選択して、任意の名前をつけます。 作成したライブラリフォルダにアセットを登録していきます。 Photoshopで登録できるアセットは、グラフィック / 文字スタイル / レイヤースタイル / カラー情報です。 アクティブなドキュメント内のアセットを選択すると、ライブラリパネル下部のアセット追加アイコンに選択したアセットが表示さます。 アセット追加アイコンをクリックすると、ライブラリ内にアセットを追加することができます。 パスや画像などのグラフィックは、ライブラリパネルに直接ドラッグ&ドロップして追加することもできます。 ライブラリフォルダをチーム内で共有する 作成したライブラリフォルダをチームメンバーに共有してみましょう。 ライブラリパネル右上のメニューから共同利用を選択すると、共同利用者を追加できるポップアップウィンドウが開きます。 共同利用者を招待し、共同利用者が招待を認証すると、共同利用者のライブラリに共有したライブラリフォルダが表示されます。 これでアセットの共同利用ができるようになりました。 共同利用者は必要なアセットを、共通ライブラリから使用したり登録できるようになります。 ※うまく共有がされない場合は、ライブラリパネル右下にあるAdobe Creative Cloudロゴをクリックするか、Adobe Creative Cloudアプリケーションが起動しているかを確認してみてください。 Creative Cloudライブラリを便利に使いこなす リンクされたアセットを使用して、データを常に最新状態に! Creative Cloudライブラリ内のグラフィックなどは、自動でリンクアセットになります。 リンクアセットになっていると、ファイルの変更を行った場合、リンクアセットを使用している全てのプロジェクトファイルが、更新された最新ファイルに置き換わります。 様々なプロジェクトファイルで共通して使用するパーツは、リンクアセットとして登録しておくと便利です。 ここで注意する事は、更新されたリンクアセットは編集しているファイル以外は自動的に更新されません。 他のファイルで更新されたリンクアセットを見ると、レイヤーに三角の黄色いアイコンがついています。 このマークがついていると、リンクアセットが最新ではないという事なので、ウインドウ→属性パネルを開いて、同じ三角のアイコンマークをクリックし「変更されたコンテンツを更新」を選択します。 するとリンクアセットが更新されて最新のデータになります。 レイヤーカンプ機能と合わせて差分を含むデザインパーツも共有 Photoshopの「レイヤーカンプ機能」と合わせて使用すると、差分を含むデータも一つのリンクアセットとして使うことができます。 レイヤーカンプとはレイヤーの形状を記憶しておける機能なのですが、リンクアセットと組み合わせて使うととても便利な機能です。 参考: レイヤーカンプ 《例》ラベルの種類を一つのリンクアセット内に複数増やしてみる まずはリンクアセットとして登録してある「ラベル」をダブルクリックし編集します。 そして「コラム」と「無料」2種類のラベルを、それぞれレイヤーに制作しておきます。 ウインドウ→レイヤーカンプパネルを開き、新規レイヤーカンプから「コラム」のレイヤーカンプを作成します。 レイヤーの「コラム」を表示、「無料」を非表示にし、「コラム」のレイヤーカンプがカレントになっていることを確認して、レイヤーカンプパネル下部の「レイヤーカンプを更新」アイコンを押します。 すると「コラム」のレイヤーカンプにレイヤー「コラム」の形状が記憶されました。 同様に「無料」のレイヤーカンプも作成し、リンクアセットを保存します。 元のファイルに戻り、ウインドウ→属性パネルを開くと、先ほど作成したレイヤーカンプが選択できるようになっています。 これでラベルの種類を一つのリンクアセット内で簡単に切り替えられるようになります。 このように、一つのリンクアセットで何種類もの差分を含むデータを作成する事ができるので、アセットを無駄に増やす事なく管理できます。 アイコンや文字など、一部のデザインパーツをいくつかの種類に変更したい時などに活躍できる技術かと思います。 Creative Cloudライブラリを使ってみた感想 実際の仕事ではヘッダーフッターなど共通パーツを登録しておくことで、チーム間で一貫性のあるデザインデータをスムーズに制作し、チームメンバーに共有する事ができました。 また、デザインがある程度出来上がった後に修正が入る事がしばしばあったのですが、Creative Cloudライブラリに登録しておけば、パーツは常に最新の状態で更新や共有ができます。 共通パーツがデザインデータによって古くなるといった事も、Creative Cloudライブラリで簡単に解決できるようになりました。 Adobe Creative Cloudアカウントがないと共有ができなので、同じ環境でない人と連携するには向いてないと思うのですが、Adobe Creative Cloudを使用しているのであればデザインデータをチーム内で管理するには本当にオススメな機能なので、みなさんもぜひお試しください!
こんにちは、インフラストラクチャー部の沼沢です。 今回は collectd を使って php の OPcache の情報を CloudWatch に連携する具体例をご紹介したいと思います。 関連記事: nginx の各種情報を collectd を使って CloudWatch に連携する php-fpm のステータス情報を collectd を使って CloudWatch に連携する 前提 Amazon Linux AMI release 2016.09 collectd 5.4.1 php 関連 5.6.28 nginx 1.10.1 jq 1.5 なお、collectd については以下と同等の状態ができあがっているという前提で進めます。 新しい collectd の CloudWatch プラグイン | Amazon Web Services ブログ collectdのCloudWatchプラグインを試してみた | Developers.IO opcache_get_status の値を CloudWatch に連携 opcache_get_status とは、OPcache のキャッシュヒット率などのステータス情報を取得するための関数です。 詳細は PHP: opcache_get_status - Manual をご確認ください。 ローカルから curl http://localhost/opcache.php を実行できるようにするため、DocumentRoot 直下に以下の内容の opcache.php を配置します。 <?=json_encode(opcache_get_status(false)) ?> curl http://localhost/opcache.php を実行すると以下のような json が返ってくる状態になっているとします。 $ curl -s http://localhost/opcache.php | jq { "opcache_enabled": true, "cache_full": false, "restart_pending": false, "restart_in_progress": false, "memory_usage": { "used_memory": 10936200, "free_memory": 123281528, "wasted_memory": 0, "current_wasted_percentage": 0 }, "interned_strings_usage": { "buffer_size": 8388608, "used_memory": 438304, "free_memory": 7950304, "number_of_strings": 4822 }, "opcache_statistics": { "num_cached_scripts": 1, "num_cached_keys": 1, "max_cached_keys": 7963, "hits": 21, "start_time": 1485163987, "last_restart_time": 0, "oom_restarts": 0, "hash_restarts": 0, "manual_restarts": 0, "misses": 1, "blacklist_misses": 0, "blacklist_miss_ratio": 0, "opcache_hit_rate": 95.454545454545 } } URL を叩いて返ってきたレスポンスから情報を取得したい場合には、 curl プラグイン を利用しましたが、今回は json なので curl_json プラグイン の登場です。 Plugin:cURL-JSON - collectd Wiki …と言いたいところですが、curl_json プラグインはうまく動かすことができなかったので、今回は exec プラグイン を利用して、curl と jq の合わせ技で実現したいと思います。 exec プラグインは、collectd が外部スクリプトを実行して、そのスクリプトが決まったフォーマットで標準出力した内容を拾ってくれるというものです。 Plugin:Exec - collectd Wiki こちらも参考にさせていただきました。 collectd の exec プラグインで俺のデータを CloudWatch に飛ばす - ようへいの日々精進XP exec プラグイン を利用して、この json の中から、memory_usage に関する3つの項目 (used, free, wasted) を CloudWatch に連携していきます。 1. 外部スクリプトの作成 exec プラグインでは前述の通り外部スクリプトを実行して値を取得するため、そのスクリプトを用意します。 今回は、 http://localhost/opcache.php のレスポンスの json から情報を取得するため、 curl で実行結果を受け取って jq で値を抽出するスクリプトを用意します。 今回用意したスクリプトは以下です。 このスクリプトを /usr/lib/collectd/opcache_stat.sh に配置します。 #!/bin/sh HOSTNAME="${COLLECTD_HOSTNAME:-localhost}" INTERVAL="${COLLECTD_INTERVAL:-60}" while sleep "${INTERVAL}"; do opcache_stat=`curl -s http://localhost/opcache.php` used_memory=`echo ${opcache_stat} | jq -r '.memory_usage.used_memory'` free_memory=`echo ${opcache_stat} | jq -r '.memory_usage.free_memory'` wasted_memory=`echo ${opcache_stat} | jq -r '.memory_usage.wasted_memory'` echo "PUTVAL \"${HOSTNAME}/exec-opcache_stat/memory-used_memory\" interval=${INTERVAL} N:${used_memory}" echo "PUTVAL \"${HOSTNAME}/exec-opcache_stat/memory-free_memory\" interval=${INTERVAL} N:${free_memory}" echo "PUTVAL \"${HOSTNAME}/exec-opcache_stat/memory-wasted_memory\" interval=${INTERVAL} N:${wasted_memory}" done このスクリプトでは、${INTERVAL} 秒毎に、 http://localhost/opcache.php のレスポンスを受け取り、以下のフォーマットに従って標準出力への echo を繰り返します。 PUTVAL "{Host 名}/{Plugin 名}-{PluginInstance 名}/{Type}-{Metrics 名}" interval={インターバル(秒)} N:{計測値} PUTVAL (恐らく)フォーマットに従って標準出力した値を collectd が拾うための接頭辞だと思います 詳細はこちら → Plain text protocol - collectd Wiki#PUTVAL Host 名 その通り、ホスト名です。 /opt/collectd-plugins/cloudwatch/config/plugin.conf に設定している host と同じもの(=${COLLECTD_HOSTNAME} の値)を指定すると良い感じになります Plugin 名 このプラグインの名前を指定します 今回は exec プラグインを使用しているので exec を指定していますが、任意の名前で構いません(例えば “opcache” 等) PluginInstance 名 CloudWatch のメトリクスの画面で表示される PluginInstance に表示される名前 大項目を Host 名とした場合、PluginInstance は中項目のようなもの 後で出てくる画像を見ていただければどこに出てくるものかわかると思います 任意の値で構いませんので、識別しやすい名前を付けると良いと思います Type php-fpm のステータス情報を collectd を使って CloudWatch に連携する にも出てきましたが、 types.db にあるものの中から指定します types.db については以下を参照 types.db(5) – collectd – The system statistics collection daemon Metrics 名 PluginInstance 名と同じようなものです 小項目という扱いがわかりやすいかと思います インターバル(秒) 何秒間隔で値を取っているか、ということを明示するために指定します N:{計測値} “N” のところは、Unixtime 形式のタイムスタンプを指定するのですが、"N" を指定すると現在時刻という意味の指定になります 計測値には該当する値を指定します 2. collectd の設定ファイル (/etc/collectd.conf) に以下の設定をする 以下の設定ファイルを /etc/collectd.d/opcache.conf に配置する LoadPlugin exec <Plugin exec> Exec nginx "/usr/lib/collectd/opcache_stat.sh" </Plugin> Exec {スクリプト実行ユーザ} "{スクリプトの場所}" という指定の仕方をします。 対象のスクリプトを実行できるユーザなら何を指定しても良いですが、 root は指定できませんのでご注意ください。 今回は php を nginx で動かしているので、nginx ユーザでこのスクリプトを実施するように指定しています。 3. 設定後、collectd を再起動 $ sudo service collectd restart 4. blocked_metrics に以下のものが追加されていることを確認する $ cat /opt/collectd-plugins/cloudwatch/config/blocked_metrics 〜略〜 exec-opcache_stat-memory-used_memory exec-opcache_stat-memory-free_memory exec-opcache_stat-memory-wasted_memory 〜略〜 5. CloudWatch に送る対象として、上記をホワイトリストに追記する $ sudo sh -c 'echo "exec-opcache_stat-.*" >> /opt/collectd-plugins/cloudwatch/config/whitelist.conf' 6. 設定後、collectd を再起動 $ sudo service collectd restart 7. blocked_metrics から追加されたものが消えているのを確認する $ cat /opt/collectd-plugins/cloudwatch/config/blocked_metrics 8. CloudWatch 上でグラフ化されていることを確認する しばらく待つと CloudWatch に以下の通り OPcache の メトリクスが追加されます。 まとめ 今回は php の OPcache のステータス情報を、exec プラグインを利用して CloudWatch に連携してみましたが、exec プラグインを利用すればどんな値でも連携可能ということになるかと思います。 今回のように、利用しようとしたプラグインでうまくいかない場合や、対応しているプラグインが無い場合などに利用していきましょう。