Amazon Aurora
Aurora のユーザーガイド

Amazon Aurora PostgreSQL を使用する際のベストプラクティス

このトピックには、Amazon Aurora PostgreSQL DB クラスターの使用およびデータ移行のベストプラクティスとオプションに関する情報が含まれます。

Amazon Aurora PostgreSQL を使用した高速フェイルオーバー

Aurora PostgreSQL でフェイルオーバーをより高速で実行するには、いくつかの方法があります。このセクションでは、次の方法のそれぞれについて説明します。

  • TCP のキープアライブの積極的な実行として設定することで、失敗イベントとして読み取りタイムアウトの時間切れになる前に、サーバーの回答待ちにより実行時間が長いクエリを終了します。

  • Java DNS キャッシュタイムアウトを積極的な実行として設定することで、Aurora の読み取り専用エンドポイントが繰り返される接続試行の読み取り専用ノードで適切にサイクルすることを確実にします。

  • JDBC 接続文字列で使用されるタイムアウト変数をできるだけ低く設定します。短期間の実行クエリと長期間の実行クエリに対して、別々の接続オブジェクトを使用します。

  • 指定される Aurora エンドポイントの読み書きを使用することで、クラスターへの接続を確立します。

  • サーバー側の失敗のテストアプリケーション回答には RDS API を使用し、クライアント側の失敗のテストアプリケーション回答にはパケットドロップツールを使用します。

TCP キープアライブパラメータの設定

TCP キープアライブのプロセスは簡単です。TCP 接続を設定するには、一連のタイマーを関連付けます。キープアライブタイマーがゼロに達したら、キープアライブプローブパケットを送信します。キープアライブプローブに回答を受信した場合、接続が引き続き確立され、稼働中であると推測できます。

TCP キープアライブパラメータを有効化して積極的な実行に設定することで、クライアントがデータベースに接続できなくなった場合に、すべての有効な接続が速やかに切断されることを確実にします。このアクションは、アプリケーションが接続できる新しいホストを選択するなどの適切な対応を可能にします。

以下の TCP キープアライブパラメータは、次のように設定する必要があります。

  • tcp_keepalive_time は、ソケットからデータが送信されない場合に (ACK はデータと見なされません)、キープアライブが送信されてからの秒単位の時間を制御します。次の設定が推奨されます。

    tcp_keepalive_time = 1

  • tcp_keepalive_intvl は、最初のキープアライブパケットが送信されてから引き続きのパケットが送信されるまでの時間を秒単位で制御します (tcp_keepalive_time パラメータを使用して設定)。次の設定が推奨されます。

    tcp_keepalive_intvl = 1

  • tcp_keepalive_probes は、アプリケーションに通知される前に発生する、認知されていないキープアライブプローブ数です。次の設定が推奨されます。

    tcp_keepalive_probes = 5

この設定で、データベースの応答が停止してから 5 秒後以内にアプリケーションに通知します。アプリケーションネットワークでキープアライブがドロップする頻度が高い場合には、tcp_keepalive_probes 値をより高く設定できます。これにより、その後実際の障害を検出するためにかかる時間が増えますが、より信頼性が低いネットワークではバッファが増えます。

Linux で TCP キープアライブパラメータを設定する

  1. TCP キープアライブパラメータの設定方法をテストする場合は、次のコマンドを使用してコマンドライン経由で行うことをお勧めします。この推奨設定はシステム全体にわたります。つまり、SO_KEEPALIVE オプションを有効にしてソケットを作成する他のすべてのアプリケーションに影響します。

    sudo sysctl net.ipv4.tcp_keepalive_time=1 sudo sysctl net.ipv4.tcp_keepalive_intvl=1 sudo sysctl net.ipv4.tcp_keepalive_probes=5
  2. アプリケーションで作動する設定を見つけたら、この設定を維持するために次のライン (と作成したすべての変更) を /etc/sysctl.conf に追加する必要があります。

    tcp_keepalive_time = 1 tcp_keepalive_intvl = 1 tcp_keepalive_probes = 5

TCP キープアライブパラメータを Windows で設定するための詳細は、「Things You May Want to Know About TCP Keepalive」を参照してください。

高速フェイルオーバー用にアプリケーションを設定する

このセクションでは、Aurora PostgreSQL 固有の設定のうち、変更可能なものについて説明します。JDBC ドライバーの一般的なセットアップと設定に関するドキュメントは、PostgreSQL JDBC site で参照できます。

DNS キャッシュタイムアウトの短縮

アプリケーションがフェイルオーバー後に接続の確立を試みる場合、新しい Aurora PostgreSQL ライターは前のリーダーになります。これは DNS の更新が完全に伝達される前に、Aurora 読み取り専用エンドポイントを使用して検索できます。Java DNS TTL を低い値に設定すると、リーダーノード間における引き続く接続試行のサイクルに役立ちます。

// Sets internal TTL to match the Aurora RO Endpoint TTL java.security.Security.setProperty("networkaddress.cache.ttl" , "1"); // If the lookup fails, default to something like small to retry java.security.Security.setProperty("networkaddress.cache.negative.ttl" , "3");

Aurora PostgreSQL 接続文字列を高速フェイルオーバーに設定する

Aurora PostgreSQL フェイルオーバーを利用するには、アプリケーションの接続文字列に、1 つのホストではなく、ホストの一覧 (次の例の太字表示) を含める必要があります。Aurora PostgreSQL クラスターに接続するために使用できる接続文字列の例を次に示します。

jdbc:postgresql://myauroracluster.cluster-c9bfei4hjlrd.us-east-1-beta.rds.amazonaws.com:5432, myauroracluster.cluster-ro-c9bfei4hjlrd.us-east-1-beta.rds.amazonaws.com:5432 /postgres?user=<masteruser>&password=<masterpw>&loginTimeout=2 &connectTimeout=2&cancelSignalTimeout=2&socketTimeout=60 &tcpKeepAlive=true&targetServerType=master&loadBalanceHosts=true

最大限の可用性のため、RDS API への依存性を回避する目的で、接続に最適なオプションは、データベースに接続を確立したときにアプリケーションが読み込んだホスト文字列でファイルを維持することです。このホストの文字列にはクラスターで使用できるすべての Aurora エンドポイントがあります。Aurora エンドポイントの詳細については、「Amazon Aurora 接続管理」を参照してください。たとえば、以下のように、エンドポイントをローカルのファイルに保存できます。

myauroracluster.cluster-c9bfei4hjlrd.us-east-1-beta.rds.amazonaws.com:5432, myauroracluster.cluster-ro-c9bfei4hjlrd.us-east-1-beta.rds.amazonaws.com:5432

アプリケーションは、このファイルを読み込んで JDBC 接続文字列のホストセクションを入力します。DB クラスターの名前を変更すると、このエンドポイントは変更します。このイベントが発生したことをアプリケーションが取り扱うように確認します。

もう 1 つのオプションは、DB インスタンスノードの一覧を使用することです。

my-node1.cksc6xlmwcyw.us-east-1-beta.rds.amazonaws.com:5432, my-node2.cksc6xlmwcyw.us-east-1-beta.rds.amazonaws.com:5432, my-node3.cksc6xlmwcyw.us-east-1-beta.rds.amazonaws.com:5432, my-node4.cksc6xlmwcyw.us-east-1-beta.rds.amazonaws.com:5432

この方法の利点は、Aurora のエンドポイントを使用すると、接続試行ごとに 2 つのノードだけ実行されるのに対して、PostgreSQL JDBC 接続ドライバーは有効な接続を見つけるためにこの一覧のすべてのノードからループすることです。DB インスタンスノードを使用するデメリットは、クラスターにノードを追加、または削除すると、インスタンスエンドポイントの一覧が古くなり、接続ドライバーが接続する接続ホストを見つけることができない場合があることです。

以下のパラメータを積極的に実行するよう設定すると、アプリケーションがいずれか 1 つのホストに接続するために必要以上の待機を行わないことを確保できます。

  • loginTimeout - ソケット接続が確立されたあとで、アプリケーションがデータベースにログインするための待機時間を制御します。

  • connectTimeout - ソケットがデータベースに接続を確立するまでの待機時間を制御します。

そのほかのアプリケーションパラメータでは、アプリケーションの希望する積極性に応じて、接続プロセスの高速化を変更できます。

  • cancelSignalTimeout - 一部のアプリケーションでは、タイムアウトがあるクエリで「ベストエフォート」型キャンセル信号を送信できます。このキャンセル信号がフェイルオーバーパスにある場合は、デッドホストにこの信号を送信しないように、この信号を積極的に設定することを検討してください。

  • socketTimeout - このパラメータは、ソケットが読み取り操作で待機する時間を制御します。このパラメータは、グローバルな「クエリタイムアウト」として使用でき、すべてのクエリがこの値以上待機しないことを確保します。グッドプラクティスとしては、短期のクエリを実行する 1 つの接続ハンドラーで値を低く設定し、長期実行のクエリには別の接続ハンドラーを用意して、その値をより高く設定します。こうして、サーバーがダウンした場合に、TCP キープアライブパラメータが長期間実行しているクエリを切断するようにできます。

  • tcpKeepAlive - このパラメータを有効にして、設定した TCP キープアライブパラメータが優先されることを確保します。

  • targetServerType- このパラメータは、ドライバーが読み取り (スレーブ) あるいは書き込み (マスター) ノードに接続するかを制御するために使用します。指定できる値は: anymasterslave preferSlavepreferSlave 値は、まずリーダーに接続の確立を試行して失敗し、確立できるリーダー接続がない場合にライターに接続します。

  • loadBalanceHosts - true に設定すると、このパラメータはアプリケーションを選択できるホストの一覧からランダムに選択されたホストに接続します。

ホスト文字列を取得するそのほかのオプション

aurora_replica_status 関数を含めたいくつかのソースから、また Amazon RDS API を使用することで、ホスト文字列を取得できます。

アプリケーションは、DB クラスターのすべての DB インスタンスに接続でき、aurora_replica_status 関数を照会して、クラスターのライターを特定したり、クラスターのその他のリーダーノードを見つけたりできます。この関数を使用することで、接続先のホストを見つける時間を短縮できます。ただし、特定のネットワークエラーのシナリオでは、aurora_replica_status 関数から古い情報や不完全な情報が返される場合があります。

アプリケーションが接続するノードを見つけることを確実にするために良好な方法は、クラスターライターエンドポイントに接続を試行したのち、読み取りできる接続を確立できるまで、クラスターリーダーエンドポイントへの接続を試行します。これらのエンドポイントは、DB クラスターの名前を変更するまで変更しませんが、アプリケーションの静的メンバーとして一般的に残すか、アプリケーションが読み取るリソースファイルに保管することができます。

これらのエンドポイントの 1 つを使用して接続を確立したら、aurora_replica_status 関数を呼び出して残りのクラスターの情報を取得できます。たとえば、次のコマンドは aurora_replica_status 関数で情報を取得します。

postgres=> select server_id, session_id, highest_lsn_rcvd, cur_replay_latency_in_usec, now(), last_update_timestamp from aurora_replica_status(); server_id | session_id | vdl | highest_lsn_rcvd | cur_replay_latency | now | last_update_time -----------------------------------+--------------------------- -----------+-----------+------------------+--------------------+- ------------------------------+------- mynode-1 | 3e3c5044-02e2-11e7-b70d-95172646d6ca | 594220999 | 594221001 | 201421 | 2017-03-07 19:50:24.695322+00 | 2017-03-07 19:50:23+00 mynode-2 | 1efdd188-02e4-11e7-becd-f12d7c88a28a | 594220999 | 594221001 | 201350 | 2017-03-07 19:50:24.695322+00 | 2017-03-07 19:50:23+00 mynode-3 | MASTER_SESSION_ID | 594220999 | | | 2017-03-07 19:50:24.695322+00 | 2017-03-07 19:50:23+00 (3 rows)

したがって、たとえば接続文字列のホストセクションは、ライタークラスターとリーダークラスターの両方のエンドポイントから開始できます。

myauroracluster.cluster-c9bfei4hjlrd.us-east-1-beta.rds.amazonaws.com:5432, myauroracluster.cluster-ro-c9bfei4hjlrd.us-east-1-beta.rds.amazonaws.com:5432

このシナリオでは、アプリケーションは、マスターまたはスレーブのすべてのノードタイプに接続の確立を試行します。接続したら、まずコマンド SHOW transaction_read_only の結果をクエリして、ノードの読み書きステータスを調べることをお勧めします。

返されるクエリの値が OFF の場合、マスターノードへの接続に成功しました。返される値が ON の場合、アプリケーションは読み書き接続を要求しています。aurora_replica_status 関数を呼び出して、server_idsession_id='MASTER_SESSION_ID' があることを判断します。この関数は、マスターノードの名前を表示します。これは、以下に説明する「endpointPostfix」とともに使用できます。

注意すべき事項は、静的になったデータがあるレプリカに接続した場合です。この場合、aurora_replica_status 関数は最新ではない情報を表示することがあります。古さのしきい値はアプリケーションレベルで設定でき、サーバー時間と更新時間の差異を見ることで確認できます。一般的にアプリケーションは、aurora_replica_status 関数から返される情報が矛盾することによる 2 つのホスト間での急変を確実に回避することが必要です。つまり、アプリケーションは、aurora_replica_status 関数から返されるデータに安易に従うのではなく、最初にすべての既知のホストを試すことで判断して、エラーを生成する必要があります。

RDS API

インスタンスの一覧をプログラムによって見つけるには、AWS Java SDK を使用し、特に DescribeDbClusters API を使用します。以下に、java 8 でこれを行う方法の小さな例を紹介します。

AmazonRDS client = AmazonRDSClientBuilder.defaultClient(); DescribeDBClustersRequest request = new DescribeDBClustersRequest() .withDBClusterIdentifier(clusterName); DescribeDBClustersResult result = rdsClient.describeDBClusters(request); DBCluster singleClusterResult = result.getDBClusters().get(0); String pgJDBCEndpointStr = singleClusterResult.getDBClusterMembers().stream() .sorted(Comparator.comparing(DBClusterMember::getIsClusterWriter) .reversed()) // This puts the writer at the front of the list .map(m -> m.getDBInstanceIdentifier() + endpointPostfix + ":" + singleClusterResult.getPort())) .collect(Collectors.joining(","));

pgJDBCEndpointStr には、エンドポイントの形式一覧が含まれています。たとえば

my-node1.cksc6xlmwcyw.us-east-1-beta.rds.amazonaws.com:5432, my-node2.cksc6xlmwcyw.us-east-1-beta.rds.amazonaws.com:5432

「endpointPostfix」の変数を定数にして、クラスターの単一インスタンスへの DescribeDBInstances をクエリするようにアプリケーションを設定、または取得することができます。この値はリージョン内と単一のカスタマーにおいて一定となるため、アプリケーションが読み取るリソースファイルにこの定数を単に維持するように API 呼び出しを保存します。上記の例では、これは次のように設定されます。

.cksc6xlmwcyw.us-east-1-beta.rds.amazonaws.com

可用性目的では、API が応答しない、または応答時間が長すぎる場合に、DB クラスターの Aurora エンドポイント を使用するようにデフォルト設定することが良好なプラクティスです。エンドポイントは、DNS レコードを更新するためにかかる時間内で最新状態に保たれることが保証されます (通常、30 秒未満)。これもまた、アプリケーションが消費するリソースファイルに保存できます。

フェイルオーバーテスト

すべての場合において、2DB インスタンス以上の DB クラスターがあることが必要です。

サーバー側では、アプリケーションがどのように応答するかをテストするために使用する停止状態を一部の API で発生できます。

  • FailoverDBCluster - DB クラスターで新規の DB インスタンスをライターに昇格することを試行します。

    public void causeFailover() { /* * See http://docs.aws.amazon.com/sdk-for-java/v1/developer-guide/basics.html for more details on setting up an RDS client */ final AmazonRDS rdsClient = AmazonRDSClientBuilder.defaultClient(); FailoverDBClusterRequest request = new FailoverDBClusterRequest(); request.setDBClusterIdentifier("cluster-identifier"); rdsClient.failoverDBCluster(request); }
  • RebootDBInstance - フェイルオーバーはこの API では保証されません。ただし、ライターでデータベースがシャットダウンされます。これは接続の解除に伴うアプリケーションの応答をテストするために使用できます (ForceFailover パラメータは Aurora エンジンには適用されないため、代わりに FailoverDBCluster API を使用します)。

  • ModifyDBCluster - ポートを変更すると、新しいポートでクラスターのノードをリッスンし始めると停止が発生します。通常、アプリケーションのみがポート変更を制御することを確保し、依存するエンドポイントを適切に更新する、API レベルで変更を加えた時点で誰かが手動でポートを更新するようにする、またはポートが変更されているかを確認するためにアプリケーションで RDS API をクエリすることでアプリケーションはこの失敗に応答できます。

  • ModifyDBInstance - DBInstanceClass を変更すると、停止が発生します

  • DeleteDBInstance - マスター/ライターを削除すると、新規の DB インスタンスが DB クラスターでライターに昇格します

アプリケーション/クライアント側では、Linux を使用する場合に、tcp キープアライブパケットが iptables を使用して送信または受信されない場合、あるいはポート、ホストによってアプリケーションの応答がどのように突然パケットドロップするかをテストできます。

高速フェイルオーバーの例

以下のコードサンプルでは、アプリケーションがどのように Aurora PostgreSQL ドライブマネージャをセットアップできるかを示しています。アプリケーションは、接続が必要になると getConnection(...) を呼び出します。ライターが見つからないのに targetServer タイプは「マスター」) として設定されているなどの場合に、この関数への呼び出しは有効なホストの検出に失敗することがあり、呼び出しアプリケーションは単に再試行します。これは容易に接続プーラーに含めることができ、再試行動作がアプリケーションに与える影響を回避します。ほとんどの接続プーラーでは JDBC 接続文字列を指定できるため、アプリケーションは getJdbcConnectionString(...) を呼び出すことができ、これを接続プーラーに渡して、Aurora PostgreSQL の高速フェイルオーバーを使用できます。

import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; import java.sql.Statement; import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; import java.util.stream.IntStream; import org.joda.time.Duration; public class FastFailoverDriverManager { private static Duration LOGIN_TIMEOUT = Duration.standardSeconds(2); private static Duration CONNECT_TIMEOUT = Duration.standardSeconds(2); private static Duration CANCEL_SIGNAL_TIMEOUT = Duration.standardSeconds(1); private static Duration DEFAULT_SOCKET_TIMEOUT = Duration.standardSeconds(5); public FastFailoverDriverManager() { try { Class.forName("org.postgresql.Driver"); } catch (ClassNotFoundException e) { e.printStackTrace(); } /* * RO endpoint has a TTL of 1s, we should honor that here. Setting this aggressively makes sure that when * the PG JDBC driver creates a new connection, it will resolve a new different RO endpoint on subsequent attempts * (assuming there is > 1 read node in your cluster) */ java.security.Security.setProperty("networkaddress.cache.ttl" , "1"); // If the lookup fails, default to something like small to retry java.security.Security.setProperty("networkaddress.cache.negative.ttl" , "3"); } public Connection getConnection(String targetServerType) throws SQLException { return getConnection(targetServerType, DEFAULT_SOCKET_TIMEOUT); } public Connection getConnection(String targetServerType, Duration queryTimeout) throws SQLException { Connection conn = DriverManager.getConnection(getJdbcConnectionString(targetServerType, queryTimeout)); /* * A good practice is to set socket and statement timeout to be the same thing since both * the client AND server will kill the query at the same time, leaving no running queries * on the backend */ Statement st = conn.createStatement(); st.execute("set statement_timeout to " + queryTimeout.getMillis()); st.close(); return conn; } private static String urlFormat = "jdbc:postgresql://%s" + "/postgres" + "?user=%s" + "&password=%s" + "&loginTimeout=%d" + "&connectTimeout=%d" + "&cancelSignalTimeout=%d" + "&socketTimeout=%d" + "&targetServerType=%s" + "&tcpKeepAlive=true" + "&ssl=true" + "&loadBalanceHosts=true"; public String getJdbcConnectionString(String targetServerType, Duration queryTimeout) { return String.format(urlFormat, getFormattedEndpointList(getLocalEndpointList()), CredentialManager.getUsername(), CredentialManager.getPassword(), LOGIN_TIMEOUT.getStandardSeconds(), CONNECT_TIMEOUT.getStandardSeconds(), CANCEL_SIGNAL_TIMEOUT.getStandardSeconds(), queryTimeout.getStandardSeconds(), targetServerType ); } private List<String> getLocalEndpointList() { /* * As mentioned in the best practices doc, a good idea is to read a local resource file and parse the cluster endpoints. * For illustration purposes, the endpoint list is hardcoded here */ List<String> newEndpointList = new ArrayList<>(); newEndpointList.add("myauroracluster.cluster-c9bfei4hjlrd.us-east-1-beta.rds.amazonaws.com:5432"); newEndpointList.add("myauroracluster.cluster-ro-c9bfei4hjlrd.us-east-1-beta.rds.amazonaws.com:5432"); return newEndpointList; } private static String getFormattedEndpointList(List<String> endpoints) { return IntStream.range(0, endpoints.size()) .mapToObj(i -> endpoints.get(i).toString()) .collect(Collectors.joining(",")); } }

ストレージ問題のトラブルシューティング

ソートあるいはインデックス作成操作に必要なメモリ量が利用可能なメモリ量を超える場合、Aurora PostgreSQL はこの過剰データをストレージに書き込みます。データを書き込むときには、エラーやメッセージログを保管する場合に使用するのと同じストレージ領域を使用します。ソートまたはインデックス作成機能が利用できるメモリを超えた場合は、ローカルストレージ不足を開発することもできます。Aurora PostgreSQL でストレージ領域不足の問題が発生した場合、より多くのメモリを使用するためにデータソートを再設定するか、あるいは PostgreSQL ログファイルのデータ保持期間を削減できます。ログ保持期間の変更の詳細については、「PostgreSQL データベースのログファイル」を参照してください。