タグ別アーカイブ: WordPress

NginxでWordPressを動かす@Ubuntu 14.04 LTS

少し流行は過ぎてしまったのですが、UbuntuのnginxでWordPressを動かしたのでメモ。

1. 構成
まずは構成について。
ウェブサーバの構成について、単にnginxを使うとしてもいろいろな構成があるんだけれど、今回はリバースプロキシを使った構成にしました。

リバースプロキシはユーザからのリクエストを受ける際、いったんプロキシサーバがリクエストを受け取って、それを本当のウェブサーバに問い合わせを行う、というそんな機能を提供してくれます。
プロキシ(代理)サーバなので、リクエストを途中から代理してくれる、そんなサーバです。
リバースと付いているのは、リバースじゃ無い普通のプロキシサーバもありまして、こちらはクライアント側で利用します。リバースプロキシはリバース、逆向きなので、サーバ側において代理させるんですね。
ちなみにリバースプロキシサーバ、プロキシサーバともにキャッシュ機能が付いている物がほとんどです。

で、今回の構成では、インターネット側からのリクエストをnginxのリバースプロキシで受けて、それを内部のnginxへリクエスト、そのnginxはWordPressに必要なPHPをPHP-FPMというものを使って動作する、ということに。
このリバースプロキシを使うことでキャッシングが容易にできるのでWordPressの高速化が進む、というものですね。

ちなみにPHP-FPMはプロセスで待ち受けさせていて、リクエストがあるとすぐにPHPを実行して結果を返すという、そういう仕組みをもったPHPのサーバ、デーモンです。

一台のサーバで”リバースプロキシ”、”ウェブサーバ”、”PHPサーバ(アプリケーションサーバ)”を動かす必要があるので、そのやりとりにはいくつかのルールが必要です。
サーバへリクエストを投げるときには必ず、どのサービスへ繋ぐか、という情報が必要なのですが、これを通常ポート番号によって振り分けるのです。
ちなみに一般的なウェブサービスだと80番を利用します。またプロキシサーバでは8080版を利用します。

今回はリバースプロキシですのでこいつが80番で待ち受けて、このリバースプロキシからウェブサーバへはウェブサーバの8080番ポートへ接続、PHPが動いているデーモンへはポート番号じゃなくてsockという仕組みで繋ぐことにしました。

2. 手順
まずはインストール。ubuntuなのでaptを利用します。今回必要になるのはnginx, php-fpmです。

shell> sudo apt-install nginx php-fpm
shell> cd /etc/nginx

インストールが終わったらnginxのデフォルトコンフィグが格納されている/etc/nginxで作業します。

まずは早速、WordPressに必要なnginxのconfigを二つ書きます。
これはWordPressの公式サイトで紹介されています。二つのconfを/etc/nginx/siteconf.d/というディレクトリを作って配置しています。

more /etc/nginx/siteconf.d/global_restrictions.conf       
# this file is from http://codex.wordpress.org/Nginx                            # Global restrictions configuration file.                                       
# Designed to be included in any server {} block.</p>                           
location = /favicon.ico {                                                       
         log_not_found off;                                                     
         access_log off;
}

location = /robots.txt {
         allow all;
         log_not_found off;
         access_log off;
}

# Deny all attempts to access hidden files such as .htaccess, .htpasswd, .DS_Sto
re (Mac).
# Keep logging the requests to parse later (or to pass to firewall utilities suc
h as fail2ban)
location ~ /\. {
         deny all;
}






shell> cat /etc/nginx/siteconf.d/wordpress_base
# this file is from http://codex.wordpress.org/Nginx

# WordPress single blog rules.
# Designed to be included in any server {} block.

# This order might seem weird - this is attempted to match last if rules below fail.
# http://wiki.nginx.org/HttpCoreModule
location / {
         try_files $uri $uri/ /index.php?$args;
}

# Add trailing slash to */wp-admin requests.
rewrite /wp-admin$ $scheme://$host$uri/ permanent;

# Directives to send expires headers and turn off 404 error logging.
location ~* ^.+\.(ogg|ogv|svg|svgz|eot|otf|woff|mp4|ttf|rss|atom|jpg|jpeg|gif|png|ico|zip|tgz|gz|rar|bz2|doc|xls|exe|ppt|tar|mid|midi|wav|bmp|rtf)$ {
       access_log off; log_not_found off; expires max;
}

# Uncomment one of the lines below for the appropriate caching plugin (if used).
#include global/wordpress-wp-super-cache.conf;
#include global/wordpress-w3-total-cache.conf;

# Pass all .php files onto a php-fpm/php-fcgi server.
location ~ \.php$ {
         # Zero-day exploit defense.
         # http://forum.nginx.org/read.php?2,88845,page=3
         # Won't work properly (404 error) if the file is not stored on this server, which is entirely possible with php-fpm/php-fcgi.
         # Comment the 'try_files' line out if you set up php-fpm/php-fcgi on another machine.  And then cross your fingers that you won't get hacked.
         try_files $uri =404;

         fastcgi_split_path_info ^(.+\.php)(/.+)$;
         #NOTE: You should have "cgi.fix_pathinfo = 0;" in php.ini

         include fastcgi_params;
         fastcgi_index index.php;
         fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
         fastcgi_param REMOTE_ADDR  $http_x_real_ip;
#        fastcgi_intercept_errors on;
         fastcgi_pass unix:/var/run/php5-fpm.sock;
}

最初のglobal_restrictions.confでは不正なアクセスを防ぐための設定です。nginxでは.htaccessを初めとしたファイルを利用しませんので、それらのファイルが標準では直接表示されたりします。中にはパスワードを書いてあったりすることもあるので安全のためこれらのファイルを非表示にするconfです。

続いてwordpress_base.confです。これがnginxとphp-fpmでwordpressを動かすための基本的なconfigになります。ほとんどは公式通りなのですが、注意点がいくつかあります。
conf本体の下部にある、 “fastcgi_param REMOTE_ADDR $http_x_real_ip;”については追記してあります。
これは今回リバースプロキシを使うに当たって、ウェブサーバで見えるクライアントIPアドレスが127.0.0.1のような自分自身のアドレスになってしまうことを防ぐ処置です。
自分自身をリバースプロキシにする場合に限らず何らかのリバースプロキシを挟むとREMOTE_ADDRが書き換わってしまいます。そうするとアクセス解析やコメントをつけた人のIPアドレスがすべて同じものになってしまい意味のないものになってしまいます。
それを防ぐための構文になります。ちなみにapacheであればmod_rpafというモジュールで対処したりします。
もう一点気をつける構文は”fastcgi_pass unix:/var/run/php5-fpm.sock;”です。今回はPHPとnginxを動かすサーバが同じサーバでしたのでsockを利用しました。他のホストで動かす場合やTCP/IPで動かす場合には変更が必要です。

これでwordppressの基本的なconfは揃いました。続いてはサイトのconfを書きます。

最近はnginxもapache同様、ドメインごとにconfigを書いてシンボリックリンクで有効化/無効化する流れになっています。今回は /etc/nginx/sites-available/example.com で作成しました。
このconfにはリバースプロキシ用の設定も含まれるので少し大変です。

shell> cat /etc/nginx/sites-available/exampel.com.conf
server {
        listen 80;
        server_name www.example.com;
        root        /home/yousan/public_html/www.example.com;
        index index.html index.htm index.php;
        include siteconf.d/global_restrictions.conf;

# ここから下がリバースプロキシ用の設定 最後にドメインの名前を設定すること
# またnginx.confのbackednともセットで設定すること
              location /wp-admin { proxy_pass http://backend; }
              location ~ .*\.php { proxy_pass http://backend; }
        location / {
              set $mobile "";
              if ($http_user_agent ~* '(DoCoMo|J-PHONE|Vodafone|MOT-|UP\.Browser|DDI
POCKET|ASTEL|PDXGW|Palmscape|Xiino|sharp pda browser|Windows CE|L-mode|WILLCOM|SoftB
ank|Semulator|Vemulator|J-EMULATOR|emobile|mixi-mobile-converter)') {
              set $mobile "@ktai";
              }
              if ($http_user_agent ~* '(iPhone|iPod|Opera Mini|Android.*Mobile|NetFr
ont|PSP|BlackBerry)') {
              set $mobile "@mobile";
              }
              if ($http_cookie ~* "comment_author_|wordpress_(?!test_cookie)|wp-post
pass_" ) {
              set $do_not_cache 1;
              }
              proxy_no_cache     $do_not_cache;
              proxy_cache_bypass $do_not_cache;
              proxy_cache czone;
              proxy_cache_key "$scheme://$host$request_uri$is_args$args$mobile";
              proxy_cache_valid  200 301 302 60m;
              proxy_cache_valid  404 5m;
              proxy_cache_use_stale  error timeout invalid_header updating http_500
http_502 http_503 http_504;
              proxy_pass http://backend;
              proxy_redirect http://www.example.com:8080/ /;
       }
}

server {
    listen      8080;
    server_name www.example.com;
    index index.html index.htm index.php;
    include siteconf.d/global_restrictions.conf;
    include siteconf.d/wordpress_base.conf;
    root            /home/yousan/public_html/www.examplecom.com;
}

shell> sudo ln -s /etc/nginx/sites-available/example.com.conf /etc/nginx/sites-enabled

このconfでは上のserverディレクティブで80番の待ち受け、リバースプロキシ側の待ち受けを設定します。この中で最後の方にあるproxy_redirectという構文で実際にPHP等が動いている8080で待ち受けをしているウェブサーバへ転送しています。
下の8080で待ち受けをしているところは先ほどwordpress用のconfを書きましたのでそれらをincludeして、あとは少し書いてやるだけでOKです。

ドメインごとのconfを書いたらlnでシンボリックリンクをsites-availableへ移してあげてください。こうすることで有効化されます。

最後にnginx本体のconfを書きます。
今回はリバースプロキシでレスポンスを早くしよう!というが目的ですのでリバースプロキシのキャッシュとgzip圧縮を有効化します。
ココでもリバースプロキシ用の設定を分けてあります。
機能別に分けられたconfはconf.dに入れると自動で読み込まれますのでそちらに入れておきます。自動に読まれますけれど*.confという名前じゃないとダメなので注意してください。

shell> cat /etc/nginx/conf.d/
proxy_cache_path  /var/cache/nginx levels=1:2 keys_zone=czone:4m max_size=50m inacti
ve=120m;
proxy_temp_path   /var/tmp/nginx;
proxy_cache_key   "$scheme://$host$request_uri";
proxy_set_header  Host               $host;
proxy_set_header  X-Real-IP          $remote_addr;
proxy_set_header  X-Forwarded-Host   $host;
proxy_set_header  X-Forwarded-Server $host;
proxy_set_header  X-Forwarded-For    $proxy_add_x_forwarded_for;
proxy_set_header  Remote-Addr        $remote_addr;
#real_ip_header X-Forwarded-For;
#real_ip_recursive on;

upstream backend {
             ip_hash;
             server 127.0.0.1:8080;
}

こちらが本体側のconfです。ほとんど書き換えてないのでコメントを外すだけですね。

shell> cat /etc/nginx/nginx.conf
... 省略 ...
       ##
        # Gzip Settings
        ##

        gzip on;
        gzip_disable "msie6";

        gzip_vary on;
        gzip_proxied any;
        gzip_comp_level 6;
        gzip_buffers 16 8k;
        gzip_http_version 1.1;
        gzip_types text/plain text/css application/json application/x-javascript tex
t/xml application/xml application/xml+rss text/javascript;

これでconfが整いました。
最後にnginxを再起動して動作確認です。

shell> sudo service nginx restart

さて、動いたでしょうか。

今回自分もすぐには動かず、いくつかはまった点がありましたので紹介しておきます。
1. 文末のセミコロン忘れ
2. www.example.comというドメインのまま、ドメイン名を書き換え忘れていた
nginxではほとんどの文末にセミコロンをつける習慣がありますので、これを忘れるとエラーになります。
ドメイン名の書き換えでは色んなサイトを参考にしながらconfを書いたのですが、confの途中にドメイン名がでていて、今回実際に運用するドメインに書き換えるのを忘れていて動きませんでした。

これらのエラーについてはerror.logを見る事でどのファイルのどこでエラーが起きている、ということが分かったりします。

shell> sudo tail /var/log/nginx/error.log

ただ中には直接的にエラーが表示されず、推測に推測を重ねてエラーを取り除く必要もありますがそちらについては…、経験が物をいいますね!

以上となります。

WordPressの検索で検索結果が表示されなくて困った

WordPressの検索をいじっている最中に検索結果が出なくて困った。

pre_get_posts (にフックした関数)をいじっていたんだけれど、全然思ったような結果にならずに結構困っていた。

原因は色々あったんだけれど、二箇所で困ったのでメモしておく。

検索結果がでない、ということはSQLに問題がある。

なのでSQLの中身を確認してなぜ出ないかを検討することに。

以下のダンプをpre_get_postではなく、利用したいテンプレート側に記載。

$wp_query->requestは実行されたSQL文なんだけど、これはpre_get_postsの後に実行されたSQL文なので該当するテンプレートに記載しないと読み出せない。


<span style="line-height: 1.5;">var_dump($wp_query->request);</span>

まず一つはpost_typeが’post’にしかなっていない点。これはpre_get_postsの関数内で


$query->set('post_type', 'post');

を記載すること。これが実は結構やっかいで、他の条件文、例えばtax_queryなどと組み合わす際には無くても何とかなったりするので、条件文を組み立てている最中のデバッグと完成したカタチではpost_typeの指定が必要か不要か、結構迷うことになる。

post_typeに何も指定されていない場合に実際に中身を決定づけているのはwp-includes/query.phpのget_posts内でpost_type = ‘any’として処理していき、状況に応じていろんなのが入る。例えばattachmentとかpostなど。

このget_postsは結構大きなメソッドでこれまた結構読みづらいんだけど、詰まるたんびに読んでいればそのうち読めるようになるかも。

さて次いで検索結果がでない状況に当たったんだけど、これはSQL内に 0 = 1 なんていう文が。例えば下記のような感じに。


string(250) "SELECT SQL_CALC_FOUND_ROWS wp_posts.ID FROM wp_posts WHERE 1=1 AND 0 = 1 AND wp_posts.post_type = 'custom_post' AND (wp_posts.post_status = 'publish' OR wp_posts.post_status = 'private') GROUP BY wp_posts.ID ORDER BY wp_posts.post_date DESC LIMIT 0, 5"

where句の二つ目にばっちり0 = 1の条件が。おかげさまで検索結果はゼロに。

wp-includes/query.phpのget_postsを丁寧に調べていくと下記の部分で付け加えられていた。


// Taxonomies
 if ( !$this->is_singular ) {
 $this->parse_tax_query( $q );

$clauses = $this->tax_query->get_sql( $wpdb->posts, 'ID' );

$join .= $clauses['join'];
 $where .= $clauses['where'];
 }

ここのtax_query->get_sql() の部分。

今回このpre_get_postsではtax_queryを使用していなかったのでどうやらそれが原因らしい。

結果としては今回対象となるページが http://www.example.com/search/ なんていう検索用のURLだったんだけど、searchを普通のpostのcategoryとして見做していたようで、get_posts内の$this->tax_queryという$qを元に作られたタクソノミクエリでは


object(WP_Tax_Query)#9 (2) {
 ["queries"]=>
 array(1) {
 [0]=>
 array(5) {
 ["taxonomy"]=>
 string(8) "category"
 ["terms"]=>
 array(1) {
 [0]=>
 string(6) "search"
 }
 ["include_children"]=>
 bool(true)
 ["field"]=>
 string(4) "slug"
 ["operator"]=>
 string(2) "IN"
 }
 }
 ["relation"]=>
 string(3) "AND"
}

という状態に。

$qや$parsed_query、$wp_queryを調べてみると’category_name’に’search’が設定されていた。

これはパーマリンク設定で記事名を/%category%/%postname%/のようにしていたことで、最初のスラッシュからスラッシュの間をカテゴリ名として認識していたから、のようだ。

なのでpre_get_postsの中で


$query->set('category_name', '');

としてやることで不必要なtax_queryを省くことができた。

WordPressのペジネーションで、アーカイブページの「次の一覧」「前の一覧」を出す方法

WordPressのペジネーションで、アーカイブページの「次の一覧」「前の一覧」を出す方法

WordPressのペジネーションについて、次のページ、前のページだとか、1,2,3,4だとか、記事がたくさんある物に対してのページング、ペジネーションをやる必要があった。

ペジネーションって結構いろんな解決策があって、そして求められることがあって、それをどうやって実現しようかな、と。

今回のケースでは5件ずつ表示の投稿タイプで、次の5件、前の5件と、前後だけへのリンクが欲しかった。

こういう場合には posts_nav_link で解決!

http://codex.wordpress.org/Template_Tags/posts_nav_link

 

ちなみにこのcodexにもあるとおり、この関数の使い方でちょっとしたテクニックがあったりする。

この関数ってこれ一つで「前のページ」「次のページ」の両方を出してくれるんだけど、その際にコードを書く人が変更できるのは、この「前のページ」「後ろのページ」という、リンクの中に入っている文言だけだったり。

となると出力されるのが


<a href="foobar"><span class="iWantToAddClass">前のページ</span></a><a href="hogepiyo"><span class="uCoudInsert">次のページ</span></a>

というのはできても


<span class="nobodyCanMakeIt"><a href="foobar">前のページ</a></span><span class="beGod"><a href="hogepiyo">次のページ</a></span>

こういうのはできない。つまりaタグの内側はいじれるのでaの中にspanは入るけれど、aの外側にspanは付けれない。
CSSで前、次のそれぞれに違うクラスを付けて色つけする、っていうのはできない。

そこでcodexにあるようなやり方だとうまくいく。

<pre><div>
<div><?php posts_nav_link('','','&laquo; Previous Entries') ?></div>
<div><?php posts_nav_link('','Next Entries &raquo;','') ?></div>
</div></pre>

すごいね!

WordPressのテーマ/テンプレート開発の時に役に立つコード

ダイエットが終わらない岡田洋一です、コンニチハ。

さて、最近ひょんなことからWordPressのテンプレートを修正する機会がありました。WordPressはで見た目をいじるときにはテーマ(テンプレート)ファイルを編集する必要があります。

WordPressでのページ表示には色々な仕組みがあるのですが、今回僕が編集することになったのは「カスタム投稿タイプのカスタムタクソノミーのアーカイブ」だったんです。この辺りについてはテンプレートヒエラルキーなんていう聞き慣れない仕組みが関わってきています。

さて、このテンプレートヒエラルキーというのは、簡単に言ってしまえば「テンプレートを適用する順序」を決めるモノなのです。「カスタムタクソノミーのアーカイブ」であれば「taxonomy-archive.php」とかになるのですが、これがなかなかうまく見つけられなかったりします。

さてこんな時、開発を行うにあたっては、現在どのテンプレートファイルを表示しているか、というのが分かると非常に助かります。

今回はこの「どのテンプレートファイルで表示しているか」を調べるためのコードを紹介します。

下記のコードを header.php の好きな位置に埋めて下さい。

<?php if(is_user_logged_in()) : ?></span>

<!-- <?php global $template; print_r($template); ?> -->
 <?php endif; ?>

Screen Shot 2013-10-01 at 4.48.45 PM

こうすることでHTMLのヘッダにコメントとして、「表示に使用しているテンプレート」が書き込まれます。

ちなみに if文で「ログインしている状態にのみ」に表示されるようになっていますので、知らない人には表示されません!ちょっと安心ですね。

Screen Shot 2013-10-01 at 4.55.28 PM

これでどのテンプレートを使っているか迷わずに開発ができますね!

リバースプロキシを使っているとWordPressで記事の参照がループしてしまう問題について

0. WordPressで記事の参照がループしてしまう問題について

WordPressで記事ページを開いたときに無限ループしてしまう。記事ページはいわゆるsingle.php シングルというページ。ブラウザの表示は

“このウェブページにはリダイレクト ループが含まれています” (chrome)
“ページの自動転送設定が正しくありません” (FireFox)

といった感じでループがおかしいという事を告げられる。
開発ツールで転送設定を見てみると、確かに同じアドレス宛に301の “Moved Permanently” が連続して返却されている。
このままでは利用者が入って来れないので調査をしてみた。

1

1. WordPressでのリダイレクト

WordPressで転送はwp-includes/canonical.phpというファイルが担当する。
このファイルはリクエストURLを見てカスタムパーマリンク設定などに照らし合わせて最適なURLへ誘導してくれる機能を持っている。

例えばパーマリンクを投稿名にしているとき、 http://www.example.com/?p=123 というURLへアクセスがあった場合、
http://www.example.com/test-post/ というパーマリンクへ301(こっちのURLが正しいですよ、というブラウザへの命令)などのコードで転送してやるのだ。

他にも http://www.example.com/test-pos/ なんていうのを http://www.example.com/test-post/ へ転送することも行っている。(推測です)

こういったことを担当しているcanonical.phpなんだけれど、その中身はちょっと複雑。主に301系の転送を担当してはいるものの、いろいろな処理が入り乱れている。

2. 問題点

そこでこの処理を超ナナメ読みして今回の問題点を洗い出すとこうだ。
以下に二つのURLがある。
パーマリンクを元に作成された、記事自体のパーマリンク。リダイレクトしたいURLを redirect_url と呼ぶ。
リダイレクトされる前の、実際に今アクセスされているURLを requested_url とする。
この二つが一致しないので延々とredurect_urlにリダイレクトさせようとしてしまっている。

canonical.phpにあるredirect_canonicalという関数。すでにかなーり縦長で読みにくい上、一度だけ自身を呼び出すプチ再帰になってるのでこれまたわかりにくい。中にデバッグ用のコードがコメントアウトされて配布されてるところをみても苦労が窺える。
canonical.phpにあるredirect_canonicalという関数。すでにかなーり縦長で読みにくい上、一度だけ自身を呼び出すプチ再帰になってるのでこれまたわかりにくい。
中にデバッグ用のコードがコメントアウトされて配布されてるところをみても苦労が窺える。

普通の情況であれば一度リダイレクトされればredirect_urlはrequested_urlとなるんだけれど、今回の問題は違った。
じつはこのrequested_urlは$_SERVERを元に作成される。サーバ環境変数。つまり見せかけ上のURL(ドメイン名)と実際にPHPが動いているドメイン名が違っていると無限ループする、というわけだ。
この見せかけ上のドメインとPHPが動いているサーバのドメイン名が違う、というのはリバースプロキシを使っていると起こってしまう。
リバースプロキシは大規模なウェブサイトになると負荷分散の為に用いられる手法だ。

今回このサイトではapacheのmod_proxyを利用した簡単なリバースプロキシを噛ましていた。
手前で動いていてWordPressに登録されているドメイン名はwww.example.comなのに、背後のウェブサーバではwww1.example.comという具合にドメイン名が違う。
なのでredirect_urlとrequested_urlが延々と違う、ということになってしまったのだ。

3. 解決方法

この問題を解決するにはいくつかの方法がある。
一つはcanonical.phpを書き換えてしまう方法。
requested_urlが$_SERVERから取得しているので該当部分を書き換えてしまえば良い。
ただしWordPressのコアファイルを書き換える、というのは禁断の果実を食べるに等しいのであまりオススメしない。

次にフィルタを書いてやる、という方法。redirect_url側をrequested_url側のドメイン名に揃えてやる、というやり方。これならば問題は起きにくいだろう。

// Note that you can use the "redirect_canonical" filter to cancel a canonical redirect for whatever reason by returning false
    $redirect_url = apply_filters('redirect_canonical', $redirect_url, $requested_url);

というわけでridirect_urlを書き換えるフィルタを書いてみました。これをfunctions.phpに加えておけばURLのドメイン部分がリバースプロキシの奥側のドメインに一致します。

add_action('redirect_canonical', 'change_requested_url');
function change_requested_url($redirect_url, $requested_url) {
 $redirect_url = is_ssl() ? 'https://' : 'http://';
 $redirect_url .= $_SERVER['HTTP_HOST'];
 $redirect_url .= $_SERVER['REQUEST_URI'];
 return $redirect_url;
}

今回のサイトでは複雑なクエリの組み立てなども行っていないのでこれで問題なさそうです。元のコードは先ほどのcanonical.phpから拝借してきました。

今回は見事、上記のフィルタ動作にてうまく動きました。めでたしめでたし。

WordPressでちょっとした項目をキャッシュしておく関数

WordPressでちょっとした項目をキャッシュしたい!というそんな要望からキャッシュ関数を作りました。

/**
* To cache data on wp_options.
* $callback function is call and store its data as serialized object.
* An optionname used in this function is  _periodic_cache_.$callback as defaul\
t.
*
* @return mixed */
function cache_($cache, $periodic = '1 hour', $param = array(),
$optionname = ''){
$optionname = $optionname
? $optionname
: '_periodic_cache_'.$callback;
$obj = get_option($optionname);
// revert $periodic to present time
if ($obj->modified < (time()*2 - strtotime($periodic)) ) {
$obj = new StdClass();
$obj->data = call_user_func_array($callback, $param);
$obj->modified = time();
update_option($optionname, $obj);
} else {
}
return $obj->data;
}

function c_($callback, $periodic='',$param = array(), $optionname=''){
return periodic_cache($callback, $periodic, $param, $optionname);
}

}

使い方は至って簡単。この関数の戻り値をキャッシュしたいな~、という時にこのキャッシュ関数を経由して呼び出せばOK。

例えばget_recent_posts()なんていう、最近の投稿一覧を取得する関数を作って、その値をキャッシュしたいのであれば


$posts = cach_('get_recent_posts', '20 hour', $param);

とすればおk。内部ではget_recent_posts()を$paramを引数として呼び出す。前回の呼び出しから20時間以内であればキャッシュを使用する。

これで簡単なものはキャッシュから読み出せるね。

削除の関数とかは作ってないよ。必要になったら作るかな~。

エックスサーバでwordpressのカスタムフィールドテンプレートが文字化けした

エックスサーバでwordpressのカスタムフィールドテンプレートが文字化けした。

エックスサーバでwordpressのカスタムフィールドテンプレートをいじる機会があった。カスタムフィールドテンプレートはわかりやすいマークダウン記法みたいな書き方でカスタムフィールドを色々と設定できる優れものなプラグイン。このプラグインなのだけれど開発環境から本番環境にアップロードすると使えなくなる現象に遭遇した。

具体的にはカスタムフィールドテンプレートでテンプレートをロードさせようとするとロードした文字が化けてしまっていること。
キャプチャ

これじゃあせっかくのテンプレートが使い物にならないです。
少し調べてみるとサーバからのレスポンスヘッダに文字コードが指定してある。


Content-Type: text/html; charset=Shift_JIS

これがどうにもこうにも原因らしい。

文字コードはすべてUTF-8で統一しているはずで、どこかにShift-JISなんて指定したか、とか思いながら確認してみた。

  • wordpressの管理画面にある文字コード
  • php.iniのmbstring.internal_encoding
  • プラグイン本体のコード

疑わしきは調べていったんだけれど解決できず。

で、テスト環境ではうまくいっているのだろうからこれはやっぱり本番環境のエックスサーバがおかしいのだろうということでさらに疑ってみた。php.iniが編集できる柔軟さは認めるのだけれど、Shift-JISを吐かれては困る。

色々調べたところapacheが.phpに対してheaderをデフォルトで吐き出す設定があることにたどり着いた。同時に湯バード先生からも同じトコロを指摘される。

通常この項目はapacheのhttpd.conf辺りで設定するのだけれど.htacessでも設定できるらしい。というわけで以下の項目をwordpressのドキュメントルートにある.htacess追記してみた。


AddDefaultCharset UTF-8

この設定で無事に治りました。

注意すべきは.htaccessはworpdressのカスタムURLストラクチャとかを書き換えることがあるのでその際には再度同じ文を書き加える必要があること、かな。忘れないようにしよう。

またこのような問題だとなかなか対処しづらいと思う。たまたまapacheのレスポンスヘッダから分かったのだけれど文字化けしたときにはmbstringを見て下さい、ってエックスサーバに書いてあったりして困る。これ恐らくデフォルトの文字コードをShift-JISで、とかに設定されたのかなと推測するけれどそれが困りました。たぶん問い合わせても対応できかねます、で終わっちゃうだろうし。しょんぼりっすね。

キャッシュを使ってwordpressを軽くしてみる

0. はじめに

冬真っ盛りですね、寒いですね。先日の成人式は東京も久方ぶりの大雪で混乱しましたが、降れば降ったでなんだか嬉しい気持ちになってしまいますね。

さて、ここ最近私の運営しているwordpressのサイトがひどく大変なことになっています。wordpressなサイトはいくつか複数運営しているのですが、今年に入って立ち上げたサイトも順調にアクセス数が増え、ついには限界点に近くなってきました。ウェブサイトはウェブサーバというサーバが提供してくれているのですがこのサーバが悲鳴を上げている感じです。CPUの使用率は100%に張り付き、ロードアベレージという負荷を表す数値も三桁突入でてんやわんやです。ちなみにロードアベレージは一桁(コア数以下)が正常値です。インフラ屋(サーバ屋)さんとしては辛い状況でもあり楽しい状況でもあるので感嘆を漏らすところですね。

さて、なんにしてもこのままではまずいです。実際に夜になると重くて重くてウェブサイトが見れない状態です。このままではアクセス数にも間違いなく影響があります。そんななかでまずは手っ取り早く、簡単な手順で負荷を軽減する術について書いてみたいと思います。

 

さてところでウェブサーバというのはどんな動きをしてるんでしょうか。
ウェブサイトを見たいというユーザのブラウザからのリクエストがあった場合、適切な返答をレスポンスとして返してあげる、ウェブサーバとはそんな動きをしているんですね。
ここでのレスポンスは通常HTMLが返答されます。ユーザのブラウザはそのHTMLを表示してあげてるんですね。それでウェブサイトを閲覧することになります。
HTMLをレスポンスとして返答する、のですがwordpressのようなPHPを使ったプログラムの場合にはまずプログラムを解釈し実行する必要があります。プログラムの実行、これはサーバに負荷を掛けます。ですのでウェブサーバを軽くしてあげるにはこのプログラムの処理をいかに軽くするか、またいかに減らすかというのがキモになってきます。
今回はこの処理を減らす方向で進めていきます。

プログラムを実行せずにレスポンスのみを返答するためにはキャッシュ機能が用いられます。いろいろなところで目にするキャッシュです。キャッシュを使えばプログラムが実行されること無く、事前に用意されて置いたキャッシュを返答することであたかもプログラムが動いたかのように見えるんですね。おかげでウェブサーバはPHPの実行が減るので軽くなります。
キャッシュはとても便利なのですがデメリットもあります。それは即時性に欠けることです。キャッシュは事前に用意しておくものですのでリアルタイムに変動する無いように対しては使うことができません。これは注意が必要ですね。

今回はトップページのみをキャッシュすることにしました。google analyticsやapachetopなどでまずは現状のアクセス状態を解析してみました。サイト全体のアクセスに対してトップページだけで7%ほどのアクセスがありました。これをキャッシュを使って表示させようというわけです。7%分のプログラム実行が減るわけですから、7%多くの他のプログラムを実行する余地が生まれるのですね。7%が多いか少ないかという判断については難しいですが、1%未満ではないので今回はキャッシュさせることにしました。

キャッシュの方法は至って簡単です。一時間おきなど決められたサイクルにトップページを取得しそれを置いておくだけです。wordpressの場合ですとhttp://www.example.com/index.phpへアクセスしてそれを置いておけば良いです。
外部からアクセスされる場合、最初のスラッシュ以降は空欄で来る場合が多いです。先ほどの例ですとhttp://www.example.com/でアクセスされることが多いのですね。この場合ウェブサーバはhttp://www.example.com/index.htmlを読んで返答します。(実際にはサーバの設定によります)。index.htmlはphpじゃないのでプログラムは実行されずにHTMLを返答するだけなんですね。なのでとても軽いです。CPU負荷はかけません。キャッシュ万々歳ですね。

さて、実際のやり方です。
ウェブサーバにログインしてcrontabを編集します。crontabに以下のようなコマンドを登録します

0       *       *       *       *       yousan  /usr/local/bin/wget -O /home/yousan/public_html/www.example.com/index.html http://www.example.com/index.php

これで毎時0分にキャッシュを生成してくれます。wgetのパスなどが違う場合があります。その際にはwhich wgetなどしてパスを確認してください。wgetは-Oオプションで取得したファイルの置く位置を決めることができます。これでキャッシュをドキュメントルートの直下におけます。
これできっと軽くなるはずです。

最後にキャッシュがちゃんと効いているか確認してみます。themesにあるindex.phpへテストコードを埋め込んでキャッシュが効いているか確認します。


<!-- time <?php echo time(); ?> -->

上記のコードはヘッダーより下の安全なところに埋め込みます。phpで現在時刻を秒数で出力しているのですね。キャッシュが無い状態でアクセスすると上のコメント部分の時刻がアクセス毎に変動します。が、キャッシュを生成すると時刻がキャッシュ生成時の時刻になります。終わったら消しておきましょう。

最後に

wordpressは便利です。でも残念ながら重いです。月間で100万PVを越える辺りから限界に近くなります。ノーマルwordpressだとまず最初に直面する上限値かもしれません。wordpressにはいろいろなキャッシュプラグインがあるのでそういったプラグイン、またテクニックを駆使してアクセスを伸ばしてやる必要があります。プラグインにも公式が配布しているキャッシュシステムにも非常に有用なものがあるのですが今回は簡単なキャッシュをまずは取り入れてみました。よかったら使ってみて下さい。

wordpressでリモートにある環境にデータベースを転送したい

はめじに

wordpressでリモートにある環境にデータベースを転送したい。転送というか同期というか。

通常の環境であれば管理者メニューからエクスポート > インポートとすればすむ話なのだけれど、今回はちょっと変わった事情があった。

その条件ってのは下記の通り

  • 手元のサーバにあるワードプレスのデータベースの一部(コメントとかは上書きしないようにする)をリモートサーバに転送したい
  • リモートサーバにはsshでログイン可能
  • テータベースの一部のうち、post_metaにあるmeta_keyがviewsは上書きしたくない
  • 転送するのは一日に一回ぐらいのそこそこのペースで転送したい

リモートホストにsshでログインできるのでsshとパイプを使ったワンライナーでやってしまおう的な。

またpost_metaのテーブルはちょっとやっかい。今回利用しているプラグインでWP-PostViewsという閲覧数を保管するプラグインがある。これは閲覧数をpost_metaにmeta_key = viewsで管理している。だからそこをハズしたい。

キャプチャ4

というわけでさっくり


mysqldump –h localdbhost –u localuser –plocaldbpassword  --add-drop-table wpdbname wp_options wp_posts wp_postmeta wp_term_relationships wp_term_taxonomy wp_terms wp_users wp_usermeta | ssh remotesshuser@remotehost.example.com mysql -f -h remotedbhost -u remotedbuser –premotedbpassword  remotewpdb

これでひとまずpost_meta以外はいけた。

データがちょっと複雑なpost_meta

post_metaに関してはmysqldumpにwhere句が使えるらしいので使ってみる。

次にpost_metaをうまく通してやるためにオプションを調整してやる。


mysqldump –h localdbhost –u localuser –plocaldbpassword   --add-drop-table=false --extended-insert=false --where="meta_key!='views'" mysqldump –h localdbhost –u localuser –plocaldbpassword wp_postmeta  |  ssh remotesshuser@remotehost.example.com mysql -f -h remotedbhost -u remotedbuser –premotedbpassword remotewpdb

キャプチャ

--add-drop-table=false

これは対象テーブルのdrop table – if exists文を出力に含めないようにするためのオプション。

基本的にはテーブルがあるっていう前提なのでcreate-infoごといらないかもしれない。(create文ごとなくすには—no-create-infoを付けるといい)

今回は安全に動作(運用)するために付けておくことにする。ここで与えたオプションはlong形式の—add-drop-tableなんだけど、実は引数でtrue falseが与えられるらしい。よくありがちなのは—no-add-drop-tableみたいに対応してる気がしてたんだけどそうじゃないみたい。

公式には書いてないので気がつかなかった。っていうか引数ありって本当意外。

  • --add-drop-table

DROP TABLEステートメントをCREATE TABLEステートメントの前に追加します。
MySQL ::   MySQL 5.1 リファレンスマニュアル :: 7.12 mysqldump — データベースバックアッププログラム

--where=”meta_key!=’views'”

これはmeta_keyで上書きしたくない条件を指定。今回は手元のデータのうち閲覧数は上書きしたくなかった。sqlだったらすんなりwhere句書いちゃえば楽なのになーって思ったらmysqldumpでもwhereが書けるらしい。

  • --where='where_condition', -w 'where_condition'

あるWHERE状態に選択された行のみダンプします。ユーザのコマンドインタープリタにとって特別なキャラクタ、もしくはスペースを含んでいる場合、状態の周りをクオートで囲まなければいけません。

MySQL :: MySQL 5.1 リファレンスマニュアル :: 7.12 mysqldump — データベースバックアッププログラム

–extended-insert=false

キャプチャ2

このオプションも引数を取るらしい。このオプションは

  • --extended-insert, -e

複数のVALUESリストを含む、複数行INSERT構文を使用してください。これにより、ダンプファイルサイズを小さくし、ファイルが再ロードされる際の挿入スピードがあがります。

MySQL :: MySQL 5.1 リファレンスマニュアル :: 7.12 mysqldump — データベースバックアッププログラム

となっている。いわゆるbulk insertとかmultiple-row insertsとか言われてるあれにするようなやつですね。


insert hoge (id, hoge) values (1, hoge), (2, hoge1), (3, hoge), (4, hoge);

で、コレって結構早くする上では重要なものなのです。このオプションがonでないと、bulk insertが有効になっていないと、かなり遅くなります。なのですが後述するmysql -fと組み合わさって今回の運用上はoffにしてないとうまく目的を達成できません。

今回の運用上、というところはどういったところなのか。

さっきの例だとid, hogeという二つのカラムに対して四つの行を一つのinsert文でinsertしようとしてる。で、カラムの名前から分かるように、idというのはpkeyなのでunique制約がある。となるとき、上のinsertだとduplicateでinsertできないことがある。もともとid=1な行があるときとか。で、そういうのは無視しながら必要な行だけをinsertしたいので、これらのinsert文を分けて書く必要がある。なのでこのオプションを有効にしてやる。するとこんな感じのinsert文になる。


insert hoge (id, hoge) values (1, hoge);

insert hoge (id, hoge) values (2, hoge1);

insert hoge (id, hoge) values  (3, hoge);

insert hoge (id, hoge) values (4, hoge);

これでid=1は通らなくても他のinsert文は通ってくれるので期待通りの結果になるかな!

mysql –f

受け側のオプション。エラーがあっても処理を続行する。先のcreate文ですでにtableが存在しても続けたり、insert文でduplicateな行だったとしても続けてくれる。おかげさまでイケるようになります。

おわりに

これでcronに入れとけばテキトウに同期してくれそう。sshでパスワードのプロンプトをなくすために公開鍵をやりとりする、とかやる必要はあるけれど。

でもこれできっと便利。しらばらくは手でやってみるけど。

wordpressのsingle.phpとかでquery_postsを自前で実行した後に元のpostを参照したかった

wordpressのsingle.phpとかでquery_postsを自前で実行した後に元のpostを参照したかった。

どういうことかというと、single.phpの中で以下のような順序で上から順にpost(ID=123)にアクセスしてた。

  1. post ID=123のタイトル
  2. post ID=123の内容
  3. 関連記事の表示のため、query_posts(array(‘post__in’)); を実行
  4. while(have_posts()) the_post(); をして関連記事のタイトルを表示
  5. post ID=123のコメントを表示したい!! <– ココ重要

最後のコメントを表示しようとするとうまくいかない。というのもその前段階の関連記事表示でthe_postをしてしまっているのでグローバル変数の$postが関連記事のものになっている。なのでコメントを表示しようとしたときに利用されるpostは関連記事の一番最後のpost、while(have_posts())が回りきった最後のpostとなってしまうのだ。

試しに echo $post->ID; すると確かにIDは123でなく関連記事の最後のpost_IDになっている。

さて困ったぞ、ということでググってたらコレを解決してくれるような関数を発見。

Function Reference/wp reset postdata » WordPress Codex

以前は$post->IDを変数に保存しておいてquery_postをかまして・・・、ってやってたんだけど、このwp_reset_postdataを使えばその手間が省ける・・・、かどうかは微妙かもしれない。結局同じような手立てに。

でも標準で用意されてるってことは今後に期待もしていいってことで利用してみる。

仕組みは単純でその使用方法で示唆されている。使い方はグローバルの$wp_queryを一時待避させておいて必要になったら戻す,というもの。


$original_query = $wp_query;

 query_posts(‘hogehoge’);

$wp_query = $original_query;

 wp_reset_postdata();

the_title(); comment_template();

コレでうまくいくようになった。