資料層與網路¶
資料層完全採用受管理服務——沒有自建資料庫、沒有需要修補的 Redis 叢集、沒有需要調校的連線池。ElastiCache Valkey Serverless 處理所有即時狀態,Aurora PostgreSQL 儲存持久性資料,RDS Proxy 吸收來自短暫 ECS Task 的連線波動。
持久性與可用性
Aurora PostgreSQL:儲存空間自動增長(10 GB 至 128 TB),Multi-AZ 故障切換 < 30 秒(1K 使用者時啟用),讀取複本延遲 < 20ms。Valkey Serverless:從 1 GB / 1K ECPU 自動擴展至 10 GB / 100K ECPU,零需干預;非交易時段成本降至儲存最低費用。RDS Proxy:將數百個短暫 ECS Task 連線多工為約 50 個持久化資料庫連線,防止規模化時連線池耗盡。
ElastiCache Valkey Serverless¶
Valkey(Redis 相容)作為平台的即時神經系統。每一個亞秒級互動——Worker 心跳、訂單路由、工作階段管理——都透過它流轉。
儲存的資料¶
| 資料類型 | 鍵模式 (Key Pattern) | TTL | 用途 |
|---|---|---|---|
| Worker 心跳標記 | worker:active:{user_id} |
30s | 存活偵測。Worker 每 5 秒重新整理。 |
| 請求佇列 | trading:user:{user_id}:requests |
— | 基於 BLPOP 的工作佇列。Worker 即時取出訂單。 |
| 回應信箱 | trading:response:{request_id} |
60s | API 在提交訂單後輪詢 Worker 的回應。 |
| 控制訊息 | worker:control:{user_id}:messages |
— | 無需重啟即可重新載入憑證的命令。 |
| Worker 中繼資料 | worker:metadata:{user_id} |
30s | Task ARN、執行個體 ID、啟動時間。 |
| 工作階段資料 | session:{session_id} |
24h | 使用者工作階段令牌和 CSRF 狀態。 |
| Webhook 重送雜湊 | webhook:replay:{hash} |
5min | 冪等性——拒絕重複的 Webhook 投遞。 |
| 速率限制計數器 | ratelimit:{user_id}:{endpoint} |
Window | 逐使用者逐端點的滑動視窗速率限制。 |
| ECS Task 快取 | ecs:tasks:cache |
90s | 快取 ECS Task 列表以避免 API 節流。 |
| 標的快取 | symbols:{broker}:{exchange} |
1h | 券商標的/合約資料。避免重複 API 呼叫。 |
擴展配置¶
| 參數 | 最小值 | 最大值 | 擴展方式 |
|---|---|---|---|
| 儲存空間 | 1 GB | 10 GB | 自動 |
| 運算 | 1,000 ECPU | 100,000 ECPU | 自動 |
為什麼選擇 Serverless 而非佈建式
交易平台的流量變異極大。在交易時段(台灣市場 09:00-13:30),數千個 Worker 每 5 秒發送心跳,訂單持續流入。收盤後,流量降至接近零。佈建式 ElastiCache 需要按尖峰配置並全天候付費。Valkey Serverless 的 ECPU 隨需求擴展——非交易時段成本降至僅有儲存最低費用。
定價模型¶
Valkey Serverless 按兩個維度計費:
- 儲存空間:$0.125/GB-小時
- ElastiCache 處理單元 (ECPU):每 ECPU $0.0000098
一個 ECPU 大約對應一個對小於 1 KB 資料的簡單命令。對較大 payload 或複雜操作(SORT、LRANGE 對長列表)的命令會按比例消耗更多 ECPU。
RDS Aurora PostgreSQL¶
Aurora PostgreSQL 是所有持久性資料的記錄系統 (system of record)。所有需要在重啟後存活的資料都儲存在這裡。
儲存的資料¶
| 表格 / 領域 | 行數估算(10K 使用者) | 用途 |
|---|---|---|
users |
10K | 使用者帳號、偏好設定、訂閱方案 |
trading_accounts |
30K(平均每使用者 3 個) | 券商憑證(AES-256-GCM 加密)、帳戶配置 |
orders |
每月 1M+ | 訂單歷史、狀態、成交、時間戳記 |
audit_logs |
每月 5M+ | 每個已驗證操作的 IP、user-agent、路徑 |
sessions |
10K 活躍 | 伺服器端工作階段儲存 |
api_keys |
15K | 每個交易帳戶的 Webhook 令牌、API 金鑰 |
subscription_plans |
約 10 | 方案定義、功能開關、限制 |
擴展層級¶
| 使用者數 | 執行個體 | 配置 | 估算成本 |
|---|---|---|---|
| 1-500 | db.t3.large | 單一執行個體,每日快照 | 約 $120/月 |
| 500-1K | db.t3.large | Multi-AZ 待命 | 約 $240/月 |
| 1K-5K | db.r6g.large | Multi-AZ + 每日快照,增強監控 | 約 $400/月 |
| 5K-10K | db.r6g.xlarge | Multi-AZ + 1 個讀取複本 | 約 $800/月 |
| 10K-50K | db.r6g.2xlarge | Multi-AZ + 2 個讀取複本 | 約 $1,800/月 |
| 50K+ | db.r6g.4xlarge | Multi-AZ + 2 個讀取複本 + 佈建 IOPS | 約 $4,000/月 |
Aurora 的優勢
Aurora 的儲存空間從 10 GB 自動增長到 128 TB,無需停機。複本延遲通常 < 20ms。故障切換到待命複本在 30 秒內完成。
RDS Proxy¶
ECS Task 是短暫的——Worker 隨著使用者上線和離開而不斷啟動和停止。如果沒有連線池,每個 Task 開啟一個 PostgreSQL 連線會產生顯著開銷(TLS 握手、身份驗證、資料庫端的記憶體分配)。
為什麼需要 RDS Proxy¶
| 問題 | 無 Proxy | 有 Proxy |
|---|---|---|
| 連線波動 | 每個 Task 新建 TCP + TLS | 在持久化連線池上多工 |
| 連線上限 | db.t3.large 最多約 680 個連線 | 應用程式端看到無限;Proxy 管理連線池 |
| 故障切換 | 應用程式必須偵測並重連 | Proxy 透明處理 |
| 憑證輪換 | 需要重啟應用程式 | Proxy 從 Secrets Manager 取得新憑證 |
配置¶
| 參數 | 值 |
|---|---|
| 最大連線數 | 資料庫最大值的 80% |
| 借用逾時 | 120 秒 |
| 閒置逾時 | 1800 秒 |
| 引擎 | PostgreSQL |
| 身份驗證 | Secrets Manager(支援自動輪換) |
Proxy 將應用程式連線(可能來自數百個 ECS Task)多工到較少的資料庫連線集,跨請求重複使用。這在規模化時至關重要——每個執行個體 30 個 Worker x 10 個執行個體 = 300 個潛在連線,但資料庫透過 Proxy 只看到約 50 個活躍連線。
VPC 架構¶
為什麼 Worker 需要公有子網路¶
Worker 必須透過公共網路存取外部券商 API(Shioaji、Fubon)。將它們放在私有子網路中需要 NAT Gateway——每個可用區域 $0.045/小時加上每 GB 處理 $0.045。對於產生大量對外流量的交易平台,NAT 成本會超過 EC2 執行個體成本。使用公有子網路搭配限制入站流量的安全群組,可以在零額外成本下達到相同的安全態勢。
安全群組規則¶
| 規則 | 來源 | 目的地 | 連接埠 | 協定 |
|---|---|---|---|---|
| ALB → API | ALB SG | API SG | 8000 | TCP |
| API → Valkey | API SG | Valkey SG | 6379 | TCP |
| Worker → Valkey | Worker SG | Valkey SG | 6379 | TCP |
| Lambda → Valkey | Lambda SG | Valkey SG | 6379 | TCP |
| API → RDS Proxy | API SG | RDS SG | 5432 | TCP |
| Worker → RDS Proxy | Worker SG | RDS SG | 5432 | TCP |
| Worker → Internet | Worker SG | 0.0.0.0/0 | 443 | TCP(出站) |
| API → Internet | API SG | 0.0.0.0/0 | 443 | TCP(出站) |
最小權限
沒有安全群組允許 0.0.0.0/0 入站。ALB 安全群組只接受來自網路的 80/443。API Task 只接受來自 ALB 安全群組的 8000。Worker 不接受任何入站——所有通訊都是對外到 Redis 和券商 API。
ALB + WAF¶
應用程式負載均衡器 (Application Load Balancer)¶
| 參數 | 值 |
|---|---|
| 模式 | 面向網際網路 (Internet-facing) |
| TLS | ACM 管理的憑證(自動續約) |
| 健康檢查路徑 | /health |
| 健康檢查間隔 | 15 秒 |
| 健康閾值 | 連續 2 次通過 |
| 不健康閾值 | 連續 3 次失敗 |
| 註銷延遲 | 120 秒 |
| 閒置逾時 | 60 秒 |
WAF 規則¶
| 規則 | 類型 | 配置 | 用途 |
|---|---|---|---|
| 管理員 IP 限制 | IP Set | 管理員 IP 白名單 | 阻擋來自未知 IP 的管理面板存取 |
| TradingView IP 豁免 | IP Set | TradingView Webhook 來源 IP | 繞過合法 Webhook 的速率限制 |
| 登入速率限制 | 基於速率 | 每 IP 每 5 分鐘 100 個請求 | 防止對身份驗證端點的暴力攻擊 |
| 全域速率限制 | 基於速率 | 每 IP 每 5 分鐘 2000 個請求 | 通用 DDoS 防護 |
| AWS 託管 — SQLi | 託管規則群組 | AWSManagedRulesSQLiRuleSet | SQL 注入防護 |
| AWS 託管 — XSS | 託管規則群組 | AWSManagedRulesCommonRuleSet | 跨站腳本攻擊、惡意輸入 |
| AWS 託管 — IP 信譽 | 託管規則群組 | AWSManagedRulesAmazonIpReputationList | 阻擋已知惡意 IP |
TradingView IP 豁免
TradingView 從一組已知的 IP 範圍發送 Webhook。這些 IP 免於速率限制,但仍通過 SQLi/XSS 規則。如果 TradingView 變更其 IP 範圍,Webhook 將被速率限制,直到 IP 集更新為止。
其他服務¶
KMS — 金鑰管理¶
| 參數 | 值 |
|---|---|
| 金鑰類型 | RSA-4096,非對稱 |
| 後端 | HSM(硬體安全模組) |
| 用途 | 包裹逐使用者 AES-256-GCM 資料加密金鑰 |
| 輪換 | 自動年度輪換 |
每個交易帳戶的券商憑證都使用唯一的 AES-256-GCM 金鑰加密。該 AES 金鑰本身使用 KMS RSA 主金鑰加密(包裹)。解密需要加密的資料金鑰(儲存在資料庫中)和 KMS 存取權限(由 IAM 政策控制)。僅竊取資料庫不會暴露任何資訊。
Secrets Manager¶
儲存應用程式配置:資料庫憑證、API 密鑰、加密參數。由 ECS Task 定義和 Lambda 函式在啟動時參照。支援自動輪換。
ECR — 容器映像檔倉庫¶
| 倉庫 | 用途 | 生命週期 |
|---|---|---|
shioaji-api |
API 容器映像檔(FastAPI + Gunicorn) | 保留最近 10 個標記映像檔 |
shioaji-worker |
Worker 容器映像檔(券商 SDK) | 保留最近 10 個標記映像檔 |
映像檔在 CI/CD 中建構、掃描漏洞,並推送到 ECR。ECS 在 Task 啟動時從 ECR 拉取。
Route 53¶
4pass.io 的 DNS 管理。A 記錄別名指向 ALB。健康檢查與 ALB 健康狀態整合。
VPC 端點 — Lambda¶
| 參數 | 值 |
|---|---|
| 類型 | 介面端點 (Interface endpoint) |
| 服務 | com.amazonaws.{region}.elasticache |
| 可用區域 | 3 個(與 Lambda 子網路匹配) |
Lambda 函式在 VPC 內運行以存取 Valkey。VPC 端點提供私有連線,無需透過網際網路路由,降低延遲並提升安全性。