Amazon Redshift カスタムJDBCドライバを使い倒す

2015.07.07

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

はじめに

今年の春にAmazon Redshiftに最適化したカスタムJDBCドライバがリリースされました。Redshiftのアプリケーションエンジニアにとって気になる、従来のPostgreSQLのJDBCドライバとの違い、各種チューニングパラメタの使い方、パフォーマンスについてご報告します。

【AWS発表】カスタムODBC/JDBCドライバとQuery Visualization for Amazon Redshift より

我々のJDBCドライバはJDBC 4.1、4.0をサポートしており、オープンソース版のドライバのオプションと比較して、 keep aliveを標準で有効にし、メモリ管理を向上させることで最大35%性能向上が行えるような設定になっています。具体的には、メモリに保存する行数を設定でき、メモリ消費をステートメント毎に管理出来ます。

JDBCドライバーの入手

Amazon Redshift console の Connect Client から取得する方法とサイトからダウンロードする方法の2種類あります。 また、Amazon Redshift カスタムJDBCドライバは、JDBC 4.0 と JDBC 4.1の2種類のドライバが提供されています。

Connect Client からダウンロード

Amazon Management Console のAmazon Redshift console の [Connect Client] ページからダウンロードする方法が簡単です。現在(2015/7時点)、ここで取得できるドライバは最新バージョンとは限りませんので、最新のドライバが欲しい場合は「サイトからダウンロード」の方が良いでしょう。

redshift-download-jdbcdriver-1

サイトからダウンロード

新旧のドライバが欲しい場合は Redshift の管理ガイドのAmazon Redshift JDBC ドライバーをダウンロードします。からダウンロードしてください。私はこちらからダウンロードをすることをおすすめします。

JDBC 4.0と4.1の選択

 

JDBC 4.0 のクラス名は com.amazon.redshift.jdbc4.Driver 、JDBC 4.1 のクラス名は com.amazon.redshift.jdbc41.Driver です。

JDBC 4.1は JDBC 4.0 に対して、2つの機能が追加されています。以下の機能が必要かどうかで選択が分かれますが、新規開発の場合はJDBC 4.1がお勧めします。JDBC 4.1で追加された2つの機能をご紹介します。

try-with-resources

try-with-resources 文は、スコープの終わりで各リソースが確実に閉じられるようにします。実際のコードは後述の「接続の例」を御覧ください。従来必要であったオブジェクトのnull判定、closeが不要になり、コードがスッキリします。Java7以降のスタンダードなコーティングスタイルですね。try-with-resources のコードはJDBC 4.0のドライバでも動作しますが、この機能を使いたい場合は必ずJDBC 4.1を選択してください。

RowSetFactoryInterface と RowSetProviderClass

RowSet ObjectはResultSetを拡張したクラスです。RowSetはアプリケーション側で扱いやすいように機能が追加された5種類のインタフェースが提供されています。

JDBCドライバで接続する

入手したJDBCドライバを使って、Redshiftに接続してみます。

JDBC接続文字列の取得

AWS マネジメントコンソールのAmazon Redshift console を開き、[Clusters] ページに JDBC URL が表示されています。JDBC URLは jdbc:postgresql から jdbc:redshift に変更されています。

redshift-jdbc-url

接続の例

以下の例では、JDBC4.1で「サーバー証明書ありでSSL接続」する場合のサンプルコードです。ご紹介した try-with-resourcesを使っています。

//Open a connection and define properties.
System.out.println("Connecting to database...");
Properties props = new Properties();
props.setProperty("user", user);
props.setProperty("password", pass);
props.setProperty("ssl","true");
props.setProperty("sslfactory","com.amazon.redshift.ssl.NonValidatingFactory");

try (Connection conn = DriverManager.getConnection(url, props)) {
    Statement stmt = conn.createStatement();
    ResultSet rs = stmt.executeQuery(sql);
    //Get the data from the result set.
    while(rs.next()){
        System.out.println(rs.getInt(1));
    }
} catch (SQLException e) {
    e.printStackTrace();
}
System.out.println("Finished connectivity test.");

設定オプション

設定オプションはRedshiftに接続するときに JDBC URL に付加します。

Amazon JDBC ドライバの設定オプション

JDBC オプション 一致する PostgreSQL オプションの有無 デフォルト値 説明
AuthMech いいえ DISABLE 使用する認証メカニズム。以下の値を指定できます。

  • DISABLE - 非 SSL 接続を確立します。
  • ALLOW — 非 SSL 接続の確立を試みます。接続に失敗すると、次に SSL 接続を試みます。
  • REQUIRE — SSL 接続を確立します。
  • PREFER — SSL 接続の確立を試みます。接続に失敗すると、次に非 SSL 接続を試みます。
BlockingRowsMode いいえ 0 メモリに保持する行数。1 つの行が破棄されると、その代わりとして別の行がロードされます。
FilterLevel いいえ NOTICE クライアントが処理するメッセージの最小重要度。以下の値を指定できます(重要度が最も低い値から最も高い値の順)。

  • DEBUG
  • INFO
  • NOTICE
  • WARNING
  • LOG
  • ERROR
loginTimeout はい 0 サーバーに接続するときにタイムアウトするまで待つ秒数。接続を確立するときにこのしきい値よりも時間がかかると、接続は中止されます。このプロパティがデフォルト値の 0 に設定されていると、接続はタイムアウトしません。
loglevel はい ドライバーによって出力されるログ情報の量。デフォルトでは、ログ記録は行われません。情報はドライバーマネージャーの LogStream または LogValue オプションで指定されている場所に出力されます。以下の値を指定できます。

  • 2 (DEBUG) — 多くの詳細な情報をログに記録します。
  • 1 (INFO) — より少ない詳細をログに記録します。

Note

パフォーマンスに影響を与える可能性があるため、ドライバーの問題をトラブルシューティングする場合にのみ、このプロパティを使用してください。

PWD はい Amazon Redshift サーバーに接続するために使用するパスワード。
socketTimeout はい 0 タイムアウトする前にソケット読み込みオペレーションを待つ秒数。このオペレーションにこのしきい値よりも時間がかかると、接続は閉じます。このプロパティがデフォルト値の 0 に設定されていると、接続はタイムアウトしません。
ssl はい false SSL 接続を使用するかどうかを決定する値。以下の値を指定できます。

  • true — SSL を使用します。
  • false — SSL を使用しません。
sslFactory はい サーバー証明書なしで SSL 接続を行うために使用するファクトリ。以下の値を指定できます。

  • com.amazon.redshift.ssl.NonValidatingFactory
  • org.postgresql.ssl.NonValidatingFactory
tcpKeepAlive はい true TCP キープアライブを有効にするかどうかを決定する値。以下の値を指定できます。

  • true — TCP キープアライブを有効にします。
  • false — TCP キープアライブを無効にします。
TCPKeepAliveMinutes いいえ 5 TCP キープアライブの送信を開始する前にアイドル状態である時間のしきい値(分単位)。
UID はい Amazon Redshift サーバーに接続するために使用するユーザー名。

PostgreSQL JDBC ドライバとの相違点

AuthMech

SSL接続の制限や優先順位を指定できます。マニュアルにはデフォルト値が DISABLEと記載されていますが、ssl オプションを trueに設定すると、デフォルト値が REQUIREと同じ動きになります。それ以外の動きについてはマニュアルに記載されているとおりです。

BlockingRowsMode

このオプションを使用することによってメモリに保持する行数を指定できるようになりました。このオプションを指定することでクライアントのメモリの使い方を最適化できるようになります。

FilterLevel

このオプションに severity を設定すると、出力されるメッセージの severity レベルを変更できます。

tcpKeepAlive

PostgreSQLのデフォルト false でしたが、Redshift Custom ドライバでは true になります。SQL Workbench/Jの設定で tcpKeepAlive オプションに true を設定しない、長いクエリ実行中に接続が切断されるトラブルに遭いましたが、そのような心配はありません。

sslFactory

PostgreSQLのJDBCドライバでは org.postgresql.ssl.NonValidatingFactory でしたが、Redshift JDBCドライバでは com.amazon.redshift.ssl.NonValidatingFactory になります。従来との互換を考慮され org.postgresql.ssl.NonValidatingFactory を設定しても、同じように設定が有効になりますのでご安心ください。といっても、将来的に deprecatedになる可能性があるので、できる限り新しい com.amazon.redshift.ssl.NonValidatingFactory を設定したほうがよいでしょう。

JDBCパラメータ

Javaのソースコードの中で、SQL関連オブジェクトに対してパラメータを設定することで有効になります。

JDBCフェッチサイズパラメータ

JDBC を使用して大きなデータセットを取得する際にユーザー側でメモリ不足エラーが発生することを避けるため、JDBC フェッチサイズパラメータを指定して、クライアントがデータをバッチ単位でフェッチするように設定できます。なお、FetchSizeパラメタは PostgreSQL JDBC 4.1 のドライバでも提供されています。

  • デフォルト動作(0)はすべての行がキャッシュされる
  • カーソルを使用したクエリは全件フェッチにフォールバックされる
  • この機能を利用するには接続が自動コミット·モードが false でなければならない

詳細については、JDBC フェッチサイズパラメータの設定 を参照してください。

以下の例では、setFetchSizeメソッドで500(レコード)を設定しています。接続に対しては事前に自動コミットを false に設定する必要があることを忘れないで下さい。

try (Connection conn = DriverManager.getConnection(url, props)) {
    // make sure autocommit is off
    conn.setAutoCommit(false);
    Statement st = conn.createStatement();
    // Turn use of the cursor on.
    st.setFetchSize(500);
    ResultSet rs = st.executeQuery(sql);
    while (rs.next()) {}
} catch (SQLException e) {
    e.printStackTrace();
}

JDBC maxRows パラメータ

Amazon Redshift は JDBC は maxRows パラメータを認識しません。その代わり、結果セットを制限するには LIMIT 句を指定します。また、OFFSET 句を使用して結果セット内の特定の開始点にスキップすることもできます。

以下の例では、lo_ordertotalprice(総受注価格)が2番めに高いレコードを取得しています。

SELECT * FROM lineorder ORDER BY lo_ordertotalprice DESC OFFSET 2 LIMIT 1;

クエリタイムアウトの方法は?

Amazon Redshift は JDBC によるクエリタイムアウト指定は提供されていません。RedshiftはWLMのクエリグループのキューに対してWLM タイムアウトを設定できますが、WLM以外にクエリを実行する前にクエリーのタイムアウトする時間(ms)を設定する方法があります。

SET statement_timeout TO 10000;

上記のタイムアウト設定するとアプリケーションからJavaの例外としてフックすることができます。

java.sql.SQLException: [Amazon](500310) Invalid operation: Query (3261) cancelled on user's request;
at com.amazon.redshift.client.messages.inbound.ErrorResponse.toErrorException(Unknown Source)
at com.amazon.redshift.client.PGMessagingContext.handleErrorResponse(Unknown Source)
at com.amazon.redshift.client.PGMessagingContext.getOperationMetadata(Unknown Source)
at com.amazon.redshift.client.PGMessagingContext.getOperationMetadata(Unknown Source)
at com.amazon.redshift.client.PGMessagingContext.handleMessage(Unknown Source)
at com.amazon.jdbc.communications.InboundMessagesPipeline.getNextMessageOfClass(Unknown Source)
at com.amazon.redshift.client.PGMessagingContext.doMoveToNextClass(Unknown Source)
at com.amazon.redshift.client.PGMessagingContext.getReadyForQuery(Unknown Source)
at com.amazon.redshift.client.PGMessagingContext.getOperationMetadata(Unknown Source)
at com.amazon.redshift.client.PGMessagingContext.getOperationMetadata(Unknown Source)
at com.amazon.redshift.client.PGMessagingContext.handleMessage(Unknown Source)
at com.amazon.jdbc.communications.InboundMessagesPipeline.getNextMessageOfClass(Unknown Source)
at com.amazon.redshift.client.PGMessagingContext.doMoveToNextClass(Unknown Source)
at com.amazon.redshift.client.PGMessagingContext.getReadyForQuery(Unknown Source)
at com.amazon.redshift.client.PGMessagingContext.getOperationMetadata(Unknown Source)
at com.amazon.redshift.client.PGMessagingContext.getOperationMetadata(Unknown Source)
at com.amazon.redshift.client.PGMessagingContext.getBindComplete(Unknown Source)
at com.amazon.redshift.client.PGClient.directExecute(Unknown Source)
at com.amazon.redshift.dataengine.PGIQueryExecutor.execute(Unknown Source)
at com.amazon.jdbc.common.SStatement.executeNoParams(Unknown Source)
at com.amazon.jdbc.common.SStatement.executeQuery(Unknown Source)
Caused by: com.amazon.support.exceptions.ErrorException: [Amazon](500310) Invalid operation: Query (3261) cancelled on user's request;
... 21 more

カスタムJDBCドライバ vs PostgreSQL JDBCドライバ

2015/7現在、それぞれ最新のAmazon Redshift JDBC4.1(RedshiftJDBC41-1.1.2.0002.jar) と PostgreSQL JDBC 4.1(9.4-1201 JDBC 41)で比較しました。設定オプションはデフォルトの状態で計測します。

Redshiftはリーダーノードで受け付けたクエリーはC++のコードに変換され、コンパイルした結果を、コンピュートノードに配布して実行しますので、一般的な行指向のRDBと比較すると最初のリクエストのオーバーヘッドは大きい傾向があります。また、クライアントの処理性能を評価するので、クエリに条件指定や集計などサーバーに負荷がかかるようなクエリではなく、単純にテーブルの全件を取得する時間を計測します。

パフォーマンスの計測条件

  • クエリ性能検証 リージョン:バージニアリージョン(us-east-1)
  • ノードタイプとノード数:dc1.large x 2
  • クライアントのEC2インスタンス:c3.2xlarge
  • VPC内同一subnet
  • データ:AWSが提供しているサンプルデータ(s3://awssampledbapnortheast1/ssbgz/lineorder)

計測用のコード(Java)

	public static void main(String[] args) {
		long startTime = System.currentTimeMillis();
		Properties props = new Properties();
		props.setProperty("user", user);
		props.setProperty("password", pass);

		long cnt = 0;
		long totolcnt = 0;
		try (Connection conn = DriverManager.getConnection(url, props)) {
			Statement stmt = conn.createStatement();
			System.out.println("Default FetchSize : " + stmt.getFetchSize());
			ResultSet rs = stmt.executeQuery(sql);
			while(rs.next()){
				cnt++;
				if(cnt >= 1000000) {
					totolcnt+=cnt;
					cnt=0;
					System.out.println("progress : " + totolcnt);
				}
			}
			totolcnt+=cnt;
		} catch (SQLException e) {
			e.printStackTrace();
		}
		System.out.println("totolcnt : " + totolcnt);
		System.out.println("elapsed time : " + (System.currentTimeMillis() - startTime) + " ms");
	}

31万レコードの取得(デフォルト)

両者の性能は大きく変わりませんでした。

  • Amazon Redshift JDBC4.1:1392ミリ秒
  • PostgreSQL JDBC 4.1:1375 ミリ秒

103万レコードの取得(デフォルト)

データ取得に要する時間にほとんど違いがありませんでした。しかし、PostgreSQL JDBC 4.1の方がメモリ使用量が多いことが気になります。

  • Amazon Redshift JDBC4.1:4440ミリ秒
  • PostgreSQL JDBC 4.1:4438 ミリ秒

1000万レコードの取得(デフォルト)

PostgreSQL JDBC 4.1はCPU使用率90%、ネットワークIOがほとんどない状態が続いた後、OutOfMemoryErrorでプロクラムが終了しました。一方、Amazon Redshift JDBC 4.1はCPU使用率12%、ネットワークIOが310Mbpsで、メモリの増減もなくデータの取得が完了しました。Amazon Redshift JDBCドライバが安定性に優れるというのはこの点を指しているのかもしれません。

  • Amazon Redshift JDBC4.1:43374ミリ秒
  • PostgreSQL JDBC 4.1:計測不能(無応答の後、OutOfMemoryErrorにて終了)

1000万レコードの取得(JDBCフェッチサイズパラメータの設定)

PostgreSQL JDBC 4.1はデフォルト設定が全てのデータを取得するように設定されているので、フェッチサイズを500に変更したところ、CPU使用率の上昇もおさまり、OutOfMemoryErrorによるプロクラム終了は収束しました。PostgreSQL JDBC 4.1はユースケースに応じてチューニングする必要がありそうです。

stmt.setFetchSize(500);
  • Amazon Redshift JDBC4.1:43945ミリ秒
  • PostgreSQL JDBC 4.1:43374ミリ秒

最後に

Amazon Redshift JDBC4.1は、パラメタチューニングなどしなくても、メモリの使用量が少なく、どのような条件下でも安定動作することが確認できました。また、keepaliveがデフォルトになりこれまで以上に扱いやすく仕様が改められています。今回の検証では、「最大35%性能向上」は見られませんでしたが、大きなデータを取り扱うように考慮して設計されたドライバなので、システムの更新タイミングや新規開発ではぜひご検討ください。