50 KiB
GoodGo Platform — Sổ Tay Vận Hành Hạ Tầng
Cập Nhật Lần Cuối: 11 tháng 4, 2026
Phiên Bản: 1.0
Mục Đích: Tài liệu tham chiếu hạ tầng toàn diện dành cho đội vận hành, SRE và kỹ sư trực
Mục Lục
- Tóm Tắt Điều Hành
- Kiến Trúc Dịch Vụ
- Thông Số Docker Compose
- Tầng Cơ Sở Dữ Liệu
- Bộ Nhớ Đệm & Tìm Kiếm
- Giám Sát & Quan Sát
- Tích Hợp Thanh Toán
- Kiểm Tra Sức Khỏe
- Biến Môi Trường
- Sao Lưu & Phục Hồi
- Quy Trình Triển Khai
- Hướng Dẫn Xử Lý Sự Cố
Tóm Tắt Điều Hành
GoodGo Platform là một monorepo marketplace bất động sản được xây dựng với:
- Frontend: Next.js (TypeScript)
- Backend API: NestJS (TypeScript)
- Dịch Vụ AI: Python/FastAPI
- Cơ Sở Dữ Liệu: PostgreSQL 16 + PostGIS
- Bộ Nhớ Đệm: Redis 7
- Tìm Kiếm: Typesense 27.1
- Lưu Trữ Đối Tượng: MinIO (tương thích S3)
- Giám Sát: Prometheus + Grafana + Loki + Promtail
- Hàng Đợi Thông Điệp: CQRS/Event Bus tích hợp sẵn (NestJS)
Tổng Số Dịch Vụ Trong Sản Xuất: 12+ (chi tiết bên dưới)
Kiến Trúc Dịch Vụ
Danh Sách Dịch Vụ
| Dịch Vụ | Image | Cổng | Mục Đích | Kiểm Tra Sức Khỏe | Phụ Thuộc |
|---|---|---|---|---|---|
| api | goodgo-api:latest |
3001 | NestJS REST API | GET /health (3x30s) |
postgres, redis, typesense, pgbouncer |
| web | goodgo-web:latest |
3000 | Frontend Next.js | GET / (3x30s) |
api |
| ai-services | goodgo-ai-services:latest |
8000 | Python FastAPI (ước tính giá, NLP) | GET /health (3x30s) |
n/a |
| postgres | postgis/postgis:16-3.4 |
5432 | Cơ sở dữ liệu chính | pg_isready (5x10s) |
n/a |
| pgbouncer | edoburu/pgbouncer:1.23.1-p2 |
6432 | Gộp kết nối (chế độ giao dịch) | pg_isready (5x10s) |
postgres |
| redis | redis:7-alpine |
6379 | Bộ nhớ đệm + lưu trữ phiên | PING (5x10s) |
n/a |
| typesense | typesense/typesense:27.1 |
8108 | Chỉ mục tìm kiếm toàn văn | GET /health (5x10s) |
n/a |
| minio | minio/minio:latest |
9000/9001 | Lưu trữ đối tượng + bảng điều khiển | mc ready local (5x10s) |
n/a |
| loki | grafana/loki:3.0.0 |
3100 | Tổng hợp nhật ký | GET /ready (5x15s) |
n/a |
| promtail | grafana/promtail:3.0.0 |
9080 | Trình chuyển nhật ký | (phụ thuộc loki healthy) | loki |
| prometheus | prom/prometheus:v2.51.0 |
9090 | Thu thập số liệu | GET /-/healthy (3x15s) |
n/a |
| grafana | grafana/grafana:10.4.1 |
3002 | Bảng điều khiển + cảnh báo | GET /api/health (3x15s) |
prometheus, loki |
| pg-backup | postgis/postgis:16-3.4 |
— | Cron sao lưu tự động | depends_on postgres | postgres |
Mạng & Ổ Đĩa
- Mạng: Mạng bridge Docker
goodgo-net - Ổ Đĩa:
pgdata— Tệp dữ liệu PostgreSQLredis_data— Ảnh chụp Redis (AOF)typesense_data— Chỉ mục tìm kiếmminio_data— Lưu trữ đối tượngpg_backups— Bản sao lưu cơ sở dữ liệu (lưu giữ hàng ngày: 7 ngày)loki_data— Khối nhật ký (lưu giữ: 15 ngày)prometheus_data— TSDB số liệu (lưu giữ: 30 ngày trên prod, 15 ngày trên dev)grafana_data— Bảng điều khiển, cấu hình nguồn dữ liệu
Thông Số Docker Compose
Môi Trường Phát Triển (docker-compose.yml)
12 Dịch Vụ (phụ thuộc tối thiểu, không giới hạn tài nguyên)
services:
postgres: PostGIS 16, port 5432, healthcheck: pg_isready (30s start-period)
redis: Alpine 7, port 6379, maxmemory: 256mb LRU, AOF enabled
typesense: v27.1, port 8108, CORS enabled, healthcheck /health
minio: latest, ports 9000 (API) / 9001 (console)
ai-services: Custom Python build, port 8000
pg-backup: Automated daily dumps at 02:00 UTC, cron retention cleanup
pg-verify-backup: On-demand backup restore verification (profile: tools)
loki: v3.0.0, port 3100, 15-day retention, 2h compaction delay
promtail: v3.0.0, Docker socket instrumentation, Pino JSON parsing
prometheus: v2.51.0, port 9090, 15-day retention, lifecycle API enabled
grafana: v10.4.1, port 3002, datasources pre-provisioned
Điểm Khác Biệt Chính So Với Prod:
- Không giới hạn tài nguyên (dùng toàn bộ CPU/bộ nhớ hiện có)
- Cửa sổ lưu giữ nhỏ hơn (7-15 ngày)
- PostgreSQL trên cổng 5432 (trực tiếp, không có pgbouncer)
- loki/prometheus/grafana trên các cổng thay thế
Môi Trường Sản Xuất (docker-compose.prod.yml)
14 Dịch Vụ (có pgbouncer, giới hạn tài nguyên, cập nhật cuốn chiếu)
services:
api: NestJS, resource limits: 1g CPU / 1g memory
web: Next.js, resource limits: 0.5 CPU / 512m memory
ai-services: Python, resource limits: 1.0 CPU / 1g memory
postgres: PostGIS, resource limits: 2.0 CPU / 2g memory
pgbouncer: Connection pool (NEW), 20 default connections, transaction mode
redis: 7-alpine, resource limits: 0.5 CPU / 768m memory, password auth
typesense: 27.1, resource limits: 1.0 CPU / 1g memory
minio: latest, resource limits: 0.5 CPU / 1g memory
loki: v3.0.0, resource limits: 0.5 CPU / 512m memory
promtail: v3.0.0, resource limits: 0.25 CPU / 256m memory
prometheus: v2.51.0, resource limits: 0.5 CPU / 1g memory, 30-day retention
grafana: v10.4.1, resource limits: 0.5 CPU / 512m memory
pg-backup: Same as dev
Cờ Chỉ Dành Cho Sản Xuất:
read_only: truetrên các container ứng dụng (api, web, ai-services)tmpfs: [/tmp]cho tệp tạm thời khi chạysecurity_opt: [no-new-privileges:true]logging: json-filevới max-size 10m, xoay vòng 3-5 tệp- PgBouncer chèn giữa ứng dụng ↔ Postgres (cổng 6432)
- Quản lý bí mật:
GRAFANA_ADMIN_USER,GRAFANA_ADMIN_PASSWORDtừ Docker secrets - Redis yêu cầu xác thực bằng mật khẩu
Môi Trường CI/E2E (docker-compose.ci.yml)
Tối Giản 4 Dịch Vụ (tmpfs để tăng tốc độ)
services:
postgres: goodgo_test DB, tmpfs (/var/lib/postgresql/data)
redis: --save "" --appendonly no (no persistence)
typesense: tmpfs (/data)
minio: tmpfs (/data)
Được Sử Dụng Bởi:
- Bộ kiểm thử E2E GitHub Actions
- Lệnh local
docker compose -f docker-compose.ci.yml up --wait
Tầng Cơ Sở Dữ Liệu
PostgreSQL + PostGIS
Phiên Bản: 16.3.4 với extension PostGIS
Schema: 22 mô hình Prisma + theo dõi migration Prisma
Các Mô Hình Schema Prisma
- Xác Thực: User, RefreshToken, OAuthAccount, Agent
- Danh Sách: Property, PropertyMedia, Listing
- Tìm Kiếm: SavedSearch
- Giao Dịch: Transaction, Inquiry, Lead
- Thanh Toán: Payment (với enum PaymentProvider: VNPAY, MOMO, ZALOPAY, BANK_TRANSFER)
- Đăng Ký: Plan, Subscription, UsageRecord
- Phân Tích: Valuation, MarketIndex
- Thông Báo: NotificationLog, NotificationPreference
- Kiểm Toán: AdminAuditLog
- Đánh Giá: Review
Tính Năng Cơ Sở Dữ Liệu Quan Trọng
- Hình Học PostGIS: Property.location (Point, SRID 4326) với chỉ mục GIST
- Enum: UserRole, KYCStatus, PropertyType, TransactionType, ListingStatus, Direction, OAuthProvider, TransactionStatus, LeadStatus, PaymentProvider, PaymentStatus, PaymentType, PlanTier, SubscriptionStatus, NotificationChannel, NotificationStatus, AdminAction, AuditTargetType
- Chỉ Mục Kép: Tối ưu hóa truy vấn trên (role, isActive, createdAt), (sellerId, status, publishedAt), (userId, status, createdAt), v.v.
- Ràng Buộc: Khóa idempotency duy nhất trên Payment (userId, provider, idempotencyKey)
Gộp Kết Nối: PgBouncer
Chế Độ Dev (docker-compose.yml):
- Ứng dụng kết nối trực tiếp đến
postgres:5432 - Không có overhead gộp kết nối
Chế Độ Prod (docker-compose.prod.yml):
- Ứng dụng kết nối đến
pgbouncer:6432 - Chế Độ Pool:
transaction(kết nối được trả về sau mỗi giao dịch) - Kích Thước Pool: 20 kết nối (mặc định, điều chỉnh qua
PGBOUNCER_POOL_SIZE) - Kết Nối Client Tối Đa: 200 (điều chỉnh qua
PGBOUNCER_MAX_CLIENT_CONN) - Pool Dự Phòng: 5 kết nối (dự phòng khi pool cạn kiệt)
- Thời Gian Chờ:
- server_connect_timeout: 15s
- server_idle_timeout: 600s
- server_lifetime: 3600s (tái sử dụng kết nối)
- query_wait_timeout: 120s
- query_timeout: 0 (vô hiệu hóa)
- Bảng Điều Khiển Admin: người dùng pgbouncer_admin (mật khẩu qua biến môi trường PGBOUNCER_ADMIN_PASSWORD)
- Bảng Điều Khiển Thống Kê: người dùng pgbouncer_stats (mật khẩu qua biến môi trường PGBOUNCER_STATS_PASSWORD)
Giải Pháp Thay Thế Cho Migration:
- API có hai biến môi trường DATABASE_URL:
DATABASE_URL→ pgbouncer:6432 (truy vấn thông thường)DATABASE_URL_DIRECT→ postgres:5432 (migration, introspection, DDL)
RUN_MIGRATIONS=truechuyển ứng dụng sang dùng DATABASE_URL_DIRECT choprisma migrate deploy
Chiến Lược Sao Lưu
Sao Lưu Tự Động:
- Lịch Trình: Hàng ngày lúc 02:00 UTC (cron bên trong container pg-backup)
- Định Dạng: Định dạng tùy chỉnh với nén gzip (cấp độ 6)
- Lưu Giữ: 7 ngày (có thể cấu hình qua BACKUP_RETENTION_DAYS)
- Vị Trí: Ổ đĩa
pg_backups(gắn vào lưu trữ bền vững trên prod) - Mẫu Tệp:
goodgo_YYYYMMDD_HHMMSS.sql.gz - Script Khôi Phục:
/scripts/backup/pg-restore.sh(khôi phục thủ công) - Script Xác Minh:
/scripts/backup/pg-verify-backup.sh(xác minh E2E tự động)
Quy Trình Xác Minh (chạy hàng tuần):
- Khôi phục bản sao lưu mới nhất vào cơ sở dữ liệu kiểm tra riêng biệt (
goodgo_verify_<timestamp>) - Xác minh tất cả 22 bảng tồn tại
- So sánh số lượng hàng giữa cơ sở dữ liệu nguồn và được khôi phục
- Tổng kiểm tra các bảng quan trọng (User, Property, Listing, Payment, Subscription, Transaction, Plan, _prisma_migrations)
- Kiểm tra extension PostGIS, chỉ mục, loại enum
- Tạo báo cáo JSON với kết quả đạt/không đạt
- Dọn Dẹp: Xóa cơ sở dữ liệu kiểm tra khi thoát (trừ khi SKIP_CLEANUP=1)
- Mã Thoát: 0=đạt, 1=kiểm tra thất bại, 2=lỗi thiết lập
Xác Minh Sao Lưu CI/CD:
- GitHub Action:
.github/workflows/backup-verify.yml - Chạy hàng tuần vào Chủ nhật 05:00 UTC
- Cũng có thể kích hoạt thủ công với tùy chọn skip_cleanup
- Tải lên báo cáo JSON dưới dạng artifact
Bộ Nhớ Đệm & Tìm Kiếm
Redis
Image: redis:7-alpine
Cổng: 6379
Cấu Hình Sản Xuất:
redis-server \
--appendonly yes \ # Lưu trữ AOF (chỉ cập nhật)
--requirepass ${REDIS_PASSWORD} \ # Yêu cầu xác thực
--maxmemory 512mb \ # Giới hạn bộ nhớ tối đa (prod)
--maxmemory-policy allkeys-lru # Loại bỏ LRU khi đầy
Cấu Hình Phát Triển:
redis-server \
--appendonly yes \
--maxmemory 256mb \
--maxmemory-policy allkeys-lru
Cấu Hình Client ioredis:
// From RedisService in apps/api/src/modules/shared/infrastructure/redis.service.ts
{
host: process.env.REDIS_HOST ?? 'localhost',
port: Number(process.env.REDIS_PORT ?? 6379),
password: process.env.REDIS_PASSWORD ?? undefined,
lazyConnect: true, // Ứng dụng khởi động ngay cả khi Redis không khả dụng
enableReadyCheck: false, // Ngăn lỗi "Redis is not ready" trong thời gian ngắn bị gián đoạn
maxRetriesPerRequest: 1, // Thất bại nhanh (thử lại một lần, không có backoff lũy thừa)
retryStrategy(times: number): number {
return Math.min(times * 1000, 5000); // 1s → 2s → 3s → 4s → 5s → 5s...
}
}
Giảm Thiểu Ưu Ái:
- Lỗi cache không làm hỏng ứng dụng
- CacheService bắt lỗi Redis và trả về cache miss
- Ứng dụng phục vụ dữ liệu trực tiếp từ PostgreSQL nếu Redis bị ngắt
- Kiểm tra sức khỏe tại
GET /health/rediscảnh báo nhưng không làm hỏng readiness probe
Các Trường Hợp Sử Dụng:
- Lưu trữ phiên
- Tầng bộ nhớ đệm cho các truy vấn tốn kém
- Giới hạn tốc độ (nếu được triển khai)
- Bộ đếm thời gian thực
Typesense
Image: typesense/typesense:27.1
Cổng: 8108 (chỉ HTTP, mạng nội bộ Docker)
API Key: ${TYPESENSE_API_KEY} (phải được đặt trong .env)
Schema Collection:
Collection Name: "listings"
Fields:
- listingId (string)
- propertyId (string)
- title (string, searchable, highlights)
- description (string, searchable, highlights)
- propertyType (string, faceted)
- transactionType (string, faceted: SALE/RENT)
- priceVND (int64, sortable)
- pricePerM2 (float, optional)
- areaM2 (float)
- bedrooms (int32, faceted)
- bathrooms (int32, faceted)
- floors (int32)
- direction (string, faceted: NORTH/SOUTH/EAST/WEST/etc)
- address (string)
- ward (string, faceted)
- district (string, faceted)
- city (string, faceted)
- location (geopoint) — for radius search
- agentId (string)
- sellerId (string)
- status (string, faceted: ACTIVE/SOLD/DRAFT/etc)
- publishedAt (int64, sortable)
- viewCount (int32)
- saveCount (int32)
- projectName (string, faceted)
- amenities (string[], faceted)
Tính Năng Tìm Kiếm:
- Tìm kiếm toàn văn trên: title, description, address, district, city, projectName
- Trọng số truy vấn: title=5, description=3, address=2, district=2, city=1, projectName=2
- Lọc: propertyType, transactionType, bedrooms, district, city, status, amenities
- Tìm kiếm địa lý: truy vấn theo bán kính (lat, lng, km)
- Sắp xếp: giá (tăng/giảm), khoảng cách (tăng từ geopoint), ngày (giảm), độ phù hợp
- Đánh dấu: Đánh dấu HTML trên các thuật ngữ khớp trong title và description
- Facets: Trả về số lượng tổng hợp để lọc
TypesenseSearchRepository (apps/api/src/modules/search/infrastructure/services/typesense-search.repository.ts):
ensureCollection()— Tạo schema nếu chưa tồn tạidropCollection()— Dọn dẹp (chỉ kiểm thử)indexDocument(doc)— Upsert một tài liệuindexDocuments(docs)— Import hàng loạt với báo cáo lỗiremoveDocument(id)— Xóa theo IDsearch(params)— Thực hiện tìm kiếm với bộ lọc, sắp xếp, phân trang
Giảm Thiểu Ưu Ái:
- Nếu Typesense bị ngắt, tìm kiếm chuyển về tìm kiếm toàn văn PostgreSQL
- TypesenseClientService triển khai logic thử lại với backoff lũy thừa
- Kiểm tra sức khỏe tại
GET /healthtrả về trạng thái JSON
Giám Sát & Quan Sát
Prometheus
Image: prom/prometheus:v2.51.0
Cổng: 9090
Lưu Giữ: 15 ngày (dev), 30 ngày (prod)
Lifecycle API: Bật (--web.enable-lifecycle)
Mục Tiêu Thu Thập (monitoring/prometheus/prometheus.yml):
scrape_configs:
- job_name: goodgo-api
metrics_path: /metrics
static_configs:
- targets: ['host.docker.internal:3001'] # Dev (API on host)
- targets: ['api:3001'] # Prod (API in container)
labels:
service: goodgo-api
environment: [development|production]
- job_name: prometheus
static_configs:
- targets: ['localhost:9090']
Số Liệu Dự Kiến Từ API:
goodgo_api_request_duration_seconds_bucket{le, route, method}— Histogram độ trễ yêu cầuhttp_requests_total{status_code, job}— Số lượng yêu cầu theo mã trạng thái- Số liệu kinh doanh tùy chỉnh (nếu được triển khai trong NestJS @prometheus decorators)
Quy Tắc Cảnh Báo (monitoring/prometheus/alert-rules.yml)
Cảnh Báo Độ Trễ:
-
ApiLatencyP99High (cảnh báo)
- Kích hoạt: độ trễ p99 > 1s trong 5 phút
- Bảng điều khiển:
/d/goodgo-api-latency/goodgo-api-latency - Sổ tay:
https://docs.goodgo.vn/runbooks/api-latency-high
-
ApiEndpointLatencyP99High (cảnh báo)
- Kích hoạt: p99 mỗi endpoint > 2s trong 5 phút
- Chú thích: nhãn method, route
-
ApiLatencyP99Critical (nghiêm trọng - vi phạm SLO)
- Kích hoạt: độ trễ p99 > 3s trong 3 phút
- Yêu cầu leo thang
- Sổ tay:
https://docs.goodgo.vn/runbooks/api-latency-critical
Cảnh Báo Tỷ Lệ Lỗi:
- ApiErrorRate5xxHigh (cảnh báo)
- Kích hoạt: tỷ lệ lỗi 5xx > 1% trong 5 phút
- Sử dụng:
(lỗi 5xx / tổng yêu cầu) * 100
Grafana
Image: grafana/grafana:10.4.1
Cổng: 3002
Xác Thực: Người dùng/mật khẩu admin từ secrets (prod) hoặc biến môi trường (dev)
Nguồn Dữ Liệu Được Cài Sẵn:
- Prometheus (mặc định, chính)
- Loki (với trường dẫn xuất để liên kết correlationId)
Bảng Điều Khiển:
api-latency.json— API p99/p95/p50, phân tích theo route, endpoint chậmapi-overview.json— Tỷ lệ yêu cầu, tỷ lệ lỗi, trạng thái uptimedatabase.json— Độ trễ truy vấn, sử dụng connection pool, truy vấn chậmlogs.json— Khối lượng nhật ký, nhật ký lỗi, liên kết trace đến Prometheussearch.json— Độ trễ truy vấn Typesense, tỷ lệ lập chỉ mục, kích thước collectionweb-vitals.json— Core Web Vitals Frontend (nếu có thiết bị đo phía client)business-metrics.json— Danh sách đã tạo, thanh toán đã xử lý, đăng ký người dùng
Truy Cập Bảng Điều Khiển Admin:
- URL:
http://localhost:3002(dev) hoặc${GRAFANA_PORT}(prod) - Người dùng mặc định:
admin(thay đổi mật khẩu khi đăng nhập lần đầu) - Chế độ không đăng ký (
GF_USERS_ALLOW_SIGN_UP: false)
Loki & Promtail (Tổng Hợp Nhật Ký)
Loki: grafana/loki:3.0.0, cổng 3100
Cấu Hình:
schema:
- from: 2024-01-01
store: tsdb
schema: v13
limits:
max_entries_limit_per_query: 5000
ingestion_rate_mb: 4
ingestion_burst_size_mb: 6
retention: 360h (15 days)
Promtail: grafana/promtail:3.0.0
Cấu Hình:
- Thu thập nhật ký Docker từ mạng bridge
goodgo-net - Phân tích nhật ký có cấu trúc Pino JSON
- Trích xuất nhãn: level, context, component, service
- Metadata có cấu trúc: method, url, statusCode, correlationId, duration
- Lấy timestamp từ đầu ra Pino (RFC3339Nano)
Định Dạng Nhật Ký Dự Kiến (Pino):
{
"level": 30, // info
"time": "2026-04-11T10:30:00Z",
"msg": "POST /api/listings",
"correlationId": "abc-123-def",
"context": "ListingController",
"component": "api",
"method": "POST",
"url": "/api/listings",
"statusCode": 201,
"duration": 150
}
Tích Hợp Thanh Toán
Các Nhà Cung Cấp Thanh Toán Được Hỗ Trợ
Enum: PaymentProvider (Prisma)
VNPAY— VNPay (cổng thanh toán Việt Nam)MOMO— MoMo (ví điện thoại di động Việt Nam)ZALOPAY— ZaloPay (ví kỹ thuật số Việt Nam)BANK_TRANSFER— Chuyển khoản ngân hàng thủ công (ngoại tuyến)
Luồng Thanh Toán & Xử Lý Callback
Schema Cơ Sở Dữ Liệu (Mô Hình Payment):
model Payment {
id String @id @default(cuid())
userId String
transactionId String?
provider PaymentProvider
type PaymentType // SUBSCRIPTION, LISTING_FEE, DEPOSIT, FEATURED_LISTING
amountVND BigInt
status PaymentStatus // PENDING, PROCESSING, COMPLETED, FAILED, REFUNDED
providerTxId String? // External transaction ID from VNPay/MoMo/ZaloPay
callbackData Json? // Raw callback payload (for audit)
idempotencyKey String? // Prevent duplicate payments (userId, provider, idempotencyKey unique)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
enum PaymentStatus {
PENDING, PROCESSING, COMPLETED, FAILED, REFUNDED
}
enum PaymentType {
SUBSCRIPTION, LISTING_FEE, DEPOSIT, FEATURED_LISTING
}
Command Handler: HandleCallbackHandler
(apps/api/src/modules/payments/application/commands/handle-callback/handle-callback.handler.ts)
-
Xác Minh Chữ Ký Callback:
- Sử dụng
PAYMENT_GATEWAY_FACTORYđể định tuyến đến nhà cung cấp đúng (VNPay/MoMo/ZaloPay) - Gateway.verifyCallback() xác thực chữ ký HMAC
- Ném
ValidationExceptionnếu chữ ký không hợp lệ
- Sử dụng
-
Chuyển Đổi Trạng Thái Idempotent:
- Chỉ cập nhật các thanh toán ở trạng thái:
PENDINGhoặcPROCESSING - Chuyển đổi nguyên tử sang
COMPLETEDhoặcFAILED - Nếu đã ở trạng thái cuối (COMPLETED/FAILED/REFUNDED), trả về trạng thái hiện có (idempotent)
- Ghi nhật ký cảnh báo nếu không tìm thấy thanh toán
- Chỉ cập nhật các thanh toán ở trạng thái:
-
Phát Sự Kiện Miền:
- Tái tạo thực thể miền từ repository
- Phát
PaymentCompletedEventhoặcPaymentFailedEvent - Event bus xuất bản sự kiện đến các subscriber (ví dụ: tạo đăng ký, kích hoạt danh sách)
-
Phản Hồi:
{ paymentId: string, status: PaymentStatus, isSuccess: boolean }
Giao Diện Payment Gateway (payment-gateway.interface.ts):
interface IPaymentGateway {
readonly provider: PaymentProvider
createPaymentUrl(params: CreatePaymentUrlParams): Promise<CreatePaymentUrlResult>
verifyCallback(data: Record<string, string>): CallbackVerifyResult
refund(params: RefundParams): Promise<RefundResult>
}
interface CreatePaymentUrlParams {
orderId: string
amountVND: bigint
description: string
returnUrl: string
ipAddress: string
}
interface CallbackVerifyResult {
isValid: boolean
orderId: string
providerTxId: string
isSuccess: boolean
rawData: Record<string, unknown>
}
interface RefundParams {
providerTxId: string
amountVND: bigint
reason: string
}
interface RefundResult {
success: boolean
refundTxId: string | null
}
Biến Môi Trường
VNPay:
VNPAY_TMN_CODE=<merchant terminal code>
VNPAY_HASH_SECRET=<HMAC secret key>
VNPAY_BASE_URL=https://sandbox.vnpayment.vn/paymentv2/vpcpay.html
VNPAY_API_URL=https://sandbox.vnpayment.vn/merchant_webapi/api/transaction
MoMo:
MOMO_PARTNER_CODE=<partner code>
MOMO_ACCESS_KEY=<access key>
MOMO_SECRET_KEY=<secret key>
MOMO_ENDPOINT=https://test-payment.momo.vn/v2/gateway/api
ZaloPay:
ZALOPAY_APP_ID=<app ID>
ZALOPAY_KEY1=<key 1 (for creating payments)>
ZALOPAY_KEY2=<key 2 (for callback verification)>
ZALOPAY_ENDPOINT=https://sb-openapi.zalopay.vn/v2
Bảo Vệ Race Condition & Idempotency
Vấn Đề: Nhiều callback có thể đến cho cùng một thanh toán (thử lại mạng, thông báo trùng lặp)
Giải Pháp:
-
Khóa Idempotency Duy Nhất:
Payment_idempotency_unique(userId, provider, idempotencyKey)- Ngăn bản ghi thanh toán trùng lặp
- Được tạo bởi client/API trước khi tạo thanh toán
-
Cập Nhật Trạng Thái Nguyên Tử:
paymentRepo.updateIfStatus(orderId, ['PENDING', 'PROCESSING'], newStatus)- Chỉ cập nhật nếu trạng thái hiện tại nằm trong danh sách được phép
- Trả về thực thể đã cập nhật hoặc null nếu đã ở trạng thái cuối
-
Kiểm Tra Trạng Thái Cuối: Nếu đã là COMPLETED/FAILED/REFUNDED, handler trả về trạng thái hiện có
- Không kích hoạt lại sự kiện miền
- Không tính tiền hai lần hoặc giao dịch trùng lặp
Kiểm Tra Sức Khỏe
Các Endpoint Sức Khỏe API
Health Controller (apps/api/src/modules/health/health.controller.ts)
-
GET /health — Liveness Probe (luôn 200 nếu tiến trình đang chạy)
- Sử dụng:
@HealthCheck()trên danh sách probe rỗng - Phản hồi:
{ "status": "ok", "timestamp": "..." } - Trường Hợp Sử Dụng: Readiness Kubernetes/Docker (khởi động ban đầu)
- Sử dụng:
-
GET /health/ready — Readiness Probe (kiểm tra các phụ thuộc)
- Kiểm tra: Kết nối PostgreSQL + Redis
- Phản hồi:
{ "status": "ok", "checks": { "database": { "status": "up" }, "redis": { "status": "up" } } } - Trường Hợp Sử Dụng: Bộ cân bằng tải, trước khi chấp nhận lưu lượng
- Thất Bại: Trả về 503 nếu bất kỳ phụ thuộc nào bị ngắt
-
GET /health/db — Chỉ Readiness Cơ Sở Dữ Liệu
- Kiểm tra: Kết nối PostgreSQL qua truy vấn
SELECT 1 - Trường Hợp Sử Dụng: Khắc phục sự cố cơ sở dữ liệu thủ công
- Kiểm tra: Kết nối PostgreSQL qua truy vấn
-
GET /health/redis — Chỉ Readiness Redis
- Kiểm tra: Lệnh Redis PING
- Trường Hợp Sử Dụng: Khắc phục sự cố Redis thủ công
Triển Khai Kiểm Tra Sức Khỏe
PrismaHealthIndicator (apps/api/src/modules/health/infrastructure/prisma.health.ts):
async isHealthy(key: string): Promise<HealthIndicatorResult> {
try {
await this.prisma.$queryRawUnsafe('SELECT 1');
return this.getStatus(key, true);
} catch {
throw new HealthCheckError('Database check failed', this.getStatus(key, false));
}
}
RedisHealthIndicator (apps/api/src/modules/health/infrastructure/redis.health.ts):
async isHealthy(key: string): Promise<HealthIndicatorResult> {
try {
const client = this.redis.getClient();
const pong = await client.ping();
const isHealthy = pong === 'PONG';
const result = this.getStatus(key, isHealthy);
if (isHealthy) return result;
throw new HealthCheckError('Redis ping failed', result);
} catch (error) {
if (error instanceof HealthCheckError) throw error;
throw new HealthCheckError('Redis check failed', this.getStatus(key, false));
}
}
Kiểm Tra Sức Khỏe Container Docker
Container API:
healthcheck:
test: ['CMD', 'node', '-e', "fetch('http://localhost:3001/health').then(r => { if (!r.ok) throw 1 }).catch(() => process.exit(1))"]
interval: 30s
timeout: 5s
retries: 5
start_period: 30s
Container Web:
healthcheck:
test: ['CMD', 'node', '-e', "fetch('http://localhost:3000').then(r => { if (!r.ok) throw 1 }).catch(() => process.exit(1))"]
interval: 30s
timeout: 5s
retries: 3
start_period: 15s
PostgreSQL:
healthcheck:
test: ['CMD-SHELL', 'pg_isready -U ${DB_USER} -d ${DB_NAME}']
interval: 10s
timeout: 5s
retries: 5
start_period: 30s
Redis:
healthcheck:
test: ['CMD', 'redis-cli', '-a', '${REDIS_PASSWORD}', 'ping']
interval: 10s
timeout: 5s
retries: 5
start_period: 10s
Typesense:
healthcheck:
test: ['CMD', 'curl', '-sf', 'http://localhost:8108/health']
interval: 10s
timeout: 5s
retries: 5
start_period: 15s
Biến Môi Trường
Tham Chiếu .env.example Đầy Đủ
PostgreSQL:
DB_HOST=localhost
DB_PORT=5432
DB_NAME=goodgo
DB_USER=goodgo
DB_PASSWORD=CHANGE_ME
DATABASE_URL=postgresql://${DB_USER}:${DB_PASSWORD}@${DB_HOST}:${DB_PORT}/${DB_NAME}?schema=public
DATABASE_URL_DIRECT=postgresql://${DB_USER}:${DB_PASSWORD}@${DB_HOST}:${DB_PORT}/${DB_NAME}?schema=public
PgBouncer (Chỉ Prod):
PGBOUNCER_POOL_SIZE=20
PGBOUNCER_MAX_CLIENT_CONN=200
PGBOUNCER_ADMIN_PASSWORD=CHANGE_ME
PGBOUNCER_STATS_PASSWORD=CHANGE_ME
Redis:
REDIS_HOST=localhost
REDIS_PORT=6379
REDIS_PASSWORD=
REDIS_URL=redis://${REDIS_HOST}:${REDIS_PORT}
Typesense:
TYPESENSE_HOST=localhost
TYPESENSE_PORT=8108
TYPESENSE_PROTOCOL=http
TYPESENSE_API_KEY=CHANGE_ME
MinIO:
MINIO_ENDPOINT=localhost
MINIO_PORT=9000
MINIO_CONSOLE_PORT=9001
MINIO_ACCESS_KEY=CHANGE_ME
MINIO_SECRET_KEY=CHANGE_ME
MINIO_BUCKET=goodgo-media
MINIO_USE_SSL=false
NestJS API:
API_PORT=3000
PORT=3001
NODE_ENV=development
CORS_ORIGINS=http://localhost:3000,http://localhost:3001
JWT / Xác Thực (BẮT BUỘC):
JWT_SECRET=<generate with: openssl rand -base64 48>
JWT_EXPIRES_IN=15m
JWT_REFRESH_SECRET=<generate with: openssl rand -base64 48>
JWT_REFRESH_EXPIRES_IN=7d
Nhà Cung Cấp OAuth:
GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=
GOOGLE_CALLBACK_URL=http://localhost:3001/auth/google/callback
ZALO_APP_ID=
ZALO_APP_SECRET=
ZALO_CALLBACK_URL=http://localhost:3001/auth/zalo/callback
FRONTEND_URL=http://localhost:3000
Next.js Web:
NEXT_PUBLIC_API_URL=http://localhost:3000
WEB_PORT=3001
Dịch Vụ AI (Python/FastAPI):
AI_SERVICE_PORT=8000
AI_SERVICE_URL=http://localhost:8000
CLAUDE_API_KEY=
AI_DEBUG=false
AI_LOG_LEVEL=info
Tích Hợp Bản Đồ:
NEXT_PUBLIC_MAPBOX_TOKEN=
Cổng Thanh Toán:
VNPAY_TMN_CODE=
VNPAY_HASH_SECRET=
VNPAY_BASE_URL=https://sandbox.vnpayment.vn/paymentv2/vpcpay.html
VNPAY_API_URL=https://sandbox.vnpayment.vn/merchant_webapi/api/transaction
MOMO_PARTNER_CODE=
MOMO_ACCESS_KEY=
MOMO_SECRET_KEY=
MOMO_ENDPOINT=https://test-payment.momo.vn/v2/gateway/api
ZALOPAY_APP_ID=
ZALOPAY_KEY1=
ZALOPAY_KEY2=
ZALOPAY_ENDPOINT=https://sb-openapi.zalopay.vn/v2
Email / SMTP:
SMTP_HOST=localhost
SMTP_PORT=1025
SMTP_USER=
SMTP_PASS=
SMTP_FROM=noreply@goodgo.vn
Firebase Cloud Messaging (Tùy Chọn):
FIREBASE_SERVICE_ACCOUNT=
Theo Dõi Lỗi Sentry:
SENTRY_DSN=
NEXT_PUBLIC_SENTRY_DSN=
SENTRY_AUTH_TOKEN=
SENTRY_ORG=
SENTRY_PROJECT=
Mã Hóa Trường KYC (BẮT BUỘC Prod):
KYC_ENCRYPTION_KEY=<generate with: openssl rand -hex 32> # 64 hex chars (32 bytes)
KYC_ENCRYPTION_KEY_VERSION=1
Ghi Nhật Ký:
LOG_LEVEL=info
Sao Lưu & Phục Hồi
Sao Lưu Tự Động Hàng Ngày
Dịch Vụ: Container pg-backup (chạy bên trong docker compose)
Script Sao Lưu: scripts/backup/pg-backup.sh
# Daily cron job: 02:00 UTC
PGHOST=postgres \
PGPORT=5432 \
PGUSER=goodgo \
PGDATABASE=goodgo \
PGPASSWORD=<secret> \
BACKUP_DIR=/backups \
RETENTION_DAYS=7 \
/scripts/pg-backup.sh
Hành Vi:
- Tạo dump với
pg_dump --format=custom --compress=6 - Lưu dưới dạng
goodgo_YYYYMMDD_HHMMSS.sql.gz - Xóa các bản sao lưu cũ hơn 7 ngày (có thể cấu hình)
- Ghi nhật ký vào
/var/log/pg-backup.log
Khôi Phục Từ Bản Sao Lưu:
# Interactive restore prompt
docker compose -f docker-compose.prod.yml exec pg-backup bash -c \
'pg_restore -h postgres -p 5432 -U goodgo -d goodgo \
--clean --if-exists /backups/goodgo_20260410_020000.sql.gz'
# Or using restore script
docker compose -f docker-compose.prod.yml run --rm pg-verify-backup bash -c \
'source /scripts/pg-restore.sh /backups/goodgo_20260410_020000.sql.gz'
Xác Minh Sao Lưu
Dịch Vụ: Container pg-verify-backup (theo yêu cầu, profile: tools)
Script Xác Minh: scripts/backup/pg-verify-backup.sh
# Usage:
docker compose -f docker-compose.prod.yml run --rm pg-verify-backup
# Or with options:
SKIP_CLEANUP=1 REPORT_FILE=/backups/verify-report.json \
docker compose -f docker-compose.prod.yml run --rm pg-verify-backup
Các Bước Xác Minh:
- Tạo cơ sở dữ liệu kiểm tra riêng biệt:
goodgo_verify_<timestamp> - Bật extension PostGIS
- Khôi phục bản sao lưu vào cơ sở dữ liệu kiểm tra
- Xác minh tất cả 22 bảng tồn tại
- So sánh số lượng hàng giữa nguồn và bản được khôi phục
- Tổng kiểm tra các bảng quan trọng bằng hash MD5
- Kiểm tra chỉ mục, loại enum
- Tạo báo cáo JSON với kết quả
- Dọn Dẹp: Xóa cơ sở dữ liệu kiểm tra (trừ khi SKIP_CLEANUP=1)
Cấu Trúc Báo Cáo JSON:
{
"timestamp": "2026-04-11T10:30:00Z",
"backupFile": "/backups/goodgo_20260410_020000.sql.gz",
"backupSize": "150M",
"testDatabase": "goodgo_verify_20260411_103000",
"restoreDurationSeconds": 45,
"passed": 28,
"failed": 0,
"warnings": 2,
"result": "pass",
"checks": [
{ "check": "Database creation", "status": "pass", "detail": "Test database created" },
{ "check": "Restore", "status": "pass", "detail": "pg_restore completed cleanly in 45s" },
{ "check": "Table existence", "status": "pass", "detail": "All 22 expected tables present" },
{ "check": "Row counts", "status": "pass", "detail": "All tables match source database" },
{ "check": "Checksum: User identities", "status": "pass", "detail": "Hashes match (abc123def456...)" },
...
]
}
Xác Minh Sao Lưu GitHub Action:
- Tệp:
.github/workflows/backup-verify.yml - Lịch trình: Hàng tuần vào Chủ nhật 05:00 UTC
- Cũng có: Kích hoạt thủ công với tùy chọn skip_cleanup
- Artifacts: Tải lên báo cáo JSON trong 30 ngày
Quy Trình Triển Khai
CI/CD GitHub Actions
Các Workflow:
.github/workflows/ci.yml— Lint, typecheck, test, build (khi push/PR đến master).github/workflows/deploy.yml— Build Docker images, triển khai đến staging/prod.github/workflows/e2e.yml— Kiểm thử E2E (khởi chạy toàn bộ docker-compose.ci.yml).github/workflows/backup-verify.yml— Xác minh sao lưu hàng tuần.github/workflows/security.yml— Quét phụ thuộc, SAST.github/workflows/codeql.yml— Phân tích GitHub CodeQL.github/workflows/load-test.yml— Kiểm thử tải K6
CI Pipeline (ci.yml)
Khi: push master, pull_request master
Node: 22
Đồng Thời: Hủy các lần chạy trước trên cùng ref
Công Việc:
-
Lint → Typecheck → Test → Build
- Cài đặt pnpm, Node 22
- Chạy linter (eslint)
- Kiểm tra kiểu (tsc)
- Kiểm thử đơn vị (jest)
- Build tất cả ứng dụng (turbo)
- Dịch vụ PostgreSQL 16 khả dụng (goodgo_test DB)
-
Kiểm Thử E2E (phụ thuộc vào công việc ci)
- Toàn bộ dịch vụ docker-compose.ci.yml (postgres, redis, typesense, minio)
- Chạy bộ kiểm thử end-to-end
- Thời gian chờ: 20 phút
- Biến môi trường: DATABASE_URL, JWT secrets, mã kiểm thử thanh toán
Deploy Pipeline (deploy.yml)
Khi:
push master(tự động triển khai đến staging)workflow_dispatchthủ công (chọn staging hoặc production)
Công Việc:
-
Build API Image
- Build:
goodgo-api:${IMAGE_TAG} - Dockerfile:
apps/api/Dockerfile - Registry:
ghcr.io/goodgo/goodgo-api - Tags: git SHA, tên nhánh,
latest(trên master)
- Build:
-
Build Web Image
- Build:
goodgo-web:${IMAGE_TAG} - Dockerfile:
apps/web/Dockerfile - Registry:
ghcr.io/goodgo/goodgo-web
- Build:
-
Build AI Services Image
- Build:
goodgo-ai-services:${IMAGE_TAG} - Context:
libs/ai-services/ - Registry:
ghcr.io/goodgo/goodgo-ai-services
- Build:
-
Triển Khai Đến Staging
- Điều kiện:
github.event_name == 'push' || inputs.environment == 'staging' - SSH vào máy chủ staging
- Pull images mới từ GHCR
- Cập nhật cuốn chiếu (không có downtime):
docker compose -f docker-compose.prod.yml up -d --no-deps --wait api docker compose -f docker-compose.prod.yml up -d --no-deps --wait web docker compose -f docker-compose.prod.yml up -d --no-deps --wait ai-services - Chạy migrations:
docker compose exec api npx prisma migrate deploy - Xóa images cũ
- Điều kiện:
-
Triển Khai Đến Production
- Chỉ khi
workflow_dispatchthủ công vớienvironment: production - Các bước giống staging
- Yêu cầu phê duyệt
environment: production(bảo mật GitHub)
- Chỉ khi
Build Docker Multi-Stage
API (apps/api/Dockerfile):
- Base: node:22-slim + pnpm 10.27.0
- Deps: Cài đặt các phụ thuộc đã khóa (cache layer)
- Build: Biên dịch TypeScript, tạo Prisma client
- Prune:
pnpm deploy --prod(xóa dev deps, nâng cấp prod deps) - Production: Image tối giản, dumb-init cho signals, người dùng không phải root
Web (apps/web/Dockerfile):
- Base: node:22-slim + pnpm
- Deps: Cài đặt phụ thuộc
- Build:
next build→ standalone output + tệp tĩnh - Production: Sao chép .next/standalone, public, tài sản tĩnh
AI Services (libs/ai-services/Dockerfile):
- Base: python:3.12-slim
- Install: Phụ thuộc hệ thống (gcc, g++), dumb-init, FastAPI/XGBoost/underthesea
- Models: Tải xuống trước các mô hình ML underthesea tại thời gian build
- User: Chạy với appuser không phải root
- CMD:
uvicorn app.main:app --host 0.0.0.0 --port 8000
Hướng Dẫn Xử Lý Sự Cố
Kiểm Tra Trạng Thái Dịch Vụ
# All services
docker compose -f docker-compose.prod.yml ps
# Single service
docker compose -f docker-compose.prod.yml ps api
# Get logs
docker compose -f docker-compose.prod.yml logs -f api --tail=100
# Health check status
docker compose -f docker-compose.prod.yml exec api curl http://localhost:3001/health
Các Vấn Đề Phổ Biến
1. Dịch Vụ API Không Khỏe Mạnh (bị kẹt ở trạng thái "health-check-failed")
Triệu Chứng:
docker compose pshiển thị(health: starting)trong hơn 2 phútdocker compose logs apihiển thị lỗi kết nối
Chẩn Đoán:
# Check API liveness
docker compose exec api curl http://localhost:3001/health
# Check readiness (includes DB + Redis checks)
docker compose exec api curl http://localhost:3001/health/ready
# Check specific dependencies
docker compose exec api curl http://localhost:3001/health/db
docker compose exec api curl http://localhost:3001/health/redis
Giải Pháp:
-
PostgreSQL chưa sẵn sàng:
docker compose ps postgres # Should show (healthy) docker compose exec postgres pg_isready -U goodgo -d goodgo docker compose logs postgres --tail=50 -
Redis chưa sẵn sàng:
docker compose exec redis redis-cli ping # Should return PONG docker compose logs redis --tail=50 -
PgBouncer chưa sẵn sàng (prod):
docker compose exec pgbouncer pg_isready -h 127.0.0.1 -p 6432 -U goodgo docker compose logs pgbouncer --tail=50 -
Schema cơ sở dữ liệu chưa được khởi tạo:
# Run migrations manually docker compose exec api npx prisma migrate deploy # Or check if schema exists docker compose exec postgres psql -U goodgo -d goodgo -c "\dt"
2. Cạn Kiệt Connection Pool Cơ Sở Dữ Liệu Cao
Triệu Chứng:
- Lỗi:
Error: unable to get a connection from the pool after X s - Các truy vấn chậm tích lũy
- Độ trễ API tăng đột biến
Chẩn Đoán:
# Check pool stats (prod, PgBouncer)
docker compose exec pgbouncer psql -h 127.0.0.1 -p 6432 -U pgbouncer_stats -c "SHOW stats"
# Or query PostgreSQL directly
docker compose exec postgres psql -U goodgo -d goodgo -c "SELECT count(*) FROM pg_stat_activity"
Giải Pháp:
- Tăng
PGBOUNCER_POOL_SIZE(mặc định: 20) - Tăng
PGBOUNCER_MAX_CLIENT_CONN(mặc định: 200) - Giảm các truy vấn chạy dài (thêm thời gian chờ truy vấn)
- Kiểm tra các kết nối nhàn rỗi:
server_idle_timeout
3. Lỗi Kết Nối Redis (Không Gây Chết)
Triệu Chứng:
- Nhật ký:
Redis check failedhoặcECONNREFUSED - Nhưng API vẫn phản hồi với các đọc cơ sở dữ liệu chậm hơn
- Kiểm tra sức khỏe
/health/readytrả về 503
Hành Vi Dự Kiến: Cache miss → ứng dụng phục vụ từ cơ sở dữ liệu
Chẩn Đoán:
# Check Redis availability
docker compose exec redis redis-cli ping
# Check RedisService logs
docker compose logs api | grep -i redis
Giải Pháp:
- Khởi động lại Redis:
docker compose restart redis - Kiểm tra bộ nhớ:
docker compose exec redis redis-cli info memory - Nếu đạt
maxmemory, tăng trong docker-compose.yml và khởi động lại
4. Typesense Không Lập Chỉ Mục
Triệu Chứng:
- Tìm kiếm trả về 0 kết quả
- Danh sách đã được tạo nhưng không thể tìm kiếm
/healthcủa typesense hiển thị xanh, nhưng collection trống
Chẩn Đoán:
# Check collection exists
curl http://localhost:8108/collections -H "X-TYPESENSE-API-KEY: ${TYPESENSE_API_KEY}"
# Check collection stats
curl "http://localhost:8108/collections/listings" \
-H "X-TYPESENSE-API-KEY: ${TYPESENSE_API_KEY}" | jq .
# Check recent docs
curl "http://localhost:8108/collections/listings/documents/search?q=*" \
-H "X-TYPESENSE-API-KEY: ${TYPESENSE_API_KEY}" | jq '.found'
Giải Pháp:
- Xác minh
TYPESENSE_API_KEYkhớp với biến môi trường container - Lập lại chỉ mục tất cả danh sách:
docker compose exec api npx ts-node scripts/reindex-listings.ts - Nếu collection bị hỏng, xóa và tạo lại:
curl -X DELETE "http://localhost:8108/collections/listings" \ -H "X-TYPESENSE-API-KEY: ${TYPESENSE_API_KEY}" # Then restart API service to recreate schema docker compose restart api
5. Lỗi Callback Thanh Toán
Triệu Chứng:
- Trạng thái thanh toán bị kẹt ở
PENDING - Nhật ký:
Invalid callback signature for provider=VNPAY
Chẩn Đoán:
# Check payment record in DB
docker compose exec postgres psql -U goodgo -d goodgo -c \
"SELECT id, status, provider, \"providerTxId\", \"callbackData\" FROM \"Payment\" \
WHERE \"providerTxId\" = 'your-txid' ORDER BY \"createdAt\" DESC LIMIT 1;"
# Check logs for callback handler
docker compose logs api | grep -i "HandleCallbackHandler\|callback"
Giải Pháp:
- Xác minh thông tin xác thực cổng thanh toán (VNPAY_HASH_SECRET, MOMO_SECRET_KEY, v.v.)
- Xác minh thủ công chữ ký callback (liên hệ hỗ trợ nhà cung cấp thanh toán)
- Phát lại callback thủ công (nếu có khóa idempotent):
curl -X POST http://localhost:3001/api/payments/callback \ -H "Content-Type: application/json" \ -d '{"provider":"VNPAY",...callback data...}'
6. Xác Minh Sao Lưu Thất Bại
Triệu Chứng:
- GitHub Action
.github/workflows/backup-verify.ymlthất bại - Cơ sở dữ liệu kiểm tra khôi phục hiển thị số lượng hàng không khớp
Chẩn Đoán:
# Run verification manually
docker compose -f docker-compose.ci.yml up postgres
docker compose -f docker-compose.ci.yml exec postgres \
/scripts/pg-verify-backup.sh /backups/goodgo_latest.sql.gz
# Check JSON report
cat /tmp/backups/verify-report.json | jq .
Giải Pháp:
- Kiểm tra nếu tệp sao lưu bị hỏng:
file goodgo_*.sql.gz - Xác minh quá trình khôi phục:
pg_restore --verbose - Kiểm tra tính khả dụng của extension PostGIS:
psql -c "CREATE EXTENSION postgis;"
7. Áp Lực Bộ Nhớ/CPU
Triệu Chứng:
- OOM kills, container thoát 137
- CPU throttling, độ trễ tăng đột biến
container_memory_usage_bytesPrometheus gần giới hạn
Chẩn Đoán:
# Check Docker stats
docker stats --no-stream
# Check limits in compose file
docker compose config | grep -A3 "resources:"
# Check actual memory usage
docker inspect goodgo-api | jq '.HostConfig.Memory'
Giải Pháp:
- Tăng giới hạn tài nguyên trong
docker-compose.prod.yml - Giảm độ chi tiết nhật ký (đặt LOG_LEVEL=warn)
- Triển khai phân trang cho các truy vấn lớn
- Mở rộng theo chiều ngang (thêm nhiều replica API)
Truy Vấn Prometheus Để Gỡ Lỗi
# API request latency p99
histogram_quantile(0.99, sum(rate(goodgo_api_request_duration_seconds_bucket[5m])) by (le))
# API error rate (5xx)
(sum(rate(http_requests_total{status_code=~"5.."}[5m])) / sum(rate(http_requests_total[5m]))) * 100
# Container memory usage
container_memory_usage_bytes{name="goodgo-api"}
# Container CPU usage
rate(container_cpu_usage_seconds_total{name="goodgo-api"}[5m])
# PostgreSQL active queries
pg_stat_activity_count{state="active"}
# Redis memory usage
redis_memory_used_bytes / 1024 / 1024 # in MB
# Typesense collection size
typesense_documents_count{collection="listings"}
Thủ Tục Khẩn Cấp
Reset Toàn Bộ Hệ Thống (chỉ dev):
docker compose down -v # Remove all volumes!
docker system prune -a
docker compose up -d --wait
docker compose exec api npx prisma db push
docker compose exec api npx ts-node scripts/seed.ts
Khôi Phục Cơ Sở Dữ Liệu Khẩn Cấp:
# Find latest backup
ls -t /var/lib/docker/volumes/pg_backups/_data/goodgo_*.sql.gz | head -1
# Restore to new database
pg_restore -h localhost -p 5432 -U goodgo -d goodgo_restored \
--clean --if-exists --verbose /path/to/backup.sql.gz
# Verify restore
psql -U goodgo -d goodgo_restored -c "SELECT count(*) FROM \"User\";"
Buộc Dừng Dịch Vụ Bị Kẹt:
# If health check broken
docker compose kill api
docker compose rm -f api
docker compose up -d api
Phụ Lục: Vị Trí Tệp Quan Trọng
/Users/velikho/Desktop/WORKING/goodgo-platform-ai/
├── docker-compose.yml # Môi trường dev
├── docker-compose.prod.yml # Môi trường prod (có pgbouncer, giới hạn tài nguyên)
├── docker-compose.ci.yml # Môi trường kiểm thử CI/E2E
├── .env.example # Mẫu cho tất cả biến môi trường bắt buộc
│
├── apps/
│ ├── api/
│ │ ├── Dockerfile # Build NestJS multi-stage
│ │ ├── docker-entrypoint.sh # Script khởi động (migrations, khởi động ứng dụng)
│ │ ├── src/
│ │ │ ├── modules/health/health.controller.ts
│ │ │ ├── modules/payments/application/commands/handle-callback/
│ │ │ ├── modules/shared/infrastructure/redis.service.ts
│ │ │ └── modules/search/infrastructure/services/typesense-search.repository.ts
│ │ └── package.json
│ │
│ └── web/
│ ├── Dockerfile # Build Next.js multi-stage
│ └── package.json
│
├── libs/
│ └── ai-services/
│ ├── Dockerfile # Build Python FastAPI
│ ├── app/main.py # Điểm vào ứng dụng FastAPI
│ └── pyproject.toml
│
├── prisma/
│ └── schema.prisma # Schema Prisma đầy đủ (22 mô hình)
│
├── infra/
│ └── pgbouncer/
│ ├── pgbouncer.ini # Cấu hình connection pooling
│ ├── userlist.txt.template # Danh sách người dùng (được tạo mẫu)
│ └── entrypoint.sh # Script thay thế biến môi trường
│
├── scripts/
│ └── backup/
│ ├── pg-backup.sh # Tự động sao lưu hàng ngày
│ ├── pg-verify-backup.sh # Xác minh khôi phục
│ └── pg-restore.sh # Script khôi phục thủ công
│
├── monitoring/
│ ├── prometheus/
│ │ ├── prometheus.yml # Cấu hình scrape (số liệu goodgo-api)
│ │ └── alert-rules.yml # Cảnh báo độ trễ + tỷ lệ lỗi
│ ├── loki/
│ │ └── loki-config.yml # Cấu hình tổng hợp nhật ký (lưu giữ 15 ngày)
│ ├── promtail/
│ │ └── promtail-config.yml # Chuyển nhật ký (phân tích Pino JSON)
│ └── grafana/
│ ├── provisioning/
│ │ ├── datasources/datasource.yml
│ │ └── dashboards/dashboard.yml
│ └── dashboards/
│ ├── api-latency.json
│ ├── api-overview.json
│ ├── database.json
│ ├── logs.json
│ ├── search.json
│ ├── web-vitals.json
│ └── business-metrics.json
│
└── .github/workflows/
├── ci.yml # Lint, test, build
├── deploy.yml # Build images, triển khai đến staging/prod
├── e2e.yml # Kiểm thử end-to-end
├── backup-verify.yml # Xác minh sao lưu hàng tuần
├── security.yml # Quét phụ thuộc/SAST
├── codeql.yml # GitHub CodeQL
└── load-test.yml # Kiểm thử tải K6
Lịch Sử Phiên Bản Tài Liệu
| Phiên Bản | Ngày | Tác Giả | Thay Đổi |
|---|---|---|---|
| 1.0 | 2026-04-11 | Đội DevOps | Sổ tay toàn diện ban đầu |
Cập Nhật Lần Cuối: 11 tháng 4, 2026
Duy Trì Bởi: Đội GoodGo Platform SRE
Liên Hệ: devops@goodgo.vn