TECH PLAY

株式会社ZOZO

株式会社ZOZO の技術ブログ

937

初めまして。かとあつと申します。 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から必要なデータを取得して最終的にレスポンスとして返却しています。 おわりに キーワード検索の精度などまだ課題はたくさんありますが、検索速度の向上など得られるものもかなり多かったと思います。 次回は実際のどのようにファッションアイテムのデータが検索、追加、削除、更新されているのかをご紹介したいと思います。
アバター
みなさん、おひさしぶりです、キュン(@kyuns)です。 記事を書く時間がすっかり空いてしまい猛省... 先日iQONがリニューアルしました。 今回から数回に渡ってリニューアルまわりの記事を書きたいと思います。 まずはじめはシステムの全体的な構成についてです。 今回のリニューアルでは主に以下の点に絞ってシステムの構成を見直しました。 1.EC2インスタンスの引越し 2.Ruby化 3.内部APIモデルの採用 4.検索システムの見直し 1.EC2インスタンスの引越し 今まではAmazon EC2のUS-WEST (西海岸)で運用していたのですが今回のリニューアルにあたり EC2(東京リージョン)に変更しました。 もともとEC2の西海岸へはstaticなページでもレイテンシーが 400ms-600ms ほどあり、 不満がたまっていたところでした。 結果的に東京インスタンスへの移行は "劇的な" パフォーマンスアップにつながりました。 HA目的以外でまだ西海岸インスタンスを使っている人は今すぐにでも引っ越しするべきです。 EBS、およびS3のデータのコピー サーバー自体を東京インスタンスに移したことによりデータの保存先であるS3やEBSも東京リージョンに移行する必要がありました。 S3については名前を変えて新しく東京のバケットにつくりなおしました。 残念なことにAWSはデータを簡単に移行できるツールなどは用意してくれていないので 自前で西海岸のバケットから東京のS3バケットにコピーするスクリプトを組んで移しました。 EBSについても同様で手動でrsyncしました。 このあたりの作業は非常にめんどくさいですね。 AWSももう少しデータ移行まわりを強化してほしいです。 Cloud Frontの廃止 もうひとつ、AmazonのCDNサービスであるCloudFrontの利用も廃止しました。 今までは画像の配信にCloudFrontを使っていたのですが、S3のみのシステムに変更しました。 CloudFrontはとても便利なのですが、一度キャッシュされたものを削除するのが非常に手間だったのです。 AmazonCDNにはキャッシュを削除するAPIが用意されてはいるのですが そのAPIを実装したクライアントがほとんどないといっても過言ではありません。 現状僕が知るかぎりではCloudberryぐらいでしょうか。(しかもWindowsのみ) S3のみに変更したことによりレイテンシーが気になることは思いますが、 実際に調べてみるとCloudFront経由でS3の画像を取得した場合、一度キャッシュされた画像は 平均50msでレスポンスが返ってくるのですが、S3(東京)でもほぼ同等の速度がでており サービス運用に問題はないと判断しました。 2.Ruby化 今まではシステムのフロントエンドもバックエンドもPHPで構成されていたのですが 独自のPHPフレームワークを用いており、開発人数が増えたことにより メンテナンスおよび学習コストがかかるようになっていたのでRuby on Rails化することによって 複雑になっていた処理の見える化を行いました。 ソースコードは全てgitで管理されており、capistranoを用いてdeploy作業を行なっています。 capistranoについては以前に こちらの記事 で紹介しています。 アプリケーションサーバーはRuby1.9.2 + Rails 3.0.X + Passenger で動いています。 3.内部APIモデルの採用 内部APIモデルの採用の目的としては"データの取りだしやすさ"と"スケールのしやすさ"です。 近年Webアプリケーションではデータの加工部分のロジックは1箇所にまとめておいて データのアウトプット先がiPhoneやスマホ向けサイト等複数になってもデータを取り扱いやすいようにWebAPI化しておく いわゆる「API化」が進んでいると思います。 今回のリニューアルでもいわゆる内部APIモデルを採用しました。 データの取得、書き込みはすべてRESTfulなAPI経由で行っています。 フロントのアプリケーションサーバーにはロジックはほとんどいれず アプリケーションサーバーからAPIサーバーに対して HTTP経由で並列にデータを取得する流れになっています。 アプリケーションサーバーとAPIサーバーを切り離しているため デプロイも別々にでき、また台数も別々に増やすことができるためスケールが容易になっています。 また、キャッシュサーバーとしてMemcachedとVarnishを利用しています。 リアルタイム性が必要でかつデータの粒度が小さいものはmemcachedに入れておき 検索や一覧など、リアルタイム性のやや低いものはVarnishでレスポンスをまるごとキャッシュしています。 アイテムのデータやコーディネートの個別データなどはmemcached、検索一覧などはVarnish、といった感じです。 4.検索システムの見直し 今まではDBから検索するようにしていましたが、データ量が増え、 検索したい軸も増えてきたことにより検索エンジンを用いることにしました。 色々検討した結果、今回はAmebaやcookpadでも使われているSolrを使うことにしました。 これについては次回以降、nakedエンジニアの@6ratsが別途解説する予定です。 次回は 次回はiQONの肝であるエディター機能、および大規模JavaScript開発におけるノウハウなどを書こうと思います!
アバター
こんにちは。 VASILYのエンジニアの福本です。 前回は、 Redmineのbacklogsプラグインの概要とインストール方法 を紹介しました。 今回はインストール後の改善点について、メール設定とストーリ入力画面の改善についてご紹介します。 メール設定 SMTPにGoogleAppsを使用する VASILYではGoogleAppsを利用しているので、Redmineからのメール送信もGmailのSMTPを利用する事にします。 メール関連の設定は下記のファイルを参考に記述するのですが、親切にもGmail用の設定も雛形になっています。 redmine/config/configuration.yml.example GmailにはTLSを使用するので、いくつか作業してねと書いてあり RedmineBlog へのリンクが貼ってあります。 こちらの手順通りにやってみたいと思います。 1. TLS用プラグインのインストール Redmineのインストールディレクトリに行き、下記のコマンドを打つとインストールしてくれます。 ruby script/plugin install git://github.com/collectiveidea/action_mailer_optional_tls.git 2.設定ファイルの編集 先ほどの雛形ファイルを参考に、redmine/config/configuration.yml を作成します。 下記のようになります。 production : email_delivery : delivery_method : :smtp smtp_settings : tls : true address : "smtp.gmail.com" port : 587 domain: "smtp.gmail.com" # 'your.domain.com' for GoogleApps authentication : :plain user_name : "your_email@gmail.com" password : "your_password" 3.テストメール送信 管理者権限でログインし、管理>設定>メール通知の一番下に「テストメールを送信」というリンクがあります。 クリックすると自分のアカウントの登録メールアドレスにテストメールが送信されます。 何か問題があれば、エラーメッセージが表示されるようになっています。 これでGmailのSMTPを利用してメール送信をする事ができると思います。 非同期設定 メール送信はできるようになったのですが、バックログ管理画面やタスクボード上でのAjax操作が異様に遅くなりました。 先ほどの設定ではメール送信が同期設定になってしまっていたからです。 これだとメールが実際に送信されるまで処理が進まず、完了までに数秒待たされる事もあります。 非同期にできないかと調べていたら、やはりRedmine.JPにありました。 小技(0.9): 非同期メール送信を設定してチケット登録を高速化 (あとから雛形に書いてあるのを発見しました^^;;;ちゃんと見なきゃダメですね) 先ほどの設定のdelivery_methodのところをsmtpからasync_smtpに変更するだけです。 これで以前のパフォーマンスを取り戻しました! production : email_delivery : delivery_method : :async_smtp # ここを変更 smtp_settings : tls : true address : "smtp.gmail.com" port : 587 domain : "smtp.gmail.com" # 'your.domain.com' for GoogleApps authentication : :plain user_name : "your_email@gmail.com" password : "your_password" メール送信制御 ただ、バックログ画面やタスクボードの操作でいちいちメールが送信されるのもうざいですよね。 少し調べた限り、設定での制御はできないようです。 こちらはコードを見てみて、変更できそうであれば改修してしまおうと思います。 うまくいったら、また紹介させていただきます。 バックログ管理画面のプチ日本語対応 バックログ管理画面のストーリー入力は日本語にやさしくありません。 漢字変換を確定させるEnterで入力が確定してしまいます。 ひとまず下記のJSを修正する事で、入力確定をEnterでは無くCtrl+Enterにすることで対処しました。 redmine/vendor/plugins/redmine_backlogs/assets/javascripts/editable_inplace.js $ diff editable_inplace.js editable_inplace.js.org 23 ,26c23, 28 < if ( event.ctrlKey === true && event.which === 13 ) { // Ctrl + Enter < that.saveEdits () ; & lt ; } else if ( event.which === 27 ) { // ESC < that.cancelEdit () ; --- > switch ( event.which ) { > case 13 : that.saveEdits () ; // Enter > break ; > case 27 : that.cancelEdit () ; // ESC > break ; > default : return true ; ストーリーを入力する部分について全般的にちょっと使いかってが悪いので、時間を取って調整をしようと思っています。 またアウトプットが出てきましたらご報告します。 VASILYではエンジニアを募集しています。 一緒に変化を楽しみませんか? 詳しくは こちら をご覧ください!
アバター
はじめまして。 梅雨で頭がモジャモジャしはじめてきた天パエンジニアの福本です。 さて、VASILYではアジャイル開発の導入を進めています。 前回は、 デプロイ自動化の話 でしたが、今回はタスク管理についてです。 アジャイル開発ではストーリーカードやタスクボードなどを使用する事が多いですが、それらをWEB上で管理できるツールを導入しました。 Backlogsプラグイン アジャイル開発用のタスク管理ツールを探してみると、BacklogsというRedmineのプラグインが評判もよくシンプルで使いやすそうでした。 タスク管理にRedmineを使用していていた事もあり試してみた所、使い勝手が良かったので紹介します。 全体的な操作感としてはAjaxを多用していて直感的でサクサク動くのが気に入ってます。 プラグインを導入すると通常のプロジェクト画面にBacklogsとReleasesというタブがあらわれます。 バックログ画面 主に利用するのはバックログ画面とタスクボードで、Backlogsタブを選択するとバックログ画面に遷移します。 バックログ画面ではプロダクトバックログとスプリントバックログの管理ができます。 右側のプロダクトバックログにストーリーカードを書き出していき、上下にドラッグアンドドロップで移動させる事により優先順位の管理を行えます。 そして、スプリント計画時に左側のスプリントへドラッグアンドドロップで移動させ、スプリントの対象ストーリを決定します。 タスクボード スプリントの対象ストーリーが決まったら、タスクボード上でストーリーに対するタスクを管理していきます。 タスクカードの管理もAjaxを多用した作りになっていて、直感的にタスクの生成、割り当て、進捗の更新などができるようになっています。 その他にもバーンダウンチャートを表示したりと色々な機能があります。 まだ小さなプロジェクトで試験運用している段階ですが、また使用感など出てきましたらレポートしたいと思います。 インストール方法 使ってみたい!と思われた方のためにCentOSへのインストール手順を公開しておきます。 ストーリーカード、タスクボードの登録がうまくいかなくハマってしまうポイントがあるので是非参考にしていただければと思います。 Redmineのインストール Redmine.JPで1.2のわかりやすいインストール方法が公開されましたので、こちらを参照ください。 Redmine 1.2をCentOS5.6にインストールする手順 Redmine自体のインストールについては、Redmine.JPの 「Redmine 1.1をCentOS5.5にインストールする手順」 がとてもわかりやすいので、こちらを参照ください。 ただ最新バージョンが1.2.0に上がっているので、下記2点にご注意ください。 ・rackのバージョンが1.0.1から1.1.0に上がっている gem install rack -v=1.1.0 ・1.2.0のソース wget http://rubyforge.org/frs/download.php/74944/redmine-1.2.0.tar.gz Redmineの基本設定 Redmine上で基本的な設定を行います。 こちらもRedmin.JPの Redmineを使い始めるための初期設定 がわかりやすいので、こちらを参考に必要な設定を行ってください。 Backlogsプラグインインストール トラッカーの登録 管理者アカウントで、管理>トラッカー画面の「新しいトラッカーを作成」ボタンから、ストーリーとタスクというトラッカーを追加しておきます。 この時、「ワークフローをここからコピー」で既存にある「バグ」や「機能」を選択しておいてください。 トラッカーにワークフローが設定されていないと、ストーリーやタスクへの操作が何も許可されない状態になってしまいます。 この状態になるとストーリーカード、タスクボードの登録時にJavaScriptが正常に動かなくハマります。 必要パッケージのインストール gem install icalendar gem install prawn gem install holidays gem install open-uri-cached gem install ruby-units yum install -y libxml2 libxml2-devel libxslt libxslt-devel gem install nokogiri rmagickのインストール rmagickはちょっとややこしいですが、手順通りやっていけば大丈夫です。 ・ImageMagickのインストール yum install ImageMagick ImageMagick-devel -y ・TrueTypeFontのインストール wget http://www.mjmwired.net/resources/files/msttcorefonts-2.0-1.noarch.rpm rpm -ivh msttcorefonts-2.0-1.noarch.rpm /etc/init.d/xfs restart ln -s /usr/share/fonts/msttcorefonts /usr/share/fonts/default/TrueType ・rmagickのインストール # yumで入れられるImageMagickに対応しているバージョンまで下げる gem install rmagick -v 1.15.17 プラグインのインストール プラグインのインストールはGit経由で行います。(GitHubからDLする場合は こちらの手順 を参照してください) cd /var/lib/redmine/vendor/plugins/ git clone git://github.com/relaxdiego/redmine_backlogs.git cd redmine_backlogs git tag # バージョンのリストが表示される git checkout v0.5.2 # 最新バージョンを指定 RAILS_ENV =production export RAILS_ENV cd ../../../ rake generate_session_store rake config/initializers/session_store.rb rake db:migrate rake db:migrate:upgrade_plugin_migrations # Cannot find old migration table - assuming nothing needs to be done # と出るけど新規インストールなのでOK。 rake tmp:cache:clear rake tmp:sessions: clear rake redmine:backlogs:install # ストーリーとタスクをどのトラッカーにするか聞かれるので、先ほど作成したストーリーとタスクのトラッカーを指定します。 # ちゃんと設定されていないとストーリーカード、タスクボードの登録時にJavaScriptが正常に動かなくハマります。 チャート描画のための設定 rake redmine:backlogs:generate_chart_data /etc/init.d/httpd restart プロジェクトの作成 ここまでできれば準備完了です。 プロジェクトの設定で、モジュールのBacklogsのチェックボックスをチェックしましょう。 先ほどのようにBacklogsとReleasesというタブが表示されるはずです。 以上になります。 アジャイルさを大切にする環境で一緒に働きませんか? 詳しくは こちら をご覧ください。 参考サイト Redmine Backlogs公式ページ Redmine 1.1をCentOS5.5にインストールする手順 / Redmine.JP Blog BacklogsプラグインをMac上のRedmineにインストール / RedmineのBacklogsプラグインを入れてみた / プログラマの思索
アバター
はじめまして。 最近結婚しましたVASILYのエンジニア庄司です。 VASILY では最近、アジャイル開発を取り入れ始めました。 アジャイル開発では開発工程の早い段階でのデプロイ自動化が推奨されています。 ・開発の終盤でデプロイスクリプトを書くより安全・安心 ・自動化されていることで細かく頻繁なアップデートが可能 そこで、Rails定番で利用実績の多いCapistranoを選択しました。 今回はRailsアプリケーションの自動デプロイツールCapistranoを紹介します。 Capistranoとは Railsアプリケーションに特化したデプロイ自動化ツールです。 以下のような特徴があります。 ・Rubyで書かれたRails定番のデプロイツール ・デプロイ/リストアがコマンド一発 ・デプロイのバージョン管理 ・複数サーバにインストール可能。 ・配信先サーバには何もインストールしない。 ・配信中コケてもデプロイ実行前の状態にロールバックしてくれる。 ・git,subversion,Mercurialなどのリポジトリのデータをデプロイできる。 ・Rails以外のデプロイなどにも利用可能 (その分設定は複雑になる) 今回テストした環境 ・CentOS release 5.5 (Final) ・ruby 1.9.2p180 ・Rails 3.0.5 ・git 1.7.4 インストール方法 Gemfileに以下を追記し、bundle install を実行 #development環境にのみインストール group :development do gem ' capistrano ' end Capistranoを採用の前提条件 ・git,subversion等のSCMでソースコード管理されていること ・デプロイ先サーバにsshでログインができること デプロイの流れ 以下の流れで、gitから最新のソースコードを取得し、Railsアプリケーションを起動します。 初回の準備 $ cd /home/vasily/test_app #Rails Application Root $ capify . #capistranoのconfigファイル作成 $ vim config/deploy.rb #下記参照 $ cap deploy:setup #デプロイ先サーバに必要なフォルダを作る(初回デプロイ時のみ) デプロイ $ cap deploy #gitから取得 + Railsアプリケーション再起動 $ cap deploy:migrate #DBスキーマに変更があった場合のみ、本番DBへのmigrationを実行 deploy.rb require " bundler/capistrano " #(1) set :application , " test_app " #(2) set :scm , :git #(3) set :repository , " ssh://... " #(4) set :scm_user , “vasily” #(5) set :deploy_to , " /home/vasily/test_app " #(6) set :deploy_via , :copy #(7) set :user , " vasily " #(8) set :use_sudo , true #(9) default_run_options[ :pty ] = true #(10) role :web , " www.exampl.com " #(11) role :app , " www.exampl.com " #(12) role :db , " www.exampl.com " , :primary => true #(13) namespace :deploy do #(14) task :start do ; end task :stop do ; end task :restart , :roles => :app , :except => { :no_release => true } do run "#{ try_sudo } touch #{ File .join(current_path, ' tmp ' , ' restart.txt ' ) }" end end (1) Rails3用の設定 デプロイ後、bundle installを実行してくれます。 (2) Railsアプリケーション名 (3) リポジトリ管理ツール VASILYではgitを使用しています。 (4) リポジトリのURI (5) リポジトリユーザ名 (6) デプロイ先ディレクトリ (7) プッシュ式デプロイ デフォルトでは、デプロイ先サーバでgit cloneを行いますが、VASILYのgitリポジトリは社内ネットワーク内のサーバにあるため、本番環境からgit pullすることができません。 set :deploy_via, :copy と設定することで、git cloneしたソースコードをtarで固めてscpで配布します。 また、プッシュ式デプロイにすることで、デプロイ先サーバにgitをインストールする必要がなくなります。 (8) デプロイ先サーバにsshするユーザ (9) sudoの使用を許可するか (10) pseudo-ttyを利用するかどうか デフォルトはfalse。 リモートでsudoコマンドを実行する際に、「sudo: no tty present and no askpass program specified」というエラーが出る場合、これを設定するといいです。 (11) webサーバ 複数指定する場合は、以下のように設定します。(11), (12)も同様です。 role :web, "web01.exampl.com", “web02.exampl.com”, ... (12) デプロイ先Railsアプリケーションサーバ ほとんどの場合webサーバと同じ (13) migrateを実行するサーバ DBサーバそのものではなく、rake db:migrate を実行するサーバ (14) Railsアプリケーション再起動の設定 デフォルトではコメントアウトされていますが、Passengerを使っている場合はこのコメントを外せばいいです。 その他のcapistranoタスク cap -T でタスクを確認できます。 以下に代表的なタスクをまとめます cap deploy :cold #ソースコード配布 + migration + Railsアプリケーション起動 cap deploy :start #Railsアプリケーション起動 cap deploy :stop #Railsアプリケーション停止 cap deploy :restart #Railsアプリケーション再起動 cap deploy :rollaback #前回のdeploy前の状態にロールバック + Railsアプリケーション再起動 cap deploy :cleanup #デプロイ先の古いソースコードを最新版から5世代分(デフォルト)残して削除 デプロイ先のディレクトリ Capistranoでデプロイした先のサーバでは、バージョン管理の都合上、通常のRailsアプリケーションのディレクトリ構成とは違います。 ・releases バージョンごとのRailsアプリケーションのコードです。 ・current releases以下のいずれかのディレクトリのシンボリックリンク。通常は最新版を指します。 ・shared Railsアプリケーション内の一部のうち、ログファイルやbundlerで管理するgemパッケージなどデプロイの度に更新してほしくないファイルが置かれます。 実際のディレクトリ構成 /home/vasily/test_app/ |-- current -> /home/vasily/test_app/releases/20110603132533 |-- releases | |-- 20110530110205 .... | |-- 20110530121423 .... | |-- 20110530122100 | | |-- app | | |-- config | | |-- db | | |-- doc | | |-- lib | | |-- log -> /home/vasily/test_app/shared/log | | |-- public | | | --- system -> /home/vasily/test_app/shared/system | | |-- script | | |-- spec | | |-- tmp | | | --- pids -> /home/vasily/test_app/shared/pids | | --- vendor | --- 20110603132533 | |-- app | |-- config | |-- db | |-- doc | |-- lib | |-- log -> /home/vasily/test_app/shared/log | |-- public | | --- system -> /home/vasily/test_app/shared/system | |-- script | |-- spec | |-- tmp | | --- pids -> /home/vasily/test_app/shared/pids | --- vendor --- shared |-- bundle | --- ruby |-- log |-- pids --- system その他の機能 ・メンテナンスページへの切り替え ・独自タスクの設定 ・Railsアプリケーション以外での利用 など、便利な機能がありますが、これらはまた今後のエントリーで。 それでは!
アバター
  はじめまして、 iQON を運営しているVASILYのCTOの今村と申します。 この度サイトのリニューアルにあたり、心機一転、個性豊かなVASILYのエンジニア達による 技術ブログをはじめたいと思います。 一発目の記事ということなので簡単に弊社の環境紹介を。 弊社ではエンジニアをはじめとする全社員がMacを使っています。 Windows環境はVmwareFusion内にしかありません。 エンジニアには全員27インチのiMacが支給され、それを用いて開発を行っています。 また、自分が使いたいマウスやキーボードなども支給されます。 ちなみにVASILYのエンジニアのキーボードはRealForce派:HHKB派が4:1となっています。 僕は静音タイプのRealForceを使っています、一度慣れるともう戻れませんね。 マウスは全員トラックボールを使っています。 これも慣れるまでは大変ですが 慣れると普通のマウスにはもどれないぐらい快適です。 Realforce   トラックボールマウス     BOSEのノイズキャンセリングヘッドフォンのQC3       この3つを合わせて社内では エンジニア三種の神器 と呼んでいたりします。 労働環境については、設立当初からこだわりをもっているところですので 今後ともよりよい労働環境の追求をしていきたいと思っています。 さて、そんなVASILYでは、只今僕達と一緒に働ける仲間を募集しています。 詳しくは こちら 僕達と一緒にインターネットを使ってファッションの未来を変えていきませんか?
アバター