株式会社ZOZOのブログ - TECH PLAY

TECH PLAY

株式会社ZOZO

株式会社ZOZO の技術ブログ

1025

バックエンドエンジニアのBoBです。 今回はiOSプッシュを最適化するためには必要不可欠な、Apple Push Notification service(以下APNs)のFeedbackサービスについて紹介したいと思います。 はじめに iPhoneやAndroidにはプッシュ通知という仕組みがあることは皆さんもよくご存知かと思いますが、より洗練された、効果的なプッシュ通知を送るために自分たちのプロダクトが送っているプッシュ通知を分析する事はとても重要な事です。 今回はiPhoneのFeedbackサービスを利用して、プッシュ通知を送れていないユーザーを把握する事に関して 1. プッシュ通知の意義 2. Feedbackサービスの説明 という2点から紹介しようと思います。 プッシュ通知の意義 iPhoneやAndroidのプッシュ通知は、ユーザーのRetention(再訪率)を高める効果があります。 弊社グロースハックブログの 広告費0円でもDAU10倍!? ユーザーRetention(再訪率)を最大化する施策10選 の記事にも、Retentionを上げる意義やそのためのプッシュ通知の必要性などが書いてあるのでぜひ参考にしてください。 しかし、ただ闇雲にプッシュ通知をユーザーに送っていてもプロダクトのRetentionを高める事にはつながりません。 プッシュ通知を最適化するためには、実際にプッシュ通知の様々な部分を日々工夫し、その結果どのようなプッシュ通知がユーザーのRetentionを高める効果があるかを分析しなければいけません。 その分析を行うためには様々な数値を正確に知る必要があります。 プッシュ通知改善の為に日々変更を加える部分は大きく分けて以下の2点があるかと思います。 ・タイミング(プッシュ通知を送る時間) ・内容/見た目 これらを変更した際の効果を測定するためには以下のような数値を知る必要があります。 ・ 何人のユーザー(何個の端末)を対象としたプッシュ通知なのか ・ 実際には何人のユーザーに送信されているのか ・ 何人のユーザーがプッシュ通知からアプリを起動したか APNsのFeedbackサービスでは、 「実際にユーザーに届ける事ができなかったプッシュ通知の情報」 を取得することができます。 つまり、上記の例で言うと二つ目にある「実際に何人のユーザーに送信されているか」というデータを知るために必要不可欠な仕組みとなっています。 Feedbackサービスとは? APNsにはFeedbackサービスという、正常に処理できなかったプッシュ通知に関する情報を返す仕組みがあります。 Feedbackサービスに接続したときに得られる情報は、前回Feedbackサービスの情報を取得した以降、配送に失敗した通知に関するものだけで、Feedbackサービスが保持しているリストは読み取り後クリアされます。 Feedbackサービスの導入方法 Feedbackサービスはプッシュ通知の仕組みを実装していれば非常に簡単に導入でき、使い方次第でサービスのグロースに絶大な効果を上げる事ができます。 1. 各種設定 Feedbackサービスを利用する際は以下のサーバー、ポートにSSL接続します。 key, certの設定はプッシュ通知を送る際と同じpemファイルを設定します。 [ruby] require 'socket' require 'openssl' # 開発環境 apns-dev-key-noenc.pem, apns-dev.pem # 本番環境 apns-prd-key-noenc.pem, pns-prd.pem context = OpenSSL::SSL::SSLContext.new('SSLv3') context.key = OpenSSL::PKey::RSA.new(File.read("apns-prd-key-noenc.pem")) # プッシュ通知を送る処理と同じpemファイル context.cert = OpenSSL::X509::Certificate.new(File.read("pns-prd.pem")) # プッシュ通知を送る処理と同じpemファイル [/ruby] 2. 結果取得処理 ssl通信で結果を取得します。 [ruby] # 開発環境: feedback.sandbox.push.apple.com # 本番環境: feedback.push.apple.com sock = TCPSocket.new('feedback.sandbox.push.apple.com', 2196) # Feedbackサービスのポートは2196で固定 ssl = OpenSSL::SSL::SSLSocket.new(sock, context) ssl.sync = true ssl.connect while line = ssl.read(38) # SSLソケットから38バイト取得する  # feedbackで取得したデータを用いた処理をここで実装する p line.unpack('N1n1H140') end ssl.close sock.close [/ruby] 3. レスポンス 上記の結果は以下のような形で出力されます。 [ruby] [1392300474, 32, "#プッシュ対象のdevice id"] [1392303094, 32, "#プッシュ対象のdevice id"] [1392295196, 32, "#プッシュ対象のdevice id"] [1392301807, 32, "#プッシュ対象のdevice id"] ........ [/ruby] Feedbackサービスから取得できる情報で利用できるものはプッシュ通知対象のdevice idのみですが、この各レスポンスからどのユーザーのプッシュ通知かを判断し、分析をかける事ができま す。 まとめ 今回APNsのFeedbackサービスによる送信失敗結果の取得方法を紹介しました。しかしここで説明した内容を実装しただけではサービスのグロースには何もつながりません。 ここで取得したデータをいかにうまく使って分析していくかが重要であり、今回紹介した内容はプッシュ通知最適化の入り口でしかないのです。 現にVASILYではFeedbackサービスで取得したデータ以外にも様々なログを用いてユーザーに送るプッシュ通知を分析し、より効果的なプッシュ通知を目指して日々改善しています。 最後になりますが、VASILYでは現在エンジニアを大募集しています! 直近ですとiQONを支えているアグレッシブな技術がどのようなものか紹介する勉強会や、会社での飲み会が予定されています。 【第2回】iQONエンジニアセミナー ~進化し続けるiQONを支えるグロースハック的エンジニアリング手法とは?~ 【2/21】「iQON」急成長を支えるエンジニアと話したい人、WANTED VASILY/iQONに少しでも興味がある方はぜひご参加ください!
  理系的素養は有るけれどもウェブ経験が全く無い学生インターンが、9日間で立派なウェブ系エンジニア になったので、そのポイントをまとめました! ざっくり概要 Getting Started with Rails をやる ユーザに届ける体験をする Pull Request のやりとりをする SQLをいじる ウェブで使うミドルウェアとインフラを知る JSFIDDLE を触る ウェブ業界について知る そしたら取り敢えずウェブ系エンジニアとして仕事ができます!   インターンで来たのはこんな人 ウェブ系の採用イベントに参加した際に、多くの方が「Twitterの分析をしています!」とか「SNSの実装をしています!」とか「検索で使えるアルゴリズムの研究をしています!」と、ウェブ系のアピールをされている中、ただ一人ウェブとは関係ない、超目立つロボットを持ってきている方がいました。 激しく動き、歌い、光るロボット。 話を聞くと、「ウェブやりたい!でもウェブ良くわからないから作れるもの持ってきた!プログラムは大好き!ファイルシステムくらいなら自分で実装できる!ウェブやりたい!」 ってことだったので、東北と遠方の方ではあったのですが、インターンで2週間だけ来て頂きました。 やった事 インターンまでの宿題 Getting Started with Rails  をやる heroku  でアカウント作ってデプロイする ウェブの勉強するのにおすすめの書籍は何か?みたいな話になった時に、取り敢えず手を動かすのを優先して欲しかったのと、作った物を自分一人ででも世に出す方法を知って欲しかったので、上記二つを私からの宿題にしました。 初デプロイ(初日) Virtual Box  で開発環境構築 Github  開設 Railsで簡単なサイトの修正 ( The NERD Tree  使ってもらった) Capistrano でデプロイ 色々な説明は端折りながらも、スピード優先で実装からデプロイまでを経験してもらいました。内容は簡単なリンクURLの差し替えです。 最初のデプロイ時は手が震え、家に帰ってからは嬉しくてその箇所を何度も見直したそうです。 基本的な開発からデプロイまでの確認(2日間) MVCについてとRailsのMVCについて説明 どんな意図でどのファイルを修正するかシェア RailsでAPIの実装 Pull Request やり取り RSpec でテスト書く 最初に実装からデプロイまでの流れを通しで経験している(=デプロイの怖さ等も理解している)状態だと、Pull RequestやRSpecの必要性やMVCについて、言葉上の理解ではなく体感として理解してもらえたと思います。  バックエンド/インフラの理解(3日間) AWS, MongoDB, Redis, MySQL,memcached の概要と、iQONで何を担っているかシェア MySQLのクエリ最適化 をひたすらやる 障害対応見学 バックエンドとインフラについては、ヨッシーさんに見てもらいました。ハーコーな方なので、私一人からは教われない物を色々学んだはずです。 障害は予期していなかったものですが、彼にとっては幸運だったと思います。普段はチャットでコミュニケーションしつつ各々のペースで開発しているメンバーが、高い緊張感の中でノートPCを寄せ合って密に連携をとりながら各プラットフォーム対応していく状況でした。そこから感じ取るものは有ったと思います。  フロントの実装(3日間) JSFIDDLE  触る HTML書く( zen-coding  使ってもらった) CSS書く スマホ/PCブラウザ対応する JavaScript書く(Chromeの開発者ツール使ってもらった) JSFIDDLE はHTML/CSS/JavaScriptが書けて、メジャーなライブラリのインクルードも簡単に出来るサービスです。気軽に試せるサンドボックスで、初学者にとっては学習速度が必ず上がるものです。 彼の自前のherokuのプロジェクトにGoogle AnalyticsとAdsenseを入れてもらいました。インターン期間中には教えられなかった、計測と改善の楽しさを自分なりに体験して欲しいです。 他 tmuxで画面共有 vimの変な癖があったら治すため、vimlogとった(けどあまり使わなかった) ウェブ業界について、今イケてる(と我々が感じている)サービスとか、IPOとか、元気なベンチャーとか、大企業のウェブについてとかを、みんなで好き勝手教える 取り敢えずfacebookのアカウント作らせる 結果 どれくらいに到達したかを表にしました。レベルは今まで見てきた複数の職場で流通していたチケット(多分難易度はウェブ系では一般的)の内、こなせる率はどれくらいかを示しています。 領域 レベル 対応可能なケース NGそうなケース HTML CSS 40% HTML5でのvalidなマークアップはできる 既存コードを参考にした要素の追加削除とかも出来る 細かいデザイン等はキツい IEががががが。。。 JavaScript 60% 一般的な物はいける 複数の非同期通信が絡むようなのは楽しそうにやっていた CSSのデザインが絡むようなのはキツい Ruby on Rails 60% 一般的な物はいける MongoDBとか、何らかのミドルウェアが絡むとキツい 急ぎ案件はキツい 元は言わずもがな全て0%。9日間でここまで出来るようになるとは思っていなかったので、教えていて非常に楽しかったです。 彼自身の地頭や根性やバックグラウンドもあったとは思いますし、英語が出来ないと宿題の時点で詰んだりする訳ですが、やはり「ウェブをやりたい!」という熱い気持ちが成長を後押ししていたように見えました。 ざっくり概要(再度) Getting Started with Rails をやる ユーザに届ける体験をする Pull Request のやりとりをする SQLをいじる ウェブで使うミドルウェアとインフラを知る JSFIDDLE を触る ウェブ業界について知る そしたら取り敢えずウェブ系エンジニアとして仕事ができます! とは言え、当然まだまだまだまだ覚える事は沢山あるので、あくまでスタート地点に立ったというイメージです。 最後に 弊社は大企業でも官公庁でもないので、それらがエンジニアに与えてくれる物の多くは弊社では得られません(特に今は)。 ただし、「成長」の一点はどこよりも得られます(特に今は!)。 人生変わる程の成長がしたい!と思ったら、是非是非 VASILYサマーインターン へ! (遠方の方でも、旅費・宿泊費は会社で負担しています!)
  iOSエンジニアの荒井です。 今回はXcodeプロジェクトのライブラリ依存関係を簡単に管理出来る” CocoaPods ”の紹介をします。 未導入の方はオープンソースの管理が劇的に変わるので是非導入してみてください。 はじめに 最近iOSの勉強会などに参加すると、CocoaPodsを導入していることが前提で話が進む事が多くなりました。また、iOSエンジニアと話す際も「CocoaPodsで管理していますか?」という質問を頂きます。 導入していないことによって不便をきたすことがないようにしたいですね。 もちろん、単に時代の流れの面でだけでなく、メリットの部分が非常に多いと感じています。 今回のエントリでは 1. CocoaPodsのメリット 2. CocoaPodsの導入手順 に焦点を当てて紹介していこうと思います。 Cocoa Podsの導入メリット 冒頭にも書きましたが、CocoaPodsは「 Xcodeプロジェクトのライブラリ依存関係を簡単に管理出来るツール 」です。 iOSの開発をはじめて、オープンソースの利用をする時、GitHubなどからソースコードを持ってきて プロジェクトに入れてという作業を行うと思います。 これは非常に手間であり、特にRubyGemsやyumを使用した経験がある方からすると 「もっと簡単に管理できないか」と考えると思います。 CocoaPodsはこの問題を解決するアプローチの一つであり、コマンドを実行する事によってこれらの 問題を簡単に解決してくれます。   CocoaPodsの導入 CocoaPodsの導入は非常に簡単です。 いくつかのコマンドを実行するだけでインストールが完了します。 では早速流れを見てみましょう。 1. CocoaPodsのgemインストール $ sudo gem install cocoapods  ※ CocoaPodsはRubyのgemとして提供されているのでruby環境が必要です。 2. セットアップ 次に~/.cocoapods の作成を行います。CocoaPodsがライブラリ管理を行うディレクトリです。  $ pod setup 3. Podfileの設定 hoge.xcodeprojが存在するディレクトリにPodfileを作成します。 今回はiQONのPodfileをサンプルとして記載します。 iQONではAFNetworkingやソーシャル連携(Facebook)のテストを実施しました。 [ruby] platform :ios, '6.0' #プラットフォームの指定 iOS6.0以上 xcodeproj './iQON.xcodeproj' pod 'AFNetworking', '~> 2.0' #2.x以下のAFNetworkingライブラリを使用 pod 'AFNetworkActivityLogger', '~> 2.0' pod 'Facebook-iOS-SDK', '~> 3.0' [/ruby] CocoaPodsで導入出来るライブラリについてはこちらで検索をすると便利です。 http://cocoapods.org/ 4. インストール $ pod install 5. ライブラリの確認 Xcodeを一旦閉じてライブラリの確認を行います。この際、Xcodeでxcworkspaceを開きます。 上記のようにライブラリが確認出来れば完了です。 通常通りimportして使いましょう。 #import "Facebook.h" まとめ VASILYのiOSエンジニアは元々rubyを使っていたので、rubyのbundler感覚で使えるCocoaPodsの導入は全く抵抗がありませんでしたが、ライブラリ管理ツールの経験が無い方でも簡単に導入出来ると思います。 まだCocoaPodsを導入していない方は、ぜひこの機会に導入を行い、快適なオープンソースライフを過して頂ければと思います。 最後になりますが、VASILYではエンジニアを募集しています。 【2/21】「iQON」急成長を支えるエンジニアと話したい人、WANTED エンジニアセミナーも実施が決まったので、皆様ふるってご参加ください。 【第2回】iQONエンジニアセミナー ~進化し続けるiQONを支えるグロースハック的エンジニアリング手法とは?~  
どうも。バックエンドエンジニアの吉田です。 前回は1サイトをクロールする際の最適化戦略としてRedisベースの分散ロック機構を使った実例を紹介しました。 前回の記事:Redis::DistMutex – 時限付き分散ロックで効率良くサイトクロールをしよう 今回は複数サイトに対する処理をResqueを使って最適化した事例を紹介したいと思います。 ※ランダムにキューをlistenする話の予定でしたが、話がとっ散らかるので主題を変更しました。 主なキーワードとしては、「Resqueのキュー分割」、「Rubyでクラス定義を動的生成」といった感じです。 おさらい 前回使った図を使います。 iQONのクローラーは、提携サイトの商品一覧から商品ページのURLを取得し、ページをダウンロードする処理(fetchフェーズ)を必要な数だけWorkerプロセスとして起動しておき、Resqueを使って処理をしています。ダウンロードが終わったら順次解析する処理(processフェーズ)にResque経由で処理を引き継ぎます。これはクローラーによくある設計です。 今回のお題は、このダウンロード処理を複数サイトに対して並列に良い感じに処理させるための最適化事例とその実装例です。 キューの分割 このような設計にした場合、ページのダウンロード処理は複数のWorkerに分散されますが、複数サイトを同時にクロールした場合、enqueueされるURLの順序は予測できません。 このダウンロード処理用のキューが1つしかないと、キューは順番にdequeueされるので1サイトのURLが連続する可能性があります。1サイトに対するHTTPリクエストは時限付きMutexによって処理スピードが制限されるため、他ドメインの処理がブロックされてしまいます。 サイトごとにキューを用意すれば、キューの順序にかかわらず1サイトの処理が他サイトの処理をブロックすることもありません。 キューの状態を図にするとこんな感じです。 ただし、Resqueの場合はキューの指定をクラス変数によって定義する前提であるため、クロール対象数だけその定義を用意しなくてはなりません。dequeして得られたURLをダウンロードする処理はサイトに依らず共通であるのに、クラス定義を複数用意するのは非効率ですし、Workerの起動もサイト数分指定しなくてはいけません。 問題の解決策として、動的にクラス定義を生成する手法を採用しました。 動的にWorkerクラスを生成する ダウンロード処理をするクラステンプレートを用意して、それを設定ファイルに定義されたサイトのリストをもとに複数のクラス定義を動的生成します。 Workerを起動するときは定義されたサイトIDから全部のクラス定義を生成すればよく、enqueするときも同様の戦略で問題解決できます。 実際のコードを見たほうが理解しやすいでしょう。 iQONではサイトをdomainと読んでいるため、コードもそれに倣います。 クラス設計概観 実際のクラステンプレートは以下のように実装しています。 サイト別定義を動的生成するための抽象クラス定義 module Crawler :: Workers class DomainSpecificWorker def self . domain_id= (domain_id) name = "#{ instance_variable_get( :@queue ) } - #{ domain_id }" instance_variable_set( :@queue , name.to_sym) instance_variable_set( :@domain_id , domain_id) end def self . domainify (domain_id) klass_name = "#{ name.split( ' :: ' ).last } _ #{ domain_id }" unless Crawler .const_defined? klass_name Crawler .const_set(klass_name.to_sym, clone) klass = Crawler .const_get(klass_name.to_sym) klass.domain_id = domain_id end Crawler .const_get(klass_name.to_sym) end end end ページをダウンロードするWorkerクラスの定義 module Crawler :: Workers class FetchPageWorker < DomainSpecificWorker @queue = :fetch_page def self . perform (params) # 実際のダウンロード処理は別クラスに定義して、それを呼び出すだけ end end ※今見ると、DomainSpecificWorkerはモジュール化してMix-inしたほうがRubyらしい感じもしますね… Before(静的にクラス定義を用意する場合) enqueする時のコード例 args = { domain_id : 1 , url : ' http://... ' } Resque .enqueue( FetchPageWorker1 , args) Workerの起動スクリプト domain_ids = config.get( :domain_ids ) # ドメインごとのクラス定義を読み出し queues = domain_ids.map { | domain_id | Crawler :: Workers .const_get( " FetchPageWorker #{ domain_id }" ) } # ランダムに並び替えた優先順でワーカーを起動 worker = Resque :: Worker .new(*queues.shuffle) worker.work After(動的にクラス定義を用意する場合) enqueする時のコード例 args = { domain_id : 1 , url : ' http://... ' } Resque .enqueue( FetchPageWorker .domainify(domain_id), args) Workerの起動スクリプト domain_ids = config.get( :domain_ids ) # ドメインごとのクラス定義を動的生成 queues = domain_ids.map { | domain_id | FetchPageWorker .domainify(domain_id) } # ランダムに並び替えた優先順でワーカーを起動 worker = Resque :: Worker .new(*queues.shuffle) worker.work このようにWorker.domainify(domain_id)をコールすると、そのサイト固有のWorkerクラス定義を生成できるので、コードもシンプルに保つことができます。 さらにWorkerの起動スクリプト中で'queues.shuffle'することで、スクリプト実行の度に毎回優先順位の異なるプロセスが起動され、複数サイトの処理ももっとバランスよく並列に処理することができます。 まとめ サイトごとのキューを用意することでクローラー全体として並列度をキープ&処理効率の最適化ができます。さらに、ResqueのWorkerクラス定義を動的生成することで、実処理が共通なクラス定義を複数用意するという実装コスト・管理コストを下げ、コードの簡潔性も保つことができました。 前回はiQONのサービス規模も交えながらの事例紹介でしたが、今回はRubyやResqueの活用方法にフォーカスした事例紹介をしてみました。 ちなみに、VASILYではエンジニア向けのセミナーを近日中に開催を予定しています。。 VASILYというスタートアップがどういう会社なのか、どんなサービスをつくっているのか、技術的にどんな事に取り組んでいるのかということをお話する予定です。 日程・内容が確定しだいお知らせしますので、興味のある方はぜひご参加ください。
はじめまして。バックエンドエンジニアの 吉田 です。 2013年5月末の入社以降、大量のEC2インスタンスのVPC移行を担当した後、今はiQONの商品DBを支えるクローラーの改善に取り組んでいます。今回はその改善の1つとして開発したRedis::DistMutexという分散ロック機構のruby実装を紹介をしようと思います。 Redis::DistMutex 開発の経緯や細かい設計の話は後述するとして、まずはつくったgemの紹介をします。 Redis::DistMutex Redisベースの分散ロック機構 rubyのライブラリにある Mutex 互換 スレッド間だけでなく、プロセス間・ホスト間でも共有できるMutex 時限つきロックの作成が可能(redisのsetnxとexpireを活用) namespaceを指定できるので、特定の処理ごとにロックの作成が可能 redis2.6以上のみサポート(1秒以下のexpireの指定が可能な pexpire 依存するため) サンプルコード sample.rb require ' redis ' require ' redis-dist-mutex ' Redis :: DistMutex .redis = Redis .new mutex = Redis :: DistMutex .new( :test_app , expire : 1 , auto_release : false ) mutex.synchronize { puts ' started ' ; sleep rand( 5 ) } これを5つ以上の複数スレッドや複数プロセスで同時実行すると、'Kernel.sleep(n)'の長さに影響されることなく全体として一定間隔でsyncrhonizeに渡されたブロック内の処理が実行されます。 複数スレッドから使うサンプル スレッドごとにsynchronize内でランダムにKernel.sleepを実行 syncrhonizeに渡すブロック内でRedisにIDと現在時刻を記録 test_mutex.rb require ' redis ' require ' redis ' require ' redis-dist-mutex ' class TestMutex def initialize @redis = Redis .new Redis :: DistMutex .redis = @redis @mutex = Redis :: DistMutex .new :test_app , expire : 1 , autorelease : false end def create_thread (id) Thread .new do now = -> { sprintf( ' %.1f ' , Time .now.to_f) } @redis .lpush( ' hoge_test:start ' , " #{id}:#{now.call}") @mutex .synchronize { @redis .lpush( ' hoge_test:end ' , " #{id}:#{now.call}"); sleep 2 + rand } end end def start puts ' - ' * 40 @redis .del( ' hoge_test:start ' ) @redis .del( ' hoge_test:end ' ) 5 .times.map { | id | create_thread(id) }.each(& :join ) puts " Start : #{@redis.lrange('hoge_test:start', 0, -1)}" puts " End : #{@redis.lrange('hoge_test:end', 0, -1)}" end end if $0 == __FILE__ test = TestMutex .new 3 .times.each { | i | test.start } end 実行してみると、スレッドのIDは実行順とは無関係に記録されていて、それぞれ約1秒おき記録されています。 $ bundle exec ruby ./test_mutex.rb ---------------------------------------- Start: [ "0:1385368596.6" , "1:1385368596.6" , "2:1385368596.6" , "4:1385368596.6" , "3:1385368596.6" ] End : [ "0:1385368600.6" , "4:1385368599.6" , "1:1385368598.6" , "2:1385368597.6" , "3:1385368596.6" ] ---------------------------------------- Start: [ "0:1385368605.0" , "1:1385368605.0" , "2:1385368605.0" , "4:1385368605.0" , "3:1385368605.0" ] End : [ "4:1385368609.0" , "0:1385368608.0" , "1:1385368607.0" , "2:1385368606.0" , "3:1385368605.0" ] ---------------------------------------- Start: [ "0:1385368613.4" , "1:1385368613.4" , "4:1385368613.4" , "3:1385368613.4" , "2:1385368613.4" ] End : [ "3:1385368617.4" , "0:1385368616.4" , "1:1385368615.4" , "4:1385368614.4" , "2:1385368613.4" ] 実行時に別のターミナルで'redis-cli monitor|grep expire'を実行しておくと、1秒おきにロックが取得されるのがよくわかると思います。 initialize時のオプション 'expire: n'を設定しないとブロックの実行が完了次第、次の処理に移る rubyのMutexと同じ動作 'autorelease'はデフォルトtrueで、'false'を設定しない限り、処理が同期的になる 'true'だとsyncrhonize終了時に必ずロックを開放 今回の要件では、'expire: 1, autorelease: false'を指定 そうすることで、ロック取得から1秒後に必ずロックが開放される サイトクロール時の問題点 iQON掲載商品数 200万件以上 iOONでは大量の商品データを保有しているのですが、それらは提携しているECサイトをクロールしてDBに取り込んでいます。さらに、いったん貯めたアイテムの在庫情報も定期的に再クロールして更新しているので、相当数の商品ページを効率的にクロールする仕組みが必要になります。 クローラー最大のボトルネックはネットワークIO クローラーの処理は、大きく"ダウンロード"と"解析"の2つに分けられます。 ページの解析については、サーバーの性能やコードの見直しで高速化が容易です。一方、ページのダウンロードについては外部サイトの性能に依存するため、効率化が難しくなります。これを効率的に行う設計が必要になります。 既存のクローラーはサイトの性能にかかわらず直列でダウンロードして、一定時間sleepしていました。そのように素直に直列でダウンロードした場合、レスポンスタイムに依存してリクエスト間隔がばらばらになります。1ページ5000msかかると1000ページで5000秒(83分20秒)かかる計算です。逆に、200msで返してくれるサイトの場合は1000ページを200秒(3分20秒)で処理できますが、5req/secの負荷がかかります。 このようなサイトごとのレスポンス性能の差異に関係なく一定間隔でページをリクエストできれば、全体としてクロール効率を向上させ、対象サイトへの負荷も軽減することができます。 今回の新クローラー開発では、そのような問題にも取り組みました。 Resqueによるダウンロード処理の並列化 今回開発したクローラーは、起点となるページから抽出した商品詳細ページのURLをResqueにenqueueし、workerはそのURLのページをダウンロードしてDBに保存し、ページ解析用のworkerに処理を引き継ぎます。この時、worker数を増やせばそれだけ並列度を上げて効率よくダウンロードできるのですが、前述した通り、増やしすぎると対象サイトに負荷をかけることになります。 また、何らかの障害によってworkerがdequeueしていなかった場合、ダウンロードキューが大量に蓄積されます。障害から復旧した後でworkerを起動すると、蓄積されたキューが一気に処理されるため、worker数だけ同時にHTTPリクエストする状態が一定時間継続することになります。これはクロール対象サイト対してDoS攻撃しているようなものです。 そこで、このクロール頻度の制御に有効な手段として、時限付き分散ロック(redis-dist-mutex)を導入しました。 時限付き分散ロック Mutex(=Binary Semaphore) Rubyにも同梱されている"Mutex"は、 wikipedia ではこう説明されています。 クリティカルセクションでアトミック性を確保するための同期機構の一種である。 つまり、複数スレッドやプロセス間の排他制御に使用するロック機構の一種であり、ロックを取得したものだけが処理をすすめられ、取得できなければ開放されるまで待つことになります。 ただし、今回の要件では厳密な排他制御は必要なく、全体として一定間隔での実行を保証できれば十分です。ロック取得から一定以上の時間が経過したらそれ以上の排他制御は必要ありません。 そのような仕組みがあることによって、各worker内のHTTPリクエスト開始のタイミングのみの制御が可能になります。(※スケジューラーに近いイメージですが、スレッドやプロセス同士での協調動作となる点でスケジューラーとは異なります。) このように時限付き分散ロックを用意しておけば、複数のResqueWorkerから特定のサイトHTTPリクエストが集中することを防止できるようになります。例えば1リクエストに3秒かかるサイトに対しては3つのworkerが起動していれば、1秒おきにHTTPリクエストを発生させることができます。仮に1秒間隔でリクエストすることが条件ならば、起動しておくべきworker数は以下の式で算出できます。 worker数 >= 平均レスポンスタイム(秒) + α ちなみに、iQONのクローラーのざっくりした構成は以下の図のようになっています。 実際にはiQONのクローラーは複数サイトをクロールするので、サイトごとにqueueとmutexを用意して、複数のworkerから全サイトに対して効率よく処理されるように設計を工夫しています。各workerはそれら複数のダウンロードキューをランダムな優先順位でlistenすれば、全体のバランスがとれます。このあたりの設計の話については別の機会に紹介したいと思います。 以上、 Redis::DistMutex と、それを使ったクローラー事例の紹介でした。 最後に 今回紹介したものはiQONのクローラーのほんの一部分であり、iQONというサービスを支えるためのサーバーサイド技術は多岐にわたります。クローラーにかかわらずiQONを支えるサーバーサイド技術に興味のある方はぜひ恵比寿にあるオフィスに直接遊びにきてください。VASILYでは現在、一緒に働いてくれるエンジニアを大募集しています。 募集要項 連絡先:info[at]vasily.jp
はじめまして、VASILYデザイナーのこんです。 今までは「Tech Blog」としてエンジニア陣で技術的な発信をしてきていたのですが、 デザイン的な発信もしていける場所を設けたいということになり、 「Developers Blog」と改名し、わたくしめが紅一点()として加わることになりました。 今回の記事はデザイナーとしての簡単な紹介となりますが、 これから有意義な情報をお届けできるようにしますので、 よろしくお願いします! 先週の話になりますが、11/20にアプリやWebサービスのデザイナーを対象にした Designers meetup Vol.2というイベントに参加してきました。 VASILYのデザイナーは現在 常駐私1人と、アルバイト3人で回していて、 普段は、社内の人間としか話さないため、 他社のデザイナーさんと赤裸々な話で盛り上がったり、 自社サービスをやってるデザイナーだからこその話もたくさんできたので、 情報共有としてもとても刺激的で楽しいひとときでした。 そんな中、私も「iQONデザインリニューアルのポイント」というテーマで 今回のiOSアプリリニューアルのお話を発表させていただきましたので、 そちらのスライドを公開したいと思います。 iQON デザインリニューアルのポイント from Kwon Miae   前の記事で、エンジニアの庄司さんが書いてた内容 と少々かぶりますが、 実際のデザイン時にどうゆう意図があって構成や表現を考えたのか、 リニューアル時に行った作業はどんなものかといったことなどを LTだったので5分という短い時間でしたが精一杯発表してきました。 あまり人前に立つこともないし、喋るのは特に苦手意識を持っていたのですが、 「このタイミングでiQONのデザインリニューアルを是非とも伝えたい、自信を持って発表してみたい!」と勇気をだして今回の発表に立候補して本当によかったです。 今まで、デザインのセミナーや勉強会には、目につけば出来る限り参加してきましたが、 発表する側での参加の方がとても有意義なものになるなぁと実感しました。 もちろん、参加者としても得られるものは多いのですが、 他の人はどんな内容を発表してどんな伝え方をしてるのかを強烈に意識して聞くようになり、 懇親会でも”さっき話してた人”で認識してもらえるので、 ただの名刺交換だけでなく、プレゼン内容に対する質問から会話も盛り上がって、 より繋がりが産まれる場となったと思います。 VASILYでデザイナーとして働き始めてもうすぐ2年で、 グラフィック上がりの私は、初期はバナーやLPくらいしか作れなかったのですが、 今では、PC、iOS、Android、スマホページ、タイアップ企画のビジュアル全般等、様々なデザインを担当させてもらえるようになりました。 まだまだ未熟なところだらけですが、そんな私だからこその情報を発信していくこときっと誰かの役に立つのではと願いつつ、一緒にデザインしてくれる仲間を増やしていくためにも、今後もブログ更新していきます。 スライドでも書いてましたが、VASILYではデザイナー絶賛大大大募集中なので、 自社サービスもクライアントワークな仕事もやりたいという欲しがり屋さんデザイナーの方々、 心よりお待ちしてます! VASILYリクルートページ 連絡先:info[at]vasily.jp
はじめまして!2013年7月にVASILYにJOINした西村と申します。VASILYでは主にiQONのAndroidアプリの開発を行っています。 先週からAndroid4.4搭載のNexus5を使っているんですが動きがかなり向上されたため、これからAndroidの快進撃が起こる予感がしてワクワクです。Androidの快進撃といえば最近iQONでも起こりはじめています。少し前まではAndroidアプリよりもiOSアプリの方が評価が高かったiQONですが、この半年でAndroidアプリのユーザーレビューが0.8ほど上がりiOSアプリに追いつきつつあります。先月にはGoogle Playの「今週のおすすめ」枠に掲載していただきました。 今回はそんなAndroidアプリの開発で使用しているツールを紹介します。 Android Studio 現在、IDEは「 Android Studio 」を使用してます。 http://developer.android.com/sdk/installing/studio.html 2ヶ月前まではAndroid開発では一般的なEclipse + ADTの構成で開発してましたが、検証をしたところ大きな手間もかからず移行できることがわかったためAndroid Studioに移りました。 まだ移行して浅いですが、以下のような開発効率が上がる利点がありました。 ・9-patchツールがIDEに組み込まれている (意外と便利) ・Vimプラグインの操作性がEclipseのときより良い (VimmerのためVimプラグインを使用) ・エディタを分割して同一ファイルを表示させることができる (Eclipse4.3 for Macではなぜかできなかった) ・全体的にEclipseのときよりも安定している (IDE自体がクラッシュすることがなくなった) Android StudioはビルドツールがGradleなんですが、まだまだ使い倒せていないので今後はそこの部分にも手を入れていこうと思ってます。 Genymotion エミュレータも2ヶ月前に「 Genymotion 」に移行しました。 http://www.genymotion.com/ Androidアプリの開発をされたことがある方ならご存知かと思いますが、Android SDKの標準のエミュレータはとにかく重くて遅い。エミュレータの起動時間や操作性が開発での大きなボトルネックになっていました。 そんな中で爆速エミュレータであるGenymotionの存在を知り導入したところ、想像以上の爆速でボトルネックだった部分が一瞬で解消され開発効率が何倍にも上がりました。もうAndroid SDKの標準のエミュレータには戻れません。導入して問題がない環境であれば絶対に導入した方がいいツールです。 インストールする際には、Genymotionのアカウントの作成と、VirtualBoxのインストールが必要になりますが、その手間に見合うだけの対価は十分にあると思います。 Localytics iQON内の数値計測、KPI管理を効率化するために「 Localytics 」を使用しており、各ページの表示回数やどの導線を経由して来たかなど幅広く分析できるように細かく数値を落としてます。 http://www.localytics.jp/ AndroidアプリはiOSアプリのような審査がなくリリースできるという利点があるため、施策のABテストや検証を行うには最適な場になります。AndroidアプリでABテストや検証を繰り返して効果が上がった施策をiOSアプリに移植しているケースもあります。 ネイティブアプリでもスピード感を落とさずに精度の高い施策を出すためにはAndroidアプリをうまく利用することが重要になってきます。 Google Analytics Localyticsとは別軸の数値計測をしたり、クラッシュレポートをみたりするために「 Google Analytics 」を使用してます。 クラッシュレポートに関しては、CrashlyticsやBugSenseを使っていた時期がありましたが、現在はGoogle Analyticsに落ち着いております。他に良いサービスがあれば移行したいと考えているのでご存知のかた教えてください。 最後に 現状のAndroidアプリの開発で使用しているツールを簡単に紹介しましたが、Androidチームは新しいモノが大好きなので今後も開発効率が上がるためならアグレッシブに取り入れていきます。なのでオススメのツール等がありましたらぜひとも教えてください。 またVASILYでは一緒にサービスを盛り上げてくれるエンジニアを大募集しております。 興味のある方、一緒にAndroidアプリを作りたい方はご連絡ください。 募集要項 連絡先:info[at]vasily.jp
2013年も終盤にさしかかり、年内に+10kgに到達しそうなiOSエンジニアの庄司です。 先日、iQONのiOSアプリをiOS7にデザインを最適化してリニューアルしました。 今回は新しくなったiQONの新機能とUIについてご紹介します。 まずはあたらしくなったiQON v2.0.0を こちら でインストールしてみてください。 新しくなったiQONの新機能 アプリのデザインをリニューアル iOS7にマッチしたシンプルで明瞭なデザインに変更しました。(あえてフラットデザインとは言いません) iOS7のトーンに合わせるだけでなく、ユーザーインターフェース部分の装飾を最低限必要なものだけにしました。 そうすることで、投稿されるコーディネートや掲載商品などのコンテンツを引き立てるように設計されています。 ↓v1.8.2 と v2.0.0   ホーム 今回新たに追加された「ホーム」は、iQONの利用シーンで多い自宅でのリラックスした時間や平日のちょっとした空き時間など、コンテンツを流して見て楽しむ利用シーンを想定し、ユーザーが主体的に検索する必要なくホーム上に流れる人気のコンテンツや最新の情報を見ているだけでiQONを楽しめるようにしました。 シンプルなナビゲーション よく使われる機能は、メニューを開くことなくいつでもアクセスできるようにタブに集約しました。 また、ナビゲーションバーとタブは透けているため、画面を広く使うことができます。 LIKEリスト 自分がLIKEしたアイテム、コーディネートなどをいつでも見返すことができるように、「LIKEリスト」にまとめました。 ナビゲーションバーの右上にLIKEリストのボタンが常にあるのでここからいつでもアクセスできます。 凝ったUI デザインはかなりシンプルになりましたが、実装においてはiOSネイティブな機能から外れることが多いため色々と凝った実装をしています。 - 全画面表示 iOS7ではデフォルトでフルスクリーンコンテンツを想定していて、ナビゲーションバーやステータスバーが透けることでコンテンツをより広く見せることが出来るようになりました。 これと同じ思想ですが、iQONでは「ホーム」のように縦に長いコンテンツをスクロールしていく時に、よりコンテンツに集中できるようにナビゲーションバーとコンテンツを隠して画面を広く取りました。 メニュー/LIKEリストのスライドジェスチャー ナビゲーションバーのボタンからメニューとLIKEリストを開くことができますが、女性の小さいの手では片手でタップするのは難しいこともあります。 また、ナビゲーションバーが適宜隠れるようになったためボタンが表示されていないこともあります。 こういう場合の補助的な機能として、画面の端からスライドしてメニューやLIKEリストを開くことができます。 アイテムポップアップ ホームなどでアイテムの画像をタップすると、アイテムの画像がズームして詳細情報が表示されます。 このアイテムポップアップではそれまで見ていた画面が透けて見えるため、前の画面に戻って流し見しやすくなっています。 ブラー (ぼかし) メニューやLIKEリストなどいつでも開ける画面は、それまで見ていた画面がわかるように、全画面を覆わずに元いた画面にブラーをかけています。 まとめ 今回は新しくなったiQONのiOSアプリについて紹介しました。 v2.0.0へのリニューアルは基本的にiOS7におけるAppleの思想から大きく外さないように、シンプルなデザインを目指して作ってきました。 また、これらの新しくなった機能はこれまでのiQONのバージョンで計測した数値やユーザーからの声をもとに使いやすく改善してきたこともあり、リリースして一週間の現在ユーザーからのレビューも上々です。 今後はiOS6との共存や具体的な実装についても紹介していきたいと思います。  
初めまして。かとあつと申します。 2013年6月からVASILYで主にAPIとフロントエンドのプログラムをいじっています。 今回はAPIサーバのパフォーマンスが気になる人に嬉しい便利ツール2つ紹介します。SiegeとHTTPingです。 目新しい訳ではないですがいずれもライトなので、小規模の受託や1人開発のサービスなんかでも試しやすい筈です! Siege Siege is an http load testing and benchmarking utility. Siege(スィージ)はHTTP負荷テストツールです。 ベーシック認証,cookies,HTTP,HTTPSサポート。 仮想ユーザを大量に作って、インターネットからのアクセスをそれっぽくシミュレートしてくれます。 インターフェースはApache Benchに近いので、ABを使った事ある人なら直感的に使える筈です。 インストール $ wget http://www.joedog.org/pub/siege/siege-3.0.5.tar.gz $ tar xfvz siege-3.0.5.tar.gz $ cd siege-3.0.5 $ ./configure $ make $ sudo make install 実行 負荷を掛けたいURLリストをURL.txtとして保存して、以下のように実行します。 $ siege -b -i -r 150 -c 100 -f URL.txt -b ベンチマークモード。仮想ユーザは待たずにアクセスを繰り返す。(これを設定しないと仮想ユーザは1アクセス毎に1秒以上待つ) -i インターネットモード。仮想ユーザは与えられたURLリストからランダムにアクセスする。 -r 与えられたURLリストの繰り返し回数。 -c 仮想ユーザの多重度。 -f URLリストのファイル名。 URLリストについて、iQONは公開済みサービスという点と、実環境と近いテストをしたいという点から、HTTPサーバのアクセスログを元にURLリストを作りました。 リリース前案件であれば、性能要件と相談して作る必要があります。 結果 以下のような値が取れます。(マスクだらけですみません。) Transactions: xxxxx hits Availability: 99.97 % Elapsed time: 39.83 secs Data transferred: xxxx.xx MB Response time: 0.20 secs Transaction rate: xxxx.xx trans/sec Throughput: xx.xx MB/sec Concurrency: xxx.xx Successful transactions: xxxxx Failed transactions: xx Longest transaction: x.xx Shortest transaction: x.xx URL毎に成功/失敗も分かります。 豆 Siegeは、(城・都市などの)包囲攻撃 みたいな意味らしいです。 パラレルで負荷を掛けるイメージにぴったりですね。 感想 軽快で、インストールも実行も容易なので、パフォーマンスの定点観測にはもってこいだと思いました! HTTPing Httping is like 'ping' but for http-requests. HTTPのPingなイメージの物です。 特定のURLに対して、Pingのようなインターフェースでレイテンシとスループットを計測できます。 iOS/Androidから! スマホ開発していると「スマホの通信のせいかも!?」「3G経由だと上手く行かない!?」みたいなことがあるので、iOS/AndroidにもHTTPingが出来る物を入れています。 HTTPingのオリジナルは多分Linux用の物なのですが、iOS/Android用のものが凄く便利なのでこっちを先に紹介します。 HTTPing for iOS HTTPing for Android メジャーサービスと自社サービスとを合わせて登録して一緒に確認できるので、端末自体の問題なのか、自社サービスの問題なのかの切り分けも出来ます。 Linux用ツールに話を戻します。 インストール お使いのパッケージマネージャで入ると思います。入らない方は以下から。 $ wget http://www.vanheusden.com/httping/httping-2.3.3.tgz $ tar xfvz httping-2.3.3.tgz $ cd httping-2.3.3 $ ./configure $ make $ sudo make install 実行 $ httping -s http://example.com -s ステータスコードを表示させる 結果 PING example.com:80 (/): connected to 93.184.216.119:80 (344 bytes), seq=0 time=234.61 ms 200 OK connected to 93.184.216.119:80 (344 bytes), seq=1 time=227.52 ms 200 OK connected to 93.184.216.119:80 (344 bytes), seq=2 time=351.41 ms 200 OK Ctl+Cで停止するまでレスポンスがリアルタイムで見れます。 感想 やはり、スマホから最初の切り分けがさくっと出来るのは便利ですね。 CLIも、リアルタイムで見慣れた形式でパフォーマンスが見れるのは嬉しいです。 最後に サーバのレスポンスは速くて沢山返せる程良いにこした事はないのですが、ハードウェア調達コストやシステムチューニングコストは無限に払えないので、性能要件と実際のパフォーマンスとを把握して先の戦略を練る必要がありますよね。 VASILYでは一緒に未来の戦略を練れるエンジニアを求めています! 興味のある方は是非一度オフィスに遊びにきてください!
はじめに こんにちは、iOSエンジニアの荒井です。 先日、株式会社Rettyのエンジニアの方々と技術勉強会を開催しました。 今回はiOSアプリについて、自分たちが使用している技術を紹介し合いました。 その場でXcodeを立ち上げ、instrumentsを起動してのLIVEメモリリーク調査など、 通常の勉強会とは違った形式で、とても有意義な技術交流の場になったと感じています。 僕からも「iQONのVIew構成」というお題でお話させて頂いたので、 今回はその資料を公開したいと思います。 スライドと補足 資料はslideshareにて公開していますが、まずスライドの流れを簡単に紹介したいと思います。 iQONでは現在多くのViewController・Xibがあります。 その数は100を超え、複雑な画面設計も相まって、工夫をしないと開発工数がかさんでしまいます。 ViewControllerとViewの役割を明確に分離し、Viewの再利用を可能な限り行っていくことで 画面を作成する時の工数を極力減らそうという内容となっています。 こちらが勉強会で使用したスライドです。 まとめ サービス機能の拡大により、アプリケーションもどんどん太ってきました。 「このままではダメだ!」という認識のもと、現在はパーツの共通化を進めており、 Xibファイル、画像などのダイエットを行っている真っ最中です。 別アプリになったと言われるくらい進化を遂げようと思いますので、 興味のある方はぜひ一度オフィスに遊びにきてください。
みなさん初めまして! 体を引き締めるため、強い漢になるために最近キックボクシングをはじめたボブです。 4月1日からvasilyでバックエンドエンジニアとして働きはじめ、今回Techブログを初めて書かさせていただきます。よろしくお願い致します。 今回は自分が今担当している、iQONのRails4.0移行について書きたいと思います。その中でも今回は「mysqlのgemの設定」に焦点を当てて記載していきたいと思います。 はじめに VASILYでは約2年ほど前に、PHPで構成されていたiQONをRuby on Railsで書き直しました。 しかし一度実装して以来、プロダクトの新規システム実装や運用に時間をとられてしまい、Railsやミドルウェアのバージョンをあげるなどの作業を行うことができていませんでした。 待望のRails4.0が正式リリースされたこのタイミングにあわせて、Railsならびにミドルウェアやgemのバージョンをあげることにしたので、その作業の中で自分が苦労した点や、参考になりそうな点をこれから何回かに分けてまとめていきたいと思います。 Rails4.0化するメリット まず、Rails4.0化するメリットとしては大きく分けて以下の3つの理由があると考えています。 速度改善 Rails4.0に移行することで、ユーザーがサービスにアクセスした際のレスポンスの速度が大きく改善されます。 あくまで一つのエントリポイントに対してのアクセスに関してですが、具体的には以下のような速度改善に成功しています。 [bob@iqon_rails iqon_api]$ ./bin/rails runner batch/test/response/response_time.rb # 既存のインスタンスのベンチマーク user system total real 0.000000 0.010000 0.010000 ( 0.270074) # Rails4.0インスタンスのベンチマーク user system total real 0.000000 0.000000 0.000000 ( 0.045121) また開発を行う面でも、サーバーやrailsコンソールを立ち上げる速度が大きく改善されていることが肌感でも感じることができます。 より効率的な書き方で実装できる Rails4.0では非効率だった既存のメソッドの削除やより最新の思想に基づいた新しい仕組みなどが導入されています。 Rails4.0に準じた書き方に修正することで今までより、さらに効率的な実装を進めることができます。 ミドルウェアやgem、ロジックの見直し、バグの発見等がある なかなか時間をとることができないgemやミドルウェアのアップデートも、Rails4.0化にあわせてアップデートするきっかけになります。またiQONのように新機能をどんどん追加していくような開発スタイルですと、スピード感を重視し、あまり効率的でないロジックを書いてしまっている場合がありますが、そちらを修正するきっかけにもなります。またその際に今まで発見されていなかったバグなどを発見することもできます。実際に今回の移行作業の中で、開発環境でのみ起きる既存のバグを見つけ改修することができました。 ActiveRecordの変更点 Rails4.0では以下が主にActiveRecord周りで改修された点となります。 set_table_nameメソッドの改修 finderメソッドの改修 ActiveRecord::SessionStoreの廃止 ActiveRecord Observersの廃止 このようにActiveRecordに関していくつかの改修が入っているため、現状のほとんどのgemが既存のままでは動かないという現象に悩まされました。これら廃止されたいくつかの機能はgemをインストールすることによって使用することが可能ですが、それでは本来のRails4.0の意図とずれてしまうため、今回はgemをforkして改修することで対応することとしました。 調査した/試したgemたち 弊社が運営するサービス、iQONではMySQLのマスター/スレーブ構成を実現するために、multi_dbというgemを使っています。 今回multi_db以外のgemもふまえて以下のgemを調査、仮導入してみました。 multi_db octopus db_charmer seamless_database_pool Rails4.0で使用されているgemの依存関係やActiveRecordに大きく改修が入っていることなどから、既存でリリースされているgemではうまく最初から動くものはありませんでした。 結論としては、gemの改修にかかるコスト、gemの思想とiQONの実装状況をふまえた上で、既存で使っているmulti_dbをforkしてきて一部修正することでmaster/slave構成を実装しました。 保留したgemとそのエラー orcpusに関して ar-octopus-0.5.0を使用してアクセスした場合、以下のようなエラーが発生しました。 /vendor/bundle/ruby/2.0.0/gems/activesupport-4.0.0/lib/active_support/core_ext/module/aliasing.rb:32:in `alias_method': undefined method `set_table_name' for class `Class' (NameError) from /home/bob/iqon_api/vendor/bundle/ruby/2.0.0/gems/activesupport-4.0.0/lib/active_support/core_ext/module/aliasing.rb:32:in `alias_method_chain' set_table_nameでgrepした結果 ./README.mkdn: set_table_name("yummy") ./spec/octopus/model_spec.rb: describe "when using set_table_name" do ./spec/octopus/model_spec.rb: Keyboard.should_not_receive(:reset_table_name) ./spec/support/database_models.rb: set_table_name "yummy" ./spec/support/database_models.rb: set_table_name { "yummy" } ./lib/octopus/model.rb: if self != ActiveRecord::Base && self.respond_to?(:reset_table_name) && !self.custom_octopus_table_name ./lib/octopus/model.rb: self.reset_table_name() ./lib/octopus/model.rb: alias_method_chain(:set_table_name, :octopus) ./lib/octopus/model.rb: def set_table_name_with_octopus(value = nil, &block) ./lib/octopus/model.rb: set_table_name_without_octopus(value, &block) ./lib/octopus/model.rb: def octopus_set_table_name(value = nil) ./lib/octopus/model.rb: ActiveSupport::Deprecation.warn "Calling `octopus_set_table_name` is deprecated and will be removed in Octopus 1.0.", caller ./lib/octopus/model.rb: set_table_name(value) set_table_nameを修正 set_table_nameはrails4.0から使用できなくなったため、修正してもう一度実行すると /home/bob/iqon_api/vendor/bundle/ruby/2.0.0/gems/activesupport-4.0.0/lib/active_support/core_ext/module/aliasing.rb:32:in `alias_method': undefined method `announce' for class `Class' (NameError) from /home/bob/iqon_api/vendor/bundle/ruby/2.0.0/gems/activesupport-4.0.0/lib/active_support/core_ext/module/aliasing.rb:32:in `alias_method_chain' エラーを調べてみると、Rails3.1でも同じエラーがでていたようでした。こちらは問題の根が深そうなのでいったんここで保留しました。 db_charmerに関して こちらはrails4.0で使用するgem同士の依存関係によりエラーが発生してしまいました。 gem 'db-charmer', '1.8.4', :require => 'db_charmer' をGemfileに記載した場合 Bundler could not find compatible versions for gem "activesupport": In Gemfile: db-charmer (= 1.8.4) ruby depends on activesupport (<= 3.2.13) ruby rails (= 4.0.0) ruby depends on activesupport (4.0.0) Bundler could not find compatible versions for gem "railties": In Gemfile: rails (= 4.0.0) ruby depends on railties (= 4.0.0) ruby sass-rails (~> 4.0.0) ruby depends on railties (4.0.0.rc2) 最新のdb-charmerをインストールしようとすると、gem同士の依存関係のエラーにより、インストールすることができませんでした。 バージョンの指定を外し、gem 'db-charmer', :require => 'db_charmer' をGemfileに記載した場合 db-charmer-1.6.13がインストールされました。そこで実際にActiveRecordでアクセスしてみると、下記のエラーが発生しました。 /home/bob/iqon_api/vendor/bundle/ruby/2.0.0/gems/activesupport-4.0.0/lib/active_support/core_ext/module/aliasing.rb:32:in `alias_method': undefined method `format_log_entry' for class `ActiveRecord::ConnectionAdapters::AbstractAdapter' (NameError) エラーがでているformat_log_entryについて各db-charmerでgrepをかけてみると、 1.6.13 ./lib/db_charmer/abstract_adapter_extensions.rb: base.alias_method_chain :format_log_entry, :connection_name 1.8.4 ./lib/db_charmer/rails2/abstract_adapter/log_formatting.rb: base.alias_method_chain :format_log_entry, :connection_name format_log_entry メソッドはrails2以前のActiveRecordでのみ使用されていたメソッドです。最新版の1.8.4ではrails2使用時にformat_log_entryを呼び出していますが、1.6.13ではrailsのバージョンに関係なく呼び出されてしまうため、エラーが発生してしまっています。 この段階でdb_charmerを保留することとしました。 seamless_database_poolに関して こちらに関しては調査した情報だと、controllerで接続先を設定するように書かれていたので、その段階で使わないことにしました。 multi_dbの改修内容 multi_dbを通常通りGemfileに入れて呼び出した場合、以下のようなエラーが出てしまいました。 /vendor/bundle/ruby/2.0.0/gems/multi_db-0.3.1/lib/multi_db/connection_proxy.rb:57:in `setup!': uninitialized constant ActiveRecord::Observer (NameError) 自分たちのプロダクトgemに求めていた機能は、「master/slaveをきちんと切り分けてアクセスできる」部分だけだったので、以下のソースをコメントアウトして対応しました。 lib/multi_db/connection_proxy.rb slaves = init_slaves raise "No slaves databases defined for environment: #{self.environment}" if slaves.empty? master.send :include, MultiDb::ActiveRecordExtensions - ActiveRecord::Observer.send :include, MultiDb::ObserverExtensions + #ActiveRecord::Observer.send :include, MultiDb::ObserverExtensions master.connection_proxy = new(master, slaves, scheduler) master.logger.info("** multi_db with master and #{slaves.length} slave#{"s" if slaves.length > 1} loaded.") end この修正のみで正常に動かすことができました。 テスト用スクリプトの実装 ここで実際にmulti_dbが、書き込みはmaster、読み込みはslaveへアクセスしているかのテストを書きたいと思います。テストをしたいと思います。 multi_db_test.rb 1 = begin 2 * MySQL に関するテスト 3 * テスト内容 4 ** modelを呼び出し、読み込みと書き込みを行う 5 ** slave, masterにきちんとアクセスが分かれているかは、mysqlのログで確認 6 = end 7 8 data = Hoge .where( :id => 1 ).first 9 p data.params1 # => 0 10 item.params1 = 1 11 item.save! 12 data_new = Hoge.where(:id => 1).first 13 p item.params1 # => 1 MySQLのクエリログを落とすようにMySQL上で設定し、実際に各サーバーに飛んできているクエリを確認してみます。 mysql> use <db名>; mysql> SET GLOBAL general_log = 'ON' ; mysql> SET GLOBAL general_log_file = '/var/log/mysql.log' ; 上記の設定をした状態で、テスト用のバッチを動かすと、masterとslaveには以下のようなログが吐き出されます。 masterのログ 130715 15 : 34 : 05 198 Connect hoge@xxx.xxx.x.xxx on hoge 198 Query SET @@ SESSION .sql_auto_is_null = 0 , @@ SESSION .wait_timeout = 2147483 , @@ SESSION .sql_mode = 'STRICT_ALL_TABLES' 198 Query SHOW TABLES LIKE 'hoge' 198 Query SHOW CREATE TABLE `hoge` 198 Query SHOW FULL FIELDS FROM `hoge` 198 Query BEGIN 198 Query UPDATE `hoge` SET `update_time` = '2013-07-13 00:29:02' WHERE `hoge`.`params1` = 1 198 Query COMMIT 198 Quit slaveのログ 130715 15:34:05 67 Connect hoge@xxx.xxx.x.xxx on hoge 67 Query SET @@SESSION.sql_auto_is_null = 0, @@SESSION.wait_timeout = 2147483, @@SESSION.sql_mode = 'STRICT_ALL_TABLES' 67 Query SHOW TABLES LIKE 'hoge' 67 Query SHOW FULL FIELDS FROM `hoge` 67 Query SELECT `hoge`.* FROM `hoge` WHERE `hoge`.`id` = 1 ORDER BY `hoge`.`id` ASC LIMIT 1 2 Query UPDATE `hoge` SET `update_time` = '2013-07-13 00:29:02' WHERE `hoge`.`id` = 1 67 Query SELECT `hoge`.* FROM `hoge` WHERE `hoge`.`id` = 1 ORDER BY `hoge`.`id` ASC LIMIT 1 67 Quit きちんとアクセスが振り分けられていることが確認できました。 今後の予定 現状としては、現在使用しているミドルウェアに接続できることの確認が終了し、いくつかのエントリポイントに関してのアクセスが正常に挙動することの確認ができ、テストを書きながら既存のソースを移行するフェーズとなっています。今後は移行に関してもっと全体的な進め方の方法などについて記載していきたいと考えています。 最後に 今回Rails4.0化におけるMySQLのmaster/slave構成に対する対処法について記載しましたが、この対処法がベストだとはおもっていません。もしもっといい方法で実現されている、おすすめの方法を知っている方がいましたらぜひ教えてください。いやVASILYに入って一緒にチャレンジしていきましょう! VASILYでは現在、一緒に働いてくれるエンジニアを大募集しています! 募集要項 連絡先:info@vasily.jp
こんにちは、金山です。 ただいま Google I/O 2013に参加 のためサンフランシスコにいます。 この一大イベントの参加報告会を5/29(水)20:30から弊社オフィスにて行います。 いつかGoogle I/Oに行ってみたいと思っている方、Googleの最新の動向が気になる方、Androidアプリの開発者の方などに現地でしか得られない情報満載でお送りしたいと思っています。参加無料ですのでお気軽に参加ください! ↓イベント参加はこちらのページから http://atnd.org/event/iqon201305
こんにちは。雨でハーフマラソンの大会をサボったiOSエンジニア庄司です。 本来大会で走っているであろう頃にこのブログを書いています。 今回はiQONアプリのWebViewで使っている技術についてです。 iOSでもAndroidでも使える内容なので、"UIWebView"ではなく"WebView"です。 実装の経緯 少ない開発リソースでマルチプラットフォームに対応するため、WebViewを利用することがよくあると思います。 iQONでも一部の機能において、iOSアプリ、Androidアプリ、スマートフォンブラウザで同一のWebViewを使って実装しているところがあります。 このWebViewについて、プラットフォームごとに別々のタイミングで変更があると、「Androidの特定のバージョンにカメラを起動するボタンを設置したい。」「でも、iOSはアップデート申請が通るまでボタンは表示できない」といったように、プラットフォームやバージョンごとにモジュールを出し分けたいといった要望が出てきました。 概要 以下の要件について実装しました。 アプリのプラットフォームやバージョンごとにWebView内で表示するモジュールを変える。 少しのデザイン修正なら、別のURLを切るのは面倒。同一のURLで済ませたい。 同じ内容のページなら、iOSでもAndroidでも同じERBテンプレートを使いたい。 実装 アプリの実装はiOSを例に説明します。 Androidについても基本的なロジックは変わりません。 フロー図 アプリからWebViewにHTTPリクエストする際に、UserAgentに "iQON/1.5.1" のようにバージョン情報を付加します。 Webサーバは、UserAgentからアプリのバージョンをチェックしてモジュールの出し分けを行いHTMLを返します。 iOSアプリの実装 UserAgentUtil.m Webサーバにリクエストする際に送るUserAgentにアプリのバージョン情報を付加します。 #import "UserAgentUtil.h" #import "UIDevice-Hardware.h" #import "ASIHTTPRequest.h" @implementation UserAgentUtil + ( void )setCustomUserAgent { NSString *defaultAgent = @"Mozilla/5.0 (iPhone; CPU iPhone OS 5_0_1 like Mac OS X) AppleWebKit/534.46 (KHTML, like Gecko) Mobile/9A405" ; NSString *iosDevice = UIDevice.currentDevice.platformString; NSString *osVersion = UIDevice.currentDevice.systemVersion; NSString *appVersion = NSBundle.mainBundle.infoDictionary[ @"CFBundleShortVersionString" ]; NSString *useragent = [NSString stringWithFormat: @" %@ iQON/ %@ ( %@ ; iOS %@ )" , defaultAgent, appVersion, iosDevice, osVersion]; // UserAgentにアプリの情報を含めて設定 ASIHTTPRequest.defaultUserAgentString = useragent; } @end AppDelegate.m アプリの起動時にカスタマイズしたUserAgentを設定します。 - ( BOOL )application:(UIApplication *)application didFinishLaunchingWithOptions:( NSDictionary *)launchOptions { ... // アプリ起動時にUserAgentを設定 [UserAgentUtil setCustomUserAgent]; ... return YES ; } Webサーバ側の実装 (Ruby on Rails) ApplicationHelper アプリのバージョンチェック用のヘルパー バージョンチェックにはRubyネイティブの Gem::Version を使っています。 module ApplicationHelper # UserAgentからiOSかどうかを判断 def ios? @ua ||= request.user_agent !!( / iOS | iPhone | iPod / =~ @ua ) end # UserAgentからAndroidかどうかを判断 def android? @ua ||= request.user_agent !!( / Android .* Mobile / =~ @ua ) end # iOSアプリのバージョンチェックメソッド def ios_app_version? (operator, version) ios? && valid_app_version?(operator, version) end # Androidアプリのバージョンチェックメソッド def android_app_version? (operator, version) android? && valid_app_version?(operator, version) end # バージョンチェック処理 def valid_app_version? (operator, version) unless %r! iQON/ ([^ ]+)! =~ @ua return false end # $1: 正規表現でチェックしたユーザのアプリのバージョン。"1.5.1"のような文字列 # version: 制限対象バージョン # operator 制限対象バージョンに対する演算子 compared = Gem :: Version .new( $1 ) <=> Gem :: Version .new(version) case operator.to_sym when :>= , :<= , :> , :< , :== return compared.try(operator, 0 ) else return false end end end ERBテンプレート側の実装例 バージョンチェックのヘルパーを使ってモジュールの出し分けます。 <% if ios_app_version?( ' >= ' , ' 1.4.0 ' ) || android_app_version?( ' >= ' , ' 1.0.31 ' ) %> < %# iOSアプリ1.4.0以上、または、Androidアプリ1.0.31以上のみ、カメラ起動ボタンを表示 %><a href="iqon://camera/">カメラ起動</a> <% end %> まとめ 今回はアプリで表示するWebViewで同一URLでもバージョンごとにデザインを分ける方法について書きました。 マルチプラットフォームのサービスのiQONでは、こういった方法でデザイン実装工数を減らすことができました。 大きなデザインの変更の場合は、別のURLで実装したり、そもそもネイティブで実装したほうが管理しやすいでしょう。 適切に判断して使ってみてください。
  先日3月18日にVASILY初となるエンジニアセミナーを行いました。 お忙しい中ご来場頂いた皆様ありがとうございました。 会場からはたくさんの質問を頂いたり、懇親会でも活発な意見交換を行なうことができ、 我々にとっても非常に有意義な時間となりました。 当日の発表資料をslideshareにあげておきましたのでぜひ御覧ください。 iQONの開発手法 at iQONエンジニアセミナー 「ぶっちぎりのファッションアプリにするために」 iQONエンジニアセミナー by VASILY (iOSチーム) iQON::BackEnd iQONエンジニアセミナー by VASILY (Backendチーム) 今回私達が紹介させていただいたものはiQONのほんの一部に過ぎません。 私達が未来のファッションメディアを作るために 日々取り組んでいることを今後もこの技術ブログで発信していければなと思います。 一緒にiQONを作っていけるエンジニアもまだまだ募集しておりますので ご興味のある方はぜひこちらまでご連絡ください。 info[at]vasily.jp
  こんにちは、CTOの今村(@kyuns)です。 今回は皆さんにお知らせがあります。 先月3億円の資金調達も実施しましたが、今回特にエンジニアチームを強化したいと思い、 エンジニアセミナーを開催することに致しました。 最近は大手広告代理店やソーシャルゲーム界隈からも営業やプロデューサーが弊社にジョインしてくれていますがまだまだエンジニアが足りません。 ファッションという媒体を扱う手前、オシャレじゃないと入社できなさそうとか、 ファッションにあんまり興味が...というエンジニアの方にもぜひ参加していただきたいセミナーとなっております。 オシャレじゃなくても大丈夫 弊社エンジニアも特別めちゃめちゃオシャレかといわれると残念ながら全くそうではありません。 どちらかというとファッションというビジネス領域に興味がある人のほうが多いです。 インターネットxファッションのメディアは実際に僕がヤフーにいた頃から 取り組んでいますが、まだまだ可能性が残されている事業領域ですし、 数年前に比べてスマートフォンが当たり前になってきている世の中にとっては さらに新しい事業やテクノロジーの出てくる分野だと考えています。 特にまだデファクトスタンダードなネットメディアがないファッションという 新しいカテゴリでチャレンジするからこそ、そこにやりがいがあると僕らは信じています。 当日は僕らがどのようなことを考えて日々iQONと関わっているのか、 なぜファッションなのか、技術視点でiQONの目指す未来を熱く語ろうと思います。 セミナー内容 第一部 1.) 弊社CEO金山裕樹によるトーク  - iQONサービス概要 20分 - iQONのサービスについての概要をご紹介いたします。 2.) 弊社CTO今村雅幸によるトーク - iQONテクノロジー概要 20分 - iQONでの開発スタイル、技術的な概要をご紹介いたします。 3.) 現場エンジニアによるLT(ライトニングトーク ) 30分 - iOS、Androidアプリエンジニアやバックエンドエンジニアによるライトニングトークです。 質疑応答 20分 第二部 懇親会 ( 60分 ) ※セミナー内容は変更される可能性があります。 開催場所・日時 3月18日 19:30~ ( 19時 受付開始 ) 株式会社グロービス・キャピタル・パートナーズ 1階セミナールーム 101 参加資格、料金 参加は無料です。 本セミナーへの参加はエンジニアの方限定とさせていただきます。 懇親会も予定していますので、弊社のエンジニアとも直接会って話する時間もございます。 持ち物 受付時に名刺を1枚頂戴いたします。懇親会用にもご用意下さいませ。 定員 50名 セミナーへの申し込みは こちら(ATND) から! 皆さんのご応募お待ちしております。
こんにちは。 年明けから自転車でずっこけて頬骨を骨折→入院→手術と迷惑かけまくったiOSエンジニアの庄司です。 最近、Objective-Cのオープンソースのライブラリを読んでいて、気になった機能があり、実際につかてみて便利だったので紹介します。 概要 クラスのカテゴリ機能を使うことで、既存クラスにメソッドを追加することはできますが、インスタンス変数を追加することはできません。 「関連参照(技術書によっては「連想参照」とも言われています。)」というテクニックを使うと『あるオブジェクトに対して別のオブジェクトの追加する』ことができます。 この機能とカテゴリを組み合わせることで、継承機能を使わずにクラス定義を柔軟に拡張することができます。 通常、クラスのインスタンス変数の定義は、インターフェース部にインスタンス変数の宣言を記述すると、そのクラスのすべてのインスタンスがその変数を持つようになります。 関連参照では、ランタイムシステムの機能を使って「オブジェクトへの参照をくっつける」「そのオブジェクトへの参照をくっつけて増やす」ことで擬似的にインスタンス変数と同じ機能を提供できます。 メリット 複数プロジェクトで使う機能や、オープンソースとして公開するときなどに、関連参照でクラスを拡張したクラスをインポートすることで、既存のソースコードの修正を最小限に抑えられます。 逆に、外部のライブラリを使っていて、コードを編集できない場合にクラスを拡張するのに使えると思います。 実装方法 オブジェクトの関連の設定、取得は以下のように書きます。ヘッダファイルは objc/runtime.h です。 オブジェクトの参照の関連付け (プロパティのセッタメソッドになる) // id object: プロパティを持たせたいオブジェクト // void *key: プロパティのアドレス (static で宣言した不変なローカルな静的変数を指定する) // id value: 保持させるのオブジェクト // objc_AssociationPolicy policy: 関連付けの方法 (後述) void objc_setAssociatedObject( id object, const void *key, id value, objc_AssociationPolicy policy) 関連付けたオブジェクトの参照 (プロパティのゲッタメソッドになる) // id object: プロパティを保持するオブジェクト // void *key: プロパティのアドレス (static で宣言した不変なローカルな静的変数を指定する) id objc_getAssociatedObject( id object, const void *key) objc_AssociationPolicy objc_AssociationPolicy は、参照オブジェクトがどのように保持されるかを指定します。 クラスのプロパティ宣言時と同じように設定すれば良いですが、現時点ではARCではweakに当たる設定がありません。 以下の5種類があります /* objc_setAssociatedObject() options */ enum { OBJC_ASSOCIATION_ASSIGN = 0 , // assign OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1 , // retain, nonatomic (ARC時はstrong, nonatomicと同意) OBJC_ASSOCIATION_COPY_NONATOMIC = 3 , // copy, nonatomic OBJC_ASSOCIATION_RETAIN = 0 1401 , // retain OBJC_ASSOCIATION_COPY = 01403 // copy }; typedef uintptr_t objc_AssociationPolicy; 詳細はMac Developer Libraryに記載されています。 実装例 UIImageView をたくさん使っているコードの中で、画像にタップイベントを持たせたいときに、 UIImageView を UIButton に置き換えて、画像を設定して、タップイベントのメソッドを設定して……とやっていると面倒です。 関連参照を使えば、 UIImagevView にBlocksのプロパティを持たせるようにして、実装できます。 カテゴリでプロパティの定義 #import <UIKit/UIKit.h> typedef void (^basicBlock)( void ); @interface UIImageView (Action) @property ( copy , nonatomic ) basicBlock imageViewPressedBlock; @end UIImageView オブジェクトにタップ時のブロック処理を設定 #import "UIImageView+Action.h" #import <objc/runtime.h> static char kImageViewPressedBlockKey; // 一意に決まって変更されないアドレスを定義 @implementation UIImageView (Action) @dynamic imageViewPressedBlock; // アクセサは自分で定義する // プロパティのセッタメソッド - ( void )setImageViewPressedBlock:(basicBlock)imageViewPressedBlock { objc_setAssociatedObject( self , // UIImageViewインスタンス(=self)にプロパティを持たせる &kImageViewPressedBlockKey, // 保持するオブジェクトのアドレスを imageViewPressedBlock, // 引数のBlocksオブジェクトをプロパティとして保時 OBJC_ASSOCIATION_COPY_NONATOMIC // Blocksオブジェクトはcopy指定 ); } // プロパティのゲッタメソッド - (basicBlock)imageViewPressedBlock { return objc_getAssociatedObject( self , // UIImageViewインスタンス(=self)が保持するデータを取り出す &kImageViewPressedBlockKey // 保持されたオブジェクトのアドレス ); } // 画像にタッチイベントの設定 - ( void )touchesBegan:( NSSet *)touches withEvent:(UIEvent *)event { [ super touchesBegan:touches withEvent:event]; if ( self .imageViewPressedBlock) { self .imageViewPressedBlock(); } } @end ↓実行結果のシミュレータのキャプチャ この UIImageView+Action は別の既存プロジェクトに持って行っても、以下の2点だけで使えるようになります。 1. UIImageView+Action.h をインポート。 2. UIImageView オブジェクトに imageViewPressedBlock を定義。 わざわざ各プロジェクトで拡張したクラスを用意する必要はありません。 iQONでの実装 iQONのアプリでは、コーディネートで使われている各アイテムをタップするとアイテムの詳細情報がポップアップするようになっています。 このポップアップの実装は、上の関連参照の実装例に少し手を加えています。 1. どのアイテムがタップされたかヒットテストを行う 2. タップされたアイテムのIDをBlocksに渡して、アイテムの詳細情報を取得してポップアップを表示する。 まとめ 継承を使わずにクラスにプロパティを保持する方法を紹介しました。 RubyやJavaScriptなど、他の言語にもあるような便利な機能ですが、他の言語と同様、既存クラスの拡張には注意が必要です。 追加するプロパティが多くなるようであれば、コード管理の混乱を避けるために通常通りクラスの継承を行うべきだと思います。
はじめに こんにちは、じゃがいもの皮はもっぱらキレイにむいて食べるエンジニアの村田です。 前回のエントリ iQONのバックエンドの非同期処理について ではざっくりとした方針とかを書きましたが今回は具体的な実装方法や運用方法などについて紹介したいと思います。 使用技術 iQONではResqueという仕組みを採用して、メール送信やDBの重たい更新処理などを非同期処理しています。 ResqueはRedisにキューを出し入れして遅延処理を実現する仕組みです。シンプルだし導入しやすいと思い採用しました。 このResqueの仕組みをdaemon-spawnというgemでデーモン化して運用しています。 イメージにするとこんな感じです。 導入方法 1.Gemfileに記述 gem ' resque ' gem ' daemon-spawn ' , :require => ' daemon_spawn ' initializersにRailsの環境ごとのredisサーバのホスト設定を記述 config/initializers/load_redis_config.rb require ' resque ' if Rails .env.to_s == " production " Resque .redis = ' redis.production.iqon.jp:6379 ' elsif Rails .env.to_s == " test " Resque .redis = ' redis.test.iqon.jp:6379 ' else Resque .redis = ' redis.dev.iqon.jp:6379 ' end 3.デーモンの起動処理を記述 script/resque_worker #!/usr/bin/env ruby require File .expand_path( ' ../../config/application ' , __FILE__ ) Rails .application.require_environment! class ResqueWorkerDaemon < DaemonSpawn :: Base def start (args) @worker = Resque :: Worker .new( ' default ' ) @worker .verbose = true @worker .work end def stop end end ResqueWorkerDaemon .spawn!({ :working_dir => Rails .root, :pid_file => File .join( Rails .root, ' tmp ' , ' pids ' , ' resque_worker.pid ' ), :log_file => File .join( Rails .root, ' log ' , ' resque_worker.log ' ), :sync_log => true , :singleton => true , :signal => ' QUIT ' } ) このスクリプトにstart/stop/restartなど渡して実行することでデーモンを操作します。 例えばこんな感じです。 起動 script/resque_worker start 停止 script/resque_worker stop キューをredisにエンキューする処理を実装する エンキューする処理はcontrollerでもmodelの中でも好きなタイミングで問題ないと思います。 大切なのはどのworker(あとでキューを処理する処理、つまり非同期で動く処理)にどんなパラメータを投げるかを指定するところです。 #TestResqueWorkerにparamsを渡す Resque .enqueue( TestResqueWorker , params) キューを処理するワーカーを実装する app/worker/test_resque_worker.rb class TestResqueWorker @queue = :default def self . perform (params) #DBの更新処理やメール送信などの重たい処理を実装する end end 具体例 ここまでは導入する最低限の概要でしたが、実際のiQONでどのようにしているかを一部紹介したいと思います。 例として、コーディネートモデルからキューを発行して、コーディネートに使われている各アイテムのブランドのコーディネート数を更新していく処理を見てみます。 キューの発行 コントローラからキューを発行してみます app/controller/coordinate.rb class CoordinateController < ApplicationController def update_brand_coordinate_count #params[:brands]は123,235,12345のようにカンマ区切りの文字列が入っている Resque .enqueue( UpdateBrandCoordinateCountWorker , { :brands => params[ :brands ]}) end end キューを処理する app/worker/update_brand_coordinate_count_worker.rb class UpdateBrandCoordinateCountWorker @queue = :default def self . perform (params) brand_id_list = params[ " brands " ].split( " , " ) brand_id_list.each do | brand_id | #brandテーブルのコーディネート数を更新していく if Brand .where( :brand_id => brand_id, :delete_flag => 0 ).exists? Brand .increment_counter( :coordinate_count , brand_id) end end end end このような形でResque.enqueueで非同期で処理させたい内容(どのworkerクラスに処理させるかを第一引数に、パラメータを第二引数に設定)をキューとしてRedisにエンキューします。 それを受けてUpdateBrandCoordinateCountWorkerがパラメータを受け取り実際の処理をしていくという流れになります。 Redisサーバを用意して、Railsアプリケーションにこれだけ実装すれば非同期処理を実現できるのは手軽で便利ですね。 気をつけること ここまでで導入と具体例を紹介させて頂きましたが、実際に開発、運用をしていく上で困ったことと対応方法を紹介したいと思います。 workerが受け取るハッシュのキーはシンボルではなく文字列になる サンプルコードでも書いてありますが、キューを発行するときにシンボルをキーにしたハッシュを渡していいますが、キューを処理するworkerの中では文字列をキーにしたハッシュとしてパラメータにアクセスしています。 これはResqueがキューを入れるときに内部的にto_jsonをしているからのようです。 開発を始めたときは結構ハマったのでこれから導入を考えている方は気をつけたほうがいいと思います。 workerがキューを処理中にdaemonをリスタートした時に中断されてしまう問題 この問題も最初はハマりましたが最初に紹介させて頂いたResqueWorkerDaemon.spawnで :signal => 'QUIT' という指定することで対応できます。 これはworkerが現在処理している内容をすべて処理し終わってからworkerプロセスを終了するというオプションになります。 特定のキューだけ優先的に処理したい場合 優先度の低い重たいキューがたまりすぎると優先度の高い軽いキューが詰まってしまう問題ですね。 class FirstPriorityWorker @queue = :first_priority #省略 end class SecondPriorityWorker @queue = :second_priority #省略 end class ResqueWorkerDaemon < DaemonSpawn :: Base @worker = Resque :: Worker .new( :first_priority , :second_priority ) #省略 end このように設定するとfirst_priorityのキューを優先的に処理することが可能です。 キューに対して優先度を設定したい場合などはこのように設定すればできると思います。 最後に Resqueはかなり簡単に非同期処理を実現できるので素晴らしいのですが、なんでもかんでも非同期処理にしてしまうと問題になることもあるのでそこは見極めが必要です。 あとは前述した注意点を守れば導入はスムーズに運用していくことができるかと思います。
  こんにちは最近一日に一回は波紋のビートを刻んでる村田です。 はじめに スマートフォンアプリの開発では回線状況や端末のスペックなど様々な状況下で動作するため、少しでも高速化できる余地があるのであればなんとか頑張りたいところですよね。 今日はサーバサイドとiOSアプリの間のデータのやり取りを高速化する方法をご紹介したいと思います。 サーバサイドはRuby on Railsを前提にすすめさせて頂きます。 こんな感じでできないか? 弊社ではアプリとサーバサイドのデータのやり取りをjsonで行なっていました。 レスポンスを受け取ってからパースする部分を高速化するには? と考えたときパーサ自体を高速なものに置き換えてみたりしましたが、それよりもplistバイナリで直接やり取りすればパースをする処理が省けるので高速化できないかという仮説に達しました。 このようなイメージですね。 実際どうなのよ? いきなり結論から行きますが、高速化できました。 以下具体的な実装の一部を紹介していきます。 実装(サーバ側) Rubyでplistのデータを扱うときにはCFPropertyListというgemを使うのが簡単でいいと思います。 Ruby on Railsで使う場合Gemfileに code  gem 'CFPropertyList' /code と追記してbundle installすれば大丈夫です。 実際にサンプルのコードを書いてみました。 code require 'cfpropertylist' class SampleController < ApplicationController def index #こんな感じのレスポンスをサーバサイドから返す response_data = {:info => {:total => 100, :count => 10}, :results => [{:name => "murata", :age => 27}, {:name => "6rats", :age => 28}, ... #省略 {:name => "ozaki", :age => 15} ] } if params[:format].to_s == "plist" #plistでレスポンスを返却 plist = CFPropertyList::List.new plist.value = CFPropertyList.guess(response_data, :convert_unknown_to_string => true) render :text => plist.to_str(CFPropertyList::List::FORMAT_BINARY) else #デフォルトではjsonで返却  render :json => response_data.to_json(:root => 'response', :skip_instruct => true, :dasherize => false, :skip_types => true) end end end /code formatというパラメータにplistが指定されたときにはplistのバイナリをテキストで返すようにしてみました。 最後のテキストにする時にCFPropertyList::List::FORMAT_BINARYを指定するなど少しだけハマりかけたところも有りましたが、これで大丈夫そうです。 サーバサイドではここまで準備できれば完了です。 実装(iOS側) 次にサーバサイドからデータを受け取ったiOSアプリ側の実装になります。 code self.parsedResponse = [NSPropertyListSerialization propertyListFromData:self.responseData mutabilityOption:NSPropertyListImmutable format:nil errorDescription:&aError]; /code self.responseDataにAPIサーバからのレスポンスが入ってself.parsedResponseが実際に扱えるデータになります。 iOS側はこの部分がJSONをパースしてという処理でした。 どれくらい速くなった? 1. APIからのレスポンスのデータ量の比較 ファッションアイテムの一覧を取得するAPIのデータ量の比較 plistバイナリのものは大体16KB jsonのものは大体約30KB 通信する際の容量の点でも結構削減できました 2.iOS内での処理速度の比較 同じくファッションアイテムの一覧を取得するAPIにリクエストした時を想定して計測してみます。 今回はAPIからのレスポンスを実機で100回パース処理を行なってみました。 計測結果 大体10倍くらいは速くなっていますね。 最後に いろいろ思考錯誤を重ねましたが、少なくともこの方法は高速化に貢献できたと思います。 サーバサイドからのデータ量が少し大きくなってしまった場合に、より効果を発揮しています。 エンジニアチームではまだまだやりたいこととできていないことがたくさんあります。 技術的なチャレンジを一緒に楽しめるエンジニアを大募集しています。
こんにちはiQONのバックエンドシステムを担当している唐揚げエンジニアの村田です。 このTechブログも最後の更新から早一年が経ってしまうのでこれはヤバイと立ち上がりました。 生きてます。 さて早速本題にはいります。 はじめに iQONのバックエンドのシステムは重たい処理をしなければいけないリクエストをAPIが受けた時に、「必ずそのタイミングで処理しなければいけないもの」でない限りできるだけ非同期に処理をしてレスポンスを素早く返すように作られています。 具体的には非同期に処理する内容をキューとしてキューサーバに登録しておき、レスポンスを返してしまって、そのあとAPIとは別のプロセスがキューサーバからキューを受け取り重たい処理を実行するという流れになります。 後で実例も含めて紹介しようと思いますが、ざっと絵にすると下のような感じです。 構成 ここに登場するものを簡単に説明すると スマートフォンアプリ、Webアプリケーション - 実際にユーザに使って頂いてるアプリケーションの部分になります。iOSアプリ、Androidアプリ、Webアプリケーションだったりですね。以下アプリケーションとします。 API - アプリケーションからリクエストを受けるバックエンドシステムのAPIになります。 DBサーバ - iQONのさまざまなデータが格納されているデータベースサーバ。 キューサーバ - APIがその場で処理しない「重たい処理」をキューとして貯めこんでおくサーバです。 Workerサーバ - キューサーバに溜まった「重たい処理」を実行するサーバになります。定期的にキューの状態をチェックしてキューがあれば順番にどんどん処理をしていきます。 メールサーバ - ユーザにお知らせのメールなどを配信するサーバになります。 このような構成で非同期処理を実現しています。 実際のiQONでの使用例 ではiQONの実際の機能ではどんなところに使われているのかを実例を交えて紹介したいと思います。 iQONでひとつコーディネートを公開したら - フォローしている人たちに公開したことをお知らせ - コーディネートに使われたアイテムをお気に入りしている人にお知らせ コーディネートの公開処理以外にもこのような処理が実行されます。 フォロワーが数百人いるユーザもいれば、コーディネートに使われたアイテムが数千人からお気に入りに登録されていることもあります。 とてもじゃないですが、そのすべてを一度のAPIの処理で完了させるのは難しいですよね。 まさにこの2つのお知らせ処理をキューにして非同期処理にしています。 上記の例で絶対に非同期処理にすべきではないのがコーディネートの公開処理ですね。 非同期処理にしてしまったら、きちんとコーディネートが公開されたのかどうかをユーザがその場で知ることができなくなってしまいます。 一方でコーディネートを公開するという処理以外はその場で認識できなくてもそこまで問題ではないですから、非同期処理にすべきです。 最後に 大事なのは非同期処理にしてもいいものとそうでないものをきちんと見極めてバランスよく実装することだと思います。 なんでもかんでも非同期処理にしてレスポンスを速く返せるというのは正しくないですね。 次回はこの仕組の具体的な実装の一部や使用している技術を紹介したいと思います。 アプリもバックエンドもいろいろな技術や方法を積極的に取り入れて常に進化していけるように日々エンジニアが挑戦をしています。 VASILYでは技術的なチャレンジを一緒に楽しめるエンジニアを大募集しています。一緒にファッションの世界を変えましょう。
こんにちは、この夏はほぼ毎日ガリガリくんを食べていた村田です。 最近無意識的にガリガリ君を食べなくなったことで秋を感じつつあります。 今回のリニューアルではiQONのバックエンド(DB、WebAPI、検索、バッチ処理など)のシステムを担当しました。 今日はファッションアイテムの検索について紹介したいと思います。 Apache/Solrの採用 今回のリニューアルを機にファッションアイテムの検索にApache/Solrを採用しました。   採用に踏み切った理由として 検索速度 今までiQONではファッションアイテムの検索にMySQLを使っていました。 日に日に増えるデータ量に合わせて検索のスピードは落ちて行き、その都度対応するという苦しい日々が続きました。 単純にMySQLを使う従来のやり方よりは確実にスピードは期待出来ると思っていました。 実際にフタを開けてみると約5〜7倍の速度を確保することができたので、速度の確保に関してはうまく行ったのかなと思います。 インデックス更新の柔軟性 ファッションアイテムはユーザもしくは僕たち運営の人間の手によって日々入れ替わっていき、更新されていきます。アプリケーションから動的にインデックスを更新できるのは必須条件でした。インデックスの更新(追加、変更、削除)がシンプルにできる点でSolrは大変使いやすいと思います。事実、実際の処理ロジックに容易に組み込むことができました。 スケーラビリティの確保 Solrはマスタ、スレーブ構成をとることができるので、今後トラフィックが増えた場合にもスケールさせることはそこまで難しくないのではと思います。 導入コスト、実績 オープンソースでわりときちんとメンテナンスされていて、導入に時間や手間がそこまでかからないという点を重視しました。Solrは毎日updateされるくらい頻繁にメンテナンスされているので、安心して導入することができました。また、実際の導入についてもJavaが動く環境さえあればダウンロードして10分もあればサンプルを動かせるほど簡単に動かすことができませんでした。 以前から気になってはいたけど、いろいろな企業での導入実績を知り、導入に踏み切りました。特にRubyKaigiに行った時にCookPadのプレゼンテーションを見てやってみようと決意しました。 ファッションアイテムに関連するバックエンドシステムの構成   処理の大枠の流れ バックエンドに対するリクエストは基本的にはすべてHTTPリクエストによって行われます。 アプリケーションからリクエストを受けてからレスポンスを返すまでの簡単な流れをまとめてみると 1. Varnishにリクエスト キャッシュの設定がされているリクエストであればキャッシュを返す、それ以外のリクエストであればRailsにリクエストを受け流します。 Varnishによるキャッシュについてはまた次回以降紹介したいと思います。 2. Railsアプリケーションによる処理 MySQLやSolrからリクエストされたデータを取得して、XMLやJSONの形でレスポンスを返却します。   これらの処理を高速にするためにMemcacheを活用しています。 Solrには検索に必要なデータのみを格納し、検索処理のあとにファッションアイテムのIDでMemcacheまたはMySQLから必要なデータを取得して最終的にレスポンスとして返却しています。 おわりに キーワード検索の精度などまだ課題はたくさんありますが、検索速度の向上など得られるものもかなり多かったと思います。 次回は実際のどのようにファッションアイテムのデータが検索、追加、削除、更新されているのかをご紹介したいと思います。