Hướng dẫn sử dụng appender để tổng hợp các chỉ số lưu vào bảng record count
Back to ezyratingSau khi dữ liệu hành vi được EzyRating tiếp nhận và lưu dưới dạng lịch sử, hệ thống cần chuyển hàng nghìn bản ghi chi tiết thành các số liệu có thể truy vấn nhanh. Đây là nhiệm vụ của appender: đọc dữ liệu mới theo từng lô, tổng hợp theo nhóm và tăng dồn kết quả vào bảng
ezy_data_record_counts.Appender là gì?
Appender là một tiến trình nền chuyên xử lý dữ liệu theo kiểu tuần tự. Mỗi appender thường gắn với một nguồn dữ liệu, chẳng hạn:
- Lịch sử hành động của người dùng.
- Lịch sử đơn hàng.
- Thay đổi trong giỏ hàng.
- Lịch sử đánh giá.
- Lượt xem nội dung.
- Dữ liệu người dùng hoạt động theo thời gian thực.
Thay vì quét lại toàn bộ bảng nguồn trong mỗi lần chạy, appender lưu một
page token để ghi nhớ vị trí đã xử lý. Ở lần tiếp theo, nó chỉ lấy những bản ghi nằm sau token này.
Luồng tổng quát như sau:
flowchart LR
A[Bảng lịch sử] --> B[Đọc một lô dữ liệu mới]
B --> C[Phân tích và chuẩn hóa]
C --> D[Gom nhóm trong bộ nhớ]
D --> E[Tăng record count]
D --> F[Lưu thống kê theo ngày]
E --> G[Lưu page token mới]
F --> G
Cơ chế này giúp chi phí xử lý phụ thuộc vào số bản ghi mới, thay vì phụ thuộc vào tổng kích thước dữ liệu lịch sử.
Cấu trúc bảng record count
Các chỉ số tổng hợp được lưu trong bảng:
ezy_data_record_counts
Ba trường có ý nghĩa quan trọng nhất là:
| Trường | Ý nghĩa |
|---|---|
data_name | Tên chỉ số cần tổng hợp |
record_type | Nhóm hoặc chiều phân loại |
record_count | Giá trị đã được cộng dồn |
Ví dụ:
| `data_name` | `record_type` | `record_count` |
|---|---|---|
| Lượt xem trang | ALL | 152.430 |
| Số hành động | CLICK | 18.920 |
| Loại thiết bị | MOBILE | 94.210 |
| Loại thiết bị | DESKTOP | 51.340 |
| Kênh truy cập | SEARCH | 38.520 |
| Khách duy nhất | USER | 12.480 |
| Khách duy nhất | IP | 27.610 |
data_name xác định loại thống kê, còn record_type cho biết thống kê đang được chia theo chiều nào.Với một chỉ số tổng không cần phân loại, EzyRating sử dụng nhóm
ALL. Với dữ liệu phân phối, record_type có thể là loại hành động, thiết bị, trình duyệt, kênh truy cập hoặc một thuộc tính khác.Cách appender đọc dữ liệu
Appender thống kê hành động đọc lịch sử theo thứ tự ID tăng dần. Mỗi lần chạy, nó lấy tối đa 100 bản ghi sau
page token hiện tại.Có thể hình dung truy vấn theo dạng:
SELECT * FROM user_action_history WHERE id > :last_page_token ORDER BY id ASC LIMIT 100;
Sau khi xử lý thành công một lô, ID của bản ghi cuối cùng được lưu làm token mới:
Token hiện tại: 12.000 Lô vừa đọc: 12.001 → 12.100 Token mới: 12.100
Nếu không có dữ liệu mới, token được giữ nguyên và appender chờ chu kỳ tiếp theo.
Page token được lưu trong phần thiết lập của hệ thống. Vì vậy, tiến trình có thể tiếp tục từ vị trí cũ ngay cả khi ứng dụng được khởi động lại.
Từ một hành động đến các chỉ số tổng hợp
Một bản ghi hành động có thể đóng góp vào nhiều chỉ số.
Ví dụ, một khách truy cập từ Google bằng điện thoại và mở trang sản phẩm có thể tạo ra các phép tăng sau:
page_view / ALL +1 action_count / PAGE_VIEW +1 device_type / MOBILE +1 os_name / ANDROID +1 browser_name / CHROME +1 traffic_channel / SEARCH +1 referrer_host / google.com +1 domain_name / example.com +1
Nếu người dùng đã đăng nhập hoặc có địa chỉ IP hợp lệ, bản ghi còn được dùng để cập nhật số khách duy nhất và khách hoạt động.
Appender không ghi từng phép tăng ngay lập tức. Các giá trị được gom trong bộ nhớ theo cấu trúc tương đương:
Ngày
└── Tên chỉ số
└── Loại bản ghi
└── Giá trị cần tăng
Ví dụ:
{
"2026-10-04": {
"page_view": {
"ALL": 75
},
"action_count": {
"CLICK": 24,
"SCROLL": 42
},
"device_type": {
"MOBILE": 50,
"DESKTOP": 25
}
}
}
Cuối lô, các giá trị cùng khóa mới được tăng vào cơ sở dữ liệu. Cách làm này giảm số lần cập nhật và tránh thực hiện một câu lệnh cho từng hành động riêng lẻ.
Tăng giá trị trong record count
Với mỗi cặp
data_name và record_type, dịch vụ record count thực hiện hai bước:- Tạo bản ghi nếu khóa tổng hợp chưa tồn tại.
- Tăng
record_countbằng giá trị vừa tính được.
Về mặt logic, thao tác tương đương:
INSERT INTO ezy_data_record_counts (
data_name,
record_type,
record_count
)
VALUES (
:data_name,
:record_type,
:increment
)
ON DUPLICATE KEY UPDATE
record_count = record_count + :increment;
Việc tăng phải được thực hiện ở tầng cơ sở dữ liệu hoặc bằng một thao tác nguyên tử tương đương. Không nên đọc giá trị hiện tại về ứng dụng, tự cộng rồi ghi lại vì nhiều tiến trình đồng thời có thể làm mất số liệu.
Ví dụ triển khai ở mức khái niệm:
recordCountService.increment(
statisticName,
recordType,
increment
);
Trong đó:
-
statisticNamelà tên ổn định của chỉ số. -
recordTypelà nhóm đã được chuẩn hóa. -
incrementcó thể là số dương hoặc âm tùy loại dữ liệu.
Phân biệt record count và thống kê theo ngày
EzyRating duy trì hai lớp dữ liệu tổng hợp.
Record count
Record count lưu giá trị tích lũy trên toàn bộ thời gian:
page_view / ALL → 152.430
Nó phù hợp cho:
- Các KPI tổng quan.
- Số liệu toàn thời gian.
- Biểu đồ phân phối.
- Truy vấn cần tốc độ cao nhưng không cần chia theo ngày.
Thống kê theo ngày
Bảng thống kê ngày lưu thêm chiều thời gian:
2026-10-02 / page_view / ALL → 1.820 2026-10-03 / page_view / ALL → 2.130 2026-10-04 / page_view / ALL → 1.540
Nó được dùng cho:
- Biểu đồ tăng trưởng.
- Lọc theo khoảng ngày.
- So sánh với kỳ trước.
- Tính tỷ lệ chuyển đổi theo ngày.
Trong một lần xử lý, appender có thể cập nhật cả hai lớp. Tuy nhiên, một số chỉ số chỉ có ý nghĩa theo ngày và không được đưa vào record count toàn thời gian. Số người hoạt động hằng ngày là một ví dụ: cộng dồn chỉ số này qua nhiều ngày không tạo thành số khách duy nhất toàn thời gian.
Xác định thời điểm thống kê
Ngày thống kê được lấy theo thời điểm xảy ra hành động, không nhất thiết là thời điểm appender đang chạy.
Thứ tự ưu tiên là:
- Thời điểm nghiệp vụ của hành động.
- Thời điểm bản ghi được tạo.
- Thời gian hiện tại nếu cả hai giá trị trên đều không có.
Nhờ đó, một hành động phát sinh hôm qua nhưng được appender xử lý hôm nay vẫn được cộng vào đúng ngày hôm qua.
Tổng hợp khách duy nhất
Đếm khách duy nhất không thể thực hiện bằng cách tăng bộ đếm cho mọi hành động. Một người có thể tạo ra nhiều lượt xem, lượt nhấp và lượt cuộn trong cùng ngày.
EzyRating nhận diện khách theo hai loại:
-
USER: ID của tài khoản đã đăng nhập. -
IP: địa chỉ IP đã được chuẩn hóa.
Trước khi tăng số khách duy nhất, appender kiểm tra định danh đó đã từng xuất hiện hay chưa.
Nếu chưa có:
unique_visitor / USER +1
hoặc:
unique_visitor / IP +1
Để tính khách hoạt động theo ngày, hệ thống kiểm tra thêm tổ hợp:
ngày + loại khách + giá trị định danh
Một người tạo 20 hành động trong cùng ngày vẫn chỉ đóng góp một đơn vị vào số người hoạt động của ngày đó.
Trong phạm vi một lô, appender còn giữ tập hợp các định danh đã xử lý trong bộ nhớ. Điều này tránh phải truy vấn lặp lại khi nhiều hành động của cùng một khách xuất hiện trong một lô.
Chuẩn hóa địa chỉ IP
Dữ liệu IP có thể đến từ proxy dưới dạng danh sách:
203.0.113.10, 10.0.0.8
Appender chỉ sử dụng địa chỉ đầu tiên. Giá trị sau đó được:
- Loại bỏ khoảng trắng.
- Kiểm tra độ dài tối đa.
- Chỉ chấp nhận ký tự hợp lệ của IPv4 hoặc IPv6.
- Bỏ qua nếu trống hoặc không hợp lệ.
Việc chuẩn hóa trước khi lưu giúp tránh coi cùng một địa chỉ là nhiều khách khác nhau và ngăn dữ liệu không kiểm soát trở thành khóa thống kê.
Loại bỏ lưu lượng bot
User-Agent của hành động được phân tích trước khi tổng hợp.
Nếu yêu cầu được nhận diện là bot:
- Lượt xem có thể được ghi vào nhóm thiết bị
BOT. - Hành động không được dùng để tính người dùng, thời gian tương tác và các chỉ số hành vi thông thường.
- Bot không được đưa vào lượng khách hoạt động như một người dùng thực.
Nhờ đó, dashboard vẫn có thể cho biết lượng truy cập từ bot nhưng không để lưu lượng tự động làm sai lệch tỷ lệ tương tác và chuyển đổi.
Tổng hợp thời gian tương tác
Thời gian tương tác không được lấy trực tiếp từ thời gian appender xử lý. Hệ thống phân tích hành động hiện tại cùng hành động trước đó của cùng phiên hoặc cùng đối tượng.
Kết quả được tăng vào chỉ số thời gian:
action_time / <loại hành động> + thời gian tính bằng mili giây
Từ tổng thời gian và số lượt xem, dashboard tính được thời gian trung bình trên trang:
Thời gian trung bình
= Tổng thời gian hành động
/ Tổng lượt xem
Một lượt xem được đánh dấu là có tương tác khi thỏa mãn điều kiện tương tác của hệ thống. Khi đó, appender tăng:
engaged_view / ALL +1
Tổng hợp độ sâu cuộn trang
Khi metadata của hành động chứa phần trăm cuộn hợp lệ, appender cập nhật hai bộ đếm:
scroll_depth_sum / ALL + phần trăm cuộn scroll_depth_sample_count / ALL + 1
Ví dụ, ba mẫu có độ sâu
20, 60 và 100 tạo ra:scroll_depth_sum = 180 scroll_depth_sample_count = 3
Độ sâu cuộn trung bình được tính:
180 / 3 = 60%
Thiết kế dùng hai bộ đếm cho phép tính trung bình mà không phải lưu từng mẫu trong bảng tổng hợp.
Phân tích thiết bị và nguồn truy cập
Với lượt xem trang hợp lệ, appender có thể tạo các nhóm record count sau:
- Loại thiết bị.
- Hệ điều hành.
- Tên trình duyệt.
- Phiên bản trình duyệt.
- Trình duyệt bên trong ứng dụng.
- Tên miền.
- Nguồn truy cập.
- Kênh truy cập.
- Website giới thiệu.
Referrer được phân tích thành các kênh tổng quát:
- Truy cập trực tiếp.
- Liên kết nội bộ.
- Công cụ tìm kiếm.
- Mạng xã hội.
- Website giới thiệu.
Các giá trị rỗng không được tạo thành một dòng thống kê riêng. Các giá trị hợp lệ được chuẩn hóa trước khi làm
record_type.
Giới hạn số nhóm khác nhau
Một số chiều dữ liệu có thể tạo ra số lượng khóa không giới hạn, đặc biệt là:
- Phiên bản trình duyệt.
- Website giới thiệu.
- Tên nguồn truy cập.
- Tên miền.
Dữ liệu này có thể bị làm nhiễu bởi User-Agent giả, referrer rác hoặc chuỗi được tạo ngẫu nhiên. Nếu mỗi giá trị trở thành một
record_type, bảng record count sẽ tăng kích thước không kiểm soát.
EzyRating giới hạn tối đa 500 loại khác nhau cho những chỉ số có rủi ro này.
Khi giới hạn đã đạt:
Giá trị đã biết → giữ nguyên nhóm Giá trị mới hiếm gặp → gộp vào nhóm OTHER
Ví dụ:
browser_version / Chrome 140 → 8.500 browser_version / Firefox 142 → 1.200 browser_version / OTHER → 315
Số liệu không bị mất; chỉ số của các giá trị mới được cộng vào nhóm
OTHER.Appender cũng giữ bộ nhớ đệm các loại đã biết để giảm số truy vấn kiểm tra sự tồn tại trong cơ sở dữ liệu.
Đánh dấu bản ghi đã xử lý
Sau khi một lô hành động được tổng hợp thành công, appender đánh dấu từng bản ghi nguồn là đã xử lý thống kê.
Cơ chế này bổ sung một lớp bảo vệ ngoài page token:
- Page token giúp đọc tiếp từ ID gần nhất.
- Dấu đã xử lý ngăn cùng một bản ghi bị cộng lại nếu token được nạp lại.
- Bản ghi chưa xử lý vẫn có thể được tìm thấy khi appender tiếp tục chạy.
Thứ tự xử lý rất quan trọng:
Tổng hợp và lưu chỉ số
↓
Đánh dấu bản ghi đã xử lý
↓
Lưu page token mới
Nếu việc tổng hợp thất bại, token không nên được tiến lên. Nếu token được cập nhật trước khi lưu thống kê thành công, một phần dữ liệu có thể bị bỏ qua vĩnh viễn.
Tạo một appender tổng hợp mới
Một appender mới cần triển khai tối thiểu năm phần.
Đọc dữ liệu theo token
@Override protected List<Event> getValueList(Long pageToken) { return eventRepository.findByIdGreaterThan( pageToken, 100 ); }
Dữ liệu phải được sắp xếp theo cùng trường dùng làm token.
Chuyển dữ liệu nguồn thành dữ liệu xử lý
Nếu bản ghi nguồn có thể dùng trực tiếp:
@Override protected Event toDataRecord(Event value) { return value; }
Nếu một bản ghi cần tạo ra nhiều dữ liệu trung gian, có thể triển khai hàm chuyển đổi trả về một tập hợp.
Gom và lưu số liệu
@Override protected void addDataRecords(List<Event> records) { Map<StatisticKey, Long> increments = new HashMap<>(); for (Event event : records) { StatisticKey key = new StatisticKey( resolveStatisticName(event), normalizeRecordType(event) ); increments.merge( key, resolveIncrement(event), Long::sum ); } increments.forEach((key, value) -> recordCountService.increment( key.getStatisticName(), key.getRecordType(), value ) ); }
Trong triển khai thực tế, nên gom thêm theo ngày nếu chỉ số cần xuất hiện trên biểu đồ tăng trưởng.
Trả về token mới
@Override protected Long extractNewLastPageToken( List<Event> values, Long currentToken ) { return values.isEmpty() ? currentToken : values.get(values.size() - 1).getId(); }
Khai báo định danh ổn định
@Override protected String getAppenderNamePrefix() { return "my_statistic"; } @Override protected Long defaultPageToken() { return 0L; } @Override protected Class<Long> pageTokenType() { return Long.class; }
Tên appender được dùng để tạo khóa trạng thái và khóa page token. Không nên đổi tên sau khi appender đã chạy trong môi trường thật, vì appender sẽ không tìm thấy checkpoint cũ.
Khởi động appender trong trang quản trị
Trong phần cài đặt EzyRating, mỗi appender có các thao tác điều khiển riêng.
Start
Start bật appender và lưu trạng thái hoạt động. Sau khi bật, appender bắt đầu từ page token gần nhất.Nếu appender chưa từng chạy, token mặc định thường là
0, vì vậy nó sẽ xử lý dữ liệu từ đầu.Stop
Stop dừng việc đọc các lô mới nhưng không xóa page token.Dữ liệu đã tổng hợp vẫn được giữ nguyên. Khi được bật lại, appender tiếp tục từ vị trí cũ.
Restart
Restart thực hiện dừng rồi bật lại appender. Thao tác này không đưa page token về ban đầu và không xây dựng lại dữ liệu.Restart phù hợp khi cần nạp lại trạng thái chạy mà vẫn muốn tiếp tục từ checkpoint hiện tại.
Reload
Reload xóa page token đang lưu trong bộ nhớ và phần thiết lập. Lần chạy sau, appender quay về token mặc định.Đây là thao tác cần đặc biệt thận trọng. Với appender không có dấu đã xử lý, reload có thể khiến dữ liệu cũ được cộng thêm lần nữa.
Reload không đồng nghĩa với việc:
- Xóa record count cũ.
- Xóa thống kê theo ngày.
- Xóa dấu đã xử lý.
- Tự động xây dựng lại dữ liệu một cách an toàn.
Chức năng này chỉ nên được bật cho quản trị viên khi hệ thống có quy trình tái tổng hợp rõ ràng.
Chu kỳ chạy
Các appender được bộ lập lịch nền gọi tuần tự theo độ ưu tiên. Một vòng chạy hướng tới chu kỳ khoảng 1,5 giây, nhưng thời gian thực tế phụ thuộc vào tổng thời gian xử lý của tất cả appender.
Mỗi appender chỉ thực hiện công việc khi:
- Nó được phép chạy trên máy chủ hiện tại.
- Trạng thái của nó đang là
started.
Trong cấu hình nhiều máy chủ, appender thường chỉ được phép chạy trên máy chủ chính. Điều này tránh nhiều máy cùng đọc một nguồn và cộng trùng chỉ số.
Theo dõi appender sau khi bật
Sau khi khởi động, nên kiểm tra theo thứ tự:
- Page token có tăng dần hay không.
- Bảng lịch sử còn bản ghi chưa xử lý hay không.
- Các dòng record count có được tạo đúng
data_namevàrecord_typekhông. -
record_countcó tăng tương ứng với dữ liệu đầu vào không. - Thống kê theo ngày có được ghi đúng ngày phát sinh không.
- Log có lỗi phân tích dữ liệu hoặc cập nhật cơ sở dữ liệu không.
- Dashboard có hiển thị các số tổng hợp vừa tạo không.
Có thể dùng truy vấn kiểm tra dạng:
SELECT data_name, record_type, record_count FROM ezy_data_record_counts WHERE data_name LIKE 'ezyrating_statistic_%' ORDER BY data_name, record_count DESC;
Khi kiểm tra khách duy nhất, không nên so sánh trực tiếp số dòng lịch sử với record count vì nhiều hành động có thể thuộc cùng một người hoặc cùng một IP.
Tái tổng hợp dữ liệu
Khi cần xây dựng lại một loại thống kê, không nên chỉ xóa page token rồi chạy lại. Quy trình an toàn cần xử lý đồng bộ ba nhóm trạng thái:
- Dừng appender liên quan.
- Xóa đúng các dòng tổng hợp thuộc chỉ số cần xây dựng lại.
- Xóa thống kê theo ngày tương ứng nếu có.
- Xóa dấu đã xử lý của nguồn dữ liệu nếu appender sử dụng cơ chế này.
- Đưa page token về giá trị ban đầu.
- Khởi động lại appender.
- Theo dõi cho tới khi token bắt kịp dữ liệu mới nhất.
Không nên xóa toàn bộ bảng record count vì bảng này có thể chứa số liệu của nhiều module khác nhau.
Việc đặt lại token nhưng giữ nguyên record count sẽ làm số liệu bị cộng trùng. Ngược lại, xóa record count nhưng giữ dấu đã xử lý có thể khiến appender không tìm thấy dữ liệu để xây dựng lại.
Xử lý các thay đổi trạng thái
Không phải mọi thống kê đều chỉ tăng.
Ví dụ, một đánh giá có thể:
- Được tạo:
+1. - Bị xóa:
-1. - Được cập nhật nội dung nhưng vẫn hoạt động:
0.
Tương tự, trạng thái thanh toán có thể chuyển từ chưa thanh toán sang đã thanh toán hoặc bị hoàn tác.
Với dữ liệu có vòng đời, appender cần so sánh trạng thái hiện tại với trạng thái trước đó:
long change; if (!previousActive && currentActive) { change = 1; } else if (previousActive && !currentActive) { change = -1; } else { change = 0; }
Nếu chỉ tăng mỗi khi gặp bản ghi lịch sử, số liệu sẽ sai khi đối tượng bị xóa, hủy hoặc chuyển trạng thái nhiều lần.
Các nguyên tắc khi phát triển appender
Một appender tổng hợp ổn định nên tuân theo các nguyên tắc sau:
- Nguồn dữ liệu phải có thứ tự phân trang ổn định.
- Token phải được cập nhật sau khi lưu dữ liệu thành công.
- Phép tăng record count phải mang tính nguyên tử.
- Dữ liệu nên được gom theo lô trước khi ghi.
- Giá trị dùng làm
record_typephải được chuẩn hóa. - Giá trị có số lượng biến thể không giới hạn phải có nhóm
OTHER. - Phải phân biệt thống kê toàn thời gian và thống kê theo ngày.
- Các đối tượng có trạng thái phải được tính theo mức thay đổi, không chỉ theo số sự kiện.
- Việc chạy lại phải có chiến lược chống cộng trùng.
- Không đổi tên appender hoặc quy ước khóa thống kê tùy tiện sau khi triển khai.
Kết luận
Appender là cầu nối giữa dữ liệu lịch sử chi tiết và các chỉ số tổng hợp của EzyRating. Nó giúp hệ thống xử lý dữ liệu tăng dần theo từng lô, tiếp tục được sau khi khởi động lại và phục vụ dashboard mà không cần quét lại hàng triệu hành động cho mỗi truy vấn.
Để sử dụng appender an toàn, cần hiểu rõ ba trạng thái độc lập: dữ liệu đã tổng hợp, dấu bản ghi đã xử lý và page token. Khi ba phần này được quản lý nhất quán, bảng record count sẽ cung cấp các KPI toàn thời gian nhanh, gọn và có thể mở rộng theo lượng dữ liệu của hệ thống.