2008年7月3日
#
久々の投稿です。最近PostgreSQLばっかりやっているので、ちょっとしたTipsでも書こうかなと思います。
PostgreSQLで大規模なDWHを構築しようとした場合に、データファイルとストレージの容量の問題に悩まされます。数億オーダーのレコードが想定されるならば、気軽にPostgreSQLを使うとはいいにくいのではないでしょうか。素直にOracleとかを使えばよいのですが、そうもなかなか言ってられません。そこでテーブルのパーティショニングでデータファイルを分散化し、分散されたデータファイルを別ストレージに追いやる方法についてまとめてみます。
(今手元に動くPostgreSQLがないので、下のコードにバグがあったら後日直します・・・。)
まずは、何を格納するかについてですが、アクセスログを格納するという感じでテーブル設計してみましょう。(実際の業務でも似たようなことを・・・ry)
まずはリクエストテーブル(request)
| 物理名 | 論理名 | 型 |
| request_id | リクエストID | serial8 |
| timestamp | リクエスト日時 | timestamp |
| uri | URI | text |
| user_agent | ユーザエージェント | text |
| remote_host | リモートホスト | text |
次にパラメータテーブル(parameter)
| 物理名 | 論理名 | 型 |
| parameter_id | パラメータID | serial8 |
| request_id | リクエストID | int8 |
| timestamp | リクエスト日時 | timestamp |
| name | パラメータ名 | text |
| value | 値 | text |
というような感じになりました。そしてDDL。
create table request(
request_id serial8 not null,
timestamp timestamp not null default now(),
uri text not null default '',
user_agent text not null default '',
remote_host text not null default '',
primary key(request_id)
);
create table parameter(
parameter_id serial8 not null
request_id int8 not null default 0,
timestamp timestamp not null default now(),
name text not null default '',
value text not null default '',
primary key(parameter_id)
);
この時点では、まだインデックスや外部キーは不要です。
そして次にパーティショニングの戦略を考える必要がありますが、年月単位でパーティショニングを行ってみましょう。PostgreSQLでのパーティショニングは自動化されないので、テーブル継承と振り分けルールによって行うようにしてみましょう。
まずは表領域を作成します。(事前にpostgresユーザでディレクトリを作っておきましょう)
create tablespace log_space_200807 location '/var/local/pgsql/log_spaces/log_space_200807';
この表領域を利用する、リクエストテーブルを継承したテーブルを作成します。check(条件)という構文が出てきましたが、これはこのテーブルにはこの条件のレコードしか入らないという宣言です。以下の場合、タイムスタンプが2008年7月の日付のレコードしか入りません。
create table request_200807(
check(
'2008-07-01' <= timestamp and timestamp < '2008-08-01'
)
)
inherits(request)
tablespace log_space_200807;
create index
ix_request_200807_1
on request_200807(request_id)
tablespace log_space_200807;
create index
ix_request_200807_2
on request_200807(timestamp, uri)
tablespace log_space_200807;
そしてパラメータテーブルを継承したテーブルを作成します。チェック制約は同様です。
create table parameter_200807(check(
'2008-07-01' <= timestamp and timestamp < '2008-08-01'
)
)
inherits(parameter)
tablespace log_space_200807;
create index
ix_parameter_200807_1
on parameter_200807(parameter_id)
tablespace log_space_200807;
create index
ix_parameter_200807_2
on parameter_200807(
request_id, name, timestamp)
tablespace log_space_200807;
ここでインデックスが出てきました。継承元にはデータを格納しない為インデックスは不要ですが、継承したテーブルはデータを格納するためインデックスが必要になります。そして外部キー制約を設定します。これも実際にデータを格納するテーブル同士で設定します。
alter table parameter_200807
add constraint fk_parameter_200807_1
foreign key(request_id) references
request_200807(request_id)
on delete cascade;
最後に振り分けルールを設定します。振り分けルールとは、あるテーブルにレコードが挿入されるとき、どのテーブルにレコードが挿入されるかというルールです。チェック制約と同じ条件が設定される事になりますが、PostgreSQLにはチェック制約と振り分けルールが同じであるかを検証するすべがありません。慎重に同じ条件を設定する必要があります。
create rule
rl_request_200807_1 as
on insert to
request
where
('2008-07-01' <= timestamp and timestamp < '2008-08-01')
do instead
insert into
request_200807
values(
new.request_id,
new.timestamp,
new.uri,
new.user_agent,
new.remote_host
);
create rule
rl_parameter_200807_1 as
on insert to
parameter
where
('2008-07-01' <= timestamp and timestamp < '2008-08-01')
do instead
insert into
parameter_200807
values(
new.parameter_id,
new.request_id,
new.timestamp,
new.name,
new.value
);
これでテーブルは以上です。次にサーバの設定で、constraint_exclusionをonにしておきましょう。レコードの検索条件にタイムスタンプが含まれる場合、関係のないテーブルは実行プランに含まれなくなります。
・・・と、非常に面倒なことになりますが、これはスクリプトを使って必要に応じて生成するのがオススメです。この構成を行った場合、通常のSQLはというと、request/parameterのテーブルに対して実行するだけでよくなります。年月のサフィックスが付いたテーブルを意識する必要はありません。
複数年月のテーブルを作成し、request/parameterに対してinsertを行ってから、以下のクエリを実行してみてください。キチンとテーブルがパーティショニングされていることが確認できると思います。
select * from request;
select * from only request;
select * from request_200807;
select * from only request_200807;
select * from request_200808;
select * from only request_200808;
で、最後に注意点になりますが、insertは年月付きの実テーブルへ行った方が早いです。ベンチマークしましたが、数倍は早いです。。。大量データのインサートは直接実テーブルを行うようにしましょう。
2008年2月11日
#
Agitarというツールがあって、これはJUnitのコードを自動生成してくれたりするツールなんですが、これまたエラい高いツールだった記憶があります。しかし、いつの間にやらウェブサービス化しているみたいで、無料で扱えるみたいです。
簡単に言うと、Agitarのサーバ上にアップしたクラスに対して、自動的にテストケースを作成してくれるみたいです。
コードの中身を見て、より高いカバレッジでテストが必ず合格するテストケースを自動生成するので、元コードがバグっていたら、そのバグを正とするテストケースが生成されます。この辺は要注意です。
詳しくはこちら。
http://www.atmarkit.co.jp/fjava/rensai3/eclipseplgn24/eclipseplgn24_1.html
2008年2月7日
#
また同じ間違いをやってしまった・・・。
IE6でhttpsのページを開くとたまに出る、「セキュリティ警告」。
このページにはセキュリティで保護されている項目と保護されていない項目が含まれています。
保護されていない項目を表示しますか?
今作っているサイトでまたやってしまったんですが、原因はこれ。http://support.microsoft.com/kb/261188/ja
簡単にいうと、iframeタグにsrc属性を付け忘れると、この警告がでます。iframeタグのsrc属性を省略すると、なかのiframeのURLはabout:blankになります。aboutとhttps、スキーム名の違いで出てくるのでしょう。aboutは確かに非httpsです。
2008年1月31日
#
なんだか事件で色々とかかわいそうなので、新しいパッケージを考えてみました。
ここはやはりJT。JTらしさを取り入れてみました。
![10046409192[1]のコピー](http://kacchan6.wankuma.com/blog/images/JT_13B4A/100464091921_thumb.jpg)
久々にデッドロックというものをやってしまいました。
- トランザクション開始
- あるレコード更新
- 外部のRESTのWEBサービス呼び出し⇒\(^o^)/
- トランザクション終了
という処理で、WEBサービス側は、
- 呼ばれる
- こっち側のWEBサービスで対象レコード参照⇒\(^o^)/
- 処理完了
という感じで、なんつーか絵に描いたようなデッドロックを経験しました。トランザクション内での外部リソースの呼び出しは要注意ですね。
2008年1月30日
#
http://mindblind.net/2008/01/24/attacking-php/
http://www.rubyist.net/~matz/20080126.html#p04
う~ん、PHPが叩かれています。PHPって言語としてはかなり微妙というかゆとり向けかなと思うんですが、テンプレートエンジンとしてはかなり賢いというか便利な方ですよ。
でも手軽だし、初心者には最適じゃないでしょうか。初心者が学ぶには、Try&Errorをより短いサイクルで繰り返せる方が楽です。これって初心者に限らずベテランにも言えるでしょう。
自分もほぼ静的なページを作るときにヘッダとフッタの共有やコピーライトの年の部分の動的化など、割と便利だなと思って使っています。
2008年1月28日
#
次はJavaScriptでのクロージャについてです。よく間違えやすいケースについて書いてみます。
まずは、シナリオを考えます。
- ボタンが3つある
- 1番目のボタンをクリックしたら1をアラートで表示
- 2番目のボタンをクリックしたら2をアラートで表示
- 3番目のボタンをクリックしたら3をアラートで表示
これをコードにしてみましょう。まずはHTMLだけ。
<html>
<head>
<script type="text/javascript">
</script>
</head>
<body>
<form name="foo">
<input type="button" id="button1" value="1">
<input type="button" id="button2" value="2">
<input type="button" id="button3" value="3">
</form>
</body>
次にScriptタグの中身を書いて見ましょう。
function button1Click(){
alert(1);
}
function button2Click(){
alert(2);
}
function button3Click(){
alert(3);
}
//前提として、bodyタグにonload="windowOnload()"が記述済みとする
function windowOnload(){
var button1 = document.getElementById("button1");
button1.onclick = button1Click;
var button2 = document.getElementById("button2");
button2.onclick = button2Click;
var button3 = document.getElementById("button3");
button3.onclick = button3Click;
}
これは取り合えず非常に冗長ですが、オーソドックスな書き方です。ではこれをクロージャで書いてみましょう。
window.onload = function(){
document.getElementById("button1").onclick = function(){
alert(1);
};
document.getElementById("button2").onclick = function(){
alert(2);
};
document.getElementById("button3").onclick = function(){
alert(3);
};
};
少しシンプルになりました。色々なライブラリとのコンフリクションがあったりするので、最近のJavaScriptの主流の書き方では、極力グローバルの変数や関数を使いません。
ただこれでもまだ冗長なので、普通に以下のようなコードを書かれると思います。
window.onload = function(){
for(var i = 1; i <= 3; i++){
var button = document.getElementById("button" + i);
button.onclick = function(){
alert(i);
};
}
};
これを実行すると分かると思いますが、どのボタンをクリックしても4が表示されます。ループの中のクロージャはループカウンタの変数iに対する参照を持ちますが、最後に評価された結果の4が全てのクロージャから参照されてしまうのです。
では、上記コードに手を入れて正しく動くようにしてみます。
window.onload = function(){
for(var i = 1; i <= 3; i++){
var button = document.getElementById("button" + i);
//もう一つ関数で囲む
(function(n){
button.onclick = function(){
alert(n);
};
//iを渡す
})(i);
}
};
匿名関数にiを都度渡していますが、これによってiを参照しなくなります。関数に渡された仮引数を都度参照するので、この場合正しく動きます。
では最後に効率化を行いましょう。関数リテラルはキャッシュされず、インタプリタによってそのステートメントが評価されるたびに新しい関数オブジェクトが作られます。すなわち"(function(n){"で始まる関数のオブジェクトは都度つくられるのです。
window.onload = function(){
var click = function(n, button){
button.onclick = function(){
alert(n);
};
};
for(var i = 1; i <= 3; i++){
var button = document.getElementById("button" + i);
click(i, button);
}
};
関数は都度作られるというのは、以下のコードで確認できます。
var list = [];
for(var i = 0; i < 3; i++){
list.push(function(){});
}
alert(list[0] == list[1]);
alert(list[1] == list[2]);
alert(list[0] == list[2]);
上記は全てfalseになります。
2008年1月27日
#
Java7からはクロージャが利用できる予定みたいですが、コンパイラの実装も出てきました。
http://javac.info/jsr166z.ztar まとまった資料は此方です。
http://gafter.blogspot.com/2007/01/definition-of-closures.html
どんな感じで書けるかというと、
{int, int => int} plus = {int x, int y => x + y};
int result = plus.invoke(1, 2);
というような感じでかけます。なんとなくイメージはつかめますよね。これは、
interface I{
int invoke(int n, int m);
}
I plus = new I(){
public int invoke(int n, int m){
return n + m;
}
};
int result = plus.invoke(1, 2);
というような感じに置き換えられます。内部でインターフェイスが作られます。仮引数として受け取るパターンはこちらです。ちなみに型パラメータもOKです。
public static <T> List<T> select(List<T> list, {T => boolean} filter){
List<T> result = new ArrayList<T>();
for(T t : list){
if(filter.invoke(t)){
result.add(t);
}
}
return result;
}
public void test(){
List<Integer> list = new ArrayList<Integer>();
list.add(1);
list.add(2);
list.add(3);
list.add(4);
list.add(5);
list = select(list, {Integer n => n % 2 == 0});
//list = [2,4]
}
既存のメソッドが1つだけのインターフェイスもクロージャとして使えます。例えば以下のような構文でスレッドを作成できます。
new Thread({=>
system.out.println(123);
}).start();
これは、
new Thread(new Runnable(){
public void run(){
System.out.println(123);
}
}).start();
に置換え可能です。
これからローカル変数や例外の扱いについても調べてみようと思いますが、雰囲気的には、
- 匿名クラスのシンタックスシュガー
- メソッドが1つのインターフェイスの定義のシンタックスシュガー
という感じがします。また色々書いていこうと思います。
勉強会お疲れ様でした。初めての勉強会でいきなり色々しゃべらせてもらいましたが、すごい緊張でした・・・。
ライブプログラミングは面白かったですね。Eclipseのコンソールに入力値が出力されていたのは単なる偶然なんですが、それがニコニコ動画っぽくて間が持った感じでした。参加型のセッションって面白いですね。ニコニコ風に突っ込みを入れれるプレゼンツールを作りたいなと思いました。
それなりに厳しい意見もあったのですが、もうちょっと今度はキチンと段取りしてからやろうと思います。
それと、みなさんに始めてお会いできたのですが、ネットで活動してからの念願でした。これからもよろしくお願いします。
2008年1月26日
#
またまた久々の更新。最近仕事が忙しくて、死にそうなかつのりです。
皆さんにお会いするのも初なのに、いきなりトークとは・・・。いまから緊張しています。初回なのでムードもよくわかりませんし、とりあえずリードは凪瀬さんにお任せします。
しゃべりが得意ではないので、聞き苦しいかもしれませんが、よろしくお願いします。