0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

RDS vs Aurora Serverless — データ案件でどっちを選ぶか

0
Posted at

はじめに

FastAPIとECSの構成でデータベースをどれにするか調べた。

選択肢は主に3つ。RDS(PostgreSQL)Aurora PostgreSQLAurora 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(夜間はスケールダウン)」という使い分けに落ち着いた。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?