こんにちは、VASILYのバックエンドエンジニアの塩崎です。 iQONの中ではクローラーと検索サーバーを担当しています。 iQONのクローラーには提携ECサイトさんからクロールした商品を商品カテゴリー(Tシャツ、ワンピース、etc.)に自動的に分類する機能があり、商品タイトルや商品説明文などのテキスト情報を元に分類を行っています。 しかし、一部のカテゴリー(セーター・ニット帽)の商品はテキスト情報だけからでは精度の良い分類を行うことができません。 そのため、これらのカテゴリーの商品については画像を用いたカテゴリー分類を導入しました。 これらの機能を実現するために、当社のデータサイエンスチームとも協力を行い、ディープラーニングを用いたカテゴリー判定器を開発しました。 また、この機能は既存のクローラーの機能からの独立性が高いので、クローラーに組み込むときにはマイクロサービス化をして組み込みました。 その結果、カテゴリ判定の精度として、セーターカテゴリーでは99.7%、ニット帽カテゴリーでは99.8%という結果を得ることができました。 背景 iQONのクローラーは提携ECサイトさんから毎日数百万商品のクロールを行っており、毎日数千〜数万商品がiQONに新たに追加されます。 これらの数の商品のカテゴリー分類を人間が行うことは大変な労力を要するため、iQONのクローラーには商品カテゴリーの分類を自動的に行う機能があります。 商品タイトルや商品説明文といったテキスト情報に対して形態素解析を行い、文脈情報を考慮した上で候補となる単語を1つに絞り込み、その単語に対して辞書マッチを行うことでカテゴリー分類を行います。 その結果、精度98%での商品カテゴリー分類を実現しました。 しかし、セーター(iQONでのカテゴリ分類ではトップス・ニットと表記されるが、紛らわしいのでこの記事ではセーターと表記)とニット帽の判定をテキストだけから精度良く行うことは困難です。 これらの商品のタイトル、説明文には「ニット」という表記しかされていない場合が多いからです。 なぜこのような曖昧な表記になってしまっているかというと、人間が商品情報を見るときには画像とテキストを同時に見て判断をするために、このような表記でも通常のECサイトには十分なためだと思います。 この問題を解決するためには、商品画像を用いたカテゴリー判定が必要になります。 画像からカテゴリー判定する手法の検討 近年、画像判定の分野では畳み込みニューラルネットワーク(CNN)が注目されていますが、この問題に対してもCNNが有効であるかを確認するために、以下に列挙された3つの手法との比較を行いました。 多層パーセプトロン (MLP) HOG特徴量 + k近傍法 (HOG + kNN) HOG特徴量 + サポートベクターマシン (HOG + SVM) これら合計4つの手法を用いて、セーターとニット帽の分類を行った結果を以下の表に示します。 なお、この検証では速度を優先してRGB画像をグレースケール画像に変換して分析を行いました。 手法 accuracy (%) CNN 97.84 MLP 93.08 HOG + kNN 96.74 HOG + SVM 97.47 この結果からCNNでも他の手法に比肩する精度が出ることがわかりました。 この後、約100のカテゴリー分類するための分類器を作る必要があることや、商品に対して複数のタグ(トップス + ニット)を付与する必要があることなどを考慮し、CNNを採用しました。 ディープラーニングを用いた商品カテゴリーの判定 画像からのカテゴリー判定を行うために、以下のようなCNNのモデルをChainerを用いて作成しました。 import chainer.functions as F import chainer.links as L from chainer import Variable, FunctionSet model = FunctionSet( conv1 = F.Convolution2D( 3 , 64 , 4 , stride = 2 , pad = 1 , wscale = 0.02 *math.sqrt( 4 * 4 * 3 )), conv2 = F.Convolution2D( 64 , 128 , 4 , stride = 2 , pad = 1 , wscale = 0.02 *math.sqrt( 4 * 4 * 64 )), conv3 = F.Convolution2D( 128 , 256 , 4 , stride = 2 , pad = 1 , wscale = 0.02 *math.sqrt( 4 * 4 * 128 )), conv4 = F.Convolution2D( 256 , 512 , 4 , stride = 2 , pad = 1 , wscale = 0.02 *math.sqrt( 4 * 4 * 256 )), fl = L.Linear( 6 * 6 * 512 , 2 , wscale = 0.02 *math.sqrt( 6 * 6 * 512 )), bn1 = F.BatchNormalization( 64 ), bn2 = F.BatchNormalization( 128 ), bn3 = F.BatchNormalization( 256 ), bn4 = F.BatchNormalization( 512 )) def forward (x_data, train = True ): x = Variable(x_data, volatile = not train) h = F.relu(model.bn1(model.conv1(x))) h = F.relu(model.bn2(model.conv2(h))) h = F.relu(model.bn3(model.conv3(h))) h = F.relu(model.bn4(model.conv4(h))) y = model.fl(h) return F.softmax(y) CNNの層数は4層で全ての層が畳み込み層です。 出力層にはソフトマックス関数を用いているために、各カテゴリに属している確率が出力されます。 入力は96 x 96 pixelのカラー画像(RGB)です。 ECサイトから取得する画像はこのサイズではないことがほとんどですので、CNNに入力する前にリサイズ処理を行います。 アスペクト比1:1ではない画像については、正方形のエリアに貼り付けを行います。 プーリング層も組み合わせたモデルも作成し検証を行いましたが、全てが畳み込み層であるようなモデルの精度が最も高かったたために、このような構成にしています。 ECサイトから取得する画像の中にあるアイテムの位置は大体固定されているため、このような結果になったのではないのかと思います。 学習にはセーター・ニット帽の各カテゴリーの画像を1.5万枚ずつ使用しました。 テキストからのカテゴリー判定の結果を補助的に用いながら、すべての画像を目視でチェックしデータクレンジングを行いました。 また、学習の際にはGoogleが2015年に発表したBatch Normalization( http://arxiv.org/abs/1502.03167 )という手法も用いて、学習の高速化を図っています。 マイクロサービス化した理由 この機能をiQONのクローラーに組み込む際にはマイクロサービスとして組み込みを行いました。 使用している言語・ライブラリなどの違いにより同一ホスト上で稼働させるよりも、独立した機能として切り出した方がクローラーシステム全体の構成がシンプルになると判断したためです。 また、画像からカテゴリーを判定するという機能それそのものの独立性が高いことも理由の1つです。 現在クローラーはCentOS 6.5 + rubyで稼働しているのに対して、画像判定処理はUbuntu 14.04 + pythonで稼働しています。 画像判定サーバーの構成 画像判定用のサーバーの構成は以下のようにしました。 リバースプロキシ: nginx( http://nginx.org/ ) アプリケーションサーバー: uWSGI( https://uwsgi-docs.readthedocs.org/en/latest/# ) webアプリケーションフレームワーク: flask( http://flask.pocoo.org/ ) 現在の負荷状況ではインスタンスタイプc4.largeが一台あれば十分です。 負荷が増えた時にはサーバーのスケールアップ・スケールアウトも検討に入れる必要があります。 VASILYではデプロイツールとしてcapistranoを使用していますが、今回はPythonで製作をしたのでデプロイツールもPython製のfabric( http://www.fabfile.org/ )を使用しました。 通常のWEBアプリケーションのデプロイに要求されるようなアプリケーションコードの変更をするデプロイの他に、モデルのパラメーターを変更した時にモデルファイルをAmazon S3から取得し更新を行うデプロイ方法もサポートしています。 Teslaを搭載した学習用サーバーで生成したモデルファイルをAmazon S3にアップロードした後に、そのファイルをAmazon S3から取得し、サーバーの再起動を行います。 このサーバーに対して以下のようにリクエストを投げることで、画像判定の結果を得ることができます。 サーバーに対するリクエスト・レスポンスのフォーマットはGoogle Cloud Vision APIを参考にしました。 $ curl -X POST -H "Content-Type: application/json" -d '\ { \ "requests":[ \ { \ "features":[ "CATEGORY_DETECTION"], \ "image":{ \ "uri":"http://www.example.com/image.jpg" \ } \ } \ ] \ }' \ "http://image-determination:8080/annotate" { "responses": [ { "categoryAnnotations": [ { "score": 80.0, "description": "sweater" }, { "score": 20.0, "description": "knit_caps" } ] } ] } ここで返されるscoreはそのカテゴリーに属する確率を%で表現したものです。 確率からカテゴリーへのマッピング カテゴリー判定器の出力は、各カテゴリに属している確率ですので、それを元にカテゴリーにマッピングする必要があります。 今回は以下のような関数を用いて各カテゴリーにマッピングを行いました。 def map_to_category : threshold = 90 if score_sweater > threshold return SWEATER elif score_knit_caps > threshold return KNIT_CAP else return UNKNOWN 単純にscoreの高い方のみを採用するということはせずに、scoreの高い方でありなおかつ score > threshold を満たすものを判定結果としています。 これは、カテゴリ判定ミスのFalse PositiveとFalse Negativeがユーザーに与える影響が大きく異なるための処理です。 簡単に言いますと、判定結果が曖昧な商品を間違ったカテゴリーに表示するよりも、その商品を出さない方がマシという考えです。 結果 このカテゴリー判定器を用いてセーターとニット帽の画像1万枚ずつを分類した結果を以下に示します。 予測 実測 セーター ニット帽 不明 セーター 9839 15 146 ニット帽 32 9636 332 これを元に、セーターカテゴリーの精度(precision)を求めると、9839 / (9839 + 32) = 99.7% という結果が得られました。 同様にニット帽カテゴリーの精度を求めると、99.8%という結果が得られました。 以下のiQONの画面からわかる通り、平置きの商品画像・人間が着用している商品の画像・マネキンが着用している画像など、様々な種類の画像を問題なく分類できています。 セーターの判定結果 ニット帽の判定結果 これからの展望 このカテゴリー判定器をさらに発展させたものとして、iQONで使用している約100のカテゴリに分類を行う判定器も開発しています。 それを実現するためには画像情報だけではなく、テキスト情報やブランド情報などの画像以外の情報も統合的に用いて判定を行います。 VASILYでは、最新の研究論文にアンテナを張りながら、同時にユーザーの課題解決を積極的に行うメンバーを募集しています。 興味のある方はこちらからご応募ください。 https://www.wantedly.com/projects/42989
概要 畳み込みニューラルネットワークによる画像生成モデル(DCGAN)に弊社のワンピース画像10万枚を学習させました。 得られた生成モデルを使って、乱数で作った100次元ベクトルからワンピース画像を生成しました。 逆に、一枚のワンピース画像を100次元ベクトルに圧縮し、可視化しました。 可視化したことで、モデルがワンピースの【色】【形】【柄】【モデルやマネキンの有無】など、基本的な特徴を捉えられていることがわかります。 この技術は、自動タグ付けや類似画像検索に応用することができます。 はじめに はじめまして。データサイエンスチームの後藤と申します。現在、アイテム画像のカテゴリ判定モデルを作ったり、各部門のKPIの日々の変動やシステムの異常を知らせるダッシュボードを作る仕事をしています。 また、最新の研究論文にもアンテナを張り、提案手法の検証にも取り組んでいます。今回は、去年の暮れあたりから話題になっている、ディープラーニングによる画像生成モデル(DCGAN)を弊社の所有するワンピースの画像に適用して、その利用価値を検討してみたいと思います。ちなみに、私はディープラーニングに入門したばかりで、今回の記事には間違いが含まれている可能性があります。その点を注意してお読み頂けば幸いです。 使用するモデル 今回用いるモデルは、DCGAN( http://arxiv.org/abs/1511.06434 )と呼ばれるものです。Deep Convolutional Generative Adversarial Networksの略で、名の通り、畳み込みニューラルネットワークによる生成モデルの一種です。大雑把に言えば、学習した特徴量を組み合わせて本物らしい画像を生成するGeneratorと、本物の画像と生成された画像を見破るDiscriminatorの二つのニューラルネットワークが互いに切磋琢磨して学習を進めていくというモデルになっています。論文中では「人の顔」や「ベッドルーム」の画像を学習し、見事に生成できています。この研究をうけて、アルゴリズムをchainerで実装し、アニメキャラなどのイラストを描かせた記事もあります。 Chainerで顔イラストの自動生成 Chainerを使ってコンピュータにイラストを描かせる 前者はアルゴリズムの定性的な解説が、後者は歴史的経緯が詳しいです。 調べた範囲では、ファッションアイテムの画像に適応した例はなかったので、アルゴリズムの適用範囲を探るという意味でやる価値はあると思いますし、特徴量を自動で抽出するというディープラーニングの特性を使ってこれまでにないサービスを作れるんじゃないか、という期待もしながら分析を進めます。 実装は、以下の二つを参考にしています。ネットワークの構成は、論文と同じものを採用します。学習を安定させるBatch normalizationを組み込んだことが肝のようです。 https://github.com/mattya/chainer-DCGAN https://github.com/Hi-king/chainer-DCGAN 学習データ 今回は、弊社のサービスで使われているワンピースの画像、約10万枚を学習に用います。前処理として、画像を96×96ピクセルのRGBに変換しておきます。数あるカテゴリの中からワンピースを選んだのは、個人的な趣味です。 ワンピースの生成 学習をある程度進めると、100個の乱数で与えることで、以下の様なワンピースを作ることができました。特徴を組み合わせて新しく生み出したものなので、この世にないワンピース、と言うことができます。生成モデル特有の不気味さがありますが、ご興味のある方は是非拡大して見てみてください。 遠目でみると自然な画像が生成されています。一枚一枚をじっくりみると、アイテムが大きく写っている画像は違和感なく綺麗に生成できていることがわかります。一方で、モデルの顔や腕の位置などバリエーションが多いパーツが含まれている場合は、若干不自然で、本物の画像でないことがすぐにわかります。まれに、マネキンの体に、首元がハンガーになっているようなものもあり、このモデルは現実を理解してワンピースを生成しているわけではない、ということも伺えます。 全体的に見て、それなりに自然で、個々の要素の特徴をある程度、捉えられていることがわかりました。ここから様々なことができるようになります。 例えば、さらなる検証が必要ですが、100次元ベクトルと得られた画像の対応関係を調べることで、モデル特有のベクトルを抽出したりできそうです。モデル着用のワンピースの画像から、モデルだけを取り除く演算をして、服だけの画像を生成することも可能になるのでは、と想像しています。 画像のベクトル化でアイテムを整理する 今度は逆に、一枚のワンピース画像を100次元ベクトルに変換して情報を圧縮します。100次元でもまだ、人が理解するには難しいので、多少の正確さを犠牲にして、2次元にマッピングします。ここでは、t-SNEというアルゴリズムを利用しています。 上図では、2000点程度のアイテムをマッピングしてみました。画像の分布を見てみると、領域ごとになんらかの特徴を持つことがわかります。具体的に4箇所拡大してみました。左から【形】、【柄】、【色】、【モデル】という特徴を持ったアイテムが集まっています。 今回のモデルは、画像の特徴を上手く100次元ベクトルで表現できているようです。このように画像データの特徴を数値で表現できると、サービスに組み込みやすくなります。例えば、どの数値が【柄】と対応しているのかを把握しておくことで、花柄やボーダー柄といったタグを自動で付与することが可能になります。また、手元にある写真の類似画像を検索する、といったタスクも少ない計算でできるようになりそうです。ほかにも様々な切り口でアイテムを捉えることできそうなので、アイテム検索がより便利な機能になるかもしれません。今回得られた知見をより深めて、サービスをより使いやすいものにしていきたいと思います。 最後に VASILYでは、技術的チャレンジをしながらiQONを開発していただける仲間を募集しています。ご興味のあるかたは こちら からご応募よろしくお願い致します。
こんにちは。iOSエンジニアの庄司です。 普段のアプリ開発において、バックエンドチームから「○○のページで△△のデータ取得するためにリクエストしているURLってどんなの?」と聞かれることがよくあります。 その都度、APIリクエストとリクエスト結果をprintで表示するフラグをONにしてアプリをビルドするということをしていたため、かなり手間がかかっていました。 こういった作業を楽にするためにネットワークデバッグライブラリをいくつか比較してみました。 ライブラリ ResponseDetective https://github.com/netguru/ResponseDetective NSURLSessionのリクエストやレスポンスをデバッガのログに流してくれるライブラリです。 特徴・利点 フルSwiftで書かれており、NSURLProtocolのメソッドに割り込んでログを落とす事ができます。 また、HTML, JSON, imageなどのレスポンスのタイプによって処理を分けることができます。 欠点 通信のデリゲートを受け取ることはできますが、デフォルトでは何もログを落とさないため、printも自分で書きます。 その結果、そこそこのコード量になってしまいますし、printを書くということは、ログを見るためにその都度ビルドが必要になってしまいます。 Wireshark https://www.wireshark.org/ Windows, OSXどちらでも使えるパケットキャプチャツールです。 特徴・利点 Remote Virtual Interface(RVI)を使って、UUIDごとの端末の通信をキャプチャできます。 あくまでパケットキャプチャツールなので、アプリにコーディングは不要。 Androidにも使えますし、他社製アプリの通信もキャプチャすることができます。 欠点 httpsの通信はドメインを見ることはできますが、エントリーポイントやレスポンスは暗号化されたままで、見ることはできません。 自分のアプリであれば、httpsの通信も 見ることができる ようです。 Charles https://www.charlesproxy.com/ OSX上で動くHTTPプロキシツールです。 多くのiOSエンジニアが使っているのではないでしょうか。 インストール手順はこちらの Qiitaの記事 が参考になります。 特徴・利点 Wiresharkと同様、アプリに対する実装は不要です。 通信を内容を見るだけではなく、ブレークポイントを仕掛けたり、レスポンスをローカルファイルの別の値に変更したりすることができるので、APIの異常系のテストにも使えます。 またWiresharkと違って、httpsの通信の中身を見ることができます。 欠点 30日は無料で使えますが、それ以降はライセンスの購入が必要になります。 1ライセンス最大$50ですが、豊富な機能を考えるとその価値があると思います。 PonyDebugger https://github.com/square/PonyDebugger square社製のネットワークデバッガです。 特徴・利点 iOSアプリ内部にライブラリを実装し、OSX側で立ち上げたサーバを介してGoogle ChromeのDeveloper Tool通信やView階層を見ることができます。 ChromeのDeveloper Toolを使い慣れている人にはとても親しみやすく見やすいです。 欠点 OSX側にpythonのサーバを立ち上げるのですが、これの環境構築に手間取りました。(OSX側のサーバインストールのエラーについてのissueが何種類もありました。今回参考になった issue ) 2013年から運用されているObjective-Cのライブラリで、もはや積極的にメンテナンスされていません。 netfox https://github.com/kasketis/netfox Swiftで書かれたの通信監視ライブラリ、通信結果をアプリ上で確認することができます。 GitHubのREADMEから 特徴・利点 セットアップは、AppDelegateに一行書くだけです。 アプリをシェイクするとリクエストしたURLリストがモーダル表示されます。 また、ジェスチャーが既存機能と衝突する場合は、カスタマイズすることもできます。 欠点 モーダル表示で確認するため、リアルタイムでURLを確認できません。 AlamofireやAPIKitなど、利用しているライブラリによっては ひと手間 かけないと通信ログを取れません。 mitmproxy 【2016/02/12 追記】 https://github.com/mitmproxy/mitmproxy OS X, Linux 上で動作する、CUIのHTTPプロキシです。Pythonでできています。 特徴・利点 Charles同様SSLのリクエストの中身を確認することができます。 リクエストのインターセプトや書き換えはもちろんのこと、Pythonスクリプトで動的にレスポンスを書き換えることができます。 ターミナルからキーボード操作で扱えるのも嬉しいですが、一番嬉しいのはここまで使えて無料であることです。 また、 公式ドキュメント も充実しています。 欠点 ちょっと触って見る限り、Wireshark, Charles でできることはだいたいできる上に無料なので、欠点は見当たりません。 まとめ ここまでのライブラリのメリット/デメリットを簡単にまとめました。 ライブラリ メリット デメリット ResponseDetective カスタマイズ性が高い アプリ実装多め Wireshark アプリ実装不要 https不可 Charles アプリ実装不要・設定が簡単・高機能 有料 PonyDebugger ChromeのDeveloper Toolが見やすい 古い・メンテ少ない netfox アプリ実装は一行だけ リアルタイムに通信を見れない mitmproxy アプリ実装不要・CUI・無料でSSLも確認できる なし 僕の場合は最終的に Charlesにお金を払うことに決めました。 mitmroxy を使うことに決めました。 冒頭であったように、「○○のページで△△のデータ取得するためにリクエストしているURLってどんなの?」となった場合でも、アプリをビルドすることなく、リクエストしているURLをすぐに確認することができます。 また、Xcodeとは別のツールであることで、APIリクエストとそのレスポンスがXcodeの狭いデバッグエリアを占領することがなくなり、ログがかなり見やすくなりました。 最後に VASILYでは、一緒にiQONのアプリを開発してくれる 仲間を募集 しています。少しでもご興味のある方は是非こちらからご応募よろしくお願いいたします。 参考URL Technical Q&A QA1176 - Getting a Packet Trace - Mac Developer Library 通信系のデバッグには Charles が便利 - Qiita
こんにちは。デザイナーの権です。 アプリ開発に関わるデザイナーの方々は、仕様とデザインが決まって実装に移る際、どのようにチームでやりとりしていますか? それぞれの開発体制や状況に合わせて進めていくので、会社によって様々だと思います。弊社でも効率的な開発のために、デザインデータや仕様書、指示書、プロトタイプの準備等、日々試行錯誤しながら最適な方法を探しています。 数ある開発手段の中で今回は、弊社が運営するサービス「 iQON 」の機能改善やデザインリニューアルに効率的に対応できるよう新たに作成した、デザインガイドラインについてご紹介したいと思います。 デザインガイドライン作成の背景 弊社では1年ほど前からデザイナーが5人に増えたことにより、プラットフォームごとに一人づつオーナーシップをとって制作を進める体制に変更されました。 私も以前は、アプリデザインや広告のデザインなどジャンル関係なく携わっていましたが、現在はiOSアプリのデザインをメインで制作しています。 しかし、体制の変化だけではまだ効率的な開発とは言えず、たとえば、UI改善する度にデザインの統一性が取れてない箇所があることで実装時にコミュニケーションコストが高くなってしまう問題などが浮き彫りになり、改めてアプリ全体でのデザインルールを定義することが必要不可欠になってきました。 ユーザビリティを考慮し、デザインの統一性が取れたクオリティの高いアプリを効率的に開発するためにも、デザインガイドラインを作成することになったのです。 デザインガイドライン作成のポイント いざ、「デザインガイドラインを作ろう」としても、正解がないため試行錯誤しながら見つけていくしかありません。 それぞれのアプリやプロジェクトごとに必要な項目など違うので、マッチしないものもあるかもしれませんが、弊社での作成ポイントをご紹介します。 -サービスやアプリのブランドを理解し、あるべき姿をデザインする ガイドラインは、これからアプリのビジュアルの基準になるものなので、どんな機能や画面にも適用できるように設計しなければなりません。 iQONはすでに成長しているサービスであり、アプリを利用している既存ユーザーもたくさんいるので、新しい機能や画面を制作する時もブランドイメージを壊さないようなデザインに仕上がるように、まずはホームやリスト、詳細などの主要画面からコンセプトデザインを作成し、あるべき姿を探ります。 そして、どのような場合でも適用できるような共通パーツを用意できるようにデザインし、ガイドライン作成に入ります。 -目的を設定し、セオリーから最適な方法を選択し作成する やみくもにモジュールを書き出して指定していくことはしないように、作成する目的を以下のように設定。 新しい画面もルールに沿って作成していくようなフローに変える 共通パーツやテキスト指定などをガイドラインから選んで指定する(または、エンジニアがガイドラインを基に実装できる)形にする。 常に更新&運用していき、チームで共有できる形にする 目的を設定することによって、本当にこのガイドラインが実用的なのかどうかを確認できたので、方向性がブレそうになっても立ち返って軌道修正することができます。 そして、全く同じ方法で展開できるものはないのですが、ガイドラインのセオリーや特性を理解するため、一般的にはどのように作られて活用されているのかを Google Material Design や 海外のUIスタイルガイドなど を見て参考にしました。 その中から自身のアプリ開発に最適だと思われる方法を選択して作成していきます。 iQONではどのようなものを作成したのか、一部ご紹介したいと思います。 1.オブジェクト別スタイル アプリ内で使用するカラー、フォント、テキストなど、オブジェクトのスタイルを一覧で表記。 乱立してしまいがちなテキストのスタイルも用途やルールを定義することで、 新しい画面作成の場合でも、用意された候補の中から適用することができます。 2.コンポーネント別スタイル 画面からコンポーネント別に抽出して、使われるテキストや画像のスタイルやマージンなどを表記。 コンポーネントで分解して表記しているので、細かい指定もまとめて確認できるようにしています。 例えば、グリッド表示のコンポーネントを、別の画面でも同じスタイルで実装して欲しい時などにも便利。 3.ステータス別スタイル ボタンのnormal時、hover時などのステータス別のスタイルを表記。 通常時やhover時など、アプリ内で基本このルールを共有しておけば、 毎回の指定が必要なくなります。 -メンバーに共有し、フィードバックをもらいながら改善する 弊社の場合だと、仕様や遷移の確認手段として InVision でプロトタイプを作成しているので、それとともにデザインガイドラインをエンジニアへお渡しして実装してもらいます。 実際に役立ててもらわないと意味がないので、はじめてガイドラインを導入してもらう時は利用の仕方などをお互いに確認しながらメリットを理解してもらい、問題や修正点があれば都度フィードバックをもらいながら修正していきました。 この際重要なのは、メンバーに「デザインガイドライン利用のメリットを実感してもらう」こと。 「同じようなモジュールを実装する時は、このガイドを参照できます」、「テキストの指定はこのリストの中から用途に当てはまるものを指定してください」などの便利な使い方を共有することで、スムーズに開発フローへの導入をすすめることができました。 -チーム内アンケートで効果測定する メンバーに「デザインガイドライン利用のメリットを実感してもらう」ために行ったことがもう一つ。 実際にデザインガイドラインを導入することで、そもそも問題を解決できているのかを測定したくても、プロジェクトによって開発期間や難易度は変わるので、定量的な数字を測ることはなかなか難しいのが現実です。 そこで、デザインガイドライン作成の目的にある、"ガイドラインは常に更新&運用"のためにも必ず行おうと思っていたのが、チーム内アンケートでした。 「デザインガイドラインを導入して開発効率が上がりましたか?」という質問に対して、メンバー全員から「上がった」との回答をもらい、以下のような具体的な効果も答えていただけました。 fromエンジニア " デザイナーに実装途中の画面を見せた時に、見てもらいたいところと関係ない箇所で「○○の色が違います」とか指摘されることが減った。 " " デザインがまだできていなくても、おおまかにガイドを見て実装ができるのでいい。 " fromデザイナー " 基準にすべきデザインデータが明確なので、都度デザインデータの場所を確認する必要がなくなった。 " " ルールが決まったことで微調整がなくなった。(作成時間を比較すると、3h〜5hくらい早くなった。) " 上記回答は、定性的ではありますがメリットとして感じるには十分な結果になったのではと思います。 個人的にも、今まで曖昧だった部分が明確になり、ルールを元にデザインができるので、「悩む時間」を「作業時間」へと変換できていたことが、効率化を実感できた瞬間でした。 改善点 効率化を成し遂げられた部分はあるものの、改善すべき点はまだたくさんあります。 アンケートではメンバーから改善点や意見も書いてもらい、その中でも多かったのが、「更新と管理」の問題でした。 ガイドライン自体の作成にはやはり時間はかかる 今の仕様のままだと少しずつボリュームが増えていくので、肥大化してしまう 人力が必要な場面が多いので、ヒューマンエラーが起きかねない GitHubで変更履歴も追えるようにするといいのでは このような問題はこれからも出てくると考えていますので、さらに改善してデザインガイドラインを日々アップデートしていく次第です。 まとめ 今回は、「効率的なアプリ開発のためにデザインガイドラインを作成した話」をご紹介しましたが、いかがでしたでしょうか。 デザインガイドライン自体は、ユーザーの目に直接触れることはありませんが、最高のユーザー体験を提供していくためには効率のいい開発環境があってこそ実現するので、デザインガイドラインの作成はデザイナーにとって大事なミッションでした。 はじめは手間がかかる作業ではありますが、チームメンバー達に、わかりやすく効率的に仕様を伝えるための手段の一つになると思います。 皆さんも、効率的なアプリ開発のために独自のデザインガイドラインを作成していきませんか。 今回の記事を少しでも参考にしていただき、さらに良い方法やご意見など、是非教えていただければ幸いです。 さいごに 「iQON」は、Webサービス開始から約5年半、アプリリリースからは3年半以上が経とうとしています。 もっと使いやすいサービスになれるよう、機能改善/追加やリニューアルしてみたいというデザイナーの方々、ただいま絶賛募集中です。 ご興味のある方は、以下ページからまずは話だけでも聞きに来てみてください。お待ちしております。 https://www.wantedly.com/projects/41710 www.wantedly.com https://www.wantedly.com/projects/42672 www.wantedly.com
RecyclerViewが発表されて1年半ほど経ちましたが、みなさんRecyclerViewは活用していますか? これまではListView・GridViewを頑張って使っていたiQONも、直近のリリースから少しずつRecyclerViewに置き換えはじめました。 RecyclerViewはListView・GridViewよりも柔軟になり拡張しやすくなった代わりに、必要なものは自分で実装しないといけなくなりました。 そのため、ListView・GridViewにはあったけどRecyclerViewではなくなった機能が存在します。 今回はRecyclerViewの GridLayoutManager を使う際、データロード中フッターにProgressBarを出す方法を紹介したいと思います。 LinearLayoutManagerに関しては今回触れませんが 『ProgressBarを表示する』 の項を参考にしてもらえれば実装できると思います。 サンプルコード 今回の内容のサンプルコードはこちらになります。 https://github.com/nissiy/GridLayoutSample 参照していただけると理解が深まると思います。 興味がある方はビルドもしてみてください。 実装 ProgressBarを表示する RecyclerViewには ListView#addFooterView のような仕組みがないためフッターを自分で実装しないといけません。 フッターを作成してそこにProgressBarを表示させるには、以下のことを行う必要があります。 データロード前と後でデータセットに細工をする データセットの中身を見て RecyclerView.Adapter#getItemViewType の返す値を変える データロード前と後でデータセットに細工をする 以下のように、通信処理の前後でデータセットにStubをセットしたり、取り除いたりします。 // MainActivity.java private void loadData( final int page) { // ProgressBarを表示させるためにStubをセット if (page > 1 ) { adapter.add( new ProgressStub()); } // postDelayedして通信処理を仮想しています handler.postDelayed( new Runnable() { @Override public void run() { // 通信処理が終わったのでセットしたStubを取り除く if (page > 1 ) { adapter.remove(adapter.getItemCount() - 1 ); } // 通信して取得したデータを処理 ... } }, 2000 ); } データセットの中身を見て RecyclerView.Adapter#getItemViewType の返す値を変える RecyclerView.Adapter#getItemViewType(int position) をOverrideして、返す値を変えることで RecyclerView.Adapter#onCreateViewHolder 側で、ViewTypeによってViewHolderを分けることができます。 今回もデータセット内のStubの有無をチェックして、ViewTypeを返し分けて、ViewHolderを分けることでProgressBarを表示させるようにしています。 ヘッダーなどを実装する場合にも同様のアプローチを取ることで実装できます。 // PhotoGridAdapter.java @Override public int getItemViewType( int position) { Object object = objects.get(position); if (object instanceof ProgressStub) { // データがProgressStubの場合は通常とは違う値を返す return TYPE_PROG; } else { return TYPE_ITEM; } } // PhotoGridAdapter.java @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { RecyclerView.ViewHolder viewHolder; if (viewType == TYPE_ITEM) { FrameLayout view = (FrameLayout) inflater.inflate(R.layout.photo_layout, parent, false ); AppCompatImageView photoImageView = (AppCompatImageView) view.findViewById(R.id.photo_image_view); photoImageView.setLayoutParams( new FrameLayout.LayoutParams(imageSize, imageSize)); viewHolder = new PhotoLayoutHolder(view); } else { // ViewTypeがTYPE_PROGの場合はProgressBarのViewHolderを返す FrameLayout view = (FrameLayout) inflater.inflate(R.layout.progress_bar_layout, parent, false ); viewHolder = new ProgressBarLayoutHolder(view); } return viewHolder; } カラム数をpositionごとに変える GridLayoutManagerを使う際には、SpanCountを 2 や 3 などに設定してカラム数を決めると思います。 GridLayoutManagerは拡張することでカラム数をpositionごとに変更することができるため、ヘッダー・フッターを作りたい時や、グリッドの途中でぶち抜きのコンテンツを出したいときに細工を行います。 今回もフッターに出すProgressBarはキレイに中央寄りになってほしいので、ProgressBarを表示するpositionではカラム数が変わるようにGridLayoutManagerを拡張しました。 public class GridWithProgressLayoutManager extends GridLayoutManager { public GridWithProgressLayoutManager(Context context, final int spanCount, final RecyclerBaseAdapter adapter) { super (context, spanCount); setSpanSizeLookup( new GridLayoutManager.SpanSizeLookup() { // 今回はここを細工しています @Override public int getSpanSize( int position) { // ProgressBarを表示するpositionではSpanSizeをいっぱいに広げる if (adapter != null && adapter.getItemViewType(position) == RecyclerBaseAdapter.TYPE_PROG) { return spanCount; } // 1を返すと通常通りのSpanSizeになる return 1 ; } // 今回は触れませんが高速化のためにOverrideしています。詳しくは下記のURLを参照してください。 // http://developer.android.com/intl/ja/reference/android/support/v7/widget/GridLayoutManager.SpanSizeLookup.html @Override public int getSpanIndex( int position, int spanCount) { if (adapter != null && adapter.getItemViewType(position) == RecyclerBaseAdapter.TYPE_PROG) { return 0 ; } return position % spanCount; } }); } } ItemDecorationを使っている場合は注意が必要 ItemDecorationを使っている場合、処理が複数回呼ばれてProgressBarがカクついてしまいます。 そのため、ProgressBarの場合は処理をスキップしてあげる必要があります。 GridLayoutManager特有の問題のためLinearLayoutManagerに関しては気にしなくて大丈夫です。 // GridSpacingItemDecoration.java @Override public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { // ProgressBarのViewHolderの場合は処理をスキップする RecyclerView.ViewHolder viewHolder = parent.getChildViewHolder(view); if (viewHolder instanceof ProgressBarLayoutHolder) { return ; } int position = parent.getChildAdapterPosition(view); int column = position % spanCount; outRect.left = column * spacing / spanCount; outRect.right = spacing - (column + 1 ) * spacing / spanCount; outRect.bottom = spacing; } まとめ 長年、ListView・GridViewを使い続けているプロジェクトの場合、RecyclerViewへ移行するとなると自分で実装しないといけないものが多くかなりハードであると思います。 iQONの場合も最適化のための独自の仕組みや、広告表示処理などが複雑に絡まっているためRecyclerViewへの移行には時間がかかっています。 ただ、RecyclerViewへ置き換えが完了したページを見ると、もともと巨大で手を入れにくかった処理がモジュールごとに分散できているのでメンテナンスがしやすくなっています。 シンプルなリスト表示・グリッド表示の場合には今まで通りListView・GridViewを使った方が良いと思いますが、positionごとにコンテンツを変えたり、アニメーションを駆使したりしたい場合は、長期的考えてRecyclerViewを使ったほうが良いと思います。 最後に VASILYでは、一緒にiQONを開発してくれる仲間を募集しています。少しでもご興味のある方は是非 こちら からご応募よろしくお願いいたします。
あけましておめでとうございます。データサイエンティストの金田です。現在 iQON では、データ分析の基盤として BigQuery を利用しており、データ分析や計算負荷の高いバッチ処理等に活用しています。しかしながら、通常のデータベースとは若干異なる点があり、効率的な運用ができるまでに様々な試行錯誤がありました。今回はそれらの試行錯誤によって得られた知見をベストプラクティスとして紹介したいと思います。 ログデータのテーブル名に日付を入れる BigQuery でログを保存する場合は、テーブル名の最後に yyyymmdd 形式で日付を入れることをお勧めします。理由は二つあり、1つ目はスキャン対象となるデータ量を抑えられるため、2つ目が Web UI でのテーブル管理が容易になるためです。 処理データ量の抑制 まず、スキャンするデータ量を抑えられるという点ですが、BigQuery ではクエリを発行した際、スキャンしたデータ量に応じて課金が決まってくるため、扱うデータ量が大量になってくると、いかに無駄なデータへのクエリの発行を減らせるかが重要になってきます。 その点、BigQuery では テーブルワイルドカード関数 という関数が容易されており、テーブル名の最後に yyyymmdd 形式で日付を入れておくと、指定した期間のテーブルだけを参照するといったことが可能です。 具体的には、「nginx」というデータセットに、「access_yyyymmdd」といった形式でアクセスログのデータが保存されているとします。その際、11月分のログを分析したいといった場合は、TABLE_DATE_RANGE 関数を使って、下記のようにクエリを書くことで、簡単に指定した期間のログだけを処理対象とすることが可能です。 [sql] SELECT * FROM (TABLE_DATE_RANGE(nginx.access_, TIMESTAMP('2015-11-01'), TIMESTAMP('2015-11-30'))) [/sql] これにより、必要な期間のログだけをクエリの対象とできるため、スキャンするデータ量を抑えることが可能になります。 Web UI でのテーブル管理 また、ログデータを日別で管理することで、Web UI でのテーブル管理も容易になります。 通常は、データセットに日別に多くのテーブルを作成してしまうと、すべてのテーブルが表示されてしまうため、Web UI での管理が煩雑になってしまいますが、テーブル名の最後に日付を入れておくことで、下記のようにまとめて表示をしてくれます。 個別の日付のテーブルに対して操作を行いたいときは、右側のプルダウンで日付を選択すると、特定の日付データだけ操作することが可能です。 \ ビューを活用する 他のデータベースと同様に、BigQuery では仮想テーブルを作成するビューの機能を持っていますので、この機能を活用することで効率的に運用をすることができます。 通常、ログやデータベースのデータを分析に使う際は、JOIN 操作を行って、トランザクションテーブルとマスタデータを結合したり、SQLの関数を使って加工を行ったりしますが、そのような操作はビューとして保存することができます。具体的には、Web UI のクエリ作成画面の下に、「Save View」というボタンがありますので、こちらを押すことでクエリで作成したテーブルを仮想テーブルとして保存することができます。 保存されたテーブルは、下記のように緑色のアイコンと共に表示されるので、ビューであることがするわかります。 実際の運用においては、 データを利用するユーザーは、分析用に加工されたビューに対してクエリを発行するようにすることで、煩雑なSQL文が量産されるという事態を防ぐことができます。 また、例えば上記で説明した TABLE_DATA_RANGE 関数と CURRENT_TIMESTAMP という関数を組み合わせて、任意の期間のデータだけを参照対象とするといったことも可能です。例えば、下記のクエリでは過去60日のログだけを計算対象にしていますが、このように期間を絞ったユーザー参照用のビューを用意することで、無駄なデータに対するクエリ発行を防ぐことができると同時に、ユーザーが意図せず大量のデータに対してクエリを発行してしまうというリスクを抑えることもできます。 [sql] SELECT * FROM (TABLE_DATE_RANGE(nginx.access_, DATE_ADD(CURRENT_TIMESTAMP(), -60, "day"), CURRENT_TIMESTAMP())) [/sql] ただし、大量の JOIN が発生したりするビューを作成する場合は、クエリの結果が返ってくるのに時間がかかることがありますので、レスポンスタイムが気になる場合は、必要に応じてビューではなく、バッチで実体のテーブルを作成することも考慮に入れてください。 非構造化データをBigQueryで扱う BigQuery には、 JSON 関数 というものが用意されており、文字列で保存されているJSONから、キーを指定して必要な値だけを抜き出すということが可能なため、非構造化データを扱うことも可能です。 特に、アクセスログ等は、ビジネス要件によって取得項目が変更になったりすることもありますので、BigQuery で非構造化データも扱うことができると知っておくと、設計の幅が広がります。 具体的には、下記のような形式で文字列としてJSONのデータが入っているとした場合、 スキーマ name type timestamp TIMESTAMP log_json STRING データ例 timestamp log_json 2015-12-27 12:07:49 UTC {"where":"item_detail","item_id":"3030910","model":"iPhone 6"} テーブルから item_id だけを抜き出したいときは、下記のように記述することで JSON 形式で保存してあるデータを取り出すことが可能です。 [sql] SELECT * FROM (TABLE_DATE_RANGE(nginx.access_, TIMESTAMP('2015-11-01'), TIMESTAMP('2015-11-30'))) [/sql] ただし、この方法を使う場合は、JSON文字列として保存されている全てのデータをスキャンする形になりますので、データ量が大きく、頻繁にクエリを発行する必要があるものに関しては、予め必要な項目だけを抜き出して、構造化したデータを別テーブルとして保存しておいた方がよいと思います。 ストリーミングインサートは必要に応じて BigQuery ではストリーミングインサートという機能があり、例えば Fluentd から直接ストリーミングで BigQuery へデータをインポートすることが可能です。 ただし、便利なストリーミングインサートですが、通常のバッチでのインポートに比べて、料金がかかるということと、データに欠損が生じる可能性があるという事があるため、特にリアルタイムに結果を把握必要のないものに関しては、日次バッチでのインポートも考慮した方がよいと思います。 また、ストリーミングインサートを使う場合でも、データに欠損が生じる場合がありますので、バックアップとしてS3やGCSにも保存しておくようにしておきましょう。 バッチ処理にAirflow を活用する 上記でも記載した通り、データ分析基盤として BigQuery を活用するためには、日次でデータを BigQuery へインポートしたり、分析用のテーブルを日次で作成したりする必要がでてきます。 通常、このようなバッチの処理には、cron を使ってバッチを起動する場合が多いと思いますが、cron だと、ジョブに依存関係をもたせたり、ジョブが失敗した場合に、どこで失敗したのか特定することが難しいということがありました。 そこで、VASILYでは、バッチの処理に AirBnB 社が開発した、Airflow というジョブ管理ツールを使うことで、バッチ処理にまつわる業務を効率化しています。このツールを使うことで、例えば下記のように複数のジョブに依存関係がある場合も、その依存関係を考慮して処理を実行してくれ、万が一失敗した場合でも、リトライ処理を自動で行ってくれたり、問題の特定がすぐにできるようになったため、データの管理に関する業務が大幅に改善されました。 最後に 今回は、データ分析基盤として BigQuery を活用するにあたり、どのような点に気をつけて設計や運用を行なったらよいかについて説明を行いました。通常のデータベースとは若干異なるところがありますので、その点は注意が必要ですが、使いこなせば従来では考えられなかった低コストでデータ分析の環境を整えることが可能です。 また、今回ご紹介した BigQuery 以外にも、VASILY では、Tableau を使ってビジネスユーザーがクエリを書くことなく自由にデータを取得できるような仕組みを構築したりと、様々な取り組みを行っていますので、次回はその辺りの仕組みも説明できればと思っています。 最後に、VASILY では、データサイエンティスト を募集しています。データの分析だけでなく、データエンジニアの役割も担う必要がありますので、幅広い経験を積むことができます。 ご興味のある方は是非 こちら からご応募よろしくお願いいたします。
Merry Christmas! フロントエンド開発の荒井です。今回はフロントエンド開発陣が3ヶ月で行ったサイト最適化を紹介したいと思います。短期間で多くの変更を施したため、今回は取り組みやすく、特に大きなインパクトがあったと思われる内容2つを紹介します。 はじめに VASILYでは提供したい価値を再定義し、ユーザーにとって価値のあるコンテンツを届けるため、2015年8月にiQONのPCサイトのリニューアルを行いました。このリニューアルでは「サイトマップの見直し」「文書構造(h1, title, description etc)」の見直し等を行い、サイトの中身をガラッと変えています。 リニューアルを行った後、Google Search ConsoleやNewRelicで日々サイトの状態を確認していたのですが、日が経つにつれ、細かな問題が出てきました。これから挙げる事例は、日々のチェックにより発見された問題の紹介と解決策です。 Google Search Consoleで発見されたエラー対応 フロントエンドエンジニアの日課として Google Search Console の確認があります。 iQONではECサイトをクロールしてアイテムを集め、ユーザーがそのアイテムを使用してコーディネートを作るというサービスの特性上、出来上がったページに以下の問題が多数報告されていました。 タイトルタグの重複 重複するメタデータ(descriptions) 「Google Search Consoleのエラーを徹底的になくそう」 当たり前のことですが、膨大なコンテンツを保持するサイトですと、数が少ないエラーは見過ごしがちです。エンジニアはまずGoogle Search Consoleに現れたエラーを無くすことに着手しました。例えそれが1件でも改修対象として手を動かしています。 タイトルタグの重複 こちらは主にユーザーが投稿したコーディネートに発生していた問題です。 タイトルタグにはユーザーが入力したコーディネートのタイトルを採用していたのですが、「冬コーデ」のような重複したタイトルのページが多数存在してしまいました。 ユーザーが作成したページのタイトルやメタデータはユニークであるべきです。 iQONでは以下のようにしてユニーク化を行っています。 [bash] 投稿されたタイトル + ユーザー名 + つくったコーディネート(◯◯件目) ex)「ワントーンコーデ♪メイクは派手めだけどコーデはシンプルに☆…」XXXさんがつくったコーディネート(335件目) [/bash] 上記は一例で、様々なところをユニークにする見直しを行っています。 重複するメタデータ こちらはアイテムページに出ていたエラーです。同じ商品が異なるECサイトで販売されていた場合などに説明文が重複してしまい、エラーが起きていました。 そこで、商品の「商品名」「ブランド」「説明文」などの条件から同一商品を判断し、自動でcanonicalを貼るように改修を続けています。 その他の対応 blockquote見直し、no indexの設定、リダイレクトの見直し..etc.. 数えていませんが、数十項目の改善をしたと思います。 大事なのはCGMでコントール不可能だったところをコントロール可能な構成に直していったということです。サイト最適化はこうした塵を積もらせる改善が必要です。 HTTPSの対応 次にHTTPSの話をします。昨今、セキュリティを優先事項とする企業が増えました。VASILYではフロントエンドサーバーのHTTPS対応が遅れていたため、iOS 9のApp Transport Securityが話題になったタイミングでHTTPS化に踏み切りました。 Google HTML/CSS Style Guide まず、100を超えるファイルにベタ書きされたhttpを書き換えるところから始めました。その際のコーディングはGoogle HTML/CSS StyleのProtocolに準拠しました。 httpとhttps両方のプロトコルに対応するところはhttp/httpsを省略し//から始めるようにします。 body { background: url( http://iqon-img.s3.amazonaws.com/static/images/top/iqon_bg_gray.png ) 0 0 repeat; } body { background: url(//iqon-img.s3.amazonaws.com/static/images/top/iqon_bg_gray.png) 0 0 repeat; } 詳細は Google HTML/CSS Style Guide に記載があります。 HTTPSを標準にする 上記書き換えがすべて終わった上で、サイトの標準をhttpsにします。 リダイレクトを設定すれば良いのですが、外部に提供しているウィジェットなどもあるので、単純に一律切り替えることは出来ず、確認の時間が非常に取られました。 また、CDNがhttps対応をしてないため画像まわりで問題が発生したりするなども起きますので、踏み切る際は事前に確認しておくことをおすすめします。 環境整備 フロントエンドでは社内確認用のステージングの環境にAmazonの Elastic Load Balancing を使用しておらず、その役割を古いバージョンの Varnish に頼っていたため、プロダクトの確認環境を整えるのにも体力を使いました。HTTPS化を進めるにあたり、規模が大きくなるほどコード修正以外にも作業が発生します。 HTTPS化をしてみて サイトの健全さの意味でも対応するべきでしたし、 HTTPS ページが優先的にインデックスに登録されるようになる といった記事にもある通り、インデックスにも効果があるようです。DAUを倍にした要因の一つだと考えています。 まとめ 今回紹介した2つは取り組みやすい2つの事例を紹介しました。 他にも App Indexing 対応や パフォーマンス最適化 などといったことも行っています。Google Search Consoleで確認する限り、クロール数は日に日に増え、3ヶ月で約5倍程度の伸びがありました。それに伴い、DAUは3ヶ月で倍になり、検索からの流入が増えている傾向にあります。 今後は AMP や Service Worker の導入を検討しているので、次回は結果を報告出来ればと思います。 さいごに VASILYではエンジニアを募集しています。 ご興味のある方は是非 こちら からご応募よろしくお願いいたします。それでは良いお年を。
こんにちは。VASILYデザイナーの文山です。 入社して1年が経つタイミングで、弊社のコーポレートサイトリニューアルを経験しました。今日はそれを例に、コンセプトをキーワードまで分解しデザインに落とし込むまでの過程や、デザイナーとして大切だったことをご紹介します。 コンセプトをキーワードまで分解するために社内アンケートを実施 弊社では採用に力を入れていることもありターゲットは採用面接応募者とし、コンセプトは「VASILYらしさを伝える」ことに決まりました。 「VASILYらしさ」をデザインに落とし込むためには、より具体的に言語化する必要があります。そこで社内アンケートを実施し、VASILYらしさをキーワードとしてあげてもらいました。 社内アンケートを実施した理由は、 ・社員が思っているVASILYらしさが把握できる ・自分にない視点が見える ・コンセプトが具体化できる というメリットがあったためです。 アンケートを集計した結果、 先進的 ・ シャープ ・ エネルギー という3つのキーワードが抽出できました。 どのようにデザインに落とし込んだか より良いデザインにするためにも、チームで共通のコンセプトを持ち、同じ方向性を持つことがとても重要です。 デザインに取り掛かる前に、様々な参考サイトをピックアップし、キーワードと一致するものをまとめてチーム内で共有しました。 そうすることで、デザインの方向性が具体化し、チーム内で共通認識を持つことができます。 参考サイトをピックアップする際は、以下のようなWEBデザインリンク集から情報を集めました。優れたデザインのリンクがカテゴリー別にたくさん紹介されているので、キーワード別に検索することもできておすすめです。 ー参考にしたWEBデザインリンク集ー AWWWARDS I/O 3000 siteinspire straightline bookmark 上記でのWEBデザインを参考に、チームでの方向性をすり合わせた後、社内アンケートから抽出したキーワードをもとに、デザインに取り掛かりました。 ここからは、TOPページ / リクルートページ / 各職種の詳細ページの3つを例に、キーワードからどのようにデザインに落とし込んだのか、ご説明していきたいと思います。 TOPページ TOPページ は第一印象となる重要なページです。 社内アンケートから抽出したキーワードの「先進的」や「エネルギー」というところが伝わるよう、会社のミッションの英字テキストを大胆にレイアウトし、フォントはDINを使用しました。 少し縦長でスタンダードなフォントではありますが、大きく使用することで、スタイリッシュな印象になります。 しかし、スタイリッシュな印象を持たせることができた反面、インパクトが強すぎてしまい、男性的なデザインになってしまいました。 社内アンケートからピックアップしたキーワードには女性的という要素は入っていませんでしたが、弊社は女性向けサービスを運営していることから、男性的なデザインに寄りすぎても会社のイメージと合いません。 そこで英字テキストに透過をかけ抜け感を出すことで、女性的な表現に近づけるよう調整をしています。 リクルートページ リクルートページ はVASILYの行動指標である「HIPSTER」がメインとなるページにしました。 弊社では「HIPSTER」という7つの項目から構成されている、VASILY社員としての行動指標があります。判断に迷った際や、何かを始める時、悩んでいる時など、この7つの項目のもと行動しています。 入社を検討されている方々にこの「HIPSTER」を理解してもらい、同じマインドのもと一緒に働きたいという意図があったため、HIPSTERの各項目の説明を入れ、その後に各職種のエントリーページに繋がるという構成にしました。 また、縦に並べてHIPSTERを説明すると項目が多く、下までスクロールしてもらえない可能性があったため、目線を自然に誘導できるよう斜めのラインを使用したレイアウトにし、「シャープ」「エネルギー」という2つのキーワードが伝わる表現にしました。 各職種の詳細ページ 各職種の詳細ページ では、働くイメージを持ってエントリーボタンへ誘導できるよう、以下の構成にしました。 ・各職種のミッション ・業務内容 ・取り組み ・よく関わる職種 しかし、これらの後にエントリー項目が続くため、コンバージョンして欲しいエントリーボタンがページ下部に下がってしまい目立たなくなってしまいます。そのため、こちらでもキーワードである「シャープ」を取り入れ、ページの背景の色を斜めで区切ったレイアウトにし、メリハリをつけることでエントリー項目がきちんと目立つよう工夫をしました。 大切だったこと より良いデザインにするためにも、一緒に制作するチーム内で方向性を合わせることは重要です。 そのためには、コンセプトをキーワードまで分解して言語化することがとても大切なことでした。そうすることで、コンセプトをより具体的にすることができ、チームでデザインの方向性を話し合う際にも、そこを軸として考えることができます。 また、コンセプト通りに制作するだけでなく、自身で考え+αしてデザインに落とし込むということもデザイナーとして大切なことだとリニューアルを通して、改めて感じました。 最後に 2015年も残りわずかですね。皆様良い年末をお過ごしください。 現在VASILYではコンセプトをキーワードまで分解して考え、デザインに落とし込めるデザイナーを絶賛募集中です! たくさんのご応募お待ちしております。 https://www.wantedly.com/projects/5485 連絡先:info[at]vasily.jp
こんにちは。iOSエンジニアの遠藤です。 最近のiQONはコンテンツ量が増えてユーザーの詳細ページに表示する情報が多くなってきました。今のデザインでは情報量が多すぎて詳細ページが見づらい状況になっています。そこで以下のようなコンテンツをタブで管理できるかつユーザー情報を表示してスクロールするデザインを実装しました。 実装について色々と調べたのですがあまり情報が無かったので共有したいと思います。実装する上で参考になれば幸いです。 今回のUIを実現する上で解決しなければいけない課題 ・スワイプでもタブの切り替えができる ・ある一定以上スクロールした場合にタブの位置を固定する 解決策 既にiQONの仕組みとしてある、スワイプでタブの切り替えをするところから考えていきたいと思います。 【スワイプでタブの切り替えについて】 ・スワイプでタブを切り替えるにはUIPageViewControllerを使用 ・リストの部分はUIPageViewControllerのChildViewControllerとして実装 ・ユーザー情報を表示するViewはUIPageViewControllerに貼り付ける UIPageViewControllerを使用することで以下のようなViewの構造になります。 【スクロールについて】 リストで表示する部分とユーザー情報を表示する部分が分割されたので2つのコンテンツのスクロールを連動させなければいけないという課題が増え、以下の3つの項目について考えていきます。 1: ある一定以上スクロールした場合にタブの位置を固定する 2: リストの部分をスクロールするとユーザー情報を表示する部分もスクロールする 3: ユーザーの情報を表示する部分をスクロールするとリストの部分もスクロールする 1: ある一定以上スクロールした場合にタブの位置を固定する リストのスクロール量を見て一定の位置まではユーザー情報を表示する部分のフレームの位置を動かします。 2: リストの部分をスクロールするとユーザー情報を表示する部分もスクロールする ユーザーの情報を表示す部分でスクロールした量をリストに渡してリストのscrollViewのcontentOffset.yを更新させます。 3: ユーザーの情報を表示する部分をスクロールするとリストの部分もスクロールする リストでスクロールした量をUIPageViewControllerに渡してユーザー情報を表示する部分のフレームを動かします。 実装について 以上の課題をふまえて以下の3つのクラスで実現していきます 今回は実装する上で大変だったスクロールについてフォーカスして3つのクラスでの処理を紹介したいと思います。 タブを含む詳細な実装については サンプルコード を用意しましたのでそちらを御覧ください ・A: PageViewController (UIPageViewControllerを継承) ・B: ViewController(リスト部分) ・C: ContentView(ユーザー情報を表示する部分) 3つのクラスはそれぞれ以下の役割になっています ・PageViewControllerはViewControllerとViewの仲介役 ・ViewControllerをPageViewControllerのChildViewControllerにすればいいように実装 ・PageViewControllerの中にframeやcontentOffsetの調整を書く A: PageViewController スクロールの課題を解決するために以下の処理を書きます。 1: ある一定以上スクロールした場合にタブの位置を固定する 2: TableViewControllerスクロールするとユーザー情報を表示する部分もスクロールする ・ChildViewControllerがスクロールされるたびに呼び出される ・ChildViewControllerのスクロール量を見てContentViewのフレームの位置を変更 ・ContentViewがある一定の位置まで来たらフレームの位置のを動かさない ``` swift // ChildViewControllerがスクロールした時に呼び出され、ContentViewのフレームの位置を変更する func upadteContentViewFrame() { guard let currentIndex = currentIndex, vc = pageViewControllers[currentIndex] as? ScrollTabPageViewControllerProtocol else { return } if vc.scrollView.contentOffset.y >= -tabViewHeight { let scroll = contentViewHeight - tabViewHeight updateContentView(-scroll) vc.scrollView.scrollIndicatorInsets.top = tabViewHeight } else { let scroll = contentViewHeihgt + vc.scrollView.contentOffset.y updateContentView(-scroll) vc.scrollView.scrollIndicatorInsets.top = -vc.scrollView.contentOffset.y } } // ContentViewのフレームの位置を変更する private func updateContentView(scroll: CGFloat) { if shouldScrollFrame { contentView.frame.origin.y = scroll scrollContentOffsetY = scroll } shouldScrollFrame = true } ``` 3: ContentViewをスクロールするとリストの部分もスクロールされる ・ContentViewのスクロール量を受け取りChildViewControllerのcontentOffset.yを調整する ``` swift private func updateContentOffsetY(scroll: CGFloat) { if let currentIndex = currentIndex, vc = pageViewControllers[currentIndex] as? ScrollTabPageViewControllerProtocol { vc.scrollView.contentOffset.y += scroll } } ``` Protocolについて 下記のことを実現するためにPageViewControllerにProtocolを宣言しています。 ・PageViewController側からChildViewControllerのscrollViewを触りたい ・ChildViewControllerのUIScrollViewDelegateが呼ばれた時にPageViewControllerのメソッドを呼び出したい このProtocolを宣言することでPageViewController内でcontentOffsetやcontentInsetを調整することができるのでChildViewControllerに処理を書くことを減らせます。 ``` swift // PageViewController.swift ScrollTabPageViewControllerProtocol { var scrollTabPageViewController: ScrollTabPageViewController { ge } var scrollView: UIScrollView { get } } ``` B: ViewController ViewControllerはPageViewControllerProtocolと以下の2つの処理を書けば動くようになっています。 ``` swift // ViewController.swift override func viewWillAppear(animated: Bool) { super.viewWillAppear(animated) scrollTabPageViewController.updateLayoutIfNeeded() } func scrollViewDidScroll(scrollView: UIScrollView) { // スクロールしたらPageViewControllerに通知してContentViewのフレームを動かす scrollTabPageViewController.updateContentViewFrame() } ``` C: ContentView ContentViewは以下のような構成になっています。 scrollの量を渡すためだけならUIViewだけの実装で"userInteractionEnabled = false"にすれば動かすことは出来ますが、UIButtonなどのアクションをするパーツを置いた時の制御が難しかったのでUIScrollViewを実装しています。 ``` swift // ContentView.swift var scrollDidChangedBlock: ((scroll: CGFloat, shouldScroll: Bool) -> Void)? func scrollViewDidScroll(scrollView: UIScrollView) { // スクロールした量をPageViewControllerに渡す if scrollView.contentOffset.y > 0.0 || frame.minY < 0.0 { scrollDidChangedBlock?(scroll: scrollView.contentOffset.y, shouldScroll: true) scrollView.contentOffset.y = 0.0 } else { let scroll = scrollView.contentOffset.y - scrollStart scrollDidChangedBlock?(scroll: scroll, shouldScroll: false) scrollStart = scrollView.contentOffset.y } } ``` ・上の方向にスクロールする場合はscrollViewのcontentOffset.yを0にしてフレームだけを動かすようにしています ・逆に下方向にスクロールする場合はフレームを動かさず、scrollViewのスクロール量を渡すだけで調整しています ``` swift // PageViewController.swift // スクロールした量を受け取りContentViewのフレームを動かす contentsView.scrollDidChangedBlock = { [weak self] (scroll: CGFloat, shouldScrollFrame: Bool) in self?.shouldScrollFrame = shouldScrollFrame self?.updateContentOffsetY(scroll) } ``` まとめ ・コンテンツを表示するViewにScrollViewを実装することでアクションを妨害せずにスクロールできる ・UIPageViewControllerを継承したクラスにスクロールの調整の処理を書くことでコンテンツを表示するViewController側は少ないコードを書くだけで済む 一定以上スクロールするとタブの位置が固定されるUIの実装についての紹介でした。 まだ改善しないといけないところはありますが、概ねやりたいことを実現することが出来ました。 最後に VASILYでは、エンジニア&学生インターンを募集しています。少しでもご興味のある方は是非 こちら からご応募よろしくお願いいたします。
こんにちは。VASILYデザイナーの半澤です。 今回はデザインに必要不可欠な写真についてご紹介します。 クオリティの高い写真素材はネットにたくさんありますが、自社のコーポレートサイトや求人用となると、自分たちで撮影せざるを得ません。 VASILYでは撮影をデザイナーが担当しており、絵のキレイさだけではなく「伝わる写真」であるかどうかを意識して撮影しています。 伝えたいことを写真でどう見せているのか、求人のバナーを例に撮影の工程とポイントをご紹介します。 STEP1:ヒアリング 求人のバナー制作依頼が来たら、まず話を聞くことから始まります。 伝えたいことを明確にすることが1番重要です。デザインする上で伝えたいことを言語化すると思いますが、写真も同じことが言えます。 今回の例は求人なので、以下の項目を依頼者からヒアリングします。 ・募集する職種 ・実際にどんな仕事をするのか ・年齢/性別 ・人柄 以上の項目をヒアリングする理由は、「来て欲しい人物像により近いイメージにするため」です。 極端ですが、以下の例を御覧ください。 このバナーはエンジニアの募集に見えますよね。でも、右側では「PUBLIC RELATIONS(広報)」と書いてあります。 広報の募集で黒い画面を開いているPCが写っているこの写真では、イメージが離れすぎてて広報希望の方は応募しにくいと思います。 応募する人にとって「自分が働く姿」をイメージしやすい絵作りが大事 なので、その素材あつめの一つとしてヒアリングを念入りにしておきます。 STEP2:構図を考える STEP1のヒアリングを元に、絵作りにはいっていきます。 1.キャストを決める ヒアリングで来て欲しい人物像を明確にしたので、その人物像に近しい年齢、性別のキャストを決めます。 2.撮影場所を決める キャストが決まったら撮影場所です。求人の写真はバナーとして求人一覧にならびます。毎回社内で同じ場所では人が違うだけで印象が似てしまうので、似すぎないよう心がけています。 3.構図を絵コンテにする キャスト、撮影場所が決まれば構図を考えます。 構図を考える際にも意識することは 応募する人にとって「自分が働く姿」がイメージしやすい絵 なので、なるべく働いている場面を再現するようにしています。 働いている場面といっても、PCに向かって仕事をしている絵だけではなく、複数人で話をしている姿を撮るようにしています。 複数人だと自然な表情が撮りやすいというメリットもありますが、VASILYで働いている人たちの雰囲気を表現するためでもあります。 以上を意識して、ざっくりと絵コンテにしていきます。こちらの絵コンテはデータサイエンティスト募集の時使用したものです。 複数人出演してもらうので、それぞれのキャスト立ち位置や、メインの人物がわかるように描いています。 絵コンテを描くことによって、撮影時にキャストへの立ち位置の説明が楽になります。 4.必要であれば小道具も考える より具体的にイメージを伝えるために小道具の使用も効果的です。例えば「データサイエンティスト」の求人用に撮影したときはホワイトボードに計算式を書いてもらいました。 キャストの年齢や性別、表情だけでは伝えきれないことは小道具も使って雰囲気作りをしましょう。 構図については以上ですが、加えて忘れていけないのは キャストへの事前の連絡 です。 服装や髪型も雰囲気作りにとても重要なので、どういう絵を撮りたいのか求人の概要と共に伝えどのような格好で参加してほしいのか伝えます。 例えば営業の求人用に撮影したときには男性は襟つきのシャツ着用で参加のお願いをしました。 実際に職種が営業の人が写真に出ていても、文脈を知らないひとには伝わりません。 「営業っぽさ」を演出する必要があるので、襟つきのシャツを着用してもらいました。 以上のことから、キャストへ事前に撮影の概要と共に、服装や髪型も事前に伝えておきます。 STEP3:撮影 いよいよ撮影です。 1.撮影準備 キャストを集めるということは、それだけ コストがかかっている ということです。 なるべく時間がかからないよう、撮影前の準備は念入りにしておきたいです。 決めた撮影場所でどの角度で撮るのか、明るさはどれくらい、邪魔なものがないか、あらかじめ確認し準備をしておきます。 2.撮影 撮影は30分以内で終わらせるようにしています。 概要説明 5分 テスト撮影 5〜15分 本番 10分 キャストに概要説明をする際に便利なのが、構図を考えたときのコンテです。 コンテを見せながら、「こういう人材がほしいから、こういう構図で、◯◯な雰囲気でとりたいです。」とキャストに伝えます。 しかし、紙に書いた構図で一発できまるわけではありません。 私はテスト撮影をしながらキャストからもフィードバックをもらうようにしています。 フィードバックをもらう時に便利なのが Eye-fi です。Eye-fiとはWi-fi内臓のSDカードで、撮った写真がリアルタイムで好きなデバイスに転送することができます。 カメラの小さいモニターでキャストに都度見てもらうのではなく、PCの画面で見てもらう事が可能です。 小道具で使用しているPCに転送すると撮影を止めることなく、キャストからフィードバックを貰えます。 フィードバックを受け、構図が決まれば本番です。ここまで念入りに準備をしたので、本番は実際10分もかからずサクッと終わります。 3.デザイン 撮影が終わればチェックです。撮影時には気にならなかった細かい髪の乱れや、洋服のヨレなど、レタッチする箇所をチェックします。 レタッチが完了すれば、デザイン作業です。 VASILYの求人バナーではデザインを統一するようにしています。 写真で職種ごとのイメージを伝えつつ、VASILYという会社のイメージもバナーで表現するためです。 最初にデザインテンプレを複数作り、どれがVASILYらしいのか検討し、現在掲載しているデザインになりました。 しかし、一部デザインが違う例もあります。 上の例はiOSエンジニアの求人バナーです。 アプリエンジニアは、それぞれのOSの専門だとわかりやすくする為に筐体を入れています。 必要に応じてレイアウトを変えていますが、一覧で見た時に統一感が出るようにデザインしています。 おわりに いかがでしたでしょうか。 求人のバナーを例に「伝わる写真」の撮影フローを紹介しました。 あくまで写真は伝える手法の1つで、目的は 応募しようとする人へ自分が働いているイメージが伝わり一緒に働きたいと思ってもらうこと です。 撮影を重ねる度にデザインと同様に伝えるべき内容を明確にし、写真でどう表現するのかが重要だと感じています。 VASILYではクリエイティブを通してユーザーに最適な価値を届けるために、あらゆるアプローチをしてくれる新卒デザイナーを募集しています! ご応募お待ちしております! https://www.wantedly.com/projects/5485 連絡先:info[at]vasily.jp
Carthageとは? こんにちはVASILYでiOSエンジニアをしているニコラスです。 今回はCarthageの紹介をしたいと思います。 Carthage はiOS / OS X開発のための分散型の (decentralized) パッケージマネージャーです。 既にプロジェクトでCocoaPodsを使っていても、同じプロジェクトでCarthageを使うことができます。 CarthageはCocoaPodsと違って中央レポジトリ型ではないので高速です。 また、100%Swiftで作られているため信頼性が高く、欧米のプログラマーコミュニティで特に盛んに利用されています。 Carthageの基本を学べば(たった5分で)すぐ使えるようになります。 Carthageを使うメリット コミュニティの活発さ Carthageのコミュニティはとても活発で、バグがあればだいたい1日以内に修正のPRが送られています。 環境依存性の低さ Carthageは100%Swiftでできているので、CocoaPodsと違って、環境ごとのRubyバージョンの違いによる問題に悩まされることがありません。 Swiftでできているので、Rubyが書けなくてもiOS開発者がOSSに貢献することができます。 (私も Pull Request を送りました。) シンプルさ Carthageはダウンロードしたらすぐに使うことができます。CocoaPodsの場合はRubyやRubyGemの依存関係により、CocoaPodsのコマンド実行に失敗することがあります。 インストール Homebrewでインストールする方法 Carthageをインストールする一番簡単な方法は Homebrew です。 [bash] $ brew install carthage [/bash] 直接ダウンロードする Homebrewがなくても、GitHubから carthage.pkg をダウンロードすることができます。 Releases Page ソースコードからインストール 最新の開発バージョンをインストールすることもできます。 [bash] $ git clone git@github.com:Carthage/Carthage.git $ cd Carthage $ make install [/bash] ライブラリをインストール Cartfileを作成 Cartfileに含めたいライブラリを書いてください。 CartfileはCocoaPodsのPodfileやBundlerのGemfileと似たようなものです。 データフォーマット CocoaPodsではインストールするライブラリのリストをPodfileというRubyのDSLで管理します。 Carthageでは、Cartfileというファイルに記述します。 これは OGDL というデータフォーマットで書かれていて、 YAML と似たようなフォーマットです。 Cartfileの文法 一つ目のキーワードに github か git を指定します。 GitHubでホストされている場合は github を使い、それ以外でホストされている場合は、 git を使います。 github キーワードでは、ブランチ名の指定やバージョンを制限することができます。 [bash] github "Username/RepositoryName" "BRANCH_NAME" OR == / >= / <= / ~> [VERSION_NUMBER] [/bash] 実例 [bash] github "ReactiveCocoa/ReactiveCocoa" "master" #最新のmasterブランチをインストール github "rs/SDWebImage" ~> 3.7 #v3.7.xx の最新版をインストール github "realm/realm-cocoa" == 0.96.2 #v0.96.2をインストール [/bash] コマンドを実行してライブラリをインストール プロジェクトのルートディレクトリで下記のコマンドを実行してください。 [bash] $ carthage bootstrap #ios, mac, watchos, tvosなど、プラットフォームを指定することもできます。 $ carthage bootstrap --platform ios [/bash] 正常に完了すると、[bash] Carthage/Build , Carthage/Checkouts [/bash]という2つのフォルダが作成されます。 Carthage/Build .framework , .framework.dySYM がここに生成されます。 Carthage/Checkouts ライブラリのソースコードファイルがここにダウンロードされます。 Xcode設定 Run Script Phase を追加する プロジェクトファイルを開く TARGETS内のプロジェクト名を選択 Build Phasesを選択 左上の「+」ボタンをクリックから"New Run Script Phase"を選択 コマンド入力欄に /usr/local/bin/carthage copy-frameworks と入力 Input Filesのところにインストールするフレームワークを追加します。 .frameworkをリンクする プロジェクトファイルを選択 Generalを選択 Linked Frameworks and Libraries にCarthageフォルダからフレームワークを選択して追加します。 これでライブラリのインストールが完了し、アプリをビルドすることができます。 Git管理 - Carthage運用のコツ 基本的にgitにコミットするべきものは Cartfile と Cartfile.resolved と Carthage フォルダです。 その理由として、いくつかのメリットがあります。 git clone した時や、ブランチを切り替えるたびに carthage update をする必要がありません。 また、プロジェクトに新しいメンバーが入った時など、 carthage update するタイミングごとにCartfileのバージョンが違ってしまうといったことが避けられます。 まとめ 今回はCarthageの導入方法を紹介しました。 CocoaPodsは根強い人気がありますが、個人的にはiOS開発の本質とは関係ないところでいろいろ問題がありました。 (RubyGemsのバージョンごとのコンフリクトであったり、CocoaPods自身のアップデートでPodsのレポジトリが破損したりなど) CarthageはSwiftで作られているため、Swiftのライブラリしか依存関係がありません。 CocoaPodsの運用で苦労している方は、ぜひCarthageを使ってみてください。
こんにちは、神崎です。今年の6月頃にアドサーバーのアプリケーションサーバ群をAWS ElasticBeanstalk w/ Dockerに置き換えをおこないました。 これにより、アプリケーションレイヤーのauto-scaling環境の構築、deployのフローの自動化、rubyなどのMiddlewareの入れ替えが比較的容易にできる環境になりました。 既存の環境は、以前ブログで紹介したとおり、EC2上にruby2.0でunicorn+sinatraで構築していました。 - iQONの広告配信システム - VASILY DEVELOPERS BLOG 構成 Containerの内部構成は下記のようになっており、中央にある、ad-serverがアプリケーションの実態のContainerになります。それ以外は、nginx,mackerel,fluentdのContainerがあり、それぞれが特定の役割を担ってアドサーバー全体を構成しています。 デプロイフローは以下のようなフローになり、特定のbranchのgithubにpullrequestをおくりmergeされると、hookされて、eb deployがはしり、docker containerがdeployされます。 またアプリケーション用のDocker imageは、baseとなるrubyとsupervisorをbuildしたimageを作成し、さらにアドサーバで必要なgem類を構築したimageを作成しています。baseのimageとアプリケーションのimageを分けているのは、アドサーバー以外のアプリケーションでも流用しやすくするためです。 外部サービスの利用 コードの管理にはgithub、DockerRegistryに quay.io 、CIに CircleCI を利用しています。これらを連携させることで、上記のように自動でdeployしたり、auto-scalingさせたりを実現しています。 トラブル ElasticBeanstalk+Docker環境に移行してから、概ね大きな問題も起きずにいたのですが、数回トラブルも発生しました。 docker-pullに失敗してダウン 一番大きなトラブルとしては、人的ミスが大きな原因ですが、DockerRegistryとして利用しているquay.ioのパスワードを変更したために、docker pullに失敗して、デプロイができず、アドサーバー全体がダウンすることがおきました。 dockercfgファイルにquay.ioのマスタのaccountを設定してしまっていたため、quay.ioのパスワードを変更したタイミングで、invalidな状態となり、docker pull時にログインに失敗。 また、auto-scalingにより大量にdocker pullが行われるタイミングがあったため、quay.ioのaccountがロックされ、しばらくの間、docker pullができない状態が発生してしまいました。 quay.ioにはdeploy用のrobot accountが設定できるので、そちらを利用することで、上記のような問題は起きないようにしました。 deployに失敗 eb deploy prod でebコマンドがデプロイ時にs3にputするソース一式のファイルの名前がconflicして、デプロイに失敗しました。ebコマンドのs3にputするファイル名は、gitのcommithashの4文字で固定されているので、それが重複して発生しました。 このときは、ElasticBeanstalk+Docker環境を構築中にebコマンド以外で作ったファイルと重複してしまった様子だったので、常にebコマンドでdeployを実行すれば防げるものだと思います。デプロイに失敗するだけならまだ良いのですがフロー的には、コードがデグレードした状態でデプロイされることも考えられるので、そこは気を使う必要があります。 また、Elasticbeanstalkのapplication versions(ソース一式)は500個が上限なので、定期的にWebConsoleから掃除してあげたほうが安心です。 ruby2.2.3へ Docker化したので、rubyの入れ替えもやりやすくなりました。そこで先日ruby2.2.3へ入れ替えを実施しました。 下準備としては、ベースとなるDocker imageをruby2.2.3でdocker buildしてそれを元にしてアドサーバーに必要なものをセットアップしたDocker imageを作成することで完了です。 デプロイには、ruby2.2.3を含んだDocker imageのsha1 hashを、Dockerrun.aws.jsonで指定してあげるだけで、入れ替えのデプロイ作業は完了です。アドサーバーはもともと、広告の配信と各種イベントの処理だけで、そこまで複雑なことはしていないので、ruby2.2.3にしたことによるコードの変更は、利用しているgemのupdateの対応を一箇所しただけでした。 ruby2.2.3による変更で遅くなっては意味が無いのですが、今回は、GCの最適化の恩恵をうけることができました。アドサーバーは50msec以下のレイテンシを最低ラインにしており、GC Executionが5~10msecかかっていたのが1msec以下に縮小しました。 以上、アドサーバーをElasticBeanstalk+Dockerに移行し、ruby2.2.3へ入れ替えの紹介でした。 最後に VASILYでは、新しい技術が大好きなエンジニアを募集しています。少しでもご興味のある方は是非 こちら からご応募よろしくお願いいたします。
みなさんこんにちは、今村( @kyuns )です。今回は弊社の新規サービス開発にて、Hashicorp製品を中心にインフラ周りを整えたお話をしていきたいと思います。今回はTerraformとAtlasの話が中心になります。 今回実現したこと TerraformでAWS上のリソースをコードで管理 GithubでPullRequestを作ってインフラに対する変更をコードベースでレビュー Github上でPullRequestに対して変更がテストされ、テスト結果が貼られる Pull Requestをマージすると自動的にAtlas経由でterraformが実行されてインフラの変更が適用される いわゆるインフラのコード化&自動化です。 導入によるメリット インフラがコードで管理されることにより属人性を排除することができる インフラの変更に対して事前にレビューすることにより事故を減らせる 変更の適用はPullRequestのmergeボタンを1つ押すだけなので格段にデプロイの手間が省けて安全(etc) 今回は0からのインフラ構築ということもあり、HashicorpのTerraformとAtlasを利用して上記を実現してみることにしました。それでは順を追って説明していきたいと思います。 Terraformって何? Terraform とはHashicorpが提供している Infrastructure as Code を体現するためのツールです。今回は新規サービス開発ということもあり、ちょうど0から作るいいタイミングであった為、採用に踏み切りました。TerraformはAWSのほとんどの設定に対応しているため、コードベースでインスタンスをたてたり、ELBやS3の設定などのAWSのリソースを管理することができます。 Terraformの導入 インストール ダウンロード ダウンロードページ から各プラットフォーム別のバイナリを取得します。プログラム自体はGoのバイナリなので好きなフォルダに展開後、PATHを通しておきましょう。 また、ruby gem経由でもインストールすることができます。 $gem install terraform $terraform -version Terraform v0.6.7 現時点(2015.11.27)での最新版は0.6.7です。 AWSクレデンシャルの設定 AWSのログインクレデンシャルを環境変数に設定しましょう。 bash/zshならexport、fish shellならsetenvを利用します。 export AWS_ACCESS_KEY_ID=XXXXX export AWS_SECRET_ACCESS_KEY=XXXXX export AWS_DEFAULT_REGION=ap-northeast-1 setenv AWS_ACCESS_KEY_ID XXXXX setenv AWS_SECRET_ACCESS_KEY XXXXX setenv AWS_DEFAULT_REGION ap-northeast-1 以後、terraformコマンドを実行する際にこのアカウントの設定が利用されます。 Terraformを理解する Terraformの構成 Terraformを理解するのに重要なファイルが2つ存在します。 .tf ファイルと .tfstate ファイルです。 .tfファイル terraformの設定を記述するファイルです。 .tfstateファイル terraformコマンドを実行した後、現在のインフラの状態が記述されたファイルです。tfstateファイルはterraformを実行すると自動的に生成されるので通常は触る必要はありません。 また、terraformコマンドを実行すると自動的に tfstate.backup というバックアップファイルも生成されます。 Terraformを使ってEC2インスタンスを立ててみる tfファイルの記述 Terraformを使って試しにインスタンスを立ててみます。 新しくインスタンスを立てる場合、例えばec2の設定は以下の様な感じになります。 ec2.tf resource "aws_instance" "web01" { ami = "ami-6927d769" availability_zone = "ap-northeast-1c" ebs_optimized = false instance_type = "t2.medium" monitoring = false key_name = "iqon" subnet_id = "subnet-4ee15817" vpc_security_group_ids = [ "sg-2f765a4a" , "sg-55755930" ] associate_public_ip_address = true private_ip = "10.0.0.10" source_dest_check = true root_block_device { volume_type = "gp2" volume_size = 20 delete_on_termination = true } tags { "Name" = "web01" } } 非常に直感的ですね。設定を記述できたらterraformコマンドを実行してみますが、その前に設定を確認してみましょう。 設定を確認してみる(Dry run) terraformにはDry runの機能があります。 予め適用する内容を出力してくれるので、事前にどのような変更が起きるかを確認することができます。 $terraform plan 気をつけなければならないのですが、完璧なDry runというわけではなくplanでうまくいっても実際に実行すると失敗するということもありますので、あくまでも目安程度に捉えておいたほうが良いと思います。 コマンドを実行する 先ほど作ったec2.tfファイルを実行するためにはapplyコマンドを使います。 $terraform apply これでしばらくするとEC2インスタンスが作成されます。 ec2のほかにもS3やRDS、ELBなどあらゆるAWSサービスに対応しています。 既存のAWSインフラの設定からtfファイルを生成する 完全に0からインフラを構築する機会はそんなにないと思うので、殆どの人は既存のインフラに少しずつ適用して試してみたいと思うのではないでしょうか。また既存のAWSの設定からtfファイルを生成したい場合はどうしたらいいでしょうか?公式ではそのようなexportの機能はサポートされていないので3rdParty製のツールを使います。 terraforming という便利なgemがあるので今回はこちらを使ってみます。 gem install terraforming 試しに現在のs3の設定を取得してみます。 $terraforming s3 resource "aws_s3_bucket" "iqonsample" { bucket = "iqonsample" acl = "private" } resource "aws_s3_bucket" "iqonsample.test" { bucket = "iqonsample.test" acl = "private" } このように $terraforming サービス名 を指定すると、自動的に現在のAWSのリソース設定からtfファイルの形式で出力してくれます。また、同時に tfstate ファイル形式を出力する機能も備えています。tfstate形式の場合はオプションで --tfstte を指定します。 $ terraforming s3 --tfstate { "version" : 1, "serial" : 1, "modules" : [ { "path" : [ "root" ] , "outputs" : { } , "resources" : { "aws_s3_bucket.iqonapi" : { "type" : "aws_s3_bucket" , "primary" : { "id" : "iqonapi" , "attributes" : { "acl" : "private" , "bucket" : "iqonapi" , "force_destroy" : "false" , "id" : "iqonapi" , "policy" : "" } } } , "aws_s3_bucket.iqonapi-test" : { "type" : "aws_s3_bucket" , "primary" : { "id" : "iqonapi-test" , "attributes" : { "acl" : "private" , "bucket" : "iqonapi-test" , "force_destroy" : "false" , "id" : "iqonapi-test" , "policy" : "" } } } } } ] } 現在のAWSの状態からtfstateファイルの出力までしてくれます。 こちらを既存のtfstateファイルにmergeする場合は --merge オプションを利用してマージすることもできます。 AtlasとGithubでインフラを自動化する Terraformで設定を記述した後は、次にAtlasとGithubを利用してインフラを自動化します。 ATLAS はHashicorpが提供するクラウド型の統合プラットフォームであり、Vagrant,Packer,TerraformやConsulなどの機能を内包しています。ここではATLASとGithubを連携させてGithub上でPRを出した時点でterraform planの実行結果がPRに貼られ、mergeすると自動的にATLAS経由でterraform applyが実行されるのを目指します。 Atlasの設定 まずはAtlasにアカウントを作成します。 次にCreate and manage infrastructure with Terraformをクリックして設定に進みます。 画面に表示されるATLAS_TOKENを環境変数に設定します。 export ATLAS_TOKEN=XXXXXXXXXXX TerraformプロジェクトをAtlasにpushする 次にローカルにあるterraformプロジェクトをAtlasにプッシュします。(チュートリアルに従うとexampleになる) $ terraform remote config -backend-config "name=vasily/example" Remote configuration updated Remote state configured and pulled. Configuration "vasily/example" uploaded! (v1) 設定がAtlasにプッシュされました。 この仕組みは、terraformのtfstateファイルを リモートで管理する機能/terraform remote を利用したものです。 注意点ですが、この段階でローカルにあるtfstateファイルは削除されます。(Atlas上で管理されます) 自動デプロイ用の設定をする Auto applyの設定 Github上でデフォルトブランチにmergeされた際に、terraform applyを自動実行するかどうかの設定ができます。ここでは自動化をするのでAuto applyにチェックをいれます。 Githubとの連携設定 次にgithubのイベントをフックできるようにAtlasの管理画面のサイドバーにあるIntegrationにてGithubの設定をします。 レポジトリのルートディレクトリにterraformの管理ファイルがない場合は、 サブディレクトリを指定することもできます。 環境変数の設定 次に管理画面のVariablesにて環境変数を設定します。 access_key,とsecret_keyを指定します。 GithubでPRを作成してみる Atlas側で設定ができたらGithubに実際にPRを作成してみます。 PRのTESTの部分にTerraformでのterraform planの実行結果が表示されます。 PRをマージしてterraform applyが実行されるのを確認する。 Github側でPRをマージするとあとは自動的にAtlas側で terraform applyが実行されます。もちろん管理画面からでも実行結果が確認できます。 これでGithubでPR作成->mergeからAWSへの反映まで自動化することができました。 terraform applyに失敗した場合 このようにterraform planが成功してもapplyが失敗する可能性があるので、手動でapplyする場合はコンソールからQueue Planを押すと再度実行されます。 まとめ おすすめの導入方法 いきなりインフラ全てをTerraformで管理する、というのは0からインフラを作る時以外はあまりオススメできません。 TerraformはほとんどのAWSリソースに対応しています、よって全てをコードで管理できるわけではありますが、全てをTerraformで管理しようとすると個人的には結構事故の原因になりかねないなぁと運用してみて感じています。 例えばVPCのサブネットやインターネットゲートウェイの設定などの変更頻度のかなり低いものは場合に応じてTerraformで管理しない、という使い方もありだとは思います。 すでにAWSのインフラがある人は、まずは前述のterraformingを用いて既存のEC2インスタンスの設定などからtfファイルをつくり、同じ設定でインスタンスをたてたりするところから始めて見ると導入しやすいかなと思います。 また、Terraformへの完全移行の際には、tfstateファイルとの差分を排除するために、AWSのコンソールからポチポチというのは無くすというルールをつくって徹底する必要があります。(IAMとかをきちんと管理する) 導入後の感想 Terraformによって運用に必要な部分のインフラがコード化され、インフラの変更に対してもPRで確認してから適用できるようなったことで格段に属人性を排除することができました。また、変更の適用はGithubでPull Requestをmergeするだけで自動的にTerraformが実行されるようになり、安全性も確保することができました。 今日は紹介しませんでしたが、今後は弊社でのPackerやConsulまわりの取り組みもご紹介できればと思います。 VASILYではHashicorp製品をつかってインフラで新しいチャレンジをしてみたいエンジニアを募集しています。 ぜひとも一緒に取り組みましょう。興味があるインフラエンジニアはぜひ こちら から応募してみてください。
先日、弊社がAndroid版iQONの開発に使用しているサービスとツールについて紹介させていただきました。 Android版iQONの開発で利用しているサービス&ツールを紹介します その中でアプリの動作確認にSORACOM Airを導入したとご紹介しましたが、今回 SORACOM ユーザーグループ発足記念リレーブログ の11/19日分として、もう少し掘り下げてご紹介しようと思います。 SORACOM Airを導入しようと思ったきっかけ VASILYでは、 Qiita:Team を導入しており、職種を問わず全員がDaily Reportと呼んでいる弊社テンプレートでの日報(以下、DR)を書いています。詳細は こちら をご覧ください。DRには、テンプレで困ったことや思ったことを書く欄があるのですが、ある社員がDRで以下のように書いていました。 http://ramenandicon.hatenablog.com/entry/2015/10/30/091352 本文とは趣旨が違うのですが、冒頭に「月末と言えば7GB超過の通信速度制限。」とあり、IT系企業従事者以外にとっては、自宅にWifiがなかったり、通信速度制限が当たり前の文化としてあるのかもしれない=通信速度をもっとシビアに意識したほうがいい、と感じました 10月はあえて自宅のWifiを切って生活してみたのですが、そうなると月の半分ほどで通信速度制限がかかり、その状態だと使うアプリもシビアに選ぶようになったので、実感をもてた気がします(意識的には乗換えや検索系は必須で使うので再優先で残したいと思い、それ以外の通信が発生するアプリは立ち上げなくなりました) ちなみに速度制限がかかった状態の4GでiQONを立ち上げると、ホーム画面が開くまで平均して3秒〜5秒ほど待ちます、自宅にいるときに使うとそこまで感じないですが、電車などで暇つぶしに立ち上げようとしたときには「かなり遅いな」という印象です このDRを読み実際に調べてみたところ、iQONユーザーの中にも通信速度の低下で快適にサービスを利用できないユーザーが一定数以上存在することがわかり、帯域制限によって通信速度が低下した場合のアプリの挙動について調査・改善することになりました。 しかし、帯域制限を受けた状態は再現しにくく、Facebookの ATC のようなOSSはありますが、より簡単に帯域制限を受けた状態を再現できる方法を探しました。 その結果注目したのがSORACOM Airです。SORACOM Airの開発者が通信速度を自由に変更できるという特徴を利用して帯域制限を受けた状態を再現し、アプリの動作確認を行う事にしました。 導入してみた結果 SORACOM Airを導入することで通信速度を制限した動作確認が簡単に行えるようになりました。ユーザーコンソールからSIM単位で簡単に速度の変更を行え、非常に便利です。実際に速度クラスをs1.slowに設定してアプリを起動してみた結果は以下の通りです。s1.fastを指定した時と比較して、ホーム画面を表示するまでの時間が平均で3倍程度かかることがわかりました。また、画像の表示完了にはより長い時間がかかることがわかります。 以下はs1.fastに設定してアプリを起動した結果です。 SORACOM Airの速度クラスをHubotから変更 また、弊社ではSORACOM Airの速度クラスをSlackからHubot経由で変更できるようにもしています。 Hubotのコードは以下の通りです。SORACOM API Clientには soracom を使用しました。AWS Lambdaを経由して通信量が一定を超えたらSlackに通知する等もやりたいと考えています。 まとめ SORACOM Airは開発者がSIMの操作を簡単に行えるので非常に便利です。また、APIドキュメントもわかりやすく、簡単に利用することができました。今後、SORACOM Airを使用し、通信速度が遅い場合でも快適に利用できるようサービスを改善していきたいと思います。 最後に VASILYでは、新しい技術が大好きなエンジニア&学生インターンを募集しています。少しでもご興味のある方は是非 こちら からご応募よろしくお願いいたします。
こんにちはVASILYエンジニアの松本です。先日 MERY を運営する株式会社ペロリと合同で Fashion Tech meetup #1 と題した勉強会を開催しました。 当日は約100名のエンジニアの方々に集まっていただき、Fashion × Technologyを題材として各社のエンジニアが、お互いのサービスを支える技術について発表しました。今回はFashion Tech meetup #1でのVASILYの発表資料をご紹介します。 iQONを支えるクローラーの裏側 iQONでは提携先ECサイトからアイテム情報をクロールしています。VASILYでは2015年の夏にクローラーの仕組みを大幅に変更することによって、1ヶ月間で400サイト分のクローラーを製作することができるようになりました。エンジニア以外のメンバーでもクローラーを作ることができるようにしたそのシステムの裏側を紹介。 iQONを支えるクローラーの裏側 from Takehiro Shiozaki トピックモデルを用いた 潜在ファッション嗜好の推定 自然言語処理の潜在意味解析の分野で主に文章解析に用いられるトピックモデルを用いて、ユーザーの行動データからファッションの嗜好を分析。分析結果からユーザーのペルソナを定義するところに至るまでを紹介。 トピックモデルを用いた 潜在ファッション嗜好の推定 from Takashi Kaneda OpenCVを使ったiQONの画像処理の全容 iQONでクロールしたアイテム画像がコーディネートに使われるまでの画像処理の全容を公開。モデル画像、トルソー画像の機械学習を用いた自動判定。さらにECサイトの画像から物体領域を抽出し背景の透過処理を行うまでの手法を公開。 OpenCVを使ったiQONの画像処理の全容 from Kazuki Matsumoto iQON ADのfluentd, mackerelを使ったモニタリング iQONの広告配信システムiQON ADのモニタリングシステムを公開。fluentdを用いたログ集計方法とmackerelでの監視方法を紹介しています。 Google Cloud Test Labによるテスト自動化 今年のGoogle I/Oで発表されたクラウドベースのAndroidアプリのテスト基盤である、 Google Cloud Test Lab について発表。具体的にどのようなテストを実行可能なのかや、Web UIやAndroid Studio、CLIからのテストのスケジューリング、テスト結果の解析、そして弊社で実際に使用しての感想について紹介しました。 最後に 今回の勉強会では懇親会とLTセッションまで多くの方々に参加していただき、大盛況に第一回を終えることができました。これからもFashion Tech meetupは定期的に開催し、ファッション領域に取り組むベンチャー企業を盛り上げていこうと思っています。今回の勉強会に参加できなかった方々も是非次回ご参加ください。 また、今回の勉強会の会場と懇親会での軽食・飲み物はすべて株式会社ディー・エヌ・エーにご提供いただきました。本当にありがとうございました。 VASILYではFashion × Technologyに興味のあるエンジニアの方々をお待ちしています。一緒に最新のテクノロジーでファッション業界を盛り上げていきましょう。 VASILYのエンジニア募集要項はこちら
こんにちはVASILYエンジニアの松本です。VASILYではクローラーの仕組みを大幅に見直した際にDynamoDBの導入を行いました。今回はその導入方法とDynamic DynamoDBを用いた運用方法について話したいと思います。 DynamoDBを導入した理由 iQONではクローラーで取得したデータをDynamoDBに保存しています。DynamoDBを導入した理由は以下の通りです。 ・ ECサイトごと、さらには商品ごとにクロールするデータの形式が異なるためスキーマレスである必要があったこと。 ・ DynamoDBはデータベース容量が増大した際も自動でスケールしてくれるのでメンテナンスコストがかからないこと。 ・ 平均レイテンシーは1桁台のミリ秒単位であること。 iQONでは1日約80万点のアイテムをクロールしているので、メンテナンスコストがかからず、ある程度のパフォーマンスが担保できることがDynamoDBの魅力でした。DynamoDBに関する説明は このスライド に詳しく書いてあります。 DynamoDBの導入手順 AWS SDK for Rubyを使ったDynamoDBの利用 まずaws-sdkのgemをインストールします。 DynamoDBはサーバーにインストールして使うものではなくあくまでサービスなので、開発環境やCircleCIなどのテスト環境ではDynamoDBと互換性のある DynamoDB Local を使います。 テーブルを作成 クロールしたアイテムを保存するテーブルを以下のように作成します。AWSコンソール上からでもテーブルの作成は可能ですが、DynamoDB Localにも同様のテーブルを作成したかったのでその方法を記載しました。 DynamoDBは基本的にはスキーマレスなのですが、HASH keyとRANGE keyについてはスキーマを定義する必要があります。DynamoDBを使いこなすためのkeyやインデックス定義の仕方は この記事 が参考になります。 データの書き込みはこんな感じ HASH keyとRANGE keyの二つをスキーマに定義した場合は二つのkeyで値を取り出す。 CircleCIへのDynamoDB Local導入 VASILYではテストツールとしてCircleCIを利用しています。 CircleCIはDynamoDBを標準ではサポートしていないため、テスト時にDynamoDB Localを動かすように設定する必要がありました。 まずはDynamoDBをubuntuに入れるシェルスクリプトを記述。 circleci.sh dynamodbディレクトリがなければAmazonから最新版を取得して展開するようにしています。 circle.ymlでDynamoDB Localを起動するように指定します。 circle.yml dependenciesのcache_directoriesに展開済のdynamodbディレクトリをキャッシュするように指定し、次回以降のCircleCIのセットアップの時間を短縮することができます。 あとは、preでDynamoDB Localを起動させればOKです。 参考にした記事 Dynamic DynamoDBを用いたプロビジョニング量自動調整 DynamoDBの利用料金はストレージ容量、転送容量、書き込み・読み込みスループットのプロビジョニング量で決まります。詳しい説明は 公式ドキュメント にあります。その中でも、プロビジョニング量の課金が一番料金を左右する部分です。AWSコンソール上でプロビジョニング量は手動で調整できるので、最初はとりあえず余裕を持って設定しました。(赤線がプロビジョニング量、青が実際に発生したスループット) しかし、書き込み・読み込みスループットは時間帯や時期によって大きく変動するので、プロビジョニング量を大きめに設定すると無駄な料金が発生してしまい、小さくしてしまうと性能劣化を招く危険性があります。そこで、プロビジョニング量をスループットの増減に応じて自動で調整する Dynamic DynamoDB を導入しました。導入の際に参考にしたのは この記事 です。 Dynamic DynamoDBで指定できるオプションについて DynamoDBはconfigファイルの設定に様々なオプションを設定することができます。オプションが多すぎるので、最低限のものだけ設定しました。 dynamic-dynamodb.conf その他のオプションについては こちらのドキュメント を参照してください。 これらのオプションを設定し、Dynamic-DynamoDBを動作させたところ以下のようにプロビジョニング量がスループットに応じて自動調整されるようになりました。 まとめ DynamoDBはフルマネージドサービスなのでメンテナンスコストもかからず、パフォーマンスも高いことが一番の利点です。しかし、部分的にスキーマ定義を行う必要があることから、スキーマレスであるmongodbと全く同じ使い方ができるわけではありません。詳しくは MongoDBとDynamoDBの性能比較記事 を参照してください。 ただし、あくまでKVS的な使いかたで利用するのであればメンテナンスコストの低いDBとして幅広く使うことができます。今回紹介したDynamic DynamoDBも併用すれば利用金額も大幅に節約することができます。 VASILYでは今回の事例のように新しい技術を使って課題を解決していけるエンジニアを募集しています。一緒に新しい技術をどんどん取り入れて、ファッション業界にイノベーションを起こしていきましょう。 VASILYのエンジニア募集要項はこちら
Androidアプリを効率良く開発する上で各社様々な外部サービスやツールを利用していると思います。今年の6月にGoogleからトップデベロッパーに選ばれた弊社VASILYでも効率化のため多くのサービスやツールを利用しています。 今回は 2015年11月 の今、実際にAndroid版の iQON で利用しているサービスやツールを紹介したいと思います。 ログ解析 ・ Puree ・ BigQuery 数ヶ月前までアプリのログ解析に関しては Localytics を利用していましたが、アプリの規模が大きくなってきたことと優秀なデータサイエンティストが会社にジョインしてくれたことで、自社で生のログを管理した方が都合良くなってきました。そのためLocalyticsをやめて新しい仕組みに置き換えました。 新しい仕組みはクックパッド社で開発されたログコレクタのライブラリであるPureeを利用して自社のサーバーにログを送り、そこからBigQueryに流す形になっています。 そこからBigQueryでSQLを実行して数値を取ったり、別のサービスを使ってBigQueryからデータを取得したりして分析を行っています。 クラッシュ解析 ・ Crashlytics クラッシュ解析に関してはCrashlyticsを利用しています。 これまでCrashlytics -> BugSence (現 Splunk MINT ) -> Google Analyticsといろいろ他社のサービスも利用していましたが、レポートの即時性や見やすさなどを考慮して、結局Crashlyticsに戻ってきました。 今ではアプリのリアルタイムの動向が見られるAnswersという機能と、後述するBetaという機能が入ったため非常に役立っています。 Twitter社が提供するFabricに統合されたため導入するとアプリ全体のメソッド数がかなり増えてしまいますが、 Multi-dex Support していたり、メソッド数に余裕があったりするプロジェクトでは利用する価値があるサービスだと思います。 リリース作業・テストの自動化 ・ Beta 社内テストや検証などでAPKの配布を行う場合にはCrashlyticsの機能であるBetaを利用しています。 過去には他社サービスを利用していた時期もありましたが、iOSで先にBetaを利用していたこともあり社内でサービスの統一ができた点と、Crashlyticsに内包されているためにすぐに利用開始できた点からBetaを採用しました。 ・ CircleCI ・ AWS Device Farm テストやリリース作業の自動化にはCircleCIを利用しています。 Betaと組み合わせてAPKの自動配布を行ったり、Developer ConsoleへのAPKの自動アップロードを行ったりして、作業効率の向上を図っています。 詳しくは こちら を参照してください。 また AWS Device FarmによってE2Eテストを自動化 しています。しかし、先日から Cloud Test Lab の検証を始めましたが、非常に素晴らしいサービスなため今後はこちらに乗り換える可能性が高くなってきました。 *Cloud Test Labは現在、クローズドαテスト中のためGoogleからの完全招待制になっているそうです IDE ・ Android Studio IDEはチーム全員がAndroid Studioを利用しています。 Preview版の頃からAndroid Studioで開発を行っているため、使い始めて早2年になりますがアップデートを重ねて非常に使いやすいものになってきました。 昨年の12月に正式版である1.0が出て現在は1.4まで開発が進んでいますが、これまで様々な機能が追加されてきました。 追加された機能の中でも特に利用しているものが Memory Monitor になります。アプリのメモリ使用量をリアルタイムに表示してくれるため開発を行う上で非常に役立っています。ただし異常を発見した際、詳細を追うには従来通りに Memory Analyzer を利用する方が効率良いので今後のAndroid Studioのアップデートに期待しています。 CLIツール ・ dumpsysコマンド VASILYでは通年で学生インターンを募集していますが、Androidチームにジョインした学生に最初に教えることは、このdumpsysコマンドの使い方です。 dumpsysコマンドをしっかり押さえていると、自分たちのサービスのコードリーディングが捗るのはもちろんのこと、他社のアプリを覗き見したいときに非常に便利です。 adbには様々なツールがありますがdumpsysは使用頻度が特に多いと感じています。 dumpsysコマンドに関しては マネーフォワード社のブログ によくまとまっているのでぜひ参照してみてください。 UI実装 ・Grid 現在少しずつマテリアルデザインに移行中ですが、マテリアルデザインのUI実装を行う際にデザインから1dpのズレも出さないために Grid というUI確認ツールを作りました。 機能は至ってシンプルで、マテリアルデザインでよく使われる間隔である 8dp の網をオーバーレイ表示で画面全体に敷き詰めるだけになります。 非常にシンプルなツールですが、エンジニアとデザイナー間でUIの確認をする際に非常に役立っています。 簡単に作れて開発効率とUIのクオリティが上げられるのでぜひとも作ってみてください。 ・開発者向けオプション > レイアウト境界を表示 開発者向けオプションの中でもお馴染みの機能ですが、Gridと同様にUI実装を行う際によく使用する機能になります。 MarginとPaddingがどのようにかかっているのかを目視で確認できるためUI実装に役立つだけではなく、コードレビュー時に最適なレイアウトファイルが組めているか確認するときにも役立っています。 その他 ・SORACOM Air 帯域制限をかけた低速な通信速度で動作確認を行えるように、 株式会社ソラコム が提供しているIoT向けデータ通信SIMであるSORACOM Airを最近導入し始めました。 ユーザーコンソールからSIMの管理・操作が行え、帯域の制限を開発者が簡単に行うことができるため使い勝手が良いです。 SDK も用意されており、コードからも容易にあらゆる操作が可能になっています。 ドキュメント も充実しているので大変便利です。 @Horie1024 がSORACOM SDKを使用して帯域を変更する方法を ブログ にまとめているのでぜひ参照してみてください。 まとめ 細かいものを入れると書ききれないほど様々なサービスやツールを利用していますが、今回は利用頻度が高いものや、チーム内でホットなものを中心に紹介しました。 2年前にも 同じ内容の記事 を書かせていただきましたが、この2年で変わったもの、変わらないものがそれぞれあり感慨深かったです。 今後もチームの開発環境や効率が向上するなら新しいサービスやツールをどんどん導入していきたいと考えているため、オススメがありましたらぜひとも教えてください。 最後に VASILYでは、新しい技術が大好きなエンジニア&学生インターンを募集しています。少しでもご興味のある方は是非 こちら からご応募よろしくお願いいたします。
こんにちは。VASILYでインターンとして働いている永井です。大学では統計の研究をしていて、VASILYでは主にデータ分析に取り組んでいます。今回は先月の10月13日にβ版で提供開始となったGoogle Cloud Datalabを試してみたので、その紹介をしたいと思います。 1.Cloud Datalabとは? 2.準備 3.Notebookの作成 4.BigQueryからのデータ取得 5.取得したデータの可視化 という流れで紹介していきます。 Cloud Datalabとは? Cloud Datalabの特徴 Cloud Datalabは、Google Cloud Platform上で起動するJupyter Notebook(旧IPython Notebook)のことです。 特徴としては、 ・BigQuery、Compute Engine、Cloud Storage上にあるデータの分析をPython、SQL、JavaScriptでおこなえる ・Jupyter Notebookを使用しているのでデータを対話的に分析でき、分析結果の描画、保存、共有などが簡単に行える ・料金はCompute EngineやBigQueryなどを使った分だけ課金される ということがあげられます。VasilyではデータをBigQueryを使って管理していて、自分が普段Jupyter Notebookを使ってデータ分析をおこなっているので、今回試してみることにしました。 Jupyter Notebookとは? Jupyter Notebookは元々はIPython Notebookという名前で使われていて、 Pythonをウェブブラウザ上で対話的に実行し、保存、共有ができるサービスです。Jupyter NotebookはそれをRやJulia、Scala等他の言語(現在は40以上の言語)でも使えるようにしているもので、現在ではIPython NotebookもJupyterプロジェクトの一部、という扱いになっています。 Cloud Datalabの使い方は基本的にはJupyter Notebookと同じなので、普段Jupyter NotebookやIPython Notebookを使用している人は普段と同じように使えると思います。 では、実際にCloud Datalabを試していきます。 準備 Cloud Datalabを使い始めるためには、Googleアカウントを取得し、Google Cloud Platformに登録する必要があります。ここで課金のための情報登録が必要になりますが、60日間、300ドル分までの無料期間が用意されています。逆にGoogle Cloud Platformに登録する以外には難しい設定は必要なく、Google Cloud Platformで新しいprojectをつくれば、後はCloud DatalabのページでDeploy Datalabボタンをクリックして少し待つだけでCloud Datalabを使えるようになります。 Notebookの作成 分析を始めるには、まずNotebookを作成します。Notebookはボタンを押せば新しいNotebookがすぐに作成できます。 これでNotebookが作成できました。後はPythonのコードを矢印で示した"Cell"に入力しながら対話的にデータを分析していくことになります。 今回はCloud Datalabの特徴である、BigQueryからのデータの読み込みを行い、結果を可視化するところまでを行いたいと思います。 使用するデータはBigQueryにサンプルデータとして用意されているgithub_timelineというテーブルで、これは過去ある期間にgithubに作成されたレポジトリの作成日時、使用言語等が入っているものです。 BigQueryからのデータ取得 Cloud DatalabからBigQueryのデータを取得するには、gcp.bigqueryをimportし、 bq.Query(query内容)として実行するだけです。 結果だけを表示したい場合には bq.Query(Query内容).results() とすれば表示できます。 また、Queryを変数として保存し、それを使ってQueryを実行するということもできます。結果をpandasのDataFrameとして出力したい場合は、 df= b q.Query(Query内容).to_dataframe() とすれば変数dfに結果を出力することもできます。 今回はテーブルから、レポジトリ作成日、閲覧数、言語を作成日が新しいものから上位百万件を取得しています。 取得したデータの可視化 DataFrameに出力したデータは、通常のPythonと同様に、matplotlibを使って可視化することもできます。 例えば日ごとのレポジトリ閲覧数合計の推移を図示したり、 言語ごとに閲覧数を合計して図示したりもできます。 また、分析した結果は通常のJupyter Notebookと同様にhtmlとして出力したり、nbviewerで見れるようにして一般に公開することもできます。 今回試した内容を下記で公開しているので、興味のある方は確認してみてください。 http://nbviewer.jupyter.org/gist/ngyk/60799e83e8e4d4a09780?flush_cache=true 使ってみた感想 今回はBigQueryからデータを取得してそれを描画する、ということを試してみましたが、やはり同じCloud Platform上で動いているということで、簡単にデータを取得することができました。今回は紹介できませんでしたがCloud Storageへのデータの読み書きも簡単にできるので、今後もデータ分析に使っていくつもりです。また、インタラクティブな図表を作成できる機能など、使いきれていない部分もまだまだ多いので、これからさらに勉強していきたいと思います。 最後に VASILYのデータサイエンスチームでは、分析を効率良く、集中しておこなえるように新しいツールも積極的に取り入れてデータ分析に取り組んでいます。 もちろん分析内容としても色々なことに挑戦していて、 多腕バンディット問題をプッシュ通知配信の最適化に応用したり 、トピックモデリングという文章分類に使われる手法をユーザーのLikeデータに適用し、ユーザーの隠れた嗜好を推定してレコメンドに応用するといったことや、ユーザーの口コミをネットワーク分析によって明らかにすることで、ファッションに関する情報伝播がどのような構造になっているのか解明するといったことを行っています。 また、ここには書ききれないこともたくさんありますので、こういった取り組みに少しでも興味があるという方がいれば、是非 こちら からご応募をよろしくお願いいたします。
エンジニアの権守です。今回は、VASILYのWebフロントチームがWeb版iQONのエラーレートを0.1%から0.003%以下まで減少させた際に、行った取り組みについて紹介します。 概要 今回行った取り組みを、ひとことで言うと、テストとデバッグの強化です。 具体的には、次の3つの取り組みを行いました。 APIモックを用いたテストの廃止 テストの高速化 New Relicの活用 各項目について、詳しく説明していきます。 APIモックを用いたテストの廃止 iQONは、iOSアプリ、Androidアプリ、Webの3つのプラットフォームを展開しており、 そのために、主なロジックをAPIサーバーで処理しています。 そして、それぞれのフロントのテストにはAPIモックを利用していました。 しかし、次の2つの理由からWebフロントで利用していたAPIモックの保守は行われていませんでした。 APIの更新をフロントチームが常に把握するのが困難 APIの更新の度にその変更をリポジトリに取り込みたくない 他のチームがAPIを更新したことを察知して、該当するエントリポイントに対してモックを作成するのは手間がかかります。 また、その都度、フロント側のリポジトリを更新し、コミットするのは気乗りしませんでした (当時、モックはAPIのレスポンスをjsonで保存し、Webフロントのgitリポジトリで管理していました)。 そのような背景からAPIモックが保守されなくなり、 テストを通っていてもエラーが発生するという事態が起こっていました。 そこで、Webフロントチームでは、モックではなく開発サーバー上で動作しているAPIを利用するようにしました (ここで参照するAPIは、APIリポジトリの最新のmasterブランチを自動的に取り込んだものです)。 これにより、フロントチームが保守せずとも自動的に最新のAPIを参照するため、テストの正当性が保たれるようになりました。 しかし、JSONで保存されたモックを利用するのとは異なり、実際にリクエストを投げるため、 テストにかかる時間は伸び、15分ほどになってしまいました。 テスト時間が伸びたことによりチームの士気は下がり、また、新たなテストの追加を躊躇するようになりました。 そこで、次に、チームはテストの高速化に取り組みました。 テストの高速化 まず、最初に行ったのはAPIの開発サーバーをwebrickで立ち上げていたのをunicornに変えました。 これは非常に手軽な変更でしたが、テスト時間を2, 3分減らすことができました。 しかし、それでもまだ10分以上かかっていたので、さらに高速化を続けました。 次に、取り組んだのは テストの並列化 です。 実は、ローカルではテストに1分ほどしかかかっていませんでした。 10分以上かかっていたのはCircle CI上からリクエストを投げることによって、 通信に時間がかかっていたのです。 そこで、テストの並列化を行うことにしました。 iQONではテストにRSpecを利用しています。 RSpecを並列処理するライブラリの候補として、次の3つを検討しました。 parallel_tests rrrspec test-queue 結果的にtest-queueを導入することにしました。 test-queueを選んだ理由としては、次に2つが挙げられます。 割り振るプロセスを動的に決めてくれる 導入が簡単 parallel_testsはテストを単純にワーカー数で割るため、重たいテストが一つのプロセスに集中すると、全体の実行時間が長くなってしまいます。 rrrspecは、その点は問題ありませんが、導入にはMySQLやRedisの準備が必要でしたので、今回は見送りました。 test-queueの導入は非常に簡単で、Gemfileへのtest-queueの追加と、Circle Ciの設定ファイルの変更のみで済みました。 # Gemfile group :test do gem ' test-queue ' , ' 0.2.13 ' end # circle.yml test: pre: override: - TEST_QUEUE_WORKERS=10 bundle exec rspec-queue spec/controllers test-queueを導入した結果、Circle CI上のテストの実行時間も5分程度に収まるようになりました。 New Relicの活用 テストがよく運用されるようになりましたが、それでも全てのバグを予め潰せるわけではありません。 そこで、起こってしまったエラーをより早く検知し、対処できるように、New Relicを活用することにしました。 Webフロントチームでは、元々、エラーの検知やパフォーマンスの測定のために、New Relicを導入していましたが、 様々な要因で、まともに機能していませんでした。 そこで、次のような改善を行いました。 404エラーの除去 カスタムパラメーターの追加 アラートの設定 404エラーの除去 以前は、正常に処理した結果としてNotFound (404) になるような場合にも、 New Relicにエラーとして送信していました。 そのため、本当に問題のあるエラーが埋もれがちでした。 そこで、404エラーの場合は、New Relicに送信するのをやめました。 また、その際に、正常に処理できていない場合に404にするといった、 適切でない例外処理を除き、エラーをNew Relicに適切に送信するようにしました。 これによって、サイト上で起こっているエラーが一目でわかるようになりました。 カスタムパラメーターの追加 New Relicにはカスタムパラメーターという機能があり、エラーの送信時に、 情報を付加することができます。 そこで、デバッグの効率を改善するために以下のパラメーターを追加しました。 パラメーター名 値 備考 full_url クエリパラメーターを含まないURL全体 デフォルトで送られるURLはurlでなくpathなので、item.iqon.jpとwww.iqon.jpを判別するために追加しました params リクエストに渡されたパラメーター 同じURLでもパラメーターによって処理が異なることもよくあるので、デバッグに必須です。パスワードやメールアドレスなどの個人情報については送信しないようにしています referrer リファラー エラーが発生した際にそのページが不要な場合があります。それを判断するためによく用います user_agent ユーザーエージェント 同じURLでもPCとスマホで処理が異なるので、その判別や、botによるアクセスかを判断するために用いています iQONのWebフロントはRailsで実装されています。 今回のカスタムパラメーターの追加は、application_controllerに以下の様なコードを追加することで実装しました。 before_action :add_custom_requests_to_newrelic def add_custom_requests_to_newrelic copied_params = params.clone copied_params[ :email ] = copied_params[ :email ].gsub( /./ , " * " ) if copied_params[ :email ].present? copied_params[ :password ] = copied_params[ :password ].gsub( /./ , " * " ) if copied_params[ :password ].present? :: NewRelic :: Agent .add_custom_parameters( params : copied_params.inspect, full_url : request.original_url, referrer : request.referrer, user_agent : request.user_agent, ) end 上の画像はNew Relicに送られてきたエラーの詳細画面です。 追加したカスタムパラメーターは画面の右下部分に表示されています。 これらのカスタムパラメーターを追加することで、デバッグの効率が大きく改善しました。 アラートの設定 エラーレートが一定以上に高まった場合に、Slackに通知がくるように設定しました。 これによって、万が一、デプロイした内容にバグが含まれていた場合などにもいち早く事態に気づくことができるようになりました。 まとめ 適切なテストを行うための仕組みを作ることと、デバッグの効率化を図ることで、 エラーレートを下げることに成功しました。 特に、404エラーの送信を止めたことがエラーレートを下げた一因ではありますが、 それによって他のエラーが表面化することで、保守されていなかったページのエラーや、 特定の条件でのみ発生するエラーに気づけたことが大きかったです。 また、今回行ったカスタムパラメーターの追加やテストの並列化はそれぞれ一日で導入できたのに対し、 非常に効果的でしたので、同様のパフォーマンス監視ツールやテスト方法を用いている方におすすめです。 最後に VASILYでは、プロダクトの品質に責任をもてるエンジニアを募集しています。 ご興味のある方は是非 こちら からご応募よろしくお願いいたします。 参考URL parallel_tests ではなく test-queue に乗り換えようと思う
こんにちは。iOSエンジニアの庄司( @WorldDownTown )です。 最近のIQONのアップデートで、コーデのタグ表示のUIを変更しました。 この変更では、ユーザーがテキストで無制限に埋め込んだタグを選択できるようになりました。 例えば「#スニーカー」をタップすると、「スニーカー」タグが付いたコーデが表示されます。 他のアプリでも見かけるUIなので、簡単にUITextViewで実装できるかと思ってたのですが… 思いの外ハマったので、今回の実装を共有します。 UITextViewのサブクラスを作成して、上記の動きを実現します。 実装 1. UIGestureRecognizerを継承したクラスを作る IQONのコーデのタグのような使い方だと、リンク文字列が多いためタッチイベントを奪ってしまい、スクロールに失敗することが多くなります。 NSAttributedString の NSLinkAttributeName の機能では上記の問題が発生するため、独自のジェスチャー ( TouchUpDownGestureRecognizer ) を実装しました。 このGestureRecognizerを次に説明するUITextViewのサブクラスに適用します。 このジェスチャーがUITextViewに対するタッチイベントの、開始、終了、移動、キャンセルの状態を決めます。 1-1. 親Viewのジェスチャーとの衝突を回避する スクロールを阻害しないようにするには UIGestureRecognizerSubclass の下記のメソッドをオーバーライドします。 // TouchUpDownGestureRecognizer.swift override func canPreventGestureRecognizer (preventedGestureRecognizer : UIGestureRecognizer ) -> Bool { if let _ = preventedGestureRecognizer.view as ? UIScrollView { return false } // UIScrollViewのスクロールイベントを優先させる return true } 2. UITextViewを継承したクラスを作る 自作したジェスチャを受け取るUITextViewのサブクラスのLinkTextViewを作ります。 2-1. ジェスチャーの設定 LinkTextViewのイニシャライザでジェスチャーを設定します。 // LinkTextView.swift required init ?(coder aDecoder : NSCoder ) { super . init (coder : aDecoder ) ... let recognizer = TouchUpDownGestureRecognizer(target : self , action : "handleTouchUpDownGesture:" ) addGestureRecognizer(recognizer) } func handleTouchUpDownGesture (recognizer : TouchUpDownGestureRecognizer ) { switch recognizer.state { case .Began : touchDownWithRecognizer (recognizer) // テキストをハイライトさせる case .Changed : break case .Ended : touchUpWithRecognizer (recognizer) // ハイライトしたテキストを戻し、テキストをクロージャに渡す default : touchCancelWithRecognizer (recognizer) // ハイライトしたテキストを戻す } } 2-2. タップした文字列を判別する 下記のメソッドでタッチした位置のリンク文字列の位置と長さをNSRangeで受け取ります。 // LinkTextView.swift /** 選択したリンク文字列のrangeを返す - parameter recognizer: ジェスチャーレコグナイザ - returns: タップしたリンク文字列のNSRange。リンク出ない場合はnil **/ private func selectedLinkRangeWithRecognizer (recognizer : TouchUpDownGestureRecognizer ) -> NSRange ? { // タッチ位置がtextContainerInsetの分だけズレるので調整 var location = recognizer.locationInView( self ) location.y -= textContainerInset.top // タッチした位置の文字列の位置 location.x -= textContainerInset.left let characterIndex = layoutManager.characterIndexForPoint(location, inTextContainer : textContainer , fractionOfDistanceBetweenInsertionPoints : nil ) // 指定した位置と属性に該当するリンク文字列のNSRangeをrangeに格納する var range = NSMakeRange( 0 , 0 ) let object = attributedText.attribute( LinkTextView.LinkKey, atIndex : characterIndex , effectiveRange : & range) return (object == nil ) ? nil : range } ハマったこと 3D Touch 対応端末で動かなかった iPhone 6s を購入して動作を検証すると、リンクが動きませんでした。 Xcode7のiPhoen 6sシミュレータでは再現しないため、かなり焦りましたが、原因は3D Touchの仕組みによるものでした。 UIGestureRecogniezrを継承したクラスでは、タッチの強さ ( touch.force ) の変化でも touchesMoved:withEvent: が呼ばれてしまうようです。 // TouchUpDownGestureRecognizer.swift // 修正前 override func touchesMoved (touches : Set <UITouch> , withEvent event : UIEvent ) { super .touchesMoved(touches, withEvent : event ) state = .Cancelled } // 修正後 override func touchesMoved (touches : Set <UITouch> , withEvent event : UIEvent ) { super .touchesMoved(touches, withEvent : event ) if let touch = touches.first { let beforePoint = touch.previousLocationInView(view) let afterPoint = touch.locationInView(view) // タッチした指が動いたらキャンセル // 3D Touch 対応端末では touch.force の変化でも touchesMoved:withEvent: が呼ばれてしまうので、厳密にtouchの位置の変化を監視する if ! CGPointEqualToPoint(beforePoint, afterPoint) { state = .Cancelled } } できあがったもの こんな感じで動きます。 使い方 LinkTextView の attributedString にリンク文字列を NSAttributedString で渡します。 この時、リンクを識別するために LinkTextView.LinkKey という属性を含めます。 タップ時の処理はクロージャプロパティに設定し、リンク文字列をタップした時のみ、このクロージャが呼び出されます。 let attributes = [ NSForegroundColorAttributeName : UIColor.blueColor (), // リンク文字列の色 LinkTextView.LinkKey : "linked" , // リンクと認識するためのカスタムキー mutableAttributedString.addAttributes(attributes, range : NSRange ( 0 , 5 )) ] // helloをリンクにする let textView = LinkTextView() textView.attributedText = mutableAttributedString textView.linkClickedBlock = { (string : String ) in // リンクがタップされた時の処理 } まとめ 文章の中の任意の文字列をリンクにする方法を紹介しました。 今回の方法を使えば、UIScrollViewにaddSubviewしてもスクロールイベントを妨害せずにリンクを作る事ができます。 3D Touch端末の問題をクリアしていますが、逆に3D TouchのPeekを実装するためには、別の実装にするのが良いと思います。 細かいところはいくつか省きましたが、そのまま動くサンプルプロジェクトを用意したので、動かしながら確認してみてください。 https://github.com/WorldDownTown/LinkTextView