iPhoneの過去のGPSデータを表示するiPhoneTracker

本日、TechCrunchに以下のような記事が掲載されソーシャルメディア上で話題になりました。

http://jp.techcrunch.com/archives/20110420surprise-your-iphone-is-tracking-your-every-move/

TechCrunch によるとiOS4からiPhone内にGPS情報が恒久的に保存されており、幸いAppleに送信されているわけではないが、ちょっと気味が悪い。とのことです。

この記事をツイートしたところ、親しいフォロワーさんから。

こんなものが、、、「iPhoneTracker」http://petewarden.github.com/iPhoneTracker/ QT: @miya0001: Phoneには過去の位置情報が逐一記録されていることが判明” http://bit.ly/g11iow

iPhoneTrackerってなんぞや?

早速試す。

iPhoneTrackerとはGitHub上で公開されているオープンソースのプロジェクトで成果物がMac用のアプリとしてダウンロード出来るようになっています。

そこで、早速試してみました。

ヒエー気持ち悪い。

操作性が悪くて、アプリとしてはいまいちですが、見事に私の行動履歴を見ることができました。

アプリ下部にあるスクロールバーを操作すると日付を絞り込んでGPSデータを閲覧できます。

最も古い日付は2010年の6月17日。ぐぐってみたらiOS4の公開は6月21日?

タイミング的には、よくわからないですが、キモイということで、インストールはオウンリスクでお願いします。

http://petewarden.github.com/iPhoneTracker/

 

mysql_upgrade を実行する

昨日MySQLをアップグレードしたんですが、いろいろすったもんだした挙句、無事に動いていると思っていました。

しかし、ログをよく確認すると以下のようなログが。。。

110420 21:20:14 [ERROR] Column count of mysql.
proc is wrong. Expected 20, found 16.
Created with MySQL 50044, now running 50511.
Please use mysql_upgrade to fix this error.

どうやら問題があるので mysql_upgrade をしてね。ということらしい。

mysql_upgrade とは?

MySQLのリファレンスに以下のようなページがありました。

MySQL のアップグレードでは、その度に、mysql_upgrade を実行します。これにより、データベース内のテーブルにおける最新の MySQL Server との互換性をチェックすることができます。該当テーブルが非互換の場合は、チェックの対象になり、問題があれば、そのテーブルを修正します。mysql_upgrade コマンドは、システム テーブルのアップグレードも行うため、新たな権限と追加機能を使用できるようになります。

MySQLをアップグレードしたら、mysql_upgradeをしてね。だそうです。知らんかった。

mysql_upgrade のやり方

mysqld が起動している状態で以下のコマンドを実行する。

mysql_upgrade -u root -p

-p はパスワードプロンプトを出してねという意味なので、環境に応じて。

というわけで、エラーログもでなくなりました。

MySQL5.5.3にアップグレードしたら再起動に失敗した。

本日、yumでアップデートを行ったところMySQLが起動しなくなって、そりゃーもうあせりました。

  • OSはCentOS5.x。
  • アップデート後のMySQLのバージョンは、mysql-server-5.5.11-1.el5

エラーログ

すぐにログを確認したところ、以下ようなログが残っていました。

/var/log/mysqld.log

[ERROR] /usr/libexec/mysqld: unknown variable 'default-character-set=utf8'

取り急ぎ/etc/my.cnfから該当する行をコメントアウトして再起動したら無事に復活。

原因

いつものとおりGoogle先生に聞いたら以下のような記述がありました。

[url2link url="http://www.mysql.gr.jp/frame/modules/news/article.php?storyid=175" summary="- いくつかの非推奨だった変数やコマンド、オプションなどが廃止になっています。特に TYPE を廃止して今後は ENGINE を使用することや、サーバの default-character-set オプションを廃止して今後は character-set-server を使用すること(クライアントの default-character-set はそのまま)など影響を受ける方も多いでしょう。"]

というわけで、[mysqld] ディテクティブの default-char-set を character-set-server に変更したら無事に再起動ができました。

ただし、[client] ディレクティブの default-charset は従来通りなので間違えないようにしましょう。

その他の廃止項目

他にも廃止となったり過去のバージョンですでに非推奨となっている項目があるようです。

アップデートする予定がなくても、今のうちに書き換えておいたほうがベターなようです。

http://d.hatena.ne.jp/sakaik/20100414/mysql533obsol

facebookアプリ ”Tokyoでんき予報”

初のfacebookアプリを作りました。

Tokyoでんき予報

このアプリは東京電力の電力需要のデータを表示するアプリです。

いいねボタンをクリックしてもらえば、最新の需要データを受け取ることができます。

今後の予定

  • スマートフォンへの対応
  • ユーザー認証してサイドバーに常駐できるように
  • サーバー負荷の低減

とりあえず、facebookアプリ開発のいい練習になってくれました。

WordPressのソースを検索するのに便利なコマンド

今日の記事は、WordPress開発で便利なコマンドの話です。

WordPressのプラグインを開発していると、フックを探したり特定の関数のソースを見る必要があったり、定数を確認したかったり、いろんな場面でWordPressのソースを検索する必要があります。

以下はLinuxやMacのターミナルでそれらを検索する方法です。

WordPressはコーディング基準が定められているので、こういう時には超便利です。

コマンドで複数ファイルの検索を行うには?

私は、find と xargs、grep という3つのコマンドを組み合わせて検索しています。

find /path/to/wordpress -name "*.php" | xargs grep "foo"

上記のコマンドでは、/path/to/wordpress ディレクトリ内(サブディレクトリも含む)の全ての.phpファイルから、ファイル内に”foo”が含まれるファイルを検索しています。

WordPressの全てのアクションフックを検索する。

あらかじめWordPressのディレクトリに移動してから以下のコマンドを入力してエンター。

find . -name "*.php" | xargs grep "do_action("

WordPressの全てのフィルターフックを検索する。

find . -name "*.php" | xargs grep "apply_filters("

WordPressで定義されている全ての定数を検索する。

find . -name "*.php" | xargs grep "define("

WordPressの特定の関数のソースを探す

たとえば、get_option() のソースがどこにあるのかを知りたければ、以下のようなコマンドで。

find . -name "*.php" | xargs grep "function get_option"

具体的に開発中にどう使うの?

たとえば、コンテンツthe_content()の中身を修正するプラグインを作成するには、the_content()のソースがどのファイルにあるのかを探します。

find . -name "*.php" | xargs grep "function the_content"

上記のコマンドを実行すると wp-includes/post-template.php というファイルにあるのがわかります。

で、実際にソースを見ると、the_content というフィルターフックがあるのがわかるので、そのフックを使用して書き換えるといった感じです。

実際には、対象の関数からさらに別の関数がコールされていたりするので、何回もこのコマンドを打つ感じになりますが。
(お役所のたらいまわしに似てるかも。)

WordPressでPubSubHubbub(パブサブハバブ)

パブサブハブブブ?どう読むねん? "PubSubHubbub でリアルタイムブログ更新通知を | Lifehacking.jp" http://bit.ly/gHKqY8
@miya0001 パブサブハバブ だよ

読み方は分かったんですが、いまいち仕組みがわからないので、ちょっと試してみることに。

WordPressでPubSubHubbubに対応するには、以下の二つの方法があるそうです。

このサイトは以前からFeedburnerを使っているので、実際にこの記事を一度公開してテストしてみました。

結果

今まで気にしてなかったんですが、Feedburnerでは記事の投稿からFeedburner側のRSSへの反映にある程度タイムラグがあるようです。

いくらRSS側がプッシュでもこれでは、いけてないですね。

というわけで、今回は読み方がわかったというだけで、後日再調査します。

WordPressで記事が多くなると遅くなるサイドバーウィジェット

前回の記事でWordPressには記事件数の増加に伴い重くなるSQLがわかりました。

http://firegoby.theta.ne.jp/archives/2159

しかしMySQLは非常に高速なので、遅いSQLが一回ぐらい入ったからと言って、特に遅いと感じることはありませんでした。

じゃあ、遅いSQLが何回も入ったらどうなるの?ということで、今回はサイドバーウィジェットのSQLを一つずつ検証します。

いきなり結論

遅いサイドバーウィジェットランキングは以下のとおりです。

環境は前回の記事と同じです。(ダミーの記事を約24,000件投稿しています。)

秒数はSQLの実行時間ですが、環境によってかわるので比較用の目安程度で考えてください。
また、複数のSQLが発行されるサイドバーウィジェットでは、合計値を表示しています。

順位 ウィジェット名 秒数
1位 最近の投稿 約0.49秒
2位 カレンダー 約0.31秒
3位 アーカイブ 約0.02秒

注意: カレンダーが遅いのはテスト方法に問題があるためと思われますので、以下を読んでください。

最近の投稿はなぜ遅いか?

前回の記事でも説明しましたが、WordPress上では記事を最新順にソートするSQLは、インデックスを活用できていないために、実際に必要なデータの件数がわずかでも全てのレコードをスキャンしています。

これは、カテゴリーアーカイブなどのようにスキャン対象が別のwhere句で絞りこまれていれば、少しはましになりますが、トップページや「最近の投稿」など「カテゴリーに関係なくとにかく最新の記事をよこせ」といったSQLでは、記事の件数が増えるに従って確実に遅くなっていきます。

カレンダー

今回のテストでカレンダーが遅かったのは、テスト環境の構築によるものだと思います。

というのは、24,000件の投稿データはループ処理でまとめて投稿しましたので、すべての記事が同じ日付で投稿されています。

したがって、通常の1日数件程度の投稿であれば1/1000秒台のスピードで処理できると思いますが、もし何らかの方法で大量の記事を投稿する場合には、パフォーマンスが問題になると思います。

アーカイブ

アーカイブは、投稿月ごとに記事をグループ分けするSQLが記述されており、全てのレコードをスキャンしていました。

そのため、他のウィジェットと比べると、遅い部類に入るサイドバーウィジェットです。

その他のサイドバーウィジェット

カテゴリーやタグクラウドのサイドバーウィジェットはこれらについで遅いSQLでしたが、0.002秒程度のものでした。
ただし、この二つはUsing filesortUsing Temporaryが出てますのでカテゴリー(タグ)が増加すると遅くなる可能があります。

これら以外のサイドバーウィジェットは、さらに一桁下がって0.0002秒とかそんなオーダーでした。
SQLだけを見ればパフォーマンスには影響ないと言えます。

最後に

サイドバーウィジェットが実行するSQLをダンプしたデータを置いておきます。

配列内には、関数の呼び出し元や、SQLのベンチマークの結果などが含まれます。

ダンプデータ

WordPressは記事の件数が多くなると重くなるのか?

数日前にtwitter上で話題になっていたので、SQLを解析して検証してみました。

方法

WordPressをロードする際に実行されるSQL文を全てダンプして、explainでインデックスの使用状況を解析した。

条件は以下のとおり

  • テーマはtwentytenを使用した。
  • プラグインは全て削除
  • 投稿は24,071件で、全て同じ本文のダミーテキストを使用した。

ちなみに本文は以下のとおり。

召使をさなかっ事も無論前でどうしてもですでしです。いかに大森さんに相当社会多少存在がなろでし必竟その根それか観念をとしてご矛盾ますですありでしから、そのほかもあなたか国家豪商を思いて、三宅さんののを詩のそれがとうていごお尋ねと売っがあなた人情からご蹂躙がするように初めてご横着が云ったませて、何でもかでもやはり相違から作るありてみましのをなっなくた。したがってすなわち大大名が起る気はこれから妙としなて、その心持へも曲げだてというmanを聴いてなりずた。

SQL文の抽出方法

  • wp-config.php に define(‘SAVEQUERIES’, true) を設定。
  • WordPress の shutdown アクションフックで $wpdb->queries 内の SQL を explain するプラグインを作成した。

プラグインのソースは以下のとおり

<?php
/*
Plugin name: Explain MySQL
*/

add_action('shutdown', 'explain');

function explain() {
    global $wpdb;
    $res = array();
    foreach ($wpdb->queries as $q) {
        $sql = 'explain extended '.$q[0];
        $res[$q[0]] = $wpdb->get_row($sql);
        $res[$q[0]]->time = $q[1];
    }

    print '<pre>';
    print_r($res);
    print '</pre>';
}

?>

先に結論

一部のSQLでインデックスが適切に使用されていないため、記事の増加に伴いパフォーマンスが低下するようです。

また、以下の機能では上述のインデックスに関係なく記事が増えるとパフォーマンスが低下します。

  • ”アーカイブ” サイドバーウィジェット
  • 検索機能

サイドバーウィジェットについては、カテゴリーやカレンダーも遅くなる傾向がありますが、ごくわずかでした。

4/13追記: 最新の記事など他にも激遅なSQLがありました。これらについては詳細を確認しますので後日あらためて。

問題のSQL

記事を最新順に取得する以下のSQLでインデックスが働いていないため、全てのレコードをスキャンしてしまっており、記事が増えるたびにパフォーマンスが低下しています。

SELECT SQL_CALC_FOUND_ROWS wp_posts.* FROM wp_posts
WHERE 1=1 AND wp_posts.post_type = 'post'
AND (wp_posts.post_status = 'publish' OR wp_posts.post_status = 'private')
ORDER BY wp_posts.post_date DESC LIMIT 0, 10

問題その1 インデックスがおかしい

order by 句で使用されている post_date は type_status_date というインデックスが設定されていますが、post_type 及び post_status とセットで設定されているため、意味が無い状態になっています。

試しにexplainの結果を以下のとおり。

mysql> explain select * from wp_posts order by post_date limit 0,10;
+----+-------------+----------+------+---------------+------+---------+------+-------+----------------+
| id | select_type | table    | type | possible_keys | key  | key_len | ref  | rows  | Extra          |
+----+-------------+----------+------+---------------+------+---------+------+-------+----------------+
|  1 | SIMPLE      | wp_posts | ALL  | NULL          | NULL | NULL    | NULL | 24071 | Using filesort |
+----+-------------+----------+------+---------------+------+---------+------+-------+----------------+

上記のSQLの実行結果では、Using filesort と表示されており、さらにrowsの値が24,071行となっている。

これは必要な結果行数が10行であるにもかかわらず 、24,071行(wp_posts内のすべてのレコード数)を参照しているということで、これがパフォーマンス悪化の原因になっています。

ためしにインデックスを設定後、同じようにexplainしてみます。

mysql> create index test_index on wp_posts(post_date);
Query OK, 24071 rows affected (1.02 sec)Records: 24071  Duplicates: 0  Warnings: 0

mysql> explain select * from wp_posts order by post_date limit 0,10;
+----+-------------+----------+-------+---------------+------------+---------+------+------+-------+
| id | select_type | table    | type  | possible_keys | key        | key_len | ref  | rows | Extra |
+----+-------------+----------+-------+---------------+------------+---------+------+------+-------+
|  1 | SIMPLE      | wp_posts | index | NULL          | test_index | 8       | NULL |   10 |       |
+----+-------------+----------+-------+---------------+------------+---------+------+------+-------+

Using file_sort が消えてrowsの値が10行となり、SQLの実行時間が0.03秒から0.00秒に短縮されました。

問題その2 SQL_CALC_FOUND_ROWS が何かおかしい。

これはWordPressの問題というよりMySQLの問題とも言えますが、なにか一定の条件を満たすとSQL_CALC_FOUND_ROWSを使用するとインデックスが働かないようです。

SQL_CALC_FOUND_ROWS ありの場合

mysql> explain SELECT SQL_CALC_FOUND_ROWS wp_posts.* FROM wp_posts WHERE 1=1 AND wp_posts.post_type = 'post' AND (wp_posts.post_status = 'publish' OR wp_posts.post_status = 'private') ORDER BY wp_posts.post_date DESC LIMIT 0, 10;
+----+-------------+----------+------+------------------+------------------+---------+-------+-------+-----------------------------+
| id | select_type | table    | type | possible_keys    | key              | key_len | ref   | rows  | Extra                       |
+----+-------------+----------+------+------------------+------------------+---------+-------+-------+-----------------------------+
|  1 | SIMPLE      | wp_posts | ref  | type_status_date | type_status_date | 62      | const | 24066 | Using where; Using filesort |
+----+-------------+----------+------+------------------+------------------+---------+-------+-------+-----------------------------+

SQL_CAL_FOUND_ROWS 無しの場合

mysql> explain SELECT wp_posts.* FROM wp_posts WHERE 1=1 AND wp_posts.post_type = 'post' AND (wp_posts.post_status = 'publish' OR wp_posts.post_status = 'private') ORDER BY wp_posts.post_date DESC LIMIT 0, 10;
+----+-------------+----------+-------+------------------+------------+---------+------+------+-------------+
| id | select_type | table    | type  | possible_keys    | key        | key_len | ref  | rows | Extra       |
+----+-------------+----------+-------+------------------+------------+---------+------+------+-------------+
|  1 | SIMPLE      | wp_posts | index | type_status_date | test_index | 8       | NULL |   10 | Using where |
+----+-------------+----------+-------+------------------+------------+---------+------+------+-------------+

SQLの実行時間もありとなしでは大きな差があります。

  • SQL_CALC_FOUND_ROWS あり – 0.30秒
  • SQL_CALC_FOUND_ROWS なし – 0.00秒

ちなみに SQL_CALC_FOUND_ROWS は、この直後に FOUND_ROWS() を実行すると limit なしの count(*) と同じ効果が得られます。

ためしに以下のように count() で置き換えたSQLを実行してみました。

mysql> SELECT count(*) FROM wp_posts WHERE 1=1 AND wp_posts.post_type = 'post' AND (wp_posts.post_status = 'publish' OR wp_posts.post_status = 'private') ORDER BY wp_posts.post_date DESC;
+----------+
| count(*) |
+----------+
|    24066 |
+----------+
1 row in set (0.02 sec)

こっちのほうが早いじゃん。。。

SQL_CALC_FOUND_ROWS のほうが早いと説明されていることが多いので、この結果は不思議なんですが、複数のサーバーのWordPressサイトで試してみましたが、おおむねこのような結果になりました。

というわけで、みなさんの環境ではどうなのかぜひ知りたいところです。

その他のパフォーマンス低下の原因となりうる要因

  • wp_options テーブルの結果はlimit なしでまとめて取得されます。多くのプラグインやテーマでは設定内容をwp_optionsに保存しており、記事の増減よりも大きな影響をあたえるようなのでプラグインのインストールは控えめにしましょう。
  • また、多くのプラグインは、アンインストールしてもwp_optionsに保存したデータは削除しません。(Windowsのレジストリが肥大化しちゃうイメージです。)
  • ページ(post_typeがpage)が大量にあると同じくパフォーマンスに悪影響があります。これはメニューを生成する際にまとめてデータベースから取得するためです。

まとめ

  • WordPress 3.1 では、記事が増えるとパフォーマンスが低下する要因となるSQLがあります。
  • SQL_CALC_FOUND_ROWS はなんか信用できない。
  • それでもMySQLがとても高速なので、2万件ぐらいのデータなら快適に表示される。
  • ただし、プラグインやテーマがスカタンなSQLを使ってたら、怪しくなるからSQL文をチェックしたほうがいいかもしれない。

もし、私の認識に間違いがあるようでしたらツッコミをお願いします。

できればやさしく。^^

4月13日追記

本家のチケットに報告されているようです。PriorityはHighになっていますが、Future Releaseなのでしばらく改善されないかも。

http://core.trac.wordpress.org/ticket/7415

WordPressにカスタムなページ

@miya0001 /user/soraiy と入れると Twitter から情報を取得するようなことが今後したいと思っています。author.php では WordPress に存在しないユーザーでは404が帰ってしまいますよね?

以下のような感じだと思います。

ステップ1

独自のURLのページを作成&URLの一部をパラメータとして取得する。

<?php
/*
Plugin Name: Custom User Tweets
*/

new AddCustomRules(
    'user/([a-zA-Z0-9]+)/?$',
    'twitter_uid',
    'callback_function'
);

function callback_function()
{
  echo get_query_var('twitter_uid');
  exit;
}

class AddCustomRules{

    private $rule     = null;
    private $query    = null;
    private $callback = null;

    function __construct($rule, $query, $callback){
        $this->rule     = $rule;
        $this->query    = $query;
        $this->callback = $callback;
        add_filter('query_vars', array(&$this, 'query_vars'));
        add_filter('rewrite_rules_array', array(&$this, 'rewrite_rules_array'));
        add_action('init', array(&$this, 'init'));
        add_action('wp', array(&$this, 'wp'));
    }

    public function init()
    {
        global $wp_rewrite;
        $rules = $wp_rewrite->wp_rewrite_rules();
        if (!isset($rules[$this->rule])) {
            $wp_rewrite->flush_rules();
        }
    }

    public function rewrite_rules_array($rules)
    {
        global $wp_rewrite;
        $new_rules[$this->rule] = $wp_rewrite->index . '?'.$this->query.'=$matches[1]';
        $rules = array_merge($new_rules, $rules);
        return $rules;
    }

    public function query_vars($vars)
    {
        $vars[] = $this->query;
        return $vars;
    }

    public function wp()
    {
        if (get_query_var($this->query)) {
            call_user_func($this->callback);
        }
    }
}

?>

以上のソースを任意のファイル名で保存して、プラグインとしてインストールする。

ステップ2

コールバック関数を定義する。

function callbak_function() {
        global $wp_filter;
        unset($wp_filter['the_content']);
/*
何らかの処理で $title 及び $content に代入する。
*/
        global $wp_query;
        $wp_query->is_404 = null;
        $wp_query->is_page = 1;
        $wp_query->is_search = null;
        $wp_query->is_archive = null;
        $wp_query->is_date = null;
        $wp_query->is_day = null;
        $wp_query->is_home = null;
        $wp_query->post_count = 1;
        $wp_query->post->post_title = $title;
        $wp_query->posts = array();
        $wp_query->posts[0]->ID = 0;
        $wp_query->posts[0]->post_author = 1;
        $wp_query->posts[0]->post_title = $title;
        $wp_query->posts[0]->comment_status = 'close';
        $wp_query->posts[0]->ping_status = 'close';
        $wp_query->posts[0]->post_type = 'user_tweets';
        $wp_query->posts[0]->post_content = $contents;
        $wp_query->posts[0]->post_date = time();
}

解説

カスタムなページを作成する際には、wp_queryを操作することでコンテンツだけでなく投稿タイプをプラグインから強制的に変更することが可能です。

この場合、/user/foo というURLでアクセスがあると、ページとしてテンプレートを使用しつつ、$contentsの中に動的なコンテンツを挿入することが可能です。

同じく$title も同様に変更が可能になります。

ただし、title要素の値を変える場合には、別途wp_headフックでタイトルを変更する処理も必要です。

WordPressで記事の最終更新日を取得

WordPressで最終更新日を表示したいのだが、the_modified_を使うと、予約投稿時に、公開日よりも更新日が早くなってしまう。何か良い方法はないものか。誰か知ってたら教えてください。

これではダメ?

/*
  get_the_modified_time()の結果がget_the_time()より古い場合はget_the_time()を返す。
  同じ場合はnullをかえす。
  それ以外はget_the_modified_time()をかえす。
*/

function get_mtime($format) {
    $mtime = get_the_modified_time('Ymd');
    $ptime = get_the_time('Ymd');
    if ($ptime > $mtime) {
        return get_the_time($format);
    } elseif ($ptime === $mtime) {
        return null;
    } else {
        return get_the_modified_time($format);
    }
}

使い方

  • 上記のソースをfunctions.phpにコピペしてください。
  • 最終更新日を表示したいところに以下のようなソースを記述してください。
<?php if ($mtime = get_mtime('Y/m/d')) echo '最終更新日: ', $mtime; ?>