AWS RDS Proxy 連線池管理

AWS RDS Proxy Connection Pooling Management

前言

在現代雲端架構中,資料庫連線管理是一個關鍵的效能瓶頸。當應用程式規模擴大,特別是在 Serverless 架構下,大量的短暫連線會對資料庫造成巨大壓力。AWS RDS Proxy 正是為了解決這個問題而生的全託管服務。

本文將深入探討 AWS RDS Proxy 的連線池管理機制,從基礎概念到進階設定,幫助您充分發揮這項服務的潛力。


1. RDS Proxy 概述與優勢

什麼是 RDS Proxy?

AWS RDS Proxy 是一個全託管、高可用的資料庫代理服務,位於應用程式與 RDS 資料庫之間。它會維護一個連線池,讓應用程式可以共享這些連線,大幅減少資料庫的連線負擔。

核心優勢

1. 連線池管理

  • 自動管理資料庫連線的建立與回收
  • 減少連線建立的延遲(通常需要 20-30ms)
  • 避免資料庫達到最大連線數限制

2. 提升應用程式彈性

  • 在資料庫故障切換期間,自動將連線路由到新的主節點
  • 減少故障切換時間,從數分鐘降至數十秒

3. 增強安全性

  • 與 AWS Secrets Manager 整合,集中管理資料庫認證
  • 支援 IAM 認證,無需在程式碼中儲存密碼
  • 強制使用 TLS 加密傳輸

4. Serverless 最佳化

  • 特別適合 Lambda 函數的短暫連線模式
  • 避免 Lambda 冷啟動時的連線風暴

架構示意

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
┌─────────────────┐     ┌─────────────────┐     ┌─────────────────┐
│   Application   │────▶│   RDS Proxy     │────▶│   RDS Database  │
│  (Lambda/EC2)   │     │  (Connection    │     │  (MySQL/        │
│                 │     │   Pooling)      │     │   PostgreSQL)   │
└─────────────────┘     └─────────────────┘     └─────────────────┘
                        ┌─────────────────┐
                        │ Secrets Manager │
                        │   (認證資訊)     │
                        └─────────────────┘

2. 支援的資料庫引擎

RDS Proxy 目前支援以下資料庫引擎:

MySQL 相容引擎

引擎支援版本
Amazon RDS for MySQL5.6, 5.7, 8.0
Amazon Aurora MySQL1.x (MySQL 5.6), 2.x (MySQL 5.7), 3.x (MySQL 8.0)

PostgreSQL 相容引擎

引擎支援版本
Amazon RDS for PostgreSQL10, 11, 12, 13, 14, 15, 16
Amazon Aurora PostgreSQL10.x, 11.x, 12.x, 13.x, 14.x, 15.x, 16.x

MariaDB

引擎支援版本
Amazon RDS for MariaDB10.3, 10.4, 10.5, 10.6

SQL Server

引擎支援版本
Amazon RDS for SQL Server2016, 2017, 2019, 2022

3. 建立與設定 RDS Proxy

使用 AWS CLI 建立 RDS Proxy

步驟一:建立 Secrets Manager Secret

首先,將資料庫認證儲存在 Secrets Manager 中:

1
2
3
4
aws secretsmanager create-secret \
    --name "rds-proxy/mydb-credentials" \
    --description "RDS Proxy 資料庫認證" \
    --secret-string '{"username":"admin","password":"your-secure-password"}'

步驟二:建立 IAM Role

建立 RDS Proxy 使用的 IAM Role,允許其存取 Secrets Manager:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "secretsmanager:GetSecretValue"
            ],
            "Resource": [
                "arn:aws:secretsmanager:ap-northeast-1:123456789012:secret:rds-proxy/*"
            ]
        },
        {
            "Effect": "Allow",
            "Action": [
                "kms:Decrypt"
            ],
            "Resource": "arn:aws:kms:ap-northeast-1:123456789012:key/your-kms-key-id",
            "Condition": {
                "StringEquals": {
                    "kms:ViaService": "secretsmanager.ap-northeast-1.amazonaws.com"
                }
            }
        }
    ]
}

建立 Role:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 建立信任政策
cat > trust-policy.json << 'EOF'
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "Service": "rds.amazonaws.com"
            },
            "Action": "sts:AssumeRole"
        }
    ]
}
EOF

# 建立 IAM Role
aws iam create-role \
    --role-name RDSProxyRole \
    --assume-role-policy-document file://trust-policy.json

# 附加政策
aws iam put-role-policy \
    --role-name RDSProxyRole \
    --policy-name RDSProxySecretsAccess \
    --policy-document file://secrets-policy.json

步驟三:建立 RDS Proxy

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
aws rds create-db-proxy \
    --db-proxy-name my-rds-proxy \
    --engine-family MYSQL \
    --auth '[
        {
            "AuthScheme": "SECRETS",
            "SecretArn": "arn:aws:secretsmanager:ap-northeast-1:123456789012:secret:rds-proxy/mydb-credentials",
            "IAMAuth": "REQUIRED"
        }
    ]' \
    --role-arn arn:aws:iam::123456789012:role/RDSProxyRole \
    --vpc-subnet-ids subnet-0123456789abcdef0 subnet-0123456789abcdef1 \
    --vpc-security-group-ids sg-0123456789abcdef0 \
    --require-tls \
    --idle-client-timeout 1800 \
    --debug-logging

步驟四:建立 Target Group 並註冊資料庫

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# 註冊 RDS 實例
aws rds register-db-proxy-targets \
    --db-proxy-name my-rds-proxy \
    --target-group-name default \
    --db-instance-identifiers my-rds-instance

# 或註冊 Aurora Cluster
aws rds register-db-proxy-targets \
    --db-proxy-name my-rds-proxy \
    --target-group-name default \
    --db-cluster-identifiers my-aurora-cluster

使用 Terraform 建立 RDS Proxy

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
# Secrets Manager Secret
resource "aws_secretsmanager_secret" "rds_credentials" {
  name = "rds-proxy/mydb-credentials"
}

resource "aws_secretsmanager_secret_version" "rds_credentials" {
  secret_id = aws_secretsmanager_secret.rds_credentials.id
  secret_string = jsonencode({
    username = var.db_username
    password = var.db_password
  })
}

# IAM Role for RDS Proxy
resource "aws_iam_role" "rds_proxy_role" {
  name = "rds-proxy-role"

  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Action = "sts:AssumeRole"
        Effect = "Allow"
        Principal = {
          Service = "rds.amazonaws.com"
        }
      }
    ]
  })
}

resource "aws_iam_role_policy" "rds_proxy_policy" {
  name = "rds-proxy-secrets-access"
  role = aws_iam_role.rds_proxy_role.id

  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Effect = "Allow"
        Action = [
          "secretsmanager:GetSecretValue"
        ]
        Resource = [
          aws_secretsmanager_secret.rds_credentials.arn
        ]
      }
    ]
  })
}

# RDS Proxy
resource "aws_db_proxy" "main" {
  name                   = "my-rds-proxy"
  debug_logging          = true
  engine_family          = "MYSQL"
  idle_client_timeout    = 1800
  require_tls            = true
  role_arn               = aws_iam_role.rds_proxy_role.arn
  vpc_security_group_ids = [aws_security_group.rds_proxy.id]
  vpc_subnet_ids         = var.private_subnet_ids

  auth {
    auth_scheme               = "SECRETS"
    iam_auth                  = "REQUIRED"
    secret_arn                = aws_secretsmanager_secret.rds_credentials.arn
  }

  tags = {
    Environment = "production"
    Name        = "my-rds-proxy"
  }
}

# Target Group
resource "aws_db_proxy_default_target_group" "main" {
  db_proxy_name = aws_db_proxy.main.name

  connection_pool_config {
    connection_borrow_timeout    = 120
    max_connections_percent      = 100
    max_idle_connections_percent = 50
  }
}

# Target Registration
resource "aws_db_proxy_target" "main" {
  db_proxy_name          = aws_db_proxy.main.name
  target_group_name      = aws_db_proxy_default_target_group.main.name
  db_instance_identifier = aws_db_instance.main.id
}

# Output Proxy Endpoint
output "rds_proxy_endpoint" {
  value = aws_db_proxy.main.endpoint
}

4. IAM 認證整合

啟用 IAM 認證

IAM 認證讓您可以使用 AWS 認證來連接資料庫,無需管理資料庫密碼。

在 RDS 實例上啟用 IAM 認證

1
2
3
4
aws rds modify-db-instance \
    --db-instance-identifier my-rds-instance \
    --enable-iam-database-authentication \
    --apply-immediately

建立資料庫使用者並授予 IAM 認證權限

MySQL:

1
2
3
CREATE USER 'iam_user'@'%' IDENTIFIED WITH AWSAuthenticationPlugin AS 'RDS';
GRANT SELECT, INSERT, UPDATE, DELETE ON mydb.* TO 'iam_user'@'%';
FLUSH PRIVILEGES;

PostgreSQL:

1
2
3
CREATE USER iam_user;
GRANT rds_iam TO iam_user;
GRANT ALL PRIVILEGES ON DATABASE mydb TO iam_user;

設定 IAM Policy

為應用程式的 IAM Role 新增連接 RDS Proxy 的權限:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": "rds-db:connect",
            "Resource": "arn:aws:rds-db:ap-northeast-1:123456789012:dbuser:prx-0123456789abcdef0/iam_user"
        }
    ]
}

使用 IAM 認證連接 RDS Proxy

Python 範例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
import boto3
import mysql.connector
from botocore.auth import SigV4Auth
from botocore.awsrequest import AWSRequest

def get_auth_token(proxy_endpoint, port, username, region):
    """產生 IAM 認證 Token"""
    session = boto3.Session()
    client = session.client('rds', region_name=region)

    token = client.generate_db_auth_token(
        DBHostname=proxy_endpoint,
        Port=port,
        DBUsername=username,
        Region=region
    )
    return token

def connect_to_rds_proxy():
    proxy_endpoint = "my-rds-proxy.proxy-xxxxxxxxxxxx.ap-northeast-1.rds.amazonaws.com"
    port = 3306
    username = "iam_user"
    region = "ap-northeast-1"
    database = "mydb"

    # 產生認證 Token
    auth_token = get_auth_token(proxy_endpoint, port, username, region)

    # 建立連線
    connection = mysql.connector.connect(
        host=proxy_endpoint,
        port=port,
        user=username,
        password=auth_token,
        database=database,
        ssl_ca='/path/to/rds-combined-ca-bundle.pem',
        ssl_verify_cert=True
    )

    return connection

# 使用連線
conn = connect_to_rds_proxy()
cursor = conn.cursor()
cursor.execute("SELECT * FROM users LIMIT 10")
results = cursor.fetchall()
conn.close()

Node.js 範例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
const mysql = require('mysql2/promise');
const AWS = require('aws-sdk');

async function getAuthToken(proxyEndpoint, port, username, region) {
    const signer = new AWS.RDS.Signer({
        region: region,
        hostname: proxyEndpoint,
        port: port,
        username: username
    });

    return new Promise((resolve, reject) => {
        signer.getAuthToken({}, (err, token) => {
            if (err) reject(err);
            else resolve(token);
        });
    });
}

async function connectToRDSProxy() {
    const proxyEndpoint = 'my-rds-proxy.proxy-xxxxxxxxxxxx.ap-northeast-1.rds.amazonaws.com';
    const port = 3306;
    const username = 'iam_user';
    const region = 'ap-northeast-1';
    const database = 'mydb';

    const authToken = await getAuthToken(proxyEndpoint, port, username, region);

    const connection = await mysql.createConnection({
        host: proxyEndpoint,
        port: port,
        user: username,
        password: authToken,
        database: database,
        ssl: {
            rejectUnauthorized: true,
            ca: fs.readFileSync('/path/to/rds-combined-ca-bundle.pem')
        }
    });

    return connection;
}

// 使用連線
(async () => {
    const conn = await connectToRDSProxy();
    const [rows] = await conn.execute('SELECT * FROM users LIMIT 10');
    console.log(rows);
    await conn.end();
})();

5. 連線池行為調校

Target Group 設定參數

RDS Proxy 的連線池行為可透過 Target Group 進行調整:

1
2
3
4
5
6
7
8
9
aws rds modify-db-proxy-target-group \
    --db-proxy-name my-rds-proxy \
    --target-group-name default \
    --connection-pool-config '{
        "MaxConnectionsPercent": 100,
        "MaxIdleConnectionsPercent": 50,
        "ConnectionBorrowTimeout": 120,
        "SessionPinningFilters": ["EXCLUDE_VARIABLE_SETS"]
    }'

關鍵參數說明

MaxConnectionsPercent

  • 定義:RDS Proxy 可使用的最大資料庫連線數百分比
  • 預設值:100
  • 建議:根據資料庫執行個體類型調整
  • 範例:若 RDS 最大連線數為 200,設定 50% 代表 Proxy 最多使用 100 個連線

MaxIdleConnectionsPercent

  • 定義:閒置連線佔 MaxConnectionsPercent 的百分比
  • 預設值:50
  • 建議:Serverless 工作負載可降低至 10-25%

ConnectionBorrowTimeout

  • 定義:等待可用連線的最長時間(秒)
  • 預設值:120
  • 建議:根據應用程式超時設定調整

Session Pinning 管理

Session Pinning 會將連線固定給特定客戶端,降低連線共享效率。

常見觸發 Pinning 的情況

  • 使用預備語句(Prepared Statements)
  • 設定會話變數
  • 使用臨時表
  • 鎖定表格
  • 使用使用者定義變數

減少 Pinning 的策略

1
2
3
4
5
6
aws rds modify-db-proxy-target-group \
    --db-proxy-name my-rds-proxy \
    --target-group-name default \
    --connection-pool-config '{
        "SessionPinningFilters": ["EXCLUDE_VARIABLE_SETS"]
    }'

可用的 SessionPinningFilters:

  • EXCLUDE_VARIABLE_SETS:排除因設定變數而產生的 Pinning

最佳實踐建議

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# 高流量生產環境
MaxConnectionsPercent: 80
MaxIdleConnectionsPercent: 30
ConnectionBorrowTimeout: 30

# Lambda Serverless 環境
MaxConnectionsPercent: 100
MaxIdleConnectionsPercent: 10
ConnectionBorrowTimeout: 60

# 開發測試環境
MaxConnectionsPercent: 50
MaxIdleConnectionsPercent: 50
ConnectionBorrowTimeout: 120

6. Lambda 與 RDS Proxy 整合

為何 Lambda 需要 RDS Proxy?

Lambda 函數的特性會造成資料庫連線問題:

  1. 冷啟動:每次冷啟動都需要建立新連線
  2. 並行執行:高併發時會產生大量連線
  3. 短暫執行:連線建立後很快就會關閉
  4. 無法共享:每個執行環境獨立,無法共享連線

Lambda 函數設定

Python Lambda 範例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
import json
import boto3
import pymysql
import os

# 在 Lambda 容器層級初始化連線
rds_client = boto3.client('rds')

def get_connection():
    proxy_endpoint = os.environ['RDS_PROXY_ENDPOINT']
    port = int(os.environ.get('DB_PORT', 3306))
    username = os.environ['DB_USERNAME']
    database = os.environ['DB_NAME']
    region = os.environ['AWS_REGION']

    # 產生 IAM 認證 Token
    token = rds_client.generate_db_auth_token(
        DBHostname=proxy_endpoint,
        Port=port,
        DBUsername=username,
        Region=region
    )

    connection = pymysql.connect(
        host=proxy_endpoint,
        port=port,
        user=username,
        password=token,
        database=database,
        connect_timeout=5,
        read_timeout=30,
        ssl={'ssl': True}
    )

    return connection

def lambda_handler(event, context):
    connection = None
    try:
        connection = get_connection()

        with connection.cursor() as cursor:
            cursor.execute("SELECT * FROM users WHERE status = 'active'")
            results = cursor.fetchall()

        return {
            'statusCode': 200,
            'body': json.dumps({
                'count': len(results),
                'data': results
            })
        }

    except Exception as e:
        return {
            'statusCode': 500,
            'body': json.dumps({'error': str(e)})
        }

    finally:
        if connection:
            connection.close()

Lambda 函數的 Terraform 設定

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
resource "aws_lambda_function" "api_handler" {
  filename         = "lambda.zip"
  function_name    = "api-handler"
  role             = aws_iam_role.lambda_role.arn
  handler          = "handler.lambda_handler"
  runtime          = "python3.11"
  timeout          = 30
  memory_size      = 256

  vpc_config {
    subnet_ids         = var.private_subnet_ids
    security_group_ids = [aws_security_group.lambda_sg.id]
  }

  environment {
    variables = {
      RDS_PROXY_ENDPOINT = aws_db_proxy.main.endpoint
      DB_NAME            = "mydb"
      DB_USERNAME        = "iam_user"
      DB_PORT            = "3306"
    }
  }
}

# Lambda 的 IAM Role
resource "aws_iam_role" "lambda_role" {
  name = "lambda-rds-proxy-role"

  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Action = "sts:AssumeRole"
        Effect = "Allow"
        Principal = {
          Service = "lambda.amazonaws.com"
        }
      }
    ]
  })
}

# 允許 Lambda 連接 RDS Proxy
resource "aws_iam_role_policy" "lambda_rds_policy" {
  name = "lambda-rds-proxy-connect"
  role = aws_iam_role.lambda_role.id

  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Effect = "Allow"
        Action = "rds-db:connect"
        Resource = "arn:aws:rds-db:${var.region}:${var.account_id}:dbuser:${aws_db_proxy.main.id}/iam_user"
      }
    ]
  })
}

# VPC 相關權限
resource "aws_iam_role_policy_attachment" "lambda_vpc" {
  role       = aws_iam_role.lambda_role.name
  policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole"
}

Lambda 連線最佳實踐

  1. 連線重用:利用 Lambda 容器重用機制,在處理函數外初始化連線
  2. 適當超時:設定合理的連線超時,避免等待過久
  3. 錯誤處理:妥善處理連線錯誤,實作重試機制
  4. 監控指標:追蹤連線使用情況和錯誤率
1
2
3
4
5
6
7
8
# 連線重用範例
connection = None

def get_or_create_connection():
    global connection
    if connection is None or not connection.open:
        connection = create_new_connection()
    return connection

7. 監控與效能指標

CloudWatch 指標

RDS Proxy 提供多種 CloudWatch 指標供監控:

連線相關指標

指標名稱說明建議閾值
ClientConnections當前客戶端連線數根據預期流量設定
ClientConnectionsNoTLS非 TLS 連線數應為 0
ClientConnectionsSetupSucceeded成功建立的連線數監控趨勢
ClientConnectionsSetupFailedAuth認證失敗的連線數應接近 0
DatabaseConnections到資料庫的連線數< MaxConnectionsPercent
DatabaseConnectionsCurrentlyBorrowed正在使用的資料庫連線監控峰值
AvailabilityPercentageProxy 可用性百分比> 99.9%

效能相關指標

指標名稱說明建議閾值
QueryRequests查詢請求數監控趨勢
QueryRequestsNoTLS非 TLS 查詢數應為 0
QueryDatabaseResponseLatency資料庫回應延遲< 100ms

建立 CloudWatch Dashboard

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
aws cloudwatch put-dashboard \
    --dashboard-name "RDS-Proxy-Monitoring" \
    --dashboard-body '{
        "widgets": [
            {
                "type": "metric",
                "x": 0,
                "y": 0,
                "width": 12,
                "height": 6,
                "properties": {
                    "metrics": [
                        ["AWS/RDSProxy", "ClientConnections", "ProxyName", "my-rds-proxy"],
                        [".", "DatabaseConnections", ".", "."]
                    ],
                    "title": "連線數",
                    "region": "ap-northeast-1",
                    "period": 60
                }
            },
            {
                "type": "metric",
                "x": 12,
                "y": 0,
                "width": 12,
                "height": 6,
                "properties": {
                    "metrics": [
                        ["AWS/RDSProxy", "QueryDatabaseResponseLatency", "ProxyName", "my-rds-proxy"]
                    ],
                    "title": "查詢延遲",
                    "region": "ap-northeast-1",
                    "stat": "p99"
                }
            },
            {
                "type": "metric",
                "x": 0,
                "y": 6,
                "width": 12,
                "height": 6,
                "properties": {
                    "metrics": [
                        ["AWS/RDSProxy", "ClientConnectionsSetupSucceeded", "ProxyName", "my-rds-proxy"],
                        [".", "ClientConnectionsSetupFailedAuth", ".", "."]
                    ],
                    "title": "連線建立成功/失敗",
                    "region": "ap-northeast-1"
                }
            }
        ]
    }'

設定 CloudWatch Alarms

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# 連線數過高警報
aws cloudwatch put-metric-alarm \
    --alarm-name "RDSProxy-HighConnections" \
    --alarm-description "RDS Proxy 連線數超過閾值" \
    --metric-name DatabaseConnections \
    --namespace AWS/RDSProxy \
    --statistic Average \
    --period 300 \
    --threshold 150 \
    --comparison-operator GreaterThanThreshold \
    --dimensions Name=ProxyName,Value=my-rds-proxy \
    --evaluation-periods 2 \
    --alarm-actions arn:aws:sns:ap-northeast-1:123456789012:alerts

# 認證失敗警報
aws cloudwatch put-metric-alarm \
    --alarm-name "RDSProxy-AuthFailures" \
    --alarm-description "RDS Proxy 認證失敗次數異常" \
    --metric-name ClientConnectionsSetupFailedAuth \
    --namespace AWS/RDSProxy \
    --statistic Sum \
    --period 300 \
    --threshold 10 \
    --comparison-operator GreaterThanThreshold \
    --dimensions Name=ProxyName,Value=my-rds-proxy \
    --evaluation-periods 1 \
    --alarm-actions arn:aws:sns:ap-northeast-1:123456789012:alerts

# 延遲過高警報
aws cloudwatch put-metric-alarm \
    --alarm-name "RDSProxy-HighLatency" \
    --alarm-description "RDS Proxy 查詢延遲過高" \
    --metric-name QueryDatabaseResponseLatency \
    --namespace AWS/RDSProxy \
    --extended-statistic p99 \
    --period 300 \
    --threshold 500 \
    --comparison-operator GreaterThanThreshold \
    --dimensions Name=ProxyName,Value=my-rds-proxy \
    --evaluation-periods 3 \
    --alarm-actions arn:aws:sns:ap-northeast-1:123456789012:alerts

啟用 Debug Logging

1
2
3
aws rds modify-db-proxy \
    --db-proxy-name my-rds-proxy \
    --debug-logging

查看 Debug 日誌:

1
2
3
4
aws logs filter-log-events \
    --log-group-name /aws/rds/proxy/my-rds-proxy \
    --start-time $(date -d '1 hour ago' +%s)000 \
    --filter-pattern "ERROR"

8. 故障切換與高可用

RDS Proxy 的高可用架構

RDS Proxy 天生具備高可用性:

  • 自動部署在多個可用區域
  • 維護與後端資料庫的健康連線池
  • 自動偵測資料庫故障並重新路由

故障切換行為

Aurora 叢集故障切換

當 Aurora 主節點故障時:

  1. RDS Proxy 偵測到連線中斷
  2. 自動將請求路由到新的主節點
  3. 應用程式可能收到暫時錯誤
  4. 重試後即可使用新連線
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# 實作重試邏輯
import time
from functools import wraps

def retry_on_db_error(max_retries=3, delay=1):
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            retries = 0
            while retries < max_retries:
                try:
                    return func(*args, **kwargs)
                except Exception as e:
                    retries += 1
                    if retries >= max_retries:
                        raise
                    print(f"資料庫錯誤,重試 {retries}/{max_retries}: {e}")
                    time.sleep(delay * retries)
            return None
        return wrapper
    return decorator

@retry_on_db_error(max_retries=3, delay=2)
def execute_query(query):
    connection = get_connection()
    try:
        with connection.cursor() as cursor:
            cursor.execute(query)
            return cursor.fetchall()
    finally:
        connection.close()

讀寫分離端點

RDS Proxy 支援建立只讀端點:

1
2
3
4
5
6
# 建立只讀端點
aws rds create-db-proxy-endpoint \
    --db-proxy-name my-rds-proxy \
    --db-proxy-endpoint-name my-rds-proxy-reader \
    --vpc-subnet-ids subnet-0123456789abcdef0 subnet-0123456789abcdef1 \
    --target-role READ_ONLY

應用程式層級的讀寫分離

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
class DatabaseRouter:
    def __init__(self):
        self.writer_endpoint = os.environ['RDS_PROXY_WRITER_ENDPOINT']
        self.reader_endpoint = os.environ['RDS_PROXY_READER_ENDPOINT']

    def get_connection(self, read_only=False):
        endpoint = self.reader_endpoint if read_only else self.writer_endpoint
        return create_connection(endpoint)

    def execute_read(self, query, params=None):
        conn = self.get_connection(read_only=True)
        try:
            with conn.cursor() as cursor:
                cursor.execute(query, params)
                return cursor.fetchall()
        finally:
            conn.close()

    def execute_write(self, query, params=None):
        conn = self.get_connection(read_only=False)
        try:
            with conn.cursor() as cursor:
                cursor.execute(query, params)
                conn.commit()
                return cursor.rowcount
        finally:
            conn.close()

# 使用範例
db = DatabaseRouter()
users = db.execute_read("SELECT * FROM users WHERE status = %s", ('active',))
db.execute_write("UPDATE users SET last_login = NOW() WHERE id = %s", (user_id,))

故障切換測試

定期測試故障切換流程:

1
2
3
4
5
6
7
8
# 模擬 Aurora 故障切換
aws rds failover-db-cluster \
    --db-cluster-identifier my-aurora-cluster

# 監控故障切換狀態
aws rds describe-db-clusters \
    --db-cluster-identifier my-aurora-cluster \
    --query 'DBClusters[0].Status'

總結

AWS RDS Proxy 是一個強大的資料庫連線管理解決方案,特別適合以下場景:

  1. Serverless 架構:Lambda 函數的大量短暫連線
  2. 高併發應用:需要高效管理資料庫連線池
  3. 微服務架構:多個服務共享資料庫連線資源
  4. 安全要求高的環境:需要 IAM 認證和強制 TLS

關鍵重點回顧

  • 選擇適合的連線池參數配置
  • 善用 IAM 認證提升安全性
  • 監控關鍵指標並設定警報
  • 實作應用程式層級的重試邏輯
  • 定期測試故障切換流程

透過正確設定和監控 RDS Proxy,您可以大幅提升資料庫的可用性、安全性和效能。


參考資源

comments powered by Disqus
Built with Hugo
Theme Stack designed by Jimmy