まだ重たいCMSをお使いですか?
毎秒 723リクエスト を捌く超高速CMS「adiary

2010/01/09(土)Perl用、日本語「全角」→「半角」変換ルーチン

よくネットショップ等で買い物をすると

郵便番号は半角で入力してください。

とか言われてウザくないですか? こういうのを解消するためのルーチンです。

条件

  • Perl 5.8以降
  • 利用可能文字列はutf-8のみ(そうでない場合はutf8に変換して渡してください)
  • WTFPL(PDS扱いでも可)。

ソースは必ずutf-8で保存してください。

日本語に混ざる全角英数等を半角にする

use utf8;
use Encode ();
sub utf8_zen2han {
	my $str = shift;
	my $flag = utf8::is_utf8($str);
	Encode::_utf8_on($str);

	$str =~ tr/ !”#$%&’()*+,-./0-9:;<=>?@A-Z[¥]^_`a-z{|}/ -}/;

	if (!$flag) { Encode::_utf8_off($str); }
	return $str;
}

実行例。

(変換前)abcdefgさささ110-2244あいう##$”
(変換後)abcdefgさささ110-2244あいう##$"

半角カタカナを全角カタカナにする

use utf8;
use Encode ();

my %hankana_map = (
'ガ'=>'ガ','ギ'=>'ギ','グ'=>'グ','ゲ'=>'ゲ','ゴ'=>'ゴ',
'ザ'=>'ザ','ジ'=>'ジ','ズ'=>'ズ','ゼ'=>'ゼ','ゾ'=>'ゾ',
'ダ'=>'ダ','ヂ'=>'ヂ','ヅ'=>'ヅ','デ'=>'デ','ド'=>'ド',
'バ'=>'バ','ビ'=>'ビ','ブ'=>'ブ','ベ'=>'ベ','ボ'=>'ボ',
'パ'=>'パ','ピ'=>'ピ','プ'=>'プ','ペ'=>'ペ','ポ'=>'ポ',
'ヴ'=>'ヴ');

sub utf8_hankana2zen {
	my $str = shift;

	my $flag = utf8::is_utf8($$str);
	Encode::_utf8_on($$str);

	$str =~ s/(ガ|ギ|グ|ゲ|ゴ|ザ|ジ|ズ|ゼ|ゾ|ダ|ヂ|ヅ|デ|ド|バ|ビ|ブ|ベ|ボ|パ|ピ|プ|ペ|ポ|ヴ)/$hankana_map{$1}/g;
	$str =~ tr/。-゚/。「」、・ヲァィゥェォャュョッーアイウエオカキクケコサシスセソタチツテトナニヌネノハヒフヘホマミムメモヤユヨラリルレロワン゛゜/;

	if (!$flag) { Encode::_utf8_off($str); }
	return $str;
}

Encode::JP::H2Z

EUC-JPの場合は標準モジュールでもできるそうです。(竹さん指摘)

use Encode ();
use Encode::JP::H2Z ();

sub eucjp_hankana2zen {
	my $str = shift;
	Encode::JP::H2Z::h2z(\$str);
	return $str;
}

逆変換は Encode::JP::H2Z::z2h()。しかし今時EUC-JPでは……

その他

バグ等あったらコメントください。

2009/09/04(金)MySQL/PostgreSQLでのシリアル値まとめ

adiaryではすべてのテーブルに pkey というシリアル値(PRIMARY KEY)を設定しています。その扱いについて。

特にMySQLで特別な加工せずに安全なシリアル値を取得する方法。

PostgreSQLの場合

PostgreSQLではそのままserial型というものがあり、

CREATE TABLE test(pkey SERIAL PRIMARY KEY, x INT);
INSERT INTO test(x) VALUES(10);

とすることで、pkeyをプライマリキーとして自動的に生成することができます。

PostgreSQLではシーケンス操作関数というものがあり、SERIAL型を定義すると自動的に作成されます。

例えば、現在の値を取得したければ

SELECT currval(pg_catalog.pg_get_serial_sequence('test', 'pkey'))

とします。pkeyに値を設定して INSERT したとき、シーケンス関数は自動的に再定義されないため次の関数で再設定する必要があります。

SELECT setval(pg_catalog.pg_get_serial_sequence('test', 'pkey'), (SELECT max(pkey) FROM test))

現在の値(最後に生成された値)を取得するには次のようにします。

SELECT currval(pg_catalog.pg_get_serial_sequence('test', 'pkey'), (SELECT max(pkey) FROM test))

同じセッション内でテーブル問わず最後に生成された値を取得する場合は次で済みます。

SELECT lastval()

安全に(他のセッションともかぶらない唯一無二な)次のシリアル値を取得するには、次のようにします。

SELECT nextval(pg_catalog.pg_get_serial_sequence('test', 'pkey'))

MySQLの場合

MySQLにも5.1以降(それより前から?)SERIAL型があります。

CREATE TABLE test(pkey SERIAL PRIMARY KEY, x INT);
INSERT INTO test(x) VALUES(10);

これによりpkeyに自動的にシリアル値を設定することができます。

  • SERIALは「BIGINT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE」のエイリアスです。
  • AUTO_INCREMENTは1つのテーブルに1カラムしか指定できない。
  • AUTO_INCREMENTを指定したカラムに、例えばpkeyを指定してINSERTしても、AUTO_INCREMENTがよきにはからってくれる(PostgreSQLのように値を再設定する必要なし)。

現在の値(最後に生成された値)を取得するには次のようにします。

SELECT LAST_INSERT_ID();

ただしC API等では mysql_insert_id()というものがあるため、こちらを使用するほうが効率が良いようです。例えばPerl DBIならば $sth->{mysql_insertid}。

追記。

SELECT * FROM test WHERE pkey IS NULL;

以前はこの方法でも取得が可能でしたが、いつの間にか使えなくなっているようなので注意しましょう。

値を取得、値の変更

SHOW TABLE STATUS WHERE NAME = 'test';

で得られたテーブル情報の中からAuto_incrementカラムの値を参照します。得られるのは次に挿入される値です*1

何かの都合で値を設定するときは次のようにします。

ALTER TABLE test AUTO_INCREMENT=1;

安全に次の値を取得

MySQLでもっとも厄介なのはこれです。auto_incrementには安全に次の値を得る方法が提供されていません。別にシーケンステーブルを作る方法などがありますが、面倒です。

INSERTでしか得られないならINSERTしてしまえばいいやという単純な方法です。

INSERT INTO test() VALUES();
DELETE FROM test WHERE pkey=LAST_INSERT_ID();
SELECT LAST_INSERT_ID();

NOT NULL制約等が付いているとINSERTが失敗するので、CREATE TABLE等であらかじめ制約を満たすDEFAULT値を設定しておく必要があります。MyISAMではトランザクションが使えないため、NOT NULLかつUNIQUE制約が付いていると、複数のプロセスが同時に"INSERT INTO test() VALUES();"を実行したとき一方が失敗します。

MySQLの場合はINSERTしてUPDATEした方がスマートなのはたしかなのですが、PostgreSQLではシーケンス型の次の値を安全に取得できるし、INSERTに成功してUPDATEに失敗した場合を考えるとややこしいので。

*1 : この値を使って次のデータをINSERTするのは危険です。

2009/08/09(日)PostgreSQLトランザクションとDBD::Pgの謎の挙動

PostgreSQLのトランザクションの仕様

CREATE TABLE test(x INT UNIQUE);
INSERT INTO test (x) VALUES (1);
INSERT INTO test (x) VALUES (2);

としておきます。

ここで、

=> BEGIN;
=> INSERT INTO test (x) VALUES (3);
INSERT 0 1
=> INSERT INTO test (x) VALUES (1);
ERROR:  duplicate key value violates unique constraint "test_x_key"

とするとエラーになります。PostgreSQLはトランザクション中にエラーが起こると、以後何をしてもエラーになり受け付けなくなります。ためしに続けて色々発行してみても、

=> INSERT INTO test (x) VALUES (10);
ERROR:  current transaction is aborted, commands ignored until end of transaction block
=> INSERT INTO test (x) VALUES (20);
ERROR:  current transaction is aborted, commands ignored until end of transaction block

こんな感じです。COMMITすると

=> COMMIT;
ROLLBACK

このようにROLLBACKされます。これがPostgreSQLの仕様です。

DBD::Pgの謎

  • 確認環境
    • PostgreSQL 8.3.7
    • DBD::Pg Ver1.49

DBD::Pgの場合

01: $dbh->begin_work;
02: $dbh->do('INSERT INTO test (x) VALUES (10)');
03: $dbh->do('INSERT INTO test (x) VALUES (1);');
04: $dbh->do('INSERT INTO test (x) VALUES (20);');
05: $dbh->commit;

とすると、3行目でエラーが起こり、4行目の実行でも"ERROR: current transaction is aborted, commands ignored until end of transaction block"といわれます。

これを、DBIのprepareを使用して

01: $dbh->begin_work;
02: $dbh->prepare('INSERT INTO test (x) VALUES (?)')->execute(10);
03: $dbh->prepare('INSERT INTO test (x) VALUES (?)')->execute(1);
04: $dbh->prepare('INSERT INTO test (x) VALUES (?)')->execute(20);
05: $dbh->commit;

とすると、3行目でエラーが起っても、4行目の実行が反映されてしまいます。謎の挙動です。

謎の解析

このようにソースを改変してログを取ってみました。

open(my $fh, ">pg_log.txt");
$dbh->pg_server_trace($fh);
$dbh->begin_work;
$dbh->prepare('INSERT INTO test (x) VALUES (?)')->execute(10);
$dbh->prepare('INSERT INTO test (x) VALUES (?)')->execute(1);
$dbh->prepare('INSERT INTO test (x) VALUES (?)')->execute(20);
$dbh->pg_server_untrace();
close($fh);

このときサーバに対して次のようなコマンドが発行されていました。

=> BEGIN;
=> INSERT INTO test (x) VALUES (10);
=> INSERT INTO test (x) VALUES (1);
エラー : duplicate key value violates unique constraint "test_x_key"
=> ROLLBACK;
=> BEGIN;
=> INSERT INTO test (x) VALUES (20);
=> COMMIT;

どうりで最後のCOMMITが成功するはずです。pg_log.txt:取得した生ログも置いておきます

これを、

01: $dbh->begin_work;
02: $dbh->prepare('INSERT INTO test (x) VALUES (10)')->execute();
03: $dbh->prepare('INSERT INTO test (x) VALUES ( 1)')->execute();
04: $dbh->prepare('INSERT INTO test (x) VALUES (20)')->execute();
05: $dbh->commit;

とすると再現しません。prepare中にUNIQUE制約をチェックして、勝手にrollbackしているようですが、原因がPostgreSQL側なのかDBD::Pg側なのかは絞り込めませんでした。

追記

DBD::Pgの実装仕様みたいです。トラックバックを参考にしてください(iakioさんに感謝)。

DBD::Pgを直すとすれば、プレースホルダが破棄されてもトランザクションが終了するまでDEALLOCATEするのを待つ、くらいでしょうが。

明示的なトランザクション内でのエラーとDEALLOCATE

ROLLBACKしていいから、DEALLOCATE後に空のトランザクションを begin して、失敗させてくれればそれで十分な予感。

メモ

  • どちらの場合も $dbh->commit(); の戻り値は "1" で成功している。

2007/12/01(土)IIS + ActivePerl で ImageMagick のインストール

ActivePerl に ImageMagick をインストールする

activeperl_installer.png

  1. ImageMagick公式サイトからWindowsバイナリ-windows-dll.exe)をダウンロードしインストーラーを上の画面まで進める
  2. インストーラーの画面から、対応するActivePerlのVersionとビルドナンバーを確認したら(この場合 Ver5.8.8 build 822)インストールせずに一度インストーラーを閉じる。
  3. 今確認したバージョンの ActivePerl インストールする
  4. ImageMagickを上記画面で「Update executable search path」「install PerlMagick」の2つにチェックを入れてインストールする。

これで ImageMagick がインストールされているはずです。コマンドラインで次のように入力すると手動で確認できます(エラーがでなければ正常です)。

c:\>perl -e "use Image::Magick"

IIS で ImageMagick を使う場合

IISで ImageMagick を使う CGI などを起動する場合は、更にWindowsを再起動します

Image::Magick関連のDLLをロードさせるためにパス情報が設定されるのですが(環境変数PATH)、IISに新しいPATHを認識させるためには再起動しなければなりません。

2007/09/26(水)PerlInterpMax の示すもの

daily dayflower などの情報をみて worker MPM な Apache を利用しても、同時に利用可能な Perlインタプリタ(mod_perlプロセス) は PerlInterpMax に制限されるように思っていました。デフォルトではこの値は 5 であり、一見少なく感じられます。

サーバDoS事件

ご存じのとおり本 blog.abk.nu サーバは、以前まで「データベースに接続できないエラー」を吐いてブログが表示されないことが多々発生していました*1。以前の設定値は次のとおりです。

mod_perl : PerlInterpMax 5
PostgreSQL : max_connections = 15

これでも、PostgreSQLへのコネクションが足りなくなりエラーとなっていました。PostgreSQLへのコネクションを保持するデーモンは adiary の他にありません。一体何が起こっていたのでしょうか。

*1 : それどころか、少し前にシステムリソースを食いつぶしサーバ機能を停止させる事件まで発生してました。

PerlInterpMax の示すもの

ここで実験です。worker MPM(スレッド動作)で動く Apache で"PerlInterpMax 3" に設定し、TCPデーモンとTCP接続をするだけのモジュールを用意して、PostgreSQL への接続と同様にコネクションを(切断することなく)プールさせてみました。この状態で

~$ ab -c 10 -n 100 http://blog.yyy.xx/

として、接続負荷をかけてみます。このときのTCPデーモンの表示は次のようになりました。

[05] Connection from 127.0.0.1
[06] Connection from 127.0.0.1
[07] Connection from 127.0.0.1
[08] Connection from 127.0.0.1
[09] Connection from 127.0.0.1
[10] Connection from 127.0.0.1
[11] Connection from 127.0.0.1
[12] Connection from 127.0.0.1

8本の同時接続です。PerlInterpMax 3 であるのにです。不思議です。ここでもう一度よく PerlInterpMax の説明を読んでみます。

PerlInterpreter を各 httpd スレッド毎に一対一対応させる代わりに,mod_perl では設定自在なインタプリタプールを管理しています。この方式により,必要最小限な数のインタプリタを用意することでメモリ使用量を押さえることができます。

daily dayflower より

どうやらスレッドに対する設定項目であることが読みとれます。そもそもよく考えてみれば、プロセス間でPerlインタプリタを共有することなんて不可能です。この仮説を検証するため、Apache のプロセス数を2つに制限してみました。

<IfModule mpm_worker_module>
    StartServers          2
    ServerLimit           2
</IfModule>

さてもう一度実験です。

[05] Connection from 127.0.0.1
[06] Connection from 127.0.0.1
[07] Connection from 127.0.0.1
[08] Connection from 127.0.0.1
[09] Connection from 127.0.0.1
[10] Connection from 127.0.0.1

今度はきちんと6本になりました。PerlInterpMax(3) * ServerLimit(2) = 6 ですから、計算に合っています。

PerlInterpMax の示すもの

mod_perl はスレッド動作時に 1プロセスごとに Perlインタプリタ をプールして保管します。1プロセスごとにその保管数まで mod_perl スクリプトを実行できます。プールされる実体は Perl 5.6 以降の ithreads そのものです。*2

この1プロセスごとのスレッドプール最大数を決めるのが PerlInterpMax です。worker MPM におけるプロセス数は ServerLimit で制限されます。つまり最大 PerlInterpMax × ServerLimit 個のmod_perl環境が同時に実行/保存されることになります。

それぞれデフォルト値では5、16ですから、80になります。この状況でデータベースへの接続をプールすると、あっと言う間にサーバ資源を食いつぶすことになるので注意が必要です。

注意

PerlInterpMax を小さくするときは、同時に PerlInterpStart も小さくします。PerlInterpStart より小さな値を PerlInterpMax に設定しても無効です

*2 : ですから、threads::shared によりスレッド間(同一プロセス)に限って変数を共有することもできます。

OK キャンセル 確認 その他