Hologres は PostgreSQL のワイヤープロトコルと互換性があるため、PostgreSQL JDBC ドライバーを使用するツールやアプリケーションはすべて接続できます。このガイドでは、JDBC 接続の設定、データの書き込みとクエリ、パフォーマンスチューニングについて説明します。
このページの内容
前提条件
開始する前に、以下が準備できていることを確認してください。
-
データベースが作成済みの Hologres インスタンス
-
インスタンスのエンドポイント、ポート、データベース名 (Hologres コンソールの [インスタンス詳細] ページの [ネットワーク情報] で確認できます)
-
AccessKey ID と AccessKey Secret
注意事項
-
JDBC 接続を介してデータを書き込むには、PostgreSQL JDBC ドライバー 42.3.2 以降を使用してください。
-
書き込み性能テストには、Virtual Private Cloud (VPC) ネットワークを使用してください。パブリックネットワークでは、性能テストのベンチマークを満たすことはできません。
-
Hologres は単一トランザクションでの複数書き込みをサポートしていません。
autoCommitをtrueに設定してください。JDBC のデフォルトはtrueなので、コード内で明示的に commit を呼び出さないでください。エラーERROR: INSERT in transaction is not supported nowが表示された場合は、autoCommitを明示的に設定してください。Connection conn = DriverManager.getConnection(url, user, password); conn.setAutoCommit(true);
JDBC を使用した Hologres への接続
ステップ 1:ドライバーの依存関係の追加
ほとんどの SQL クライアントツールには、PostgreSQL ドライバーが組み込まれています。利用可能な場合は、それを使用してください。Java アプリケーションの場合は、PostgreSQL JDBC ドライバーを Maven プロジェクトに追加します。jdbc.postgresql.org/download からダウンロードし、バージョン 42.3.2 以降 (最新の安定バージョンを推奨) を使用してください。
Hologres は標準の PostgreSQL JDBC ドライバーを使用します。インストールが必要な Hologres 専用のドライバーはありません。
pom.xml に以下の依存関係を追加します。
<dependencies>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>42.3.2</version>
</dependency>
</dependencies>
ステップ 2:接続文字列の構築
接続文字列のフォーマットは以下の通りです。
jdbc:postgresql://<ENDPOINT>:<PORT>/<DBNAME>?user=<ACCESS_ID>&password=<ACCESS_KEY>
必須パラメーター
| パラメーター | 説明 |
|---|---|
<ENDPOINT> |
Hologres インスタンスのネットワークエンドポイント。Hologres コンソールの [インスタンス詳細] ページの [ネットワーク情報] で確認できます。コードが実行されるネットワーク環境に一致するエンドポイントを選択してください。ネットワークタイプが一致しない場合、接続エラーが発生します。 |
<PORT> |
Hologres インスタンスのポート。同じ [ネットワーク情報] セクションで確認できます。 |
<DBNAME> |
Hologres 内のデータベース名。 |
<ACCESS_ID> |
ご利用の AccessKey ID。ハードコーディングするのではなく、環境変数に保存してください。 |
<ACCESS_KEY> |
ご利用の AccessKey Secret。ハードコーディングするのではなく、環境変数に保存してください。 |
オプションパラメーター
必要に応じて、これらのパラメーターを接続文字列に追加します。複数のパラメーターは & で区切ります。
| パラメーター | 効果 |
|---|---|
ApplicationName=<name> |
アプリケーション名で接続にタグを付け、スロークエリチェックリストで接続を特定しやすくします。 |
reWriteBatchedInserts=true |
バッチ挿入を単一の複数値 INSERT 文に書き換えることで、書き込みスループットを向上させます。 |
currentSchema=<schema> |
デフォルトのスキーマを設定します。MaxCompute からの外部テーブルの自動ロードを有効にした後、外部テーブルをクエリする際に必要です。MaxCompute プロジェクト名は、同じ名前のスキーマにマッピングされます。 |
推奨オプションを含む接続文字列の例:
jdbc:postgresql://<ENDPOINT>:<PORT>/<DBNAME>?user=<ACCESS_ID>&password=<ACCESS_KEY>&ApplicationName=myApp&reWriteBatchedInserts=true
ステップ 3:環境変数への認証情報の保存
接続文字列に認証情報をハードコーディングすると、セキュリティリスクが生じます。代わりに環境変数として保存してください。Linux では、以下の内容を ~/.bash_profile に追加します。
export ALIBABA_CLOUD_USER=<ACCESS_ID>
export ALIBABA_CLOUD_PASSWORD=<ACCESS_KEY>
ステップ 4:接続とクエリの実行
接続のハングを防ぐために、PGProperty を介して socket_timeout、login_timeout、tcp_keep_alive を設定します。以下の例をご参照ください。
以下の例では、環境変数から認証情報を読み取り、タイムアウトプロパティを設定し、Hologres に接続し、標準の Statement を使用して基本的な SELECT クエリを実行します。
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 * FROM table where xxx limit 100";
try (ResultSet rs = st.executeQuery(sql)) {
while (rs.next()) {
// 最初の列の値を読み取る
String c1 = rs.getString(1);
}
}
}
}
}
}
データの書き込みとクエリ
データの書き込み
JDBC の Statement モードまたは Prepared Statement モードを使用してデータを書き込むことができます。書き込み操作には Prepared Statement モードを使用してください。このモードでは、サーバーが SQL のコンパイル結果をキャッシュするため、書き込みレイテンシが短縮され、スループットが向上します。バッチサイズは 256 の倍数に設定してください。推奨される最小バッチサイズは 256 です。
バッチ挿入
/*
* Prepared Statement モードを使用してデータをバッチで書き込みます。
* バッチサイズ: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");
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 を使用したアップサート
競合時に既存の行を更新するには、PostgreSQL の INSERT ON CONFLICT 構文を使用します。送信先のテーブルにはプライマリキーが必要です。
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");
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);
}
}
以下の例では、書き込み操作に Prepared Statement モードを使用しており、繰り返し挿入のスループットを向上させます。
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)) {
String sql = "insert into test values" +
"(?, ?), (?, ?), (?, ?), (?, ?), (?, ?), " +
"(?, ?), (?, ?), (?, ?), (?, ?), (?, ?)";
try (PreparedStatement st = conn.prepareStatement(sql)) {
for (int i = 0; i < 10; ++i) {
for (int j = 0; j < 2 * 10; ++j) {
st.setString(j + 1, UUID.randomUUID().toString());
}
System.out.println("affected row => " + st.executeUpdate());
}
}
}
}
データのクエリ
標準の SQL SELECT 文を使用して、Hologres テーブルからデータをクエリします。ステップ 4 の基本的な SELECT の例は、このパターンを示しています。
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 Secret -->
<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" />
<!-- 借用/返却時に接続をテストしない (オーバーヘッドを削減) -->
<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 パラメーター」をご参照ください。
パフォーマンスチューニング
書き込みスループットを最大化するには、以下のプラクティスを適用してください。
-
VPC ネットワークを使用する。 パブリックネットワークはレイテンシを発生させ、最適な書き込みパフォーマンスの達成を妨げます。
-
バッチの再書き込みを有効にする。 接続文字列に
reWriteBatchedInserts=trueを追加します。これにより、個々の挿入が単一の複数値の文に書き換えられ、スループットが大幅に向上します。 -
Prepared Statement モードを使用する。 サーバーが SQL のコンパイル結果をキャッシュするため、行ごとのレイテンシが短縮されます。
-
バッチサイズを 256 の倍数に設定する。 有効な最小バッチサイズは 256 です。より大きな倍数にすると、さらにスループットが向上します。自動バッチ処理には、Holo Client を使用してください。
すべてのパフォーマンスオプションを有効にした接続文字列の例:
jdbc:postgresql://<ENDPOINT>:<PORT>/<DBNAME>?ApplicationName=<APPLICATION_NAME>&reWriteBatchedInserts=true
負荷分散
Hologres V1.3 以降では、JDBC で複数の読み取り専用セカンダリインスタンスを構成して、読み取りワークロードを分散できます。設定手順については、「JDBC ベースの負荷分散」をご参照ください。