Monday, February 25, 2008

WebSphere から Oracle にアクセスすると、ArrayIndexOutOfBoundsException が発生した件

WAS から JDBC Thin ドライバを使って、Oracle 10g にアクセスしているのだが、表題のようなエラーが発生した。発生時の環境は以下のとおり。


  • Oracle JDBC ドライバのバージョン: 10.2.0.3.0
  • Oracle のバージョン:10.2.0 Express
  • WAS のバージョン:IBM WebSphere Application Server - Express, 6.0.0.1
  • WAS で使われる Java のバージョン:Classic VM (build 1.4.2, J2RE 1.4.2 IBM build cxia32142sr1w-20041028 (JIT enabled: jitc))


Oracle JDBC ドライバのバージョンは、java.sql.Driver のメソッドを呼び出さなくても、ojdbc14.jar を展開して MANIFEST.MF を見れば分かる。WAS のバージョンは管理画面から分かるし、WAS の java バージョンは、WAS 配下の java コマンドを -version オプションで実行すれば分かる。

この環境で以下のようなエラーが発生したというわけだ。

java.lang.ArrayIndexOutOfBoundsException: 451808508
at oracle.jdbc.driver.T4CCallableStatement.doOall8(T4CCallableStatement.java(Compiled Code))
at oracle.jdbc.driver.T4CCallableStatement.executeForRows(T4CCallableStatement.java:965)
at oracle.jdbc.driver.OracleStatement.doExecuteWithTimeout(OracleStatement.java(Compiled Code))
at oracle.jdbc.driver.OraclePreparedStatement.executeInternal(OraclePreparedStatement.java(Compiled Code))
at oracle.jdbc.driver.OraclePreparedStatement.execute(OraclePreparedStatement.java:3445)
at oracle.jdbc.driver.OracleCallableStatement.execute(OracleCallableStatement.java:4394)
at com.ibm.ws.rsadapter.jdbc.WSJdbcPreparedStatement.pmiExecute(WSJdbcPreparedStatement.java:632)
at com.ibm.ws.rsadapter.jdbc.WSJdbcPreparedStatement.execute(WSJdbcPreparedStatement.java:427)
.....



ググるとこういうページが。

http://www.ibm.com/developerworks/forums/thread.jspa?messageID=14018143

どうも、IBM Java の JIT と JDBC ドライバの相性が悪いらしい。JIT を無効にする(環境変数 JAVA_COMPILER に NONE をセットする)ことで回避できるようだが、パフォーマンスに問題が出てくるかもしれないし、本番環境で同じ問題が出やしないかと心配なので、JDBC ドライバを本番環境で使っているもので置き換えて実行してみた。

ちなみに本番での環境は以下のとおり。本番では上の問題は出ていない。


  • Oracle JDBC ドライバのバージョン: 10.2.0.2.0
  • Oracle のバージョン:10.2.0 Enterprise Edition
  • WAS のバージョン:IBM WebSphere Application Server - Express, 6.0.2.17
  • WAS で使われる Java のバージョン:Classic VM (build 1.4.2, J2RE 1.4.2 IBM build cxia32142-20061124 (SR7) (JIT enabled: jitc)


上にあるとおり、本番環境では、Oracle JDBC ドライバのバージョンは、10.2.0.2.0 なのだが、これに置き換えてみると、表題のエラーは発生しなくなった。

Wednesday, January 16, 2008

Java 例外のコスト

気になったので次のようなコードで計測してみた。JDK のバージョンは 1.5 。


public static void main(String a[]) throws Exception {
int c = 0;
System.out.println(new Date());
for (int i = 0; i < 1000000000; i++) {
c = test(0);
}
System.out.println(new Date());
System.out.println(c);
}

static int test(int a) {
int c = 0;
if (a == 0) {
String s = new String("13");
return Integer.parseInt(s);
} else {
return c + 1;
}

}

ここでは、軽い仕事をして普通にリターンしている。


public static void main(String a[]) throws Exception {
int c = 0;
System.out.println(new Date());
for (int i = 0; i < 1000000000; i++) {
try {
c = test(0);
} catch (Exception e) {
c = i;
}

}
System.out.println(new Date());
System.out.println(c);
}

static int test(int a) throws Exception {
int c = 0;
if (a == 0) {
throw new Exception();
} else {
return c + 1;
}

}

ここでは、何もせずに例外をスローすることでリターンしているのだが、前のコードの 16 倍 の時間がかかった。Exception をその都度生成するのではなく、static に確保しておいて、それをスローするだけにすると時間はほぼ同じだった。

結局、例外の throw/catch そのものは大したことないが、例外オブジェクトの生成に相当のコストがかかることが分かる。この理由は、例外オブジェクトの生成時にスタックトレースのスナップショットをとることにある。これが高くつくというわけだ。(パフォーマンスへの目: 開発プロセスを改善する 中の例外のコスト)

したがって、パフォーマンスの面からだけでも、例外は例外状態のみに使用すべきであり、正常フローには使っていけないということになる。そして、正常フローでの使用を避けるために状態チェックメソッドや、null 等の区別できる値を返したりする。

ところで、例外がサポートされていない言語では、関数内でエラーが起こったときにそのエラーの種類を複数のエラーコードで返すようになっていることがよくある。

この場合、例外が使えるならば、回復可能と考えられるエラーにはチェックされる例外、そうでないエラーには実行時例外を対応させて例外クラスを定義し、エラーが起こったらそれをスローすることになるだろう。そうすれば、エラーコードと違って、呼び出し側に対応を強制することができ、コードの信頼性・保守性が向上する。これはまさに正しい例外の使い方といえる。

ただ、このようなメソッドでは例外状態であるにも関わらず、それが頻繁に発生するものがある。例外の生成には非常にコストがかかるので、これは困ったことであり、コードの信頼性・保守性とパフォーマンスとが両立しない状態になってしまっている。

このとき、例外を使わずにすまそうとするなら、状態チェックメソッドの導入が考えられるが、エラーが複数ある場合にはそれぞれに状態チェックメソッドが必要となり、メソッドが使いにくくなるし、成功か失敗かを判定するのにデータベースへのアクセスが必要だったりすると逆にパフォーマンスが悪くなってしまったりする。

これの解決案としては、例外のコストは例外オブジェクトの生成が大部分であり、throw/catch にはほとんどかからなかったことから、例外オブジェクトをあらかじめ static で作成しておき、それを使いまわすということが考えられる。

ただ、これをやると、スタックトレースの取得が不可能になるのと、(マルチスレッドで使用されるときには)エラー発生時の状況を例外オブジェクトに組み込むことができないという代償はある。

Friday, January 04, 2008

Linux を CD ブートして、ハードディスクの障害を復旧する方法

障害というほどではないが、実験中に重要な共有ライブラリ(libcrypt)の名前を変更してからルートを抜けてしまい、戻せなくなってしまった。sudosu もこのライブラリを使っているのでルートになることも新たにログインすることもできない。

そこで、インストールに使った Ubuntsu の CD からブートして復旧を試みた。当然、BIOS の設定はハードディスクより CD を優先してブートするようになっていなくてはならないが、幸い、これは問題なかった。

Ubuntsu のインストールCD でブートすると、最初にメニュー画面が出てきて Rescue Mode のような選択肢が選べるようになっている。とりあえずこれを選んでみたが、ファイル名を元に戻すだけなので、すぐに Alt-F2 のようにして別コンソールに移動して、シェルに抜けた。そこで


# mkdir /a
# mount -t ext3 /dev/sda1 /a


のようにして問題のディスクをマウントし、ファイル名を修正した。ちなみに /a は RAM ディスク上に作られている。

後は umount してリブート、CD をイジェクトして終わり。うまく元通りにできた。

Thursday, January 03, 2008

PostgreSQL 8.2.5 のインストール

PostgreSQL 8.1.5 をインストール と同様にソースからビルドしてインストール。実は以前インストールしていたのを忘れてて、再度インストールし直した。8.1.5 はシステムから削除した。

8.1.5 同様にインストールしたが、ライブラリまわりのトラブルは解決しておいたので、今回はスムーズにビルド&インストールがうまくいった。ただ、インストールやリグレッションテストのときにファイルの権限を PostgreSQL 用の管理ユーザに変更して実行するのは面倒なので、最初から PostgreSQL 用の管理者ユーザでビルド&インストールした。

つまり、postgres のホームに 8.2.5 のソースを展開し、

./configure --prefix=/opt/pgsql-8.2.5
make
make install

を実行。/opt/pgsql-8.2.5 はあらかじめルートで作成しておき、

# chown -R postgres:postgres pgsql-8.2.5

を実行しておいた。/opt/pgsql-8.2.5/ には /opt/pgsql というシンボリックリンクを張り、これを環境変数 PGDATA にセットするようにした。

リグレッションテストだが、インストールする前に最上位ディレクトリまたは src/test/regress/ ディレクトリで

gmake check

で実行できる。インストールしてから、src/test/regress/ 内で実行したとき、FAILED が結構出たのだが、make clean を実行してから再度実行するとうまくいった。どういう原因だったのかは分からない。

インストール後、管理者ユーザで

initdb -E UTF8

でデフォルトエンコーディング UTF8 でデータベースクラスタを作成し、

postgres

でデータベースサーバを起動できる。アーキテクチャが変更され、postmaster コマンドは廃止されている。サーバを起動すると、/src/test/regress/ で

gmake installcheck
gmake installcheck-parallel

とすれば、インストール後にリグレッションテストができる。パラレルチェックでは複数プロセスを起動して、テストスクリプトを並行に実行する。

Wednesday, January 02, 2008

java.util.List の size() メソッドをループの範囲チェックでは呼び出さない方がいい

List をループで使用する場合、次のようなコードを書きがちだ。

List li = new ArrayList();
// この辺は省略
for(int i = 0; i < li.size(); i++) {
Object o = li.get(i);
}

これは、次のように書いた方がいい。

List li = new ArrayList();
// この辺は省略
for(int i = 0, n = li.size(); i < n; i++) {
Object o = li.get(i);
}

僕のマシンでこのループを 10億回まわしてみたところ、ループの都度 size() を呼び出すと 43秒ほどかかり、初期化のところで1回だけ呼び出すようにすると 25秒ほどかかった。

修正後の所要時間は修正前の 58% にまで下がっている。

また、ここでは List の要素にアクセスするのにイテレータではなく、get(int) メソッドを使っている。これがふさわしいのは、List の実装がランダムアクセスの場合だけだ。LinkedList などの場合は、ランダムアクセスではなくシーケンシャルアクセスなので、イテレータを使うべきだ。

以上のことは、Joshua Bloch 著 『Effective Java』 の29項に触発されて調べてみた。

Saturday, October 13, 2007

ある関数従属性をもつ属性に対してどのようなリレーションを作成すべきか?

この投稿、近いうちに削除します。もう少し、うまくまとめて再度アップするつもり。

正規化の議論は、あるリレーションが正しい構造か?更新異常がない構造になっているか?という観点からのもの。ここでは逆に属性をどのように組み合わせていけば正しい構造のリレーションになるかを考えてみる。

まず、2つの属性 A, B は次の3つの関係(リレーションシップ)を持ちうる。

  • 1対1のリレーションシップ。このとき、A → B かつ B → A の関係である。
  • 多対1のリレーションシップ。このとき、A → B かつ B NOT → A の関係である。
  • 多対多のリレーションシップ。このとき、A NOT → B かつ B NOT → A の関係である。


1対1のリレーションをもつとき。

この場合、A と B は少なくとも1つのリレーション R 上に共存しなければならず、A または B が R のキーとなる。A が B を決め、B が A を決めるということは、A と B がそれぞれ何らかのエンティティ・インスタンスを決定していると捉えて、そのリレーションをつくるわけだ。そして、ある属性 C をこの R に追加するには、A → C または B → C が成立しなくてはならない。また、データの不要な重複を避けるために複数のリレーションに A, B が共存することは避けるべき。リレーション間のリレーションシップを作るために A または B が他のリレーションシップに存在する場合はあるが(いわゆる外部キーであろう)、その場合、あるリレーションには A、もう1つのリレーションには B というようにはせず、A または B の1つのみを使うようにした方がいい。

多対1のリレーションをもつとき。

この場合、A と B はリレーション内で共存でき、そのときは A がそのリレーションのキーになる。ある属性 C をそのリレーションに追加できるのは A → C が成立する場合だけ。

多対多のリレーションをもつとき。

この場合、A と B はリレーション内で共存でき、そのとき、(A,B) がそのリレーションのキーになる。(A,B) → C が成立するときだけ C をそのリレーションに追加できる。ただ、(A,B) NOT → C でも、C NOT → (A,B) だとすると、この関係は (A,B) と C との多対多のリレーションシップである。したがって、C をリレーションに追加して、(A,B,C) をキーとできる。ただし、こうしてしまうと、もはやこのリレーションは違う主題を表していることになるので、リレーションの名前を変えた方がよいということになる。

ちなみに、上で C → (A,B) が成立としたら、関数従属性の性質から C → A かつ C → B が成立する。この場合、多対1のリレーションということで、C,A,B が共存することは可能であり、C がそのリレーションのキーということになるだろう。

このように属性を組み合わせてリレーションを作っていけば、それらは DK/NF を満たすようになるだろう。

上の多対多のリレーションで (A,B,C) をキーにする場合だが、こうできるのは、多値従属性 A →→ B | C または B →→ A | C が成立しない場合に限られる。そうでないと 4NF にならず、当然 DK/NF も満たさないから更新時異常が起こってしまう。

たとえば、R{ 教授名, クラス } というリレーションがあって、教授とクラスの組み合わせがキーだとする。授業をする教室が教授の気まぐれで決まるとしたら、使用教室、という属性と (教授名, クラス) とは多対多のリレーションシップをもつ。そして明らかに、ここには多値従属性はないので、{ 教授名, クラス, 使用教室 } をキーとした新しいリレーションをつくることができる。

ドメイン/キー正規形(DK/NF)

この投稿、近いうちに削除します。もう少し、うまくまとめて再度アップするつもり。

リレーションの更新時異常を解消していく過程で、1NF, 2NF, 3NF, BCNF, 4NF, 5NF と正規化が進んでいく。5NF まで正規化されると更新時異常は起こらなくなるので、5NF がある意味究極の正規形である。

この正規形の系列とは別にドメイン/キー正規形(DK/NF)がある。この DK/NF でも更新時異常は起こらず、また更新時異常が起こらないリレーションは DK/NF であることが示されている。リレーションが DK/NF であるための条件は以下のとおり。

「リレーション上のすべての制約条件がキーとドメインの定義の論理的な帰結である場合にそのリレーションは DK/NF となる」

制約条件とは、「属性の静的な値を決定するためのルールで、真か偽かを明確に決定できるもの」である。したがって、関数従属性や多値従属性、リレーション間・リレーション内の制約条件などが該当する。なお、「静的な値」なので時間に依存する条件は含まない。たとえば、セールスマンの給料を表す属性値を変更するときは前の値より多くなければならない、などだ。

ドメインとは、属性の定義域のことだ。物理的定義と意味的定義の2つの側面があるが、DK/NF では物理的意味だけを考えればいい。

DK/NF の利点は、ドメインとキーというデータベース実務者にとって基本的な概念のみに関連していること。リレーションのすべての制約条件がこの基本的な概念の論理的な帰結になっているかどうかをチェックすればいいので、1NF から 5NF の正規化よりもずっと理解しやすくなっている。

直感的には、リレーションが1つの主題のみをもつようにすれば、DK/NF になるだろう。

Tuesday, October 09, 2007

多値従属性と 4NF

この投稿、近いうちに削除します。もう少し、うまくまとめて再度アップするつもり。


多値従属性(MVD)とは関数従属性(FD)を一般化した概念。したがって、FD は MVD でもあるということになる。

A, B, C, X をそれぞれ属性の集合としたとき、リレーション R{A,B,C,X} 上で、値 (A値,C値) に対応する B の値の集合が A値によってのみ決まり、C値とは独立しているとき、B は A に多値的に従属する、または A は B を多値的に決定する、という。これを A →→ B と書く。

「A の値によってのみ決まり C の値とは独立」とはどういうことかというと、ある A値をもつタプルの集合における B値、C値の集合を考えたとき、その「B値の集合」と、「C値の集合のそれぞれの C値に対応する B値の集合」が一致することをいう。このことから、A →→ B であるとき、かつそのときに限り A →→ C でもあることが分かる。そこで、この関係を A →→ B | C とも書く。

ここで X が空集合だとしてみる。すると R{A,B,C} はすべての属性の組み合わせをキーとする BCNF なるだろう。このとき、関数従属性と更新時異常で述べたように、R には FD を原因とする更新時異常は起きないが、A →→ B | C の MVD を原因とした更新時異常が発生する。

これを解消するために R{A,B,C} は2つのリレーション {A,B} と {A,C} に無損失分解することができる。結局、BCNF であるリレーション R をさらなる正規形に分解できたことになるのだが、この新しい正規形を第4正規形(4NF)と呼ぶわけだ。

4NF とは、「BCNF であり、かつそこにおけるすべての MVD が候補キーからの FD であるリレーション」と定義できる。

ちなみに無損失分解とは、分解してできた新たなリレーションを結合したときに元のリレーションに戻せるような分割のことだ。結局、正規化とは、更新時異常を避けるためにリレーションを無損失分解することだといえる。