タグ別アーカイブ: WordPress

PHPのWordPressがApacheを巻き込んで死ぬので鯖変えた

題名の通り。

Windows + Apache + MySQLの環境だけど、何故かプロセスが死んだり、CPUが100%になってハングアップしてたりする。
これまでは安定してたのだけど、050plusの件でアクセス数が増えたからなのか、どうも知らないうちに鯖が止まってしまう。
というわけで、対症療法かも知れないが、VPSから自宅サーバ―(この前購入した鼻毛鯖。Ubuntuをインストール)に急遽移転してみた。

CPUはCorei3よりも下のクラスのPentium G6950だけど、VPSよりも軽く動作する。CPUの世代が新しい&占有できるだけあって、さすがというべきか。今の所安定しているけれど、大量にアクセスが来るとやっぱり不安定になりそうな気もする。
とか書いてる最中に、sshをいじっていると、何かエラー出た。

fork 失敗: メモリを確保できません

APCを導入したせいかな。
とりあえず、適当にググると、共有メモリが足りない(デフォルトは32MB)っぽいので、/etc/sysctl.confに1行追加して共有メモリを増やしておく。

kernel.shmmax = 536870912

メモリ8GBつんでるし512MBぐらいあっても大丈夫だよね。
sudo sysctl -p
も忘れずに。

あとは……ネックになるのは回線速度。
とりあえずは、W3 Total CacheのCDN機能を使って、画像等だけを回線速度の速いVPSから配信する形にした。

これでしばらく試して見よう。

さくらVPSのWordPressが軽くなった。不思議。

ブログ用に新たなドメイン(btmn.jp)を取得し、キャッシュ設定してもwordpressが重いのでVPSのアップグレードを検討していたのだけれど、最近なぜか軽く感じる。

慣れたからなのだろうか。それとも、同じ物理サーバーのお隣さんがCPUリソースを喰うような実験をしていたのだろうか。
色々と謎だけど、まあいいかw

WordPressプラグインMT Style Post Nameの重複タグ問題を解決(WP3.2)

WordPressユーザーの間では有名な(たぶん)プラグインである「MT Style Post Name」。一応説明しておくと、これは、スラッグ(パーマリンク文字列)の生成をMTっぽくするプラグインである。

さて、これだけ有名なプラグインではあるが、古いためか、WP3系で使用すると、重大なバグが発生する。例えば、管理画面のJavascriptが誤動作するというバグや、全角文字のみのタグの場合投稿するたびに同名のタグがどんどん増殖してゆくというバグである。前者に対しては、カスタム投稿タイプでpermalink設定がうまくできないときの対策を無い知恵絞って考えた。 | Liglogでも対応が試みられている。本稿ではこのプログラムをさらに改良し、後者に対応してゆきたい。

本家のコードでは、単純に正規表現で全角文字を除去するだけの実装だ。一方、Liglogのコードでは、postidに置き換えられることを期待して、全角文字が含まれていた場合には問答無用で空文字を返却するという処理を行っている。確かに記事投稿の場合はこの処理で問題ない。しかし、タグやカテゴリのスラッグ生成にもここで使用されている関数が用いられているらしく、どうやらこのタグ登録時の処理に異常を来してしまうらしい。

実際にソースを確認したわけではないのだが、入力文字列から一意に決定されるスラッグ文字列を出力しなければ出力されるスラッグ文字列は入力文字列に対して1つに定まらなければ都合が悪いようだ。postidやtagidなどは可変であるため、同じ入力文字列でも異なるスラッグが出力されてしまう。このあたりの挙動が、重複タグ問題を起こしているらしい。

そこで、入力文字列に対して一意な文字列を出力すれば良いということになる。bin2hexを用いて文字列を16進数文字列化しても良いし、md5ハッシュを求めても良い。ただ、それでは桁数が増えすぎるため、今回はCRC32チェックサムを用いることにした。ソースコードを次に示す。

/*
* Plugin Name: MT Style Post Name Kai
* Description: MT Style Post Nameのタグ重複問題対応版。スラッグに強制的にタイトルのCRC32値を出力する
* Author: 449, Modified By Butaman-kun
* Plugin URI: http://pc10.2ch.net/test/read.cgi/blog/1163599919
* Version: 0.2.1
* */
add_filter('sanitize_title','sanitize_title_numalpha_only',9);
function sanitize_title_numalpha_only($title) {
    if(strlen($title) == 0){
        return '';//空文字の入力時は空文字を返す
    }elseif(mb_strlen($title) == strlen($title)){
        return $title;//2バイト文字が含まれていない場合はそのまま値を返す
    }else{
        $title_old = $title;

        //とりあえず2バイト文字を置換する処理
        $title = preg_replace('/[^%a-zA-Z0-9 \(\)_-]/', '-', $title);
        $title = preg_replace('/\-+/', '-', $title);
        $title = trim($title, '-');

        if(mb_strlen($title) == 0) {
            //何も残らなかったらCRC32のチェックサムに変換
            //(同じ入力に対して同じ出力になればいいのでmd5とかでも良い)
            $title = sprintf("%x", crc32($title_old));
        }
        return $title;
    }
}

さて、元の処理から正規表現による置換が増えたことが気になる方もおられるだろう。もともとのコードでは返却先で不正な文字列は除去されることを期待して、完璧なスラッグ文字列を出力していなかった。たとえば「あいう」という入力に対しては「—」と返却されていたのだ。しかし、これでは「すべてパーマリンクに使用出来ない文字(全角文字など)か」という判定を行うには都合が悪い。

そこで、2連続以上の連続ハイフンの除去、行頭、行末のハイフンの除去を行う処理を追加した。この時点で空文字になっていれば、それは入力文字列がすべて不正文字であったと判定できる(そう言う意味では、正規表現を使わずにハイフンを空文字に置換して判定しても良いかも知れない)。すべて全角であったと判定されたときに限り、CRC32チェックサムを返却する。

なお、入力文字列が空文字の場合のみ空文字を返却するようにしている。(さもなくば、0が返却されてしまうので)

パフォーマンスはあまり考慮していないコードだが、とりあえず適当にテストしたところ、うまく動いているようだ。

何か問題点があればご指摘願いたい。

追記 (2011.09.19)
掲載しているソースコードを差し替え。動作に変更はないが、置換処理の正規表現を減らした。

その後、ソースを確認してみた。結論から言うと、(1)タグやカテゴリのスラッグ生成ではPostの場合と異なり最初にterm_id(タグやカテゴリのID)が補完されるわけではないこと、(2)タグやカテゴリは名前ではなくスラッグによって重複確認がされていること、が誤動作の原因であった。

まず、wp-includes/formatting.php内のsanitize_title関数は、次の通りである。

/**
 * Sanitizes title or use fallback title.
 *
 * Specifically, HTML and PHP tags are stripped. Further actions can be added
 * via the plugin API. If $title is empty and $fallback_title is set, the latter
 * will be used.
 *
 * @since 1.0.0
 *
 * @param string $title The string to be sanitized.
 * @param string $fallback_title Optional. A title to use if $title is empty.
 * @param string $context Optional. The operation for which the string is sanitized
 * @return string The sanitized string.
 */
function sanitize_title($title, $fallback_title = '', $context = 'save') {
	$raw_title = $title;

	if ( 'save' == $context )
		$title = remove_accents($title);

	$title = apply_filters('sanitize_title', $title, $raw_title, $context);

	if ( '' === $title || false === $title )
		$title = $fallback_title;

	return $title;
}

これを見る限り、第1引数($title)の文字列を各フィルタ(sanitize_title_numalpha_only関数やsanitize_title_with_dashes関数など)に通した結果が空文字であれば、第2引数($fallback_title)の文字列を結果として返却するという実装になっているようだ。
次に、postのスラッグを生成する処理をwp-includes/post.phpから抜粋すると次のようになっている。

$post_name = sanitize_title($post_title, $post_ID);

したがって、sanitize_title_numalpha_only関数で空文字を返却すると、post_idがslugとして使用されるのだ。

一方で、タグやカテゴリの場合はどうなっているのだろうか。タグやカテゴリの追加処理を行うのは、wp-includes/taxonomy.php内のwp_insert_term関数のようだ。その中で、スラッグが未指定の場合は、sanitize_title関数を使用してスラッグを生成する処理がある。しかし、ここでは第2引数は指定されていない

	if ( empty($slug) )
		$slug = sanitize_title($name);

したがって、ここで$slugには空文字が代入されてしまう。これが後々誤動作を引き起こす。
次に$slugが使用されるのは次の箇所。

	if ( $term_id = term_exists($slug) ) {

ここで重複確認が行われているのだが、$slugが空の時は0になり、重複は存在しないと判断される。
このif文のelse句は以下のようになっている。

	} else {
		// This term does not exist at all in the database, Create it.
		$slug = wp_unique_term_slug($slug, (object) $args);
		if ( false === $wpdb->insert( $wpdb->terms, compact( 'name', 'slug', 'term_group' ) ) )
			return new WP_Error('db_insert_error', __('Could not insert term into the database'), $wpdb->last_error);
		$term_id = (int) $wpdb->insert_id;
	}

wp_unique_term_slug関数は、既に同じスラッグが存在していれば、ユニークになるように末尾に数字を付け足したりする関数なのだが、この場合は$slugは空文字でDB上には存在しないスラッグであり、なにもしないでそのまま空文字が返却される。この時点でまだ$slugは空文字である。ここで、スラッグが空のままデータベースに登録(ここで、増殖が起こってしまった)され、その時に振られたterm_idを取得している。

そして、次の部分でやっとこのことでスラッグにterm_idが入力される。

	// Seems unreachable, However, Is used in the case that a term name is provided, which sanitizes to an empty string.
	if ( empty($slug) ) {
		$slug = sanitize_title($slug, $term_id);
		do_action( 'edit_terms', $term_id );
		$wpdb->update( $wpdb->terms, compact( 'slug' ), compact( 'term_id' ) );
		do_action( 'edited_terms', $term_id );
	}

このときには既に増殖してしまった後なので、もはや後の祭りである。

ただ、不思議なのは、空文字を返さないようにすれば、上記のコードにもかかわらず正常に動作することだ。
タグ名やカテゴリ名が同じものが存在すれば、sanitize_title関数の結果とスラッグが異なっていても、重複したタグが生成されることはない。上記のコードの所々で空文字かどうかというのが判定基準に使われているところがあり、それが関係しているのだろう。
とりあえずうまく動いているのでこれ以上深追いはしないことにする。

追記(2011.09.20)
本文を少し手直し。

WordPress に W3 Total Cacheを入れてみる

WordPressは高機能な代わりに非常に重いことが分かったので、キャッシュプラグインを入れてみることにした。
W3 Total Cacheが評判なので、それを使ってみる。

まぁこの手のキャッシュプラグインでよく言われる、モバイル用テーマとの相性。結論から言うと、WPTouchとは相性が悪い。
確かにこのW3 Total Cacheプラグインは、Useragentグループを設定することができ、グループごとにページキャッシュを作成できるのだけど、例えばiPhone使ってる誰かが「モバイルテーマをオフ」という操作をしてしまうと、パソコン用のテーマで表示されたページがキャッシュされ、他のiPhoneユーザーにもパソコン用テーマで表示されてしまう。したがって、モバイルデバイスではページキャッシュをしないように設定した。方法は、Page cacheのRejected User Agents:を設定するだけ。ここに設定するリストは、Useragentグループ2つに入っていたリストをつなげただけのもの。

acer\ s100
android
archos5
blackberry9500
blackberry9530
blackberry9550
blackberry\ 9800
cupcake
docomo\ ht\-03a
dream
htc\ hero
htc\ magic
htc_dream
htc_magic
incognito
ipad
iphone
ipod
kindle
lg\-gw620
liquid\ build
maemo
mot\-mb200
mot\-mb300
nexus\ one
opera\ mini
samsung\-s8000
series60.*webkit
series60/5\.0
sonyericssone10
sonyericssonu20
sonyericssonx10
t\-mobile\ mytouch\ 3g
t\-mobile\ opal
tattoo
webmate
webos
240x320
2\.0\ mmp
\bppc\b
alcatel
amoi
asus
au\-mic
audiovox
avantgo
benq
bird
blackberry
blazer
cdm
cellphone
danger
ddipocket
docomo
dopod
elaine/3\.0
ericsson
eudoraweb
fly
haier
hiptop
hp\.ipaq
htc
huawei
i\-mobile
iemobile
j\-phone
kddi
konka
kwc
kyocera/wx310k
lenovo
lg
lg/u990
lge\ vx
midp
midp\-2\.0
mmef20
mmp
mobilephone
mot\-v
motorola
netfront
newgen
newt
nintendo\ ds
nintendo\ wii
nitro
nokia
novarra
o2
openweb
opera\ mobi
opera\.mobi
palm
panasonic
pantech
pdxgw
pg
philips
phone
playstation\ portable
portalmmm
proxinet
psp
qtek
sagem
samsung
sanyo
sch
sec
sendo
sgh
sharp
sharp\-tq\-gx10
small
smartphone
softbank
sonyericsson
sph
symbian
symbian\ os
symbianos
toshiba
treo
ts21i\-10
up\.browser
up\.link
uts
vertu
vodafone
wap
willcome
windows\ ce
windows\.ce
winwap
xda
zte

深くは考えずに設定したが、とりあえずAndroidではうまく動いている。iPhoneは持ってないので知らない。

ちなみに、Pagecacheを切ってもDBやObjectがキャッシュされるので、それなりに効果がでるし、うちのサイトをモバイルから見るのは僕ぐらいしかいないだろうから、特に問題ないだろう。

 

さらに設定を最適化していきたい。

Page Cache、Minify、DB Cache、Object Cacheを有効にする。ここで、それぞれのキャッシュの保存先をどうするかという問題がある。

うちの環境ではキャッシュ用に、DiskとMemcachedが使える。パフォーマンスを考えると全部memcachedにしたほうがいいのかと思っていたが、次のページを見るとそうでもないらしい。

http://cd34.com/blog/scalability/wordpress-cache-plugin-benchmarks/

結果では、ページキャッシュはDisk (Enhanced)、その他をAPCに設定するのがベストらしい。うちではAPCは使えないので、memcachedで代用する。これでもそれほど違いはないはずだ。

これらの結果、さくらVPS 1GBプランの環境でのレスポンスタイムは、未キャッシュの状態で約5秒、キャッシュ済みの状態で約1〜3秒まで向上した。ただ、コメント投稿とかのアクションではレスポンスに20秒ほどかかってしまう。これはスパム対策プラグインが悪さをしているのだろうか。とりあえず色々調整していこう。