[ ♥REC. ] · Báo cáo phân tích hiệu năng — Bản nội bộ (VN)
🇻🇳 Tiếng Việt 🇯🇵 JP: customer-report-jp.html
🔴 Ưu tiên cao LRCC-2386 2026-05-19 | Đội phát triển Amela

CMS phản hồi chậm —
Báo cáo nguyên nhân

Về phản hồi từ khách hàng 「CMS 側の反応スピードが遅い」 (tốc độ phản hồi phía CMS rất chậm), đội đã phân tích video khách gửi kèm với source code liên quan và xác định được nguyên nhân gốc cùng lộ trình khắc phục.

TÓM TẮT
  • Màn hình: Quản lý doanh thu Partnership — Creator
  • Hiện trạng: bảng creator >60 giây chưa load xong
  • Sau 4 bước fix: còn <1 giây
  • Riêng bước 1: giảm 50% (1 ngày dev)
01 | Hiện trạng

Xác nhận hiện trạng

Đội đã phân tích từng frame của video 60 giây khách gửi → xác nhận bảng creator không load xong cho đến hết video.

📹 Tiến trình video (60 giây)
/revenue-partnership-creator
frame at 0s Trước khi chuyển 00:00
frame at 8s Vừa click menu 00:08
frame at 28s Bảng vẫn loading 00:28
frame at 48s Mới có sub-table 00:48
frame at 60s ⚠ Bảng list vẫn trống 00:60
Trang gọi đồng thời 2 API và cả 2 đều quá chậm:
GET /cms/revenue/partnership/statistic-detail (sub-table tổng) ~ 46 giây
GET /cms/revenue/partnership (bảng creator list) >60 giây ⚠ chưa xong
※ Đến giây thứ 60 spinner () vẫn quay = server chưa trả response. Khách hàng phải chờ vô thời hạn.
02 | Nguyên nhân gốc

Bốn nguyên nhân chính

Phân tích SQL của API GET /cms/revenue/partnership cho thấy 4 vấn đề thiết kế cộng dồn tạo nên độ trễ này.

Mức độ ★★★★★
01

Cùng 1 SQL khổng lồ chạy 2 lần

Một query lấy dữ liệu, một query đếm tổng số trang (COUNT) — cả 2 chạy cùng 1 SQL với 10 LEFT JOIN giống y hệt. DB phải làm cùng phép tính nặng 2 lần liên tiếp cho mỗi request.

backend/src/app/admin/revenue/revenue.service.ts : 5397–5432

1 request Query A (lấy data) 10 LEFT JOIN + UNION × 3 Query B (COUNT) làm lại đúng phép tính đó
=> Cùng phép tính nặng nhưng phải chịu tải gấp đôi
Mức độ ★★★★★
02

Khối UNION 5–6 bảng bị lặp 3 lần

Một sub-query gồm UNION 5–6 phần (video_view + video_comment + 3 sub-query trên bảng lịch sử giao dịch) được dán nguyên xi vào 3 chỗ khác nhau trong query chính. MySQL không cache derived table → phải thực thi UNION này lặp lại 3 lần.

revenue.service.ts : 5120, 5134, 5152

1 query chính UNION sub #1 video_view video_comment tx_history (sub) tx_history (donate) tx_history (retail) 🔁 UNION sub #2 video_view video_comment tx_history (sub) tx_history (donate) tx_history (retail) 🔁 UNION sub #3 video_view video_comment tx_history (sub) tx_history (donate) tx_history (retail) 🔁
=> Quét cùng 5 bảng đó × 3 lần = 15 lần scan/request
Mức độ ★★★★
03

Nhồi 10 bảng aggregate vào 1 SQL

Doanh thu video, coin bình luận, reaction, share, follower, subscription, single-item … 10 loại aggregate được LEFT JOIN trong 1 query. MySQL phải dựng đầy đủ 10 derived table xong rồi mới join với memberId. Không có LIMIT trên sub-query → không thể short-circuit.

revenue.service.ts : 5345–5393

memberVideos (driving) videoPoint videoCoin partnerCoin donated reaction share follower subscription retail mlss
=> 1 request đụng đến 10 phép aggregate khác nhau
Mức độ ★★★
04

Không có cache layer

Admin thường mở lại cùng filter (cùng khoảng tháng, cùng plan type) nhiều lần trong ngày, nhưng mỗi lần là chạy lại y nguyên SQL khổng lồ từ đầu. Không Redis cache, không HTTP cache — F5 cũng không nhanh hơn.

Request 1 Request 2 Request 3 Không cache DB tính lại từ đầu
=> F5 vẫn chậm như lần đầu
03 | Ước tính chi phí

Mỗi request đang đọc bao nhiêu dòng?

Ước tính trên data size hiện tại của STG (video_view ~5M dòng, transaction history ~3M dòng)

Sub-query lặp
×3
UNION giống nhau
chạy lại 3 lần
Query nhân đôi
×2
Lấy data + COUNT
= toàn bộ chạy 2 lần
Ước tính
~150M
dòng đọc/request
(table thật × hệ số lặp)
Mỗi lần admin mở trang, DB đang đọc ~150 triệu dòng. Đây là lý do trang treo 60+ giây.
04 | Lộ trình fix

Roadmap 4 bước

Cách tiếp cận theo bậc thang: ngắn hạn ăn ngay lợi lớn → trung hạn fix triệt để → dài hạn xây nền cho các trang aggregate khác.

1

Tối ưu query COUNT (xoá double execution)

Effort: ~1 ngày Giảm: ~50%

Đổi câu COUNT để chỉ chạy trên driving table (memberVideos) thay vì re-join toàn bộ 10 bảng. Do toàn bộ là LEFT JOIN nên số dòng cuối không thay đổi → COUNT trên driving table cho ra cùng kết quả. Giải quyết ngay vấn đề "chạy 2 lần".

2

Đưa UNION sub-query về TEMPORARY TABLE

Effort: ~1–2 ngày Giảm thêm: ~60%

Refactor: dùng CREATE TEMPORARY TABLE cho khối UNION 5–6 phần, rồi 3 chỗ trong main query chỉ reference temp table. MySQL chỉ phải tính UNION 1 lần. Giải quyết vấn đề "lặp 3 lần".

3

Thêm Redis cache layer

Effort: ~1–2 ngày Lần load lại: < 100ms

Cache key = hash(date range + filters), TTL 5–10 phút. Hạ tầng đã có Redis sẵn, chi phí thêm gần như 0. Lần thứ 2 admin mở cùng filter sẽ trả về tức thì.

4

Pre-aggregate bằng cron job (giải pháp gốc)

Effort: ~3–5 ngày Response: < 1 giây

Tạo bảng denormalized partnership_revenue_daily, worker cron-job-service đã có sẵn sẽ refresh mỗi 15–30 phút. Lúc đó query chính chỉ còn SUM GROUP BY trên bảng nhỏ. Áp dụng được cho cả các trang aggregate khác trong tương lai.

05 | Hiệu quả dự kiến

Tốc độ sau từng bước

Mức cải thiện cộng dồn qua từng bước fix

Hiện tại
chưa fix gì
60s +
Sau Bước 1
tối ưu COUNT
~30s
Sau Bước 2
+ UNION temp table
~12s
Sau Bước 3
+ Redis cache
~3s
Sau Bước 4
+ pre-aggregate
< 1s
Tổng effort
6–10 ngày
Bước đầu mất
1 ngày
Speed cuối
60× +

Đề xuất next step nội bộ

BA + dev review bản tiếng Việt này trước. Sau khi confirm content + đồng ý thứ tự ưu tiên (Bước 1 đi trước, hay đẩy Bước 3 Redis lên trước để hiệu quả nhanh cho lần load thứ 2?), team sẽ translate sang JP gửi khách hàng.

Khuyến nghị: commit Bước 1 ngay (effort 1 ngày, giảm 50%) để có evidence trước khi reply KH.

FILE LIÊN QUAN
  • 📋 Backlog: LRCC-2386
  • 📁 Báo cáo kỹ thuật: analysis/perf-root-cause.md
  • 🇯🇵 Bản JP (gửi KH): analysis/customer-report-jp.html
  • 🎬 Video gốc: attachments/gyazo_…mp4
  • 🧩 Tickets liên quan: LRCC-1535 / LRCC-1811 / LRCC-1518
Amela Dev Team | Bản nội bộ — chưa gửi KH
2026-05-19