考える×つくる×動かす

主に技術系のことを書いていきます。

社内のISUCONに参加しました。

昨日のことですが社内のISUCONに参加しました。

ISUCON2に関しては以下のブログに書いてあります
http://blog.livedoor.jp/techblog/archives/67728751.html
http://github.com/tagomoris/isucon2

今回はPHPを選択しました。
だって一番慣れてるしー
ホントだったらRubyでやりたいんだけどねー

実を言うと自分は業務がちょっと忙しくて
ソースを完全に見たのが当日の昼という状況でした。

まず基本的な設計指針

PHP不要モジュールの削除
→ほぼ必須モジュールのみだったので手を加えず

Apache不要モジュールの削除
→Auth系とかProxy系はOFF

・不要なサーバーのサービスのKill
→chkconfigで確認してPostfixとかOFF

CSS,JS,画像ファイルの最小化
→JSはGoogleから引っ張ってきて画像は圧縮してBASE64エンコで内部に埋め込み

・Memcacheの導入
→別途記載(1)

・DBテーブルカラムの最適化
→別途記載(2)

Apache等各種ログファイル出力のOFF
httpd.confですべてOFF

・Keep Aliveの設定
→一回のリクエスト数が少ないので重くなりOFFにした


まず初期状態でのベンチスコアは110チケット前後
WebよりもDBのLoadAverageがいっぱいな感じ。

Memcacheの導入(1)

スロークエリーが出ていた場所は

SELECT stock.seat_id, variation.name AS v_name, ticket.name AS t_name, artist.name AS a_name
FROM stock
JOIN variation ON stock.variation_id = variation.id
JOIN ticket ON variation.ticket_id = ticket.id
JOIN artist ON ticket.artist_id = artist.id
WHERE order_id IS NOT NULL
ORDER BY order_id DESC LIMIT 10

最近購入されたチケットの10件を取得して表示する場所。

これは様々なページで呼ばれていたのでMemcacheに入れた。
チケットが売れた時に10件を超えていた場合はarray_popし古いのを1件削除して
array_unshiftで直近で売れたチケット情報を加えることにした。

Memcacheの導入(2)

他の部分でスロークエリーが出ていた場所は

SELECT COUNT(*) FROM variation
INNER JOIN stock ON stock.variation_id = variation.id
WHERE variation.ticket_id = :ticket_id AND stock.order_id IS NULL

残りのチケットの枚数を取得する場所。

はじめの枚数をMemcacheに入れて、
チケットが売れた時にそのvariationの枚数を-1する処理に変更。

ここで400前後のスコアまで改善した。

APCの導入+サーバーのチューンナップ

APCを入れたらスコアが一気に700前後まで改善した。
KeepAliveはスコアが落ちたのでOFFにした。

DBテーブルカラムの最適化

アーティストとチケットと開催場所をDBではなくソースに直接配列として記述した。
これらの値は基本固定だったので配列として処理した。

以下やらなかったこと

・nginxの導入
→時間が足りなかった
Mysqlのエンジン変更
Mysqlサーバはボトルネックになってなかったから
フレームワークの変更
→時間が足りなかった
Mysqlのテーブルカラムの構造変更
(stockのseat_idをvarchar255から2個にカラム分割してintに変換しindexを貼り直す)
Mysqlサーバはボトルネックになってなかったから

以下反省点

・事前に時間を作ってちゃんとソースとサーバーを見ておくべきだった
・テストのソースをしっかりと確認してどこをテストしてるのかを確認すべきだった
・Webサーバっていう大まかな負荷を見るだけじゃなくて、
どの画面がボトルネックになっているのかっていうのをしっかりと把握すべきだった
・実際の業務じゃないので新しい技術に積極的にチャレンジしてもよかった

最後に

チームメンバーのお陰でなんと優勝することができました。
最小の処理でなかなかのパフォーマンスを引き出すことができ良かったかなと思います。
運営の皆様お疲れ様でした。次回ありましたら是非また参加したいです。

賞金5000円分の商品券もらったんで飲みにでも行きましょう(笑)

CakePHPでBlogを作る

某社の課題でBlogを作る。

基本設計編

まず基本となる言語環境選択

PHP(一番得意)
Perl(はてななら)
RubyRails使うなら)

今回は単純に一番得意なPHPにて開発することにした。

次にデータベースの選択

Mysql(ローカルにインストール済)
PostgreSQL(ローカルに未インストール)
Sqlite(環境が変わってもファイルなんで手軽)

ローカル環境なので何も考えずにMysqlで作ることにした。

次にフレームワークの選択

・ZendFramework(ちょっと規模が小さい)
CakePHP(お手軽)
Ethna(昔使ってた)

ZendFrameworkだと1時間かからずに作成できるけど、
自分フレームワークも含んでいるためかなり規模が大きくなって、
Zend内部と自分フレームワークと2重の説明をしないといけなくて煩雑になりそう。
お気軽っていう意味でCakePHPを選択した。
ただCakePHPは1.3系までしか使ったことがないのと、
最後に使ったのが数年前なのでちょっとブランクあり。


ローカル環境編

ホストの設定

hostsファイルにローカルアクセスできるように以下を記述する。

sudo vi /etc/hosts 
127.0.0.1       blog.localhost
Apacheの設定

バーチャルホストの設定する。

sudo vi /opt/local/apache2/conf/extra/httpd-vhosts.conf
<VirtualHost *:80>
    DocumentRoot "/Users/kazuya/home/blog/app/webroot/"
    ServerName blog.localhost
</VirtualHost>
サーバー再起動
sudo apachectl restart
データベース作成

本当ならユーザーも作成すべきだけどローカル環境なのでrootで全てやっちゃう。

mysql -u root -p   
# (パスワード入力)
mysql > CREATE DATABASE  `blog` DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci;
CakePHPのインストール

http://cakephp.jp/ より最新版をゲットして/Users/kazuya/home/blogに設置する。

CakePHPの設定

セキュリティーの値を変更

app/Config/core.php以下の値を変更する。

'Security.cipherSeed'
'Security.salt'
データベースアクセスの設定

app/Config/databese.phpを作成する。
ファイル内容はこんな感じ。(パスワードとユーザー名は適宜変えてね)
またMacportsでインストールしたMysqlだとソケットを指定しないと動かないので注意する。

	public $default = array(
		'datasource' => 'Database/Mysql',
		'persistent' => false,
		'host' => 'localhost',
		'login' => 'root',
		'password' => 'XXXXXXXXX',
		'database' => 'blog',
		'prefix' => '',
		'encoding' => 'utf8',
	    'unix_socket' => '/opt/local/var/run/mysql5/mysqld.sock'
	);
書き込みの権限を付与

キャッシュとかセッションを保存する一時フォルダーに書き込み権限をつける。

chmod -Rf 0777 app/tmp


http://blog.localhost/ にアクセスして問題なく動作していることを確認。
f:id:kazupyong:20121104231442j:plain


アプリケーション設計

データベース設計

データベースはERMasterで作った。
Blogで最低限記事とタグ付けが出来ればいいとの課題なので、
記事とタグと関連付けテーブル。あと認証を使いたいのでユーザーテーブルを作成する。

ER図はこんな感じ。
f:id:kazupyong:20121104232233p:plain

ERMasterでSQLをエクスポートしてコマンドラインからテーブルを作成する。

> mysql -u root -p blog < blog.sql 
Bakeでモデル、コントローラー、ビューを作成する

CakePHPにはBakeっていうインタラクティブなコマンドラインツールがあるんだけど、
2.2で久しぶりに使ってみたらかなり使えるツールになってた。

php -f ./app/Console/cake.php bake
---------------------------------------------------------------
Interactive Bake Shell
---------------------------------------------------------------
[D]atabase Configuration
[M]odel
[V]iew
[C]ontroller
[P]roject
[F]ixture
[T]est case
[Q]uit
What would you like to Bake? (D/M/V/C/P/F/T/Q) 

と表示されるのでMを選択してModelを作成する。

Use Database Config: (default/test) 
[default] > 
Possible Models based on your current database:
1. ArticleTag
2. Article
3. Tag
4. User

と4テーブル分のモデルをチュートリアルに従って作成する。
バリデーターとかも事細かにそれぞれのフィールドごとに指定できる。

ここで大切なのは
ArticleとTagはArticleTagにhasAndBelongsToManyであり、
ArticleTagはArticleとTagにbelongsToしていると関連付けしておく。

ControllerとViewも同様にして作成する。


アプリケーション実装

ルーティング処理

articlesのindexがblog.localhostにアクセスした際にデフォルトの表示になるように
app/Config/routes.phpに以下を記述する。

	Router::connect('/', array('controller' => 'articles', 'action' => 'index'));

またコントローラーのadmin_の関数を有効化する為に
app/Config/core.phpに以下を記述

	Configure::write('Routing.prefixes', array('admin'));
ページング処理

ArticlesControllerにページング処理の設定。
記事の公開日時順に並び替えたいのとデフォルトで5件表示に変更。

クラス変数に以下を追記

public $paginate = array(
        'Article' => array(
        'maxLimit' => 5,
        'order' => array('published' => 'desc'),
));

あとはタグIDが指定されている場合にそのタグの記事だけが表示されるように、
ArticlesControllerのページング処理に絞込みの条件を指定。

function index ()内に以下を追記した。

	if(isset($this->params['named']['tag'])){
	    $cond = "ArticleTag.tag_id = {$this->params['named']['tag']} and ";
	    $this->paginate['Article']['joins'] = array(array('type' => 'LEFT', 'alias' => 'ArticleTag', 'table' => 'article_tags','conditions' => 'Article.id = ArticleTag.article_id'));
	}
ログイン処理

LoginControllerとAppControllerにadminのログイン処理を記述。
今回はAuthコンポーネントを使って処理する。

AppControllerにコンポーネントの有効化を記述することにより、
基本すべてのコントローラーがAppControllerを継承しているので全てに反映される。

クラス変数に以下を追記

    public $components = array(
            'Session',
            'Auth' => array(
                    'loginRedirect' => Array('controller'  => 'admin', 'action' => 'articles'),
                    'logoutRedirect' => Array('controller' => 'login', 'action' => 'index'),
                    'loginAction' => Array('controller' => 'login', 'action' => 'index')),
            );

ただ、Blogのトップと記事詳細はログインしてなくてもアクセスできるように、
AppControllerのbeforeFilterに以下を記述。

    function beforeFilter() {
        $this->Auth->authError = __('Login Error');
        $this->Auth->allow('article','index');
        $this->Auth->allow('article','view');
    }
日本語化対応

デフォルトでエラーメッセージやデータベースのカラムとかは全て英語表示。
一応日本語化しておく。
コマンドで全ファイル内の文字出力関数__()内の翻訳ファイルを作る。

php -f ./app/Console/cake.php i18n 

これでapp/Locale/以下にdefault.potファイルが出来る。

mkdir -R app/Locale/jpn/LC_MESSAGES/
cp app/Locale/default.pot app/Locale/jpn/LC_MESSAGES/default.po

日本語の翻訳メッセージを入力しておく。
デフォルトで英語環境で使っているので日本語のFirefoxをインストールして確認した。



こんな感じで最低限ひと通りの機能は完成。


ソースコードは以下で。
https://github.com/kazupyong/Blog

MyWi4.0+B-Mobileの組み合わせ最強説

以前の記事の続き。

MyWiがアップデートしてiOS4に対応しました。
3GをiOS4にすると電池のもちが悪くなったり、もっさりしたので、3.1.3に戻しました。

以下新しいフィーチャーの日本語訳。
http://www.rockyourphone.com/index.php/mywi-4.0.html
・より早く起動
・より消費バッテリーを少なく
・公式のテザリングバーとステータスバーに表示を選べる
・ステータスバー表示にすれば10-15%のバッテリーの持ちが良くなる
Wifiの電波強度を30-100%の間で調整できる
・USBブリッジモードを搭載


個人的にはWifiの電波強度を調節できるのがいい。
基本的にすぐ近くに置いて使うので強度は殆どいらなく、
バッテリーの持ちと発熱を抑えられるのがいい。
あと起動がかなり早くなって使わないときはすぐOFFにできる。


5日くらい使いましたが、
たまに繋がらなくなったり不安定なことはあるものの、
基本的にiPhone4の3Gデータ通信の電波はOffのままで十分運用可能。
ドコモの広範囲+安定したネットワークで3Gにつながるのはすごい便利だ。
またMacbookProでUSB給電しながらのテザリングだと充電しがらテザリングできる。

E-MobileのPocketWifiなんかいらない

導入

昨日iPhone4を買いました。
今まではiPhone3Gを発売日に表参道で並んで買って丸々2年近く使ってきました。

昨年3GSが販売されて、
周りがiPhone3GSに機種変更していく中、我慢して3Gを使い続けていました。

最近外で仕事をする機会が多くなりiPhone4の購入と同時に、
E-MobileのPocketWifiを契約しよっかなーって思っていました。

iOS4には標準でAPNDisabler(3Gのデータ通信をOFFにする機能)が入っているのもあって、
iPhoneの最低パケットを維持すればトータルの差額1000円くらいで
PocketWifiを導入できるんじゃないかと考えていました。


でも個人的に回線契約を複数するのは嫌だし、
E-Mobileの2年縛りはあんまりいい話を聞かない。

どこでもネットはしたい、でもE-Mobileとは契約したくない。
その二つを解決してくれるのがBMobileとMyWiでした。

BMobile導入

BMobileは300kbpsとあんまり高速じゃないけど、
DocomoのFomaエリアなのでかなりの広範囲で使えるし、
もちろんテザリングOKだし、SIMフリー端末なら差し替えできるし、
半年で14900円(月あたり2500円)とかなり安いし変な縛りもない。

しかもビックカメラで買ったので10%のポイントが付いたので、
実質月に2235円と超お得だった。
開通手続きも電話するだけで簡単に開通。


MyWiはiPhoneをPocketWifiみたいにできるアプリケーションで10ドル。
SIMロックのiPhone
BMobileで使用するにはiPhone3Gを脱獄してSIMロックを解除しないといけない。
そこら辺の情報はGoogleさんに聞いてください。

SIMアンロック後に以下のようにAPNを設定してiPhone3単体でもネットできるようになった。

f:id:kazupyong:20100625130110p:image

MyWi導入

んでMyWiでiPhone3GをPocketWifi化した感想。
300kbpsだけどかなり快適で、速度チェックしてみても安定して250-280kbpsの速度は出てる。
山手線でSoftBankだと絶対圏外になっていた新大久保->新宿、原宿->渋谷間も途切れない。

昨日の夜に試したところ、
2時間程度のテザリング状態で3Gの電池は半分以上残っていたので4時間くらいなら持ちそう。
(最近iPhone3Gを自分で分解して電池を交換したおかげもあると思うが。)

同時に3台Wifiで繋いでみたけど、
速度がちょっとイラつくぐらいで大丈夫そうだった。

3Gがスリープ状態でもテザリング状態になってしまうので、
テザリングが不要な場合はAirPlaneモードにするのがいいみたい。


f:id:kazupyong:20100625130111p:image

必要になったらAirPlaneモードを解除してMywiのテザリングをOnにすればすぐに繋がる。
(1分かからないくらいでiPhone4で通信できた)

f:id:kazupyong:20100625130112p:image

もちろんiPhone3G単体でもネットできるし
MacbookProの場合はUSB・Bluetoothでテザリング可能

感想

満足点
・iPhone3G,iPhone4,iPad,MacbookProでいつでもどこでもネットできる
・月々の支払金額が安くなりそう(下の表を参照)
Softbankが圏外でWifiがない場所でもネットに繋がる

不満点
・ちょっと初期設定がめんどくさい
・やっぱり速度がちょっと遅い
・MMSが使えない(元々使ってないので影響はない)

料金比較表

項目 iPhone4 iPhone4
+PocketWifi
iPhone4
+BMobile
初期金額 0円 1円 10ドル(MyWi)
+13410円(BMobile)
=
14400円
月額(ソフトバンク) 980+315
+4200(パケ放)
+1920(端末代金)
-1920(月々割)=
5495円
980+315
+1029(パケ放)
+1920(端末代金)
-1440(月々割)=
2804円
980+315
+1029(パケ放)
+1920(端末代金)
-1440(月々割)=
2804円
月額(その他) 0円 4980円(E-Mobile) 0円
6ヶ月合計 32970円 46704円 31224円
1ヶ月あたり 5495円 7784円 5204円
PC・iPadとのテザリング ×
めんどくささ - あんまりない 多少
持ち歩く物 iPhone4
KBC-L2S
iPhone充電ケーブル
iPhone4
KBC-L2S
PocketWifi
iPhone充電ケーブル
PocketWifi
ケーブル
iPhone4
iPhone3G
KBC-L2S
iPhone充電ケーブル

※iPhone4は16Gを想定。
※パケ放題フラットは4200円固定だが、月々割が1920円になる。普通のパケ放題の場合は1029円〜4200円で月々割が1440円となる。

なんと、普通にソフトバンクに契約するよりも安くなってしまった。
自分的には変な縛りのある回線契約しなくていいのが嬉しい。


まぁJailbreakしてiPhoneが壊れても一切責任を負わないので自己責任でおねがいしますね。

iPad iPhoneでGoogle Syncを使う

何回やっても忘れてしまうので自分的なmemo。
※自分は言語を英語にしているので、日本語の人は注意。

iPhoneiPad側の設定方法

一度設定すればMailとかカレンダーをPushで受け取ることができる。
ただOS3.1ではひとつのExchnageサーバーしかSyncできない。
OS4では複数のExchangeサーバーとSync可能。


1.Settings->Mail,Contacts,CalendarsからAdd Accountを選択する。
2.Microsoft Exchangeを選択する。
3.UserNameに[xxx]@gmail.comを入力する。
4.PasswordにGoogleのパスワードを入力する。
5.Nextボタンを押すとServerっていう項目が表示されるのでm.google.comを設定する。
6.さらにNextボタンを押すとMail, Calendar, Contactsが表示されて自分がSyncしたい項目だけOnにして完了。

詳しくはここ。
http://www.google.com/support/mobile/bin/answer.py?hl=en&answer=138740

複数のカレンダーをSyncしたい場合の設定方法

デフォルトでは自分のカレンダーしかPushされないので、複数のカレンダーをPushしたい場合は以下の設定が必要。


1.Mac or Windows上にFirefoxのUserAgentSwitcherをインストールしてUserAgentを変更できる状態にする。
2.UserAgentに以下を追加する

Mozilla/5.0(iPad; U; CPU iPhone OS 3_2 like Mac OS X; en-us) AppleWebKit/531.21.10 (KHTML, like Gecko) Version/4.0.4 Mobile/7B314 Safari/531.21.10

3.UserAgentを変更した状態で以下のURLにアクセス

https://m.google.com/sync/settings/iconfig/welcome?source=mobileproducts&hl=en

4.設定したい機器を選んでSaveで終了