Redshift と PostgreSQL に同時に JDBC 接続する

 
TECHSCORE Advent Calendar 2015 の 1日目の記事です。

はじめに

Amazon Redshift を使っています。大きなサイズのデータを簡単にお安く分析できるので重宝しているのですが、小さなデータに対する応答時間は決して短くありません。やはりここは PostgreSQL などの普通の RDBMS と得意分野で使い分けたいところ。
ということで Redshift と PostgreSQL の両方に対して同時に JDBC 接続しようとしたんですが、ちょっと困ったことが起こったのでいろいろ調べてみました。

いきなり解決策を知りたい方はこちらへ。

何が困ったの?

JDBC を使って、Redshift と PostgreSQL の両方に接続してみます。極々シンプルな実装です。

★の2箇所で、得られた Connection のクラス名を出力しています。
以下のようにコンパイルします。

実行時にはクラスパスに Redshift と PostgreSQL それぞれの JDBC ドライバの設定が必要です。以下のように実行してみました。

両方とも Redshift 用のクラスが使われています。これはちょっと嬉しくありません。

何が起こったの?

何が起こったのが調べるために、DriverManager のソースを見てみます。
今回使用したメソッド DriverManager.getConnection(String url, ...) からは最終的にこちらのメソッドが呼ばれます。メンバ変数 registeredDrivers に入っている Driver を順番に取ってきて、最初に接続に成功した Driver が使われるという実装になっています。

では、registeredDrivers にはどのように登録されるのでしょうか。

ここの静的初期化子にそのヒントがあります。
DriverManager がロードされた時にこの静的初期化子が実行され、loadInitialDrivers() が呼ばれます。loadInitialDrivers() ではサービス・プロバイダ・ロード機能を使って、java.sql.Driver サービスをロード(ServiceLoader.load(Driver.class))します。
(システムプロパティ jdbc.drivers に関する処理もありますが、ここは今回は関係ないので省略します。)
サービス・プロバイダ・ロード機能では、クラスパスの順に jar を走査して、jar 内の META-INF/services/java.sql.Driver に記述されたクラスのインスタンスが作成されます。

jar 内の META-INF/services/java.sql.Driver の記述は、それぞれ
- Redshift / com.amazon.redshift.jdbc41.Driver
- PostgreSQL / org.postgresql.Driver
となっていますので、これらのインスタンスが new されます。

さて、new org.postgresql.Driver() までたどり着きました。ソースを見てみます。
こちらの静的初期化子から register() が呼ばれ、ここで DriverManager.registerDriver(Driver) が呼ばれています。このメソッドで registeredDrivers に Driver が登録されています。

これまでの記述を簡単にまとめます。
DriverManager を使用する際に、クラスパス順に JDBC ドライバがロードされて DriverManager に登録されます。
DriverManager.getConnection(String url, ...) では登録順に Driver を使って接続し、最初に接続に成功した Driver が返却される、という動きになっています。

次に、今回の実行での動きを見てみます。
クラスパスは Redshift 用ドライバ -> PostgreSQL 用ドライバの順に設定しています(③)。

①では [jdbc:redshift] スキーマに接続しようとします。
最初に Redshift 用ドライバを試します。当然接続に成功しますので、Redshift 用ドライバが使われます。

②では [jdbc:postgresql] スキーマに接続しようとします。
ここでも最初に Redshift 用ドライバを試しますが、

Note
jdbc:postgresql://エンドポイント:ポート/データベース という旧形式で指定されている JDBC URL は、まだ動作します。

Amazon Redshift 管理ガイド
こちらも接続に成功し、Redshift 用ドライバが使われてしまいます。

解決したい その1 クラスパスでなんとか...

クラスパスの順序を変更して、先に PostgreSQL 用のドライバに接続するようにすれば解決できます。
上記のソースそのまま、再コンパイルなしで、クラスパスだけ変更して実行します。

確かに意図通りの動作ですが、クラスパスは必ずしも明示的に設定できるとも限りませんので、この方法はいまひとつです。

解決したい その2 明示的にドライバを指定する...

自動解決されるドライバが意図したものと異なるならば、明示的にドライバを指定することで解決できます。
DriverManager.getConnection(String url, ...) の代わりに、Driver インスタンスを new して Driver.connect(String url, ...) を使います。

コンパイルして、実行すると、

意図通りの動作になっています。

でも、なんだかしっくりきません。確かに理屈はわかりますが、こんな面倒なことしなきゃいけないんでしょうか、Redshift のドライバは JDBC のバージョンが上がったらクラス名変わりそうだし...と思ったらスマートな解決方法がありました。

解決したい その3 決定版

Redshiftの JDBC ドライバー設定オプションに OpenSourceSubProtocolOverride というものがあります。

有効になっている場合、この設定は、Amazon Redshift JDBC ドライバと PostgreSQL JDBC ドライバとの間に発生する可能性のある競合を防ぎます。ご使用のアプリケーションが、Amazon Redshift JDBC ドライバを使ってクラスターに接続すると同時に、PostgreSQL JDBC ドライバを使って他のデータソースに接続する場合、PostgreSQL データソースに接続するために使用する JDBC URL にこの接続属性を追加します。

これを使います。

コンパイルして、実行します。

うまくいきました。

おわりに

Redshift 用のドライバを使って PostgreSQL に接続しても、単純な機能を使用している限りではエラーにもならず、データも取得できてしまいます。TIMESTAMP 型のデータを取り出そうとしたときに「変換できないよ!」と言われ、そこから慌てて調べました。
気付かずに使ってしまっているケースもあるかと思います。一度実際に使われているクラスを確認することをおススメします。

Comments are closed, but you can leave a trackback: Trackback URL.

Advent Calendar 2015の連載記事

  1. TECHSCORE Advent Calendar 2015
  2. Redshift と PostgreSQL に同時に JDBC 接続する
  3. Lombok で Spice up your Java!
  4. 画像を指定するだけ!非デザイナーでも簡単にそれっぽい配色ができるツールを作ってみた
  5. 新卒文系エンジニアの記録:配属半年間の失敗を振り返ってみた
  6. 非同期処理のすすめ
  7. ioDrive2の導入で支える、そのIOPS - 導入検討編.
  8. GoでパイプラインからSlackに通知する
  9. fuse でオレオレファイルシステムを作ってみた (Haskell で)
  10. Erlang はじめました
  11. ちょっと地味なビルドとリリースの話 (レガシーシステム改革、はじめの一歩)
  12. Java8 最速 boolean[] to Stream 選手権
  13. Google Apps の Directory API にてWebブラウザを介さずに認証する
  14. 風データをビジュアルに表現する
  15. マイクロフレームワーク「Ninja」を使ってみる
  16. 赤ちゃんvimmerからよちよちvimmerにクラスチェンジを果たすためのTips
  17. PostgreSQL FDW を作ってSQLでログ検索してみた
  18. Goで偽名ジェネレータを作りました
  19. 書き込み中に削除されたファイルを救出する
  20. 運用情報更新のススメ
  21. ちゃんと読んでくれましたか?
  22. Presto コネクターを実装する 第三回
  23. Ruby2.3を触ってみる
  24. Git 困ったときのtips集
  25. 5分で読む入門編:Java 8 ラムダ式 コレクション編(2)リストの検索
  26. CloudFront (+ S3) + JWPLAYER で様々なデバイスのブラウザから動画をストリーミング再生する