# 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](#tóm-tắt-điều-hành) 2. [Kiến Trúc Dịch Vụ](#kiến-trúc-dịch-vụ) 3. [Thông Số Docker Compose](#thông-số-docker-compose) 4. [Tầng Cơ Sở Dữ Liệu](#tầng-cơ-sở-dữ-liệu) 5. [Bộ Nhớ Đệm & Tìm Kiếm](#bộ-nhớ-đệm--tìm-kiếm) 6. [Giám Sát & Quan Sát](#giám-sát--quan-sát) 7. [Tích Hợp Thanh Toán](#tích-hợp-thanh-toán) 8. [Kiểm Tra Sức Khỏe](#kiểm-tra-sức-khỏe) 9. [Biến Môi Trường](#biến-môi-trường) 10. [Sao Lưu & Phục Hồi](#sao-lưu--phục-hồi) 11. [Quy Trình Triển Khai](#quy-trình-triển-khai) 12. [Hướng Dẫn Xử Lý Sự Cố](#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)** ```yaml 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)** ```yaml 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 độ)** ```yaml 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_`) 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:** ```bash 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:** ```bash redis-server \ --appendonly yes \ --maxmemory 256mb \ --maxmemory-policy allkeys-lru ``` **Cấu Hình Client ioredis:** ```typescript // 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`):** ```yaml 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:** ```yaml 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):** ```json { "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):** ```typescript 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:** ```typescript { paymentId: string, status: PaymentStatus, isSuccess: boolean } ``` **Giao Diện Payment Gateway (`payment-gateway.interface.ts`):** ```typescript interface IPaymentGateway { readonly provider: PaymentProvider createPaymentUrl(params: CreatePaymentUrlParams): Promise verifyCallback(data: Record): CallbackVerifyResult refund(params: RefundParams): Promise } interface CreatePaymentUrlParams { orderId: string amountVND: bigint description: string returnUrl: string ipAddress: string } interface CallbackVerifyResult { isValid: boolean orderId: string providerTxId: string isSuccess: boolean rawData: Record } interface RefundParams { providerTxId: string amountVND: bigint reason: string } interface RefundResult { success: boolean refundTxId: string | null } ``` ### Biến Môi Trường **VNPay:** ```env 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:** ```env MOMO_PARTNER_CODE= MOMO_ACCESS_KEY= MOMO_SECRET_KEY= MOMO_ENDPOINT=https://test-payment.momo.vn/v2/gateway/api ``` **ZaloPay:** ```env ZALOPAY_APP_ID= ZALOPAY_KEY1= ZALOPAY_KEY2= 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: ```json { "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`): ```typescript async isHealthy(key: string): Promise { 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`): ```typescript async isHealthy(key: string): Promise { 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:** ```yaml 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:** ```yaml 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:** ```yaml healthcheck: test: ['CMD-SHELL', 'pg_isready -U ${DB_USER} -d ${DB_NAME}'] interval: 10s timeout: 5s retries: 5 start_period: 30s ``` **Redis:** ```yaml healthcheck: test: ['CMD', 'redis-cli', '-a', '${REDIS_PASSWORD}', 'ping'] interval: 10s timeout: 5s retries: 5 start_period: 10s ``` **Typesense:** ```yaml 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:** ```env 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):** ```env PGBOUNCER_POOL_SIZE=20 PGBOUNCER_MAX_CLIENT_CONN=200 PGBOUNCER_ADMIN_PASSWORD=CHANGE_ME PGBOUNCER_STATS_PASSWORD=CHANGE_ME ``` **Redis:** ```env REDIS_HOST=localhost REDIS_PORT=6379 REDIS_PASSWORD= REDIS_URL=redis://${REDIS_HOST}:${REDIS_PORT} ``` **Typesense:** ```env TYPESENSE_HOST=localhost TYPESENSE_PORT=8108 TYPESENSE_PROTOCOL=http TYPESENSE_API_KEY=CHANGE_ME ``` **MinIO:** ```env 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:** ```env 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):** ```env JWT_SECRET= JWT_EXPIRES_IN=15m JWT_REFRESH_SECRET= JWT_REFRESH_EXPIRES_IN=7d ``` **Nhà Cung Cấp OAuth:** ```env 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:** ```env NEXT_PUBLIC_API_URL=http://localhost:3000 WEB_PORT=3001 ``` **Dịch Vụ AI (Python/FastAPI):** ```env 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 Đồ:** ```env NEXT_PUBLIC_MAPBOX_TOKEN= ``` **Cổng Thanh Toán:** ```env 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:** ```env SMTP_HOST=localhost SMTP_PORT=1025 SMTP_USER= SMTP_PASS= SMTP_FROM=noreply@goodgo.vn ``` **Firebase Cloud Messaging (Tùy Chọn):** ```env FIREBASE_SERVICE_ACCOUNT= ``` **Theo Dõi Lỗi Sentry:** ```env SENTRY_DSN= NEXT_PUBLIC_SENTRY_DSN= SENTRY_AUTH_TOKEN= SENTRY_ORG= SENTRY_PROJECT= ``` **Mã Hóa Trường KYC (BẮT BUỘC Prod):** ```env KYC_ENCRYPTION_KEY= # 64 hex chars (32 bytes) KYC_ENCRYPTION_KEY_VERSION=1 ``` **Ghi Nhật Ký:** ```env 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` ```bash # Daily cron job: 02:00 UTC PGHOST=postgres \ PGPORT=5432 \ PGUSER=goodgo \ PGDATABASE=goodgo \ PGPASSWORD= \ 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:** ```bash # 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` ```bash # 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_` 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:** ```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): ```bash 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ụ ```bash # 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:** ```bash # 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:** ```bash 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:** ```bash docker compose exec redis redis-cli ping # Should return PONG docker compose logs redis --tail=50 ``` - **PgBouncer chưa sẵn sàng (prod):** ```bash 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:** ```bash # 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:** ```bash # 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:** ```bash # 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:** ```bash # 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: ```bash docker compose exec api npx ts-node scripts/reindex-listings.ts ``` - Nếu collection bị hỏng, xóa và tạo lại: ```bash 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:** ```bash # 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): ```bash 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:** ```bash # 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:** ```bash # 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 ```promql # 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):** ```bash 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:** ```bash # 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:** ```bash # 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