Files
goodgo-platform/docs/audits/INFRASTRUCTURE_RUNBOOK.md
Ho Ngoc Hai 11f2bf26e6
Some checks failed
CI / Lint → Typecheck → Test → Build (22) (push) Failing after 29s
CI / E2E Tests (push) Has been skipped
CodeQL Analysis / CodeQL (javascript-typescript) (push) Failing after 2m42s
Deploy / Build Web Image (push) Failing after 27s
Deploy / Build AI Services Image (push) Failing after 29s
E2E Tests / Playwright E2E (push) Failing after 43s
Deploy / Build API Image (push) Failing after 1m31s
Security Scanning / Dependency Audit (pnpm) (push) Failing after 6s
Security Scanning / Trivy Scan — API Image (push) Failing after 5m35s
Security Scanning / Trivy Scan — AI Services Image (push) Failing after 3m45s
Deploy / Deploy to Staging (push) Has been skipped
Deploy / Smoke Test Staging (push) Has been skipped
Deploy / Deploy to Production (push) Has been skipped
Deploy / Smoke Test Production (push) Has been skipped
Deploy / Rollback Staging (push) Has been skipped
Deploy / Rollback Production (push) Has been skipped
Security Scanning / Trivy Scan — Web Image (push) Failing after 13m51s
Security Scanning / Trivy Filesystem Scan (push) Failing after 14m46s
Security Scanning / Security Gate (push) Has been cancelled
chore: update project documentation, audit reports, and initialize IDE configuration files
2026-04-19 03:12:54 +07:00

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

  1. Tóm Tắt Điều Hành
  2. Kiến Trúc Dịch Vụ
  3. Thông Số Docker Compose
  4. Tầng Cơ Sở Dữ Liệu
  5. Bộ Nhớ Đệm & Tìm Kiếm
  6. Giám Sát & Quan Sát
  7. Tích Hợp Thanh Toán
  8. Kiểm Tra Sức Khỏe
  9. Biến Môi Trường
  10. Sao Lưu & Phục Hồi
  11. Quy Trình Triển Khai
  12. 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 PostgreSQL
    • redis_data — Ảnh chụp Redis (AOF)
    • typesense_data — Chỉ mục tìm kiếm
    • minio_data — Lưu trữ đối tượng
    • pg_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: true trên các container ứng dụng (api, web, ai-services)
  • tmpfs: [/tmp] cho tệp tạm thời khi chạy
  • security_opt: [no-new-privileges:true]
  • logging: json-file vớ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_PASSWORD từ 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

  1. Xác Thực: User, RefreshToken, OAuthAccount, Agent
  2. Danh Sách: Property, PropertyMedia, Listing
  3. Tìm Kiếm: SavedSearch
  4. Giao Dịch: Transaction, Inquiry, Lead
  5. Thanh Toán: Payment (với enum PaymentProvider: VNPAY, MOMO, ZALOPAY, BANK_TRANSFER)
  6. Đăng Ký: Plan, Subscription, UsageRecord
  7. Phân Tích: Valuation, MarketIndex
  8. Thông Báo: NotificationLog, NotificationPreference
  9. Kiểm Toán: AdminAuditLog
  10. Đá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=true chuyển ứng dụng sang dùng DATABASE_URL_DIRECT cho prisma 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):

  1. 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>)
  2. Xác minh tất cả 22 bảng tồn tại
  3. So sánh số lượng hàng giữa cơ sở dữ liệu nguồn và được khôi phục
  4. Tổng kiểm tra các bảng quan trọng (User, Property, Listing, Payment, Subscription, Transaction, Plan, _prisma_migrations)
  5. Kiểm tra extension PostGIS, chỉ mục, loại enum
  6. Tạo báo cáo JSON với kết quả đạt/không đạt
  7. Dọn Dẹp: Xóa cơ sở dữ liệu kiểm tra khi thoát (trừ khi SKIP_CLEANUP=1)
  8. 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/redis cả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ại
  • dropCollection() — Dọn dẹp (chỉ kiểm thử)
  • indexDocument(doc) — Upsert một tài liệu
  • indexDocuments(docs) — Import hàng loạt với báo cáo lỗi
  • removeDocument(id) — Xóa theo ID
  • search(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 /health trả 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ầu
  • http_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ễ:

  1. 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
  2. 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
  3. 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:

  1. 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:

  1. api-latency.json — API p99/p95/p50, phân tích theo route, endpoint chậm
  2. api-overview.json — Tỷ lệ yêu cầu, tỷ lệ lỗi, trạng thái uptime
  3. database.json — Độ trễ truy vấn, sử dụng connection pool, truy vấn chậm
  4. logs.json — Khối lượng nhật ký, nhật ký lỗi, liên kết trace đến Prometheus
  5. search.json — Độ trễ truy vấn Typesense, tỷ lệ lập chỉ mục, kích thước collection
  6. web-vitals.json — Core Web Vitals Frontend (nếu có thiết bị đo phía client)
  7. 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)

  1. 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 ValidationException nếu chữ ký không hợp lệ
  2. Chuyển Đổi Trạng Thái Idempotent:

    • Chỉ cập nhật các thanh toán ở trạng thái: PENDING hoặc PROCESSING
    • Chuyển đổi nguyên tử sang COMPLETED hoặc FAILED
    • 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
  3. Phát Sự Kiện Miền:

    • Tái tạo thực thể miền từ repository
    • Phát PaymentCompletedEvent hoặc PaymentFailedEvent
    • 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)
  4. 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:

  1. 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
  2. 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
  3. 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)

  1. 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)
  2. 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
  3. 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
  4. 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:

  1. Tạo dump với pg_dump --format=custom --compress=6
  2. Lưu dưới dạng goodgo_YYYYMMDD_HHMMSS.sql.gz
  3. Xóa các bản sao lưu cũ hơn 7 ngày (có thể cấu hình)
  4. 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:

  1. Tạo cơ sở dữ liệu kiểm tra riêng biệt: goodgo_verify_<timestamp>
  2. Bật extension PostGIS
  3. Khôi phục bản sao lưu vào cơ sở dữ liệu kiểm tra
  4. Xác minh tất cả 22 bảng tồn tại
  5. So sánh số lượng hàng giữa nguồn và bản được khôi phục
  6. Tổng kiểm tra các bảng quan trọng bằng hash MD5
  7. Kiểm tra chỉ mục, loại enum
  8. Tạo báo cáo JSON với kết quả
  9. 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:

  1. .github/workflows/ci.yml — Lint, typecheck, test, build (khi push/PR đến master)
  2. .github/workflows/deploy.yml — Build Docker images, triển khai đến staging/prod
  3. .github/workflows/e2e.yml — Kiểm thử E2E (khởi chạy toàn bộ docker-compose.ci.yml)
  4. .github/workflows/backup-verify.yml — Xác minh sao lưu hàng tuần
  5. .github/workflows/security.yml — Quét phụ thuộc, SAST
  6. .github/workflows/codeql.yml — Phân tích GitHub CodeQL
  7. .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:

  1. 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)
  2. 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_dispatch thủ công (chọn staging hoặc production)

Công Việc:

  1. 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)
  2. Build Web Image

    • Build: goodgo-web:${IMAGE_TAG}
    • Dockerfile: apps/web/Dockerfile
    • Registry: ghcr.io/goodgo/goodgo-web
  3. Build AI Services Image

    • Build: goodgo-ai-services:${IMAGE_TAG}
    • Context: libs/ai-services/
    • Registry: ghcr.io/goodgo/goodgo-ai-services
  4. 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ũ
  5. Triển Khai Đến Production

    • Chỉ khi workflow_dispatch thủ công với environment: production
    • Các bước giống staging
    • Yêu cầu phê duyệt environment: production (bảo mật GitHub)

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 ps hiển thị (health: starting) trong hơn 2 phút
  • docker compose logs api hiể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 failed hoặc ECONNREFUSED
  • 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/ready trả 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
  • /health củ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_KEY khớ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.yml thấ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_bytes Prometheus 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