Hướng dẫn sử dụng appender để tổng hợp dữ liệu theo ngày và lưu vào các bảng của EzyRating
Back to ezyratingRecord count phù hợp với các số liệu tích lũy toàn thời gian, nhưng chưa đủ để dựng biểu đồ tăng trưởng hoặc so sánh giữa các giai đoạn. Để giải quyết nhu cầu này, EzyRating sử dụng các appender chuyên tổng hợp dữ liệu theo ngày và lưu kết quả vào hai bảng thống kê chính.
Hai bảng thống kê theo ngày
EzyRating chia dữ liệu thành thống kê toàn hệ thống và thống kê theo từng đối tượng.
ezyrating_daily_statistics
Bảng này lưu số liệu chung của toàn hệ thống theo khóa:
statistic_date + statistic_name + record_type
Các trường quan trọng:
| Trường | Ý nghĩa |
|---|---|
statistic_date | Ngày phát sinh dữ liệu |
statistic_name | Tên chỉ số |
record_type | Nhóm phân loại |
record_value | Giá trị số nguyên |
record_decimal_value | Giá trị thập phân, thường dùng cho doanh thu |
created_at | Thời điểm tạo dòng |
updated_at | Thời điểm cập nhật gần nhất |
Ví dụ:
| Ngày | Chỉ số | Loại | Giá trị |
|---|---|---|---|
| 2026-10-04 | Lượt xem | ALL | 2.450 |
| 2026-10-04 | Lượt nhấp | CLICK | 730 |
| 2026-10-04 | Đơn đã thanh toán | ALL | 42 |
| 2026-10-04 | Doanh thu | 1 | 86.500.000 |
Với doanh thu,
record_type chứa ID đồng tiền. Số đơn đóng góp vào doanh thu được lưu ở record_value, còn tổng tiền được lưu ở record_decimal_value.ezyrating_daily_item_statistics
Bảng này lưu dữ liệu theo từng đối tượng cụ thể. Khóa thống kê gồm:
item_type + item_id + statistic_date + statistic_name + record_type
Các trường
item_type và item_id xác định đối tượng được thống kê, chẳng hạn bài viết, sản phẩm hoặc thuật ngữ.Ví dụ:
| Đối tượng | ID | Ngày | Chỉ số | Giá trị |
|---|---|---|---|---|
| Sản phẩm | 101 | 2026-10-04 | Lượt xem | 180 |
| Sản phẩm | 101 | 2026-10-04 | Lượt thêm giỏ | 24 |
| Sản phẩm | 101 | 2026-10-04 | Số lượng bán | 7 |
| Bài viết | 205 | 2026-10-04 | Lượt xem có tương tác | 96 |
Bảng này là nguồn dữ liệu cho danh sách nội dung nổi bật, xu hướng của từng sản phẩm và phân tích kênh truy cập theo đối tượng.
Luồng tổng hợp
Các appender đọc dữ liệu nguồn theo từng lô 100 bản ghi. Trong mỗi lô, dữ liệu được gom theo ngày và khóa thống kê trước khi ghi xuống cơ sở dữ liệu.
flowchart TD
A[Lịch sử hành động, đơn hàng hoặc đánh giá]
B[Đọc tối đa 100 bản ghi sau page token]
C[Xác định ngày nghiệp vụ]
D[Phân tích trạng thái và loại dữ liệu]
E[Gom các số gia cùng khóa]
F[ezyrating_daily_statistics]
G[ezyrating_daily_item_statistics]
H[Lưu page token mới]
A --> B
B --> C
C --> D
D --> E
E --> F
E --> G
F --> H
G --> H
Appender không tạo một dòng mới cho từng sự kiện. Nếu nhiều sự kiện có cùng khóa, chúng được cộng lại trong bộ nhớ.
Ví dụ, một lô có 30 lượt xem trong cùng ngày:
2026-10-04 / page_view / ALL → +30
Appender chỉ cần cập nhật một dòng thống kê thay vì thực hiện 30 lần ghi riêng biệt.
Cách cập nhật một dòng thống kê
Khi lưu kết quả, appender tìm dòng theo khóa duy nhất.
Nếu chưa có, hệ thống tạo mới:
record_value = số gia record_decimal_value = số tiền hoặc 0 created_at = thời điểm hiện tại updated_at = thời điểm hiện tại
Nếu đã tồn tại, hệ thống tăng dồn:
record_value mới
= record_value hiện tại
+ số gia
record_decimal_value mới
= record_decimal_value hiện tại
+ số gia thập phân
Về mặt logic:
Statistic statistic = repository.findByKey(
statisticDate,
statisticName,
recordType
);
if (statistic == null) {
statistic = new Statistic(
statisticDate,
statisticName,
recordType
);
}
statistic.recordValue += increment;
statistic.decimalValue += decimalIncrement;
repository.save(statistic);
Khóa duy nhất ở tầng cơ sở dữ liệu giúp ngăn việc tạo nhiều dòng cho cùng một ngày và cùng một loại chỉ số.
Các appender cần dùng
Trong phần cài đặt EzyRating, những appender sau tham gia tổng hợp dữ liệu theo ngày.
| Appender | Nguồn dữ liệu | Đích |
|---|---|---|
| Thống kê hành động người dùng | Lịch sử hành động | Thống kê chung theo ngày |
| Thống kê từng đối tượng | Lịch sử hành động | Thống kê đối tượng theo ngày |
| Thống kê đơn hàng | Lịch sử đơn hàng | Thống kê chung theo ngày |
| Thống kê sản phẩm trong đơn | Lịch sử thanh toán và sản phẩm của đơn | Thống kê đối tượng theo ngày |
| Thống kê giỏ hàng | Sản phẩm được thêm vào giỏ | Thống kê chung theo ngày |
| Thống kê đánh giá | Lịch sử đánh giá | Thống kê chung theo ngày |
| Thống kê lượt xem theo thuật ngữ | Lượt xem bài viết và quan hệ bài viết–thuật ngữ | Thống kê đối tượng theo ngày |
Mỗi appender có page token riêng. Việc một appender đã bắt kịp dữ liệu không có nghĩa các appender khác cũng đã hoàn thành.
Thống kê hành động người dùng
Appender thống kê hành động tạo dữ liệu chung theo ngày, gồm:
- Lượt xem trang.
- Số hành động theo loại.
- Tổng thời gian hành động.
- Lượt xem có tương tác.
- Tổng độ sâu cuộn.
- Số mẫu cuộn trang.
- Người dùng và IP hoạt động theo ngày.
Ngày thống kê được ưu tiên lấy từ thời điểm xảy ra hành động. Nếu không có, hệ thống dùng thời điểm tạo bản ghi; cuối cùng mới sử dụng ngày hiện tại.
Một hành động cuộn 70% có thể tạo ra:
scroll_depth_sum / ALL +70 scroll_depth_sample_count / ALL +1
Nếu trong ngày có ba mẫu
20, 70 và 100, dữ liệu lưu là:scroll_depth_sum = 190 scroll_depth_sample_count = 3
Dashboard tính độ sâu cuộn trung bình:
190 / 3 = 63,33%
Thống kê theo từng đối tượng
Appender thống kê đối tượng sử dụng lịch sử hành động nhưng bổ sung hai chiều:
item_type + item_id
Các chỉ số được tổng hợp gồm:
- Số hành động.
- Thời gian hành động.
- Lượt xem.
- Lượt xem có tương tác.
- Tổng độ sâu cuộn.
- Số mẫu cuộn.
- Kênh truy cập.
Ví dụ, một lượt xem sản phẩm bằng truy cập từ công cụ tìm kiếm có thể tạo:
product / 101 / 2026-10-04 / page_view / ALL +1 product / 101 / 2026-10-04 / traffic / SEARCH +1
Các hành động không có
item_type hoặc item_id hợp lệ bị bỏ qua vì không thể gắn với một đối tượng cụ thể.Lưu lượng bot cũng không được đưa vào thống kê đối tượng. Điều này tránh để crawler làm sai lệch bảng nội dung hoặc sản phẩm nổi bật.
Thống kê đơn hàng
Appender đơn hàng phân tích lịch sử thay đổi trạng thái thay vì đếm trực tiếp số dòng.
Các chỉ số chính gồm:
- Đơn hàng đã tạo.
- Giỏ hàng đã chuyển thành đơn.
- Đơn hàng đã thanh toán.
- Doanh thu theo đồng tiền.
Khi đơn được tạo:
order_placed / ALL +1
Nếu đơn được tạo từ giỏ hàng:
cart_checked_out / ALL +1
Khi đơn chuyển sang trạng thái đã thanh toán:
order_paid / ALL +1 order_revenue / currency +1 đơn và +doanh thu
Nếu thanh toán bị hoàn tác:
order_paid / ALL -1 order_revenue / currency -1 đơn và -doanh thu
Ngày thống kê đơn hàng là ngày tạo đơn. Vì vậy, nếu một đơn được tạo hôm trước nhưng thanh toán hôm nay, thay đổi thanh toán vẫn được cộng vào ngày tạo đơn.
Quy tắc này giúp các chỉ số của một đơn được giữ cùng một ngày nghiệp vụ. Tuy nhiên, khi phân tích cần hiểu đây không phải là báo cáo dòng tiền theo ngày thanh toán.
Lưu doanh thu
Doanh thu cần cả số nguyên lẫn số thập phân:
record_value = số đơn tạo ra doanh thu record_decimal_value = tổng giá trị doanh thu record_type = ID đồng tiền
Ví dụ:
Ngày: 2026-10-04 Chỉ số: order_revenue Loại: 1 Số đơn: 4 Doanh thu: 8.750.000
Không nên lưu doanh thu vào trường số nguyên vì:
- Tiền có thể có phần thập phân.
- Một số hệ thống sử dụng nhiều đồng tiền.
- Dashboard cần biết cả số đơn và tổng doanh thu để tính giá trị đơn trung bình.
Giá trị tiền không hợp lệ được xem là
0 và ghi cảnh báo, thay vì làm dừng toàn bộ appender.
Thống kê số lượng sản phẩm đã bán
Appender thống kê sản phẩm trong đơn chỉ xử lý các thay đổi thanh toán.
Khi một đơn được thanh toán, số lượng từng sản phẩm được tăng:
product / 101 / sold_quantity / ALL +3
Nếu trạng thái thanh toán bị hoàn tác:
product / 101 / sold_quantity / ALL -3
Nếu cùng một đơn có nhiều lần thay đổi trạng thái trong một lô, appender cộng các mức thay đổi theo ID đơn trước. Các thay đổi triệt tiêu nhau sẽ bị loại bỏ.
Ví dụ:
Đã thanh toán: +1 Hủy thanh toán: -1 Tổng thay đổi: 0
Trong trường hợp này, appender không cập nhật số lượng bán.
Thống kê giỏ hàng
Một giỏ hàng có thể chứa nhiều sản phẩm nhưng chỉ nên được tính một lần vào chỉ số “giỏ có sản phẩm”.
Appender xác định bản ghi sản phẩm đầu tiên của từng giỏ:
cart_with_products / ALL +1
Ví dụ:
Giỏ 1001 thêm sản phẩm A → +1 Giỏ 1001 thêm sản phẩm B → 0 Giỏ 1001 thêm sản phẩm C → 0
Cách tính này đo số giỏ đã bắt đầu có sản phẩm, không phải tổng số thao tác thêm vào giỏ.
Thống kê đánh giá
Appender đánh giá so sánh trạng thái hiện tại với trạng thái trước đó.
Nếu đánh giá chuyển từ không tồn tại hoặc đã xóa sang hoạt động:
rating / ALL +1
Nếu đánh giá chuyển từ hoạt động sang đã xóa:
rating / ALL -1
Nếu chỉ sửa nội dung, số sao hoặc phản hồi nhưng đánh giá vẫn hoạt động:
rating / ALL 0
Nhờ đó, số đánh giá theo ngày phản ánh số đánh giá đang có hiệu lực, thay vì số lần bản ghi được chỉnh sửa.
Ngày thống kê ưu tiên ngày tạo của đánh giá gốc. Khi không tìm thấy đánh giá gốc, hệ thống dùng ngày tạo của bản ghi lịch sử.
Thống kê lượt xem theo thuật ngữ
Một bài viết có thể thuộc nhiều thuật ngữ, danh mục hoặc thẻ. Khi bài viết được xem, appender ánh xạ lượt xem sang các thuật ngữ đang được gắn với bài.
Ví dụ:
Bài viết 205 thuộc: - Java - Backend - Tutorial
Một lượt xem hợp lệ tạo ra:
term / Java / post_view +1 term / Backend / post_view +1 term / Tutorial / post_view +1
Chỉ lượt xem bài viết từ người dùng thực mới được tính. Các hành động khác và truy cập được nhận diện là bot bị bỏ qua.
Dữ liệu này được sử dụng để xây dựng bảng thuật ngữ nổi bật trên dashboard.
Cách viết bộ gom số liệu theo ngày
Đối với thống kê chung, có thể dùng một cấu trúc gom theo khóa:
record DailyKey(
LocalDate date,
String statisticName,
String recordType
) {}
Sau đó gom số nguyên và số thập phân:
Map<DailyKey, StatisticValue> increments = new LinkedHashMap<>(); void increase( LocalDate date, String name, String type, long value, BigDecimal decimalValue ) { DailyKey key = new DailyKey(date, name, type); StatisticValue current = increments.computeIfAbsent( key, ignored -> new StatisticValue() ); current.value += value; current.decimalValue = current.decimalValue.add(decimalValue); }
Với thống kê đối tượng, mở rộng khóa:
record DailyItemKey(
String itemType,
long itemId,
LocalDate date,
String statisticName,
String recordType
) {}
Điều quan trọng là khóa gom trong bộ nhớ phải giống khóa duy nhất trong cơ sở dữ liệu. Nếu thiếu một thành phần, dữ liệu của các ngày hoặc đối tượng khác nhau có thể bị gộp nhầm.
Tạo appender theo ngày mới
Một appender mới cần thực hiện các bước sau.
Chọn nguồn dữ liệu
Nguồn nên là bảng lịch sử hoặc bảng có ID tăng dần ổn định:
@Override protected List<Event> getValueList(Long pageToken) { return repository.findByIdGreaterThan( pageToken, 100 ); }
Danh sách phải được sắp xếp tăng dần theo ID.
Xác định ngày nghiệp vụ
Không nên mặc định dùng ngày appender chạy:
private LocalDate resolveStatisticDate(Event event) { if (event.getIssueDate() != null) { return event.getIssueDate().toLocalDate(); } if (event.getCreatedAt() != null) { return event.getCreatedAt().toLocalDate(); } return clock.nowDate(); }
Xác định số gia
Với sự kiện chỉ phát sinh một lần:
increments.increase(
date,
"my_statistic",
"ALL",
1L
);
Với sự kiện có thể bị đảo ngược:
long change = calculateChange(current, previous); if (change != 0) { increments.increase( date, "my_statistic", "ALL", change ); }
Gom dữ liệu trước khi lưu
Không nên gọi repository cho từng sự kiện. Hãy gom toàn bộ lô rồi lưu theo từng khóa duy nhất.
Cập nhật page token sau cùng
Chỉ cập nhật page token sau khi toàn bộ lô đã được lưu thành công:
@Override protected Long extractNewLastPageToken( List<Event> values, Long currentToken ) { return values.isEmpty() ? currentToken : values.get(values.size() - 1).getId(); }
Cách bật các appender
Trong phần cài đặt EzyRating, hãy bật tối thiểu các appender tương ứng với dữ liệu cần xuất hiện trên dashboard:
- Thống kê hành động người dùng.
- Thống kê từng đối tượng.
- Thống kê đơn hàng.
- Thống kê sản phẩm trong đơn.
- Thống kê giỏ hàng.
- Thống kê đánh giá.
- Thống kê lượt xem theo thuật ngữ nếu sử dụng module nội dung.
Sau khi bật, appender tiếp tục từ page token gần nhất. Nếu chưa từng chạy, nó bắt đầu từ dữ liệu có ID lớn hơn token mặc định, thường là
0.
Appender chạy nền và xử lý từng lô. Với lượng dữ liệu lịch sử lớn, dashboard không cập nhật toàn bộ ngay lập tức; số liệu sẽ tăng dần cho đến khi các token bắt kịp bản ghi mới nhất.
Kiểm tra dữ liệu sau khi bật
Có thể kiểm tra thống kê chung:
SELECT statistic_date, statistic_name, record_type, record_value, record_decimal_value FROM ezyrating_daily_statistics ORDER BY statistic_date DESC, statistic_name;
Kiểm tra thống kê từng đối tượng:
SELECT item_type, item_id, statistic_date, statistic_name, record_type, record_value FROM ezyrating_daily_item_statistics ORDER BY statistic_date DESC, record_value DESC;
Kiểm tra một chỉ số trong một khoảng ngày:
SELECT statistic_date, SUM(record_value) AS total_value FROM ezyrating_daily_statistics WHERE statistic_name = :statistic_name AND statistic_date BETWEEN :from_date AND :to_date GROUP BY statistic_date ORDER BY statistic_date;
Kiểm tra sản phẩm nổi bật:
SELECT item_type, item_id, SUM(record_value) AS total_value FROM ezyrating_daily_item_statistics WHERE statistic_name = :statistic_name AND statistic_date BETWEEN :from_date AND :to_date GROUP BY item_type, item_id ORDER BY total_value DESC LIMIT 10;
Kiểm tra tính đúng đắn
Khi đối chiếu dữ liệu, nên kiểm tra theo từng quy tắc nghiệp vụ.
| Chỉ số | Cách đối chiếu |
|---|---|
| Lượt xem | Đếm hành động xem hợp lệ, loại bot |
| Lượt tương tác | Đếm lượt xem thỏa điều kiện tương tác |
| Độ sâu cuộn | Tổng phần trăm chia số mẫu |
| Giỏ có sản phẩm | Đếm giỏ có bản ghi sản phẩm đầu tiên |
| Đơn đã tạo | Đếm lần đầu chuyển sang trạng thái đã tạo |
| Đơn đã thanh toán | Cộng thay đổi trạng thái thanh toán |
| Doanh thu | Tổng tiền của các chuyển đổi thanh toán, có tính hoàn tác |
| Đánh giá | Cộng đánh giá hoạt động, trừ đánh giá bị xóa |
| Số lượng bán | Số lượng sản phẩm nhân với mức thay đổi thanh toán |
Không nên chỉ đếm số dòng lịch sử vì một đối tượng có thể xuất hiện nhiều lần khi trạng thái thay đổi.
Chạy lại appender
Restart chỉ dừng rồi bật lại appender. Page token vẫn được giữ nguyên, vì vậy tiến trình tiếp tục từ vị trí cũ.Reload đưa page token về trạng thái ban đầu. Nếu các dòng thống kê cũ vẫn còn, appender có thể cộng lại dữ liệu và tạo số liệu trùng.Quy trình tái tổng hợp an toàn:
- Dừng appender cần xây dựng lại.
- Xác định chính xác các
statistic_namedo appender đó quản lý. - Xóa các dòng tương ứng trong bảng thống kê theo ngày.
- Xóa dữ liệu ở bảng thống kê đối tượng nếu appender có ghi vào đó.
- Xử lý dấu đã tổng hợp nếu appender sử dụng cơ chế đánh dấu.
- Đặt lại page token.
- Bật lại appender.
- Theo dõi cho đến khi appender bắt kịp dữ liệu mới nhất.
- Đối chiếu một vài ngày mẫu với dữ liệu nguồn.
Không nên xóa toàn bộ hai bảng thống kê nếu chỉ cần xây dựng lại một nhóm chỉ số.
Tránh cộng trùng
Page token giúp xử lý tăng dần nhưng tự nó không tạo ra tính idempotent tuyệt đối. Nếu cùng một lô được lưu thành công nhưng token chưa kịp cập nhật, lô đó có thể được xử lý lại.
Để tăng độ an toàn cho appender mới, có thể áp dụng một trong các chiến lược:
- Đánh dấu từng bản ghi nguồn đã được tổng hợp.
- Lưu nhật ký sự kiện đã xử lý bằng ID duy nhất.
- Dùng bảng staging và transaction.
- Lưu checkpoint cùng transaction với kết quả tổng hợp.
- Thiết kế phép ghi theo giá trị cuối cùng thay vì tăng dồn khi phù hợp.
- Cho phép quy trình xóa và xây dựng lại có kiểm soát.
Khóa duy nhất trong bảng thống kê chỉ ngăn tạo dòng trùng; nó không ngăn cùng một số gia được cộng hai lần vào một dòng.
Xử lý đồng thời
Mỗi appender chỉ nên chạy trên một máy chủ chịu trách nhiệm tổng hợp. Nếu hai tiến trình cùng xử lý một page token, cả hai có thể cộng cùng dữ liệu.
Trong hệ thống nhiều máy chủ:
- Chỉ máy chủ chính nên chạy appender.
- Các máy còn lại chỉ phục vụ request hoặc đọc thống kê.
- Không tự tạo thêm scheduler cho cùng một appender.
- Khi chuyển vai trò máy chủ, cần bảo đảm appender cũ đã dừng.
Những lưu ý quan trọng
- Dùng ngày nghiệp vụ, không dùng ngày xử lý nếu hai thời điểm khác nhau.
- Phân biệt chỉ số toàn hệ thống và chỉ số từng đối tượng.
- Chuẩn hóa
record_typetrước khi lưu. - Bỏ qua khóa rỗng hoặc đối tượng không hợp lệ.
- Không tính bot vào số liệu tương tác của người dùng.
- Dùng
BigDecimalcho doanh thu, không dùngdouble. - Cho phép số gia âm với dữ liệu có thể bị hoàn tác.
- Gom theo lô để giảm số lần truy cập cơ sở dữ liệu.
- Không cập nhật token nếu việc lưu dữ liệu chưa hoàn tất.
- Không dùng
Reloadnhư một thao tác khởi động lại thông thường.
Kết luận
Các appender theo ngày giúp EzyRating chuyển lịch sử hành động, đơn hàng, giỏ hàng và đánh giá thành dữ liệu có thể truy vấn nhanh theo thời gian. Bảng
ezyrating_daily_statistics cung cấp xu hướng toàn hệ thống, còn ezyrating_daily_item_statistics cung cấp góc nhìn chi tiết theo từng bài viết, sản phẩm hoặc thuật ngữ.Khi xây dựng appender mới, ba yếu tố quan trọng nhất là xác định đúng ngày nghiệp vụ, tính đúng số gia trạng thái và quản lý page token an toàn. Nếu ba phần này nhất quán, dashboard có thể hiển thị chính xác tăng trưởng, chuyển đổi và hiệu quả của từng đối tượng mà không phải quét lại dữ liệu lịch sử trong mỗi lần truy vấn.