INDEXとかを作ったり消したりする時の進捗確認

非力なマシンで膨大なレコードを持ってるテーブルのINDEXを作ってたんですがいつになったら終わるのか今どれくらい出来てんのかわかんなくてヤキモキしてました。

という訳でヤキモキしててもしょうが無いので何か方法無いかしらと調べてみました。

ほんでMySQLのデータディレクトリ(/var/db/mysqlとか)にいかにもテンポラリっぽいファイルを発見。
↓こんなファイルが開始と同時に出来てましてみるみる太ってます。

#sql-[ID].MYD
#sql-[ID].MYI
#sql-[ID].frm

ちなみに各ファイルの役割

[テーブル名].frm => テーブル構造関連ファイル
[テーブル名].MYD => データファイル
[テーブル名].MYI => インデックスデータファイル

悲しいかなMySQLの中は分からにゃいので、勝手に想像でこれはインデックス作成時のテンポラリファイルだと推測。
実行が完了すれば対象テーブルのMYDとかと入れ替わるんでしょうとこれまた勝手に推測。

という訳で進捗を計るには以下で良いんじゃないかしらという結論にたどり着きました。

#sql-[ID].MYDのファイルサイズ / 対象テーブルのMYDのファイルサイズ = 進捗率!

そんな訳で待ってるのも暇なのでスクリプト仕込んでみました。
scpチックに進捗を出してくれます。終わりたい時はctl+cで
↓出力イメージはこんな感じです。

 069% |*********************************************************************                               |

但し、データサイズを変更しないDDLを流す時にだけ使えるという何とも言えない代物です。
具体的にはalter table系はだめでcreate/drop indexとか制約つけた時にいけると思います。
INDEX関連はMYIファイルが太るけどデータサイズは大して変わらんでしょうから。
ALTER系はINDEX追加しないならMYIを指標に出来る鴨。

まぁ何はともあれ以下ソース。

use strict;
use warnings;

use DBI;
use Getopt::Long;
use File::Find;
use File::stat;

my %opt;
Getopt::Long::GetOptions(\%opt, "table=s");

# 以下環境に合わせて・・・
our @DATA_SOURCE = ('dbi:mysql:スキーマ名;host=ホスト','ユーザーID','パスワード');


my $_DATA_DIR = undef;
sub get_datadir($) {
    my $dbh = shift;
    if ($_DATA_DIR) {
        return $_DATA_DIR;
    }
    my $sth = $dbh->prepare('show variables');
    $sth->execute();
    my @ret = ();
    while (my $row = $sth->fetch) {
        my ($key, $val) = @$row;
        if ($key eq 'datadir') {
            $_DATA_DIR = $val;
            return $val;
        }
    }
    die "mysql datadir not found.";
}

sub exists_table($$) {
    my $dbh = shift;
    my $tname = shift;
    my $sth = $dbh->prepare('show tables');
    $sth->execute();
    while (my $row = $sth->fetch) {
        my ($t) = @$row;
        if ($tname eq $t) {
            return 1;
        }
    }
    return 0;
}

sub get_schema_name() {
    my ($dbs) = @DATA_SOURCE;
    my ($db, $attr) = split /;/, $dbs;
    my @info= split /:/, $db;
    return $info[2];
}

sub guess_work_file($) {
    my $dbh = shift;
    my $schema = get_schema_name;
    my $data_dir = get_datadir $dbh;
    my $wf;
    File::Find::find(sub {
        my $fname = $File::Find::name;
        if ($fname =~ /$data_dir$schema\/#sql.+\.MYD$/) {
            $wf = $fname;
        }
    }, "$data_dir$schema");
    if ($wf) {
        return $wf;
    }
    die "work file not found.";
}

sub get_myd_file_name($$) {
    my $dbh = shift;
    my $tname = shift;
    my $schema = get_schema_name;
    my $data_dir = get_datadir($dbh);
    my $myd_file = "$data_dir$schema/$tname.MYD";
    unless (-f $myd_file) {
        die "myd file not found.($myd_file)";
    }
    return $myd_file;
}

sub process {
    my $dbh = DBI->connect(@DATA_SOURCE, {RaiseError => 1});
    unless ($opt{table}) {
        die "require option --table=[stirng].";
    }
    unless (exists_table($dbh, $opt{table})) {
        die "not exists `$opt{table}' in database.";
    }
    my $work_file = guess_work_file($dbh);
    my $myd_size = stat(get_myd_file_name($dbh, $opt{table}))->size;
    $dbh->disconnect();

    my $parcent = 0;
    my $templete = " %03d%% |%s%s|\r";
    while (1) {
        my $parcent = sprintf("%.0f", ((stat($work_file)->size / $myd_size) * 100));
        print STDERR sprintf $templete, $parcent, join('', ('*') x $parcent), join('', (' ') x (100 - $parcent));
        sleep(1);
    }
}

process();

テーブルが引き数になってます。後root(かmysql)じゃないと動かないと思うので起動は↓こんな感じ

sudo perl スクリプト名 -t テーブル名

完了したら寝よう・・・。

startup.plに書いておいた方が良い事。

わたくしMy三国志というサイトをmod_perl+Apacheでやってるんですが、やはりネックになるのがメモリです。やりくりにはいわるゆるstartup.plの最適化が必須という事で結構色々やってます。結構、枯れてる話題で色んな情報が転がっていますが、参考までに当サイトでやっている事を挙げてみました。

ローカルのモジュールをくまなくrequireし取りこぼしを減らす

サイト用のモジュールのパッケージ名をベッタ〜とstartup.plに書いてたんですが、モジュールを追加したり、削除したりいくうちにstartup.plのメンテナンスが追いつかなくなってきました。こりゃいかんという事でもう一発で読み込んでしまおうという事で次のように書いてます。

use File::Find ();

my $lib_root = "モジュールのあるディレクトリ";
File::Find::find(sub {
    my $name = $File::Find::name;
    return if -d $name;
    return if $name !~ /\.pm$/;
    $name =~ s/^$lib_root\///;
    $name =~ s/^$lib_root//;
    require $name;
}, $lib_root);

$lib_rootを消しているのは$File::Find::nameがフルパスで返してくるからです。例えば/usr/local/lib/My3594.pmというモジュールが有る場合、そのままだと%INCでは下記のように突っ込まれてます。

'My3594.pm' => '/usr/local/lib/My3594.pm',

しかしPERL5LIBに/usr/local/libが通っている場合上以下の形で%INCに突っ込まれます。

'My3594.pm' => 'My3594.pm',

ここでredfiened警告が出てしまうので、パスを揃えるためにlibまでのパスを消しているのです。まぁ当サイト固有の話なかもしれませんが恐らく皆様そんな感じじゃないかしら。

動的に読み込まれるモジュールに対する対処

遅延ロードされるモジュールについてはuseとかrequireしただけでは、動的に読み込まれるモジュールについてはメモリ上にロードされません。という訳で色々ぶらさがってそうなモジュールの処理を一回流してみる、というのをやってます。

TemplateToolkit
一通り色々やってみてる処理を流してます。

use Template ();
my $template = qq/
[%- USE Class('Data::Page') -%]
[%- USE JavaScript -%]
[% hoge | html | js %]
/;
Template->new()->process(\$template, {hoge => 'hoge'}, \my $out) or die;

LWP::UserAgent
getまで一回やってみます。

use LWP::UserAgent;
my $lwp = LWP::UserAgent->new(agent => 'My3594/1.0', timeout => 3);
my $res = $lwp->get("http://feeds.feedburner.jp/my3594-pedia-new_entry");

Class::DBIのサブクラス
retrieveしたりsearchしたり・・・半分おまじないですw

use My3594::Data::Member;
My3594::Data::Member->retrieve(1);
my @list = My3594::Data::Member->search(id => 1);
my $list = My3594::Data::Member->search(id => 1);
@list = My3594::Data::Member->retrieve_from_sql(q/id = 1/);
$list = My3594::Data::Member->retrieve_from_sql(q/id = 1/);

この辺のチェックについては下記のINCDiff.pmを活用させていただいております。
http://d.hatena.ne.jp/hideden/20080409/1207740439


さてさて後何ができるだろうか・・・。

OS付属のrpmでローカルyumリポジトリを作ったり

F/Wで全てのポートを閉ざされた世界の中でパッケージ管理したいんですが、rpmしこしこやってると依存関係に吐きそうになってきました。

そこでOS付属の標準rpm達のリポジトリをローカルに作ってしまおうと思いましてやってみましたという話。

リポジトリのパスは

/usr/local/repository/

でOSはCentOS4という事にします。


準備
まずは何はなくともrpm達を用意します。

まずOSのisoからローカルにコピー

mount -t iso9660 -o loop /usr/local/src/CentOS-4.7-i386-binDVD.iso /mnt/iso/
cp -p /mnt/iso/CentOS/RPMS/*.rpm /usr/local/repository/

そしてこれだけは手作業でinstallしなくてはならないcreaterepoをinstallします。
http://dag.wieers.com/rpm/packages/createrepo/

wget http://dag.wieers.com/rpm/packages/createrepo/createrepo-0.4.6-1.el4.rf.noarch.rpm
rpm -ihv createrepo-0.4.6-1.el4.rf.noarch.rpm

そして先ほどのrpm群をリポジトリ化。

createrepo /usr/local/repository/

そして以下のファイルを作り設定を追加します。

/etc/yum.repos.d/local.repo
[local]
name=local - file
baseurl=file:///usr/local/repository/
gpgcheck=0
enabled=1

そして共通設定ファイルにenabled=0を追加して他のリポジトリを無効化します。

vim /etc/yum.conf
enabled=0

他に/etc/yum.repos.d/以下にenabled=1の設定が有る場合そちらもenabled=0にしておきます。
※有効なリポジトリが有る場合はそちらへもリクエストが飛んでしまうのでポートが閉じられていた場合は詰まってしまう恐れ有りです。

これでめでたく有効なリポジトリはlocalだけとなりました。

とりあえずHTTPとFTPのポートを閉じてみます。

iptables -A OUTPUT -p tcp --dport 80 -j DROP
iptables -A OUTPUT -p tcp --dport 20 -j DROP
iptables -A OUTPUT -p tcp --dport 21 -j DROP
service iptables start

いざインストール
では実行前にキャッシュを消しておきます。

rm -rf /var/cache/yum/*

さぁ快適なyum生活!!と思いきや↓とかいきなりやってもどっかで詰まってしまいました・・・orz

yum search zsh

他のリポジトリは全部enable=0にしたんでおkかと思ったんですがそうではないのかしら・・・?

ともあれ--enablerepoオプションをつければ何とかなりました。

yum --enablerepo=local install zsh

まぁupdateとか出来ないんで微妙なんですけどもね・・・。
OSがupdateされたら都度持ってきたりすれば良いんですかねぇ。

参考URL
http://www.proustcafe.com/2006/01/createrepoyum.html
http://wiki.poyo.jp/read/Writing/fc-expert/making_rpm/100.make_repository
http://www.atmarkit.co.jp/flinux/rensai/linuxtips/795tmprepo.html

TypePadの絵文字アイコンの並び順に困ったら

接頭番号とかがファイル名にないので困ったってたら。

↓ここに答えがあった。
http://code.sixapart.com/trac/mtplugins/browser/trunk/EmoticonButton/mt-static/plugins/EmoticonButton/js/emoticon.js#L80

grepとzgrepの速度差

この前、周りで話題に出てたのでgrepとzgrepのベンチをとってみた。
サンプルはアクセスログでYahooという文字列をgrepする。ファイルは11Mくらい。

要点は圧縮しない状態だとどれくらい速いのかしら?という事。
容量を圧迫する事を犠牲にしてでも良いくらいのパフォーマンスの差がでるかどうか。

まず圧縮。

$ diff access_log.1 access_log.2
$ gzip access_log.2
$ ls -lha access_log.*
-rw-r--r--  1 yumatsumo  yumatsumo       11M Feb 13 00:32 access_log.1
-rw-r--r--  1 yumatsumo  yumatsumo        1M Feb 13 00:21 access_log.2.gz

そしてYahooの出現回数を数えるサブルーチンで比較。

use strict;
use warnings;

use Benchmark;
use FindBin qw($Bin);

use Fatal qw(open close);

sub count_yahoo($)  {
    my $fh = shift;
    my $counter = 0;
    while (my $row = <$fh>) {
        $counter++;
    }
}

sub comp {
    open my $fh, "grep Yahoo $Bin/access_log.1 |";
    count_yahoo $fh;
    close $fh;
}

sub uncomp {
    open my $fh, "zgrep Yahoo $Bin/access_log.2.gz |";
    count_yahoo $fh;
    close $fh;
}

my $b = Benchmark::timethese(1000, {
            comp   => \&comp,
            uncomp => \&uncomp,
          });

Benchmark::cmpthese($b);

結果はこんな感じ。圧縮してない方がやや速いくらい。

Benchmark: timing 1000 iterations of comp, uncomp...
      comp: 54 wallclock secs ( 2.30 usr 15.27 sys + 14.37 cusr 21.12 csys = 53.06 CPU) @ 56.92/s (n=1000)
    uncomp: 201 wallclock secs ( 0.57 usr 12.74 sys + 31.15 cusr 154.74 csys = 199.20 CPU) @ 75.13/s (n=1000)
         Rate   comp uncomp
comp   56.9/s     --   -24%
uncomp 75.1/s    32%     --

だけどギガ単位のファイルだとまた違って来るか。うーむ。
今度ファイルサイズ変えて段階的にとってみよう。

coolirisに対応してみた。

ちょっと勘違いしててcoolirisって名前は聞いた事はあったんですがLightBox的なものかと思ってました。
(いわゆるポップアップを出すようなプラグインか何かなんだろなと思ってました。)

手が空いたのでもっかい見直して見ると全然違った・・・。
スタンドアロンのぬるぬる動く超coolなビューワーでした。
ブラウザのAddOnとかはcoolirisを起動させるためのトリガを仕込むためのようなものかと。

そんで我が愛しのMy三国志も対応させようかと思いましてやってみました。

と言ってもやる事はかなり少ない。以下のリンクをヘッダに仕込むだけ。

<link rel="alternate" href="[% RSSのURL %]" type="application/rss+xml" title="[% タイトル %]" id="gallery" />

RSSの中身はY!Incが提唱しているMedia RSS Moduleという仕様にそった形にすれば良いみたい。(この行全部想像w)
http://search.yahoo.com/mrss

↓こんな感じで吐けば良いみたい。(コンテンツヘッダがtext/htmlのはやっつけだから・・・・すいません。後で直します。)
http://my3594.net/pedia/photo?member_id=1

これで無事ぬるぬるぬるぬるぅ〜〜〜〜っと出ました!

so cooooooooooooooooooooooooooool!!


で、coolirisの読み方って「クーリリス」でいいのかしら。「クーリライス」?

見えざるものを見よこれぞ博打の真骨頂

今日も新たな発見がありました・・・・。
ほんとにもう何年やっとんねんみたいな話。
きっと・・・おそらく・・・多分・・・常識なんだろうなぁ。

やりたい事は変数をexportするという事。

↓のようなコードで変数をexportさせたいと。

$ HOGE=ABC
$ echo $HOGE
ABC

で↓のコードを書きましたとさ。

$ cat env.sh
HOGE=ABC
export HOGE

これでexportされるだろうと実行しましたとさ。

$ bash -x env.sh
+ HOGE=ABC
+ export HOGE

めでたくexportされませんでしたとさorz

$ echo $HOGE

$

恥ずかしながら何故だか見当がつかず暫らく悩みました。
そして原因判明↓のコードで分かると思います。($$はperlと同じくpid)

$ cat pid.sh
echo $$
$ echo $$; bash ./pid.sh
41252
41936

pidが違う・・・・。

そうつまり起動したプログラムは別プロセス、つまり別セッションで動いてました。
なのでexportしても今いるセッションからは変数掴めないと。

でこういう時はどうするかというと

can be executed within the running shell's process

って訳で.bash_profileとか.bashrcでお馴染みsourceを使うとさ。

これでめでたく・・・・・

$ source env.sh
$ echo $HOGE
ABC

できた!

それではお休みなさい・・・・。