PostgreSQL勉強会@札幌に参加してきた

開催から2週間近くも経ってしまいましたが、PostgreSQL勉強会@札幌に参加させていただきました。

以下、私的まとめ等です。

@syachiさんの「削除フラグのはなし」(スライド

はじめに

論理削除でおこりがちなケースとして……

  • 削除フラグがあるのだから、リレーションは使わない、レコードも消さない。プログラム側で何とかするから、不整合は起こらない!
    • 将来的に、データ構造と蓄積されたレコードの整合性・正当性・有効性が不明瞭になりやすい。
    • データの矛盾や誤データをカバーするためのコードが増えていく。

データベースの基本は「矛盾を排除すること」。データベース側で削除フラグを利用するときに想定されそうな矛盾を極力排除し、付き合いやすいデータベースを維持するためにはどうするか。

生きているレコードは1件のみ。死んだレコードは複数件許可。こんなときは?

ユーザIDなどの本来一意なカラムに対して、有効な削除フラグが1件になるような部分インデックスが有効。

論理削除を伝播させたい時(例:削除されたユーザにひもづくレコードも削除としたい)は?

削除フラグを、bool値ではなく、「キー値が負のもの」などというルールにしてしまう。
削除動作として主キーに-1をかければ、外部キーで関連するものを変更できる。

いっそ削除テーブルに入れてしまいたい時は?

各テーブルのレコードが削除された事をトリガにして、関連するテーブルのレコードを削除テーブルに移動してしまう様なFUNCTIONを用意する。
テーブル名を一貫したルール(delete_xxx)にしておけば、類推も楽。

削除フラグは我々の中でも一番の小物……

業務分析や設計を怠ると、下書きフラグ・一時停止フラグなど、次なる強敵も。

@iakioさんの「XIDを周回させてみよう」(スライド

はじめに
  • PostgreSQLを長く使ってると、データが見えなくなってしまうXIDの周回問題が発生する仕様らしい
    • じゃあ発生する瞬間を見てみよう!
トランザクションIDとは

トランザクションごとに発行されるID。
このIDの大きさで、データベースに要求された各トランザクションの実行順番の整合性管理している。トランザクションIDは各テーブルに Xmin, Xmax という隠し属性的なカラムで管理されていて、SELECT文で閲覧可能。

ある環境化でXmin, Xmaxの値がオーバーラップすると、トランザクションが無効になる≒データが消失する。

PostgreSQLの自衛手段

トランザクションIDの逆転防止の為に、PostgreSQLには自衛手段が組み込まれている。

このため、通常利用などにおいては、周回問題を発生させるのは難しい。(逆に言えば安全)

周回問題を防ぐ為に運用側で意識することは?
  • PostgreSQLの自衛手段が動作するために,VACUUM FREEZEをきちんと行う様にしておくのがベター。

質疑やフリートークで出た話。

  • 論理削除について
    • トリガは多用すると、コストが高かったり、全容を隠蔽してしまう可能性もあるので、ここぞというポイントで利用するのがベターかも。
    • トリガを使ったときのテストには、PG_TAPみたいな応用出来そうなツールもある
    • 削除テーブル方式は、元テーブルのレコード件数も少なく保っておけるので、(生きているデータを相手にするデータアクセスには)より良いかも。
    • 削除テーブルの実現には継承、パフォーマンスの悪化にはWAL無しテーブル(9.1から)っていう方向性もあるよね!
    • 最近はORMで論理削除をサポートしようとする動きも若干ある。(そもそも論理削除は日本ではニーズが高いけど、諸外国はそうでもないらしい)
  • その他
    • みんなデータベースのmigrationってどうやってるんだろう?→ツールやサポートを行うORMが幾つかあるものの、大規模な変更時の物も含めてベターな手段はなかなか難しい模様。
    • Macユーザー多いですね!(林檎マークの背面パネルで埋め尽くされた机を見ながら)
    • 次回は10月頃?

感想

論理削除について。私もプロジェクトのデータベースで論理削除を使っているんですが、削除フラグを伝播しなくても良かったり、矛盾が発生するような要求が(まだ)無いので、比較的救われてるんだなと実感しました。
ただ「削除はされてないけど基本的には不要になったデータ」はどうしても出てしまうわけで、紹介していただいた手法はそういったデータにも使えそう。特に「削除フラグはbool値じゃなくてキー値が負数でも良いよね」っていう考え方は、普段エラーコードなどを使っていながら、目から鱗でした。大変勉強になりました。

周回問題について。PostgreSQLについて検索したときに単語は良く見かけていて、漠然と怖いなーと思っていたんですが、どういう仕組みなのか、現在のPostgreSQLがどういった対策をとっているのかを学べて一歩理解が深まりました。「運用で役に立つかどうかは別として」みたいな前置きもされていましたが、Xidの様な仕組みはアルゴリズム的な視点からも非常に興味深かったです。

時間が押していたので自重してしまったんですが、実は聞いてみたかった(リクエストしたかった)ネタとして、PostgreSQLには、複数のデータベースから固有のテーブルだけをレプリケーションする仕組みってあるのでしょうか。
例えば,Aデータベースからはfooテーブル、Bデータベースからはbar, bazテーブルだけをレプリケーションするCデータベースといった感じのものは実現できるのかなあと。やっぱり同期ツール的なプログラムをアプリケーション側で書くしか無いのでしょうか。

Wicketで新しいウィンドウ(タブ)での表示を抑制するための試行錯誤(中)

どうしても画面を新しいタブ・ウィンドウでの表示を抑制したいというニーズがあり、Wicketで実現可能かどうか、試行錯誤中。

いまのところ、Wicketの複数ウィンドウサポート機能をオンにした上で、ページマップの数や名前でウィンドウ(タブ)をcloseする案をチームで検討していて、一見行けそうな気がしているものの、まだまだ隠れた問題がありそう。

Applicationのinitメソッド

protected void init() {
	super.init();
	getPageSettings().setAutomaticMultiWindowSupport(true);
}

全てのページの親となるWebPageクラス

public class HeaderPage extends WebPage {
	public HeaderPage() {
		// PortalSession.get().getPageMaps().size() > 1 などの条件の方が良い?
		if(getPageMap().getName() < null) {
			/* ウィンドウを閉じる処理や、不要になったPageMapを
         removeする処理をここに書く */
		 }
	}
}

上記のソースで現在の問題点として解っているのは、ModalWindowやPopupWindowの表示も阻害してしまうこと。
ModalWindowやPopupWindowを阻害せずに、意図したURLの新しいタブ・ウィンドウでの表示だけを抑制できる方法は無いものかなあ。

もし、良い意見をお持ちの方がいらっしゃいましたらよろしくお願いします……

Wicket 1.5(rc3) でURLのファイルパスを暗号化する

Wicket 1.4 では、WebApplication クラスのサブクラスで、 newRequestCycleProcessor() メソッドをオーバライドして、

@Override
protected IRequestCycleProcessor newRequestCycleProcessor() {
	return new WebRequestCycleProcessor() {
		@Override
		protected IRequestCodingStrategy newRequestCodingStrategy() {
			return new CryptedUrlWebRequestCodingStrategy(
					new WebRequestCodingStrategy());
		}
	};
}

と書いていたが、Wicket 1.5 では、同じくWebApplication クラスのサブクラスで、init() メソッドの中で、

protected void init() {
	super.init();
	setRootRequestMapper(new CryptoMapper(getRootRequestMapper(), this));
}

と書く。
詳しくは ここ に記載してある。

Wicket 1.5(rc3) でCSVをダウンロードするLinkコンポーネントを作る

importとコンストラクタは省略している。

public class CSVDownloadLink<T> extends Link<T> {

	@Override
	public final void onClick() {
		//getCsvData()はcsvの内容をString型で取得する。
		String csvData = getCsvData();

		StringResourceStream stream = 
			new StringResourceStream(csvData, "application/octet-stream;");
		stream.setCharset(Charset.forName("Windows-31J"));

		getRequestCycle().replaceAllRequestHandlers(
				new ResourceStreamRequestHandler(stream, "filename.csv");
	}

}

WicketとSessionについてのまとめ

Wicket 1.4.13にアップグレードしてから、StackOverflowError()が頻発する現象が発生。
いろいろ試してみたけれどもどうにも原因がわからず、途方にくれていたところ、id:mdgw 先輩と id:t_yano さんに助けていただきました。
お二方とも、重ね重ねありがとうございました。いつもご助言をいただきまして恐縮です。

ご助言の内容も含めて、簡単にまとめておきたいと思います。

続きを読む

WicketのDateTextField、DatePickerでハマってます

Wicket 1.3 から Wicket 1.4 へマイグレートした後に、仕様変更なのかバグなのか、動作が異なって困る点がいくつかありました。
表題のもその一つで、en(en-US)ロケールでDateTextFieldとDatePickerを使うと、挙動がおかしいというものです。

Foo.html

(略)
<span wicket:id="feedBack"></span>
<form wicket:id="form">
	<input type="text" wicket:id="date" />
	<input type="submit" />
</form>
(略)

Foo.java

import org.apache.wicket.datetime.markup.html.form.DateTextField;
(略)
public class Foo extends WebPage {
	public Foo() {
		Form<Void> form = new Form<Void>("form") {
			@Override
			protected void onSubmit() {
				super.onSubmit();
			}
		};
		DateTextField date = new DateTextField("date", new Model<Date>(new Date()), new StyleDateConverter("M-", true));
		this.add(new FeedbackPanel("feedBack"));
		this.add(form);
		form.add(date);
		date.add(new DatePicker());
	}
}

というソースコードがあったとして、これを実行すると、

といった画面がでます。DatePickerのアイコンで日付を選択すると、

ここまではよし。Wicket素敵。


さて、同じFooクラスを、ロケールをen(en_US)に変更したブラウザで閲覧すると、

と、表記を勝手に MMM dd, yyyy のフォーマットにしてくれて嬉しい。


しかし、ここでDatePickerのアイコンで日付を選択すると、

あれ。MMM dd, yyyy のフォーマットになってない。


ついでにこの状態でonSubmitを実行すると、

このように、バリデータにかかります。


wicket-extentionsの方のDateTextFieldを使って、ロケールにかかわらず yyyy/MM/dd というフォーマットになるようにすれば回避できるんですが、折角StyleDateConverterがあるのに勿体ない orz

なお、この現象が発生している環境は、

です。

日本PostgreSQLユーザ会 北海道支部 勉強会(2010-08-18)に参加してきた

2010-08-18に開催された日本PostgreSQLユーザ会 北海道支部 勉強会に参加してきました.

@syachiさんのテーブル設計の話

syachiさんのオレオレルールの紹介から,ATNDを画面仕様に見立ててテーブル設計を実演されました.
私もSQLを10年近く使っていながら,テーブル設計はこれまで身内としか議論したことが無かったので,外部の方の設計思想がとても勉強になりました.命名規則(時刻テーブルの"foo_at")とか,データをシリアライズして1カラムに保存してしまう設計などは真似したい.

実演中,EclipseのERMasterを実演に使っていらっしゃったのですが,2バイト文字を特定の入力ボックスに入れるとコケるというハプニングが多発で大変なことに……
でもERMasterは使いやすそうだったので,今秋の開発で使ってみたいです.

id:iakioさんのPostgreSQL9のreplicationの話.

iakioさんが,PostgreSQL9で採用されるreplication機能をを実演されました.
WALとかは上辺の知識しか無かったのですが,replicationの流れの輪郭が解りましたし,MasterやSlaveサーバの作り方が意外に簡単で驚きました.replicationの設定は,blogで公開される予定だそうです.
単なるreplicationだけではなく,フェイルオーバーや,参照はSlave,更新はMasterサーバで行うような仕組みもpg-pool3との組み合わせでできる(様になるはず)だそうです.
こちらも一度試してみたい.今のプロジェクトでは予算が無くて,実運用ではサーバーが確保できませんが……

最後に

RETURNINGはモダンですね!

次回のテーマは「論理削除」だそうなので(?),今から楽しみです.
syachiさん,iakikoさん,ありがとうございました.参加された皆さんもお疲れ様でした.