Appearance
Go-Live Checklist — Nâng cấp Voucher Management: Kiểm soát, Thống kê & Đối tác
Version: 1.0 Ngày: 2026-03-19 PRD version: v1.1 Tham chiếu:
prd.md·dev-spec.md·qa-test-plan.mdComplexity: Large — 4 phases triển khai tuần tự + 1 PERF-FIX prerequisite Database: PostgreSQL 14 — ecommerce schema
E1. Go/No-Go Gates
1.1 Gate tổng quan
| # | Gate | Tiêu chí | Phase áp dụng | Responsible | Status |
|---|---|---|---|---|---|
| G-01 | Code Review | PR approved bởi ≥ 1 Senior Dev + Tech Lead | Tất cả phases | Tech Lead | ⬜ |
| G-02 | Unit Tests | Coverage ≥ 80% cho Go handlers mới, all pass | Tất cả phases | Dev | ⬜ |
| G-03 | QA Sign-off | 76 test cases pass (theo qa-test-plan.md), 0 Critical/High open | Per phase | QA Lead | ⬜ |
| G-04 | Performance Verified | Đạt NFR targets (xem Section 1.2) | PERF-FIX, P2, P4 | Dev + QA | ⬜ |
| G-05 | Migration Dry-run | Chạy thành công trên staging (data production-like) | Tất cả phases | DBA / Dev | ⬜ |
| G-06 | Rollback Tested | down.sql chạy thành công trên staging, verify data intact | Tất cả phases | Dev | ⬜ |
| G-07 | Hasura Metadata | hasura metadata apply thành công, permission YAML reviewed | Tất cả phases | Dev | ⬜ |
| G-08 | Staging Smoke Test | Happy path + 3 edge cases pass trên staging environment | Per phase | QA | ⬜ |
| G-09 | PO Acceptance | PO confirm UI flow + business logic trên staging | Per phase | PO | ⬜ |
| G-10 | Monitoring Ready | Alert rules + dashboard panels configured (xem E3.3) | Tất cả phases | DevOps | ⬜ |
1.2 Performance Gates (NFR)
| Metric | Target | Đo tại | Phase | Gate |
|---|---|---|---|---|
| Time validation overhead (create_order) | < 50ms p95 | Go handler log | P1 | G-04 |
| Override dashboard list (20 rows) | < 500ms p95 | Browser DevTools → Network | P1 | G-04 |
get_voucher_staff_statistics (1000 staff × 50k vouchers) | < 2s | EXPLAIN ANALYZE trên staging | P2 | G-04 |
get_voucher_usage_cycle_statistics (100k vouchers) | < 3s | EXPLAIN ANALYZE trên staging | P4 | G-04 |
get_voucher_usage_cycle_distribution (100k vouchers) | < 3s | EXPLAIN ANALYZE trên staging | P4 | G-04 |
| Campaign detail page (perf-fix) | 6-8x cải thiện vs hiện tại | Browser DevTools → total load time | PERF-FIX | G-04 |
| FE bundle size increase | < 50KB gzipped (Chart.js lazy-loaded) | npm run build -- --report | P4 | G-04 |
1.3 Go/No-Go Decision Matrix
| Tình huống | Quyết định | Escalation |
|---|---|---|
| Tất cả gates PASS | GO — triển khai theo lịch | — |
| G-03 fail (QA) nhưng chỉ Low severity | GO with conditions — hotfix trong 48h | QA Lead → PO |
| G-04 fail (Performance) | NO-GO — optimize trước khi deploy | Dev → Tech Lead |
| G-05 fail (Migration dry-run) | NO-GO — fix migration, re-test | DBA → Tech Lead |
| G-06 fail (Rollback) | NO-GO — rollback PHẢI work trước khi go-live | Dev → Tech Lead |
| ALTER TABLE user_vouchers timeout trên staging | NO-GO — chuyển sang online ALTER approach (xem E2.2) | DBA → Tech Lead |
E2. Pre-deploy Checks
2.1 Checklist chung (áp dụng mọi phase)
| # | Check | Command / Action | Expected | ⬜ |
|---|---|---|---|---|
| PRE-01 | Backup database | pg_dump -Fc ecommerce > backup_$(date +%Y%m%d_%H%M%S).dump | File dump created, size > 0 | ⬜ |
| PRE-02 | Verify disk space | df -h /var/lib/postgresql/ | ≥ 20% free (ALTER TABLE cần temp space) | ⬜ |
| PRE-03 | Check active connections | SELECT count(*) FROM pg_stat_activity WHERE datname='ecommerce'; | < 100 (bình thường) | ⬜ |
| PRE-04 | Kill long-running transactions | SELECT pid, age(clock_timestamp(), xact_start), query FROM pg_stat_activity WHERE xact_start < NOW() - INTERVAL '10 minutes' AND state != 'idle'; | Không có transaction > 10 min (quan trọng cho CREATE INDEX CONCURRENTLY) | ⬜ |
| PRE-05 | Verify current schema version | SELECT MAX(version) FROM schema_migrations; | Match expected version | ⬜ |
| PRE-06 | Hasura health check | curl -s http://hasura:8080/healthz | {"status":"OK"} | ⬜ |
| PRE-07 | Go service health | curl -s http://ecommerce-api:8080/healthz | {"status":"ok"} | ⬜ |
| PRE-08 | Notify team | Post in Slack #deploy channel | "Deploy voucher-enhancement Phase X starting" | ⬜ |
| PRE-09 | Verify staging passed | Check staging deploy log | Gate G-08 PASS | ⬜ |
| PRE-10 | Maintenance window | Confirm deploy time slot (21:00-23:00 VN time recommended) | Team acknowledged | ⬜ |
2.2 Check đặc biệt: ALTER TABLE user_vouchers (Phase 2)
RISK: Bảng
user_voucherscó ~5M rows. ALTER TABLE ADD COLUMN thông thường sẽ lock bảng.
| # | Check | Command / Action | Expected | ⬜ |
|---|---|---|---|---|
| PRE-ALT-01 | Estimate table size | SELECT pg_size_pretty(pg_total_relation_size('user_vouchers')); | Ghi nhận size (dùng ước tính thời gian) | ⬜ |
| PRE-ALT-02 | Check blocking locks | SELECT relation::regclass, mode, granted FROM pg_locks WHERE relation = 'user_vouchers'::regclass; | Không có exclusive lock | ⬜ |
| PRE-ALT-03 | Estimate ALTER time | Test trên staging với data production-like | < 5 phút cho ADD COLUMN nullable (PostgreSQL 14: instant cho nullable columns) | ⬜ |
| PRE-ALT-04 | Plan B: Online ALTER | Nếu staging > 5 phút → dùng pg_repack hoặc manual approach: CREATE new column → backfill → swap | Documented, tested trên staging | ⬜ |
Lưu ý PostgreSQL 14:
ALTER TABLE ADD COLUMN ... DEFAULT NULLlà instant operation (chỉ metadata change). Columndistributor_type TEXTnullable, KHÔNG có DEFAULT value → instant. Tuy nhiênCREATE INDEXtrên 5M rows cần thời gian — dùngCONCURRENTLY.
2.3 Check đặc biệt: Expression Index (PERF-FIX)
| # | Check | Command / Action | Expected | ⬜ |
|---|---|---|---|---|
| PRE-IDX-01 | Verify no concurrent long tx | SELECT pid, age(clock_timestamp(), xact_start) FROM pg_stat_activity WHERE state = 'active' AND xact_start IS NOT NULL ORDER BY xact_start; | Không có tx > 5 min | ⬜ |
| PRE-IDX-02 | CREATE INDEX CONCURRENTLY retry plan | Nếu fail do concurrent tx → kill blocking tx → retry | Documented in runbook | ⬜ |
| PRE-IDX-03 | Verify index validity sau CREATE | SELECT indexrelid::regclass, indisvalid FROM pg_index WHERE indexrelid::regclass::text LIKE '%user_vouchers%'; | indisvalid = true cho index mới | ⬜ |
E3. Deploy Steps — Per Phase
3.0 PERF-FIX: Tối ưu Campaign Detail (Prerequisite)
PHẢI deploy TRƯỚC Phase 2 và Phase 4. Phase 1 và Phase 3 không phụ thuộc.
Thời điểm deploy: Sprint X — trước P2/P4 ít nhất 1 ngày (cần verify performance trên production)
| Step | Action | Command / Detail | Verify | Rollback | ⬜ |
|---|---|---|---|---|---|
| 0.1 | Backup | pg_dump -Fc ecommerce > backup_perf_fix.dump | File created | — | ⬜ |
| 0.2 | Run migration: expression index | hasura migrate apply --database-name ecommerce file: {ts}_add_expression_index_user_vouchers.sql | Index created | DROP INDEX CONCURRENTLY IF EXISTS idx_user_vouchers_issued_or_activated; | ⬜ |
| 0.3 | Verify index valid | SELECT indisvalid FROM pg_index WHERE indexrelid = 'idx_user_vouchers_issued_or_activated'::regclass; | true | Nếu false → DROP + re-CREATE | ⬜ |
| 0.4 | Apply function optimization | Deploy optimized get_voucher_analytics_summary (merge CTEs, single voucher_logs JOIN) | Function replaced | Restore original function từ down.sql | ⬜ |
| 0.5 | Deploy FE: split query | Deploy updated VoucherCampaignDetail.tsx — tách aggregate query riêng, cache count | Build + deploy FE | Revert FE build | ⬜ |
| 0.6 | Performance verify | Mở campaign detail trên production, check DevTools Network tab | Load time giảm 6-8x so với trước | — | ⬜ |
| 0.7 | Monitor 24h | Check error rate + response time dashboard | Không tăng error, response time giảm | — | ⬜ |
3.1 Phase 1: Kiểm soát thời gian (P0 — Urgent)
Dependencies: Không — triển khai độc lập. Có thể deploy song song với PERF-FIX.
Thời điểm deploy: Sprint X (cùng sprint hoặc trước PERF-FIX)
3.1.1 Database Migrations
| Step | Action | Migration File | Verify | Rollback | ⬜ |
|---|---|---|---|---|---|
| 1.1 | ALTER voucher_campaigns | {ts}_alter_voucher_campaigns_min_activation_hours.sql | SELECT column_name, data_type, column_default FROM information_schema.columns WHERE table_name='voucher_campaigns' AND column_name='min_activation_hours'; → integer, default 24 | ALTER TABLE voucher_campaigns DROP COLUMN IF EXISTS min_activation_hours; | ⬜ |
| 1.2 | CREATE voucher_activation_overrides | {ts}_create_voucher_activation_overrides.sql | SELECT count(*) FROM information_schema.tables WHERE table_name='voucher_activation_overrides'; → 1 | DROP TABLE IF EXISTS voucher_activation_overrides; | ⬜ |
| 1.3 | Verify indexes | — | SELECT indexname FROM pg_indexes WHERE tablename='voucher_activation_overrides'; → 4 indexes (user_voucher_id, approved_by, branch_id, created_at) | — | ⬜ |
| 1.4 | UPDATE app_setting | {ts}_update_app_setting_voucher.sql | SELECT value->'AppSettings'->'VoucherSetting' FROM app_setting WHERE key='AppSettings'; → {"DefaultMinActivationHours": 24, "DefaultManualVoucherExpiryDays": 30} | UPDATE app_setting SET value = value #- '{AppSettings,VoucherSetting}' WHERE key='AppSettings'; | ⬜ |
3.1.2 Hasura Metadata
| Step | Action | File | Verify | Rollback | ⬜ |
|---|---|---|---|---|---|
| 1.5 | Track table | public_voucher_activation_overrides.yaml | Hasura Console → Data → voucher_activation_overrides visible | hasura metadata apply (revert YAML) | ⬜ |
| 1.6 | Update voucher_campaigns permissions | public_voucher_campaigns.yaml — thêm min_activation_hours vào select permissions | GraphQL query voucher_campaigns { min_activation_hours } → trả giá trị | Revert YAML | ⬜ |
| 1.7 | Verify permissions per role | Hasura Console → Permissions | Admin: CRUD, Manager: select + insert (branch scoped), Staff: select (self) | — | ⬜ |
3.1.3 Backend (Go)
| Step | Action | Files | Verify | Rollback | ⬜ |
|---|---|---|---|---|---|
| 1.8 | Deploy ecommerce-api | approve_voucher_override.go (mới), create_order.go (sửa), activate_offline_voucher.go (sửa), pkg/store/app_setting.go, pkg/store/voucher_campaigns.go | curl -s http://ecommerce-api:8080/healthz → OK | Revert Docker image to previous tag | ⬜ |
| 1.9 | Verify action handler | — | POST /actions với approve_voucher_override → 200 (valid input) hoặc 400 (invalid) | — | ⬜ |
| 1.10 | Test time validation | Tạo ĐH với voucher kích hoạt < 24h trước | Response: 400 + message "Voucher chưa đủ thời gian chờ" + remaining_hours | — | ⬜ |
3.1.4 Frontend
| Step | Action | Components | Verify | Rollback | ⬜ |
|---|---|---|---|---|---|
| 1.11 | Deploy FE build | VoucherConfigForm, VoucherTimeBlockDialog, VoucherOverrideDashboard, VoucherSettingsSection | Build success, no console errors | Revert FE build | ⬜ |
| 1.12 | Smoke test: Config campaign | Mở tạo campaign → field "Thời gian chờ" hiện, nhập 48h → save → reload → giá trị đúng | Field hiện + giá trị persist | — | ⬜ |
| 1.13 | Smoke test: Block dialog | Tạo ĐH với voucher kích hoạt 2h trước (campaign set 24h) | Dialog chặn hiện + nút "Yêu cầu duyệt" + countdown "Còn 22 giờ" | — | ⬜ |
| 1.14 | Smoke test: Override | Login Manager → approve override → tạo ĐH thành công | ĐH tạo thành công + override record trong dashboard | — | ⬜ |
| 1.15 | Smoke test: Settings | Settings → Voucher → thấy DefaultMinActivationHours = 24, DefaultManualVoucherExpiryDays = 30 | Giá trị hiển thị đúng, thay đổi → save → persist | — | ⬜ |
3.1.5 Post-deploy Verify
| # | Check | Expected | ⬜ |
|---|---|---|---|
| 1.16 | Tạo ĐH KHÔNG dùng voucher | Hoạt động bình thường (regression) | ⬜ |
| 1.17 | Tạo ĐH với voucher đã đủ thời gian | Thành công, không bị chặn | ⬜ |
| 1.18 | Tạo ĐH với voucher campaign có min_activation_hours = 0 | Thành công, skip time check | ⬜ |
| 1.19 | Kích hoạt voucher offline (manual) | expired_at được set = activated_at + DefaultManualVoucherExpiryDays | ⬜ |
| 1.20 | Override dashboard | Hiển thị override record vừa tạo, filter by branch/date hoạt động | ⬜ |
| 1.21 | Monitor error logs 1h | Không có error mới liên quan voucher | ⬜ |
3.2 Phase 2: Thống kê nhân viên (P1 — High)
Dependencies: PERF-FIX PHẢI đã deploy thành công (expression index + optimized function).
Thời điểm deploy: Sprint X+1 (sau PERF-FIX ≥ 1 ngày)
3.2.1 Pre-deploy: Verify PERF-FIX
| # | Check | Expected | ⬜ |
|---|---|---|---|
| DEP-01 | Expression index tồn tại | SELECT indisvalid FROM pg_index WHERE indexrelid = 'idx_user_vouchers_issued_or_activated'::regclass; → true | ⬜ |
| DEP-02 | Optimized function active | Campaign detail load time < 3s trên production | ⬜ |
3.2.2 Database Migrations
| Step | Action | Migration File | Verify | Rollback | ⬜ |
|---|---|---|---|---|---|
| 2.1 | ALTER user_vouchers (ADD COLUMN nullable — instant on PG14) | {ts}_alter_user_vouchers_distributor_type.sql | SELECT column_name FROM information_schema.columns WHERE table_name='user_vouchers' AND column_name='distributor_type'; → exists | DROP INDEX IF EXISTS idx_user_vouchers_distributor_type; ALTER TABLE user_vouchers DROP COLUMN IF EXISTS distributor_type; | ⬜ |
| 2.2 | CREATE INDEX CONCURRENTLY | Partial index trên distributor_type WHERE NOT NULL | SELECT indisvalid FROM pg_index WHERE indexrelid = 'idx_user_vouchers_distributor_type'::regclass; → true | DROP INDEX CONCURRENTLY IF EXISTS idx_user_vouchers_distributor_type; | ⬜ |
| 2.3 | CREATE return type table | voucher_staff_statistics_result | SELECT count(*) FROM information_schema.tables WHERE table_name='voucher_staff_statistics_result'; → 1 | DROP TABLE IF EXISTS voucher_staff_statistics_result; | ⬜ |
| 2.4 | CREATE FUNCTION | {ts}_create_fn_voucher_staff_statistics.sql | SELECT proname FROM pg_proc WHERE proname='get_voucher_staff_statistics'; → exists | DROP FUNCTION IF EXISTS get_voucher_staff_statistics; DROP TABLE IF EXISTS voucher_staff_statistics_result; | ⬜ |
| 2.5 | Performance verify | EXPLAIN ANALYZE SELECT * FROM get_voucher_staff_statistics(NULL, NULL, NULL, NULL); | Execution time < 2s (staging with production-like data) | — | ⬜ |
3.2.3 Hasura Metadata
| Step | Action | File | Verify | Rollback | ⬜ |
|---|---|---|---|---|---|
| 2.6 | Track function | public_get_voucher_staff_statistics.yaml | GraphQL query get_voucher_staff_statistics(args: {}) → trả data | Revert YAML + hasura metadata apply | ⬜ |
| 2.7 | Update user_vouchers permissions | public_user_vouchers.yaml — thêm distributor_type vào select | Query user_vouchers { distributor_type } → OK | Revert YAML | ⬜ |
3.2.4 Backend (Go)
| Step | Action | Files | Verify | Rollback | ⬜ |
|---|---|---|---|---|---|
| 2.8 | Deploy ecommerce-api | activate_voucher.go (sửa — set distributor_type), pkg/store/user_vouchers.go | healthz → OK | Revert Docker image | ⬜ |
| 2.9 | Verify distributor_type set | Kích hoạt 1 voucher offline bởi Staff → check user_vouchers.distributor_type | 'internal_staff' | — | ⬜ |
3.2.5 Frontend
| Step | Action | Components | Verify | Rollback | ⬜ |
|---|---|---|---|---|---|
| 2.10 | Deploy FE build | VoucherStaffStatisticsTab, StaffDrillDownDialog, updated VoucherCampaignDetail | Build success | Revert FE build | ⬜ |
| 2.11 | Smoke test: Tab thống kê | Campaign detail → tab "Thống kê nhân viên" | Table hiện danh sách NV + số liệu + conversion rate | — | ⬜ |
| 2.12 | Smoke test: Drill-down | Click 1 NV → dialog chi tiết | Danh sách voucher đã phát, status, ngày | — | ⬜ |
| 2.13 | Smoke test: Export | Click "Xuất Excel" (< 1000 rows) | File download, data đúng | — | ⬜ |
3.2.6 Post-deploy Verify
| # | Check | Expected | ⬜ |
|---|---|---|---|
| 2.14 | Legacy vouchers (distributor_type = NULL) hiển thị "Nhân viên" | Fallback hoạt động | ⬜ |
| 2.15 | Filter by branch (Manager) | Chỉ thấy NV branch mình | ⬜ |
| 2.16 | Campaign detail page performance | Không bị chậm hơn sau deploy (regression perf-fix) | ⬜ |
| 2.17 | Monitor error logs 1h | Không có error mới | ⬜ |
3.3 Phase 3: Đối tác Affiliate (P1 — High)
Dependencies: Phase 2 đã deploy (reuse thống kê NV để phân biệt internal vs affiliate).
Thời điểm deploy: Sprint X+1 hoặc X+2 (sau P2)
3.3.1 Pre-deploy: Verify Phase 2
| # | Check | Expected | ⬜ |
|---|---|---|---|
| DEP-03 | Column distributor_type tồn tại | SELECT column_name FROM information_schema.columns WHERE table_name='user_vouchers' AND column_name='distributor_type'; → exists | ⬜ |
| DEP-04 | Function get_voucher_staff_statistics hoạt động | SELECT * FROM get_voucher_staff_statistics(NULL, NULL, NULL, NULL) LIMIT 1; → returns row | ⬜ |
| DEP-05 | Module Affiliate hiện có hoạt động | SELECT count(*) FROM affiliate_user WHERE deleted_at IS NULL; → > 0 (có data) | ⬜ |
3.3.2 Database Migrations
| Step | Action | Migration File | Verify | Rollback | ⬜ |
|---|---|---|---|---|---|
| 3.1 | CREATE voucher_campaign_affiliates | {ts}_create_voucher_campaign_affiliates.sql | SELECT count(*) FROM information_schema.tables WHERE table_name='voucher_campaign_affiliates'; → 1 | DROP TRIGGER IF EXISTS update_voucher_campaign_affiliates_timestamp ON voucher_campaign_affiliates; DROP TABLE IF EXISTS voucher_campaign_affiliates; | ⬜ |
| 3.2 | Verify indexes | — | SELECT indexname FROM pg_indexes WHERE tablename='voucher_campaign_affiliates'; → 3 indexes (campaign_id, affiliate_user_id, quota_check) | — | ⬜ |
| 3.3 | Verify UNIQUE constraint | — | SELECT conname FROM pg_constraint WHERE conrelid='voucher_campaign_affiliates'::regclass AND contype='u'; → voucher_campaign_affiliates_campaign_id_affiliate_user_id_key | — | ⬜ |
| 3.4 | Verify trigger | — | SELECT tgname FROM pg_trigger WHERE tgrelid='voucher_campaign_affiliates'::regclass AND tgname='update_voucher_campaign_affiliates_timestamp'; → exists | — | ⬜ |
3.3.3 Hasura Metadata
| Step | Action | File | Verify | Rollback | ⬜ |
|---|---|---|---|---|---|
| 3.5 | Track table | public_voucher_campaign_affiliates.yaml | Hasura Console → table visible, permissions correct | Revert YAML | ⬜ |
| 3.6 | Verify permissions | — | Admin: CRUD, Manager: select + insert (branch scope qua campaign), Staff: no access | — | ⬜ |
3.3.4 Backend (Go)
| Step | Action | Files | Verify | Rollback | ⬜ |
|---|---|---|---|---|---|
| 3.7 | Deploy ecommerce-api | create_draft_voucher_campaign.go (sửa), update_voucher_campaign.go (sửa), activate_offline_voucher.go (sửa — quota check) | healthz → OK | Revert Docker image | ⬜ |
| 3.8 | Verify campaign create | Tạo campaign mới + gán 1 affiliate | voucher_campaign_affiliates có record, distributed_count = 0 | — | ⬜ |
| 3.9 | Verify atomic quota | Kích hoạt voucher cho affiliate có quota = 1, đã phát 0 | distributed_count = 1, response OK | — | ⬜ |
| 3.10 | Verify quota reject | Kích hoạt voucher cho affiliate có quota = 1, đã phát 1 | Response: reject, distributed_count vẫn = 1 | — | ⬜ |
3.3.5 Frontend
| Step | Action | Components | Verify | Rollback | ⬜ |
|---|---|---|---|---|---|
| 3.11 | Deploy FE build | AffiliateAssignStep, AffiliateSourceBadge, updated VoucherCreate, updated VoucherStaffStatisticsTab | Build success | Revert FE build | ⬜ |
| 3.12 | Smoke test: Wizard | Tạo campaign → step "Đối tác" hiện → search + chọn affiliate → set quota → save | Affiliate assigned, quota displayed | — | ⬜ |
| 3.13 | Smoke test: Badge | Mở chi tiết ĐH có voucher từ affiliate | Badge "Voucher do [Tên KOL] phát" hiện | — | ⬜ |
| 3.14 | Smoke test: Report phân biệt | Tab thống kê NV → filter by "Đối tác" | Chỉ hiện affiliate, distributor_type = 'affiliate' | — | ⬜ |
3.3.6 Post-deploy Verify
| # | Check | Expected | ⬜ |
|---|---|---|---|
| 3.15 | Campaign KHÔNG có affiliate | Hoạt động bình thường (regression) | ⬜ |
| 3.16 | Affiliate unlimited quota (NULL) | Phát voucher thành công, không bị chặn | ⬜ |
| 3.17 | Concurrent activation test | 2 requests đồng thời cho affiliate quota=1 → chỉ 1 thành công | ⬜ |
| 3.18 | Monitor error logs 1h | Không có error mới | ⬜ |
3.4 Phase 4: Chu kỳ sử dụng (P2 — Medium)
Dependencies: PERF-FIX PHẢI đã deploy thành công.
Thời điểm deploy: Sprint X+2 hoặc X+3
3.4.1 Pre-deploy: Verify PERF-FIX
| # | Check | Expected | ⬜ |
|---|---|---|---|
| DEP-06 | Expression index tồn tại + valid | indisvalid = true | ⬜ |
| DEP-07 | Campaign detail page < 3s | Performance OK | ⬜ |
3.4.2 Database Migrations
| Step | Action | Migration File | Verify | Rollback | ⬜ |
|---|---|---|---|---|---|
| 4.1 | CREATE return type tables | voucher_usage_cycle_statistics_result, voucher_usage_cycle_distribution_result | information_schema.tables check → 2 tables exist | DROP TABLE IF EXISTS both | ⬜ |
| 4.2 | CREATE FUNCTION cycle statistics | {ts}_create_fn_voucher_usage_cycle_statistics.sql | SELECT proname FROM pg_proc WHERE proname='get_voucher_usage_cycle_statistics'; → exists | DROP FUNCTION IF EXISTS get_voucher_usage_cycle_statistics; DROP TABLE IF EXISTS voucher_usage_cycle_statistics_result; | ⬜ |
| 4.3 | CREATE FUNCTION cycle distribution | {ts}_create_fn_voucher_usage_cycle_distribution.sql | SELECT proname FROM pg_proc WHERE proname='get_voucher_usage_cycle_distribution'; → exists | DROP FUNCTION IF EXISTS get_voucher_usage_cycle_distribution; DROP TABLE IF EXISTS voucher_usage_cycle_distribution_result; | ⬜ |
| 4.4 | Performance verify: statistics | EXPLAIN ANALYZE SELECT * FROM get_voucher_usage_cycle_statistics(NULL, NULL, NULL, NULL, NULL); | Execution time < 3s (staging with 100k vouchers) | — | ⬜ |
| 4.5 | Performance verify: distribution | EXPLAIN ANALYZE SELECT * FROM get_voucher_usage_cycle_distribution(NULL, NULL, NULL, NULL, NULL); | Execution time < 3s (staging with 100k vouchers) | — | ⬜ |
3.4.3 Hasura Metadata
| Step | Action | File | Verify | Rollback | ⬜ |
|---|---|---|---|---|---|
| 4.6 | Track function: statistics | public_get_voucher_usage_cycle_statistics.yaml | GraphQL query trả data | Revert YAML | ⬜ |
| 4.7 | Track function: distribution | public_get_voucher_usage_cycle_distribution.yaml | GraphQL query trả data | Revert YAML | ⬜ |
3.4.4 Frontend
| Step | Action | Components | Verify | Rollback | ⬜ |
|---|---|---|---|---|---|
| 4.8 | Deploy FE build | VoucherUsageCycleTab, CycleBucketChart (Chart.js), updated VoucherCampaignDetail | Build success, Chart.js lazy-loaded | Revert FE build | ⬜ |
| 4.9 | Verify bundle size | npm run build -- --report | Chart.js chunk < 50KB gzipped, lazy-loaded | — | ⬜ |
| 4.10 | Smoke test: Tab chu kỳ | Campaign detail → tab "Chu kỳ sử dụng" | KPI cards hiện: Trung bình, Trung vị, Nhanh nhất, Tỉ lệ sử dụng | — | ⬜ |
| 4.11 | Smoke test: Chart | Tab chu kỳ → horizontal bar chart | 7 buckets hiện đúng (0-3, 4-7, 8-14, 15-30, 31-60, 60+, pending) | — | ⬜ |
| 4.12 | Smoke test: So sánh | Chọn 2-3 campaigns → compare | Bảng so sánh hiện đúng metrics per campaign | — | ⬜ |
| 4.13 | Smoke test: Export | Click "Xuất Excel" | File download, data đúng | — | ⬜ |
3.4.5 Post-deploy Verify
| # | Check | Expected | ⬜ |
|---|---|---|---|
| 4.14 | Campaign không có voucher redeemed | KPI hiện "—", chart hiện 100% pending | ⬜ |
| 4.15 | Campaign có 1 voucher redeemed | Median = avg = min = cycle_days của voucher đó | ⬜ |
| 4.16 | Filter by branch/staff/date | Kết quả thay đổi phù hợp | ⬜ |
| 4.17 | Monitor error logs 1h | Không có error mới | ⬜ |
E4. Rollback Plan
4.1 Nguyên tắc rollback
| Nguyên tắc | Chi tiết |
|---|---|
| Phase độc lập | Mỗi phase rollback KHÔNG ảnh hưởng phase khác (trừ dependency chain) |
| Thứ tự rollback | Ngược với deploy: FE → Go → Hasura metadata → Database migration |
| down.sql bắt buộc | Mọi migration đều có down.sql tested trên staging |
| Data integrity | Rollback KHÔNG xóa user data đã tạo (override records, statistics...) — chỉ xóa schema objects |
| Quyết định rollback | Tech Lead quyết định trong 30 phút nếu có Critical issue post-deploy |
4.2 Rollback per phase
Rollback Phase 1
| Step | Action | Command | Verify | ⬜ |
|---|---|---|---|---|
| R1.1 | Revert FE | Deploy previous FE build tag | UI không còn các component P1 | ⬜ |
| R1.2 | Revert Go | Deploy previous Docker image ecommerce-api:prev-tag | healthz → OK + time validation removed | ⬜ |
| R1.3 | Revert Hasura metadata | git checkout HEAD~1 -- controller/metadata/ + hasura metadata apply | Table voucher_activation_overrides untracked | ⬜ |
| R1.4 | Revert app_setting | UPDATE app_setting SET value = value #- '{AppSettings,VoucherSetting}' WHERE key='AppSettings'; | VoucherSetting removed | ⬜ |
| R1.5 | Drop override table | DROP TABLE IF EXISTS voucher_activation_overrides; | Table gone | ⬜ |
| R1.6 | Drop column | ALTER TABLE voucher_campaigns DROP COLUMN IF EXISTS min_activation_hours; | Column gone | ⬜ |
| R1.7 | Verify | Tạo ĐH với voucher → thành công (no time check) | Order created | ⬜ |
Rollback Phase 2
| Step | Action | Command | Verify | ⬜ |
|---|---|---|---|---|
| R2.1 | Revert FE | Deploy previous FE build tag | Tab thống kê NV không hiện | ⬜ |
| R2.2 | Revert Go | Deploy previous Docker image | distributor_type không được set khi activate | ⬜ |
| R2.3 | Revert Hasura metadata | Untrack function + revert YAML | Function not accessible via GraphQL | ⬜ |
| R2.4 | Drop function + type | DROP FUNCTION IF EXISTS get_voucher_staff_statistics; DROP TABLE IF EXISTS voucher_staff_statistics_result; | Function gone | ⬜ |
| R2.5 | Drop column | DROP INDEX IF EXISTS idx_user_vouchers_distributor_type; ALTER TABLE user_vouchers DROP COLUMN IF EXISTS distributor_type; | Column gone | ⬜ |
| R2.6 | Verify | Campaign detail → chỉ có tabs cũ | Không có tab "Thống kê nhân viên" | ⬜ |
Lưu ý: Rollback P2 KHÔNG ảnh hưởng P1 (independent). Nhưng nếu P3 đã deploy, PHẢI rollback P3 trước vì P3 phụ thuộc P2.
Rollback Phase 3
| Step | Action | Command | Verify | ⬜ |
|---|---|---|---|---|
| R3.1 | Revert FE | Deploy previous FE build tag | Wizard không có step "Đối tác", badge không hiện | ⬜ |
| R3.2 | Revert Go | Deploy previous Docker image | Quota check removed, campaign không insert affiliates | ⬜ |
| R3.3 | Revert Hasura metadata | Untrack table + revert YAML | Table not accessible via GraphQL | ⬜ |
| R3.4 | Drop table | DROP TRIGGER IF EXISTS update_voucher_campaign_affiliates_timestamp ON voucher_campaign_affiliates; DROP TABLE IF EXISTS voucher_campaign_affiliates; | Table gone | ⬜ |
| R3.5 | Verify | Tạo campaign → không có step affiliate | Normal flow | ⬜ |
Rollback Phase 4
| Step | Action | Command | Verify | ⬜ |
|---|---|---|---|---|
| R4.1 | Revert FE | Deploy previous FE build tag | Tab chu kỳ không hiện, Chart.js chunk removed | ⬜ |
| R4.2 | Revert Hasura metadata | Untrack 2 functions + revert YAML | Functions not accessible via GraphQL | ⬜ |
| R4.3 | Drop functions + types | DROP FUNCTION IF EXISTS get_voucher_usage_cycle_statistics; DROP FUNCTION IF EXISTS get_voucher_usage_cycle_distribution; DROP TABLE IF EXISTS voucher_usage_cycle_statistics_result; DROP TABLE IF EXISTS voucher_usage_cycle_distribution_result; | Functions + types gone | ⬜ |
| R4.4 | Verify | Campaign detail → chỉ có tabs cũ + tab thống kê NV (P2) | Không có tab "Chu kỳ sử dụng" | ⬜ |
Lưu ý: Rollback P4 KHÔNG ảnh hưởng các phase khác.
Rollback PERF-FIX
| Step | Action | Command | Verify | ⬜ |
|---|---|---|---|---|
| RPF.1 | Revert FE | Deploy previous FE build tag (query không split) | Single query restored | ⬜ |
| RPF.2 | Restore original function | Apply down.sql — restore get_voucher_analytics_summary original | Function reverted | ⬜ |
| RPF.3 | Drop expression index | DROP INDEX CONCURRENTLY IF EXISTS idx_user_vouchers_issued_or_activated; | Index gone | ⬜ |
| RPF.4 | Verify | Campaign detail page loads (slower but functional) | Page loads, data correct | ⬜ |
CRITICAL: Nếu rollback PERF-FIX thì P2 và P4 KHÔNG ĐƯỢC deploy (dependency chain).
4.3 Rollback Dependency Matrix
Rollback P4 → OK (independent, chỉ DROP functions)
Rollback P3 → OK (independent từ P4, nhưng P2 vẫn cần)
Rollback P2 → PHẢI rollback P3 trước (nếu P3 đã deploy)
Rollback P1 → OK (independent)
Rollback PERF-FIX → PHẢI rollback P2 + P4 trước (nếu đã deploy)E5. Day-0 / Day-1 Monitoring & Sign-off
5.1 Day-0: Ngày deploy (per phase)
| Thời điểm | Action | Responsible | Kênh |
|---|---|---|---|
| T+0 (deploy xong) | Chạy smoke tests (Section E3.x.4-5) | QA | Staging → Production |
| T+15min | Check error rate dashboard | DevOps | Grafana / Datadog |
| T+30min | Check slow query log | DBA / Dev | pg_stat_statements |
| T+1h | First monitoring report | Dev → Tech Lead | Slack #deploy |
| T+2h | Regression spot-check (tạo ĐH bình thường) | QA | Production |
| T+4h | Second monitoring report | Dev → Tech Lead | Slack #deploy |
| T+12h (sáng hôm sau) | Overnight error review | Dev | Error tracking tool |
5.2 Day-0: Monitoring Dashboard
| Panel | Metric | Alert Threshold | Query Source |
|---|---|---|---|
| Voucher Time Block Rate | Số lần chặn / tổng tạo ĐH có voucher | > 50% trong 1h → alert (có thể misconfigured) | voucher_activation_overrides count vs orders count |
| Override Rate | Override / tổng bị chặn | > 30% trong 1 ngày → alert (có thể lạm dụng) | voucher_activation_overrides count |
| Create Order Latency p95 | Response time create_order | > 2s (baseline + 50ms overhead) → alert | Go handler metrics |
| Staff Statistics Query Time | get_voucher_staff_statistics execution | > 5s → alert | pg_stat_statements |
| Cycle Statistics Query Time | get_voucher_usage_cycle_* execution | > 5s → alert | pg_stat_statements |
| Affiliate Quota Reject Rate | Quota reject / total activation attempts | > 20% → notify (quota có thể cần tăng) | Go handler log |
| DB Connection Pool | Active connections | > 80% pool size → alert | pg_stat_activity |
| Error Rate (ecommerce-api) | 5xx responses / total | > 1% → alert | Service metrics |
5.3 Day-1: Ngày +1 sau deploy (per phase)
| # | Check | Responsible | Expected | ⬜ |
|---|---|---|---|---|
| D1-01 | Error review | Dev | Không có error mới liên quan voucher enhancement | ⬜ |
| D1-02 | Performance baseline | Dev | Response times trong target (Section E1.2) | ⬜ |
| D1-03 | Data integrity | QA | Spot-check 10 records: overrides, staff stats, affiliate quota, cycle data | ⬜ |
| D1-04 | User feedback | PO | Thu thập feedback từ Manager/Staff dùng thử | ⬜ |
| D1-05 | Slow query check | DBA | SELECT * FROM pg_stat_statements WHERE mean_exec_time > 1000 AND query LIKE '%voucher%' ORDER BY mean_exec_time DESC LIMIT 10; | ⬜ |
| D1-06 | Index usage verify | DBA | SELECT relname, indexrelname, idx_scan FROM pg_stat_user_indexes WHERE relname IN ('voucher_activation_overrides','voucher_campaign_affiliates','user_vouchers') ORDER BY idx_scan; → idx_scan > 0 cho indexes mới | ⬜ |
| D1-07 | Table bloat check | DBA | SELECT relname, n_dead_tup, last_autovacuum FROM pg_stat_user_tables WHERE relname='user_vouchers'; → autovacuum đã chạy, dead tuples OK | ⬜ |
5.4 Day-7: Review tuần đầu (sau phase cuối cùng)
| # | Check | Responsible | ⬜ |
|---|---|---|---|
| D7-01 | Tổng hợp override statistics — bao nhiêu override, top reason, branch nào nhiều nhất | PO + Tech Lead | ⬜ |
| D7-02 | Affiliate quota usage — đối tác nào gần full quota, cần tăng không | PO | ⬜ |
| D7-03 | Performance trend — query times có tăng theo data growth không | Dev | ⬜ |
| D7-04 | User adoption — bao nhiêu Manager dùng override dashboard, NV xem thống kê | PO | ⬜ |
| D7-05 | Bug report tổng hợp — danh sách bug từ production, classify severity | QA Lead | ⬜ |
| D7-06 | Capacity projection — ước tính khi nào cần partition/archive cho voucher_activation_overrides | DBA | ⬜ |
5.5 Sign-off Matrix
| Phase | QA Lead | Tech Lead | PO | DBA | Status |
|---|---|---|---|---|---|
| PERF-FIX | ⬜ | ⬜ | ⬜ | ⬜ | Pending |
| Phase 1 | ⬜ | ⬜ | ⬜ | ⬜ | Pending |
| Phase 2 | ⬜ | ⬜ | ⬜ | ⬜ | Pending |
| Phase 3 | ⬜ | ⬜ | ⬜ | ⬜ | Pending |
| Phase 4 | ⬜ | ⬜ | ⬜ | ⬜ | Pending |
| TOÀN BỘ FEATURE | ⬜ | ⬜ | ⬜ | ⬜ | Pending |
Sign-off criteria:
- QA Lead: Tất cả test cases PASS, 0 open Critical/High bugs
- Tech Lead: Performance targets MET, rollback tested, monitoring active
- PO: Business flows verified trên production, user feedback positive
- DBA: Migrations clean, indexes valid, no bloat/slow queries
Phụ lục: Deploy Timeline tổng quan
Week 1 ─────────────────────────────────────────────────────────
│ Sprint X
│
├── PERF-FIX ──────────────── Prerequisite (deploy trước)
│ ├── Expression index
│ ├── Function optimization
│ └── FE query split
│
├── Phase 1 (P0) ─────────── Song song với PERF-FIX
│ ├── ALTER voucher_campaigns
│ ├── CREATE voucher_activation_overrides
│ ├── UPDATE app_setting
│ ├── Go handlers (create_order, activate_offline, override)
│ └── FE: config, dialog, dashboard, settings
│
Week 2 ─────────────────────────────────────────────────────────
│ Sprint X+1
│
├── Phase 2 (P1) ─────────── SAU PERF-FIX
│ ├── ALTER user_vouchers (instant PG14)
│ ├── CREATE INDEX CONCURRENTLY
│ ├── CREATE FUNCTION staff_statistics
│ ├── Go handlers (activate_voucher)
│ └── FE: tab thống kê, drill-down, export
│
├── Phase 3 (P1) ─────────── SAU Phase 2
│ ├── CREATE voucher_campaign_affiliates
│ ├── Go handlers (campaign create/update, quota)
│ └── FE: wizard step, badge, report filter
│
Week 3-4 ───────────────────────────────────────────────────────
│ Sprint X+2/X+3
│
└── Phase 4 (P2) ─────────── SAU PERF-FIX (independent of P2/P3)
├── CREATE FUNCTION cycle_statistics
├── CREATE FUNCTION cycle_distribution
└── FE: tab chu kỳ, Chart.js chart, exportPhụ lục: Emergency Contacts
| Role | Tên | Liên hệ | Khi nào |
|---|---|---|---|
| Tech Lead | [TBD] | Slack / Phone | Quyết định rollback, escalation |
| DBA | [TBD] | Slack / Phone | Migration fail, performance issue |
| PO | [TBD] | Slack | Business logic verify, Go/No-Go |
| DevOps | [TBD] | Slack | Deploy pipeline, monitoring alert |
| QA Lead | [TBD] | Slack | Test failure, regression report |
Tài liệu liên quan:
- PRD — Requirements + Decision Log
- UI Spec — Screen map + wireframes
- Dev Spec — Data model + API + migrations
- QA Test Plan — 76 test cases
- Perf-fix — Performance optimization prerequisite