Hologres は PostgreSQL のワイヤープロトコルと互換性があるため、PostgreSQL JDBC Driver を使用する任意のツールまたはアプリケーションから接続できます。このガイドでは、JDBC 接続の設定、データの書き込みとクエリ、パフォーマンスチューニングについて説明します。
このページの内容
前提条件
開始する前に、次の項目を用意してください:
データベースが作成済みの Hologres インスタンス
インスタンスのエンドポイント、ポート、データベース名 (Hologres コンソールの[インスタンス詳細]ページの[ネットワーク情報]で確認できます)
AccessKey ID と AccessKey シークレット
注意事項
PostgreSQL JDBC Driver 42.3.2 以降を使用してください。このバージョンでは CVE-2022-21724 の脆弱性が修正されています。これより前のバージョンにはセキュリティリスクがあります。
書き込みパフォーマンステストでは Virtual Private Cloud (VPC) ネットワークを使用してください。パブリックネットワークでは、パフォーマンステストのベンチマークを満たせません。
Hologres は、同一トランザクション内で DDL と書き込み操作を混在させることをサポートしていません。DDL と書き込み操作は別々のトランザクションで実行してください。混在させると、
ERROR: INSERT in ddl transaction is not supported now.エラーが返されます。autoCommitはtrueのままにしてください。これは JDBC のデフォルト値であるため、コード内で明示的に commit を呼び出さないでください:Connection conn = DriverManager.getConnection(url, user, password); conn.setAutoCommit(true);Hologres の書き込み操作はロールバックできません。
Connection.rollback()でも、SQL のROLLBACKステートメントでも、すでに実行済みの INSERT、UPDATE、DELETE は取り消せません。また、書き込みは直ちに反映されます。つまり、commit 前でも他の接続から参照可能です。障害補償に JDBC トランザクションを使用しないでください。代わりに、アプリケーション側でべき等性または調整を実装してください。たとえば、
INSERT ON CONFLICTを使用してリトライをべき等にできます。
JDBC を使用した Hologres への接続
手順 1:ドライバーの依存関係の追加
多くの SQL クライアントツールには PostgreSQL ドライバーが組み込まれています。利用できる場合は、それを使用してください。Java アプリケーションの場合は、Maven プロジェクトに PostgreSQL JDBC Driver を追加します。jdbc.postgresql.org/download からダウンロードし、バージョン 42.3.2 以降 (最新の安定版を推奨) を使用してください。
Hologres は標準の PostgreSQL JDBC Driver を使用します。Hologres 専用のドライバーを別途インストールする必要はありません。
次の依存関係を pom.xml に追加します:
<dependencies>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<!-- 42.3.2 以降を使用します。最新の安定版を推奨します。 -->
<version>42.7.13</version>
</dependency>
</dependencies>手順 2:接続文字列の作成
接続文字列の形式は次のとおりです:
jdbc:postgresql://<ENDPOINT>:<PORT>/<DBNAME>?user=<ACCESS_ID>&password=<ACCESS_KEY>必須パラメーター
パラメーター | 説明 |
| Hologres インスタンスのネットワークアドレスです。コンソールでは、アドレスとポートが |
| Hologres インスタンスのポートは、上記の ドメイン名 の値にあるコロン以降の部分です。 |
| Hologres 内のデータベース名です。 |
| AccessKey ID です。ハードコーディングせず、環境変数に格納してください。 |
| AccessKey シークレットです。ハードコーディングせず、環境変数に格納してください。 |
オプションパラメーター
必要に応じて、これらを接続文字列に追加します。複数のパラメーターは & で区切ります。
パラメーター | 効果 |
| 接続にアプリケーション名のタグを付け、[slow query checklist] で識別しやすくします。 |
| バッチ挿入を 1 つの複数値 INSERT ステートメントに書き換え、書き込みスループットを向上させます。 |
| デフォルトのスキーマを設定します。MaxCompute からの外部テーブル自動ロードを有効化した後に外部テーブルをクエリする場合に必要です。MaxCompute のプロジェクト名は、同名のスキーマにマッピングされます。 |
| PostgreSQL JDBC ドライバーを、拡張プロトコル ( |
preferQueryMode について
拡張プロトコルでは、PreparedStatement のパラメーターが明示的な型とともに送信され、ドライバーは暗黙の型変換を行いません。そのため、整数列を varchar パラメーターと比較すると、ERROR: operator does not exist: integer = character varying のようなエラーで失敗する場合があります。この型不一致エラーが発生した場合は、接続文字列に preferQueryMode=simple を追加してください。これにより、ドライバーは SQL をプレーンテキストとして送信し、サーバーがコンテキストからパラメーター型を推論します。
simple を使用すると、ドライバーはサーバーサイドのプリペアドステートメントを使用しなくなります。そのため、プリペアドステートメントモードが依存する SQL コンパイル結果のキャッシュが失われます。高頻度クエリやバッチ書き込みではスループットが目に見えて低下します。これは、本トピックの別の箇所で推奨している「高スループットのためにプリペアドステートメントモードを使用する」という推奨と相反します。
型不一致エラーは、次の優先順で解決してください:
列の型に一致する setter を使用します。たとえば、INT 列には
setIntを使用し、setStringは使用しません。または、SQL 内でパラメーターを明示的にキャストします。例:
where id = ?::intサードパーティの ORM や BI ツールなど、呼び出し側コードを変更できない場合に限り、
preferQueryMode=simpleを使用します。バッチ書き込み性能も必要な場合は、reWriteBatchedInserts=trueと組み合わせることができます。これら 2 つは併用できます。
推奨オプションを有効化した接続文字列の例:
jdbc:postgresql://<ENDPOINT>:<PORT>/<DBNAME>?user=<ACCESS_ID>&password=<ACCESS_KEY>&ApplicationName=myApp&reWriteBatchedInserts=truePreparedStatement クエリが、クエリパラメーターと列の型不一致により ERROR: operator does not exist: integer = character varying のようなエラーで失敗する場合は、接続文字列に preferQueryMode=simple を追加してください:
jdbc:postgresql://<ENDPOINT>:<PORT>/<DBNAME>?user=<ACCESS_ID>&password=<ACCESS_KEY>&preferQueryMode=simple手順 3:環境変数への認証情報の格納
接続文字列に認証情報をハードコーディングすると、セキュリティリスクになります。代わりに環境変数に格納してください。Linux の場合は、次の内容を ~/.bash_profile に追加します:
export ALIBABA_CLOUD_USER=<ACCESS_ID>
export ALIBABA_CLOUD_PASSWORD=<ACCESS_KEY>手順 4:接続してクエリを実行
接続がハングしないように、socketTimeout、loginTimeout、tcpKeepAlive を設定してください。これらは PGProperty では SOCKET_TIMEOUT、LOGIN_TIMEOUT、TCP_KEEP_ALIVE として公開されています。次の例を参照してください。
次の例では、環境変数から認証情報を読み取り、タイムアウトプロパティを設定し、Hologres に接続して、標準の Statement を使用して基本的な SELECT クエリを実行します:
import org.postgresql.PGProperty;
import java.sql.*;
import java.util.Properties;
public class HologresTest {
private void jdbcExample() throws SQLException {
String user = System.getenv("ALIBABA_CLOUD_USER");
String password = System.getenv("ALIBABA_CLOUD_PASSWORD");
String url = String.format(
"jdbc:postgresql://<ENDPOINT>:<PORT>/<DBNAME>?currentSchema=<SCHEMA_NAME>&user=%s&password=%s",
user, password
);
Properties props = new Properties();
// 実際のクエリ実行時間に基づいて SOCKET_TIMEOUT を設定し、
// クエリ完了前にタイムアウトしないようにします。
PGProperty.SOCKET_TIMEOUT.set(props, 3600);
PGProperty.LOGIN_TIMEOUT.set(props, 60);
PGProperty.TCP_KEEP_ALIVE.set(props, true);
try (Connection conn = DriverManager.getConnection(url, props)) {
try (Statement st = conn.createStatement()) {
String sql = "SELECT pk, f1 FROM test_tb WHERE pk > 0 LIMIT 100";
try (ResultSet rs = st.executeQuery(sql)) {
while (rs.next()) {
// 1 列目の値を読み取ります
String c1 = rs.getString(1);
}
}
}
}
}
}データの書き込みとクエリ
データの書き込み
JDBC では、Statement またはプリペアドステートメントモードを使用してデータを書き込めます。書き込み操作にはプリペアドステートメントモードを使用してください。このモードでは、サーバーが SQL コンパイル結果をキャッシュするため、書き込みレイテンシが低減し、スループットが向上します。バッチサイズは 256 の倍数に設定してください。推奨される最小バッチサイズは 256 です。
このセクションの例では、次のテーブルを使用します。あらかじめデータベースに作成してください。
CREATE TABLE test_tb (
pk int PRIMARY KEY,
f1 text,
f2 timestamptz,
f3 double precision
);バッチ挿入
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.text.SimpleDateFormat;
/*
* プリペアドステートメントモードでデータをバッチ書き込みします。
* バッチサイズ:256 行(推奨される最小値)。
*/
private static void writeBatchWithPreparedStatement(Connection conn) throws Exception {
try (PreparedStatement stmt = conn.prepareStatement("insert into test_tb values (?,?,?,?)")) {
int batchSize = 256;
for (int i = 0; i < batchSize; ++i) {
stmt.setInt(1, 1000 + i);
stmt.setString(2, "1");
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
java.util.Date parsedDate = dateFormat.parse("1990-11-11 00:00:00");
stmt.setTimestamp(3, new java.sql.Timestamp(parsedDate.getTime()));
stmt.setDouble(4, 0.1);
stmt.addBatch();
}
stmt.executeBatch();
}
}INSERT ON CONFLICT を使用した Upsert
競合時に既存行を更新するには、PostgreSQL の INSERT ON CONFLICT 構文を使用します。宛先テーブルにはプライマリキーが必要です。
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.text.SimpleDateFormat;
private static void insertOverwrite(Connection conn) throws Exception {
try (PreparedStatement stmt = conn.prepareStatement(
"insert into test_tb values (?,?,?,?), (?,?,?,?), (?,?,?,?), (?,?,?,?), (?,?,?,?), (?,?,?,?) " +
"on conflict(pk) do update set f1 = excluded.f1, f2 = excluded.f2, f3 = excluded.f3"
)) {
int batchSize = 6;
for (int i = 0; i < batchSize; ++i) {
stmt.setInt(i * 4 + 1, i);
stmt.setString(i * 4 + 2, "1");
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
java.util.Date parsedDate = dateFormat.parse("1990-11-11 00:00:00");
stmt.setTimestamp(i * 4 + 3, new java.sql.Timestamp(parsedDate.getTime()));
stmt.setDouble(i * 4 + 4, 0.1);
}
int affectedRows = stmt.executeUpdate();
System.out.println("affected rows => " + affectedRows);
}
}次の例では、書き込み操作にプリペアドステートメントモードを使用し、繰り返しの INSERT でのスループットを向上させます:
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.UUID;
private void jdbcPreparedStmtExample() throws SQLException {
String user = System.getenv("ALIBABA_CLOUD_USER");
String password = System.getenv("ALIBABA_CLOUD_PASSWORD");
String url = String.format(
"jdbc:postgresql://<ENDPOINT>:<PORT>/<DBNAME>?currentSchema=<SCHEMA_NAME>&user=%s&password=%s",
user, password
);
try (Connection conn = DriverManager.getConnection(url)) {
// 10行を1回の複数値INSERT文で挿入します。
String sql = "insert into test_tb (pk, f1) values " +
"(?, ?), (?, ?), (?, ?), (?, ?), (?, ?), " +
"(?, ?), (?, ?), (?, ?), (?, ?), (?, ?)";
try (PreparedStatement st = conn.prepareStatement(sql)) {
for (int j = 0; j < 10; ++j) {
// 一意のプライマリキーとランダムな文字列を設定します
st.setInt(j * 2 + 1, 4000 + j);
st.setString(j * 2 + 2, UUID.randomUUID().toString());
}
System.out.println("affected row => " + st.executeUpdate());
}
}
}データのクエリ
Hologres のテーブルからデータをクエリするには、標準の SQL SELECT ステートメントを使用します。手順 4 の基本的な SELECT の例が、このパターンを示しています。
パラメーター化されたクエリには、プリペアドステートメントモードを使用し、列の型に一致する setter でパラメーターを渡してください:
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
private static void queryWithPreparedStatement(Connection conn) throws Exception {
String sql = "select pk, f1, f2, f3 from test_tb where pk = ?";
try (PreparedStatement stmt = conn.prepareStatement(sql)) {
// pk は INT 列のため setInt を使用します。setString で String を渡すと
// 拡張プロトコルでは次のエラーで失敗します:
// ERROR: operator does not exist: integer = character varying
stmt.setInt(1, 1000);
try (ResultSet rs = stmt.executeQuery()) {
while (rs.next()) {
System.out.println(rs.getInt("pk") + ", " + rs.getString("f1"));
}
}
}
}Druid コネクションプールの設定
Hologres への接続には Druid 1.1.12 以降を使用してください。
使用上の注意:
接続を再利用して接続チャーンを回避するために、
keepAlive=trueを設定してください。Druid 1.2.12 から 1.2.21 には、
connectTimeoutとsocketTimeoutを指定しない場合にデフォルトが 10 秒になる既知の問題があります。該当する場合は、新しいバージョンにアップグレードしてください。インスタンスサイズとワークロードに基づいて、
initialSize、minIdle、maxActiveを設定してください。
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
init-method="init" destroy-method="close">
<!-- コンソールのインスタンス設定ページにあるエンドポイント URL -->
<property name="url" value="${jdbc_url}" />
<!-- ユーザーアカウントの AccessKey ID -->
<property name="username" value="${jdbc_user}" />
<!-- ユーザーアカウントの AccessKey シークレット -->
<property name="password" value="${jdbc_password}" />
<!-- プールサイズ:インスタンスサイズとワークロードに応じて調整します -->
<property name="initialSize" value="5" />
<property name="minIdle" value="10" />
<property name="maxActive" value="20" />
<!-- プールからの接続取得を最大 60 秒待機します -->
<property name="maxWait" value="60000" />
<!-- アイドル接続を 2 秒ごとにチェックします -->
<property name="timeBetweenEvictionRunsMillis" value="2000" />
<!-- 10 分以上アイドルの接続を破棄します -->
<property name="minEvictableIdleTimeMillis" value="600000" />
<property name="maxEvictableIdleTimeMillis" value="900000" />
<property name="validationQuery" value="select 1" />
<property name="testWhileIdle" value="true" />
<!-- borrow/return 時に接続をテストしません(オーバーヘッドを削減) -->
<property name="testOnBorrow" value="false" />
<property name="testOnReturn" value="false" />
<property name="keepAlive" value="true" />
<property name="phyMaxUseCount" value="100000" />
<property name="filters" value="stat" />
</bean>GUC パラメーターの設定
Grand Unified Configuration (GUC) パラメーターは、タイムアウトなどのセッションレベルの動作を制御します。接続時に PGProperty.OPTIONS を使用して設定します。
次の例では、statement_timeout と idle_in_transaction_session_timeout を 12,345 ミリ秒に設定します:
import org.postgresql.PGProperty;
import java.sql.*;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
public class GucDemo {
public static void main(String[] args) {
String hostname = "hgpostcn-cn-xxxx-cn-hangzhou.hologres.aliyuncs.com";
String port = "80";
String dbname = "demo";
String jdbcUrl = "jdbc:postgresql://" + hostname + ":" + port + "/" + dbname;
Properties properties = new Properties();
properties.setProperty("user", "xxxxx");
properties.setProperty("password", "xxxx");
// セッションレベルの GUC パラメーターを設定します
PGProperty.OPTIONS.set(properties,
"--statement_timeout=12345 --idle_in_transaction_session_timeout=12345");
try {
Class.forName("org.postgresql.Driver");
Connection connection = DriverManager.getConnection(jdbcUrl, properties);
PreparedStatement preparedStatement =
connection.prepareStatement("show statement_timeout");
ResultSet resultSet = preparedStatement.executeQuery();
while (resultSet.next()) {
ResultSetMetaData rsmd = resultSet.getMetaData();
int columnCount = rsmd.getColumnCount();
Map<String, Object> map = new HashMap<>();
for (int i = 0; i < columnCount; i++) {
map.put(rsmd.getColumnName(i + 1).toLowerCase(), resultSet.getObject(i + 1));
}
System.out.println(map);
}
} catch (Exception exception) {
exception.printStackTrace();
}
}
}使用可能な GUC パラメーターの一覧については、GUC parameters を参照してください。
パフォーマンスチューニング
書き込みスループットを最大化するには、次の方法を試してください:
VPC ネットワークを使用してください。 パブリックネットワークではレイテンシが増加するため、最適な書き込み性能を達成できません。
バッチ書き換えを有効にしてください。 接続文字列に
reWriteBatchedInserts=trueを追加します。これにより、個々の INSERT が 1 つの複数値ステートメントに書き換えられ、スループットが大幅に向上します。プリペアドステートメントモードを使用してください。 サーバーが SQL コンパイル結果をキャッシュし、行あたりのレイテンシを低減します。
バッチサイズを 256 の倍数に設定してください。 効果が出る最小バッチサイズは 256 です。倍数を大きくするほど、スループットがさらに向上します。自動バッチ処理には Holo Client を使用してください。
すべてのパフォーマンスオプションを有効化した接続文字列の例:
jdbc:postgresql://<ENDPOINT>:<PORT>/<DBNAME>?ApplicationName=<APPLICATION_NAME>&reWriteBatchedInserts=true負荷分散
Hologres V1.3 以降では、JDBC で複数の読み取り専用セカンダリインスタンスを設定し、読み取りワークロードを分散できます。設定手順については、「JDBC-based load balancing」をご参照ください。