Aurora DSQLとは?エラー別の原因切り分けと対処
目次
Aurora DSQLは「PostgreSQL互換」と紹介されるため、Aurora PostgreSQLの感覚で触ると接続・SQL・コミットのそれぞれでつまずきます。しかも失敗の見え方が紛らわしく、IAMの権限不足が password authentication failed として返ったり、アプリのバグに見えるエラーが分散トランザクションの正常な挙動だったりします。
この記事では、挙動の違いが分かる範囲でAurora DSQLを整理したうえで、接続 → 認証 → SQL → トランザクションの順にエラーを逆引きできる形にまとめます。クォータや未サポート機能は更新が入る領域なので、具体的な数値は公式ドキュメント(Aurora DSQLユーザーガイドのQuotas / Unsupported features)で確認してください。
結論:つまずきの大半は「認証トークン」と「分散DB特有の制約」
Aurora DSQLで最初に踏む地雷は、ほぼ次の4つに収まります。
- 認証はIAMトークンのみ。パスワードを設定する概念がないので、
generate-db-connect-admin-auth-tokenで発行したトークンをパスワード欄に渡します。トークンには有効期限があり、リージョン・ホスト名・ユーザー名のどれかがずれると「パスワード認証失敗」として返ります。 - 接続継続時間にも上限があります。永続接続を前提にしたコネクションプールは、一定時間で切断される前提で組みます。
- PostgreSQLの一部機能が使えません。外部キー制約、シーケンス(
SERIAL)、トリガー、一時テーブルなど、ORMが当たり前に生成するDDLが0A000 feature_not_supportedで失敗します。 - コミット時に競合エラーが出ます。楽観的同時実行制御(OCC)を採用しているため、書き込みが衝突するとトランザクション全体が
OC001系のエラーで失敗します。仕様どおりの挙動で、アプリ側のリトライ実装が前提です。
この4点を設計時に押さえてしまえば、残りはおおむね普通のPostgreSQLとして書けます。
Aurora DSQLとは何か:通常のAurora PostgreSQLとの違い
Aurora DSQLは、PostgreSQL互換のインターフェースを持つサーバーレスの分散SQLデータベースです。インスタンスクラスやストレージ容量は指定せず、クラスターを作ると接続エンドポイントだけが払い出されます。マルチリージョン構成では複数リージョンで同時に書き込みを受け付けられ(アクティブ-アクティブ)、強整合性を保ったまま読み書きできます。
従来のAurora PostgreSQLと対比すると次のようになります。
| 観点 | Aurora PostgreSQL | Aurora DSQL |
|---|---|---|
| 構成単位 | DBインスタンス+共有ストレージ | インスタンス非公開のサーバーレス |
| 書き込み | ライター1台 | 単一/複数リージョンで分散 |
| 認証 | パスワード認証 / IAM認証 | IAMトークンのみ |
| 互換性 | ほぼフルのPostgreSQL | PostgreSQLのサブセット |
| 同時実行制御 | MVCC+行ロック(悲観的) | MVCC+楽観的同時実行制御 |
| スケール操作 | インスタンスクラス変更、レプリカ追加 | 操作不要(自動) |
インスタンスという概念がないので、Auroraインスタンスクラス変更の手順|ダウンタイム最小化で扱うようなスペック変更の段取りは不要です。その代わり、パラメータグループによるチューニングや拡張機能の自由度も失われます。
アーキテクチャの違いが挙動の違いになる理由
挙動の差は、ストレージとトランザクション処理の分離構造から来ます。
従来のPostgreSQLは、共有バッファ上の1枚のページを複数バックエンドが参照し、更新時は行に排他ロックを取り、WALを先に書いてコミットします。同じ行を更新しようとした後続セッションは、ロック待ちでブロックされます(wait_event_type = Lock)。
Aurora DSQLでは、データはキー範囲で分割されて複数ノードに分散し、トランザクションの更新内容はコミットまでローカルに保持されます。コミット時に「読んだデータが途中で他のトランザクションに変更されていないか」を検証し、衝突していればそのトランザクションを中断します。これがOCCで、結果として次の違いが生まれます。
- ロック待ちで詰まる代わりに、コミット時にエラーが返る。デッドロックは起きにくいが、リトライ実装が必須。
- 分離レベルはスナップショット分離(
REPEATABLE READ相当)。READ COMMITTED前提で「SELECTして値を見てからUPDATE」する処理は、同時実行時にコミットが失敗しうる。 - ホットな単一行への集中更新が弱点。カウンター1行を全員で更新する設計は、リトライが頻発する。
- 主キーがデータの物理配置を決める。テーブルは主キー順に格納されるため、連番IDは書き込みが特定範囲に集中する。UUIDなど分散するキーが推奨される。
- DDLは分散スキーマ変更になる。1トランザクションにDDLは1つまで、インデックス作成は非同期(
CREATE INDEX ASYNC)といった制約がある。
使えない機能・気をつける上限値のチェックリスト
PoCの前に、設計がこれらに触れていないかを確認します(詳細と最新の値は公式ドキュメントを参照)。
使えない、または制限される代表的な機能は、外部キー制約(参照整合性はアプリ側で担保)、シーケンスと SERIAL / BIGSERIAL(UUIDやアプリ側生成のIDへ置き換え)、トリガー、一時テーブル、TRUNCATE、任意のPostgreSQL拡張のインストール、主キーの後からの変更(作り直しが必要)です。EXTENSION を前提にした監視・移行系ツールも引っかかりやすい部分です。
確認すべき上限値は、次のカテゴリです。
- 1トランザクションで変更できる行数・データサイズ
- トランザクションの最大実行時間
- 1接続の最大継続時間
- クラスターあたりの同時接続数
- クラスターあたりのテーブル・インデックス数
特にトランザクションの行数・サイズ上限は、バッチ処理の設計に直結します。数十万行を1トランザクションで投入する既存バッチは、ほぼ確実に分割が必要になります。
エラー別の切り分け手順:接続 → 認証 → SQL → トランザクション
切り分けは必ずこの順で行います。層をまたいで同時に疑うと、エラーメッセージの表現が紛らわしいために迷子になります。
接続できない・認証に失敗する場合の確認順序
まず、タイムアウトなのか認証失敗なのかを分けます。メッセージで判別できます。
| 症状 | 層 |
|---|---|
connection timed out / could not connect to server |
ネットワーク |
FATAL: password authentication failed for user "admin" |
認証(トークンまたはIAM) |
| SSL関連のFATAL | 接続オプション |
| 一定時間後に毎回切れる | 接続時間クォータ |
ネットワーク層では経路を確認します。Aurora DSQLのエンドポイントはインターネット経由で到達できる形で払い出されますが、VPC内のLambdaやECSタスクから使う場合は、NAT経由の経路かPrivateLink(VPCエンドポイント)が必要です。VPC内から疎通しないときは、まずVPC外(Cloud9やローカル)から psql で届くか確認し、アプリ固有の問題と切り分けます。考え方はRDS 接続できない・タイムアウトの原因と切り分け手順と同じで、「SG → 経路 → エンドポイント」の順です。
認証層は、トークンを手で発行して psql から入ってみるのが最短です。
CLUSTER_HOST=abcdefghijklmnop.dsql.us-east-1.on.aws
# adminロールで接続する場合のトークン
export PGPASSWORD=$(aws dsql generate-db-connect-admin-auth-token \
--region us-east-1 \
--hostname "$CLUSTER_HOST" \
--expires-in 3600)
psql --host "$CLUSTER_HOST" \
--port 5432 \
--username admin \
--dbname postgres \
--set=sslmode=require
ここで password authentication failed になる場合、原因は次のどれかです。
--regionがクラスターのリージョンと違う。トークンの署名にリージョンが含まれるため、ずれると無効になる。--hostnameにエンドポイント以外を指定した。ポート付き、https://付き、カスタムDNS名などは不可。- トークンの種類とユーザー名が不一致。
adminで入るならgenerate-db-connect-admin-auth-token、それ以外のDBロールならgenerate-db-connect-auth-token。取り違えやすい最頻出ポイント。 - トークンの有効期限切れ。
--expires-inの既定値は短いので、アプリでは接続直前に毎回発行する。キャッシュするなら期限管理も実装する。 - IAM権限不足。
dsql:DbConnectAdminまたはdsql:DbConnectが対象クラスターのARNに対して許可されているか。権限不足もAccessDeniedではなく認証失敗として見えるため、CloudTrailと合わせて確認する。 - 接続URIに埋め込んでURLエンコードしていない。トークンには
%や/が含まれるので、PGPASSWORDや接続パラメータで渡す。
adminではない独自ロールで接続する場合は、DB側のロールとIAMロールの紐付けが必要です。
CREATE ROLE app_rw WITH LOGIN;
AWS IAM GRANT app_rw TO 'arn:aws:iam::123456789012:role/app-task-role';
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO app_rw;
「最初は繋がるが一定時間で切れる」場合は、接続継続時間のクォータを疑います。コネクションプールの maxLifetime をクォータより十分短く設定し、トークン再発行とセットで接続を作り直します。プールの最大接続数も同時接続数クォータから逆算します。接続数の上限が何で決まるのかという考え方は、Aurora Serverless v2のmax_connectionsは最大ACUで決まると同じく、設計時に数字を確定させておくのがポイントです。
SQLが通らない場合:未サポート構文の見分け方
アプリの初回マイグレーションで大量にエラーが出るパターンです。見分けの鍵はSQLSTATEです。
0A000(feature_not_supported)はAurora DSQLがその構文・機能に対応していない状態で、書き換えが必要です。42601(syntax_error)は純粋な構文ミスです。42P01などの通常のPostgreSQLエラーはDSQL固有ではないので、普通にデバッグします。
0A000 が出たら、SQLを「何を使っているか」で分類します。典型的な置き換えは次の通りです。
-- NG: SERIAL(シーケンス依存)と外部キー
CREATE TABLE orders (
id SERIAL PRIMARY KEY,
customer_id INT NOT NULL REFERENCES customers(id),
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
-- OK: UUID主キー、参照整合性はアプリ側で担保
CREATE TABLE orders (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
customer_id UUID NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
インデックスは非同期作成の構文を使い、ジョブの進行状況を確認します。
CREATE INDEX ASYNC idx_orders_customer ON orders (customer_id);
-- 作成ジョブの状態を確認
SELECT * FROM sys.jobs;
ORMを使っている場合、db.Model から自動生成されるDDLが外部キーやシーケンスを含むため、マイグレーションの自動生成をあきらめてSQLを手書きするほうが早いことが多いです。アプリ本体のSELECT/INSERT/UPDATEは大半がそのまま通るので、問題はDDLに集中します。移行判断では、スキーマ定義ファイルを 0A000 の観点でgrepするだけでも、工数見積もりの精度が上がります。
コミット時に失敗する場合:同時実行制御エラーとリトライ設計
COMMIT の瞬間に失敗する、負荷をかけると失敗率が上がる、という症状はOCCによる衝突です。Aurora DSQLは OC001 などの独自エラーコードで競合を返します。メッセージ文面は変わる可能性があるので、SQLSTATEで判定してください。
これは仕様上発生するもので、従来のPostgreSQLなら行ロック待ちで吸収されていた競合が、ここではエラーとして表面化します。対処は2段構えです。
(1) リトライを実装する
import time
import psycopg
RETRYABLE = ("OC001", "OC000", "40001") # 競合系。実際のコードはドライバのsqlstateで確認する
def run_tx(conn, fn, max_attempts=5):
for attempt in range(max_attempts):
try:
with conn.transaction():
return fn(conn)
except psycopg.errors.Error as e:
if e.sqlstate not in RETRYABLE or attempt == max_attempts - 1:
raise
# 指数バックオフ+ジッタ(衝突を再現させないため必須)
time.sleep(min(0.05 * (2 ** attempt), 1.0) * (0.5 + 0.5 * time.time() % 1))
ポイントは3つです。
- トランザクション単位でやり直す。失敗したSQLだけ再実行しても、スナップショットが無効なので意味がない。
- 指数バックオフとジッタを入れる。即時リトライは衝突を増幅させる。
- 冪等性を確認する。リトライ前提なので、外部API呼び出しなどの副作用をトランザクション内に置かない。
(2) 衝突そのものを減らす
リトライ率が数パーセントを超えるなら、設計側を直します。
- トランザクションを短く保つ。読み取りからコミットまでの時間が長いほど、他のトランザクションと衝突する窓が広がる。トランザクション内でアプリの重い処理や外部通信を挟まない。
- ホットな行をなくす。集計カウンターは1行更新をやめ、明細を追記して集計時に合算するか、キーを分割して合算する。
- 更新行の粒度を見直す。1トランザクションで広い範囲を
UPDATEすると衝突面積が増える。 - バッチは分割する。行数・サイズ・実行時間のクォータに触れないチャンクサイズに揃え、チャンクごとに独立したトランザクションにして失敗分だけリトライできるようにする。
- 読み取り専用の処理は読み取り専用として実行する。更新を伴わなければ衝突検証の対象が減る。
「思っていた性能・コストと違う」ときに見る場所
Aurora DSQLにはインスタンスメトリクスもパラメータグループもないので、調査の入口が従来と変わります。
レイテンシが想定より大きい場合、最初に疑うのは往復回数です。分散アーキテクチャではコミット時にノード間の調整が入るため、1件ずつINSERTしてコミットする実装は、オートコミットの往復がそのままレイテンシに乗ります。複数行を1つのINSERTにまとめる、1トランザクションに適切な件数を詰めるといったバッチ化の効果が、従来より大きく出ます。マルチリージョン構成では書き込みのコミットがリージョン間の調整を含むため、読み取りと書き込みでレイテンシ特性が異なる点も織り込みます。
実行計画は EXPLAIN / EXPLAIN ANALYZE で確認できます。見るべきポイントは従来と同じです。
- 主キーやインデックスを使った絞り込みになっているか(フルスキャンになっていないか)
- 分散ストレージからフェッチする行数が過大でないか
- ソートや結合の順序が意図通りか
ただし、コストモデルは分散前提なので、オンプレPostgreSQLの感覚で「インデックスを張れば必ず速い」とは限りません。カバリングできているか、主キー順のアクセスになっているかを優先して見ます。
コストは、消費したリソース(DPU)とストレージ量で決まる課金体系です。インスタンス時間課金ではないため、書き込みと読み取りの回数・量に比例して増える構造になります。想定より高い場合に見る順序は次の通りです。
- リトライによる無駄な実行が多発していないか(失敗トランザクションも処理は発生する)
- 必要のない全件スキャンが定期的に走っていないか(ダッシュボードの集計クエリなど)
- マルチリージョンの書き込みが意図せず増えていないか
- ストレージ上の不要データ(ログテーブルの無期限保持など)
CloudWatchに出るクラスターのメトリクスで消費リソースとコミット状況の推移を押さえておくと、アプリのリリースと増減を突き合わせられます。考え方はAurora料金の見積もり方|I/Oコストを実測で試算する手順と同じで、カタログの単価から机上計算するより、小さく動かして実測値から外挿するほうが精度が出ます。
よくある質問
既存のPostgreSQLアプリをそのまま移行できる?
無改修での移行は想定しないほうが安全です。チェックすべきは次の順です。
- スキーマに外部キー・シーケンス・トリガー・拡張機能があるか(あれば要改修)
- 接続部分がパスワード認証前提になっているか(IAMトークン発行への書き換えが必須)
- 「SELECTしてからUPDATE」する処理と、大量行を一括更新するバッチがあるか(リトライ実装と分割が必要)
- 運用ツール(監視、バックアップ、ETL)が未サポート機能に依存していないか
新規開発でマルチリージョンの書き込みが要件に入っているなら、最初からDSQL前提で設計するほうが素直です。
Aurora Serverless v2とはどう使い分ける?
おおまかな判断基準は2つです。既存のPostgreSQL資産をそのまま動かしたい場合や、拡張機能やフル互換が必要な場合はAurora PostgreSQL(Serverless v2を含む)。複数リージョンで同時に書き込みたい場合や、インスタンス管理とスケール設計を手放したい場合はAurora DSQLになります。
Serverless v2はあくまで「インスタンス容量が自動で伸縮するAurora」で、互換性や同時実行制御の挙動は従来のPostgreSQLのままです。DSQLは互換性のサブセットと引き換えに、分散書き込みと運用の自動化を得る選択になります。どちらも「サーバーレス」と呼ばれますが、適用判断の軸は別物だと考えてください。
psqlや既存のドライバ・ORMから接続できる?
PostgreSQLのワイヤプロトコル互換なので、psql、JDBC(pgjdbc)、psycopg、Npgsql、node-postgresなどの標準的なドライバで接続できます。TLSは必須で、sslmode は require 以上(証明書検証を行うなら verify-full)を指定します。
ORMについては、クエリ発行は通る一方で、マイグレーション機能とIDジェネレータが問題になります。具体的には次の4点です。
- ID生成戦略をシーケンスからUUIDに変更する
- 外部キー制約を生成しない設定にする
- マイグレーションはORM自動生成に任せず、DSQLで通るDDLを管理する
- コネクションプールの
maxLifetimeを接続時間クォータより短くし、パスワード(トークン)を動的に差し替えられるようにする
これを先に決めておけば、多くのフレームワークで実用的に使えます。
再発防止:導入前チェックリストと分散データベースの学び方
PoCに入る前に、次のリストを埋めておくと手戻りが減ります。
- トークン発行・再発行の責務をどこに置くか(アプリ/サイドカー/プールのフック)
- 接続継続時間と同時接続数のクォータを確認し、プール設定に反映したか
- スキーマから外部キー・シーケンス・トリガー・拡張機能を排除したか
- 主キーの設計が特定範囲に書き込みを集中させないか
- 全トランザクションにリトライ(指数バックオフ+ジッタ)が入っているか
- トランザクション内に外部通信や重い処理が混ざっていないか
- バッチのチャンクサイズが行数・サイズ・時間のクォータ内か
- リトライ率とコミットのレイテンシを監視項目に入れたか
- マルチリージョンが要件なら、リージョン構成と障害時の挙動を確認したか
学び方としては、PostgreSQLの内部挙動を理解したうえでDSQLとの差分を取るのが最短です。MVCC、スナップショットの取得タイミング、行ロックと待機の関係を押さえていれば、なぜロック待ちにならずコミットエラーになるのか、なぜトランザクションを短くするとリトライが減るのかを理屈で説明できます。DSQL固有の制約だけを暗記すると、本番でリトライが増えたときに打ち手が出てきません。
RDS/Aurora全般の運用観点での切り分けはRDS・Aurora運用トラブルの切り分け|接続断と性能劣化にまとめてあるので、従来型Auroraとの比較軸を作るために併せて確認してください。そのうえで、Aurora DSQL公式ドキュメントの「Unsupported features」「Quotas」「Concurrency control」の3ページを設計レビューのたびに最新版で読み直す運用にすれば、仕様追加のキャッチアップも兼ねられます。