# 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_<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:**
```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<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:**
```env
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:**
```env
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:**
```env
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:
     ```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<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`):
```typescript
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:**
```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=<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:**
```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=<generate with: openssl rand -hex 32> # 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=<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:**

```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_<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:**
```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
