As Sloth As Possible

可能な限りナマケモノでありたい

ふと思い立ってA successful Git branching modelとかを今頃読んでみたり。

最近のお仕事、基本的に一人でiPhone/Androidアプリを作ってて同じコードを複数人で弄ることがなくて、しかも大体の案件が一月くらいかけて一から作るとか丸ごと作り直すとかそういう感じなので、割と開発プロセスとか考えずにフリーダムにやっちゃってるので、まぁあまりよろしくないなと反省をしている。かといってgitの機能を隅から隅まで使いこなしてるとか自分にとって最高にやりやすい状況になってるわけでもなく、むしろあんまり開発サイクルが上手く回ってなくて、でも人にすぐ渡せる状態かというとそうでもなく、というわけで意識的になんかの約束事に乗ってみることにした。

で。ついでにgitconfigいじってたりああそう言えばと思い出してzshrcいじったりしてたら日が暮れてた。開発環境整備すんのなんであんな楽しいんだろな。やりすぎ良くない。

なんか金縛りにあって眠れなくなったのでTwitter見てたら面白いやりとりをみかけた。すげー大雑把に要約すると、こんな感じ。

  1. Railsの勉強をしてる人が「なんでHello worldしたいだけなのにDB必要なの!?DB使うにしたってMySQLなんか今必要じゃないしチュートリアルではSQLiteとかにしといてくれたっていいのに!プリプリ!」みたいな呟きをする
  2. 「いやRails使うんなら普通DB要るでしょ。なんでそこに文句言ってるの。」とツッコミが入る
  3. 「とりあえずチュートリアルは一番簡単なやり方書いといてくれた方が親切じゃないですか。」と反論
  4. 「いやだから、Railsを使うのに一番当たり前の構成を作るのにはどうしたらいいかがチュートリアルでしょ。それが必要ないんなら別なの、例えばSinatraとか使えばいいじゃん。フレームワークの特性も分からずに使っておいてチュートリアルがおかしいとか筋違いだろ。」と再度ツッコミ
  5. 「Railsがどんな特性のフレームワークとか知らないから勉強してるのに…最初っからそういうの全部考えてやれって言われても…僕はただRailsを手っ取り早く学ぶ手段があればいいのにって思っただけなのに…」みたいに凹んでしまう

ってな感じ。んで、それと隣接して外野で「最初は知らないことだらけなんだからおまじないだって言っておけばいいじゃん、フレームワークの特性がーとか大鑑巨砲主義がーとかどうでもいいだろ、やってみなきゃ分かんないことだってあんじゃん」「いや不要なとこに不要なもの使おうとしてるから適切なのはこっちだって教えてやっただけだろ、なんでわざわざ面倒なことやろうとしてるのにフレームワークに文句付けてんだよ」みたいな論争が若干生じてたりとか。

なんちゅーかね。この件に関して言えば、「それRailsに文句付けるとこじゃねーよ」って指摘は妥当だとは思う。Railsは「中規模のアプリケーションを、一般的な構成で」作るときに楽になるように設計されてるものなので、それを知らないまま進むと大概後で苦労する(レールに乗るから楽なんであって、レールから外れると荒野だ。今は大分良いけど、昔は黒魔術に手を染めないとレールから外れることすらできなかったんだよ!)。

そんなの知らないから勉強してるんだろ、それは後から考えさせろって?いや、WAFの選択って、「どんな目的に何を使うか」も重要なことなので、何かWAFを学ぶってときにはそういう選択の力も付ける必要はあると思うよ。WAF一般について知りたいんじゃないんだ、Railsについて勉強してるんだって?だったらとりあえずRailsの流儀に沿っておきなよ、何か意味があるからそういう構成になってるんだよ。分からないなら文句付けてないで、何でそうなってるか考えようよ。

少しまとめる。

  • 何かにとりかかろうとして、チュートリアルの時点で「なんでこんなことせにゃならんのだ?」って思うなら、「自分がやろうとしてることに適切じゃないものを使おうとしている」か、「今取り組んでいることを正しく理解できてない」可能性を先に疑った方がいい。大概の場合は「あー俺が間違えてたわー」ってなるか「あーこういうことだったのかー」って後で分かる。まぁ本当に提供側が不親切だったりいい加減だったりすることもままあるけど、それにつっこめるのはある程度使い熟せてからだ。
  • 教える側は「これはおまじないだから、そういうことだって思ってやりなさい」を安易に使うべきじゃない。何にか躓いてるときは、何が間違ってて何が理解できてないのか把握する・させるチャンスなので、そのタイミングで教えてあげればいいのにと思う。あんまり深入りさせてなかなか最初のステップに到達しないのはアレだけど、「おまじないだからとりあえず無視して」って言っちゃうとほんの少し潜ってみる足掛かりすら失なう。
  • でも、分からないことを責めるな、とは思う。「うぇぶかいはつしゃもすなるれいるずをしてみんとてするなり」みたいな人に「お前はゴキブリ退治に第七艦隊を呼ぶのか?」みたいなツッコミ入れたってそもそも分かってなかったからそんなことしてるんでしょと。単に教えてあげればいいじゃない。

どうでもいいけどRailsは「最初の一歩」には丸っきり向いてないと思うんだけど、なんでみんなRailsやりたがるし教えたがるんだろね。不思議。

あるいは、お遊びチーム2号は一体何をしていたのかについて

ISUCONという大変白熱した楽しいお祭を開催するにあたって、その前夜祭的な環境試験のためのチューニング祭が社内の有志数名で行われていて、そのときに色々学んだことをおまけとして書いておきます。

ISUCONて何?

下記参照。

要するに、「閲覧者視点での振る舞いさえ満たしてくれれば何をしようと構わんからWebアプリのレスポンスを改善しなさい」というお題で、誰が一番速くできるか競うイベント。

最初にある程度環境が整備されてるサーバ4台と、主催者側が用意した参考実装のアプリとテスト用データが渡される。このアプリってのはごくシンプルな個人ブログだと思ってもらえば良くて、最新記事10件が表示されるインデックスページと、記事全文が見られる詳細ページがあり、記事の投稿とコメントの投稿ができて即時反映される。全てのページには「最近コメントが付いた記事10件」が表示されるサイドバーがあり、ヘッダ画像、スタイルシート、JavaScriptが読み込まれる。テストデータにはそれなりに大量の記事データとコメントデータが入っている。フロントのWebサーバはApache、DBはMySQL、アプリはPerlとRubyとNode.jsのものが用意されている。

「お遊び組」って?

ISUCONは見学席が用意されていたのだけど、参加者以外はぶっちゃけ暇なので、空いたサーバ1チーム分を使って好きに遊んでいいことになっていた。で。そのお遊び用サーバで、事前に社内βで散々いじくり倒した俺とhidedenさんが空気読まずに自分のアプリで参加者達に対抗する、という大人気ないことをしていた。(すいません…)

一応ちゃんと言っておくと、中の人なので裏ルールや罠の存在についてある程度聞いていた上、6時間どころか2日3日費して試行錯誤していて、加えてhidedenさんに圧倒的な差を付けられた時は教えを乞うなどしていて、完全なるチートです。正直あの短時間でここまでやってくるって本当凄いな参加者のみなさん、と感動しっぱなしでした。勝手に熱くなって最後の方はムキになってたのは内緒です。

※ あ、でもでも、参加者の条件と対等じゃない「強くてニューゲーム」だった(会場に着くやいなや会社に行ってソース取って来た)という意味での「チート」で、コンテスト向けの実運用ではありえない実装にするとかはしてないです。なるべく突飛なことをせず普通にWAFを使い普通に配置するように心掛けてます。サーバのセットアップとかは手伝って貰ってるけど、アプリの実装は一人でやってるし、戦略そのものも自分で考えてます。

というわけで以下は俺のアプリで実際にやったこと。お遊び組ベストスコアを出した方とは別のアプリです。ちなみに使用言語はRuby。隣に座ったCTOと向かいに座った部長に「うちPerlの会社だからね?」とニヤニヤされながらも100%趣味全開のチョイス。「言語処理系の性能の違いが戦力の決定的差では無いということを教えてやる!」と赤くて速い人気取りで息巻いてみたものの、実際問題別な意味で全くその通りだった

糞クエリ対策とキャッシュ

殆どのチームがまずはクエリの見直しとDBのチューニングに手を付けてた様子。テーブル構造の見直しからMySQLのオプションやストレージエンジンの変更とかをやってたチームもあったみたいだけど、俺は必要なカラムにインデックスを貼る程度に留めて、データをガンガンキャッシュしてなるべくDBまで到達しないようにする戦略にすることにした。

まぁまずみんな真っ先に気付いてたところとしては、アプリ内に非常に残念なクエリがわざと仕込んであるということ。素晴しく分かりやすいお手本のような糞クエリだった。サイドバーのデータを取得するのが次のようなクエリ。

SELECT a.id, a.title
FROM comment c
LEFT JOIN article a ON c.article = a.id
GROUP BY a.id
ORDER BY MAX(c.created_at) DESC LIMIT 10;

記事が数千件、コメントが十数万件あるので、これは大分辛い。しかもサイドバーは全ページに表示されるので、全てのリクエストでこのクエリが発行されるという鬼畜さ。なので、次のように変更する。

  1. キャッシュからサイドバーに表示する記事IDのリスト取得を試みる
    1. なければ、commentテーブルから記事IDのみのリストを取得する
    2. キャッシュする
  2. 記事IDのリストを元に、キャッシュから記事データ10件のリスト取得を試みる
    1. それでも無かった数件をarticleテーブルからWHERE id INで取得する
    2. キャッシュする

あと、サイドバー・インデックスページ・記事詳細ページでそれぞれ必要なカラムだけを取って使ってたけど、これは逆にID・タイトル・本文・投稿日時全てを取得してIDをキーにキャッシュに突っ込むようにした。こうすることでサイドバーとインデックスと記事詳細でキャッシュを共有できるようになる上、コメントの投稿も記事の参照も新しい記事に偏っているので、サイドバーを読み込む時点では記事データはほぼ全てキャッシュに載っていることが期待できる。

commentテーブルから記事IDのリストを取得してるところはまだ重いと思うけど、当初のクエリよりは遥かに速いし、並行してコメントが投稿されたときの整合性の担保とかするのが地味に面倒だったので、とりあえず後回しにする。

コメントはコメントのIDではなく記事のIDでまとめてキャッシュするようにした。ページングや並べかえ、コメントの削除や編集などは仕様になく、コメントのパーマリンクなどもないので記事ページ以外では表示されないため、1回で全部取れるのがよかろうという判断。それらがあるようだったら、記事と同じようにコメントIDをキーにして「リストの順番や内容が変更されてもIDだけを取ってくるようにして、データ自体はキャッシュに載ってるものは使う」みたいにしたかもしれない。でもあんまりDBやmemcachedへのリクエストが増え過ぎるのはアレなので何らかのまとまりでキャッシュするかなぁ、とか色々考えてたけど、複雑になって余計悪化する、みたいなことにもなりかねないし、微妙に難しい。これもとりあえずこれでいいやってことにして後回し。

ノンブロッキングなフレームワーク

若干工夫してみたのは、フレームワークにはGoliathを選んだあたり。EventMachineベースのRubyのWebアプリケーションフレームワークおよびサーバで、これでI/Oを多重化して、同時接続数が増えてきても重いI/O処理でブロックしないで効率的に処理してくれることを期待する。まぁ、ぶっちゃけこれは実際にはそんなに効果なかった。

当初はPOSTのリクエストがばんばん飛んでくるとか、複数のテーブルに跨ってデータを取ってきて複雑な集約をするとか、画像アップロードみたいな長時間コネクション張り続けるようなリクエストがあるかなとか、そういう状況を想定してたんだけど、実際にはGETの比率のが圧倒的に高いしテーブル構造もシンプルで投稿はテキストのみ、みたいなパターンだったので「並列にI/O処理をする」ことがあまりなかった。

「遅延書き込みをするようにしてPOSTの際は即座にレスポンスを返してしまう」みたいなこともちょっとはやろうとしてたのだけど、「POSTリクエスト完了後1秒以内に表示に反映すること」ってルールにひっかかってテスト通らないことが頻繁にあったりして地味に嫌な感じになったので方針転換した。ここはむしろ同期処理にして「終わったら即書き込み即キャッシュ破棄」するようにして(実際には反映までにかかる時間に大差は無いはずなんだけど、こう振る舞った方がクライアント側からは速く反映されてるように見える)、POSTでは若干の時間をかけてしまってもいい、それよりもGETリクエストが来たときに既に準備が整ってるようにここで再取得再キャッシュまでやってしまう。

同時接続数の方はどうかというと、そもそもベンチマークスクリプトの並列数が最大10とかだったしコネクションも一瞬で切断されるし、じゃあワーカプロセスを10個立ち上げちゃえばいいじゃん、というオチが付いた。CPUもメモリもスッカスカでアプリサーバが遊んでたし、RubyやPythonみたいな言語を使う分には、1プロセス辺りの並列処理の効率化みたいなとこよりもUnicornの割り切りっぷりの方が現状理にかなってると思う。てことで言えばぶっちゃけ最初の素のSinatraで別に問題なかったような…どうしてもEventMachine使いたいならRainbows!Async Sinatraとかでも良かったような…まぁもう書いちゃったしいいか…。

余談だけどGoliath採用に併せて関連ライブラリも選び直すことになったので、MySQLクライアントにはMysql2を、mechachedクライアントにはremcachedを、ついでに趣味でテンプレートエンジンにSlimを使った。Mysql2やSlimは大分良かったので何か機会があれば使っていきたいところ。

Web「アプリ」って何?

とにかくもうDBに複雑なことさせたら負け、それ以前にDBに行ったら負け、と来たら次に来るのは「ていうかアプリに行ったら負け」。この段階ではフロントのWebサーバはアプリサーバ2台のロードバランサとしての仕事しかしていなくて、静的ファイルもアプリ側でファイル読んで返してたし、更新してないページも毎回アプリにリクエストが来ていた。バックエンドのアプリケーションサーバがどんなに速くなってもフロントエンドのWebサーバの処理速度とは文字通り桁が違うので、できればなるべくバックエンドに行って欲しくない。ならばということで、フロントエンドでページ丸ごとキャッシュしてGETリクエストは全部そっちで返してしまうことにする。

VCLの記述力やキャッシュ管理のしやすさ、ESI機能、あと名前のかっこよさなどが魅力的だったので、最初はVarnishを使ってみた。directorでアプリサーバ2台をまとめて、GET以外のリクエストは素通りするようにして、GETのレスポンスはページ単位でキャッシュするようにする。このままだと当然「投稿は1秒以内に反映されること」というルールが満たせないので、HTTPでパージできるように設定して、POSTのリクエストを処理したらアプリ側からVarnishにPURGEリクエストを送るようにした。

そうなると今度はキャッシュの破棄のタイミングが問題になってくる。記事の投稿はまぁいい。記事が投稿されて内容が更新されるのはインデックスページだけなので、1ページ破棄してやればいい。が、問題はコメントの方。コメントが投稿されるとまず記事ページが更新されて、「最近コメントが付いた記事10件」が表示されるサイドバーも更新される。が、このサイドバーは全てのページで表示されている。どこかの記事に1件コメントされる度に全ページのキャッシュが破棄されてたのでは殆どキャッシュの意味を為さない。なのでサイドバーは各ページのレンダリング時には生成せず、ESIでVarnishの段階でincludeさせるようにした。こうすれば、コメントが投稿されたときにパージするのは該当する記事ページとサイドバーだけになる。

そこまでやると最初の数回とPOST直後の数回以外は全部Varnishが返してくれるようになるので、アプリサーバの方は殆ど遊んでいる状態になる。前の段で「リソース余ってるからガンガンプロセス増やしちまおうぜ」って言えたのはこのおかげ。数万リクエスト処理しても数百とか数千くらいしか後ろに到達しなくて、さらにその後ろのDBまで行くのはもっと絞られてくる。この段階で初期状態から10倍くらいパフォーマンスが向上している。

ところがこの辺りで地味に嫌な罠を踏む。VarnishでESIを使うとContent-Lengthヘッダを返さなくなるので、Keep-Aliveで接続してるクライアントがいつレスポンスが終わったのかよくわからなくてタイムアウトするまで一部のクライアントできちんと扱えないのにKeep-Aliveのリクエストを送ってきたときにいつレスポンスが完了したのか判断できず、コネクションが切れるまで待ち続けてしまう。設定でどうにかできそうな気がするけど不慣れなVarnishに四苦八苦してなかなかうまくいかず、前段にもう一段Nginxを立ててリクエスト/レスポンスをいじってみたりするも今度は多段にしたのが祟ってそのオーバーヘッドで大分パフォーマンスが落ちてしまう。

結局Keep-Alive問題は真面目に対応するのをやめていかなる場合でも無効にしてしまおうかーとか考えてるあたりで、hidedenさんのNginx/SSI+SCGI構成にダブルスコアをつけられてしまって、NginxマジはえーほんとパネェつかVarnishってぶっちゃけNginxより遅いのに何で選んだの、みたいな声に負けてVarnishと戯れるのを放棄することにした。いや多分、俺のVarnish力の低さのせいで真の実力を発揮できてなかっただけでそこまでオワコンでは無いと信じたいのだけど、と一応擁護しておく。でもしばらくはNginx一択だけど。

ちなみに、ベンチマークスクリプトはそもそもKeep-Aliveに対応してないその「一部のクライアント」と同じ挙動をしていたわけではないので、この時点ではこの問題は割り切って無視するという選択肢もあった。が、本番当日の講評の際にkazeburoさんが恐しい罠を仕込んでたことを知らされる(っていうか社内では普通に話してたらしいけど聞いてなかった)。なんと、HTTP1.1じゃない持続的接続ができないのにそうできるかのように偽装してKeep-Aliveって付けた嫌がらせリクエストを3%程混ぜており、これに律儀に応えると、ベンチマークスクリプトはコネクション切れるまで待ち続けてしまって致命的に遅くなる、というもの。Nginxが素晴らしいのは周知の事実なので本番でも使ってくるチームが多いことを予想して、「Nginx(や、他の高速Webサーバやキャッシュサーバ)をチューニングせずにただ設置するとハマる罠」を仕掛けたんだとか。そうと知らずにそれを回避することに成功していて怪我の功名だった。本当運営の人達悪魔や…。

Nginxのチューニング

気を取り直してNginxの設定。まずNginxはWebサーバなので、餅は餅屋ということで静的なファイルはアプリサーバからフロントサーバに全部持ってきてNginxに返させてしまう。これでアプリから静的ファイルの配信機能を取っ払うのに成功して、本当に若干だけど無駄な処理を減らせる。リバースプロキシの設定は簡単なのでこれも普通に設定してしまう。それからVarinishのESI同様NginxでもSSIを有効にする。

それからキャッシュ。Nginxのキャッシュの方法はいくつかある。まず直感的なのはファイルキャッシュ。キャッシュファイルの置き場所を決めておいて、upstreamに飛ばすlocationのところでcacheを使うよって指定してあげれば、upstreamからのレスポンスを自動的にそこに貯めてってくれて、二回目以降は勝手にそのファイルから返すようにしてくれる。Varnishのときはキャッシュストレージにメモリキャッシュとファイルキャッシュが指定できて、ファイルの方を指定すると格段に遅くなるので、Nginxもそうなるんじゃないかと思ったけどこれが驚く程高速でびっくりする。むしろVarnishのメモリキャッシュの時より速いくらいだった気がする。ちゃんとは検証してない。ただ、このファイルキャッシュはuriをハッシュしてディレクトリに配置してしまうため、外からはどれが何のキャッシュなのか分からなくて、Varnishに比べるとキャッシュの管理が難しい、と思ってたら、ちゃんとこういうモジュール作ってる人がいた。これならVarnishのときに作ったHTTP越しのパージの仕組みがそのまま使える。

もう一つ、memcachedをまるでアプリサーバのように見立てて、pathをキーにしてmemcachedにあるデータをそのままレスポンスボディにして返してしまうという驚きのモジュールもある。こっちの利点は、アプリ側からもキャッシュが扱い易いということ。普通にアプリからmemcachedに繋いで、Nginxにレスポンスを返すときに同時にmemcachedにも書き込んでおくと、次はそっちから読んでくるようにしてくれる。破棄するのも普通にdeleteすればいい。HTTPリクエストを投げてパージするよりは分かりやすいし、memcachedプロトコルのがHTTPよりは速そうだ。が、問題は、前述のファイルキャッシュより遥かに遅いということ。これはmemcachedが遅いというよりmemcachedに毎回コネクションを張り直すコストがファイルキャッシュからの読み込み(これはおそらくかなり内部で最適化されているはず)のコストよりも高いせいらしい。ほぼ同じ状態でファイルキャッシュをmemcachedに切り換えたら、スコアが半分以下になってしまって絶望的な気分になった。仮にHTTP越しのパージよりもmemcachedのがアプリ側からは扱い易くてコストも低かったとしても、大半はNginxの段階で完結するキャッシュ済みGETリクエストなので、そっちのオーバーヘッドの方がもろに結果に影響する。ので最初はファイルキャッシュを採用した。

けど、hidedenさんの方はmemcachedを使っててそれでも俺のやつよりパフォーマンス出てるのでなんでだろうと聞いてみたら、upstreamとのコネクションを繋ぎっぱなしにしていたからだったらしい。試しにkeepaliveを設定してみたら、アプリ側何もせずに一気に3倍くらいのスコアになってhidedenさんのスコアに肉迫するレベルになった。同じことを当日もやらかした。hidedenさんが毎分11万リクエストというハイスコアを出した一方、前日まで大差はついてなかった俺の方は3万程度しか出てなくて焦ったのだけど、nginx.confを見直してkeepalive付け忘れに気付いて再起動したらちゃんと動いた。

で、結果はというと、ギリギリ100000req/minを越えるくらい。「お前はチートしてそれかよショボいな」と言われないくらいの結果は出せたと思うのでちょっとホッとした…。

この戦略の意味

POSTよりGETが圧倒的に多く、大半のリクエストが新しいデータに集中し、一度書き込まれた投稿はその後はあまり書き換えが起こらない、というのは、「大部分がほとんど書き換わらない動的コンテンツ」ではなくて「一部分が書き換わることがある静的コンテンツ」だと思っちゃえば少し話が簡単になる。

GETリクエストなんか静的なHTMLファイルを自動生成するためのトリガー、くらいに考えて、ただそれがファイル書き出しじゃなくてメモリ上のキャッシュに突っ込む方が扱い易いよねって発想で行けば、どんなミドルウェアが欲しいかとか、アプリは何をするべきなのかとか、どこで一番頑張るんだそうかフロントのWebサーバか、みたいなことでやることが決まる。てか、どっか1チームくらい本当にHTMLファイル書き出してデプロイしちゃうとこ無いかなーとか思いながら見てた。多分それはそれで速い。絶対面倒なのであまりやりたくは無いけど。

もっとぶっちゃけ話をすると、ライブドアブログの閲覧側チューニング戦略が(実現してる方法は違うけど)大体この形なので、ページのキャッシュと更新の局所化は最初からやる予定でいた。アプリエンジニアの性でついついアプリいじりに時間を割きたくなっちゃうとか、ミドルウェアやサーバ管理の知識経験が致命的に不足していたので時間ばっかり食ってしまったとか、ってのが「時間内には終わらなかったけど最終的には出来た」の実情だったりします。アプリエンジニアは常日頃からそういう知識をちゃんと収集しておけ、あとインフラチームと仲良くしておけ、色々捗るぞ、というお話でした。

上手く行かないケースと使い回せるテクニック

ブログ型の単純な表示系のリクエストが多いお題だったからWebサーバの性能が結果に直結してたけど、更新系のリクエストが多くて条件によって表示要素の個数や種類が大きく変わる、みたいな場合だと今度はアプリやDBの方に比重が移ってくる。例えばTwitterのサブセットみたいなのがお題だったらまた全然結果が違ったはず。

データのキャッシングやSSIみたいな仕組みはその場合でも有効だろうけど、「Nginx置いたらパフォーマンス20倍になったwwwwマジうめぇwwwwアプリ関係ないwwww」みたいなシンプルなことにはならないので、そっちの場合はアプリの実装力を鍛えてないと死ぬ。ISUCONに「部門別」とかあったら面白いかもねーとか思ったけど準備する側が死にそうなので軽々しく言うのはやめときます。僕お手伝いって名目なのに普通に遊んでただけでほんとごめんなさい。

反省点とか

DBロクに見てない。上位陣の方々見てるとMySQLバリバリチューニングしてるので、もっとちゃんといじればもう少し速度出るはず。一番効果が高いところを優先したと言えばそうだけど、あんま詳しくないところを放置しただけだったりもするので(アプリの全面書き直しとかマジで要らんかった)、ちゃんと勉強する。

みんなもやってみるといいよ

ISUCON運営チーム謹製のベンチマークツールと各言語の参考アプリは公開されてるので、是非触ってみてくだしあ。ゲーム感覚で楽しいし、各参考実装や意図的に仕込まれてる罠は、新人教育なんかにもうってつけだと思う。ええはい。自分の実力不足をガチで痛感した次第です。いや本当勉強しよう。ちゃんと。

「スマートフォンについて何か喋ってよ」というかなり大雑把なネタ振りをされたので、第五回ライブドア・テクニカルセミナーでこんな感じの話をしてきました。

あとで動画や資料は公開されるはずですが、大変申し訳ないことにテンパりまくって見るに耐えない感じになっちゃってると思うので、一応何を言いたかったのかを補足しておきます。

iOSとAndroidは違うものだという話

開発環境の違いについてはまぁいいとして、例えばアプリの設計思想。iOSでは「まずアプリケーションという大きなプログラムがあって、その中で画面を表示したり通信したりしてる」って感じの構成になってるのだけど、Androidの場合「画面とかデータ管理機能とかそういう独立した部品があって、それらをまとめたものに名前を付けてアプリケーションという風に見せる」という感じになっていて、後者の方が若干まどろっこしい感じはある。一方でアプリ間でのやりとりについて考えてみるとiOSでは「openURLで他のアプリを起動する」とか「Keychainだとかアルバムだとかを使って(限定的に)データを共有する」とかその程度に留まっているのだけど、Androidの場合はOSが提供する機能も同じアプリ内の機能も他のアプリの機能も全て「部品」という形になっているので、きちんと設計しさえすればデータや機能をシームレスに連携させられて大分可能性が広がる。この考えかたの違いはアプリの作り方の違いにモロに出てくる。

それから、デバイスの違い。iOSの場合はiPhone/iPod touch系とiPad系の二系統しかなく、どちらもApple一社が開発しているので、OSや周辺サービスも含めたプラットフォーム全体のコンセプトから良くも悪くも外れない。一方のAndroidはと言うと、Googleが主導してはいるものの、端末メーカー各社それぞれがOSにカスタマイズを加えてたり、独自のハードウェア(キーボードが付いてたり、先行してFeliCa対応してたり、裸眼立体視ディスプレイが付いてたり、そう言えばゲームのコントローラが付いてたりするようなのも出るんだっけ?)を搭載してたり、小さな画面のものがあったり、横固定だったり、多種多様だ。ハードウェアの違いがソフトウェアに影響を与えないわけが無く、「使い易いアプリって何?」って問いの答えはiOSとAndroidでは当然違うし、Android端末間でも異なってくることもある。

ここまでは技術者視点だけど、当然ビジネス的にも文化的にも異なる展開をするだろうね。と、いうことを考えたら、アプリの企画や開発をするのに「iOSとAndroidは違うもの」って認識は当然持っておく必要はあるだろう。

iOSアプリとAndroidアプリはどっちが作り易いか

率直に言うと、最初のとっつき易さに関して言えばAndroidの方が圧倒的に上。僕の元々のお仕事はPerlやRubyでWebアプリケーションを開発することで、今もアプリ作りのかたわらWebやってるのだけど、そういう人が、ObjCとXcode/IBでアプリを作りクローズドなプラットフォームでお仕事するのと、JavaやJVM上で動く言語とEclipse+ADTでアプリを作りオープンなプラットフォームでお仕事するのとで、どっちが大変かなんてのは言うまでもない。

で、だからAndroidのが素晴しいって話になるのかというとそれはそれで違うんだよってのが、無茶苦茶RTされまくってハッシュタグを埋めてしまった件のスライド(本当にすいませんでした!)の意図。「最初は薔薇色の世界に見えるAndroidも作り続けて行くと段々難しさに気付いていく」「最初はとても理解できないと思ったiOSも作り続けて行くと良さに気付いていく」「だからつまり、最初のとっつき易さだけでどっちかが圧倒的に優れていると言えるわけではない、最初のハードルを越えたら別の問題が見えてくる」っていうのを(一部の人には)視覚的に分かってもらえるように、と思ってあのイラストを入れたんだけど、前日にまどか☆マギカの8話を観て「正直やりすぎた」と反省はしました。あのスライドを見て「きっとこの人はAndroidよりiPhone派で、あと赤い子が好きなんだろう」ってコメントしてる人がいて、いやうん割とその通り(「杏子に『食うかい?』って言ってもらう」ことを願って白い獣と契約しかねないです)だけど、いくらなんでもアレに例えなきゃいけないほどAndroidを悪くは思ってない。Androidが孵す卵はもう少し夢のあるものだって信じてますし、iOSのあの林檎を輝かせ続けるために何が犠牲になってるかに思い至らないわけでもないですし。

魔法少女の話は置いといて、じゃあその「別の問題」ってなんなの、という話でいうと、まず一つはマルチデバイス/OS対応。Androidは自由度の高さが最大の売りだけど最大の欠点でもあって、Androidに取り組んでるところは軒並「端末やキャリアによってお約束事が違い戸惑う」とか「バージョンアップしない端末があってどのバージョンのOSを対象にするか迷う」とかそういうことに悩んでいる。正直、Android開発に関わってない人が想像してる以上にデバイスごとの差異が大きいし、今後収束するどころかもっと個性的なものが増える方向に進むだろう、だってそれが一番の売りなんだから。それに真面目に対応していくことの面倒臭さは、iOSアプリの最初の学習コストを上回ると思う。iOSだってもちろんiPhoneとiPadで両対応ってのは凄く大変だし、最近はケータイだと思って買っていくので家にパソコンも無くアップデートしないみたいな人も増えてると聞くので、その問題からは無縁じゃないんだけど、それでも現時点ではAndroidよりは幾分マシだ。

それから、パフォーマンスチューニング。いくらスマートだスマートだって言ったって所詮はモバイルデバイスなので、タイトなリソースをやりくりしていかなきゃいけない。今まで組み込み機器やローレイヤーなところの開発をしてた人からすれば「何を贅沢な」「ゆとりめ」と冷笑されそうな話だけどさ。スマートフォンアプリに求められる「リッチさ」はどんどん上がって行く一方だけど手持ちの戦力は高性能な戦闘機と言ったところで、その気になれば空母の艦隊を投入できるサーバサイドの開発とは全然違う。これに関して言えばiOSでもAndroidでも同じで、そういう段階に至ったらどっちにしてもそれなりに「難しい」。

クロスプラットフォーム開発に関して思うこと

きっと伊藤直也さんが肯定的な文脈で言及するに違いないと思って(実際してた)内心ビクビクしながら話してたんだけど、どうしても言わざるを得なかったので言ってしまった。一つのコードで両方のプラットフォームに対応するのは、現時点ではあまり現実的じゃないと思う

もちろんアリだろうと思ってる領域もあって、例えばゲームエンジンみたいなものは、凄く効果が高いと思う。最近は一般的な携帯ゲーム機向けのものと同じくらいのクオリティの高いゲームがどんどん増えてるけど、ああいうのは大概UIから自前で作ってしまうし、中のロジックもiPhoneだからAndroidだからということも無いだろうから、細かい差異はライブラリに吸収させちゃうのが定石だろう。それから、コアな部分をHTML5+JSで作って、ブラウザ上でできないことやパフォーマンスが必要なことをやらせるためにネイティブのUIでWebViewをラップする、というハイブリッド戦略も良いと思う。SNSとかブラウザゲームとかだと期間限定のイベントがあったり速いペースで機能改善していったりする必要があって、それで言うとアプリ開発して(iOSならAppleの審査を通して)ユーザにアップデートしてもらう、というプロセスを通すと遅すぎて全然上手くいかない。うちで作ってるアプリだとロケタッチとかは部分的にそんなアプローチをしてるし、今後そういうアプリも増えると思う。

懐疑的なのはトランスレータとかミドルウェアとかの話。Titaniumとかが盛り上がってるのはもちろん知ってるし追ってるけど、最近だと「一つのコードで両方のプラットフォームに対応」みたいな話より「iPhone(or Android)のアプリ開発を楽にする」って文脈で語られることのが多くなってきてるかなーという印象を受ける。まだまだ発展途上でどっちかのプラットフォームのサポートは手厚いけどもう片方は追いついてないだけで、今後は次第に改善していくだろう、ってところもあったりするんだけど、それとは別に個人的には「これだけ"違い"があるんだから、無理して同じ扱いをするよりそれぞれに合ったやりかたをした方がいいんじゃないか」って思ってる。特にツール系のアプリはそうで、それぞれにUIのお作法があるのでちゃんとそれぞれの特性を活かしたUIにした方がよくて、そうなったときに内部のロジックだって無理に同じにしておくより別プロジェクトにしておいた方がかえってメンテしやすかったりする。将来的にどうなるかは分かんないけども、今現在両方のアプリを作ってての実感としてはそんなところ。

というところまで書いて

そんな話が全然出来てなかった自分の話下手っぷりに絶望したのでもう少し練習しないとな、と思いました。あたしって、ほんとバカ。

謝辞

キュゥべえドロイドくんはSENさんに、杏子の林檎はニチロさんに描いてもらいました。素敵なイラストありがとうございました!

ニンテンドー3DSの体験会があるから行かないかと友達に誘われたので、今日は朝から幕張メッセまで行ってきた。恐しく混んでてろくに遊べないんじゃないかと不安だったんだけど、タイミングが良かったのか案外しっかり触ってこれた。体験したのはSTEEL DIVERとARゲームズとnintendogs+cats。ゼルダやMGSはまぁ、流石に長蛇の列で映像を見るだけに留めた。パルテナの鏡かパイロットウイングスかどっちかは試しておけばよかったなぁとちょっと後悔。んでまぁ、総合的な感想としては、予想してたより面白いデバイスで好感触。

裸眼立体視ディスプレイ

3DSというくらいなので目玉機能なわけだけど、思ったことは二つ。「かなりちゃんと3Dしてるし、綺麗」「んでもまぁ、面白い映像だなーっていうくらい」。

実はこないだ誘惑に負けてLYNX 3Dを買ってしまったんだけど、個人的にはこれよりも立体感あるなぁと思ったし、ディスプレイそのものも現行のDSより大きく綺麗になってるので映像は良かった。

一方で、3Dテレビや3D Androidのときも思ったんだけど、「おおすげー、3Dだ!…確かに凄いけど、凄いんだけど、それで?って感じだなぁ…」という印象は拭えなかった。面白い演出は出来るようになるんだろうけど、ゲームそのものには(今までとまるで違うというほどには)影響しなさそうというか。見え方にも個人差があるし(ガチャ目や乱視持ちにはちょいキツい)、結構目や脳にかかる負荷もあるし、映像展示の方で色々見てたらソフトによって大分差が出てたし、3Dオフにしてもちゃんとゲームできないといけないし、というわけで、まぁぶっちゃけオマケ感は否めない。本当に立体映像でゲームががらっと変わるとしたら、やっぱり本当に立体投影できるようになるまで待たないとなんだろうなぁと思った。

モーションセンサー、ジャイロセンサー

個人的におっと思ったのはこっちの方。STEEL DIVERの潜望鏡モードやARゲームズでは「3DS自体を動かす」というのがゲームの操作の一部になってて、これがかなり良かった。これとあとスライドパッド(アナログスティック)が追加されて、タッチパネルやフロント/リアカメラと合わせれば携帯ゲーム機としては「入力手段」がかなり多いデバイスになるので、それこそゲーム性にかなり影響してくると思う。

特にARゲームズ!twitterとかでも行ってる人が口を揃えて「ARゲームズ面白い!」って言ってたのが分かると思うけど、実際これが大分良かった。ゲーム自体は他愛のないもので、机の上に置いたマーカを3DSのカメラで認識すると、そこに的やらドラゴンやらが出てきてそれを撃ったりして遊ぶ(体験版なのでそれだけだったけど、実際にはもっと色んなミニゲームが入るとのこと)というものなのだけど、マーカを置いてる机ごとぼよんぼよん波打つのを上から横から狙いを付けるとか、ドラゴンの背後に回り込んで背中を攻撃するとか、「ゲームの演出として」ARとモーションセンサーをしっかり組み込んでて、拡張現実で遊ぶってのはこういうことだよなーと思わせるものだった。

ていうか、一応スマートフォンアプリの開発者のはしくれとしては、「何でこれをiPhoneやAndroidで先に"ちゃんと"やらなかったんだろう…」と大分反省した。モーションセンサーとカメラが付いててOpenCVやARToolKitを使えてある程度の処理性能があるデバイス、もうあるのに、ね。

ステージイベント

ゲームミュージックライブがとても良かった。ジャズアレンジのマリオやゼルダがすごく素敵で、もし明日明後日行く人がいたら、12:30からライブなのでそれに間に合うように行くことをおすすめする。並んで待ってる間に演奏を聴けたので、退屈せずに待ってられた。ちなみにステージイベントの様子はインターネット中継されてるようなので、ストIVやバイオのソフト紹介とかを見たい人は時間を確認して見てみるといいですよ。

全然関係ない話

隣でペット博をやってたらしく、会場付近にはそこかしこにnintendogsじゃない本物のdogsが走り回っていて、一旦外出て休憩してる間に凄く和んだ。うん、本当に全然関係ない話。

↑このページのトップヘ