Tài liệu này mô tả phương án lấy tệp khách hàng từ OCS/Hadoop để sử dụng trong Segment và Campaign của LaoAds V2.
Thiết kế cần đáp ứng các yêu cầu sau:
SegmentVersion bất biến và một TargetSnapshot đã được chốt.Các thành phần sau tiếp tục được sử dụng:
segment và giá trị type = QUERY.segmentType = DYNAMIC và refreshMode = PER_RUN đối với Segment nguồn OCS.segment_version để quản lý từng lần build dữ liệu.segment_version_member để lưu danh sách khách hàng lớn.CampaignRun, TargetSnapshot và ClickHouse campaign_target_member.user_basic_info bằng JdbcTemplate trong LaoAds.CustomerQueryBuilder hiện tại cho OCS.SELECT * và không nối trực tiếp giá trị người dùng vào SQL.user_lao_id_field_config, UserFieldController và các lớp LaoID liên quan chỉ được xóa sau khi rà soát chắc chắn không còn module khác sử dụng. Nếu vẫn có luồng cũ phụ thuộc, đánh dấu TODO và tách việc loại bỏ sang task riêng.
Luồng OCS kế thừa cách dựng form điều kiện của user_lao_id_field_config, nhưng không dùng chung collection. OCS có nhiều bảng và cần thêm metadata về quyền select, quyền filter, operator cùng cách ánh xạ kết quả nên phải có model riêng.
ocs_dataset_config
Danh mục bảng OCS mà LaoAds cho phép hiển thị trên FE.
ocs_dataset_field_config
Danh mục cột của từng bảng, phát triển từ pattern user_lao_id_field_config.
queryDefinition trong segment
Định nghĩa bảng, cột và điều kiện lọc mà người dùng đã chọn.
segment_build_job
Theo dõi tiến độ build SegmentVersion, retry và quyền xử lý giữa nhiều worker.
OCS Audience Service
Kiểm tra query definition, đọc Hadoop và stream kết quả về Segment Import Worker.
| Hệ thống | Trách nhiệm |
|---|---|
| Frontend | Hiển thị danh sách bảng/cột do LaoAds cung cấp, tạo điều kiện lọc và gửi cấu hình Segment. |
| LaoAds API | Quản lý danh mục OCS, validate request, gọi OCS verify, lưu Segment và cung cấp API cho FE. |
| MongoDB | Lưu metadata, trạng thái SegmentVersion, CampaignRun và build job; không lưu hàng triệu member. |
| Segment Import Worker | Gọi stream OCS, normalize dữ liệu, insert ClickHouse theo batch và quản lý retry. |
| OCS Audience Service | Kiểm tra bảng/cột thực tế, tạo câu SQL an toàn, bind parameter và stream ResultSet. |
| Hadoop | Nguồn dữ liệu OCS, chỉ cho tài khoản service đọc. |
| ClickHouse | Lưu member dung lượng lớn và phục vụ xem, tạo target, thống kê. |
GET /ocs-datasets
GET /ocs-datasets/{datasetCode}/columns
POST /segments
PUT /segments/{segmentId}
Hai API /ocs-datasets đọc dữ liệu từ MongoDB của LaoAds, không gọi sang OCS theo từng request của FE.
Cách dựng giao diện giữ cùng nguyên tắc với luồng LaoID cũ:
user_lao_id_field_config cũ trả metadata field để FE dựng điều kiện.ocs_dataset_field_config của bảng đã chọn.dataType để chọn loại input và dựa vào allowedOperators để hiển thị toán tử.fieldCode; backend mới resolve sang columnName vật lý.Khác biệt là LaoID cũ chỉ có một bảng user_basic_info, còn OCS có nhiều bảng. Vì vậy FE phải chọn datasetCode trước rồi mới tải field của dataset đó.
Response field cho FE tham khảo:
[
{
"fieldCode": "DATA_USAGE_MB",
"label": "Dung lượng Data đã sử dụng",
"dataType": "NUMBER",
"selectable": true,
"filterable": true,
"targetField": "ATTRIBUTE",
"allowedOperators": [
"EQUALS",
"GREATER_THAN",
"GREATER_THAN_OR_EQUALS",
"LESS_THAN",
"BETWEEN"
]
}
]
Backend chọn label theo X-Accept-Language giống API /user-field hiện tại; FE không phải tự chọn giữa labelVi, labelLo, labelEn và labelZh.
Request tham khảo:
{
"name": "Thuê bao có nhu cầu nâng cấp gói Data",
"description": "Tệp chạy chiến dịch nâng cấp Data",
"type": "QUERY",
"segmentType": "DYNAMIC",
"queryDefinition": {
"datasetCode": "SUBSCRIBER_DAILY",
"selectedFields": [
"MSISDN",
"FIRST_NAME",
"PACKAGE_CODE"
],
"conditions": [
{
"fieldCode": "SNAPSHOT_DATE",
"operator": "EQUALS",
"valueSource": "RUN_DATE",
"dayOffset": -1
},
{
"fieldCode": "DATA_USAGE_MB",
"operator": "GREATER_THAN_OR_EQUALS",
"valueSource": "FIXED",
"value": 5120,
"relation": "AND"
}
]
}
}
Quy tắc:
datasetCode và fieldCode, không gửi tên bảng/cột vật lý tùy ý.schemaName, tableName từ ocs_dataset_config và lấy columnName từ ocs_dataset_field_config.selectedFields phải chứa ít nhất một trường được ánh xạ thành PHONE hoặc EMAIL.operator phải thuộc danh sách allowedOperators của field.valueSource = FIXED dùng giá trị cố định do người dùng nhập.valueSource = RUN_DATE được resolve theo ngày chạy thực tế của CampaignRun.Màn chi tiết hiển thị:
SegmentVersion đã build.latestReadyVersionId; người dùng có thể chọn version cũ để đối soát.Khi Segment mới được tạo nhưng chưa chạy Campaign, danh sách version có thể rỗng. Đây là trạng thái hợp lệ vì Segment QUERY chỉ lưu định nghĩa; dữ liệu được build khi Campaign chạy.
ocs_dataset_configMỗi document đại diện cho một bảng OCS mà LaoAds cho phép người dùng chọn.
{
"_id": "subscriber-daily",
"datasetCode": "SUBSCRIBER_DAILY",
"displayName": "Thông tin thuê bao theo ngày",
"description": "Dữ liệu thuê bao được chốt theo ngày",
"schemaName": "ocs",
"tableName": "subscriber_daily",
"status": "ACTIVE",
"requiredFilterFieldCodes": [
"SNAPSHOT_DATE"
],
"maxQueryRangeDays": 31,
"createdAt": "2026-07-29T00:00:00Z",
"updatedAt": "2026-07-29T00:00:00Z"
}
Các field thuộc bảng không nhúng vào document này. Chúng được lưu riêng trong ocs_dataset_field_config để giữ cách quản lý gần giống user_lao_id_field_config và tránh document bảng tăng kích thước khi số cột nhiều.
ocs_dataset_field_configMỗi document đại diện cho một cột được phép hiển thị hoặc dùng làm điều kiện của một dataset.
{
"_id": "subscriber-daily-msisdn",
"datasetCode": "SUBSCRIBER_DAILY",
"fieldCode": "MSISDN",
"columnName": "isdn",
"label": "Phone number",
"labelEn": "Phone number",
"labelVi": "Số thuê bao",
"labelLo": "ເບີໂທ",
"labelZh": "电话号码",
"dataType": "STRING",
"selectable": true,
"filterable": true,
"targetField": "PHONE",
"allowedOperators": [
"EQUALS",
"IN"
],
"status": "ACTIVE",
"sortOrder": 1,
"createdAt": "2026-07-29T00:00:00Z",
"updatedAt": "2026-07-29T00:00:00Z"
}
Ví dụ field chỉ dùng để lọc theo ngày:
{
"datasetCode": "SUBSCRIBER_DAILY",
"fieldCode": "SNAPSHOT_DATE",
"columnName": "snapshot_date",
"labelEn": "Snapshot date",
"labelVi": "Ngày dữ liệu",
"labelLo": "ວັນທີຂໍ້ມູນ",
"labelZh": "数据日期",
"dataType": "DATE",
"selectable": false,
"filterable": true,
"targetField": "ATTRIBUTE",
"allowedOperators": [
"EQUALS",
"BETWEEN"
],
"status": "ACTIVE",
"sortOrder": 2
}
user_lao_id_field_configuser_lao_id_field_config cũ |
ocs_dataset_field_config mới |
Ghi chú |
|---|---|---|
key |
fieldCode |
Mã field FE gửi về. |
table |
Quan hệ qua datasetCode |
OCS phải chọn dataset trước vì có nhiều bảng. |
fieldName hoặc key |
columnName |
Tên cột vật lý, không trả cho FE nếu không cần. |
label, labelEn, labelVi, labelLo, labelZh |
Giữ nguyên | Tiếp tục hỗ trợ nhãn theo ngôn ngữ hiện tại. |
dataType |
Giữ nguyên nhưng chuyển thành enum | FE dùng để dựng đúng input. |
| Chưa có | selectable |
Xác định cột có được lấy ra làm dữ liệu người nhận không. |
| Chưa có | filterable |
Xác định cột có được viết điều kiện không. |
| Chưa có | allowedOperators |
Không cho FE tự gửi operator bất kỳ. |
| Chưa có | targetField |
Ánh xạ về phone, email, tên hoặc attribute. |
| Chưa có | status, sortOrder |
Ẩn field và sắp xếp giao diện mà không xóa dữ liệu. |
Không tái sử dụng trực tiếp UserLaoIdFieldConfigModel vì model đó có key unique toàn hệ thống. Trong OCS, hai bảng khác nhau có thể cùng có field MSISDN, nên uniqueness phải nằm trong phạm vi datasetCode.
| Trường | Ý nghĩa |
|---|---|
datasetCode |
Mã ổn định FE và Segment sử dụng; không phụ thuộc tên bảng hiển thị. |
schemaName, tableName |
Bảng vật lý trong Hadoop. Chỉ backend sử dụng. |
status |
ACTIVE cho phép tạo/sửa Segment; INACTIVE ẩn khỏi lựa chọn mới. |
requiredFilterFieldCodes |
Các điều kiện bắt buộc để tránh query quét toàn bộ Hadoop. |
maxQueryRangeDays |
Khoảng ngày tối đa được phép query nếu dataset có filter theo thời gian. |
fieldCode |
Mã field ổn định FE gửi về. |
columnName |
Tên cột vật lý trong Hadoop. |
dataType |
STRING, NUMBER, DATE, DATETIME, BOOLEAN. |
selectable |
Field có được đưa vào kết quả hay không. |
filterable |
Field có được dùng làm điều kiện lọc hay không. |
targetField |
Ánh xạ kết quả về PHONE, EMAIL, FIRST_NAME, LAST_NAME hoặc ATTRIBUTE. |
allowedOperators |
Danh sách toán tử FE được phép hiển thị và backend chấp nhận. |
labelEn, labelVi, labelLo, labelZh |
Nhãn field theo ngôn ngữ hiện tại của người dùng. |
sortOrder |
Thứ tự field trên giao diện. |
ocs_dataset_config:
(datasetCode) unique
(schemaName, tableName) unique
(status, displayName)
ocs_dataset_field_config:
(datasetCode, fieldCode) unique
(datasetCode, columnName) unique
(datasetCode, status, sortOrder)
API lấy field luôn có datasetCode; các index bắt đầu bằng datasetCode để query đúng phạm vi bảng và không quét toàn collection.
Hai API catalog của OCS chỉ dành cho kỹ sư kiểm tra bằng Swagger, curl hoặc Postman. LaoAds không gọi hai API này trong luồng tạo Segment hoặc chạy Campaign.
segmentqueryDefinition{
"type": "QUERY",
"segmentType": "DYNAMIC",
"refreshMode": "PER_RUN",
"queryDefinition": {
"datasetCode": "SUBSCRIBER_DAILY",
"schemaName": "ocs",
"tableName": "subscriber_daily",
"selectedColumns": [
{
"fieldCode": "MSISDN",
"columnName": "isdn",
"dataType": "STRING",
"targetField": "PHONE"
}
],
"conditions": [],
"definitionChecksum": "sha256...",
"verifiedAt": "2026-07-29T10:00:00Z"
}
}
LaoAds lưu cả mã logic và tên vật lý đã được resolve tại thời điểm lưu Segment. Vì vậy việc sửa ocs_dataset_config hoặc ocs_dataset_field_config không âm thầm làm thay đổi một Segment đang hoạt động.
Nếu schema OCS thay đổi:
ocs_dataset_config và ocs_dataset_field_config.definitionChecksum mới.CampaignRun đã tạo trước đó vẫn giữ source snapshot cũ; Run mới dùng định nghĩa mới.SegmentSourceSnapshotModelKhi build một version, phải snapshot đầy đủ:
type
queryDefinition
resolvedConditions
sourceDataAsOf
definitionChecksum
resolvedConditions là điều kiện sau khi các giá trị như RUN_DATE đã được chuyển thành giá trị cụ thể. Snapshot này không được sửa sau khi SegmentVersion được tạo.
segment_versionGiữ collection hiện tại và bổ sung thông tin nguồn cần thiết:
companyId
segmentId
versionNo
sourceType = QUERY
sourceDefinitionSnapshot
status = BUILDING | READY | FAILED | ARCHIVED
totalMemberCount
validMemberCount
invalidMemberCount
builtAt
completedAt
errorSummary
Version READY là bất biến. Sửa Segment không cập nhật member hoặc source snapshot của version cũ.
GET /internal/v1/catalog/tables
GET /internal/v1/catalog/tables/{schemaName}/{tableName}/columns
API chỉ trả các bảng/cột mà JDBC service account nhìn thấy. API phải được bảo vệ bằng mạng nội bộ và xác thực dành cho kỹ sư vận hành.
POST /internal/v1/audiences/verify
Request tham khảo:
{
"requestId": "verify-uuid",
"schemaName": "ocs",
"tableName": "subscriber_daily",
"selectedColumns": [
{
"columnName": "isdn",
"dataType": "STRING",
"targetField": "PHONE"
}
],
"conditions": [
{
"columnName": "snapshot_date",
"dataType": "DATE",
"operator": "EQUALS",
"value": "2026-07-28"
}
]
}
OCS Service kiểm tra:
SELECT; không có DDL, DML, raw expression, subquery hoặc SQL do FE truyền lên.EXPLAIN hoặc query giới hạn 0/1 tùy khả năng của Hive, Impala hoặc Trino; không chạy full query khi verify.Response tham khảo:
{
"success": true,
"data": {
"valid": true,
"queryChecksum": "sha256...",
"warnings": []
}
}
POST /internal/v1/audiences/stream
Content-Type: application/json
Accept: application/x-ndjson
Request chứa:
requestId
companyId
segmentId
segmentVersionId
importAttemptId
schemaName
tableName
selectedColumns
resolvedConditions
queryChecksum
OCS Service không tin queryChecksum thay cho validation. Service vẫn kiểm tra request và compile câu query từ cấu trúc đã nhận trước khi thực thi.
Mỗi dòng là một JSON độc lập:
{"recordType":"START","requestId":"...","startedAt":"..."}
{"recordType":"DATA","phone":"85620...","email":null,"firstName":"Somchai","lastName":null,"attributes":{"packageCode":"PKG01"}}
{"recordType":"DATA","phone":"85620...","email":"user@example.com","firstName":null,"lastName":null,"attributes":{}}
{"recordType":"END","requestId":"...","rowCount":250000,"completedAt":"..."}
Nếu lỗi xảy ra trước khi response bắt đầu, OCS trả HTTP status và error body chuẩn. Nếu lỗi xảy ra sau khi stream đã bắt đầu, OCS ghi một dòng ERROR cuối cùng rồi đóng stream:
{"recordType":"ERROR","errorCode":"OCS_STREAM0004","message":"Hadoop query interrupted"}
Worker chỉ chuyển version sang READY khi nhận đúng END và đối soát được số dòng. Mất kết nối hoặc nhận ERROR luôn được coi là attempt thất bại.
OCS Service phải:
fetchSize phù hợp driver, ví dụ 1.000-5.000 dòng.resultSet thành List.Segment Import Worker phải:
Khi sửa Segment:
SegmentVersion đã có.CampaignRun đã chốt source snapshot không bị ảnh hưởng.buildKey và trường hợp nhiều Campaign dùng chung SegmentbuildKey được tạo từ:
companyId
segmentId
definitionChecksum
resolvedConditions
sourceDataAsOf
Sau đó canonicalize dữ liệu và băm SHA-256.
Nếu nhiều CampaignRun cùng sử dụng đúng một Segment, cùng điều kiện đã resolve và cùng thời điểm dữ liệu:
SegmentVersion và build job.buildKey đó.READY, từng Run tạo TargetSnapshot riêng.Nếu ngày dữ liệu, điều kiện hoặc định nghĩa Segment khác nhau thì buildKey khác và phải build version mới.
segment_build_jobid
companyId
segmentId
segmentVersionId
buildKey
status: QUEUED | RUNNING | RETRY_WAIT | COMPLETED | FAILED
attemptNo
currentAttemptId
leaseOwner
leaseUntil
lastHeartbeatAt
processedCount
validCount
invalidCount
nextRetryAt
errorCode
errorSummary
createdAt
updatedAt
Index bắt buộc:
(buildKey) unique
(status, nextRetryAt)
(status, leaseUntil)
(segmentId, createdAt desc)
leaseOwner và leaseUntil đảm bảo nhiều worker không xử lý đồng thời cùng một job. Worker phải gia hạn lease trong khi stream; node khác chỉ được claim khi lease hết hạn.
segment_version_memberGiữ schema hiện tại và bổ sung:
import_attempt_id String
source_data_as_of Nullable(DateTime64(3))
import_attempt_id giúp phân biệt dữ liệu của từng lần thử. Khi retry, dữ liệu của attempt thất bại không được tính vào version hoàn chỉnh.
Phương án đọc an toàn:
segment_version lưu successfulAttemptId khi build thành công.import_attempt_id = successfulAttemptId.Quy tắc:
importAttemptId mới.FAILED; CampaignRun chuyển FAILED với mã lỗi rõ ràng.READY hoặc TargetSnapshot chưa được chốt.Các task dưới đây đủ lớn để giao độc lập nhưng vẫn có hợp đồng rõ giữa các nhóm. Không tách riêng task kiểm thử; mỗi task phải có unit test, integration test và log trong phạm vi của task đó.
Mục tiêu
Tạo JAR độc lập triển khai trong OCS, có khả năng kiểm tra metadata, verify cấu hình audience và stream dữ liệu Hadoop mà không giữ toàn bộ kết quả trong RAM.
Phạm vi
SELECT, dùng PreparedStatement cho toàn bộ value.fetchSize, timeout và cancel khi client ngắt.phone, email, firstName, lastName, attributes.API bàn giao
GET /internal/v1/catalog/tables
GET /internal/v1/catalog/tables/{schemaName}/{tableName}/columns
POST /internal/v1/audiences/verify
POST /internal/v1/audiences/stream
GET /actuator/health
GET /actuator/prometheus
Các trường hợp phải xử lý
| Trường hợp | Xử lý |
|---|---|
| Table hoặc column không tồn tại | Trả error code, không mở stream. |
| Kiểu value không khớp column | Trả field error tương ứng. |
| Operator không hỗ trợ | Từ chối request. |
| Query timeout | Cancel statement, đóng stream, ghi log thời gian và requestId. |
| Client ngắt kết nối | Dừng đọc ResultSet và giải phóng connection. |
| Hadoop trả hàng triệu dòng | Stream liên tục, bộ nhớ không tăng theo tổng số dòng. |
| JDBC pool hết connection | Fail fast với mã lỗi quá tải, không chờ vô hạn. |
Kết quả bàn giao
fetchSize và timeout với team OCS.Mục tiêu
Tạo nguồn cấu hình duy nhất trong LaoAds để FE chỉ nhìn thấy các bảng, cột và operator được phép sử dụng.
Phạm vi
ocs_dataset_config và ocs_dataset_field_config.user_lao_id_field_config, nhưng dùng DTO/model riêng cho OCS.ACTIVE và danh sách column.datasetCode, trùng table; field phải unique theo (datasetCode, fieldCode) và (datasetCode, columnName).PHONE hoặc EMAIL nếu dataset dùng làm audience gửi tin.MongoDB thay đổi
New: ocs_dataset_config
New: ocs_dataset_field_config
API bàn giao
GET /ocs-datasets
GET /ocs-datasets/{datasetCode}/columns
POST /admin/ocs-datasets/search
POST /admin/ocs-datasets
PUT /admin/ocs-datasets/{datasetCode}
POST /admin/ocs-datasets/{datasetCode}/activate
POST /admin/ocs-datasets/{datasetCode}/inactivate
POST /admin/ocs-datasets/{datasetCode}/fields
PUT /admin/ocs-datasets/{datasetCode}/fields/{fieldCode}
POST /admin/ocs-datasets/{datasetCode}/fields/{fieldCode}/activate
POST /admin/ocs-datasets/{datasetCode}/fields/{fieldCode}/inactivate
Luồng cập nhật schema
Kết quả bàn giao
Mục tiêu
Thay luồng QUERY hiện tại bằng định nghĩa OCS có cấu trúc, verify trước khi lưu và giữ nguyên kiến trúc Segment/SegmentVersion.
Phạm vi
queryDefinition vào SegmentModel, SegmentSourceSnapshotModel, DTO và response.ocs_dataset_config và ocs_dataset_field_config.fieldCode sang column vật lý ở backend; không tin schema/table/column từ FE.POST /internal/v1/audiences/verify khi tạo hoặc sửa Segment.definitionChecksum và verifiedAt sau verify thành công.segmentType = DYNAMIC, refreshMode = PER_RUN.SegmentVersion ngay.CustomerQueryBuilder -> JdbcTemplate -> user_basic_info khỏi Segment QUERY.Code hiện tại bị ảnh hưởng
SegmentModel
SegmentSourceSnapshotModel
CreateSegmentRequest / Update Segment request
SegmentDto
SegmentService
CampaignService validation
CustomerQueryBuilder
UserFieldController và user_lao_id_field_config sau khi kiểm tra dependency
Các trường hợp phải xử lý
| Trường hợp | Xử lý |
|---|---|
Dataset INACTIVE |
Không cho tạo/sửa Segment mới. |
| Field không thuộc dataset | Trả lỗi đúng field. |
| Field không selectable nhưng nằm trong selectedFields | Từ chối. |
| Field không filterable nhưng được dùng làm condition | Từ chối. |
| Thiếu required filter | Từ chối để tránh full scan. |
| Khoảng ngày vượt giới hạn | Từ chối và trả giới hạn cho FE. |
| Không có PHONE/EMAIL | Từ chối vì không tạo được recipient. |
| OCS verify timeout | Không lưu cấu hình mới; Segment cũ không bị thay đổi. |
| Sửa Segment sau khi đã có Run | Run cũ giữ snapshot; Run mới dùng định nghĩa mới. |
| Segment QUERY test cũ | Chỉnh sửa và verify lại hoặc xóa rồi tạo mới; không tự diễn giải điều kiện LaoID thành OCS. |
Kết quả bàn giao
Mục tiêu
Cho người dùng tạo Segment OCS bằng các điều khiển rõ ràng mà không cần biết SQL hoặc tên cột vật lý.
Phạm vi
dataType: text, number, date, date range hoặc multi-select.FIXED và RUN_DATE cho field thời gian được backend cho phép.segment/user/{segmentId} cũ.Các trường hợp phải xử lý
| Trường hợp | Xử lý FE |
|---|---|
| Dataset bị inactivate khi form đang mở | Submit lỗi thì reload catalog và giữ các field còn hợp lệ. |
| Đổi dataset | Xóa selected field và condition của dataset cũ sau khi người dùng xác nhận. |
| Segment chưa có version | Hiển thị “Chưa có lần build dữ liệu”, không coi là lỗi. |
| Version BUILDING | Hiển thị tiến độ và tự refresh có giới hạn. |
| Version FAILED | Hiển thị mã lỗi và thời điểm lỗi; không hiển thị dữ liệu attempt dở. |
Kết quả bàn giao
Mục tiêu
Đọc NDJSON từ OCS, ghi ClickHouse theo batch và đảm bảo một build có thể retry an toàn trong môi trường nhiều node.
Phạm vi
segment_build_job, unique buildKey, lease và heartbeat.RestClient hỗ trợ đọc line-by-line; không deserialize toàn bộ body.START, DATA, END, ERROR.recipientKey, validate member.importAttemptId cho từng attempt.READY sau khi nhận END, flush hết batch và đối soát count.Dữ liệu thay đổi
New MongoDB: segment_build_job
Alter MongoDB: segment_version.successfulAttemptId
Alter ClickHouse: segment_version_member.import_attempt_id
Alter ClickHouse: segment_version_member.source_data_as_of
Các trường hợp phải xử lý
| Trường hợp | Xử lý |
|---|---|
| Hai worker nhận cùng message | Chỉ worker claim được lease mới chạy. |
| Kafka gửi lại message | Unique buildKey và trạng thái job làm request idempotent. |
| Dòng thiếu cả phone và email | Ghi invalid count; không insert target member. |
| Trùng recipient trong một version | Dedupe theo recipientKey theo chiến lược đã chốt. |
| ClickHouse lỗi giữa batch | Không ack thành công; attempt bị fail và retry với attemptId mới. |
| OCS stream kết thúc không có END | Attempt thất bại. |
| Worker chết | Lease hết hạn để node khác claim và chạy attempt mới. |
| Attempt cũ có dữ liệu trong ClickHouse | Không được đọc vì khác successfulAttemptId; cleanup sau. |
Kết quả bàn giao
buildKey.requestId, buildJobId, segmentVersionId và importAttemptId.Mục tiêu
Đảm bảo mỗi lần Campaign chạy chờ đúng SegmentVersion, chốt đúng TargetSnapshot và không dispatch dữ liệu cũ.
Phạm vi
CampaignRun với unique scheduleOccurrenceKey.PREPARING_TARGET.RUN_DATE theo timezone Campaign/company.buildKey; tạo mới hoặc dùng chung SegmentVersion đang build/đã READY phù hợp.TargetSnapshot và campaign_target_member riêng cho run.READY_TO_DISPATCH rồi mới gửi Kafka dispatch.FAILED với errorCode từ build job.Các trường hợp phải xử lý
| Trường hợp | Xử lý |
|---|---|
| 222 Campaign cùng Segment và cùng sourceDataAsOf | Dùng chung một SegmentVersion, mỗi Run có TargetSnapshot riêng. |
| Cùng Segment nhưng chạy ngày khác | Tạo version khác do buildKey khác. |
| Segment được sửa trong lúc version đang build | Build hiện tại giữ snapshot cũ; run sau dùng định nghĩa mới. |
| Segment bị archive sau khi Run đã PREPARING_TARGET | Run đã snapshot có thể tiếp tục theo chính sách; không tạo Run mới. |
| OCS chậm | Run ở PREPARING_TARGET, hiển thị progress; không dispatch version cũ thay thế. |
| Scheduler chạy trên nhiều node | Unique scheduleOccurrenceKey và buildKey ngăn tạo trùng. |
Kết quả bàn giao
latestReadyVersionId của lần trước.campaignRunId, targetSnapshotId và segmentVersionId.Mục tiêu
Chuẩn hóa cấu hình để hệ thống chạy ổn định giữa hai vùng LaoAds và OCS, đồng thời đủ dữ liệu để phát hiện và truy vết sự cố.
Phạm vi
Kết quả bàn giao
Thứ tự đề xuất:
ocs_dataset_config và ocs_dataset_field_config.Tên code có thể điều chỉnh theo registry hiện tại, nhưng phải tách rõ lỗi cấu hình, lỗi OCS và lỗi build:
OCS_DATASET0001 Dataset không tồn tại
OCS_DATASET0002 Dataset không hoạt động
OCS_DATASET0003 Field không thuộc dataset
OCS_DATASET0004 Field không được phép select
OCS_DATASET0005 Field không được phép filter
OCS_DATASET0006 Operator không hợp lệ
OCS_DATASET0007 Thiếu điều kiện bắt buộc
OCS_DATASET0008 Khoảng dữ liệu vượt giới hạn
OCS_VERIFY0001 OCS verify thất bại
OCS_VERIFY0002 Table hoặc column thực tế không tồn tại
OCS_VERIFY0003 Kiểu dữ liệu không khớp
OCS_VERIFY0004 OCS verify timeout
OCS_STREAM0001 Không thể mở stream
OCS_STREAM0002 Stream kết thúc không đầy đủ
OCS_STREAM0003 Dữ liệu stream không đúng contract
OCS_STREAM0004 Hadoop query bị gián đoạn
SEGMENT_BUILD0001 Build job không tồn tại
SEGMENT_BUILD0002 Không thể claim build job
SEGMENT_BUILD0003 ClickHouse insert thất bại
SEGMENT_BUILD0004 Build vượt số lần retry
SEGMENT_BUILD0005 Count đối soát không khớp
Message trả về lấy theo StatusCodeEnum và file đa ngôn ngữ. Không hard-code message nghiệp vụ trong service nếu error code đã có message tương ứng.
Phương án cuối cùng không dùng Query Template và không dùng GraphQL. Độ linh hoạt được cung cấp bằng danh mục bảng/cột có kiểm soát trong MongoDB và cấu trúc điều kiện cố định.
Ranh giới quan trọng:
datasetCode, fieldCode, operator và value.SegmentVersion READY và TargetSnapshot đã được chốt.Thiết kế này cho phép OCS thay đổi bảng/cột mà không phải sửa giao diện hoặc build lại JAR cho mỗi thay đổi nhỏ, đồng thời vẫn giữ quyền kiểm soát dữ liệu và hiệu suất trong LaoAds.