Oracle への JDBC 接続に時間がかかる現象の回避方法

 まさか、そんな原因だったとは......

 いや、あのですね、Oracle への JDBC 接続の話なんですけれども、以前から、なーんか矢鱈と Connect に時間かかるなぁ、とは思ってたんですよ(特に繰り返し接続した時)。

 でも、企業の案件で使う場合は、まずコネクションプールを利用しますし、個人的な趣味の場合は Oracle よりもっと軽量な DB を使うことが殆どだったので、あんまり気にしていなかったんですね。

 ていうか、Oracle さんはそういうコなんだと、ハナから諦めてたというか(笑)

 なので、その場でバッチを書いてちょろっと流したりする時だけ、「なんで接続にこんな時間かかんだよ、イライラ」みたいに思っていたのですが、この度、思いがけないことが原因だったことを知りました。

 はー、そうだったのかー。

 回避方法があることも分かりましたので、追加情報と合わせて記事にしておきます。

スポンサーリンク

読み込み中です。少々お待ち下さい

原因

 まず最初にお断りしておきますが、これは Linux や BSD のような Unix ライクなシステムで発生する問題です(そういや最近 Solaris 触ってないな)。

 Windows では、同じ原因による問題は発生しません。

 それから、いまお使いの環境で JDBC 接続に時間がかかっていない場合は、ここで紹介しているような設定なりを適用する必要はありません(そんな人は、この記事を開いてないでしょうが)。

 と、前置きが終わったところで、今回の件を知る機会を与えてくれた記事を引用します。

 えぇ~、そんなことが原因だったのか......

 具体的には、以下の記事が分かりやすいです。

  • Linusのクリスマスプレゼントが引き起こした問題(2/2) - @IT
  •  Linuxカーネルには、環境ノイズをエントロピー源として利用し、乱数生成に活用する機能があります。具体的には人間の利用するキーボードなどの入力デバイスのタイミングの「ぶれ」などを活用します。

     /dev/randomで提供されている乱数生成の仕組みは、この機能を活用していますが、サーバ運用の場合に問題が発生します。サーバは、人間が入力デバイスで直接操作しない場合が多いため、エントロピーが足りなくなり、十分にランダムな乱数が生成できなくなるのです。

     このように十分なエントロピーが得られない場合、/dev/urandomは妥協して疑似乱数を返しますが、/dev/randomはそうした場合、十分なエントロピーが得られるまで待ちます。そのため、/dev/randomを利用しているアプリケーションがそこで止まってしまい、パフォーマンスが異様に遅くなるのです。この問題に遭遇した人も多いのではないでしょうか?

 言われてみれば、聞いたことがあるような。

 とはいえ、自分の中では、少なくとも Oracle の JDBC 接続が遅い問題とは、マッタク結びついていませんでした。

 原因がコレだとすると、OS 以下の環境が全く同じだとしても、使用状況次第で Oracle の JDBC 接続問題が発生する場合としない場合があるんでしょうか。

 なかなか厄介な話ですね。ただ、サーバーの場合は、ほぼ発生しそうな気もしますけど。

 ていうか、これ、コネクションプールを使ってても、プールの実装次第では問題になりそうな気もしますね(普通はまず大丈夫でしょうけど、独自実装とかの場合は気をつけた方がいいかも)。

回避方法

 java コマンド自体の起動オプションでシステムプロパティを設定するか、マスターセキュリティプロパティファイルそのものを編集することで、この問題を回避できます。

 マスターセキュリティプロパティファイルの位置は、通常「$JAVA_HOME/jre/lib/security/java.security」になります。

 パッケージマネージャ経由でインストールしたなどの理由で、環境変数 JAVA_HOME が設定されておらず、「which java」と入力しても「/usr/bin/java」みたいに返される環境では、おそらく「/usr/lib/jvm」配下に jdk(あるいは jre)がインストールされています。

 それらしいパスが見当たらない場合は、シェルを開いて「java -verbose」を実行すると、ダラダラと標準出力される情報から判断がつきます。

 さて、それでは「java.security」ファイル自身に記載されているコメントを参考にしながら、具体的な方法を見ていきましょう。

java.security を書き換えて回避する場合

上で説明した「java.security」ファイルを開いて、 securerandom.source=file:/dev/random という行を、↓のように書き換え securerandom.source=file:/dev/urandom

起動オプションで回避する場合

java 実行時に、以下のようにオプションを記述します % java -Djava.security.egd=file:/dev/urandom MainClass ※両方指定した場合、こちらが優先されます

 まー、要するに、前段の引用にあるように、「/dev/random」ではなく「/dev/urandom」の方を使って妥協するということですね(ほとんどの場合で問題無いとは思いますが、別の処理との兼ね合いで妥協してはいけない場合は、この方法は取らないようにしましょう)。

 ちなみに、こちらで試した環境の1つでは、平均で 15,000 ミリ秒(約 15 秒)ほどかかっていたローカルホストの Oracle への接続が、平均 400 ミリ秒(約 0.4 秒)程度まで短縮されました。

 このくらいの速度なら、その場限りのちょっとした処理を流したい時に、ベタ書きでいちいち接続し直しても、あまり気にならないですよね。

改善が見られない場合は

 これだけだと、「最初に紹介した記事に任せておけばいいじゃん」と怒られてしまいそうなので、ちょっぴり追加情報を。

 ジツは、ウチの環境だと、上で書いた方法では、接続にかかる時間に改善が見られませんでした。

 では、どうやったら改善したのかというと、最初に引用した2つ記事の前者の方で紹介されている書き方、具体的には以下のように書いたら、何故か上手くいきました。

% java -Djava.security.egd=file:/dev/./urandom MainClass

 そして、ふと思いついたこの書き方でも、上手く回避できたという。

% java -Djava.security.egd=file:///dev/urandom MainClass

「java コマンドを実行する時に "-Djava.security.egd=file:/dev/urandom" ってオプションつけたのに、接続時間が全然変わらないよ!」

「てか、java.security には、最初から securerandom.source=file:/dev/urandom が設定されてたのに遅いんですけど」

 といった場合は、試してみても良いかも知れません。

 ちなみに、「java.security」を書き換えるやり方でも、
「securerandom.source=file:/dev/urandom」だとダメで、
「securerandom.source=file:/dev/./urandom」または
「securerandom.source=file:///dev/urandom」だと大丈夫でした。

 マッタク存在しない場所を指定すると、デフォルトの「/dev/random」が使われたっぽいので、書き方のせいで無視されて上手くいったという訳でも無さそうです。

 うーん、これは......

 あっ、もしかして、コレのこと?

 いやー、こんなの、知らないと分かんないよ。

 ていうか、もしかして固定で見てるってこと? いいの、コレ?(笑)

 まぁ、仕方がないので、今回のような問題に対処する場合は、「file:/dev/./urandom」と指定した方が間違いなさそうです。

 でも、いくつか試した限りでは「file:/dev/./urandom」と書かないと上手くいかなかったのは古い Java 6 の環境で、最新の Java 7、8 の環境だと「file:/dev/urandom」でも大丈夫だったので、最近の環境なら気にしなくてもいいのかも。

どちらで設定すべきか

 ところで、起動オプションで指定する方法と、プロパティファイルを書き換える方法、どちらの方法で設定するのがより望ましいでしょうか。

 個人的には、java 実行時のオプションで指定する方を推したいところです。java.security ファイルを元から書き換えちゃうのも、どうかなーと思いますし。

 アルゴリズムに NativePRNGNonBlocking が指定できるようになった Java 8 以降に関する意見ですが、参考までに以下の記事を。

  • [java] SecureRandom のアルゴリズムの選択について - tokuhirom blog
  • インターネットを見ていると、securerandom.source を変更して /dev/urandom を見るようにしろ、という記述を見かけますが、これは Java 8 の世界では間違ったやり方だと私は思います。

    NativePRNGNonBlocking を利用するように実装を変更するのが正しいアプローチだと私は考えます。

 す、すみません、そっちを書き換える方法も紹介してしまいました。Oracle JDBC の中の話なので、許してください。なんでもはしませんけど。

 あと、関連して、こちらもご参考まで。

雑談

 以下は、なんか良く分からんけど不思議だなぁ、という不確かな情報ですので、雑談として聞き流してください。

 今回、浅く知り得た限りでは、どうやら java.security ファイルの「securerandom.source」が「/dev/random」になったのは Java 8 からっぽくて、実際、手元にファイルがあっていますぐに確認できる範囲では、Java 6 と Java 7 は「/dev/urandom」、Java 8 では「/dev/random」が設定されているんですが、Java 7 以前に「/dev/random」がデフォルトで設定されていた環境もあった筈なんですよね(見たことがあるので)。

 Oracle にも、Java 7(2011 年リリース)より前の 2008 年の文書として、こんなのが残ってますし。

  • 乱数生成に伴う JVM の遅延の回避
  • Sun の JVM で乱数を生成するために使用されるライブラリは、UNIX プラットフォームではデフォルトで /dev/random に依存します。

    <meta name="LASTUPDATED" content="08/15/08 16:17:57" />

 正直、あまり気にしたことが無かったのですが、どこかの時点で切り替わったりしたんでしょうか。

 ということで、手持ちに残ってる一番古い環境を引っ張りだして確認したところ、JDK 1.4.2 は「/dev/random」だったみたいですね。JDK 1.5 で「/dev/urandom」に変わって、それが JDK 1.7 まで続き、JDK 1.8 になって「/dev/random」に戻ったっぽい(JDK 1.3.1 まで遡ると、「securerandom.source」プロパティ自体が存在しない模様)。

 あれ、でも、Java 6 で「/dev/random」が設定されていた環境を見たことある気がするんですが、勘違いだったのかな。パッケージによっても違うとか? 漁ればどっかに載ってる気もしますが、まぁ、これ以上はもういいや(笑)

 ていうか、(実質的には bea とはいえ)oracle ドメインの文書で、java.security ファイルの「securerandom.source」書き換えが提案されちゃってますね。

 ま、まぁ、まだ Java 6 時代の文書ですから。

 あと、ちょっと気になったので、新規に Debian と CentOS をインストールして Oracle への JDBC 接続を試してみたんですが、Debian は「/dev/random」だろうが「/dev/urandom」だろうが、どちらを指定しても挙動が変わらないように見えるんですよね(一瞬で接続が完了する)。ところが、「head -n 1 /dev/random」は返ってこない。

 んでもって、CentOS の方は「/dev/random」を指定すると、この記事で問題にしているように、ちゃんと JDBC 接続がブロックされるのです(ちゃんとってのもオカシイけど)。

 そして、いま手元に無いんですが、以前に Oracle への JDBC 接続に時間がかかる経験をした環境も、Red Hat Linux でした。

 なので、個人的な経験も踏まえたいまのところの印象としては、Red Hat 系を使っている場合は、この辺りのプロパティを特に気にした方が良さそうということは言えるのかな、と思います(すみませんが、他のディストリビューションまでは、今回ちょっと手が回りませんでした)。

 BSD ベースということで、いちおう Mac でも試してみたのですが、当然ながら十分に使い込んでいる環境なので、あんまり参考にはならなかったです(さすがに、この為に Mac をインストールし直す気にはなれなかった)。

おわりに

 もうちょっと広く読まれそうな記事を書こうといつも反省しているのに、気が付くと誰も見ないような記事を書いてしまう病。

この記事をシェア
  • このエントリーをはてなブックマークに追加
  • Share on Google+
  • この記事についてツイート
  • この記事を Facebook でシェア