TL;DR
Migrate app AWS → Cloudflare là quyết định kiến trúc, không phải task engineering thuần. 3 chiến lược khác nhau về thời gian + rủi ro + khối lượng code.
Luận điểm chính:
Đừng migrate thẳng từ Lambda sang Worker mà không đánh giá toàn stack. Primitive Cloudflare khác đủ để một số pattern AWS (DynamoDB hot partition, RDS connection pool, S3 Glacier lifecycle) không map 1-1. Migration tốt là portfolio thinking: cái nào migrate được (saving lớn), cái nào giữ AWS (chuyên biệt), cái nào refactor lại (legacy). Strangler fig pattern an toàn nhất.
Bài này đi qua: mapping từng service AWS → Cloudflare, 3 chiến lược (strangler, big bang, hybrid), data migration (DynamoDB export, S3 sync, RDS dump), cutover plan với DNS TTL, rollback strategy, và 10 pitfall thật.
Bài này đóng Block 5 (Production) và series 20 phần Cloudflare Developer Platform Handbook.
Dành cho ai
- Team đang AWS muốn đánh giá migration khả thi.
- Engineering lead đã quyết định migrate, cần playbook cụ thể.
- Architect designer hệ thống mới: “AWS hay Cloudflare?”.
Nên đọc trước: toàn series (19 phần trước), đặc biệt Part 19 (cost model).
Sau bài này bạn sẽ:
- Biết primitive nào map được, cái nào cần refactor.
- Chọn chiến lược migration phù hợp với team + risk profile.
- Có playbook cụ thể: data migration, cutover, rollback.
- Tránh 10 pitfall thật của team khác.
Bài này không nói về gì
- Quyết định migrate hay không: giả định đã có lý do business (cost, ops, edge performance).
- Migrate sang Cloudflare từ Vercel/Netlify: thường đơn giản hơn (chủ yếu Astro/Next.js adapter switch). Focus AWS là use case phức tạp hơn.
- Migration Kubernetes workload: khác phạm trù. Workers không thay thế container runtime.
Mapping primitive
Lambda → Worker
Map thẳng: Lambda handler(event, context) → Worker fetch(request, env, ctx).
Khác:
- Runtime: Lambda = Node/Python/Go/Ruby. Worker = V8 isolate, chỉ JS/TS/WASM. Python native qua Pyodide (chậm, preview).
- Cold start: Lambda 100-500ms. Worker < 5ms.
- CPU: Lambda tính theo GB-s, config memory. Worker tính CPU time pure.
- Filesystem: Lambda
/tmp512MB. Worker không có filesystem.
Code migration Lambda Node.js → Worker:
// Lambda
exports.handler = async (event) => {
const body = JSON.parse(event.body);
// ...
return { statusCode: 200, body: JSON.stringify(result) };
};
// Worker
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const body = await request.json();
// ...
return Response.json(result);
},
};
Lambda Python: rewrite sang TypeScript. Heuristic: 1 Lambda Python 200 dòng ≈ 2-4 ngày rewrite TypeScript nếu team biết JS.
API Gateway → Worker routing
API Gateway: cấu hình trong AWS console, integration với Lambda.
Worker: routing trong code (Hono, itty-router, Part 9).
import { Hono } from "hono";
const app = new Hono();
app.get("/api/users/:id", handleUser);
app.post("/api/subscribe", handleSubscribe);
export default app;
Lợi: routing version control được. Hại: mất feature chuyên biệt (APIGW request validator, usage plan).
DynamoDB → KV hoặc D1
DynamoDB là đa dụng. Map theo access pattern:
Key-value đơn giản, eventually consistent OK → KV:
// DynamoDB
await ddb.put({ TableName: "users", Item: { id, name, email } });
const { Item } = await ddb.get({ TableName: "users", Key: { id } });
// KV
await env.USERS_KV.put(id, JSON.stringify({ name, email }));
const user = JSON.parse(await env.USERS_KV.get(id));
Query theo index, filter, aggregate → D1:
// DynamoDB với GSI
await ddb.query({ TableName: "posts", IndexName: "byTag", KeyConditionExpression: "tag = :t", ... });
// D1
await env.DB.prepare("SELECT * FROM posts WHERE tag = ? ORDER BY pubdate DESC LIMIT 10")
.bind(tag).all();
Hot partition / high throughput → D1 với pattern partition hoặc giữ DynamoDB qua Worker fetch.
RDS / Aurora → D1 hoặc Hyperdrive
Schema nhỏ (< 10GB), traffic trung bình → D1:
- Dump RDS:
pg_dump -h rds-endpoint -U admin dbname > backup.sql - Chuyển syntax Postgres → SQLite:
SERIAL→INTEGER PRIMARY KEY AUTOINCREMENT.TIMESTAMPTZ→TEXT(ISO 8601 string).JSONB→TEXT(parse trong app).- Không có stored procedure, trigger đơn giản, không có array.
- Apply vào D1:
wrangler d1 execute my-db --file=backup.sql
Schema lớn, feature Postgres-specific (array, JSON op, extension) → Hyperdrive: Hyperdrive = Cloudflare managed pool + cache trước Postgres external (Neon, RDS, Supabase). Worker kết nối qua Hyperdrive, giảm latency + connection overhead.
import postgres from "postgres";
const sql = postgres(env.HYPERDRIVE.connectionString);
const users = await sql`SELECT * FROM users WHERE id = ${id}`;
Giữ hạ tầng Postgres, thêm Cloudflare edge layer.
S3 → R2
Migrate đơn giản nhất. API tương thích (S3 signature).
Tool:
- rclone:
rclone sync s3:my-bucket r2:my-bucket— parallel, resumable. - superglue (Cloudflare tool): bulk migration.
- Worker sync: code tuỳ biến fetch S3 → put R2 với progress tracking.
Sau migrate, cập nhật endpoint trong code:
// SAI: endpoint S3
const response = await fetch(`https://my-bucket.s3.amazonaws.com/${key}`);
// ĐÚNG: binding R2
const object = await env.BUCKET.get(key);
return new Response(object.body);
Cập nhật hết mọi chỗ s3.amazonaws.com trong code + IAM policy + lifecycle config.
SQS → Queues
SQS → Cloudflare Queues. Concept tương tự:
// SQS Lambda consumer
exports.handler = async (event) => {
for (const record of event.Records) {
const body = JSON.parse(record.body);
await process(body);
}
};
// Queue consumer Worker
export default {
async queue(batch: MessageBatch<MyMsg>, env: Env): Promise<void> {
for (const msg of batch.messages) {
try {
await process(msg.body);
msg.ack();
} catch {
msg.retry();
}
}
},
};
Khác:
- SQS message group ID → không có ở Queues. Order giữ qua message key hoặc DO.
- SQS FIFO → Queues không đảm bảo strict order.
- SQS visibility timeout → Queues có tương tự (
retryDelay).
ElastiCache → Durable Objects + KV
Cache đơn giản, đọc nhiều → KV (eventually consistent OK).
Session, lock, rate limit → Durable Objects (strong consistency).
Pub/Sub → Durable Objects với WebSocket broadcast.
Data structure Redis (Sorted Set, Hash, List) không có Cloudflare equivalent trực tiếp. Rewrite logic với D1 (cho SQL-friendly) hoặc DO (cho state machine).
EventBridge cron → Scheduled Worker
{
"triggers": {
"crons": ["0 * * * *"]
}
}
export default {
async scheduled(event: ScheduledEvent, env: Env, ctx: ExecutionContext) {
// mỗi giờ
},
};
Map 1-1. EventBridge rule phức tạp (multi-target, dead letter) cần logic tuỳ biến Worker.
Cognito → Access + OIDC
Cognito user pool → Cloudflare Access với SSO IdP (Google, Okta, GitHub).
Migration user: export Cognito, import vào IdP. Không có tool native Cloudflare cho identity migration (khác phạm vi platform).
Bedrock → Workers AI + AI Gateway
Part 13 đã cover. Model Workers AI tier entry (Llama, Mistral). Frontier (GPT-4, Claude 4.7, Gemini 2.5 Pro) vẫn gọi qua AI Gateway.
SES → external
Cloudflare không có Email Sending. Dùng Resend, Postmark, SendGrid qua HTTP từ Worker.
SageMaker / EMR / Redshift → giữ AWS
Workload chuyên biệt không có tương đương Cloudflare. Chiến lược hybrid (phần dưới).
3 chiến lược migration
① Strangler fig (khuyến nghị cho prod)
Worker đặt trước AWS origin. Route 1-2 endpoint qua Worker mỗi tuần, rest proxy về AWS.
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const url = new URL(request.url);
// Endpoint đã migrate
if (url.pathname === "/api/posts") return handlePosts(request, env);
if (url.pathname === "/api/search") return handleSearch(request, env);
// Chưa migrate → proxy tới AWS
const awsUrl = `https://old-app.example.com${url.pathname}${url.search}`;
return fetch(awsUrl, request);
},
};
Ưu:
- Rollback per-endpoint dễ.
- Zero downtime (DNS không đổi).
- Test với production traffic thật.
- Team tự tin dần.
Nhược:
- Chậm (tuần-tháng).
- 2 stack song song = 2 deployment.
- Thêm latency cho endpoint chưa migrate (Worker → AWS → Worker → client).
Khi nào: app prod critical, team nhỏ, không muốn downtime.
② Big bang (cho app nhỏ)
Build full Cloudflare stack trên staging, QA, data migrate, cutover DNS 1 đêm.
Timeline ví dụ:
- Week 1-3: rewrite Lambda → Worker, schema D1/R2, test staging.
- Week 4: QA end-to-end, load test.
- Week 5: data migration (offline job).
- Week 6: cutover DNS, monitor 48h, rollback nếu cần.
Ưu:
- Dứt điểm nhanh.
- Không 2 stack song song.
- Team một lần vượt bão.
Nhược:
- Cửa sổ downtime trong cutover.
- Rollback phức tạp (TTL DNS 24-48h).
- Bug prod chỉ thấy sau cutover.
Khi nào: app nhỏ (< 20 endpoint), team có kinh nghiệm cả AWS lẫn CF, cửa sổ dev rảnh.
③ Hybrid permanent (cho enterprise)
Cloudflare làm edge (Worker, WAF, cache, CDN). AWS giữ core (Aurora, SageMaker, EMR, chuyên biệt).
User → Cloudflare Worker (auth, cache, rate limit)
→ AWS ALB (qua Tunnel hoặc Hyperdrive)
→ Aurora / SageMaker / EMR
Ưu:
- Không migrate workload khó.
- Edge saving rõ ràng (WAF, cache, DDoS).
- Không ép team retrain hoàn toàn skill AWS → CF.
Nhược:
- 2 vendor quản lý.
- Egress AWS vẫn tính (Worker → AWS kéo data).
- Cost ngang hoặc không giảm nhiều.
Khi nào: có workload AWS chuyên biệt (ML training, data warehouse) không thể migrate.
Tool data migration
Export DynamoDB
Dùng feature S3 Export:
aws dynamodb export-table-to-point-in-time \
--table-arn arn:aws:dynamodb:us-east-1:xxx:table/my-table \
--s3-bucket my-export-bucket
Output: file JSON Lines trên S3.
Worker ingest vào D1:
export async function importDynamoDB(request: Request, env: Env) {
const { files } = await request.json(); // list S3 URL
for (const url of files) {
const response = await fetch(url);
const text = await response.text();
const records = text.split("\n").filter(Boolean).map(JSON.parse);
// Batch insert vào D1
const stmts = records.map((r) =>
env.DB.prepare("INSERT INTO users VALUES (?, ?, ?)")
.bind(r.id, r.name, r.email)
);
await env.DB.batch(stmts);
}
}
S3 → R2 sync
rclone (khuyến nghị):
# Config 2 remote
rclone config # add s3 + r2
# Sync
rclone sync s3:my-bucket r2:my-bucket --progress --transfers=10
# Verify
rclone check s3:my-bucket r2:my-bucket
Parallel, resumable, delta sync. 10TB mất vài giờ.
RDS Postgres dump
# Export
pg_dump -h rds.example.com -U admin -d mydb \
--no-owner --no-privileges --data-only \
> data.sql
# Convert syntax
sed -i 's/TIMESTAMPTZ/TEXT/g' data.sql
# Manual review cho JSONB, array
# Import vào D1
wrangler d1 execute my-db --file=data.sql
Chuyển schema: manual hơn. Types Postgres → SQLite không 1-1.
Alternative: giữ Postgres, dùng Hyperdrive.
Cutover plan: chi tiết big bang
24h trước cutover:
- Staging working đầy đủ (smoke test pass).
- Backup data full (S3 snapshot, RDS snapshot, DynamoDB PITR).
- Rollback plan documented.
- Lịch oncall team.
- Communication plan (status page, customer email).
Cutover steps (Saturday 2AM Vietnam time, traffic thấp nhất):
- T-60min: freeze write vào AWS (mode read-only).
- T-55min: sync data incremental (S3 → R2, DynamoDB → D1 delta).
- T-30min: verify data parity (sample check).
- T-15min: deploy Worker config production (binding point về R2/D1).
- T-5min: giảm DNS TTL xuống 60s (đã làm 24h trước để cache expired).
- T-0: cutover DNS → Cloudflare Worker.
- T+5min: smoke test live (Part 12).
- T+10min: monitor tỉ lệ 4xx, 5xx, latency.
- T+30min: nếu OK, unfreeze write.
- T+24h: monitor sát. Cửa sổ rollback.
Rollback (nếu cần)
- Revert DNS về AWS (TTL 60s → propagate 1-2 phút).
- Unfreeze write gate AWS.
- Mất mát: request trong 10-15 phút có thể double-write (vào R2 và S3 nếu chưa gate đúng).
Post-incident: review root cause, lên kế hoạch thử lại.
10 pitfall thật
① Python Lambda → phải rewrite TypeScript
Workers Python (Pyodide) còn preview. Rewrite TS là path chuẩn. Ước lượng không đủ effort = slip timeline.
② Hot partition DynamoDB ≠ KV partition
DynamoDB phân partition theo hash key. KV không có khái niệm đó. Hot key trên DynamoDB có thể thành single-key bottleneck trên KV (write conflict).
③ Query JSONB Postgres → D1 không có
-- Postgres
SELECT * FROM events WHERE data->>'type' = 'login';
-- D1 (không support jsonb op)
SELECT * FROM events WHERE json_extract(data, '$.type') = 'login';
json_extract của SQLite thay thế, nhưng không index được. Với query nhiều, denormalize cột riêng (event_type) để index trực tiếp.
④ Lifecycle Glacier S3 không map
R2 không có Glacier tier (cold storage rẻ hơn 10 lần). Pattern archive khác: store nén vào R2 hoặc giữ AWS Glacier + proxy qua Worker.
⑤ Memory Lambda vs CPU limit
Lambda config memory gián tiếp set CPU. Worker tính CPU time pure. Handler CPU-bound 300ms trong Lambda 3GB memory = fine. Worker 30s CPU limit có thể OK nhưng CPU chart khác AWS CloudWatch.
⑥ Export user Cognito không dễ
Cognito không export password hash. User phải reset password khi migrate sang IdP mới. Communication plan critical.
⑦ Stage var API Gateway mất
API Gateway có stageVariables.key. Worker không có. Thay bằng env var bình thường, deploy per environment.
⑧ Lambda VPC không map
Lambda trong VPC truy cập private RDS trực tiếp. Worker edge không trong VPC. Cần Cloudflare Tunnel hoặc Hyperdrive route qua endpoint Postgres public (có auth).
⑨ SQS DLQ ≠ Queues DLQ
SQS DLQ: message fail retry max lần sang DLQ. Queues có dead_letter_queue binding. Config khác, format message không giống, cần adapter.
⑩ CloudFormation drift
IaC (CloudFormation, Terraform AWS) không map sang Cloudflare. Cloudflare có Terraform provider nhưng coverage khác. Re-IaC migration = thêm effort.
Khi nào KHÔNG migrate
Không phải mọi app nên migrate. Giữ AWS nếu:
Workload AWS-native
- SageMaker training: không có equivalent CF.
- EMR / Athena / Redshift: data warehouse, ETL.
- Kinesis streaming: stream throughput cao.
- Lambda với runtime khác (Rust, .NET): không JS-friendly.
Đã có investment lớn
- 100+ Lambda function, 50+ DynamoDB table, 5 năm tech debt. Saving $/tháng không justify 6 tháng migration cost.
- Team chuyên AWS, không kinh nghiệm CF. Cost retrain > saving.
Yêu cầu compliance
- HIPAA, PCI với BAA AWS cụ thể.
- FedRAMP. Cloudflare có FedRAMP Moderate cho một số service, nhưng kiểm tra coverage.
Discount enterprise sâu
- AWS 3-year RI giảm 60%. Cost AWS có thể thấp hơn Cloudflare list price.
Checklist readiness migration
Trước khi bắt đầu:
- Đánh giá cost rõ ràng (template Part 19).
- Xác định workload không migrate được (SageMaker, EMR, v.v.).
- Chọn chiến lược (strangler / big bang / hybrid).
- Team training CF fundamental (series này cover).
- Staging environment Cloudflare sẵn sàng.
- Tool data migration chọn xong (rclone, DynamoDB export).
- Rollback plan documented.
- Giảm DNS TTL 24h trước cutover (nếu big bang).
- Communication plan (status page, customer notify).
- Bản đối chiếu monitoring (AWS CloudWatch vs CF Analytics Engine).
Trong quá trình:
- Smoke test post-deploy (Part 12).
- Monitor error rate + latency (Part 17).
- Kiểm tra data parity.
- Verify security header (Part 18).
Sau:
- Tracking cost 1 tháng post-migration.
- So sánh performance baseline AWS vs CF.
- Retrospective team: cái gì work, cái gì không.
- Dừng resource AWS (tiết kiệm cost).
Kết series
20 phần. 58 bài blog. 100+ SVG diagram. Từ Part 1 (Cloudflare Developer Platform là gì) đến Part 20 (migration).
Block 1: Foundation (1-4) — platform, runtime, mental model, dev loop.
Block 2: Storage (5-8) — KV, D1, R2, Queues + Durable Objects.
Block 3: Framework (9-12) — Router, ORM, Astro/Remix/SvelteKit, CI/CD.
Block 4: AI + Media (13-16) — Workers AI, Vectorize RAG, Durable Objects realtime, Stream + Images.
Block 5: Production (17-20) — Observability, Security, Cost, Migration.
Cloudflare Developer Platform không phải cloud “mới”. Nó là shift mental model từ compute region-based + egress nhiều sang edge-first + zero egress + bundle include. Một số pattern không migrate được, nhưng phần lớn app web — static site, blog, SaaS, API, realtime — fit tốt hơn Cloudflare về cost + performance + ops simplicity.
Series này viết trong ~4 tháng cho blog cá nhân. Với team/startup: đọc → thử → quyết định. Không có “đúng tuyệt đối”. Có workload đúng cho platform đúng.
Cảm ơn các bạn đã đọc qua. Phản hồi qua KhaVan hoặc reply comment bài nào. Chúc build cái gì đó thú vị.