2010/06/28  |  Written by  |  under Blog

と言うわけで、Rack化してみました。

darashi's jpmobile at rack – GitHub

Github上のrackブランチがそれにあたります。

with on GAE

以下で実験中です。ちゃんと絵文字と漢字コードが変換されたときはちょっとうれしかったりしました。

http://rust-stnard.appspot.com/

使い方

特に難しいことはなく、以下のような感じで動作させることができます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# -*- coding: utf-8 -*-
require 'rubygems'
require 'sinatra'
$LOAD_PATH << './lib/jpmobile/lib'
require 'jpmobile'
require 'jpmobile/rack'

require 'pp'

use Jpmobile::Rack::MobileCarrier
use Jpmobile::Rack::ParamsFilter
use Jpmobile::Rack::Filter

get '/' do
  erb :index
end
1
2
3
4
5
6
7
8
<html>
  <head>
    <title>そうでもない</title>
  </head>
  <body>
    &#xe63e;言わずもがな&#xe668;
  </body>
</html>

Encoding on Ruby 1.9

またパラメータのEncodingも変換しています。Rack::Request#paramsが必ずASCII-8BITになるので、それを無理矢理UTF-8に戻しています。

そして次は

on Ruby 1.9 で動作する jpmobile と言うのが目標です。と言うかRubyKaigi 2010までには何とかしないとダメなので、7月初旬にはリリースできるようにしたいと思っています。 3.0対応のパッチとか送ってもらっているのですが、Rack化でいろいろ変わったのと、見直したいところも多々あるので、しばしお待ちください。すんません…

 
このエントリーを含むはてなブックマークはてなブックマーク - Sinatra with jpmobile この記事をクリップ!Livedoorクリップ - Sinatra with jpmobile Googleブックマークに追加 Digg This
Tags: , , ,


Extending Index for Innodb tables can hurt performance in a surprising way

多くのキーを使うクエリーのときに、よく最適化するときにインデックスを拡張する。通常は問題なく、インデックスの長さが劇的に増加しない限り、インデックスを使うクエリーは新しいインデックスの先頭を使うことができる。では、そうではない場合を見てみよう。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
CREATE TABLE `idxitest` (
  `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
  `a` INT(11) NOT NULL,
  `b` INT(11) NOT NULL,
  PRIMARY KEY (`id`),
  KEY `a` (`a`)
) ENGINE=InnoDB AUTO_INCREMENT=6029313 DEFAULT CHARSET=latin1

mysql> SELECT COUNT(*) FROM idxitest WHERE a=5 AND b=5;
+----------+
| COUNT(*) |
+----------+
|    60434 |
+----------+
1 ROW IN SET (0.69 sec)

mysql> EXPLAIN SELECT COUNT(*) FROM idxitest WHERE a=5 AND b=5;
+----+-------------+----------+------+---------------+------+---------+-------+--------+-------------+
| id | select_type | TABLE    | TYPE | possible_keys | KEY  | key_len | REF   | ROWS   | Extra       |
+----+-------------+----------+------+---------------+------+---------+-------+--------+-------------+
|  1 | SIMPLE      | idxitest | REF  | a             | a    | 4       | const | 707820 | USING WHERE |
+----+-------------+----------+------+---------------+------+---------+-------+--------+-------------+
1 ROW IN SET (0.00 sec)

さて、カラム(a)から(a, b)にインデックスを拡張することで、高速化して他に影響を与えない最適化ができる。いいかな?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
mysql> ALTER TABLE idxitest DROP KEY a,ADD KEY(a,b);
Query OK, 0 ROWS affected (24.84 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> SELECT COUNT(*) FROM idxitest WHERE a=5 AND b=5;
+----------+
| COUNT(*) |
+----------+
|    60434 |
+----------+
1 ROW IN SET (0.02 sec)

mysql> EXPLAIN SELECT COUNT(*) FROM idxitest WHERE a=5 AND b=5;
+----+-------------+----------+------+---------------+------+---------+-------------+--------+-------------+
| id | select_type | TABLE    | TYPE | possible_keys | KEY  | key_len | REF         | ROWS   | Extra       |
+----+-------------+----------+------+---------------+------+---------+-------------+--------+-------------+
|  1 | SIMPLE      | idxitest | REF  | a             | a    | 8       | const,const | 120640 | USING INDEX |
+----+-------------+----------+------+---------------+------+---------+-------------+--------+-------------+
1 ROW IN SET (0.00 sec)

素晴らしい。クエリーは30倍も速くなった。スキャンする行が減っただけではなく、インデックスですべてまかなえるようになった。これだとデータにアクセスする必要がない。

でも喜ぶには早すぎる。ほとんど気がついていないようなそれまでは高速に実行されていたクエリーが他にもあった。しかしそれが遅くなるのだ。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
# 古いスキーマで実行

mysql> SELECT * FROM  idxitest  WHERE a=100 ORDER BY id DESC LIMIT 1;
+---------+-----+---+
| id      | a   | b |
+---------+-----+---+
| 3000000 | 100 | 7 |
+---------+-----+---+
1 ROW IN SET (0.00 sec)

mysql> EXPLAIN SELECT * FROM  idxitest  WHERE a=100 ORDER BY id DESC LIMIT 1;
+----+-------------+----------+------+---------------+------+---------+-------+--------+-------------+
| id | select_type | TABLE    | TYPE | possible_keys | KEY  | key_len | REF   | ROWS   | Extra       |
+----+-------------+----------+------+---------------+------+---------+-------+--------+-------------+
|  1 | SIMPLE      | idxitest | REF  | a             | a    | 4       | const | 126074 | USING WHERE |
+----+-------------+----------+------+---------------+------+---------+-------+--------+-------------+
1 ROW IN SET (0.00 sec)



# 新しいスキーマで実行(インデックスを拡張)

mysql> SELECT * FROM  idxitest  WHERE a=100 ORDER BY id DESC LIMIT 1;
+---------+-----+---+
| id      | a   | b |
+---------+-----+---+
| 3000000 | 100 | 7 |
+---------+-----+---+
1 ROW IN SET (1.01 sec)

mysql> EXPLAIN SELECT * FROM  idxitest  WHERE a=100 ORDER BY id DESC LIMIT 1;
+----+-------------+----------+-------+---------------+---------+---------+------+------+-------------+
| id | select_type | TABLE    | TYPE  | possible_keys | KEY     | key_len | REF  | ROWS | Extra       |
+----+-------------+----------+-------+---------------+---------+---------+------+------+-------------+
|  1 | SIMPLE      | idxitest | INDEX | a             | PRIMARY | 4       | NULL |   36 | USING WHERE |
+----+-------------+----------+-------+---------------+---------+---------+------+------+-------------+
1 ROW IN SET (0.00 sec)

# 実行計画は以下のようになることもある

mysql> EXPLAIN SELECT * FROM  idxitest  WHERE a=100 ORDER BY id DESC LIMIT 1;
+----+-------------+----------+------+---------------+------+---------+-------+------+------------------------------------------+
| id | select_type | TABLE    | TYPE | possible_keys | KEY  | key_len | REF   | ROWS | Extra                                    |
+----+-------------+----------+------+---------------+------+---------+-------+------+------------------------------------------+
|  1 | SIMPLE      | idxitest | REF  | a             | a    | 4       | const |    1 | USING WHERE; USING INDEX; USING filesort |
+----+-------------+----------+------+---------------+------+---------+-------+------+------------------------------------------+
1 ROW IN SET (0.01 sec)

なぜこんなに遅くなってしまうのか。理由はInnoDBの特性に有利な実行計画にある。つまり、インデックスが各キーの値に対する主キーでソートされているということ。なのでインデックス(a)があってidが主キーであるとき、実際のインデックスは(a, id)となり、拡張されたインデックスが(a, b)であるときには実際のインデックスは(a, b, id)になるということ。なので、元にインデックスで(a, id)を使っていたようなクエリーでは、新しいインデックスを使うことができない。

じゃあどうすれば?こんなときは「冗長」なインデックスを(a)(a, b)に同時に張ることができる。だが、これをちゃんと動くと仮定する仮定しがちだが、しばしば動作しない。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
CREATE TABLE `idxitest` (
  `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
  `a` INT(11) NOT NULL,
  `b` INT(11) NOT NULL,
  PRIMARY KEY (`id`),
  KEY `a` (`a`),
  KEY `a_2` (`a`,`b`)
) ENGINE=InnoDB AUTO_INCREMENT=6029313 DEFAULT CHARSET=latin1

mysql> SELECT * FROM  idxitest  WHERE a=100 ORDER BY id DESC LIMIT 1;
+---------+-----+---+
| id      | a   | b |
+---------+-----+---+
| 3000000 | 100 | 7 |
+---------+-----+---+
1 ROW IN SET (1.03 sec)

mysql> EXPLAIN SELECT * FROM  idxitest  WHERE a=100 ORDER BY id DESC LIMIT 1;
+----+-------------+----------+-------+---------------+---------+---------+------+------+-------------+
| id | select_type | TABLE    | TYPE  | possible_keys | KEY     | key_len | REF  | ROWS | Extra       |
+----+-------------+----------+-------+---------------+---------+---------+------+------+-------------+
|  1 | SIMPLE      | idxitest | INDEX | a,a_2         | PRIMARY | 4       | NULL | 2247 | USING WHERE |
+----+-------------+----------+-------+---------------+---------+---------+------+------+-------------+
1 ROW IN SET (0.00 sec)

MySQLのオプティマイザーは(a)(a, b)のインデックスの利用を考えた後、最後にどちらも使わずにフルインデックススキャンすることにしたようだ。(a)のインデックスを使えば1行だけでいいことが保証されるのに、2247行もスキャンするような計画を見積もったこのケースでは、オプティマイザーの誤動作のように見える。

正しい計画をMySQLオプティマイザーに使わせるためには、FORCE INDEX(a)を使う必要がある。

これは冗長なインデックスを調べるツールであるmk-duplicate-key-checkerの結果から、インデックスを変更するときには十分気をつけなければならないことを意味する。インデックスの中で主キーによってデータがソートされているInnoDBに依存するようなクエリーがあるのなら、変更が重大な影響を及ぼすことになるからだ。

オプティマイザーの振る舞いはMySQLのバージョンによって異なる。今回は5.1.45で行ったが、5.0でも同じ振る舞いだったことを記しておく。

 
このエントリーを含むはてなブックマークはてなブックマーク - InnoDBテーブルでインデックスを拡張するとパフォーマンスが落ちるかも from MySQL Performance Blog この記事をクリップ!Livedoorクリップ - InnoDBテーブルでインデックスを拡張するとパフォーマンスが落ちるかも from MySQL Performance Blog Googleブックマークに追加 Digg This
Tags:

いつものように気になったので意訳

Joining on range? Wrong! | MySQL Performance Blog

これら書く問題というのはMySQLの最初の方にある問題なんだが、気をつけてクエリーを解析しないと知らないまま終わってしまうことだ。phpDay.it でこの落とし穴についてデモしてきたが、ブログの記事にするのもいいとおもったんだ。

問題をデモするための典型的な例として、売上げクエリーを考えよう。データは次の3つのテーブルに入っている。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
CREATE TABLE `products` (
  `prd_id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
  `prd_name` VARCHAR(32) NOT NULL,
  PRIMARY KEY (`prd_id`),
  KEY `name` (`prd_name`)
)

CREATE TABLE `tags` (
  `tag_prd_id` INT(10) UNSIGNED NOT NULL,
  `tag_name` VARCHAR(32) NOT NULL,
  PRIMARY KEY (`tag_name`, `tag_prd_id`)
)

CREATE TABLE `items_ordered` (
  `itm_id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
  `itm_prd_id` INT(10) UNSIGNED NOT NULL,
  `itm_order_timestamp` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`itm_id`),
  KEY `itm_prd_id__and__itm_order_timestamp` (`itm_prd_id`,`itm_order_timestamp`)
)

そして十分な量のデータを入れた。

そして次のクエリーを考える。昨日にどれだけ液晶テレビが売れたのかを調べるクエリーだ。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
SELECT        COUNT(1)
       FROM   tags t
              JOIN products p
              ON     p.prd_id = t.tag_prd_id
              JOIN items_ordered i
              ON     i.itm_prd_id    = p.prd_id
       WHERE  t.tag_name             = 'lcd'
       AND    i.itm_order_timestamp>= '2010-05-16 00:00:00'
       AND    i.itm_order_timestamp  <'2010-05-17 00:00:00'
+----------+
| COUNT(1) |
+----------+
|     4103 |
+----------+

ちゃんとできたかな!

データ構造を見る限り、うまくいったように見える。tagsテーブルにはインデックスtag_nameがあり、items_orderedには(itm_prd_id, itm_order_timestamp)などが結合時に使われている。ではさらに詳細に見てみるとどうだろう。

1
2
3
4
5
6
7
8
9
10
11
12
SHOW STATUS LIKE 'Handler_read%';

+-----------------------+--------+
| Variable_name         | VALUE  |
+-----------------------+--------+
| Handler_read_first    | 0      |
| Handler_read_key      | 3      |
| Handler_read_next     | 118181 |
| Handler_read_prev     | 0      |
| Handler_read_rnd      | 0      |
| Handler_read_rnd_next | 0      |
+-----------------------+--------+

ちょっとよくないように見える。結果が4103行なのに、120000行もスキャンしている。必要なカラムにはちゃんとインデックスつけているのに!EXPLAINはどうなっているのか。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
*************************** 1. ROW ***************************
           id: 1
  select_type: SIMPLE
        TABLE: t
         TYPE: REF
possible_keys: PRIMARY
          KEY: PRIMARY
      key_len: 98
          REF: const
         ROWS: 1
        Extra: USING WHERE; USING INDEX
*************************** 2. ROW ***************************
           id: 1
  select_type: SIMPLE
        TABLE: p
         TYPE: eq_ref
possible_keys: PRIMARY
          KEY: PRIMARY
      key_len: 4
          REF: example_db.t.tag_prd_id
         ROWS: 1
        Extra: USING INDEX
*************************** 3. ROW ***************************
           id: 1
  select_type: SIMPLE
        TABLE: i
         TYPE: REF
possible_keys: itm_prd_id__and__itm_order_timestamp
          KEY: itm_prd_id__and__itm_order_timestamp
      key_len: 4
          REF: example_db.p.prd_id
         ROWS: 10325
        Extra: USING WHERE; USING INDEX

ここでちょっと思い出してみる。

1
2
3
`itm_prd_id` INT(10) UNSIGNED NOT NULL
  `itm_order_timestamp` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
  KEY `itm_prd_id__and__itm_order_timestamp` (`itm_prd_id`,`itm_order_timestamp`)

EXPLAINの3行目のkey_lenは4バイトなのだが、(itm_prd_id, itm_order_timestamp)は4+4で8バイトあるはずだ。と言うことは、1つのカラムしか見ていないことになる。

これをどう理解すべきか。データベースはlcdとタグの付いたソートされたデータを全て読み込む。それはやく120000行で、そのあと日付で絞り込まれる。なんと非効率な方法だ!MySQLは製品IDと日付範囲をインデックスでマッチして関連する行を読み込むだけな単純な最適化もできない。

これは結合するときだけ影響する。範囲条件を最初のテーブル(そしてそれのみ)に限定すれば、期待通りに動く。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
EXPLAIN
SELECT        COUNT(1)
       FROM   items_ordered i
       WHERE  i.itm_prd_id           = 5
       AND    i.itm_order_timestamp>= '2010-05-16 00:00:00'
       AND    i.itm_order_timestamp  <'2010-05-17 00:00:00'

*************************** 1. ROW ***************************
           id: 1
  select_type: SIMPLE
        TABLE: i
         TYPE: range
possible_keys: itm_prd_id__and__itm_order_timestamp
          KEY: itm_prd_id__and__itm_order_timestamp
      key_len: 8
          REF: NULL
         ROWS: 1306
        Extra: USING WHERE; USING INDEX

この場合、結合しないのでMySQLはrefとは表示せず、key_lenも8バイトとなっている。つまりちゃんとインデックスが利用されていると言うことだ。

この問題に対しては多くの回避策があるが、基本的に状況に依存する。本質的には、結合から範囲条件を外すことになる。今回の場合は、日付(DATA)のカラムを追加して、それで絞り込むことにすればよい。

1
2
ALTER TABLE items_ordered ADD itm_order_date DATE NOT NULL, ADD INDEX itm_prd_id__and__itm_order_date (itm_prd_id, itm_order_date);
UPDATE items_ordered SET itm_order_date = DATE(itm_order_timestamp);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
EXPLAIN
SELECT        COUNT(1)
       FROM   tags t
              JOIN products p
              ON     p.prd_id = t.tag_prd_id
              JOIN items_ordered i
              ON     i.itm_prd_id = p.prd_id
       WHERE  t.tag_name          = 'lcd'
       AND    i.itm_order_date    = '2010-05-16'

*************************** 1. ROW ***************************
           id: 1
  select_type: SIMPLE
        TABLE: t
         TYPE: REF
possible_keys: PRIMARY
          KEY: PRIMARY
      key_len: 98
          REF: const
         ROWS: 1
        Extra: USING WHERE; USING INDEX
*************************** 2. ROW ***************************
           id: 1
  select_type: SIMPLE
        TABLE: p
         TYPE: eq_ref
possible_keys: PRIMARY
          KEY: PRIMARY
      key_len: 4
          REF: example_db.t.tag_prd_id
         ROWS: 1
        Extra: USING INDEX
*************************** 3. ROW ***************************
           id: 1
  select_type: SIMPLE
        TABLE: i
         TYPE: REF
possible_keys: itm_prd_id__and__itm_order_timestamp,itm_prd_id__and__itm_order_date
          KEY: itm_prd_id__and__itm_order_date
      key_len: 7
          REF: example_db.p.prd_id,const
         ROWS: 206494
        Extra: USING WHERE; USING INDEX

このクエリーはitm_prd_id__and__itm_order_dateで7バイトとなっているので、(itm_prd_id, itm_order_date)をちゃんと利用していることが分かる。またrefでも2つのカラムを結合で利用していることが分かる。

1
2
3
4
5
6
7
8
9
10
11
SHOW STATUS LIKE 'Handler_read%';
+-----------------------+-------+
| Variable_name         | VALUE |
+-----------------------+-------+
| Handler_read_first    | 0     |
| Handler_read_key      | 3     |
| Handler_read_next     | 4104  |
| Handler_read_prev     | 0     |
| Handler_read_rnd      | 0     |
| Handler_read_rnd_next | 0     |
+-----------------------+-------+

非常によくなったことが分かるだろう。

しかし、クエリーが異なれば、解決方法も異なることは覚えておいて欲しい。

この問題に対するバグレポートがいくつかある(例えば #8569, #19548)が、MySQLからの回答では6.0かそれ以降のバージョンで解決されると言うことだ。または「それは解説されている振る舞いである」と言うことらしい。でも実際には仕様ではなく致命的なバグであり、修正が必要だ。

 
このエントリーを含むはてなブックマークはてなブックマーク - JOIN使うときに気をつけること from MySQL Performance Blog この記事をクリップ!Livedoorクリップ - JOIN使うときに気をつけること from MySQL Performance Blog Googleブックマークに追加 Digg This
Tags:


王様のレストラン DVD-BOX La Belle Equipe

  • 販売元:ポニーキャニオン( 2003-09-03 )
  • 時間:514 分
  • 4 枚組 ( DVD )

ラビリンス

  • アーティスト:TVサントラ
  • レーベル:ソニーレコード( 1999-05-21 )

というわけで、買ってみました。ただラビリンスの方はいまだDVD化されていないようなので、とりあえずサントラだけでも。でもこの頃のドラマって面白いですね。妻に教えてもらっていろいろ見てますが、なんか勢いがある感じ。早くDVD化されないかな、ラビリンス。

 
このエントリーを含むはてなブックマークはてなブックマーク - 懐古主義と言われようとも &#8211; 王様のレストランとラビリンス この記事をクリップ!Livedoorクリップ - 懐古主義と言われようとも &#8211; 王様のレストランとラビリンス Googleブックマークに追加 Digg This
No tags for this post.
2010/06/08  |  Written by  |  under Blog

実は前回のはうまく使えていません。というのも、どうもinline patchの方が23.2では良くないらしく、頻繁に落ちるからです。という訳なので、typesterさんのfull screen patchだけを当てていたのですが、今度はIME経由のShiftキー押下の記号が全部半角で入るという悲しいことに。なので、これまたtypesterさんのfix-shiftmodifier-with-ime patchを当てたものを使ってみることにします。

1
2
3
4
5
6
7
8
9
10
11
% wget http://ftp.gnu.org/pub/gnu/emacs/emacs-23.2.tar.gz
% wget http://github.com/downloads/typester/emacs/feature-fullscreen.patch
% wget http://github.com/downloads/typester/emacs/fix-shiftmodifier-with-ime.patch
% tar zxvf emacs-23.2.tar.gz
% cd emacs-23.1
% patch -p1 < ../feature-fullscreen.patch
% patch -p1 < ../fix-shiftmodifier-with-ime.patch
% ./configure --with-ns --without-x
% make bootstrap
% make install
% open nextstep/Emacs.app

基本的に前回と同じです。これで全角括弧とかが入力できるようになりました。

 
このエントリーを含むはてなブックマークはてなブックマーク - Emacs 23.2 with Shift IME patch on Mac OS X この記事をクリップ!Livedoorクリップ - Emacs 23.2 with Shift IME patch on Mac OS X Googleブックマークに追加 Digg This
Tags: ,

Math bookというところに、大学初等向けの数学の入門テキストがありました。というわけで、誰か読書会とかやらんかね。場所さえあれば、大学初等数学の講師経験もあるので、解説しますよ。というわけで、場所募集中。

 
このエントリーを含むはてなブックマークはてなブックマーク - 「数学 — 物理を学び楽しむために —」の読書会的な何かをやってみたい この記事をクリップ!Livedoorクリップ - 「数学 — 物理を学び楽しむために —」の読書会的な何かをやってみたい Googleブックマークに追加 Digg This
Tags: , ,