Home > Web+DB

Web+DB Archive

Webシステム開発に便利な7つのツール

この記事の所要時間: 98

Webシステム開発で使っている便利なツールをあげてみました。

あらためて社内の開発環境を見直す機会があったので、使っているツールを並べてみました。こうして見ると色々なツールを使って開発をしていますね。わりと定番系なものが多いですが、良かったら参考にどうぞ。

1. Apache / PostgreSQL / PHP

Mac OS X に MacPorts でインストールしたApache / PostgreSQL / PHP 環境を使って開発をしています。

PHPは5.3、PostgreSQLは8.4 or 9.0です。

ただ旧バージョンのPHPを使ったり、Linux でなければ動かないモジュールを使うこともあるので、その際は社内のCentOSサーバにSSHで入って開発したりもします。

Vimを使ってるので、SSHで入ればどのサーバでも開発できるのは利点ですね。

Mac を使い出して、しばらくはこれで良かったのですが、たまにLinux環境が欲しい時があったり、社内スタッフと開発環境を揃えることを考えると、vmwareでLAPP環境を構築しておいて、それをみんなで共有するほうが良いかもしれません。

2. Vim

開発エディタにはVimを使っています。

昔はWindowsのPeggyを使っていたのですが、サーバにSSHで入ってメンテナンスする時にVimでもたもたするのがいやだったのと、何よりVimで颯爽とコードを書くのがかっこ良さそうだったのでメインで使うようになりました。

シェルの延長上で使えて、起動にストレスが無いですね。基本コマンドは手に馴染んでいるので、純粋に「書く」という行為については文句無いです。IDE並とは言わないですが、拡張していけば補完なんかもそれなりに効きます。

一度覚えておけば、Unix系を使う限りは下手すれば一生ものになるので楽できますね。

ただ絶対にVimじゃなきゃイヤだということも無くて、最近はNetBeansも気になったりもしてます。(Vimプラグインとかあるみたいだし)

http://www.vim.org/

3. xdebug

xdebug

PHPで開発している人にはおなじみのxdebugを入れてます。

インストールするだけで、PHPのエラーメッセージを見やすくしたり、スタックトレースを表示してくれたり、var_dump()した時が見やすくなったりで開発する時には嬉しい機能満載です。

例えば、PHPエラーが見やすくなって、スタックトレースが表示されたり。

xdebug_php_error

例えば、var_dump()で変数をダンプすると見やすくなってたり。

xdebug_var_dump_array

オブジェクトなら、インスタンス変数のアクセス権も表示してくれます。

xdebug_var_dump_object

他にも実行コードがトレースできたり、コードカバレッジを計測できたりと、とても重宝しています。

xdebug は pecl コマンドでインストールできます。

$ sudo pecl install xdebug

もちろん yum でもインストールすることもできます。

$ sudo yum install php-pecl-xdebug

PHPで開発する人ならぜひ開発環境に入れてみてください。

xdebug

4. Firefox

Firefox ブラウザ無料ダウンロード

Webを見るときのブラウザはChromeとFirefoxを併用していますが、やはりWebシステム開発に使うならFirefoxがメインです。

理由はアドオンが充実していることです。もちろんChromeでも同様のことができると思うのですが、使い慣れているということとFirefoxにしか無いアドオンがあるのが大きいですね。

豊富なアドオンの中でも特によく使うものをあげてみました。

4-1. Firebug

言うまでもなく定番のツールですね。

表示されているHTMLタグを確認したり、CSSを修正したり、JavaScriptのデバッグに使ったりと機能満載なのですが、Webシステム開発という意味では「ネット」が便利です。

ここを開くとブラウザからWebサーバへどのようなHTTP通信を行っているかが分かります。あるページを読み込むと画像やCSSが次々と読み込まれるのが分かります。

firebug_1

さらに各行をクリックするとヘッダを含む、リクエスト、レスポンスの内容が確認できます。

firebug_2

POSTの内容も見やすいです。

firebug_3

Firebug :: Add-ons for Firefox

4-2. Selenium IDE

自動ブラウザテストツールです。

Selenium IDEを使うとブラウザでの操作、入力を記録することができ、何度でも同じ操作を繰り返すことができます。さらにページやURL、Cookieなどの内容が想定されている値になっているかを検証することができます。

特に複雑な入力を伴うような購入フォームやユーザ登録などは、操作を記録してテストケースを作成しておくと、開発途中で動作確認するときに同じ操作を人がするのではなくツールにさせることができて、効率的です。

操作さえ慣れればかなり適用範囲の広いツールなので、Webシステムを開発する人でまだ使っていない人は一度試してみてください。

Selenium IDE

4-3. FireMobileSimulator

携帯電話対応のWebシステム開発では欠かせないツールです。

このツールを使うと擬似的に各キャリア、各機種の携帯電話からのアクセスを行うことができます。膨大な機種データが登録されており、新機種への対応も随時行われています。

画面サイズや絵文字表示などもシミュレートしてくれるので表示についても確認できます。(もちろん最終的には実機テストが必要ですが)

FireMobileSimulator.org

5. SimpleTest / PHPUnit

PHPでユニットテストを行うツールです。

SimpleTestはCakePHPで開発するときに使っています。それ以外で開発するときはPHPUnitを使うようにしています。

ユニットテストの効能については様々な情報があり、あえてここで触れる必要はないと思いますが、個人的に感じているメリットが2つあります。

5-1. 安心感を得る

テストを書いて、いつでもテストを実行できる環境を作っておくとそのコードが想定どおりに動く安心感が得られます。リファクタリングなどでコードを書き換える時もそうですが、開発が終了して何ヶ月か経った後でもそのコードが確実に動くことが保証できます。(もちろんテストを書いた範囲においては、ですが)

あと他人(数ヶ月後の自分を含む)が書いたコードがどのような動作を想定したものかを掴むことができます。

こうした書いたコードに対する安心感を得られるのが大きいです。

5-2. テストしやすいコード=独立性の高いコードを書くようになる

ユニットテストを書きだすと分かるのですが、メソッド内で様々なクラスと密結合になっていて様々な処理を詰め込んでいるとテストが書きづらくなります。

テストを書く段でそういった密結合を解きほぐしていって、メソッド毎の処理を小さな単に分けていくと、テストが書きやすくなり、自然と独立性の高い実装となっていきます。こうした独立性の高い実装はその役割がはっきりしているので再利用が容易となります。

独立性の高いコード、疎結合なコードを書くための訓練といういう意味でもテストコードを書くというのは効果があると思います。

とはいえ、やはりテストを書くのが面倒な場面はあるので、今はいかに効率的に(手間をかけずに)テストが書けるかを模索しています。

SimpleTest – Unit Testing for PHP
PHPUnit Manual

6. Redmine

社内の開発タスク管理にはRedmineを使ってます。

3,4年くらい前にBTSを導入しようとして、TracやMantisなども検討したのですが、複数プロジェクトが管理できるのとGUIが取っ付きやすそうだったので、Redmineを導入しました。

まだ今の使い方が正解かどうかは分からないですが、ざっくりとしたタスク単位でチケットを発行しています。チケットには仕様の詳細までは書いていないことの方が多くて、実際にアサインする時に打ち合わせで説明しています。

このあたりをどう活用しているのかは他の人と情報共有したいところですね。

Redmineの機能としては、やっぱりGitやSubversionなどのSCMと連携できて、コミットとチケットを関連付けられるのが便利ですね。チケットに対する変更差分をブラウザでさっと確認できます。

SCMを初めて使う人にとっても、commit / push した内容がブラウザで確認できるので分かりやすいみたいです。

http://redmine.jp/

7. Git / Subversion

ソースコードは、Git / Subversion で管理しています。

SCMはCVSから使いはじめました。その頃は一人で開発をやっていましたが、変更履歴や差分が取れるし、開発環境とテストサーバなど複数の箇所で同じソースが簡単に取得できるので、それ以来利用しています。(当時使っていたPeggy ProにCVS連携が付いていたのも一つのきっかけになりました。)

それからSubversionを使い出して、CVSのソースはSubversionに移行しました。スタッフが入って、複数人での開発になってからはさらにツールの重要性が増しました。誰がどう変更したかを間違いなく把握できるので、自分が書いたコードという意識も高まりますし、疑問点があってもすぐに書いた本人に聞くことができます。

そういえば、Redmineを使い出したのもSubversion連携があったというのもありますね。

そして昨年くらいからGitを使い出しました。まだベストプラクティスが決まらず、本来の分散リポジトリという部分は生かせていないですが、基本操作は浸透してきたので、そろそろ色々と模索していこうと思います。

Git – Fast Version Control System
Apache Subversion

ツールの提供者に感謝!

こうしてあらためて見ると普段多くのツールを活用して開発を行っていることが分かります。

ここで挙げたツールはいずれもオープンソースで配布されているものであり、無償で利用することができます。当然ながこうしたツールは誰かが開発しているもので、さらにテストをしたり、マニュアルを書いたり、それを翻訳する人がいたり、と多くの人の力で提供されています。

今後も感謝の気持ちを忘れず、どんどん活用していきたいですね。

nginx+php-fpmをyumでインストールして、WordPress/CakePHPを動かす設定

この記事の所要時間: 4242

www.1×1.jpの環境をApache+mod_phpな環境から、nginx+php-fpmな環境へ移行しました。

さくらVPSのCentOS5.5環境にnginx+php-fpmをyumでインストールして、CakePHPとWordPressを動かす設定を行いました。

このエントリでは導入ということで、インストールから、とりあえず動作するところまでをご紹介します。

0. 構成

nginx+php-fpm環境にCakePHPとWordPressをインストールします。

それぞれ以下のURLでアクセスできるようにします。

http://www.1×1.jp/ -> CakePHP

http://www.1×1.jp/blog/ -> WordPress

1. インストール

nginxとphp-fpmだとソースからインストールするパターンが多いですが、ここではyumでインストールします。

1-1. remiリポジトリを登録

centosリポジトリにはnginx、php-fpmが含まれていないので、epel / remiリポジトリを登録します。

$ sudo wget http://download.fedora.redhat.com/pub/epel/5/i386/epel-release-5-4.noarch.rpm
$ sudo wget http://rpms.famillecollet.com/enterprise/remi-release-5.rpm
$ sudo rpm -Uvh remi-release-5*.rpm epel-release-5*.rpm

1-2. yumでインストール

nginxとphp-fpmをyumでインストールします。–enablerepo=remi で1-1で登録したremiリポジトリを指定することを忘れないようにしましょう。

他にphp-mbstringやphp-cliなんかもいれてますが、このあたりはお好みで。

$ sudo yum -y install nginx php-cli php-fpm php-mbstring --enablerepo=remi

2011/05/16現在インストールされるバージョンは以下です。

$ /usr/sbin/nginx -V
nginx version: nginx/0.8.54
built by gcc 4.1.2 20080704 (Red Hat 4.1.2-50)
TLS SNI support disabled
configure arguments: --user=nginx --group=nginx --prefix=/usr/share/nginx --sbin-path=/usr/sbin/nginx --conf-path=/etc/nginx/nginx.conf --error-log-path=/var/log/nginx/error.log --http-log-path=/var/log/nginx/access.log --http-client-body-temp-path=/var/lib/nginx/tmp/client_body --http-proxy-temp-path=/var/lib/nginx/tmp/proxy --http-fastcgi-temp-path=/var/lib/nginx/tmp/fastcgi --http-uwsgi-temp-path=/var/lib/nginx/tmp/uwsgi --http-scgi-temp-path=/var/lib/nginx/tmp/scgi --pid-path=/var/run/nginx.pid --lock-path=/var/lock/subsys/nginx --with-http_ssl_module --with-http_realip_module --with-http_addition_module --with-http_xslt_module --with-http_image_filter_module --with-http_geoip_module --with-http_sub_module --with-http_dav_module --with-http_flv_module --with-http_gzip_static_module --with-http_random_index_module --with-http_secure_link_module --with-http_degradation_module --with-http_stub_status_module --with-http_perl_module --with-mail --with-file-aio --with-mail_ssl_module --with-ipv6 --with-cc-opt='-O2 -g -pipe -Wall -Wp,-D_FORTIFY_SOURCE=2 -fexceptions -fstack-protector --param=ssp-buffer-size=4 -m64 -mtune=generic' --with-cc-opt='-O2 -g -pipe -Wall -Wp,-D_FORTIFY_SOURCE=2 -fexceptions -fstack-protector --param=ssp-buffer-size=4 -m64 -mtune=generic'
$ /usr/sbin/php-fpm -v
PHP 5.3.6 (fpm-fcgi) (built: Apr 15 2011 18:13:37)
Copyright (c) 1997-2009 The PHP Group
Zend Engine v2.3.0, Copyright (c) 1998-2011 Zend Technologies

2. nginxの設定

nginxの設定を行います。yumでインストールすると設定ファイルは /etc/nginx 以下に配置されます。/etc/nginx/nginx.confがメインの設定ファイルになっているのでこれを編集します。

Apacheの設定を知っていれば何となく理解できるかと思いますが、リクエストURIがlocationディレクティブにマッチすれば該当ブロックの設定が適用されます。

2-1. バージョン情報の出力をoff

HTTPレスポンスヘッダやエラーページでnginxのバージョン情報を出力しないようにします。

server_tokens     off;

2-2. gzipをon

gzipをonにします。

    gzip  on;

2-3. /blogをWordPressに

/blogというURLに対してWordPress用の設定を行っています。

内容はなんとなく分かるかと思いますが、前半のブロックでApacheでいうmod_rewriteのような設定を行っています。これにより存在しないディレクトリ、ファイルへのアクセスがあった場合は/blog/index.php?q=PATHへURLをリライトします。

後半のブロックがphp-fpm(FastCGI)の設定です。^/blog/.+\.php$にマッチするリクエストであれば、php-fpmへリクエストを送信します。

        # wordpress
        location /blog {
            alias   /path/to/wordpress;
            index  index.php;

            if (!-e $request_filename) {
                rewrite ^/blog(.+)$  /blog/index.php?q=$1 last;
                break;
            }
        }
        location ~ ^/blog/.+\.php$ {
            fastcgi_pass   127.0.0.1:9000;
            fastcgi_index  index.php;
            fastcgi_split_path_info ^/blog(.+\.php)(.*)$;
            fastcgi_param  SCRIPT_FILENAME  /path/to/wordpress$fastcgi_script_name;
            fastcgi_intercept_errors on;
            include        fastcgi_params;
        }

2-4. /blog以外のURLはCakePHPに

/blog以外のURLに対してCakePHP用の設定を行っています。

2-3と同じく、前半のブロックでURLリライトの設定を行っています。存在しないディレクトリ、ファイルへのアクセスがあった場合は/index.php?url=PATHへURLをリライトします。

後半のブロックでは、\.php$にマッチするリクエストであれば、php-fpmへリクエストを送信します。

        # cakephp
        location / {
            root   /path/to/cakephp/app/webroot;
            index  index.php index.html index.htm;

            if (!-e $request_filename) {
                rewrite ^(.+)$  /index.php?url=$1 last;
                break;
            }
        }
        location ~ \.php$ {
            fastcgi_pass   127.0.0.1:9000;
            fastcgi_index  index.php;
            fastcgi_param  SCRIPT_FILENAME  /path/to/cakephp/app/webroot$fastcgi_script_name;
            fastcgi_intercept_errors on;
            include        fastcgi_params;
        }

2-5. .htaccess / .git / .svnはアクセス禁止に

.htaccess / .git / .svnはアクセスできないようにしています。

        location ~ (\.ht|\.git|\.svn) {
            deny  all;
        }

2-6. /etc/nginx/nginx.conf

変更した /etc/nginx/nginx.conf は以下です。

#######################################################################
#
# This is the main Nginx configuration file.
#
# More information about the configuration options is available on
#   * the English wiki - http://wiki.nginx.org/Main
#   * the Russian documentation - http://sysoev.ru/nginx/
#
#######################################################################

#----------------------------------------------------------------------
# Main Module - directives that cover basic functionality
#
#   http://wiki.nginx.org/NginxHttpMainModule
#
#----------------------------------------------------------------------

user              nginx;
worker_processes  1;

error_log  /var/log/nginx/error.log;
#error_log  /var/log/nginx/error.log  notice;
#error_log  /var/log/nginx/error.log  info;

pid        /var/run/nginx.pid;

#----------------------------------------------------------------------
# Events Module
#
#   http://wiki.nginx.org/NginxHttpEventsModule
#
#----------------------------------------------------------------------

events {
    worker_connections  1024;
}

#----------------------------------------------------------------------
# HTTP Core Module
#
#   http://wiki.nginx.org/NginxHttpCoreModule
#
#----------------------------------------------------------------------

http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;
    server_tokens     off;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    #keepalive_timeout  0;
    keepalive_timeout  65;

    gzip  on;

    server {
        listen       80;
        server_name  _;

        #charset koi8-r;

        #access_log  logs/host.access.log  main;

        # wordpress
        location /blog {
            alias   /path/to/wordpress;
            index  index.php;

            if (!-e $request_filename) {
                rewrite ^/blog(.+)$  /blog/index.php?q=$1 last;
                break;
            }
        }
        location ~ ^/blog/.+\.php$ {
            fastcgi_pass   127.0.0.1:9000;
            fastcgi_index  index.php;
            fastcgi_split_path_info ^/blog(.+\.php)(.*)$;
            fastcgi_param  SCRIPT_FILENAME  /path/to/wordpress$fastcgi_script_name;
            fastcgi_intercept_errors on;
            include        fastcgi_params;
        }

        # cakephp
        location / {
            root   /path/to/cakephp/app/webroot;
            index  index.php index.html index.htm;

            if (!-e $request_filename) {
                rewrite ^(.+)$  /index.php?url=$1 last;
                break;
            }
        }
        location ~ \.php$ {
            fastcgi_pass   127.0.0.1:9000;
            fastcgi_index  index.php;
            fastcgi_param  SCRIPT_FILENAME  /path/to/cakephp/app/webroot$fastcgi_script_name;
            fastcgi_intercept_errors on;
            include        fastcgi_params;
        }

        location ~ (\.ht|\.git|\.svn) {
            deny  all;
        }
    }

    # Load config files from the /etc/nginx/conf.d directory
    include /etc/nginx/conf.d/*.conf;
}

3. php-fpmの設定

次にphp-fpmを設定します。 yumでインストールすると設定ファイルが/etc/php-fpm.confと/etc/php-fpm.d/www.confに配置されます。

php-fpm.confはとりあえずデフォルトのままで、www.confを編集します。変更するのは4箇所です。

3-1. 実行ユーザ

php-fpmの実行ユーザをnginxと同一にしておきます。

user = nginx

3-2. 実行グループ

実行ユーザと同じく実行グループもnginxと同一にしておきます。

group = nginx

3-3. プロセス生成方法

php-fpmの実行プロセス生成方法を指定します。動的にプロセス数を増減するならdynamicを、設定したプロセス数に固定するならstaticを指定します。

ここでは利用するリソースが把握しやすいstaticにします。

pm = static

3-4. 生成するプロセス数

生成するphp-fpmプロセス数を指定します。ここでは10プロセスを生成します。

他にpm.start_servers / pm.min_spare_servers/ pm.max_spare_serversといったプロセス数に関する設定がありますが、pm=staticの場合は影響ありません。

pm.max_children = 10

3-5. expose_phpをoffに

HTTPレスポンスヘッダに含まれるPHPバージョン情報を出力しないようにexpose_phpをoffにしています。

このようにPHP関連の設定はwww.confで記述することもできます。(もちろんphp.iniで設定しても良いです。)

php_admin_flag[expose_php] = off

3-6. /etc/nginx/nginx.conf

; Start a new pool named 'www'.
[www]

; The address on which to accept FastCGI requests.
; Valid syntaxes are:
;   'ip.add.re.ss:port'    - to listen on a TCP socket to a specific address on
;                            a specific port;
;   'port'                 - to listen on a TCP socket to all addresses on a
;                            specific port;
;   '/path/to/unix/socket' - to listen on a unix socket.
; Note: This value is mandatory.
listen = 127.0.0.1:9000

; Set listen(2) backlog. A value of '-1' means unlimited.
; Default Value: -1
;listen.backlog = -1

; List of ipv4 addresses of FastCGI clients which are allowed to connect.
; Equivalent to the FCGI_WEB_SERVER_ADDRS environment variable in the original
; PHP FCGI (5.2.2+). Makes sense only with a tcp listening socket. Each address
; must be separated by a comma. If this value is left blank, connections will be
; accepted from any ip address.
; Default Value: any
listen.allowed_clients = 127.0.0.1

; Set permissions for unix socket, if one is used. In Linux, read/write
; permissions must be set in order to allow connections from a web server. Many
; BSD-derived systems allow connections regardless of permissions.
; Default Values: user and group are set as the running user
;                 mode is set to 0666
;listen.owner = nobody
;listen.group = nobody
;listen.mode = 0666

; Unix user/group of processes
; Note: The user is mandatory. If the group is not set, the default user's group
;       will be used.
; RPM: apache Choosed to be able to access some dir as httpd
user = nginx
; RPM: Keep a group allowed to write in log dir.
group = nginx

; Choose how the process manager will control the number of child processes.
; Possible Values:
;   static  - a fixed number (pm.max_children) of child processes;
;   dynamic - the number of child processes are set dynamically based on the
;             following directives:
;             pm.max_children      - the maximum number of children that can
;                                    be alive at the same time.
;             pm.start_servers     - the number of children created on startup.
;             pm.min_spare_servers - the minimum number of children in 'idle'
;                                    state (waiting to process). If the number
;                                    of 'idle' processes is less than this
;                                    number then some children will be created.
;             pm.max_spare_servers - the maximum number of children in 'idle'
;                                    state (waiting to process). If the number
;                                    of 'idle' processes is greater than this
;                                    number then some children will be killed.
; Note: This value is mandatory.
pm = static

; The number of child processes to be created when pm is set to 'static' and the
; maximum number of child processes to be created when pm is set to 'dynamic'.
; This value sets the limit on the number of simultaneous requests that will be
; served. Equivalent to the ApacheMaxClients directive with mpm_prefork.
; Equivalent to the PHP_FCGI_CHILDREN environment variable in the original PHP
; CGI.
; Note: Used when pm is set to either 'static' or 'dynamic'
; Note: This value is mandatory.
pm.max_children = 10

; The number of child processes created on startup.
; Note: Used only when pm is set to 'dynamic'
; Default Value: min_spare_servers + (max_spare_servers - min_spare_servers) / 2
pm.start_servers = 5

; The desired minimum number of idle server processes.
; Note: Used only when pm is set to 'dynamic'
; Note: Mandatory when pm is set to 'dynamic'
pm.min_spare_servers = 5

; The desired maximum number of idle server processes.
; Note: Used only when pm is set to 'dynamic'
; Note: Mandatory when pm is set to 'dynamic'
pm.max_spare_servers = 35

; The number of requests each child process should execute before respawning.
; This can be useful to work around memory leaks in 3rd party libraries. For
; endless request processing specify '0'. Equivalent to PHP_FCGI_MAX_REQUESTS.
; Default Value: 0
;pm.max_requests = 500

; The URI to view the FPM status page. If this value is not set, no URI will be
; recognized as a status page. By default, the status page shows the following
; information:
;   accepted conn    - the number of request accepted by the pool;
;   pool             - the name of the pool;
;   process manager  - static or dynamic;
;   idle processes   - the number of idle processes;
;   active processes - the number of active processes;
;   total processes  - the number of idle + active processes.
; The values of 'idle processes', 'active processes' and 'total processes' are
; updated each second. The value of 'accepted conn' is updated in real time.
; Example output:
;   accepted conn:   12073
;   pool:             www
;   process manager:  static
;   idle processes:   35
;   active processes: 65
;   total processes:  100
; By default the status page output is formatted as text/plain. Passing either
; 'html' or 'json' as a query string will return the corresponding output
; syntax. Example:
;   http://www.foo.bar/status
;   http://www.foo.bar/status?json
;   http://www.foo.bar/status?html
; Note: The value must start with a leading slash (/). The value can be
;       anything, but it may not be a good idea to use the .php extension or it
;       may conflict with a real PHP file.
; Default Value: not set
;pm.status_path = /status

; The ping URI to call the monitoring page of FPM. If this value is not set, no
; URI will be recognized as a ping page. This could be used to test from outside
; that FPM is alive and responding, or to
; - create a graph of FPM availability (rrd or such);
; - remove a server from a group if it is not responding (load balancing);
; - trigger alerts for the operating team (24/7).
; Note: The value must start with a leading slash (/). The value can be
;       anything, but it may not be a good idea to use the .php extension or it
;       may conflict with a real PHP file.
; Default Value: not set
;ping.path = /ping

; This directive may be used to customize the response of a ping request. The
; response is formatted as text/plain with a 200 response code.
; Default Value: pong
;ping.response = pong

; The timeout for serving a single request after which the worker process will
; be killed. This option should be used when the 'max_execution_time' ini option
; does not stop script execution for some reason. A value of '0' means 'off'.
; Available units: s(econds)(default), m(inutes), h(ours), or d(ays)
; Default Value: 0
;request_terminate_timeout = 0

; The timeout for serving a single request after which a PHP backtrace will be
; dumped to the 'slowlog' file. A value of '0s' means 'off'.
; Available units: s(econds)(default), m(inutes), h(ours), or d(ays)
; Default Value: 0
;request_slowlog_timeout = 0

; The log file for slow requests
; Default Value: not set
; Note: slowlog is mandatory if request_slowlog_timeout is set
slowlog = /var/log/php-fpm/www-slow.log

; Set open file descriptor rlimit.
; Default Value: system defined value
;rlimit_files = 1024

; Set max core size rlimit.
; Possible Values: 'unlimited' or an integer greater or equal to 0
; Default Value: system defined value
;rlimit_core = 0

; Chroot to this directory at the start. This value must be defined as an
; absolute path. When this value is not set, chroot is not used.
; Note: chrooting is a great security feature and should be used whenever
;       possible. However, all PHP paths will be relative to the chroot
;       (error_log, sessions.save_path, ...).
; Default Value: not set
;chroot =

; Chdir to this directory at the start. This value must be an absolute path.
; Default Value: current directory or / when chroot
;chdir = /var/www

; Redirect worker stdout and stderr into main error log. If not set, stdout and
; stderr will be redirected to /dev/null according to FastCGI specs.
; Default Value: no
;catch_workers_output = yes

; Pass environment variables like LD_LIBRARY_PATH. All $VARIABLEs are taken from
; the current environment.
; Default Value: clean env
;env[HOSTNAME] = $HOSTNAME
;env[PATH] = /usr/local/bin:/usr/bin:/bin
;env[TMP] = /tmp
;env[TMPDIR] = /tmp
;env[TEMP] = /tmp

; Additional php.ini defines, specific to this pool of workers. These settings
; overwrite the values previously defined in the php.ini. The directives are the
; same as the PHP SAPI:
;   php_value/php_flag             - you can set classic ini defines which can
;                                    be overwritten from PHP call 'ini_set'.
;   php_admin_value/php_admin_flag - these directives won't be overwritten by
;                                     PHP call 'ini_set'
; For php_*flag, valid values are on, off, 1, 0, true, false, yes or no.

; Defining 'extension' will load the corresponding shared extension from
; extension_dir. Defining 'disable_functions' or 'disable_classes' will not
; overwrite previously defined php.ini values, but will append the new value
; instead.

; Default Value: nothing is defined by default except the values in php.ini and
;                specified at startup with the -d argument
;php_admin_value[sendmail_path] = /usr/sbin/sendmail -t -i -f www@my.domain.com
;php_flag[display_errors] = off
php_admin_value[error_log] = /var/log/php-fpm/www-error.log
php_admin_flag[log_errors] = on
php_flag[expose_php] = off
;php_admin_value[memory_limit] = 32M

4. nginx / php-fpmの起動

nginxとphp-fpmを起動します。

$ sudo /etc/init.d/nginx start
$ sudo /etc/init.d/php-fpm start

起動に成功すれば、0.0.0.0:80と127.0.0.1:9000がLISTENになっているはずです。

$ netstat -ltn | grep -E '(80|9000)'
tcp        0      0 127.0.0.1:9000              0.0.0.0:*                   LISTEN
tcp        0      0 0.0.0.0:80                  0.0.0.0:*                   LISTEN

サーバ起動時に自動実行されるようにchkconfigで登録しておきます。

$ sudo /sbin/chkconfig nginx on
$ sudo /sbin/chkconfig php-fpm on

あとはCakePHP / WordPressをインストールすればokです。

5. 実際に使ってみて

Apache+mod_phpからの移行ということで基本Apache感覚で触りだしたわけですが、つまづいたというか気になったことを。

5-1. Aliasはaliasで

ApacheのAliasのようなことをやりたい場合、locationディレクティブでURIを指定して、そのブロック内ではrootではなく、aliasにてディレクトリを指定します。

        location /blog {
            alias   /path/to/wordpress;
            index  index.php;
        }

5-2. .htaccessは使えない

CakePHPでもWordPressでも使われている.htaccessですが、nginxでは利用できません(反映されません)。

nginxには.htaccessのようにサーバが設定ファイルを再読込することなく設定を動的に変更する方法はありません。

.htaccessで指定するような、アクセス制御、mod_rewrite、Basic認証等はnginx.confなどの設定ファイルにて記述し、nginxを再起動(再読込)を実行して設定ファイルを反映します。

# nginx 再起動
$ sudo /etc/init.d/nginx restart

# nginx 設定ファイル再読込
$ sudo /etc/init.d/nginx reload

5-3. Digest認証がない

nginxにはDigest認証を実現するモジュールは無いようです。以前はDigest認証を利用している箇所があったのですが、Basic認証は利用できるのでHTTPS+Basic認証で代用しました。

5-4. PHP関連の設定を変更したら

php.iniやwww.confでPHP関連の設定を変更した場合、php-fpmを再起動(再読込)します。nginxを再起動したところでphp-fpmには反映されませんのでご注意を。

# php-fpm 再起動
$ sudo /etc/init.d/php-fpm restart

# php-fpm 設定ファイル再読込
$ sudo /etc/init.d/php-fpm reload

5-5. rewrite対象URLにアクセスして画面が真っ白に

ここで設定した内容では、http://example.com/foo/bar といったファイルが存在しないURLへアクセスすると /index.php?url=URLというURLへrewriteして処理を行います。

こういった状況で画面が真っ白になるという現象がありました。

結局、原因はnginx.confの設定で、rewriteしたPHPスクリプトがSCRIPT_FILENAME で指定したパスに存在しない(アクセスできない)とこういった現象になるようです。

nginx、php-fpm どちらのログにも何も出力されなかったのではじめは嵌りました。もし同じ現象になった際はnginx.confのSCRIPT_FILENAMEの設定を確認してみて下さい。

nginxをはじめるのにオススメの本

これからnginxをはじめるなら「ハイパフォーマンスHTTPサーバ Nginx入門」がおすすめです。

現時点(2011/05/19)で日本語で書かれたnginx解説本がこれしか無いということもありますが、nginxをこれから利用するにとっては分かりやすく解説されています。

ただ、いきなりシェルコマンドやLinuxファイルシステム、システム管理ツールの解説が60ページほど続くので、この部分だけを立ち読みして「こりゃダメだ」と判断しないように注意して下さい:D このあと30ページほどnginxをインストールする内容があって、ようやく本編がはじまります。その後はしっかりとnginxについて解説されていますのでご安心を。

各ディレクティブの設定については公式WikiをはじめWebに情報があるのですが、設定の基本的な思想や細かなTipsなどは書籍にまとまっていると理解しやすいですね。php-fpmとの連携やApacheとの連携(リバースプロキシ)、Apacheからの移行なども簡単に解説されているので、Apahce+mod_phpから移行しようかなという方は一度手にとってみてください。

Google Docs のスプレッドシートに表データをコピペする

この記事の所要時間: 211

Google Docs のスプレッドシートに表データをコピペする方法です。

Google Docs 便利ですね。Excel に比べると多少操作性や機能は劣りますが、どこからでも共有できるというのはそれを補って余りあります。

そんな便利なスプレッドシートなのですが、コピーしたCSVを表形式でスプレッドシートに貼る機能がありません。

もちろんCSVをファイルに書き出してインポートすれば良いのですが、今画面上に表示しているCSVをそのままコピペしたい時ってありませんか。

例えば、以下のようなCSVをコピペしてスプレッドシートに貼り付けると、1行が1つのセルに文字列として格納されてしまいます。

1,原田哲也,TZM250
2,加藤大治郎,NSR250
3,青山博一,RS250RW

タブ区切りならいける

何とかならないかなーと試してみると、なぜかタブ区切りだと各セルに値を入れることができました!

先のCSVをタブ区切り(TSV)に変更して、スプレッドシートに貼り付けると、

1    原田哲也    TZM250
2    加藤大治郎 NSR250
3    青山博一    RS250RW

ばっちりいけます。

ただし貼り付けはショートカットキーで

これ不思議なのですが、マウスの右クリックから「貼り付け」を選択するとセルに値が入りません。

ショートカットキーの command+v(Windowsなら、ctrl+v)でペーストすれば ok です。

psqlの検索結果をスプレッドシートに貼り付け

そもそもなんでこれをやりたかったかというと、psqlで検索した結果をスプレッドシートに貼り付けたかったから。

psqlで検索結果をTSVにするのは以下。

$ psql db_name
db_name=> \a   // クエリ結果の整形オフ
db_name=> \t    // 検索結果のみ出力
db_name=> \pset fieldsep '\t'  // 列の区切り文字をタブに(カンマにすれば CSV に)

db_name=> select id,name,code from users order by id; // 検索クエリ
1     原田哲也     TZM250
2     加藤大治郎     NSR250
3     青山博一     RS250RW

あとはこの結果をコピーして、スプレッドシートに貼り付けるだけ。あー便利。

iPhone/iPadのスクリーンショットを共有する「iScreenShot」をリリースしました

この記事の所要時間: 421

iPhone(iPod touch)、iPadのスクリーンショットを投稿して、みんなで見て楽しむサービス「iScreenShot」をリリースしました。

iScreenShot

=> iPhone/iPadのスクリーショットを共有 | iScreenShot

機能の詳細は、実際にサイトにアクセスして頂くのが分かりやすいので(ヘルプもありますし)、ここではサイトを作る経緯までや公開してからについて、ざくっと書いてみます。

構築した経緯

iPhone 3GSを昨年(2009年)の夏に購入したのですが、アプリを入れてカスタムし出した頃から、他人のホーム画面にとても興味が出てきました。どうにかして色々な人のホーム画面が見れないかな、ということでこのサービスを考えました。

ただ、アイデアとしてはあったのですが、なかなか手が付けられずに結局そのまま放置されることになりました。

そして今年の春、iPadが発売され、さらにiPhone4(iOS4)が発表されました。デバイスがというよりOSのバージョンアップにより、当時よりホーム画面のカスタマイズの自由度が広がりました。つまりこれまで以上に多様なホーム画面が見られるわけです。

これは作るしかないでしょ、ということで公開にするにいたりました。

公開して

まだβ版ということで、8/1夜と翌日昼にこっそりTwitterにだけお知らせをpostしました。

TwitterでのRTやblog等でのご紹介で、少しづつですがスクリーンショットが投稿されはじめました。実際に投稿されたスクリーンショットを見ると、それぞれに個性があって面白いですね。知人がiPhoneを持っていてもホーム画面をじっくり見る機会はあまり無いので、こうやって色々なホーム画面が見られるのは想定していたより楽しいですね:D

使った方、見た方のコメントでも「面白そう」「人のホーム見たいよね」という話があって、作って良かったなーと思っています。

早速、iPhone/iPadのblogでお馴染みのtobu iPhoneさんととはてブニュースさんにも取り上げて頂けました!はじめは知っている人でボチボチ投稿してもらえれば良いかなと思っていたのですが、一気に色々な方に投稿して頂けるようになりました。ありがとうございます。やっぱり、すごい。

お気に入りのホーム画面

どんなスクリーンショットがあるかは見るのが一番なので、投稿されたスクリーンショットの中で自分のお気に入りのものをご紹介します。

=> お気に入り | iScreenShot

まずは公開早々に投稿頂いたitayanさんのホーム画面。

青系の背景とアイコンできれいにまとまっていますね。色合いを合わせるように意識してアイコンを並べているとのこと。なるほど。

ご自慢のホーム画面をキャプチャーしてみました。 | iScreenShot

次にユニークなホーム画面を投稿されているfool4rootさんのホーム画面。

アイコンを全面に置かずにあえて背景を見せるのがポイントですね。しかしFlashって:D

Flash 対応しました :-P | iScreenShot

そして今のいちおしがこれ!

Kzh_09Jpさんのホーム画面です。

はじめて見た時に驚いて、さっそくコメント入れました:D iOS4のホームボタン2度押しによるアイコングレーアウトを上手く利用して背景を浮かせています。これはすごい。ぜひ実機で次元が現れるところを見てみたいです。

ホームボタンを連続二度押したときに現れるこのショットが何気に気に入っ... | iScreenShot

あとおまけで、自分が投稿したものを。

文字アイコンを並べて作ってみました。この画面を2ページ以降に用意しておいて、相手に「ほら、スライドさせて」といって、見せると効果ありそうです:D

@crema おたんじょうび、おめでとうございます! | iScreenShot

ホーム画面ではなく、ロック中の画面です。iPhone使っている人ならいつも見るバッテリ表示の電池から液漏れです:D これは実際に人に見せると反応が良いです。

バッテリーから液漏れ | iScreenShot

ほかにもいろいろなスクリーンショットがあるのでサイトで見てみて下さい。

ホーム画面は楽しい!

投稿されたスクリーンショットを見て、あらためて思うのはやっぱりiPhone/iPadは画面が美しいですね。並べてみると惚れ惚れします。また、その美しい画面をそのままスクリーンショットで保存できる機能を標準で用意しているのは、さすがAppleだなーと思います。

これまで、それほどホーム画面の体裁を意識していなかったのですが、色々な人のホーム画面を見ると俄然やる気がでて来ますね。

見て楽しい、投稿して楽しい、というようにiPhone/iPadユーザ(+気になる人)が楽しめるサービスになればなあ、と思っています。ホーム画面コンテストとかやると楽しそうなので、企画も色々とやっていきますね。

どうですか?あなたのご自慢のスクリーンショットも投稿したくなってきましたか?

=> iPhone/iPadのスクリーショットを共有 | iScreenShot

Amazonからメール送信制限に関するEC2 Account Notificationが来た

この記事の所要時間: 437

とあるイベントで使うWebシステムでEC2を使ってたところ、Amazonからメール送信制限に関する通知が来たという話です。

このイベントでは、短時間で多くのリクエストを捌かないといけないので、あらかじめWebサーバを何台か立てて準備していました。さらに想定を超える負荷に備えて、増設用AMIも作成して、すぐに増設できる体制にしていました。もちろん増設のリハーサルもやりました。

そして、イベントが始まります。

アクセスがボチボチ来始めました。リソースにはまだまだ余裕があるので順調に捌いてくれています。とりあえず無難な立ち上がりだなと、ひと安心したところにメールが一通。

EC2 Account Notification

Dear EC2 Customer,
You recently reached a limit on the volume of email you were able to send out of SMTP port 25 on your instance:

Instance ID: i-xxxxxxxx
* IP Address: XXX.XXX.XXX.XXX
* Start date: 2010-NN-NN NN:NN +0000
……

うん?メールの送信制限?何これ?

たしかにメール送信は行っていますが、1hで数十通程度です。しかも一気に送るというよりバラバラと送信しています。この程度で制限がくるとは思わなかったのですが、イベント中に送信を止められるとシャレにならないので、すぐにメールに記載されたフォームからメール送信制限解除申請を送りました。

Request to Remove Email Sending Limitations – Amazon Web Services

本文の内容でググってみると同じようなメールが来た人は何人か見つかったのですが、解決したという内容は見あたりませんでした。中には実際に Port25 をblockされて、仕方なく外部のメールサーバに別ポートで接続してメールを送信している人もいました。

その時点では送信はできていたので、イベントが終わるまではとにかく持ってくれという心境でした。

何とか回避策を

制限解除申請をしたものの、ぼーと待っていても仕方が無いので取れる策をということであれこれやってみました。

まず、送られてきたメールにはインスタンスIDとIPが記載されていたので、別インスタンスとIPなら回避できるかもと思い、作成しておいたAMIから新しいインスタンスを起動しました。さらにElastic IPで別IPを発行しました。これはもともと高負荷時にスケールアウトすることを想定していたのですんなりと準備できました。

ここでふと、もしかするとアカウントを停止させられるかも、という考えが頭をよぎりました。もしそうなると何台増設していても意味がありません。

実際にそうなるかは分かりませんが、とにかく止められないので、新たなアカウントを発行することにしました。てきぱきと登録をこなして、スケールアウト用AMIの共有設定を行い、なんとかLB+Web+DBの構成を構築しました。

あらかじめDNSのTTLは短めにしておいたので、もしアカウントを停止された時はDNSを書き換えて別アカウントの構成にアクセスを向けます。

この間もサービスとしては問題無く稼働していました。

Amazonからメールが

あれこれやっているとAmazonからメールが来ました。

内容は「申請を受け付けた」「2営業日以内に回答するよ」といったもの。2営業日以内って!とか思いつつ、とにかく回避策をひたすら準備。

サーバログ(主にmaillog)と睨めっこしながらイベントが無事終わるのを待ちます。inboundもoutboundも変なエラーは無く順調に稼働しています。自分でもサイトにアクセスして試してみますが問題ありません。とにかく無事に終わってくれ。。。

乗り切った!

結局、blockされることなく無事にイベントは乗り切ることができました。やれやれ。

再びAmazonからメールが

無難にイベントが終わって、ひと息ついていた頃に再びAmazonからメールが来ました。

内容は「制限を解除した」とのこと。

本文には申請したインスタンス、IPに対する制限を解除しただけなく、アカウントに紐付く全てのインスタンス、IPに対する制限を解除した、と書かれていました。

対応は2営業日以内と書かれていたのですが、申請から数時間で対応してもらえたのは驚きでした。

イベントはこの日限りで翌日にはサーバを停止する予定だったのですが、今後の利用のためにElastic IPで割り振ったIPだけは残しておくことにします。

EC2からメールを送る際は念のため申請を

イベント中に来たAmazonからのNotificationには本当に焦りました。

それほど多くのメールを送ったわけではなく、どういった基準でこの通知が来るかは分かりません。イベント前のテストでは問題無かったので、閾値は低いものの一定時間中に一定数を外部に送信すると通知される仕組みなのかもしれません。

とにかく、ここぞの時にblockされるとシャレにならないので、EC2から数多くのメール配信を行うなら、あらかじめ制限解除申請をするなり、別環境を用意するなり準備をしておいた方が良いでしょうね。

CakePHPとnginx+memcachedで手軽にキャッシュを活用する

この記事の所要時間: 439

nginx+memcachedがめちゃ気になったので試してみました。

元ネタは下記です。

A 53,900% speedup: Nginx, Drupal, and Memcache bring concurrency up and page load time way down | TechnoSophos

nginxをリバースプロキシに利用した構成で、バックエンドの出力をmemcachedにキャッシュしておけば、次回リクエストではnginxがそのキャッシュを読み取ってそのまま出力してくれます。

つまりバックエンドにリクエストを経由させずにnginxから即出力するのでかなりの高速化が見込めるという優れものです。

リンク先ではバックエンドにDrupalを利用していたのですが、ここではCakePHPを利用してみます。

1. 全体構成

リバースプロキシにnginx(Port: 80)を使い、バックエンドにはapache+mod_php(Port: 8080)を使います。

nginxはリクエストが来るとURLをキーとしたキャッシュがmemcachedにあるかを調べます。キャッシュがあればそれをレスポンスとしてそのまま返します。

キャッシュが無い場合はバックエンドにリクエストが飛びます。バックエンドではページ生成処理を行い、出力内容をmemachedURLにキャッシュします。

これにより次回リクエストからはこのキャッシュがnginxから出力されます。

試したバージョンは以下。

  • nginx 0.7.65
  • memcahed 1.2.8
  • apache 2.2.14
  • PHP 5.2.10
  • CakePHP 1.2.6

2. nginx+memcached設定

nginxのmemcached連携を行うためにNginxHttpMemcachedModuleを利用します。NginxHttpMemcachedModuleはnginxをデフォルトでインストールすれば含まれています。

nginx.confを以下のように設定します。

# バックエンド
upstream backends {
    server 127.0.0.1:8080;
}

http {
    #(snip)

    server {
        #(snip)

        location / {
            # POSTはそのままバックエンドへ
            if ($request_method = POST) {
                proxy_pass http://backend;
                break;
            }

            # memcachedにキャッシュがあればキャッシュを
            # 無ければバックエンドへ
            set $memcached_key $uri;
            memcached_pass     127.0.0.1:11211;
            default_type       text/html;
            error_page         404 502 = @fallback;
        }

        location @fallback {
            proxy_pass         http://backend;
        }
    }
}

ここで注意が必要なのが「error_page 404 502 = @fallback;」の箇所。

memcachedにキャッシュが無い場合はバックエンドへリクエストを投げるのですが、この箇所を[=]抜きにすると404(キャッシュが無い)や502(memcachedが繋がらない)がステータスコードとして出力されてしまいます。

つまりキャッシュが無い場合だとページ自体はバックエンドが生成した内容が出力されるのですが、ステータスコードが404となってしまいます。

[=] を付けるとバックエンドで出力されたステータスコードがそのままレスポンスとして出力されるので、[=]を忘れないようにしましょう。(はまりました。。。)

3. バックエンドでキャッシュ処理

次にバックエンドで生成した内容をmemcachedにキャッシュします。

まずPHPからMemcacheにアクセスするために、pecl::memcacheをインストールします。

$ sudo pecl install memcache

ここではCakePHPでの処理を想定しており、MemcacheViewHelperというヘルパを作成してキャッシュを行います。

MemcacheViewHelperはafterLayoutメソッドでビューからの出力全体をmemcachedにキャッシュします。

ちなみにHelper#afterLayout()はフレームワークから呼ばれるフックメソッドで、下記のように出力全体に対して処理を行うことが出来ます。例えば文字エンコーディングを変換する時などなかなか便利なメソッドです。

[app/views/helpers/memcache_view.php]

PHP:
  1. <?php
  2. class MemcacheViewHelper extends AppHelper {
  3.   public function afterLayout() {
  4.     if (!empty($this->params['memcache_view']['is_cache'])) {
  5.       $view = ClassRegistry::getObject('view');
  6.  
  7.       $memcache = new Memcache();
  8.       $memcache->addServer('localhost', 11211);
  9.       $memcache->set(Router::url(), $view->output, false, 3600);
  10.     }   
  11.   }
  12. }

コントローラでMemcacheViewHelperを読み込みます。キャッシュを有効にするアクションでは$this->params['memcache_view']['is_cache']にtrueをセットします。

下のUsersControllerの場合、indexアクションはキャッシュされますが、viewアクションはキャッシュされません。

[app/controllers/users_controller.php]

PHP:
  1. <?php
  2. class UsersController extends AppController {
  3.   public $name = 'Users';
  4.   public $helpers = array('MemcacheView');
  5.  
  6.   public function index() {
  7.     $this->User->recursive = 0;
  8.     $this->set('users', $this->paginate());
  9.     // キャッシュする
  10.     $this->params['memcache_view']['is_cache'] = true;
  11.   }
  12.  
  13.   public function view($id = null) {
  14.     if (!$id) {
  15.       $this->Session->setFlash(__('Invalid User', true));
  16.       $this->redirect(array('action' => 'index'));
  17.     }   
  18.     $this->set('user', $this->User->read(null, $id));
  19.   }
  20. }

これでブラウザからnginx(ex. http://localhost/users/index)にアクセスすると 1 回目はバックエンドにリクエストが飛びますが、2回目以降はmemcachedのキャッシュが出力されます。

4. パフォーマンス

キャッシュの効果を見るために、nginx+memachedを使ったパターンとバックエンドに直接接続した場合とでパフォーマンスを計測してみました。

計測はabで行っています。(-c 100 -n 1000)数値はRequests per secondで、5回行った平均値です。

参考にキャッシュをファイルに出力してnginxから出力するパターンも計測しました。

構成 Requests per second
apache+mod_php+CakePHP
(ダイレクト)
13.814 100%
nginx+memcached 2034.262 14726%
nginx+ファイル(参考) 5806.798 42037%

やはりapache+mod_php+CakePHPよりnginx+memcachedが圧倒的に早いですね:D ここまで大きな差があると使ってみたくなります。

5. memcached or ファイル

上記パフォーマンスを見るとmemcachedよりfileの方がより多くのリクエストを捌いています。

パフォーマンスだけを見るとファイルが有利ですが、memcachedの方はnginxサーバやWebサーバが複数台になった場合にキャッシュの取り扱いが簡単(複数台で共有しやすい)という利点があります。また、memcachedなら有効期限が簡単に付けられるというのもあります。

どちらを使うのが良いかはケースバイケースですが、キャッシュの取り扱いを考えると個人的にはmemcachedが良いバランスかなと思っています。更新頻度が少ないがアクセス数が多いページだけはファイルに出力してしまうという方法もあります。

いかに遅い箇所を経由させずにレスポンスを返すかという視点で見るとnginx+memcachedはまさに願ったり叶ったりの構成です。ここではバックエンドにCakePHPを使いましたが、他のフレームワークでも他のシステムでも他の言語でも何でも理屈は同じです。

多くの環境から操作できるmemcachedをキャッシュエンジンに使うということは、バックエンドを選ばずにキャッシュ機構が使えるということで、これはよく考えられていますね。

PHP×携帯サイト デベロッパーズバイブル

この記事の所要時間: 21

PHP×携帯サイト デベロッパーズバイブルを著者の荒木さんから献本して頂きました。
# 荒木さんおめでとうございます&ありがとうございます。

現場から生まれた本

ざっと読ませて頂いたのですが、さすが携帯サイトの開発に携われてきた荒木さんが書かれた本だけに、様々なノウハウがぎゅっと詰まっています。

3キャリアの公式情報から目的別(文字エンコーディング変換やらメールやら絵文字やらセッション管理等々)に欲しい情報が抽出されているのはもちろんのこと、公式の情報には明示的に記載されいていないノウハウが解説されています。

# 個人的にはメール絵文字(特にVodafone/Softbank)が泣けてきました:-D

こうした情報は実際に携帯サイトの開発に携わらないと分からない点で、まさに現場から出てきた本だと言えます。

対象機種選定の材料に

本書では携帯サイトの仕様について多くの解説があるのですが、構築する携帯サイトの対象機種を現行機種(3G)に限定すると、全てを実装する必要はありません。(もちろん本書内でも触れられれています)

これから携帯サイトを作ろうとしている人が陥りがちなのが、こうした仕様を見ると、全てを実装しようとしてしまう、すべきだ、と思い込んでしまう事です。

全端末対応が理想なのは確かなのですが、下位機種については対応する実装が複雑になりがちなわりにユーザ数が少なかったりするので、こうした機種は対応しないという選択肢が現実的だったりします。

そうしたケースで判断に必要なのが「下位機種対応のコスト」で、コストを計る上でこの本にある詳細な解説が参考になります。(単純にクライアントや上司を説得する材料になりますね)

そういった意味では、実際に開発をしなくても、携帯サイト制作に関わる人(デザイナさんや営業さん等々)なら、携帯サイトの仕様を知る面で価値のある一冊ではないでしょうか。

将来は

個人的には近い将来3キャリアの仕様が統一されて、本書のような解説書が無くとも携帯サイトが作れるようになってくれると嬉しいですね。:-D

現在の流れとしては、以前よりも3キャリア共に同じような仕様になりつつありますので、いずれは実現すると思います。

3キャリアの仕様が統一されて手軽に作れるようになった頃に、本書を見て、携帯サイト開発の歴史を振り返る、なんてのもオツですね:-D

vodafone.ne.jpアドレスのメールサーバが変わってた

この記事の所要時間: 240

この話は一般的なMTA経由でメールを送る場合は問題ありません。専用メール配信システム等でキャリアのメールサーバに直接配信する時のみ関係ある話です:-D

vodafoneメールアドレスのメールサーバが気がついたら変わってました。

まず各地域アドレスのMXレコードですが、以前は各地域毎にMXが異なっていたのですが、全てmx.k.vodafone.ne.jpに統一されています。

これ自体は以前から変わっていたような気もするのですが、地域毎のメールサーバが生きていたため、実は各々のメールサーバにメールを送ることが可能でした。

しかしここ数ヶ月の間で、[rnsq].vodafone.ne.jpの地域メールサーバ自体が存在しなくなったようで、それらのアドレスに送るには、MXレコードで指定されているmx.k.vodafone.ne.jpを利用するしかなくなりました。

各地域のアドレスを調べてみると以下のようになっていました。

地域 ドメイン MX 地域MX
北海道地区 d.vodafone.ne.jp mx.k.vodafone.ne.jp ok
東北・新潟地区 h.vodafone.ne.jp mx.k.vodafone.ne.jp ok
関東・甲信地区 t.vodafone.ne.jp mx.k.vodafone.ne.jp ok
東海地区 c.vodafone.ne.jp mx.k.vodafone.ne.jp ok
関西地区 k.vodafone.ne.jp mx.k.vodafone.ne.jp ok
北陸地区 r.vodafone.ne.jp mx.k.vodafone.ne.jp -
中国地区 n.vodafone.ne.jp mx.k.vodafone.ne.jp -
四国地区 s.vodafone.ne.jp mx.k.vodafone.ne.jp -
九州・沖縄地区 q.vodafone.ne.jp mx.k.vodafone.ne.jp -

今のところ[dhtc]については地域メールサーバが生きていますが、MX自体は[k]になっているので将来無くなってしまうかもしれません。

通常のMTAのようにMXレコードを引いて配信先メールサーバを決定する場合は問題無いのですが、メールサーバを決め打ちしている場合は対応が必要となるので要注意です。

いっそvodafone.ne.jpアドレスがsoftbank.ne.jpに移行してくれると助かるのですが:-P

[2009/02/19追記]

どうやら全ての地域メールサーバ(kを除く)が使用できなくなったようです。vodafone.ne.jpアドレスは素直にMXであるmx.k.vodafone.ne.jpに送信するようにしましょう:-D

Twitterのキーワードをグラフで見るTwitterTrendをリリースしました。

この記事の所要時間: 136

Twitterで、あるキーワードがどれくらい投稿されているかをグラフ化するTwitterTrendをリリースしました。

TwitterTrend

なにをするの?

キーワードを入力して「集計」ボタンをクリックすると直近1ヶ月でキーワードが含まれた投稿数をグラフで表示します。

流行のキーワードがどの程度反応されているかを見ることができます。

例えば最近物議を醸しているあの話題で集計すると↓のようになります。

TwitterTrend : PHP - .php

キーワード

キーワードはTwitter検索と同じ検索式が使えます。

また3つまでなら複数のキーワードを指定することもできます。この場合それぞれのキーワードについてグラフが表示されるので、投稿数を比較することができます。それぞれのキーワードは[,](カンマ)で区切ります。

TwitterTrend : ラーメン,うどん,そば

集計対象

集計されるのは前日から直近1ヶ月間の日本語ユーザの投稿です。

Twitterでの伝播

今回はblogでリリースを出す前にTwitterでURLを投稿してみました。ものの数分でサーバにアクセスが来だして、はてブにブックマークが付きました。1h経過した頃には10users程度になってました。その後予定があったので外出したのですが、帰ってきた頃には30usersが付いていました。

Twitterで呟いたサービスがどんどん広がっていく様は、改めて驚きました。情報の伝達スピードがハンバじゃないですね。

TwitterやSBMで速攻で反応が返ってくるのはわくわくしますね。この感覚はやみつきになりそうですw。

# ちなみにこのサービスは今話題のPHPで作ってます。

# id:moto0915さん、はてぶで不具合報告ありがとうございました!「OR」の件は修正しましたー。

PostgreSQL VACUUM FULLせずに不要領域を削除する

この記事の所要時間: 41

PostgreSQLではDELETEしたレコードは物理的には削除されずそのまま残り続けます。テーブル自体のサイズ(容量)を削減するにはDELETEした後にVACUUM FULLを行う必要があります。

このVACUUM FULLはテーブルへの書き込みロックがかかります。また数GクラスのテーブルへのVACUUM FULLは数時間かかることがあるので、常時書き込みがあるテーブルへは処理を行うタイミングが難しいです。

そこでVACUUM FULLを行わずに不要領域を削除(テーブルサイズを減らす)する方法を考えました。

方法は単純で「テーブルを新たに作って、そちらにデータを移行する」だけです。流れとしては以下のようになります。

  1. 不要領域を削除するテーブル(移行元テーブル)と同じレイアウトのテーブルを作成する(移行先テーブル)
  2. 移行先テーブルにレコードを移行する
  3. 移行先テーブルにインデックスを設定する
  4. 移行元テーブル名を変更する
  5. 移行先テーブル名を移行元テーブル名に変更する

ここでは不要領域を削除するテーブル(移行元)をitems、移行先をitems_newとします。

1. items_newテーブルを作る

create table as文でitems_newテーブルを作成します。「limit 0」を指定することによりレコードを移行することなくitemsと同じレイアウトのテーブルを生成します。但しこの方法では制約等は受け継がれないので別途作成する必要があります。

SQL:
  1. CREATE TABLE items_new AS SELECT * FROM items LIMIT 0;

2. レコードを移行する

insert into select文でitemsテーブルからitems_newテーブルにレコードを格納します。ポイントは全レコードを一度に移す必要が無く、select文のwhere句で条件を指定して序々に移行することができる点です。ログのような時系列で並ぶレコードでは一度登録されたレコードを変更されることが無いので、この方法で少しづつ移行することができます。

逆に各レコードがランダムに変更されるようなテーブルだとこのアプローチは使えません。

order by句を指定することにより任意の順序でレコードを格納することができます。

SQL:
  1. INSERT INTO items_new SELECT * FROM items WHERE created BETWEEN '2007/1/1 00:00:00' AND '2007/1/31 23:59:59' ORDER BY created;

3. items_newテーブルにインデックスを設定する

items_newテーブルにインデックスを設定します。インデックス設定はロックがかかるのでこの段階で行います。

SQL:
  1. CREATE INDEX items_new ON ...;

● itemsテーブルへの書き込みを停止する

常時itemsテーブルへの書き込みがある場合は、このタイミングで書き込みを一旦停止します。停止の方法には、サービスを一時停止する、テンポラリテーブルに書き出す、ファイルに書き出す等が考えられます。

4. itemsテーブルのテーブル名を変更する

itemsテーブルのテーブル名を適当な名前に変更します。

SQL:
  1. ALTER TABLE items RENAME TO items_old;

5. itemsテーブルのテーブル名を変更する

items_newテーブルのテーブル名をitemsに変更します。

SQL:
  1. ALTER TABLE items_new RENAME TO items;

● itemsテーブルへの書き込みを再開する

itemsテーブルへの書き込みを再開して完了です。もし停止期間に書き込むデータがあればitemsに書き戻します。またこの方法では外部制約は設定していないので、もしitemsやそれに関連するテーブルに外部参照制約があるならそちらも設定します。(そもそも外部参照がからむようなテーブルにはこの方法自体が不向きですね。)

VACUUM FULLは意外とやっかい

テーブルサイズが巨大化するとVACUUM FULLは意外とヘビーです。通常はコンカレントVACUUMで用が済むかもしれませんが、テーブルサイズを小さくしたい場合などは、今回のようにちょっとした工夫が必要となります。

ちなみにこの方法を取ると当然ながらレコードのoidが変わります。oidに依存しているシステムでは適用できませんのでご注意を。

ホーム > Web+DB

検索
フィード
メタ情報

Return to page top