はじめに
FastAPIとECSの構成でデータベースをどれにするか調べた。
選択肢は主に3つ。RDS(PostgreSQL)、Aurora PostgreSQL、Aurora Serverless v2。「RDSで十分じゃないの?」という感覚から始まって、実際に試した結果を整理する。
3つの選択肢の概要
RDS(PostgreSQL):
従来のマネージドPostgreSQL
設定がシンプル
コストが予測しやすい
Aurora PostgreSQL:
AWSが独自開発したMySQL/PostgreSQL互換のDB
RDSよりI/Oが高速
ストレージが自動拡張
Aurora Serverless v2:
負荷に応じてACUを自動でスケール
最小0.5ACUまで下げられる
使った分だけ課金
RDSとAuroraのアーキテクチャの違い
RDS(従来型):
EC2インスタンス + EBSストレージ
ストレージはインスタンスに紐づく
フェイルオーバーに1〜2分かかる
Aurora:
コンピュート層とストレージ層が分離
ストレージは3AZに6コピー(自動)
フェイルオーバーが30秒以内
ストレージが10GBから最大128TBまで自動拡張
SnowflakeのようにコンピュートとストレージをAWS独自の設計で分離したのがAurora。RDSより可用性が高い。
RDS vs Aurora の性能比較
公式ベンチマーク(目安):
書き込みスループット: Aurora ≈ RDS × 5倍
読み取りスループット: Aurora ≈ RDS × 3倍
ただし:
小規模・低トラフィックでは差がほぼ出ない
差が出るのはI/O集中ワークロード
データ処理案件では大量のINSERT/UPDATEが走ることがあるのでAuroraのほうが有利な場面がある。一方でFastAPIのCRUDAPIレベルのトラフィックではRDSで十分なことが多い。
Aurora Serverless v2
Aurora Serverless v2は「使っていないときは小さく、負荷が高いときは自動で大きくなる」DBサービス。
ACU(Aurora Capacity Unit)= コンピュートの単位
1 ACU ≈ 2GBメモリ + 対応するCPU
最小: 0.5 ACU(最安)
最大: 128 ACU(最大スペック)
課金: 使用したACU × 時間
# Aurora Serverless v2の設定例(boto3で作成)
import boto3
rds = boto3.client('rds', region_name='ap-northeast-1')
rds.create_db_cluster(
DBClusterIdentifier = 'my-aurora-serverless',
Engine = 'aurora-postgresql',
EngineVersion = '15.4',
DatabaseName = 'mydb',
MasterUsername = 'admin',
MasterUserPassword = 'password',
ServerlessV2ScalingConfiguration = {
'MinCapacity': 0.5, # 最小0.5 ACU
'MaxCapacity': 4.0, # 最大4 ACU
},
VpcSecurityGroupIds = ['sg-xxxxx'],
DBSubnetGroupName = 'my-db-subnet-group',
BackupRetentionPeriod = 7,
DeletionProtection = True,
)
コスト比較
東京リージョン(ap-northeast-1)の概算。
RDS PostgreSQL(db.t3.medium)
インスタンス: $0.076/時間
ストレージ: $0.138/GB/月
I/O: $0.02/100万リクエスト
常時起動(730時間/月):
インスタンス: $55.5/月
ストレージ(100GB): $13.8/月
合計: 約$70/月
Aurora Serverless v2
ACU: $0.12/ACU時間
ストレージ: $0.12/GB/月
I/O: $0.02/100万リクエスト
常時0.5 ACU(最小):
コンピュート: $0.12 × 0.5 × 730 = $43.8/月
ストレージ(100GB): $12/月
合計: 約$56/月(最小時)
ピーク時に2 ACUになる場合(1日8時間、30日):
ピーク: $0.12 × 2 × 8 × 30 = $57.6
オフピーク: $0.12 × 0.5 × 16 × 30 = $28.8
合計: $86.4/月(コンピュートのみ)
まとめ:
常時低負荷 → Aurora Serverless v2が安い場合がある
常時高負荷 → RDSのほうが安い
負荷が不安定 → Aurora Serverless v2が向いている
Pythonからの接続
psycopg2での接続
# FastAPIとSQLAlchemyを使った接続
import os
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
DATABASE_URL = os.environ['DATABASE_URL']
# "postgresql://user:pass@cluster-endpoint.rds.amazonaws.com:5432/mydb"
engine = create_engine(
DATABASE_URL,
pool_size = 5,
max_overflow = 10,
pool_timeout = 30,
pool_recycle = 1800, # 30分で接続をリサイクル
pool_pre_ping = True, # 接続確認してから使う
echo = False,
)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
pool_pre_ping=TrueはAurora Serverless v2で重要。Serverlessはスケールダウン後に接続が切れることがあるため、使う前に接続を確認する。
Aurora Serverless v2特有の接続注意点
# Aurora Serverless v2はスケールアップ時に接続が一時的に切れることがある
# → pool_pre_pingとリトライロジックが必要
from sqlalchemy import create_engine, event
from sqlalchemy.exc import OperationalError
import time
def create_engine_with_retry(database_url: str, retries: int = 3):
engine = create_engine(
database_url,
pool_pre_ping = True,
pool_recycle = 300,
)
return engine
# リトライデコレータ
def with_db_retry(max_retries: int = 3, wait: float = 1.0):
def decorator(func):
def wrapper(*args, **kwargs):
for attempt in range(max_retries):
try:
return func(*args, **kwargs)
except OperationalError as e:
if attempt == max_retries - 1:
raise
print(f"DB接続エラー。リトライ {attempt + 1}/{max_retries}")
time.sleep(wait * (attempt + 1))
return wrapper
return decorator
マルチAZ構成
# RDS マルチAZの設定(boto3)
rds.create_db_instance(
DBInstanceIdentifier = 'my-rds-primary',
DBInstanceClass = 'db.t3.medium',
Engine = 'postgres',
EngineVersion = '15.4',
MasterUsername = 'admin',
MasterUserPassword = 'password',
AllocatedStorage = 100,
MultiAZ = True, # マルチAZ有効
DBSubnetGroupName = 'my-subnet-group',
VpcSecurityGroupIds = ['sg-xxxxx'],
BackupRetentionPeriod = 7,
StorageEncrypted = True,
DeletionProtection = True,
)
RDS マルチAZ:
プライマリとスタンバイが別AZに存在
フェイルオーバー: 1〜2分
スタンバイは読み取り不可
Aurora:
自動でマルチAZ(ストレージが3AZに6コピー)
フェイルオーバー: 30秒以内
リードレプリカを追加すれば読み取り分散も可能
リードレプリカ
# リードレプリカの追加
rds.create_db_instance_read_replica(
DBInstanceIdentifier = 'my-rds-replica',
SourceDBInstanceIdentifier = 'my-rds-primary',
DBInstanceClass = 'db.t3.small',
AvailabilityZone = 'ap-northeast-1c',
)
# 読み取りと書き込みで接続先を分ける
WRITE_DATABASE_URL = os.environ['DATABASE_URL'] # プライマリ
READ_DATABASE_URL = os.environ['DATABASE_URL_READONLY'] # リードレプリカ
write_engine = create_engine(WRITE_DATABASE_URL, ...)
read_engine = create_engine(READ_DATABASE_URL, ...)
WriteSession = sessionmaker(bind=write_engine)
ReadSession = sessionmaker(bind=read_engine)
# 使う側
def get_write_db():
db = WriteSession()
try:
yield db
finally:
db.close()
def get_read_db():
db = ReadSession()
try:
yield db
finally:
db.close()
# FastAPIのエンドポイント
@app.get("/users")
def list_users(db: Session = Depends(get_read_db)): # 読み取りはリードレプリカ
return db.query(User).all()
@app.post("/users")
def create_user(user: UserCreate, db: Session = Depends(get_write_db)): # 書き込みはプライマリ
...
バックアップとリストア
# スナップショットの作成
aws rds create-db-snapshot \
--db-instance-identifier my-rds \
--db-snapshot-identifier my-rds-snapshot-$(date +%Y%m%d)
# スナップショットの一覧
aws rds describe-db-snapshots \
--db-instance-identifier my-rds \
--query 'DBSnapshots[*].{ID:DBSnapshotIdentifier,Time:SnapshotCreateTime,Status:Status}'
# スナップショットからリストア
aws rds restore-db-instance-from-db-snapshot \
--db-instance-identifier my-rds-restored \
--db-snapshot-identifier my-rds-snapshot-20240401 \
--db-instance-class db.t3.medium \
--multi-az
ポイントインタイムリカバリ
# 任意の時点にリストア(バックアップ保持期間内)
aws rds restore-db-instance-to-point-in-time \
--source-db-instance-identifier my-rds \
--target-db-instance-identifier my-rds-recovered \
--restore-time 2024-04-01T12:00:00Z
誤ってテーブルをDROPしてしまった場合など、5分前の状態に戻せる。Snowflakeのtime travelに相当する機能。
データ処理案件での選定基準
実際の案件で考えた判断基準。
FastAPI(CRUD API)のDB:
トラフィックが予測できる → RDS(コストが安定)
トラフィックが不安定 → Aurora Serverless v2(スケールが柔軟)
高可用性が必要 → Aurora(フェイルオーバーが速い)
データパイプラインのDB(Snowflake連携):
中間データの一時置き場 → Aurora Serverless v2(夜間は小さく、ETL時は大きく)
ログデータの蓄積 → RDS(単純なINSERT中心)
3択の整理
| 項目 | RDS PostgreSQL | Aurora PostgreSQL | Aurora Serverless v2 |
|---|---|---|---|
| 性能 | 標準 | 高い(5倍書き込み) | 高い(自動スケール) |
| 可用性 | マルチAZで99.95% | 99.99%(自動3AZ) | 99.99% |
| フェイルオーバー | 1〜2分 | 30秒以内 | 30秒以内 |
| コスト | 固定 | RDSより高め | 使った分 |
| 最小コスト | インスタンス常時起動 | インスタンス常時起動 | 0.5 ACU/時間 |
| スケール | 手動 | 手動(リードレプリカ) | 自動 |
| 向いている用途 | 安定トラフィック | 高I/O・高可用性 | 可変トラフィック |
まとめ
- RDSはシンプルで予測しやすいコスト。安定トラフィックに向いている
- AuroraはRDSより高性能・高可用性。書き込みが多い場合に有効
- Aurora Serverless v2はトラフィックが不安定な場合に向いている。開発環境にも使いやすい
-
pool_pre_ping=TrueはAurora Serverless v2で必須 - マルチAZはRDS、Auroraともに設定で有効化。Auroraはデフォルトでストレージが3AZに冗長化
- ポイントインタイムリカバリでバックアップ保持期間内の任意の時点に戻せる
個人的にはFastAPIのAPIサーバー用には「RDSかAurora Serverless v2(最小0.5ACU)」、データパイプラインの中間DBには「Aurora Serverless v2(夜間はスケールダウン)」という使い分けに落ち着いた。