- Bài toán: Phát hiện “đang nóng” hay chỉ là nhiễu?
- Bốn meta key — tất cả những gì cần thiết
- Phần I: Tiếp cận tuyến tính — nền tảng cơ bản
- Phép chiếu tuyến tính (Linear Projection)
- Vấn đề đầu ngày: Nhiễu tuyến tính
- Vấn đề volume: Truyện nhỏ bị thiệt thòi
- Kết hợp ngày và tuần — Momentum Signal
- Độ tự tin theo thời gian (Confidence Weighting)
- Phần II: Tích hợp Traffic Shape Learner
- Traffic không phân phối đều — và đây là vấn đề lớn
- Cách Traffic Shape Learner hoạt động
- Chuyển đổi từ đồng hồ sang CDF traffic
- Tác động đến toàn bộ hệ thống
- Dự đoán lượt xem cuối ngày — Hybrid Bayesian Smoothing
- Thông tin bổ sung từ 4 meta key
- Hiệu năng: Một query, ngàn bộ truyện
- Kết luận
Bài toán: Phát hiện “đang nóng” hay chỉ là nhiễu?
Giả sử lúc 9 giờ sáng, một bộ truyện đã có 80 lượt xem. Câu hỏi tự nhiên là: con số này có đáng chú ý không? Câu trả lời phụ thuộc hoàn toàn vào ngữ cảnh — hôm qua cùng giờ này bộ truyện đó có bao nhiêu view? Cả tuần trước thì sao? Và 9 giờ sáng đại diện cho bao nhiêu phần trăm lưu lượng trong ngày?
Nếu chỉ nhìn vào con số tuyệt đối, ta sẽ luôn thiên vị những bộ truyện vốn đã lớn. Hệ thống HeatWave giải quyết bài toán này bằng cách đo tốc độ tăng trưởng tương đối, dựa trên lịch sử của chính bộ truyện đó — không so sánh chéo giữa các bộ.
Bốn meta key — tất cả những gì cần thiết
Toàn bộ hệ thống chỉ dựa vào 4 giá trị được lưu trên mỗi post:
_init_view_day_count— lượt xem hôm nay (từ 00:00 đến hiện tại)_init_view_day_yesterday— tổng lượt xem của hôm qua (toàn ngày)_init_view_week_count— lượt xem tuần này (từ đầu tuần đến hiện tại)_init_view_week_last— tổng lượt xem của tuần trước (toàn tuần)
Đây đều là những con số thường trực trong hệ thống đếm view thông thường. Không cần schema mới, không cần bảng log, không cần queue xử lý nặng.
Phần I: Tiếp cận tuyến tính — nền tảng cơ bản
Phép chiếu tuyến tính (Linear Projection)
Ý tưởng đầu tiên và đơn giản nhất: nếu một bộ truyện đã đạt X lượt xem sau khi trôi qua p phần trăm thời gian trong ngày, thì đến cuối ngày nó dự kiến sẽ đạt X / p lượt xem.
// Elapsed ratio tuyến tính: giờ hiện tại / 24
$elapsed = ($hour + 1) / 24;
// Chiếu số view cuối ngày
$projected = $current_views / $elapsed;
// Tốc độ tăng trưởng so với hôm qua
$raw_growth = ($projected - $yesterday) / $yesterday;
Nếu $raw_growth dương và đủ lớn, bộ truyện đang “nóng”. Đây là nền tảng của toàn bộ thuật toán — và cũng là điểm yếu đầu tiên cần xử lý.
Vấn đề đầu ngày: Nhiễu tuyến tính
Khi mới 2 giờ sáng, elapsed ratio chỉ là 0.125 (3/24). Nếu một bộ truyện vừa có 10 lượt xem từ một nguồn giới thiệu ngẫu nhiên, phép chiếu tuyến tính sẽ dự đoán 10 / 0.125 = 80 lượt xem — và nếu hôm qua chỉ có 30, tốc độ tăng trưởng sẽ trông rất ấn tượng dù chỉ là ngẫu nhiên.
Để giải quyết, ta đặt ngưỡng elapsed tối thiểu — ví dụ 0.33 (khoảng 8 giờ sáng) — trước khi tính toán bất kỳ điều gì. Và thêm một bộ lọc volume tối thiểu: nếu số view tích lũy hiện tại quá nhỏ so với hôm qua, ta coi là dữ liệu chưa đủ tin cậy.
function init_html_calc_growth_score_linear(
int $current,
int $previous,
float $elapsed
): ?float {
// Chưa đủ thời gian trôi qua
if ($elapsed < 0.33) return null;
// Hôm qua không đủ baseline
if ($previous < 50) return null;
// View hiện tại quá nhỏ để tin
if ($current < $previous * 0.05) return null;
$projected = $current / $elapsed;
$raw_growth = ($projected - $previous) / $previous;
return $raw_growth;
}
Vấn đề volume: Truyện nhỏ bị thiệt thòi
Trong mô hình tuyến tính thuần túy, một bộ truyện tăng từ 20 lên 40 view (tăng 100%) sẽ đứng trên bảng xếp hạng cùng với một bộ tăng từ 2.000 lên 4.000 view. Tín hiệu từ truyện nhỏ quá nhiễu — vài lượt click từ một share duy nhất có thể tạo ra tốc độ tăng trưởng ảo khổng lồ.
Giải pháp tuyến tính đơn giản nhất là áp thêm hệ số giảm chấn dựa trên quy mô: score của truyện nhỏ được nhân với một hệ số nhỏ hơn 1. Ví dụ, nếu ngưỡng “bình thường” của site là 400 view/ngày, thì một truyện chỉ có 100 view/ngày hôm qua sẽ có score bị giảm còn sqrt(100/400) = 0.5.
$site_target = 400; // Tùy theo quy mô site
if ($previous < $site_target) {
$low_volume_factor = sqrt($previous / $site_target);
$score *= $low_volume_factor;
}
Kết hợp ngày và tuần — Momentum Signal
Chỉ nhìn vào một chiều (ngày hoặc tuần) dễ bị đánh lừa. Một bộ truyện có thể có ngày hôm nay đột biến nhưng xu hướng tuần lại đang giảm (ví dụ tuần trước có chapter mới, tuần này không). Ngược lại, một bộ tăng đều đặn cả ngày lẫn tuần cho thấy tín hiệu thực sự bền vững hơn.
Ta tính song song hai score — một cho ngày, một cho tuần — rồi lấy score tốt nhất. Nếu cả hai cùng dương, thưởng thêm một hệ số momentum. Nếu tuần này sụt giảm mạnh so với tuần trước (trong khi đã đủ dữ liệu), phạt score xuống.
$best = max(array_filter([$day_score, $week_score], fn($s) => $s !== null));
// Cả hai chiều cùng tích cực: thưởng momentum
if ($day_score > 0 && $week_score > 0) {
$best *= 1.15;
}
// Tuần này suy giảm đáng kể so với tuần trước: phạt
if ($week_last >= 50 && $week < $week_last * 0.65 && $week_elapsed >= 0.5) {
$best *= 0.65;
}
Độ tự tin theo thời gian (Confidence Weighting)
Ở giai đoạn tuyến tính nâng cao, ta thêm một lớp tin cậy theo thời gian: score không chỉ là tốc độ tăng trưởng thô, mà còn được nhân với sqrt(elapsed). Kết quả là cùng một tốc độ tăng, số liệu ghi nhận lúc chiều tối sẽ có trọng lượng cao hơn số liệu lúc sáng sớm.
$confidence = sqrt($elapsed);
$score = $raw_growth * $confidence;
Đây là mô hình hoàn chỉnh ở giai đoạn tuyến tính. Nó hoạt động tốt trong điều kiện bình thường, nhưng vẫn còn một giả định ngầm chưa được giải quyết: rằng traffic phân phối đều trong ngày và trong tuần — điều này hầu như không bao giờ đúng.
Phần II: Tích hợp Traffic Shape Learner
Traffic không phân phối đều — và đây là vấn đề lớn
Trên hầu hết các site nội dung, traffic không chia đều 24 giờ. Thực tế phổ biến: rất ít người online lúc 3-4 giờ sáng, traffic bắt đầu tăng từ 7-8 giờ, đạt đỉnh vào buổi tối, rồi giảm dần. Nếu ta dùng elapsed tuyến tính (giờ / 24) để chiếu số liệu, ta đang giả định 10% traffic đã trôi qua lúc 2h24 sáng — trong khi thực tế có thể chưa đến 1%.
Hệ quả: một bộ truyện có 50 lượt xem lúc 8 giờ sáng (khi traffic thực sự mới bắt đầu) sẽ bị chiếu thấp hơn nhiều so với thực tế, và ta có thể bỏ lỡ tín hiệu nóng thực sự.
Traffic Shape Learner giải quyết chính xác vấn đề này. Module này học phân phối traffic của site theo từng giờ trong ngày và từng ngày trong tuần, sử dụng kỹ thuật Exponential Moving Average (EMA) kết hợp với Bayesian Prior để làm mượt dữ liệu.
Cách Traffic Shape Learner hoạt động
Mỗi lần có lượt xem được ghi nhận, module cập nhật “thùng” (bin) theo giờ hiện tại:
// Ghi nhận view theo giờ site
$hour = (int) wp_date('G', $now_gmt);
$state['hour_bins'][$hour] += 1;
$state['total'] += 1;
Cuối mỗi ngày (tại thời điểm reset), hệ thống chạy rollup: tính tỉ lệ phần trăm traffic của từng giờ trong tổng traffic ngày hôm qua, rồi cập nhật EMA để tiếp tục học:
// Tính share của từng giờ, có Prior Bayesian để chống nhiễu
$alpha = 0.25; // Tốc độ học
$kappa = 48; // Sức mạnh prior (tương đương 48 view "mặc định" cho mỗi giờ)
for ($h = 0; $h < 24; $h++) {
$num = $hour_bins[$h] + $kappa * (1.0 / 24.0);
$share[$h] = $num / ($total + $kappa);
$mult = $share[$h] / (1.0 / 24.0); // So sánh với phân phối đều
$shape[$h] = (1 - $alpha) * $shape[$h] + $alpha * $mult;
}
// Chuẩn hóa để mean = 1
Kết quả sau vài tuần học: mảng shape['hour'] có 24 phần tử, mỗi phần tử là hệ số nhân so với phân phối đều. Ví dụ, giờ 21 có thể có shape = 1.8 (cao hơn trung bình 80%), trong khi giờ 3 có shape = 0.15 (chỉ bằng 15% trung bình).
Chuyển đổi từ đồng hồ sang CDF traffic
Với shape đã học, hàm tính elapsed ratio không còn là phép chia đơn giản nữa:
function init_html_get_day_elapsed_ratio(): float {
$hour = (int) current_time('G');
$shapes = /* lấy từ Traffic Shape Learner */;
if (is_array($shapes) && isset($shapes['hour'])) {
// Tính lũy kế: tổng traffic từ 0h đến giờ hiện tại
$elapsed_sum = 0;
for ($i = 0; $i <= $hour; $i++) { $elapsed_sum += (float) ($shapes['hour'][$i] ?? 1.0); } $total_sum = array_sum($shapes['hour']); return $total_sum > 0 ? ($elapsed_sum / $total_sum) : (($hour + 1) / 24);
}
// Fallback tuyến tính nếu chưa có dữ liệu
return ($hour + 1) / 24;
}
Đây chính là Cumulative Distribution Function (CDF) của traffic thực tế. Lúc 8 giờ sáng, thay vì luôn trả về 0.375 (9/24) như mô hình tuyến tính, hàm này có thể trả về 0.12 — phản ánh thực tế rằng chỉ 12% traffic cả ngày đã đến vào thời điểm đó.
Tác động đến toàn bộ hệ thống
Khi elapsed ratio chính xác, toàn bộ các phép tính phía trên trở nên đáng tin cậy hơn:
Thứ nhất, ngưỡng elapsed tối thiểu có thể hạ xuống từ 0.33 xuống còn 0.10 — tức là hệ thống có thể phát hiện truyện nóng sớm hơn đáng kể (ngay khi 10% traffic thực tế của ngày đã đến, thay vì phải chờ đến 8 giờ sáng).
Thứ hai, Volume Guard (bộ lọc chặn truyện view quá thấp) cũng thông minh hơn. Thay vì áp dụng ngưỡng cứng bất kể giờ nào, ta điều chỉnh ngưỡng theo đúng dòng chảy traffic:
// Đầu giờ cao điểm: yêu cầu tối thiểu cao hơn để tránh spike ảo
if ($elapsed_ratio < 0.5 && $current < ($previous * 0.12)) {
return null; // Chưa đủ tín hiệu
}
Thứ ba, Breakout Detection — cơ chế phát hiện truyện bùng nổ đột biến — cũng áp dụng cùng elapsed ratio để chiếu dự báo:
function init_html_is_breakout(int $current, int $previous, float $elapsed): bool {
if ($elapsed < 0.10 || $previous < 50) return false; $projected = $current / $elapsed; return ($projected / $previous) >= 4.5; // Dự kiến gấp 4.5x baseline
}
Một truyện dự kiến đạt gấp 4.5 lần ngày hôm qua — dù mới giữa ngày — sẽ được đánh dấu “breakout” ngay lập tức, không cần chờ đủ elapsed ratio cao.
Dự đoán lượt xem cuối ngày — Hybrid Bayesian Smoothing
Song song với việc phát hiện truyện nóng, hệ thống còn có khả năng dự báo tổng lượt xem khi kết thúc ngày. Đây là bài toán hữu ích cho bảng xếp hạng thực thời và hệ thống gợi ý nội dung.
Phương pháp thuần túy — projected = current / elapsed — hoạt động tốt cuối ngày nhưng rất bất ổn đầu ngày: một spike nhỏ lúc sáng sớm có thể thổi phồng dự báo lên 10 lần thực tế.
Giải pháp là Hybrid Bayesian Smoothing: kết hợp giữa chiếu thực tế và baseline hôm qua, với trọng số phụ thuộc vào elapsed ratio:
function init_html_predict_eod_views(int $post_id, array $meta): int {
$current = $meta['day'];
$yesterday = $meta['day_yesterday'];
if ($current <= 0) return $yesterday;
$elapsed = init_html_get_day_elapsed_ratio();
$pure_projected = $current / $elapsed;
// Đầu ngày: tin vào baseline hôm qua nhiều hơn
// Cuối ngày: tin vào thực tế đang diễn ra
$weight = $elapsed;
$predicted = ($pure_projected * $weight) + ($yesterday * (1 - $weight));
return max($current, (int) round($predicted));
}
Khi elapsed = 0.10 (sáng sớm), công thức cho 10% trọng lượng vào chiếu thực tế và 90% vào baseline hôm qua — dự báo sẽ không nhảy loạn vì spike. Khi elapsed = 0.90 (gần cuối ngày), trọng lượng đảo ngược và dự báo bám sát thực tế. Đường cong chuyển đổi mượt mà, không bước nhảy đột ngột.
Thông tin bổ sung từ 4 meta key
Ngoài phát hiện nóng và dự báo view, cùng 4 meta key đó còn cho phép tính thêm hai chỉ số có giá trị:
Trạng thái phong độ tuần — dựa trên tốc độ tăng trưởng tuần so với tuần trước, có thể phân loại bộ truyện vào các nhóm: “Skyrocketing” (tăng trên 50%), “Gaining momentum” (tăng 10-50%), “Holding steady” (dao động trong ngưỡng bình thường), hoặc “Cooling down” (giảm trên 30%). Chỉ số này hữu ích hơn con số tuyệt đối khi hiển thị trên trang tác giả hoặc bảng quản trị.
Record Breaker Detection — kiểm tra xem hôm nay bộ truyện đã vượt tổng view cả ngày hôm qua chưa, dù ngày chưa kết thúc. Đây là tín hiệu mạnh nhất có thể rút ra từ 4 meta key: không cần chiếu, không cần ước lượng, chỉ so sánh trực tiếp.
function init_html_is_record_breaker(array $meta): bool {
return $meta['day_yesterday'] >= 50
&& $meta['day'] > $meta['day_yesterday'];
}
Hiệu năng: Một query, ngàn bộ truyện
Thiết kế toàn bộ hệ thống từ đầu đã tính đến việc chạy trong vòng lặp hàng trăm, thậm chí hàng nghìn bộ truyện:
Elapsed ratio — cả ngày lẫn tuần — được tính một lần duy nhất nhờ static cache bên trong hàm, sau đó tái sử dụng cho toàn bộ vòng lặp. Traffic shape được cache qua transient với TTL 2 giờ, không query database trong mỗi request. Toàn bộ 4 meta key được nạp vào object cache bằng một lệnh update_post_meta_cache($post_ids) duy nhất trước khi vào vòng lặp.
// Nạp cache 1 lần cho toàn bộ danh sách
init_html_prime_view_meta_cache($post_ids);
// Tính elapsed 1 lần bên ngoài vòng lặp
$day_elapsed = init_html_get_day_elapsed_ratio();
$week_elapsed = init_html_get_week_elapsed_ratio();
$threshold = init_html_get_hot_threshold();
foreach ($posts as $post) {
$meta = init_html_get_view_meta($post->ID);
$score = init_html_calc_hot_score($meta, $day_elapsed, $week_elapsed);
$results[$post->ID] = $score >= $threshold;
}
Kết luận
HeatWave là minh chứng rằng một hệ thống phát hiện xu hướng thực thời không nhất thiết phải phức tạp về hạ tầng. Chỉ với 4 meta key và một module học phân phối traffic chạy ngầm, ta có thể xây dựng một bộ máy phân tích có chiều sâu: phát hiện truyện bùng nổ sớm, lọc nhiễu đầu ngày, dự báo lượt xem cuối ngày, theo dõi phong độ tuần và nhận diện kỷ lục theo thời gian thực.
Điểm then chốt là sự tách biệt sạch giữa hai lớp: lớp tuyến tính thuần túy (không phụ thuộc Traffic Shape) đủ dùng ngay từ ngày đầu, và lớp Traffic Shape Learner nâng cấp dần theo thời gian khi site tích lũy đủ dữ liệu thực tế. Hai lớp này cộng hưởng qua một điểm duy nhất — hàm tính elapsed ratio — giữ cho toàn bộ hệ thống gọn và dễ bảo trì.
Bình luận