RDS & Aurora: PostgreSQL của shop-app rời khỏi Docker
Tiếp đi: RDS. Hiện PostgreSQL của shop-app vẫn chạy Docker trên một EC2 riêng với volume EBS, mình tự pg_dump mỗi đêm bằng cron. Tuần trước EC2 đó reboot do maintenance, app lỗi 10 phút, và mình phát hiện cron backup đã im lặng chết từ 3 tuần. Có nên chuyển sang RDS không, và RDS khác gì "Postgres trên EC2"?
Nên, và đây là một trong những quyết định "mua thay vì tự làm" rõ ràng nhất trên AWS. Amazon RDS (Relational Database Service) là PostgreSQL/MySQL/MariaDB/Oracle/SQL Server/Db2 do AWS vận hành: bạn vẫn dùng JDBC, SQL, Flyway như cũ; AWS lo cài đặt, patch, backup, failover, replica. Aurora là engine riêng của AWS tương thích PostgreSQL/MySQL với tầng storage phân tán. Bài này nằm trọn trong Domain 2 Resilient: Multi-AZ, backup, PITR, replica là chủ đề đề thi hỏi nhiều nhất về database.
1. Bài toán: PostgreSQL tự quản trên EC2
EC2 db-01 (public-a)
└─ docker run postgres:16 -v /data:/var/lib/postgresql/data
└─ cron 02:00: pg_dump | gzip > /backup/shop-$(date).sql.gz (3 tuần nay fail vì đầy đĩa)
shop-app (ASG, 2 instance) ── jdbc:postgresql://10.0.1.50:5432/shop ── password trong application.yml
2. Vấn đề
- Một máy, một AZ: reboot, lỗi EBS, hay AZ sự cố là app down. Không có ai promote standby.
- Backup là script tự viết, không ai kiểm tra; đã fail 3 tuần. Restore chưa bao giờ thử. RPO thực tế: 3 tuần.
- Patch: minor version PostgreSQL có CVE, ai cập nhật? Downtime bao lâu?
- Scale đọc: báo cáo cuối tháng làm chậm cả checkout; muốn replica phải tự cấu hình streaming replication (xem Replication).
- Password nằm trong file cấu hình, commit lên Git; đổi password là deploy lại app.
- Connection: 2–4 instance app × HikariCP 10 = 40 connection; scale ASG lên 10 instance là chạm
max_connections.
3. Giải pháp: RDS PostgreSQL trong private subnet, Multi-AZ, secret quản lý
shop-vpc 10.0.0.0/16
┌────────────────────────────────┬────────────────────────────────┐
│ ap-southeast-1a │ ap-southeast-1b │
│ shop-private-a 10.0.11.0/24 │ shop-private-b 10.0.12.0/24 │
│ ┌──────────────┐ │ ┌──────────────┐ │
│ │ EC2 shop-app │──┐ │ │ EC2 shop-app │──┐ │
│ └──────────────┘ │ 5432 │ └──────────────┘ │ 5432 │
│ ▼ │ ▼ │
│ ┌──────────────────────┐ sync │ ┌──────────────────────┐ │
│ │ shop-db PRIMARY │══════│═▶│ shop-db STANDBY │ │
│ │ db.t3.micro, PG 16 │ repl │ │ (Multi-AZ, không đọc)│ │
│ └──────────────────────┘ │ └──────────────────────┘ │
│ shop-db-sg: 5432 chỉ từ shop-app-sg │
└────────────────────────────────┴────────────────────────────────┘
Endpoint DNS: shop-db.abc123.ap-southeast-1.rds.amazonaws.com (trỏ primary; failover đổi IP)
Secret: shop/db (Secrets Manager, rotation tự động)
| Thành phần | Giải thích | Từ khóa đề thi |
|---|---|---|
| DB instance | Một PostgreSQL chạy trên instance class (db.t3.micro burstable, db.m6g general, db.r6g memory) với EBS storage (gp3/io1) | instance class, storage autoscaling, gp3 vs io1 |
| DB subnet group | Danh sách subnet (≥ 2 AZ) RDS được đặt vào; luôn là private subnet | "database in private subnet", không public accessible |
| Security group | shop-db-sg: 5432 chỉ từ shop-app-sg (source là SG, không phải CIDR) | SG chaining |
| Parameter group | postgresql.conf do AWS quản: max_connections, shared_buffers, log_min_duration_statement; static param cần reboot | parameter group vs option group |
| Multi-AZ | Standby đồng bộ ở AZ khác, không phục vụ đọc; failover tự động 60–120s, DNS endpoint giữ nguyên | high availability, synchronous, failover, không phải scaling |
| Multi-AZ DB cluster | Biến thể mới: 1 writer + 2 reader standby đọc được, failover ~35s (PostgreSQL/MySQL) | "readable standbys" |
| Read Replica | Bản sao bất đồng bộ, phục vụ đọc; cùng/khác AZ/khác region; promote thành DB độc lập | read scaling, asynchronous, cross-region, promote |
| Automated backup | Snapshot hàng ngày + transaction log mỗi 5 phút → PITR trong retention 1–35 ngày | point-in-time recovery, retention, backup window |
| Manual snapshot | Giữ vô hạn, copy sang region/account, share | long-term retention, before major upgrade |
| Encryption | KMS at rest (phải chọn lúc tạo; bật sau = snapshot → copy encrypted → restore); TLS in transit | "encrypt existing unencrypted DB" → snapshot copy |
| Secrets Manager integration | RDS tự tạo và xoay master password (--manage-master-user-password) | rotate credentials, no hardcode |
| IAM DB authentication | Token 15 phút thay password, cho user DB đánh dấu rds_iam | "use IAM instead of passwords" |
| RDS Proxy | Connection pool quản lý giữa app và DB; giảm connection storm từ Lambda/ASG; failover nhanh hơn | Lambda + RDS, "too many connections", faster failover |
| Performance Insights / Enhanced Monitoring | Top SQL theo wait event; metric OS 1s | identify slow queries |
4. Cấu hình từng bước (CLI + Java)
Bước 1: Subnet group và SG.
export AWS_REGION=ap-southeast-1
export ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text --profile shop-lab)
VPC_ID=$(aws ec2 describe-vpcs --filters Name=tag:Name,Values=shop-vpc --query 'Vpcs[0].VpcId' --output text --profile shop-lab)
PRIV_A=$(aws ec2 describe-subnets --filters Name=tag:Name,Values=shop-private-a --query 'Subnets[0].SubnetId' --output text --profile shop-lab)
PRIV_B=$(aws ec2 describe-subnets --filters Name=tag:Name,Values=shop-private-b --query 'Subnets[0].SubnetId' --output text --profile shop-lab)
APP_SG=$(aws ec2 describe-security-groups --filters Name=group-name,Values=shop-app-sg --query 'SecurityGroups[0].GroupId' --output text --profile shop-lab)
aws rds create-db-subnet-group --db-subnet-group-name shop-db-subnets \
--db-subnet-group-description "shop private subnets 1a/1b" \
--subnet-ids $PRIV_A $PRIV_B \
--tags Key=Project,Value=shop-app Key=Env,Value=lab --profile shop-lab
DB_SG=$(aws ec2 create-security-group --group-name shop-db-sg --description "PostgreSQL from app" \
--vpc-id $VPC_ID \
--tag-specifications 'ResourceType=security-group,Tags=[{Key=Name,Value=shop-db-sg},{Key=Project,Value=shop-app},{Key=Env,Value=lab}]' \
--query GroupId --output text --profile shop-lab)
aws ec2 authorize-security-group-ingress --group-id $DB_SG \
--protocol tcp --port 5432 --source-group $APP_SG --profile shop-lab
Bước 2: Parameter group (tùy chọn nhưng nên có, để bật log slow query và buộc TLS).
aws rds create-db-parameter-group --db-parameter-group-name shop-pg16 \
--db-parameter-group-family postgres16 --description "shop-app PG16" \
--tags Key=Project,Value=shop-app Key=Env,Value=lab --profile shop-lab
aws rds modify-db-parameter-group --db-parameter-group-name shop-pg16 --parameters \
"ParameterName=log_min_duration_statement,ParameterValue=500,ApplyMethod=immediate" \
"ParameterName=rds.force_ssl,ParameterValue=1,ApplyMethod=immediate" \
"ParameterName=shared_preload_libraries,ParameterValue=pg_stat_statements,ApplyMethod=pending-reboot" \
--profile shop-lab
Bước 3: Tạo instance. Password do Secrets Manager sinh và quản; bạn không bao giờ thấy nó trong shell history.
aws rds create-db-instance --db-instance-identifier shop-db \
--engine postgres --engine-version 16 \
--db-instance-class db.t3.micro \
--allocated-storage 20 --storage-type gp3 --max-allocated-storage 100 \
--db-name shop --master-username shop \
--manage-master-user-password \
--db-subnet-group-name shop-db-subnets --vpc-security-group-ids $DB_SG \
--db-parameter-group-name shop-pg16 \
--no-publicly-accessible \
--no-multi-az \
--backup-retention-period 7 --preferred-backup-window 18:00-19:00 \
--preferred-maintenance-window sun:19:30-sun:20:30 \
--storage-encrypted \
--enable-performance-insights --performance-insights-retention-period 7 \
--enable-cloudwatch-logs-exports '["postgresql"]' \
--deletion-protection \
--tags Key=Project,Value=shop-app Key=Env,Value=lab --profile shop-lab
aws rds wait db-instance-available --db-instance-identifier shop-db --profile shop-lab # ~5–10 phút
DB_HOST=$(aws rds describe-db-instances --db-instance-identifier shop-db \
--query 'DBInstances[0].Endpoint.Address' --output text --profile shop-lab)
SECRET_ARN=$(aws rds describe-db-instances --db-instance-identifier shop-db \
--query 'DBInstances[0].MasterUserSecret.SecretArn' --output text --profile shop-lab)
Chú ý: backup window 18:00–19:00 UTC = 1–2 giờ sáng Việt Nam. Secret RDS tạo có tên dạng rds!db-xxxx; để app đọc theo tên cố định shop/db, bước 5 sẽ tạo thêm một secret alias hoặc trỏ app bằng ARN.
Bước 4: Role app đọc secret. Thêm vào identity policy của shop-app-ec2-role:
{
"Effect": "Allow",
"Action": ["secretsmanager:GetSecretValue", "secretsmanager:DescribeSecret"],
"Resource": "SECRET_ARN"
},
{
"Effect": "Allow",
"Action": "kms:Decrypt",
"Resource": "*",
"Condition": {"StringEquals": {"kms:ViaService": "secretsmanager.ap-southeast-1.amazonaws.com"}}
}
Bước 5: Spring Boot đọc secret. Cách sạch nhất là Spring Cloud AWS 3 (awspring): import secret thành property source, không viết code gọi SDK.
<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.awspring.cloud</groupId>
<artifactId>spring-cloud-aws-dependencies</artifactId>
<version>3.2.1</version><type>pom</type><scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependency>
<groupId>io.awspring.cloud</groupId>
<artifactId>spring-cloud-aws-starter-secrets-manager</artifactId>
</dependency>
<dependency><groupId>org.postgresql</groupId><artifactId>postgresql</artifactId><scope>runtime</scope></dependency>
# application-aws.yml
spring:
config:
import: "aws-secretsmanager:${SHOP_DB_SECRET}" # ARN hoặc tên secret, truyền qua env/user data
datasource:
url: jdbc:postgresql://${host}:${port}/${dbname}?sslmode=require
username: ${username}
password: ${password}
hikari:
maximum-pool-size: 10
minimum-idle: 2
max-lifetime: 900000 # 15 phút: < thời gian xoay password, kết nối cũ tự thay
connection-timeout: 5000
keepalive-time: 60000
jpa:
open-in-view: false
flyway:
enabled: true
locations: classpath:db/migration
Secret của RDS là JSON {"username":"shop","password":"...","engine":"postgres","host":"shop-db.abc.ap-southeast-1.rds.amazonaws.com","port":5432,"dbname":"shop"}, nên các placeholder ${host}, ${password} được điền thẳng. Đọc thêm về profile và config ở Profile & Config, pool ở Connection pool.
Nếu không dùng awspring, dùng SDK v2 trực tiếp:
@Configuration
public class DataSourceConfig {
@Bean
DataSource dataSource(@Value("${SHOP_DB_SECRET}") String secretId, ObjectMapper om) throws Exception {
try (SecretsManagerClient sm = SecretsManagerClient.create()) { // credential từ role EC2
String json = sm.getSecretValue(r -> r.secretId(secretId)).secretString();
JsonNode s = om.readTree(json);
HikariConfig cfg = new HikariConfig();
cfg.setJdbcUrl("jdbc:postgresql://%s:%d/%s?sslmode=require"
.formatted(s.get("host").asText(), s.get("port").asInt(), s.get("dbname").asText()));
cfg.setUsername(s.get("username").asText());
cfg.setPassword(s.get("password").asText());
cfg.setMaximumPoolSize(10);
cfg.setMaxLifetime(Duration.ofMinutes(15).toMillis());
return new HikariDataSource(cfg);
}
}
}
Hạn chế của cách này: password chỉ đọc một lần lúc start; khi secret xoay, kết nối mới sẽ fail cho tới khi restart. Vì vậy production dùng AWS Secrets Manager JDBC driver (software.amazon.awssdk.secretsmanager:aws-secretsmanager-jdbc, URL jdbc-secretsmanager:postgresql://host/shop, username = secret id) tự refresh khi gặp lỗi xác thực, hoặc RDS Proxy giữ password ở proxy, app chỉ cần IAM token.
Bước 6: Deploy user data truyền secret ARN (bổ sung vào launch template bài 22.5):
Environment=SPRING_PROFILES_ACTIVE=aws
Environment=SHOP_DB_SECRET=arn:aws:secretsmanager:ap-southeast-1:ACCOUNT_ID:secret:rds!db-xxxx
Hoặc tạo secret shop/db của riêng bạn (bài 22.14) và cho RDS dùng nó bằng cách tắt managed password rồi đặt lại; trong lab giữ cái RDS tạo cho đơn giản.
5. Cách hoạt động
Multi-AZ failover:
bình thường: app ── DNS shop-db.xxx.rds.amazonaws.com → 10.0.11.80 (primary, 1a)
mỗi commit: ghi EBS primary + ghi EBS standby (sync) → ack (latency +1–2 ms)
sự cố 1a: RDS phát hiện primary mất (health check nội bộ)
t+0s bắt đầu failover: promote standby 1b
t+~30s đổi DNS record shop-db.xxx → 10.0.12.90
t+60–120s standby thành primary, nhận kết nối
app: HikariCP thấy connection hỏng → tạo mới → resolve DNS lại → kết nối 1b
RDS tạo standby mới ở 1a (nền)
ghi chú: - DNS TTL của RDS endpoint ~5s; JVM phải không cache DNS vô hạn
(networkaddress.cache.ttl mặc định 30s khi không có SecurityManager → OK)
- Transaction đang bay lúc failover: lỗi; app retry ở tầng service (idempotent)
- Reboot "with failover" dùng để tập dượt
Backup và PITR:
00:00 ─────────────────────────────────── 7 ngày ───────────────────────────────────▶ now
│ snapshot tự động hàng ngày (backup window) │ snapshot │ snapshot │ ...
└─ WAL/transaction log upload S3 mỗi 5 phút ──────────────────────────────────────┘
Restore to time 2025-03-10 14:37:00:
= snapshot gần nhất trước đó + replay log tới 14:37 → TẠO INSTANCE MỚI shop-db-restored
(không ghi đè instance cũ; app đổi endpoint hoặc rename)
RPO ≈ 5 phút (latest restorable time), RTO = thời gian restore (phút → giờ tùy dung lượng)
Read Replica:
primary (1a) ──WAL streaming async──▶ replica-1 (1b) ──▶ báo cáo, export
└──▶ replica-2 (us-west-2, cross-region) ──▶ DR / user Mỹ
replica lag: ms → giây (xem metric ReplicaLag); đọc sau ghi có thể chưa thấy
promote replica → DB độc lập, mất liên kết replication (dùng khi DR hoặc tách hệ thống)
Spring: định tuyến read/write bằng hai DataSource và AbstractRoutingDataSource theo @Transactional(readOnly = true) — bài Replication đã mô tả; trên RDS chỉ đổi URL replica endpoint.
Aurora khác RDS ở kiến trúc storage:
RDS: mỗi instance có EBS riêng; Multi-AZ = copy block sang EBS standby; replica = replay WAL
Aurora: cluster volume chung, 6 copy trên 3 AZ, tự heal, tăng 10 GB/lần tới 128 TB
┌──────────┐ ┌──────────┐ ┌──────────┐
│ writer │ │ reader 1 │ │ reader 2 │ ... tới 15 reader, lag thường < 20 ms
└────┬─────┘ └────┬─────┘ └────┬─────┘
└──────────────┴────────────┘
shared storage (log-structured, ghi redo log, không ghi page)
endpoints: cluster (writer) · reader (LB giữa reader) · custom · instance
failover: promote reader ≈ 30s (có reader) · tạo instance mới ≈ phút (không reader)
backtrack (MySQL): quay lui cluster tới thời điểm mà không restore
Serverless v2: ACU 0.5 → 256, scale theo giây, tính tiền theo ACU-giờ; v1 đã ngừng
Global Database: 1 primary region + tới 5 secondary, lag < 1s, promote < 1 phút → DR RPO≈1s
6. So sánh
Multi-AZ vs Read Replica — câu đề thi hỏi nhiều nhất về RDS:
| Multi-AZ (instance) | Multi-AZ DB cluster | Read Replica | |
|---|---|---|---|
| Mục đích | Availability (HA) | HA + chút read scaling | Read scaling, DR, báo cáo |
| Replication | Đồng bộ (sync) | Bán đồng bộ (1 trong 2 standby ack) | Bất đồng bộ (async) |
| Đọc từ bản sao? | Không | Có (2 reader) | Có |
| Failover | Tự động, 60–120s, cùng endpoint | Tự động, ~35s | Thủ công: promote (hoặc tự động hóa bằng script/Lambda) |
| Vị trí | AZ khác, cùng region | 3 AZ, cùng region | Cùng AZ, khác AZ, khác region |
| Số lượng | 1 standby | 2 standby | Tới 15 (PostgreSQL), 5 (MySQL cũ) |
| Backup | Lấy từ standby, không ảnh hưởng primary | Từ reader | Replica có backup riêng nếu bật |
| Chi phí | ~2× instance + storage | ~3× | +1× mỗi replica; cross-region thêm data transfer |
| Free Tier | Không (Single-AZ db.t3.micro 750 giờ thì có) | Không | Không |
Hai thứ kết hợp được: primary Multi-AZ + read replica (replica cũng có thể Multi-AZ). Đề nói "improve availability" → Multi-AZ; "improve read performance / offload reporting" → Read Replica; "disaster recovery in another region" → cross-region Read Replica hoặc Aurora Global Database.
RDS PostgreSQL vs Aurora PostgreSQL vs tự dựng trên EC2
| PostgreSQL trên EC2 | RDS PostgreSQL | Aurora PostgreSQL | |
|---|---|---|---|
| Bạn quản | Mọi thứ: OS, patch, backup, HA, replica | Schema, query, parameter group | Schema, query |
| Tương thích | 100%, extension tùy ý, superuser | Hầu hết extension phổ biến, không superuser | Tương thích PG nhưng chậm theo major version mới hơn RDS |
| HA | Tự làm (Patroni...) | Multi-AZ, 60–120s | 6 copy/3 AZ, failover ~30s |
| Read scaling | Tự làm | Tới 15 replica, lag giây | 15 reader, lag ms, reader endpoint tự LB |
| Storage | EBS, bạn resize | EBS gp3/io1, autoscaling tới giới hạn bạn đặt | Tự tăng tới 128 TB, trả theo dùng |
| Chi phí | Rẻ nhất về hóa đơn, đắt nhất về người | Trung bình; có Free Tier db.t3.micro | Đắt hơn RDS ~20% cùng instance + phí I/O (hoặc I/O-Optimized); Serverless v2 cho tải thưa; không Free Tier |
| Chọn khi | Cần extension/kernel đặc thù, license riêng | Hầu hết app, ngân sách vừa | Cần HA cao, nhiều reader, tải biến động mạnh, multi-region |
Cách quản credential
| Password trong yml/env | Secrets Manager | IAM DB auth | RDS Proxy + IAM | |
|---|---|---|---|---|
| Rotation | Tay, phải deploy | Tự động (RDS managed hoặc Lambda rotation) | Không có password; token 15 phút | Proxy giữ secret; app dùng IAM |
| Thay đổi code | Không | Property import / JDBC wrapper | Sinh token mỗi connection (Hikari cần hook) | Đổi host + IAM token |
| Giới hạn | Lộ trong Git/log | Phí nhỏ/secret/tháng + API call | < 200 kết nối mới/giây, không hợp pool lớn | Phí theo vCPU của DB |
7. Khi nào dùng / không
- RDS: app OLTP quan hệ, muốn giữ PostgreSQL/MySQL và bỏ việc vận hành. Luôn: private subnet, SG chain, encrypted, backup retention ≥ 7, deletion protection, secret quản lý. Production: Multi-AZ.
- Aurora: cần failover nhanh, nhiều reader, tải biến động (Serverless v2), hoặc multi-region (Global Database). Không cần cho app nhỏ: đắt hơn và không Free Tier.
- Không phải RDS: schema key-value/giỏ hàng/session với throughput khủng → DynamoDB; data warehouse → Redshift (22.17); cần extension/engine AWS không hỗ trợ hoặc license BYOL đặc thù → EC2.
- RDS Proxy khi: Lambda hoặc ASG lớn tạo nhiều connection ngắn, muốn failover trong suốt với app, cần IAM auth tập trung.
Mình bật Multi-AZ rồi và trỏ job báo cáo cuối tháng vào cùng endpoint, nghĩ RDS sẽ tự chia SELECT sang standby. Nhưng primary vẫn 100% CPU còn standby hình như không làm gì. Mình hiểu sai chỗ nào?
Bạn hiểu gần đúng về "có hai máy", nhưng nhầm vai trò của máy thứ hai. Standby trong Multi-AZ không nhận bất kỳ kết nối nào: nó chỉ nhận block ghi đồng bộ từ primary và ngồi chờ để lên thay. Nó là bảo hiểm, không phải công suất. Đây là điểm đề SAA đánh rất thường xuyên bằng câu "the standby instance can be used for read traffic" như một lựa chọn sai.
Multi-AZ: app ──▶ primary (xử lý tất cả) ══sync══▶ standby (không có endpoint)
Read Replica: app ──▶ primary ──async──▶ replica (có endpoint riêng, nhận SELECT)
Giải pháp cho báo cáo: tạo Read Replica và trỏ job vào endpoint của nó.
aws rds create-db-instance-read-replica --db-instance-identifier shop-db-report \
--source-db-instance-identifier shop-db --db-instance-class db.t3.micro \
--availability-zone ap-southeast-1b --no-publicly-accessible \
--tags Key=Project,Value=shop-app Key=Env,Value=lab --profile shop-lab
# endpoint: shop-db-report.xxx.ap-southeast-1.rds.amazonaws.com
Phía Spring Boot, job báo cáo dùng DataSource thứ hai:
spring:
datasource:
url: jdbc:postgresql://${host}:5432/shop?sslmode=require
report-datasource:
url: jdbc:postgresql://${REPORT_DB_HOST}:5432/shop?sslmode=require&readOnly=true
@Configuration
public class ReportDataSourceConfig {
@Bean @ConfigurationProperties("spring.report-datasource")
DataSourceProperties reportProps() { return new DataSourceProperties(); }
@Bean(name = "reportDataSource")
DataSource reportDataSource(DataSourceProperties reportProps,
@Value("${username}") String user, @Value("${password}") String pw) {
return reportProps.initializeDataSourceBuilder().username(user).password(pw)
.type(HikariDataSource.class).build(); // cùng user/password từ secret
}
@Bean(name = "reportJdbc")
JdbcTemplate reportJdbc(@Qualifier("reportDataSource") DataSource ds) { return new JdbcTemplate(ds); }
}
Ba điều cần nhớ khi đọc từ replica:
- Lag: ghi đơn hàng xong rồi query replica ngay có thể chưa thấy. Báo cáo cuối tháng không sao; "xem đơn vừa đặt" thì phải đọc primary. Theo dõi metric
ReplicaLag. - Replica là read-only:
INSERTvào replica némcannot execute INSERT in a read-only transaction. Job báo cáo nếu có ghi bảng tổng hợp thì ghi về primary. - Giá: thêm một instance. Với báo cáo chỉ chạy 2 ngày/tháng, cân nhắc tạo replica trước 1 ngày, xóa sau bằng EventBridge Scheduler + Lambda, hoặc đẩy dữ liệu sang S3 (export snapshot to S3) và chạy Athena (22.17) — rẻ hơn nhiều.
Còn Multi-AZ bạn đã bật thì giữ: đó là thứ giúp shop-db sống qua sự cố AZ mà bài mở đầu than phiền. Multi-AZ giải "mất dịch vụ", Read Replica giải "chậm vì đọc nhiều". Hai bệnh, hai thuốc.
FATAL: remaining connection slots are reserved.Flash sale hôm qua ASG lên 8 instance, mỗi instance HikariCP max 10, và Postgres bắt đầu ném FATAL: remaining connection slots are reserved. db.t3.micro có max_connections bao nhiêu vậy? Mình tăng tham số đó lên 500 là xong chứ?
Tăng được, nhưng bạn sẽ chuyển lỗi từ "hết slot" sang "hết RAM và chậm toàn bộ". Hãy nhìn con số trước.
RDS đặt max_connections theo công thức dựa trên RAM của instance class: LEAST({DBInstanceClassMemory/9531392}, 5000). Với db.t3.micro (1 GB) ra khoảng ~100; db.t3.small (2 GB) ~200; db.r6g.large (16 GB) ~1700. Mỗi connection PostgreSQL là một process tốn vài MB RAM (work_mem, buffer) dù đang idle. 500 connection trên 1 GB RAM là OOM hoặc swap.
8 instance × 10 pool = 80 kết nối app
+ 3 (rds superuser reserved) + Performance Insights + replica + psql của bạn ≈ 90–100 → đụng trần
Khi ASG lên max 4 → 40, ổn; cấu hình pool đúng theo max của ASG, không theo 1 instance.
Ba hướng xử lý, từ rẻ đến đúng:
| Hướng | Làm gì | Khi nào đủ |
|---|---|---|
| 1. Co pool | HikariCP maximum-pool-size = (max_connections − 10) / ASG max. Với 100 slot, ASG max 4 → 20; max 8 → 10 (đúng con số đang có, nên lỗi xuất hiện khi lên 8 là do những kết nối "ngoài pool"). | Pool nhỏ hơn bạn nghĩ thường đủ: PostgreSQL phục vụ tốt nhất với ít connection bận hơn nhiều connection chờ (xem Connection pool). |
| 2. Đổi instance class | db.t3.small/db.t4g.medium: gấp đôi RAM, max_connections gấp đôi, cũng là giải pháp cho CPU credit của t3 cạn khi flash sale (burstable!). | Lab/dev; production nhỏ. Có downtime vài phút (Multi-AZ: failover thay vì downtime). |
| 3. RDS Proxy | Proxy đứng giữa, giữ pool tới DB (ví dụ 90), nhận hàng nghìn kết nối từ app/Lambda, multiplex: một connection DB phục vụ nhiều client xen kẽ khi họ không trong transaction. Pinning xảy ra khi session có state (prepared statement, SET, advisory lock). | Lambda gọi RDS, ASG lớn, muốn failover nhanh (proxy giữ kết nối app, chỉ đổi phía DB). |
aws rds create-db-proxy --db-proxy-name shop-db-proxy --engine-family POSTGRESQL \
--auth "AuthScheme=SECRETS,SecretArn=$SECRET_ARN,IAMAuth=REQUIRED" \
--role-arn arn:aws:iam::$ACCOUNT_ID:role/shop-rds-proxy-role \
--vpc-subnet-ids $PRIV_A $PRIV_B --vpc-security-group-ids $DB_SG \
--require-tls --idle-client-timeout 1800 --tags Key=Project,Value=shop-app Key=Env,Value=lab --profile shop-lab
aws rds register-db-proxy-targets --db-proxy-name shop-db-proxy --db-instance-identifiers shop-db --profile shop-lab
# app: jdbc:postgresql://shop-db-proxy.proxy-xxx.ap-southeast-1.rds.amazonaws.com:5432/shop
# role shop-rds-proxy-role cần secretsmanager:GetSecretValue trên secret; app dùng IAM token hoặc password
RDS Proxy tính phí theo vCPU của instance phía sau (có mức tối thiểu), không Free Tier; với db.t3.micro nó có thể đắt hơn cả DB, nên trong lab chỉ cần biết lệnh, không bắt buộc tạo. Trong đề SAA, "Lambda functions open many connections and exhaust the database" → RDS Proxy gần như luôn là đáp án.
Cuối cùng, kiểm tra ai đang chiếm slot:
SELECT usename, application_name, state, count(*)
FROM pg_stat_activity GROUP BY 1,2,3 ORDER BY 4 DESC;
-- idle nhiều → pool để minimum-idle cao hoặc app nào quên đóng; idle in transaction → bug @Transactional dài
Cho mình bài lab để tự tay làm phần này nhé.
Lab 22.7: shop-db PostgreSQL 16 trong private subnet, secret quản lý, Multi-AZ failover, snapshot và PITR
Mục tiêu
shop-dbPostgreSQL 16db.t3.microchạy trongshop-private-a/b, không public, chỉ nhận 5432 từshop-app-sg, password nằm trong Secrets Manager và bạn chưa từng gõ nó.- Từ EC2 của ASG (Session Manager),
psqlkết nối được bằng password lấy từ secret; từ máy bạn thì không kết nối được. - Bật Multi-AZ, chạy
reboot --force-failover, đo thời gian app/psql mất kết nối và thấy AZ của primary đổi. - Tạo manual snapshot và restore PITR ra instance mới, chứng minh dữ liệu "xóa nhầm" khôi phục được.
Chi phí & thời gian
- RDS Single-AZ
db.t3.micro+ 20 GB gp3: trong Free Tier 750 giờ/tháng (12 tháng đầu). Ngoài Free Tier khoảng 0.02–0.03 USD/giờ. - Multi-AZ: không Free Tier, gấp đôi phí instance + storage trong thời gian bật (bước Thử thêm A, giữ ~30 phút ≈ vài cent). Chuyển Single-AZ ↔ Multi-AZ mất 5–15 phút mỗi lần.
- Instance restore PITR: thêm một
db.t3.micronữa, xóa ngay sau khi kiểm tra. - Secrets Manager: khoảng 0.40 USD/secret/tháng (tính theo giờ) + API; snapshot: storage ngoài 20 GB Free Tier tính theo GB-tháng, lab này gần 0.
- Thời gian: 90–120 phút (phần lớn là chờ RDS available/modify).
Chuẩn bị
- Lab 22.3 (
shop-vpc, private subnet, NAT để EC2 cài psql) và 22.5 (ít nhất một EC2 trongshop-app-sgvới roleshop-app-ec2-rolecó SSM). Nếu ASG đã xóa, chạy tay một EC2 từ launch templateshop-ltvàoshop-private-a. - Biến môi trường và profile như mục 4. Role
shop-app-ec2-rolesẽ được thêm quyền đọc secret ở Bước 4.
Các bước
Bước 1 – Subnet group + SG: chạy Bước 1 mục 4.
Console: RDS → Subnet groups → Create (VPC shop-vpc, AZ 1a/1b, chọn 2 private subnet). EC2 → Security Groups → Create shop-db-sg, inbound PostgreSQL 5432, Source = shop-app-sg.
Bước 2 – Parameter group: chạy Bước 2 mục 4.
Console: RDS → Parameter groups → Create (postgres16) → Edit: log_min_duration_statement=500, rds.force_ssl=1.
Bước 3 – Tạo instance: chạy Bước 3 mục 4; trong lúc chờ ~8 phút đọc tiếp Bước 4.
Console: RDS → Create database → Standard create → PostgreSQL 16 → Templates: Free tier → identifier shop-db → master username shop → Credentials management: Managed in AWS Secrets Manager → db.t3.micro → gp3 20 GB, autoscaling 100 → Connectivity: VPC shop-vpc, subnet group shop-db-subnets, Public access No, SG shop-db-sg → Additional: initial database shop, parameter group shop-pg16, backup 7 ngày, encryption, Performance Insights, deletion protection → Create.
aws rds describe-db-instances --db-instance-identifier shop-db \
--query 'DBInstances[0].[DBInstanceStatus,Endpoint.Address,AvailabilityZone,MultiAZ,PubliclyAccessible,StorageEncrypted]' \
--output table --profile shop-lab
Bước 4 – Cho role app đọc secret: chạy Bước 4 mục 4 với SECRET_ARN thật:
cat > secret-policy.json <<EOF
{"Version":"2012-10-17","Statement":[
{"Effect":"Allow","Action":["secretsmanager:GetSecretValue","secretsmanager:DescribeSecret"],"Resource":"$SECRET_ARN"},
{"Effect":"Allow","Action":"kms:Decrypt","Resource":"*",
"Condition":{"StringEquals":{"kms:ViaService":"secretsmanager.$AWS_REGION.amazonaws.com"}}}]}
EOF
aws iam put-role-policy --role-name shop-app-ec2-role --policy-name shop-db-secret-read \
--policy-document file://secret-policy.json --profile shop-lab
Console: IAM → Roles → shop-app-ec2-role → Add permissions → Create inline policy → JSON.
Bước 5 – Kết nối từ EC2 bằng psql:
INSTANCE_ID=$(aws ec2 describe-instances --filters Name=tag:Project,Values=shop-app Name=instance-state-name,Values=running \
--query 'Reservations[0].Instances[0].InstanceId' --output text --profile shop-lab)
aws ssm start-session --target $INSTANCE_ID --profile shop-lab
# ----- trong EC2 (role shop-app-ec2-role) -----
sudo dnf install -y postgresql16 jq
SECRET_ARN=<dán ARN>
S=$(aws secretsmanager get-secret-value --secret-id $SECRET_ARN --query SecretString --output text)
export PGHOST=$(echo $S | jq -r .host) PGUSER=$(echo $S | jq -r .username) PGDATABASE=$(echo $S | jq -r .dbname)
export PGPASSWORD=$(echo $S | jq -r .password) PGSSLMODE=require
psql -c "select version(), inet_server_addr();"
psql -c "create table orders(id bigserial primary key, sku text, qty int, created_at timestamptz default now());"
psql -c "insert into orders(sku, qty) select 'SKU-'||g, (random()*5)::int from generate_series(1,1000) g;"
psql -c "select count(*) from orders;" # 1000
unset PGPASSWORD
Console: Secrets Manager → secret rds!db-… → Retrieve secret value (chỉ để xem cấu trúc JSON; không copy password ra ngoài).
Bước 6 – Spring Boot trên EC2 đọc secret (nếu ASG đang chạy): thêm SHOP_DB_SECRET vào user data của launch template version mới và instance refresh (bài 22.5), hoặc tạm sửa /etc/systemd/system/shop-app.service trên một instance và systemctl restart shop-app. Kiểm tra curl localhost:8080/actuator/health có "db":{"status":"UP"}. Flyway migrate chạy khi start.
Kiểm tra kết quả
# 1. Không public, không kết nối được từ máy bạn
nslookup $DB_HOST # trả IP 10.0.11.x hoặc 10.0.12.x (private)
psql "host=$DB_HOST user=shop dbname=shop sslmode=require" -c 'select 1' # timeout (đúng)
# 2. SG chỉ cho shop-app-sg
aws ec2 describe-security-groups --group-ids $DB_SG \
--query 'SecurityGroups[0].IpPermissions[].[FromPort,UserIdGroupPairs[0].GroupId]' --output text --profile shop-lab
# 3. Secret do RDS quản, rotation tự động 7 ngày
aws secretsmanager describe-secret --secret-id $SECRET_ARN \
--query '[Name,RotationEnabled,RotationRules.AutomaticallyAfterDays,OwningService]' --output text --profile shop-lab
# rds!db-... True 7 rds
# 4. Backup đang bật, có latest restorable time
aws rds describe-db-instances --db-instance-identifier shop-db \
--query 'DBInstances[0].[BackupRetentionPeriod,LatestRestorableTime]' --output text --profile shop-lab
# 5. TLS bắt buộc: psql không sslmode=require (từ EC2) phải bị từ chối
# PGSSLMODE=disable psql -c 'select 1' → "no pg_hba.conf entry ... no encryption"
# 6. Log slow query lên CloudWatch
aws logs describe-log-streams --log-group-name /aws/rds/instance/shop-db/postgresql --max-items 1 --profile shop-lab
Thử thêm
A. Multi-AZ và failover có kiểm chứng. (Không Free Tier; bật ~30 phút rồi tắt.)
aws rds modify-db-instance --db-instance-identifier shop-db --multi-az --apply-immediately --profile shop-lab
aws rds wait db-instance-available --db-instance-identifier shop-db --profile shop-lab # 10–15 phút
aws rds describe-db-instances --db-instance-identifier shop-db \
--query 'DBInstances[0].[MultiAZ,AvailabilityZone,SecondaryAvailabilityZone]' --output text --profile shop-lab
# True ap-southeast-1a ap-southeast-1b
# Trong EC2: vòng lặp đo mất kết nối (mỗi giây in AZ server qua IP)
while true; do date +%T; psql -tAc "select inet_server_addr()" 2>&1 | head -1; sleep 1; done
# Từ máy bạn: ép failover
aws rds reboot-db-instance --db-instance-identifier shop-db --force-failover --profile shop-lab
Quan sát: IP đổi từ 10.0.11.x sang 10.0.12.x; khoảng 30–90 giây lỗi "could not connect" rồi tự nối lại; describe-db-instances cho thấy AvailabilityZone giờ là 1b. Xem thêm event:
aws rds describe-events --source-identifier shop-db --source-type db-instance --duration 60 \
--query 'Events[].[Date,Message]' --output table --profile shop-lab
# "Multi-AZ instance failover started" → "Multi-AZ instance failover completed"
Nếu app Spring Boot đang chạy: HikariCP log vài dòng Connection is not available rồi phục hồi; không cần restart. Tắt Multi-AZ khi xong: modify-db-instance --no-multi-az --apply-immediately.
B. Xóa nhầm và PITR.
# Trong EC2: ghi nhớ giờ, rồi "xóa nhầm"
psql -c "select now();" # ví dụ 2025-03-10 07:42:10+00
sleep 60
psql -c "delete from orders where qty > 2;" # mất ~60% dữ liệu
psql -c "select count(*) from orders;"
# Từ máy bạn: restore về trước lúc xóa 30s → instance MỚI
aws rds restore-db-instance-to-point-in-time --source-db-instance-identifier shop-db \
--target-db-instance-identifier shop-db-pitr --restore-time 2025-03-10T07:42:40Z \
--db-instance-class db.t3.micro --db-subnet-group-name shop-db-subnets --vpc-security-group-ids $DB_SG \
--no-publicly-accessible --tags Key=Project,Value=shop-app Key=Env,Value=lab --profile shop-lab
aws rds wait db-instance-available --db-instance-identifier shop-db-pitr --profile shop-lab # ~10 phút
# Secret của RDS chỉ gắn với shop-db; instance restore dùng cùng master password tại thời điểm restore
PITR_HOST=$(aws rds describe-db-instances --db-instance-identifier shop-db-pitr --query 'DBInstances[0].Endpoint.Address' --output text --profile shop-lab)
# trong EC2: PGHOST=$PITR_HOST psql -c "select count(*) from orders;" → 1000
Thực tế khi khôi phục production: pg_dump bảng từ instance PITR và nạp lại vào primary, hoặc đổi endpoint app sang instance mới (rename identifier). PITR không ghi đè instance gốc, đó là điểm đề thi hay hỏi.
C. Manual snapshot, share và copy encrypted.
aws rds create-db-snapshot --db-instance-identifier shop-db --db-snapshot-identifier shop-db-before-upgrade \
--tags Key=Project,Value=shop-app Key=Env,Value=lab --profile shop-lab
aws rds wait db-snapshot-available --db-snapshot-identifier shop-db-before-upgrade --profile shop-lab
# Copy sang region khác (DR) — snapshot encrypted phải chỉ định KMS key ở region đích
aws rds copy-db-snapshot --source-db-snapshot-identifier arn:aws:rds:$AWS_REGION:$ACCOUNT_ID:snapshot:shop-db-before-upgrade \
--target-db-snapshot-identifier shop-db-before-upgrade-dr --kms-key-id alias/aws/rds \
--source-region $AWS_REGION --region ap-northeast-1 --profile shop-lab
D. Xem Performance Insights: Console RDS → shop-db → Monitoring → Performance Insights; chạy trong EC2 psql -c "select pg_sleep(0.6), count(*) from orders a, orders b limit 1;" vài lần rồi xem top SQL theo wait event, và log slow query xuất hiện trong CloudWatch /aws/rds/instance/shop-db/postgresql.
Dọn tài nguyên
Thứ tự: instance phụ (PITR, replica, proxy) → tắt deletion protection → xóa instance (có/không snapshot cuối) → snapshot thủ công → parameter/subnet group → SG → policy/secret. Multi-AZ, RDS Proxy, replica, snapshot copy ở region khác là những thứ tốn tiền nếu quên.
# 1. Instance phụ
aws rds delete-db-instance --db-instance-identifier shop-db-pitr --skip-final-snapshot --profile shop-lab
aws rds delete-db-instance --db-instance-identifier shop-db-report --skip-final-snapshot --profile shop-lab 2>/dev/null
aws rds delete-db-proxy --db-proxy-name shop-db-proxy --profile shop-lab 2>/dev/null
# 2. Tắt Multi-AZ (nếu còn) và deletion protection, rồi xóa shop-db
aws rds modify-db-instance --db-instance-identifier shop-db --no-multi-az --no-deletion-protection --apply-immediately --profile shop-lab
aws rds wait db-instance-available --db-instance-identifier shop-db --profile shop-lab
# giữ lại DB cho các bài sau? → bỏ qua lệnh dưới. Xóa hẳn:
aws rds delete-db-instance --db-instance-identifier shop-db --skip-final-snapshot --delete-automated-backups --profile shop-lab
aws rds wait db-instance-deleted --db-instance-identifier shop-db --profile shop-lab
# Secret rds!db-... bị xóa theo instance (sau thời gian chờ); kiểm tra:
aws secretsmanager list-secrets --filters Key=owning-service,Values=rds --query 'SecretList[].Name' --profile shop-lab
# 3. Snapshot thủ công (cả region DR)
aws rds delete-db-snapshot --db-snapshot-identifier shop-db-before-upgrade --profile shop-lab
aws rds delete-db-snapshot --db-snapshot-identifier shop-db-before-upgrade-dr --region ap-northeast-1 --profile shop-lab
# 4. Group và SG
aws rds delete-db-parameter-group --db-parameter-group-name shop-pg16 --profile shop-lab
aws rds delete-db-subnet-group --db-subnet-group-name shop-db-subnets --profile shop-lab
aws ec2 delete-security-group --group-id $DB_SG --profile shop-lab
# 5. Policy inline trên role
aws iam delete-role-policy --role-name shop-app-ec2-role --policy-name shop-db-secret-read --profile shop-lab
# 6. Kiểm tra
aws rds describe-db-instances --query 'DBInstances[].DBInstanceIdentifier' --profile shop-lab
aws rds describe-db-snapshots --snapshot-type manual --query 'DBSnapshots[].DBSnapshotIdentifier' --profile shop-lab
Nếu bạn tiếp tục bài 22.8–22.13 ngay, hãy giữ shop-db Single-AZ (Free Tier) và chỉ xóa instance PITR/replica. Xem thêm quy trình backup/restore tổng quát ở Backup & Restore.
Phỏng vấn và đề thi SAA hỏi về RDS và Aurora thì sao?
Q1. Multi-AZ và Read Replica khác nhau thế nào? Khi nào dùng cái nào?
🇻🇳 Tiếng Việt:
Multi-AZ là bài toán sẵn sàng: một standby ở AZ khác nhận ghi đồng bộ, không phục vụ đọc, khi primary hỏng RDS tự failover trong một hai phút và endpoint DNS giữ nguyên nên app chỉ cần pool tạo lại kết nối. Read Replica là bài toán hiệu năng đọc: bản sao bất đồng bộ có endpoint riêng, có thể ở region khác, app trỏ query báo cáo vào đó và chấp nhận lag. Production em luôn bật Multi-AZ; thêm replica khi đọc nặng hoặc cần DR khác region. Hai thứ dùng chung được.
🇬🇧 English:
Multi-AZ is about availability: a standby in another AZ receives synchronous writes, serves no reads, and when the primary fails RDS fails over within a minute or two while the DNS endpoint stays the same, so the app only needs its pool to reconnect. A Read Replica is about read performance: an asynchronous copy with its own endpoint, possibly in another region, where I point reporting queries and accept some lag. In production I always enable Multi-AZ and add replicas when reads are heavy or I need cross-region DR. They can be combined.
Q2. Bạn quản password DB trong Spring Boot trên AWS như thế nào?
🇻🇳 Tiếng Việt:
Em không để password trong yml hay biến môi trường. RDS tạo với managed master password nên secret nằm trong Secrets Manager và tự xoay 7 ngày. Role EC2 của app chỉ có quyền GetSecretValue trên đúng secret đó. Trong Spring Boot em dùng Spring Cloud AWS import secret thành property source, datasource đọc host, username, password từ đó; Hikari max-lifetime đặt 15 phút để kết nối cũ tự thay sau khi xoay. Khi cần không gián đoạn tuyệt đối em dùng Secrets Manager JDBC driver tự refresh hoặc RDS Proxy với IAM auth, app không còn giữ password.
🇬🇧 English:
I never keep the password in YAML or environment variables. The RDS instance is created with a managed master password, so the secret lives in Secrets Manager and rotates every 7 days. The app's EC2 role only has GetSecretValue on that one secret. In Spring Boot I use Spring Cloud AWS to import the secret as a property source, and the datasource reads host, username and password from it; Hikari max-lifetime is 15 minutes so old connections are recycled after rotation. When I need zero interruption I use the Secrets Manager JDBC driver, which refreshes on auth failure, or RDS Proxy with IAM authentication so the app holds no password at all.
Q3. Khôi phục dữ liệu xóa nhầm trên RDS ra sao? RPO/RTO là gì?
🇻🇳 Tiếng Việt:
Automated backup gồm snapshot hàng ngày và transaction log mỗi 5 phút, cho phép point-in-time recovery tới bất kỳ giây nào trong retention tối đa 35 ngày, nên RPO khoảng 5 phút. Restore luôn tạo instance mới, không ghi đè, nên em restore về trước thời điểm xóa, pg_dump bảng bị ảnh hưởng và nạp lại, hoặc đổi endpoint. RTO là thời gian restore cộng thời gian chuyển app, thường vài chục phút. Trước thay đổi lớn em chụp manual snapshot vì nó giữ vô hạn; snapshot còn copy được sang region khác cho DR.
🇬🇧 English:
Automated backups consist of a daily snapshot plus transaction logs every 5 minutes, enabling point-in-time recovery to any second within the retention period of up to 35 days, so the RPO is about 5 minutes. A restore always creates a new instance rather than overwriting, so I restore to just before the deletion, dump the affected tables and load them back, or switch the endpoint. RTO is the restore time plus the application switch, typically tens of minutes. Before major changes I take a manual snapshot because it is kept indefinitely, and snapshots can be copied to another region for DR.
Q4. Khi nào bạn chọn Aurora thay RDS? Aurora Serverless v2 hợp với gì?
🇻🇳 Tiếng Việt:
Aurora khi cần failover nhanh hơn, nhiều reader với lag mili giây qua reader endpoint, storage tự lớn tới 128 TB, hay multi-region với Global Database lag dưới một giây. Nó đắt hơn RDS cùng cỡ và không có Free Tier nên app nhỏ em dùng RDS. Serverless v2 hợp với tải thưa hoặc biến động mạnh như môi trường dev, test, SaaS nhiều tenant nhỏ: scale theo ACU từng giây, trả theo dùng, có thể trộn với instance provisioned trong cùng cluster.
🇬🇧 English:
I choose Aurora when I need faster failover, many readers with millisecond lag behind a reader endpoint, storage that grows automatically up to 128 TB, or multi-region with Global Database and sub-second lag. It costs more than RDS of the same size and has no Free Tier, so small apps stay on RDS. Serverless v2 fits sparse or highly variable workloads such as dev and test environments or multi-tenant SaaS with many small tenants: it scales in ACUs per second, bills for what is used, and can be mixed with provisioned instances in the same cluster.
Góc thi SAA-C03
Câu 1: Một ứng dụng thương mại điện tử dùng RDS PostgreSQL Single-AZ. Công ty yêu cầu database vẫn hoạt động nếu một Availability Zone bị sự cố, với thay đổi ít nhất cho ứng dụng. Giải pháp nào đáp ứng?
A. Tạo Read Replica ở AZ khác và cấu hình ứng dụng chuyển sang replica khi lỗi
B. Chuyển instance sang Multi-AZ deployment
C. Tăng backup retention lên 35 ngày và bật PITR
D. Tạo snapshot hàng giờ và copy sang AZ khác
Đáp án: B. Multi-AZ: standby đồng bộ, failover tự động, endpoint giữ nguyên → không đổi app. A replica async có thể mất dữ liệu, promote thủ công, phải đổi endpoint. C backup phục hồi dữ liệu, không giữ dịch vụ chạy. D snapshot là regional, không có khái niệm "copy sang AZ", và restore mất hàng chục phút.
Câu 2: Một ứng dụng serverless dùng hàng trăm Lambda function kết nối tới RDS MySQL. Khi tải tăng, database báo "Too many connections" và failover mất vài phút khiến ứng dụng lỗi. Giải pháp với LEAST operational overhead?
A. Tăng max_connections trong parameter group và dùng instance class lớn hơn
B. Triển khai RDS Proxy và trỏ Lambda vào proxy endpoint
C. Viết connection pool trong mỗi Lambda và giữ kết nối trong biến global
D. Chuyển sang DynamoDB
Đáp án: B. RDS Proxy pool và multiplex kết nối, giữ kết nối phía client khi DB failover nên app gần như không thấy gián đoạn. A chỉ đẩy giới hạn, tốn tiền, không giải quyết failover. C mỗi Lambda execution environment vẫn giữ một kết nối, hàng trăm environment vẫn là hàng trăm kết nối. D thay đổi toàn bộ data model, không phải "least overhead".
Câu 3: Công ty vận hành ứng dụng toàn cầu trên Aurora PostgreSQL ở eu-west-1. Yêu cầu: người dùng châu Á đọc với latency thấp, và nếu region chính sự cố phải khôi phục trong vòng 1 phút với mất dữ liệu dưới 1 giây. Chọn gì?
A. Cross-region Read Replica của RDS PostgreSQL và promote khi sự cố
B. Aurora Global Database với secondary cluster ở ap-southeast-1
C. Sao chép snapshot sang ap-southeast-1 mỗi giờ
D. Aurora Multi-AZ với 3 reader trong eu-west-1
Đáp án: B. Global Database: replication storage-level lag thường < 1 giây, secondary phục vụ đọc tại chỗ, promote < 1 phút → RPO ~1s, RTO ~1 phút. A replica RDS lag giây đến phút, promote tay lâu hơn. C RPO 1 giờ. D không rời khỏi region nên không chống sự cố region và không giúp latency châu Á.
Cách nhớ ngắn gọn
- Multi-AZ = HA (sync, không đọc, failover tự động, cùng endpoint). Read Replica = read scaling / DR (async, endpoint riêng, promote tay).
- Backup tự động → PITR tới 35 ngày, RPO ~5 phút, restore ra instance mới. Snapshot thủ công → giữ vô hạn, copy/share.
- Password: Secrets Manager managed + role chỉ đọc đúng secret; không có password trong yml. Hikari max-lifetime < chu kỳ xoay.
- Nhiều kết nối (Lambda, ASG lớn) → RDS Proxy. Mã hóa DB cũ → snapshot → copy encrypted → restore.
- Aurora: 6 copy/3 AZ, reader endpoint, failover ~30s, Serverless v2, Global Database (RPO 1s, RTO 1 phút).
| Từ khóa đề | Nghĩ đến |
|---|---|
| highly available, survive AZ failure, minimal app change | Multi-AZ |
| offload reporting / read-heavy / read latency other region | Read Replica (cross-region) |
| point-in-time, accidental deletion, within last N days | Automated backup + PITR (retention ≤ 35) |
| retain backup for years, before upgrade | Manual snapshot (copy cross-region) |
| too many connections, Lambda + RDS, faster failover | RDS Proxy |
| rotate credentials automatically | Secrets Manager (RDS managed password) |
| encrypt an existing unencrypted database | Snapshot → copy with KMS → restore |
| unpredictable / intermittent workload, dev-test | Aurora Serverless v2 |
| RPO < 1 s, RTO < 1 min, multi-region | Aurora Global Database |
| undo bad change without restore (MySQL) | Aurora Backtrack |
| identify slow SQL, wait events | Performance Insights |
| database in private subnet, only app can connect | DB subnet group private + SG source = app SG |
Tự kiểm tra
Standby của Multi-AZ có nhận query SELECT không?
Không. Nó chỉ nhận ghi đồng bộ và chờ failover. Muốn đọc từ bản sao thì tạo Read Replica (hoặc dùng Multi-AZ DB cluster có 2 reader).
PITR có ghi đè instance đang chạy không? RPO xấp xỉ bao nhiêu?
Không, PITR luôn tạo instance mới. Log upload mỗi 5 phút nên RPO ≈ 5 phút; retention tối đa 35 ngày.
Vì sao Hikari max-lifetime nên nhỏ hơn chu kỳ xoay password?
Password xoay thì kết nối đang mở vẫn sống, nhưng kết nối mới dùng password cũ sẽ fail. Đặt max-lifetime ngắn để pool thay kết nối đều, và dùng JDBC wrapper/awspring refresh hoặc RDS Proxy để không gián đoạn.
DB hiện chưa mã hóa, muốn bật encryption at rest thì làm gì?
Không bật trực tiếp được. Snapshot → copy snapshot với KMS key → restore thành instance mới → đổi endpoint app.
Lab xong, thứ gì trên RDS tốn tiền nếu quên tắt?
Multi-AZ (gấp đôi), instance PITR/replica, RDS Proxy, snapshot copy ở region khác, và secret trong Secrets Manager; Single-AZ db.t3.micro còn trong Free Tier 12 tháng đầu.
🧪 Thực hành bài này: Intermediate: Ứng dụng HA: ALB + ASG + RDS + S3 + SQS (chưa triển khai) · lab labs/aws