Access Hub Agent
Đồng bộ từ mã nguồn lúc 15:42, 03/10/2026
Skip to content

L3 - Monitoring Platform - Agent - WAL và Sender (Bộ đệm đĩa, gửi lô, truyền tải) ​

Trạng thái
Bản nháp
Phiên bản
0.1, ngày 30/09/2026

Ghi chú: khi tài liệu và mã khác nhau, mã thắng

Đã làm nghĩa là có mã và kiểm thử trong repo. Chỉ thiết kế nghĩa là có trong tài liệu nhưng chưa có mã. Chỗ lệch được ghi ở mục nợ kỹ thuật.

Thông tin tài liệu đầy đủ
TrườngGiá trị
Tên trangL3 - Monitoring Platform - Agent - WAL và Sender
Trạng tháiBẢN NHÁP (tài liệu chưa sẵn sàng trình thẩm định)
Phiên bảnv0.1, 2026-09-30: bản nháp đầu tiên, dựng từ mã tại commit a23f929 (internal/buffer, internal/sender, internal/backoff, internal/transport, internal/wire, internal/stats, internal/agent/agent.go, internal/cli/run.go) và hợp đồng Agent - Collector ở docs/10-wire-contract.md
Tên dự ánMonitoring Platform (Access Hub Monitoring)
Bên thẩm định / Phê duyệtchưa chỉ định. Chưa ai sign-off
Tài liệu tầng trên (Parent)L2 - Monitoring Platform - Agent - SAD, thành phần CMP-3 WAL Buffer, CMP-4 Sender và Backoff, CMP-5 Transport Client (mục 2.3) và vòng cycle của CMP-1 Runtime Core (L2 D-14). FR-08 đến FR-13, FR-03, L2-NFR-04, 05, 07, 10, 11, 13, 14, 15, 18. Truy vết tiếp lên L1: L1 HLD mục 2 (Mục tiêu 4), mục 3 (T5, T7), mục 4
Tài liệu anh emL3 Collectors, L3 Enroll, Credentials, Config, L3 Service và Packaging
Mục lục0 Governance, 1 Phạm vi, 2 Yêu cầu, 3 Kiến trúc, 4 Domain model, 5 Hợp đồng API, 6 Dữ liệu vật lý, 7 Thuật toán, 8 Xử lý lỗi, 9 Suy thoái, 10 Đồng thời, 11 Bảo mật, 12 Cấu hình, 13 Telemetry, 14 Kiểm thử, 15 Trình tự xây dựng, Phụ lục A, B, C

Quy ước nhãn trạng thái (giống L2): ĐÃ HIỆN THỰC (đã xác minh trong mã), THIẾT KẾ, CHƯA XÂY (chỉ có trong tài liệu thiết kế), MỘT PHẦN, ĐỀ XUẤT. Khi mã và tài liệu khác nhau, mã thắng và sai lệch được nêu trong tài liệu này và gom về L2 mục 16.2. Mọi con số chưa có trong mã hoặc giao thức được gắn nhãn "đề xuất".


0. Front Matter & Approvals ​

Governance metadata

TrườngGiá trị
ComponentCMP-3 WAL Buffer (internal/buffer), CMP-4 Sender và Backoff (internal/sender, internal/backoff), CMP-5 Transport Client (internal/transport, internal/wire), cộng vòng cycle của CMP-1 (internal/agent/agent.go, internal/cli/run.go, internal/stats)
Truy vết L2L2-SAD-agent.md mục 2.3, 3 (FR-03, FR-08 đến FR-13), 4 (L2-NFR-04, 05, 07, 10, 11, 13, 14, 15, 18), 7, 16 (R-07, R-09, R-10, D-08, D-09, D-10, D-14, D-15, D-16)
Phân loại rủi roĐề xuất, chưa xác nhận: Tier 2, cao hơn mức kế thừa của L2 mục 14. Lý do: đây là đường duy nhất đưa số liệu ra khỏi máy, giữ bearer token trong bộ nhớ và là nơi mất số liệu âm thầm (R-07, R-10)
Data classificationNội bộ (số liệu vận hành máy chủ, không PII) cho lô và WAL. Bí mật cho bearer token trong bộ nhớ và trên dây (chỉ giữ ở Sender.Token và header Authorization, không ghi đĩa ở thành phần này)
Blast radiusMột máy chủ. Lỗi nặng nhất: WAL hỏng nhiều đoạn làm mất số liệu đệm của máy đó, hoặc bug hợp đồng làm rơi hàng loạt lô (R-10). Thành phần không ảnh hưởng máy khác. Nhiều agent cùng thử lại có thể tạo đỉnh tải lên Collector, giảm bằng phân pha và full jitter

Sign-off gate

Vai tròTênTrách nhiệm duyệtTrạng tháiNgày
Tech lead thành phầnchưa chỉ địnhĐúng đắn của WAL, backoff, xử lý mã trạng tháiChưa duyệtchưa có
SA hệ thống Agentchưa chỉ địnhNhất quán với L2 và giao thứcChưa duyệtchưa có
Bảo mậtchưa chỉ địnhKênh TLS, xử lý token, quyền tệp WALChưa duyệtchưa có
SA Collectorchưa chỉ địnhNhất quán hợp đồng /metrics, giới hạn tốc độ, cửa sổ thời gian điểmChưa duyệtchưa có

1. Component Scope & Non-Goals ​

Vai trò: cụm này là bộ gửi số liệu theo mẫu hàng đợi bền có xác nhận (durable at-least-once queue) đi kèm bộ gửi thử lại có lùi bước (retrying sender with backoff). Mỗi chu kỳ, mẫu đã chuẩn hóa được đóng thành một lô protobuf, ghi xuống WAL đĩa (có fsync), rồi Sender lấy lô cũ nhất, nén gzip và gửi HTTPS. Lô chỉ bị xóa khỏi WAL khi Collector xác nhận (200 hoặc 202) hoặc khi bị quyết định bỏ theo mã trạng thái. Gửi lặp an toàn vì TSDB khử trùng theo (series, ts) (giao thức, mục idempotency).

Sơ đồ ngữ cảnh

mermaid
flowchart LR
  classDef bc fill:#1f3a5f,stroke:#4a90d9,color:#fff
  classDef owned fill:#2d4a3e,stroke:#5fb37a,color:#fff
  classDef datastore fill:#3a2d4a,stroke:#a06fd9,color:#fff
  classDef infra fill:#444,stroke:#aaa,color:#fff

  COLL(["Collector: /agent/v1"]):::infra
  DISK[("Thư mục state: wal/")]:::datastore

  subgraph BC["WAL, Sender, Transport"]
    RT["cycle: thu, xếp hàng, gửi"]:::owned
    SND["Sender và Backoff"]:::owned
    WAL["WAL Buffer"]:::owned
    TRN["Transport Client"]:::owned
  end

  ENG["Collector Engine"]:::bc
  CFG["Config và Credentials"]:::bc

  ENG -->|"mẫu đã chuẩn hóa"| RT
  RT -->|"đóng lô và gửi"| SND
  SND -->|"ghi và đọc lô"| WAL
  WAL -->|"đoạn tệp có fsync"| DISK
  SND -->|"POST metrics đã nén"| TRN
  TRN -->|"HTTPS protobuf"| COLL
  CFG -.->|"token, URL, tham số"| RT

Mô tả quan hệ

ChiềuBênNội dung
VàoCollector EngineResult.Samples mỗi chu kỳ (L3 Collectors). cycle gọi Collect(now) rồi Sender.Enqueue(now, samples)
VàoConfig và Credentialsagent_id, agent_token, machine_id (từ creds), collector_url, ca_file, proxy_url, insecure_skip_verify, send_timeout, interval, buffer.*, limits.memory_limit (từ config)
VàoTín hiệu hệ điều hànhSIGTERM và SIGINT hủy ngữ cảnh. SIGHUP nạp lại cấu hình cục bộ. Tín hiệu trạng thái ghi log Status() (internal/cli/run.go)
RaThư mục state_dir/walĐoạn %016x.seg và tệp checkpoint. Thư mục quyền 0700, tệp 0600
RaCollectorPOST /agent/v1/metrics (lô nén gzip, application/x-protobuf) và GET /agent/v1/config (chỉ thăm dò lúc 401). Ping và Enroll có trong client, Enroll thuộc L3 Enroll
RaThống kê dùng chung (internal/stats)Ghi SendFailures, DroppedSamples, WALBytes, WALBatches, độ lệch đồng hồ. Bộ thu tự thân đọc lại để phát agent_*, và AgentStats mang các số này trong từng lô
RaLog (logx)Sự kiện agent started, delivery failed, backing off, collector rejected a batch, dropping it, wal dropped old batches, wal recovered with damage

Trong phạm vi (bao phủ)

Trong phạm viGhi chú
Interface Queue và Entryinternal/buffer/buffer.go. ĐÃ HIỆN THỰC
Hàng đợi RAM có giới hạn Memory (phương án dự phòng)internal/buffer/buffer.go. ĐÃ HIỆN THỰC. Mất dữ liệu khi khởi động lại
WAL đĩa: bản ghi có CRC, đoạn, checkpoint, phục hồi, giới hạn dung lượng và tuổiinternal/buffer/wal.go. ĐÃ HIỆN THỰC
Đóng lô (BuildBatch), seq, AgentStats, lô rỗng làm heartbeatinternal/sender/sender.go. ĐÃ HIỆN THỰC
Máy trạng thái giao nhận (ok, backoff, unauthorized, agent_revoked)internal/sender/sender.go. ĐÃ HIỆN THỰC
Xử lý từng mã trạng thái, tách lô khi 413, gửi bù, thăm dò cấu hình khi 401internal/sender/sender.go. ĐÃ HIỆN THỰC
Lịch backoff full jitter, Retry-After, độ lệch pha khởi đầuinternal/backoff/backoff.go. ĐÃ HIỆN THỰC
Client HTTPS: TLS, ghim CA, proxy, header giao thức, giới hạn kích thước, không theo redirectinternal/transport/transport.go. ĐÃ HIỆN THỰC
Mã hóa dây (protobuf sinh từ agent.proto)internal/wire. ĐÃ HIỆN THỰC
Vòng cycle, dừng khi lệch machine_id, chọn WAL hay RAM, nạp lại cấu hình truyền tảiinternal/agent/agent.go, internal/cli/run.go. ĐÃ HIỆN THỰC. CMP-1 chưa có L3 riêng nên mô tả ở đây (L2 D-14)
Gọi OnConfigETag để kéo cấu hình từ xaMỘT PHẦN: Sender có móc OnConfigETag và có kiểm thử, nhưng agent.go không gán (L2 D-01). THIẾT KẾ, CHƯA XÂY ở phía Runtime (AGT-10)
POST /inventoryĐÃ XÂY (AGT-9): transport.PostInventory, bộ gửi riêng internal/inventory.Reporter ngoài WAL (báo cáo mới nhất thay báo cáo cũ, không cần đệm)
POST /credentials/renewĐÃ HIỆN THỰC (AGT-10). Sender đọc token qua TokenFunc nên token mới có hiệu lực ngay
GET /updateTHIẾT KẾ, CHƯA XÂY (L2 D-08, AGT-12)

Ngoài phạm vi

Không thuộc BCThuộc về
Đọc /proc, chuẩn hóa mẫu, cắt seriesL3 Collectors
Đọc và kiểm tra cấu hình, ghi credentials.json, luồng enroll, SIGHUP nạp tệpL3 Enroll, Credentials, Config
Thư mục state_dir, quyền tài khoản, unit systemd, gói càiL3 Service và Packaging
Nhận lô, kiểm tra hợp lệ, giới hạn tốc độ, ghi TSDB, phát hiện im lặngCollector (L2 Collector)
Hiệu chỉnh mẫu theo độ lệch đồng hồĐỀ XUẤT, chưa có. Agent chỉ ghi và báo độ lệch (L2 FR-12)

2. Detailed Requirements & Acceptance Criteria ​

Functional Requirements

#Trách nhiệmGiải thíchHiện thực ở
FR-W01Đóng lôMỗi mẫu thành một Series với một Point, dấu thời gian là thời điểm thu (now, ms), không phải lúc gửi. Seq tăng dần, khởi đầu từ unix ms. Kèm SentAtMs và AgentStatssender.go (BuildBatch, SeedSeq). ĐÃ HIỆN THỰC
FR-W02HeartbeatChu kỳ không có mẫu vẫn tạo và gửi lô rỗng. Yêu cầu metrics đồng thời là heartbeatsender.go (Enqueue). ĐÃ HIỆN THỰC
FR-W03Ghi bềnMỗi lô được nén deflate, gắn tiêu đề 24 byte có CRC32C, ghi vào đoạn hiện hành rồi Sync. Lỗi ghi thì cắt lại về kích thước cũwal.go (Append). ĐÃ HIỆN THỰC
FR-W04Phục hồiKhi mở: quét đoạn, cắt đuôi rách của đoạn cuối, bỏ qua bản ghi sai CRC hoặc id không tăng, đọc checkpoint, không cấp lại id đã dùngwal.go (OpenWAL, recover, scanRecords). ĐÃ HIỆN THỰC
FR-W05Giới hạn WALXóa bản ghi quá buffer.max_age (từ đầu tới bản ghi tươi đầu tiên), rồi xóa nguyên đoạn cũ nhất khi tổng vượt buffer.max_bytes, không bao giờ xóa đoạn cuốiwal.go (enforce). ĐÃ HIỆN THỰC
FR-W06Gửi theo thứ tự cũ nhất trước, có gửi bùMỗi tick gửi lô cũ nhất, rồi thêm tối đa catch_up_batches lô nữa, dừng ở lỗi đầu tiênsender.go (Tick). ĐÃ HIỆN THỰC
FR-W07BackoffLỗi thử lại: chờ ngẫu nhiên đều trong [0, hiện tại], hiện tại bắt đầu 5 s, nhân đôi tới 300 s, lấy giá trị lớn hơn giữa jitter và Retry-After (trần 1 giờ). Thành công thì đặt lạibackoff.go, sender.go (failure, success). ĐÃ HIỆN THỰC
FR-W08Xử lý mã trạng thái200, 202: xóa lô. 400, 422: bỏ cả lô và đếm điểm. 413: tách đôi và gửi mỗi nửa một lần. 401: dừng gửi và thăm dò GET /config mỗi 10 phút. 403: dừng vĩnh viễn, xóa token trong bộ nhớ, giữ dữ liệu. 426: đặt cờ nâng cấp, giữ lô, thử lại theo backoff. 429, 5xx, lỗi mạng: giữ lô, backoffsender.go (post, handle, failure, splitAndSend, probe). ĐÃ HIỆN THỰC
FR-W09Kênh truyền an toànTLS 1.2 trở lên, xác minh chứng chỉ (InsecureSkipVerify chỉ khi bật rõ), ghim CA từ tệp PEM, proxy từ môi trường hoặc cấu hình, không theo redirect, giới hạn 1 MiB nén và 20.000 điểmtransport.go. ĐÃ HIỆN THỰC
FR-W10Giao thứcMọi yêu cầu có X-AH-Proto: 1, X-AH-Agent-Version, X-Request-Id (16 ký tự hex), User-Agent, và Authorization: Bearer khi có token. Chấp nhận phản hồi JSON hoặc protobuftransport.go (do, decodeInto). ĐÃ HIỆN THỰC
FR-W11Độ lệch đồng hồSau mỗi ack có server_time_ms, ghi (server_time_ms - now)/1000 giây vào thống kê. Chỉ ghi và báosender.go (success). ĐÃ HIỆN THỰC
FR-W12Phân phaChu kỳ đầu lệch sha256(agent_id)[0:8] mod interval để các agent không gửi cùng lúcbackoff.go (PhaseOffset), agent.go. ĐÃ HIỆN THỰC
FR-W13Vòng chu kỳMồi engine một lần, rồi mỗi chu kỳ: Collect, Enqueue, Tick. Lỗi Enqueue được ghi log nhưng Tick vẫn chạyagent.go (Run, cycle). ĐÃ HIỆN THỰC
FR-W14Bảo vệ danh tínhNếu machine_id hiện tại khác lúc enroll, dừng gửi, ghi lỗi identity_mismatch, chờ tới khi hủy ngữ cảnhagent.go (Run). ĐÃ HIỆN THỰC (L2 FR-03)
FR-W15Phương án dự phòng RAMNếu không mở được WAL, dùng hàng đợi RAM giới hạn buffer.max_bytes, ghi lỗiagent.go (openQueue), buffer.go (Memory). ĐÃ HIỆN THỰC
FR-W16Nạp lại truyền tảiSIGHUP đổi interval, catch_up_batches, và dựng lại client nếu khóa truyền tải (url|ca|proxy|insecure|send_timeout) đổiagent.go (Reload, vòng chính). ĐÃ HIỆN THỰC. Không đổi kích thước WAL và không dựng lại engine
FR-W17Kéo cấu hình từ xa khi ack đổi config_etagSender gọi OnConfigETag một lần cho mỗi etag mới. Runtime chưa gán mócMỘT PHẦN (L2 D-01, AGT-10)
FR-W18Tắt êmKhi ngữ cảnh bị hủy, trả về nil ("agent stopped"), không xả lô. Lô chưa gửi ở lại WALagent.go. ĐÃ HIỆN THỰC (không có bước xả, OQ-W7)

Non-Functional Requirements

NFRTarget (Ý nghĩa)Parent L2-NFR (Kiểu)Satisfied-by (Tactic → Mục)
NFR-W01Sau mỗi lần ghi, đĩa cho WAL không vượt buffer.max_bytes (mặc định 50 MiB) khi còn từ hai đoạn trở lên, và không giữ lô quá buffer.max_age (24 giờ)L2-NFR-04 (Allocated)Xóa theo tuổi rồi theo đoạn (7.4.2), tham số ở 12.1. Đoạn cuối không bao giờ bị xóa, nên một bản ghi đơn lẻ lớn hơn max_bytes là ngoại lệ (7.4.2)
NFR-W02Mất điện đột ngột chỉ mất lô đang ghi, WAL không hỏngL2-NFR-07 (Allocated)fsync mỗi Append, CRC32C, cắt đuôi rách, checkpoint ghi bằng tệp tạm và đổi tên (7.2 luồng 8, 7.4.1). TestWALSurvivesKill9
NFR-W03Kích thước lô gửi không vượt 1 MiB nén và 20.000 điểm, lô quá lớn được tách một lầnL2-NFR-05 (Allocated)Kiểm cục bộ, ErrTooLarge, splitAndSend (7.4.4). Mục tiêu 10 KB chưa đo (L2 D-16)
NFR-W04Kênh TLS 1.2 trở lên, xác minh chứng chỉ, không theo redirectL2-NFR-11 (Allocated)New dựng tls.Config, CheckRedirect trả ErrUseLastResponse (11)
NFR-W05Token không xuất hiện trong log, lỗi hay String()L2-NFR-13 (Allocated)Lỗi mạng chỉ mang URL, Sender.String() không có token (11). TestTokenNeverReachesTheLogs, TestNetworkErrorsNeverContainTheToken
NFR-W06Chu kỳ gửi mặc định 30 s (10 s đến 300 s) với pha lệch ổn định theo agent_idL2-NFR-14 (Allocated)PhaseOffset (7.4.3), interval (12.1). TestPhaseOffsetIsStableBoundedAndSpread
NFR-W07Lịch backoff 5 s tới 300 s, full jitter, Retry-After tối đa 1 giờL2-NFR-15 (Allocated)backoff.Next (7.4.3). TestScheduleDoublesToCapWithFullJitter
NFR-W08Xử lý 426 không mất lô và không giả vờ nâng cấpL2-NFR-18 (Allocated)Cờ upgrade, giữ lô (7.3). TestUpgradeRequiredIsFlaggedAndBatchKept
NFR-W09Dừng êm khi SIGTERM trong 10 giâyL2-NFR-10 (Allocated một phần)Hủy ngữ cảnh truyền tới Tick và HTTP. Chưa có phép đo (L2 D-16, OQ-W7)
NFR-W10Mọi lô bị bỏ hoặc bị WAL xóa phải quan sát được bằng số đếm và logKhông có tương ứng đầy đủ trong L2 (Owned)Điểm bỏ vì 400, 422, không chia được đã vào DroppedSamples. Lô bị WAL xóa mới chỉ có log (D-10, OQ-W5). Metric ở 13.1
NFR-W11Thử lại không tạo bão yêu cầu lên Collector: tốc độ trung bình không vượt hạn mức của giao thứcKế thừa ANFR (truyền) và giới hạn 4 yêu cầu mỗi phút mỗi agent của giao thức (Owned)Backoff, catch_up_batches. Rủi ro đã biết ở OQ-W1: gửi bù tối đa 3 yêu cầu mỗi tick

Acceptance Criteria

ACKịch bản đặc tả (Given / When / Then)Truy vết → Test ID
AC-W01Given lô được Append rồi Remove, When đọc Oldest, Then thứ tự vào trước ra trước và Oldest không tự xóaTestWALFifoRoundTrip, TestWALOldestDoesNotRemove
AC-W02Given lô đã ack rồi tiến trình khởi động lại, When mở WAL, Then lô đã ack không gửi lại và id không bao giờ quay vềTestWALSurvivesRestartWithoutResendingAcknowledged, TestWALIdsNeverRestartAfterEmptyAndReopen
AC-W03Given WAL đầy nhiều đoạn, When Append vượt giới hạn, Then đoạn cũ nhất bị xóa và đoạn đã xong bị dọnTestWALRotatesAndDeletesFinishedSegments, TestWALSizeCapDropsOldestSegments
AC-W04Given bản ghi quá max_age, When Append, mở hoặc Oldest, Then bản ghi bị bỏ và đếmTestWALAgeCapDropsExpiredBatches, TestWALAgeCapAppliesOnOpenAndOldest
AC-W05Given bản ghi sai CRC (lúc phục hồi hoặc sau khi mở), When đọc, Then bản ghi bị bỏ, đếm là hỏng, các bản ghi khác cònTestWALSkipsRecordWithBadCRCOnRecovery, TestWALDetectsCorruptionAfterOpen
AC-W06Given tiến trình bị kill -9 giữa lúc ghi, When mở lại, Then WAL đọc được và đuôi rách bị cắtTestWALSurvivesKill9, TestWALTruncatesTornTail, FuzzWALScanRecords
AC-W07Given Remove sai thứ tự hoặc id lạ, When gọi, Then không lỗi và không xóa nhầmTestWALOutOfOrderRemoveAndUnknownIDs
AC-W08Given WAL đã tạo, When kiểm quyền, Then thư mục 0700 và tệp 0600. Given WAL đã đóng, Then mọi thao tác bị từ chốiTestWALFilesAreOwnerOnly, TestWALClosedRejectsUse, TestWALOpenRequiresDir
AC-W09Given hàng đợi RAM đầy, When thêm lô, Then bỏ lô cũ nhưng luôn giữ lô mới nhất, dữ liệu được sao chépTestMemoryEvictsOldestWhenFull, TestMemoryKeepsNewestEvenWhenOversized, TestAppendCopiesData
AC-W10Given Collector trả 200, When Tick, Then lô bị xóa, độ lệch đồng hồ cập nhật, lô rỗng vẫn được gửi làm heartbeatTestSuccessDeliversAndUpdatesSkew, TestEmptyBatchIsAHeartbeat
AC-W11Given 5xx hoặc mất kết nối, When Tick, Then lô được giữ, Sender vào trạng thái backoff, Retry-After được tôn trọngTestServerErrorKeepsBatchAndBacksOff, TestDroppedConnectionKeepsBatch, TestRetryAfterIsHonored
AC-W12Given 400 hoặc 422, When Tick, Then lô bị bỏ và điểm được đếm vào DroppedSamplesTestBadRequestAndUnprocessableAreDropped
AC-W13Given 413, When Tick, Then lô tách đôi và mỗi nửa gửi một lần. Nửa vẫn quá lớn thì bỏ. Nửa gặp lỗi tạm thời thì xếp lạiTestTooLargeIsSplitAndSentOnce, TestSplitHalfStillTooLargeIsDropped, TestSplitRequeuesUnsentHalvesOnRetryableFailure
AC-W14Given lô có hơn 20.000 điểm hoặc không thể tách, When gửi, Then tách hoặc bỏ cục bộ, không gọi mạng vô íchTestOversizePointCountIsSplitLocally, TestUnsplittableOversizeBatchIsDropped
AC-W15Given 401, When Tick, Then dừng gửi và thăm dò GET /config. Given 403, Then dừng vĩnh viễn, giữ dữ liệuTestUnauthorizedStopsSendingAndProbesConfig, TestRevokedStopsPermanentlyAndKeepsData
AC-W16Given 426, When Tick, Then đặt cờ nâng cấp, giữ lô, cờ xóa sau lần gửi thành côngTestUpgradeRequiredIsFlaggedAndBatchKept
AC-W17Given hàng đợi 6 lô và CatchUp 2, When một Tick, Then gửi 3 lô cũ nhất trướcTestCatchUpSendsOldestFirstWithinLimit
AC-W18Given ack đổi config_etag, When Tick, Then OnConfigETag gọi một lần cho mỗi etag mớiTestConfigETagCallbackFiresOncePerChange
AC-W19Given bản ghi không đọc được (giải mã protobuf lỗi), When gửi, Then bị bỏ có log và không chặn hàng đợiTestUnreadableEntryIsDropped
AC-W20Given nhiều lô, When đóng, Then seq tăng dần và khởi đầu từ thời gianTestSequenceNumbersIncreaseAndSeedFromTime
AC-W21Given lỗi mạng hoặc log, When gửi, Then token không xuất hiệnTestTokenNeverReachesTheLogs, TestNetworkErrorsNeverContainTheToken
AC-W22Given yêu cầu gửi, When kiểm header, Then có X-AH-Proto, phiên bản agent, request id, bearer. Redirect không được theo. Lô nén quá 1 MiB bị chặn cục bộTestPostMetricsSendsProtocolHeaders, TestRedirectsAreNotFollowed, TestOversizeBatchIsRejectedLocally
AC-W23Given phản hồi lỗi có JSON, When phân tích, Then lấy code và message (cắt 200 ký tự), hiểu Retry-After dạng giây hoặc ngày HTTPTestAPIErrorParsing, TestRetryAfterHTTPDate
AC-W24Given GET /config có ETag, When gọi lại, Then gửi If-None-Match và 304 cho kết quả rỗngTestGetConfigETag
AC-W25Given phản hồi enroll thiếu trường, When phân tích, Then bị từ chối. Given phản hồi proto hoặc JSON, Then giải mã đúngTestEnrollDecodesProtoAndJSON, TestEnrollRejectsIncompleteResponse
AC-W26Given tùy chọn client sai, When New, Then bị từ chốiTestNewValidatesOptions
AC-W27Given lịch backoff, When gọi Next nhiều lần, Then nhân đôi tới trần với jitter trong khoảng, Retry-After lớn hơn thắng và bị chặn ở 1 giờ, Reset bắt đầu lạiTestScheduleDoublesToCapWithFullJitter, TestJitterStaysInRange, TestRetryAfterWinsWhenLargerAndIsCapped, TestResetRestartsSchedule
AC-W28Given nhiều agent_id, When tính pha, Then ổn định, nằm trong interval và phân tánTestPhaseOffsetIsStableBoundedAndSpread
AC-W29Given agent chưa enroll, When Run, Then trả ErrNotEnrolled. Given machine_id lệch, Then dừng gửi. Given bị thu hồi, Then không liên lạc Collector nữaTestRunWithoutCredentialsIsNotEnrolled, TestMachineIDMismatchStopsDelivery, TestRevokedAgentStopsContactingTheCollector
AC-W30Given agent chạy, When qua mỗi interval, Then một lô được gửi. Pha làm trễ lần đầu. Reload đổi được Collector đíchTestRunDeliversBatchesEveryInterval, TestPhaseOffsetDelaysTheFirstSend, TestReloadSwitchesCollector
AC-W31Given lô đang đệm và agent khởi động lại, When chạy lại, Then lô được gửi. Given thư mục state không dùng được, Then dùng RAMTestBufferedBatchesSurviveRestart, TestUnusableStateDirFallsBackToMemory
AC-W32Given engine hỏng, When Run, Then trả lỗiTestBrokenEngineFailsRun

Quality Attribute Scenarios

Mã kịch bản / NFRNguồn & Kích thíchMôi trườngPhản hồi của hệ thống (Tactic)Thước đo chất lượng (Measure)
QAS-W01 / NFR-W02Mất điện hoặc kill -9 đúng lúc ghi lôChạy bình thườngWAL cắt đuôi rách, đọc lại các bản ghi còn nguyên vẹn, log wal recovered with damageMất tối đa lô đang ghi (khoảng một interval). Không có bản ghi hỏng được gửi đi. Mô phỏng 20 lần mất điện chưa chạy (L2 D-16)
QAS-W02 / NFR-W01Mất kết nối tới Collector trong nhiều giờMạng lỗi kéo dàiLô tích trong WAL, xóa theo tuổi rồi theo đoạn khi vượt giới hạn, ghi log cảnh báoĐệm giữ tối thiểu 2 giờ (L2-NFR-07). Đĩa không vượt max_bytes sau mỗi lần ghi (tạm thời vượt trong lúc ghi, tối đa một bản ghi). Lỗ hổng số liệu quan sát được ở log và agent_wal_*, chưa ở dropped_samples (D-10)
QAS-W03 / NFR-W07Collector trả 5xx hoặc 429 với Retry-AfterCollector quá tảiBackoff full jitter, tôn trọng Retry-After, giữ lôKhoảng chờ trong [0, 5 s] rồi nhân đôi tới [0, 300 s], không dưới Retry-After (trần 1 giờ). agent_send_failures_total tăng mỗi lần
QAS-W04 / NFR-W11Collector hồi phục sau sự cố, hàng nghìn agent có đệmSau sự cốPhân pha, jitter và catch_up_batches giới hạn tốc độ xảMỗi agent tối đa 1 + catch_up_batches yêu cầu mỗi tick (mặc định 3, có thể vượt hạn mức 4 yêu cầu mỗi phút, OQ-W1)
QAS-W05 / NFR-W03Lô vượt 1 MiB nén (nhiều mount, nhiều giao diện)Chạy bình thườngTách đôi theo series hoặc theo điểm, gửi mỗi nửa một lầnKhông lô nào vượt 1 MiB gửi lên. Nếu không tách được, bỏ và đếm điểm
QAS-W06 / NFR-W04, NFR-W05Kẻ tấn công chặn đường hoặc chuyển hướng, hoặc lỗi cấu hình proxyMạng không tin cậyXác minh chứng chỉ, không theo redirect, lỗi chỉ mang URLKhông có token trong log. Kết nối tới chứng chỉ không hợp lệ bị từ chối
QAS-W07 / NFR-W08Collector nâng giao thức và trả 426Nâng cấp lệch phiên bảnĐặt cờ, giữ lô, thử lại theo backoff, hiện upgrade_required ở Status()Lô không mất. Cờ hiện trong String(). Không có nâng cấp tự động (AGT-12)

3. Kiến trúc ứng dụng ​

3.1. Kiến trúc runtime ​

Toàn bộ cụm chạy trong một tiến trình. Vòng chu kỳ, engine, Enqueue và Tick cùng chạy trên một goroutine (Sender.Enqueue và Sender.Tick bắt buộc gọi từ một goroutine, nếu không seq và nextAttempt không an toàn). Chỉ trạng thái đọc bởi Status() được bảo vệ bằng mutex. Không có goroutine gửi nền và không có hàng đợi trong bộ nhớ giữa engine và WAL. Đây là quyết định ngầm của mã (ADR-W03 ở Phụ lục B).

mermaid
flowchart LR
  classDef bc fill:#1f3a5f,stroke:#4a90d9,color:#fff
  classDef owned fill:#2d4a3e,stroke:#5fb37a,color:#fff
  classDef datastore fill:#3a2d4a,stroke:#a06fd9,color:#fff
  classDef infra fill:#444,stroke:#aaa,color:#fff

  RT["Vòng cycle · thu, xếp hàng, gửi"]:::owned
  ENG["Collector Engine"]:::bc
  SND["Sender · trạng thái giao nhận"]:::owned
  BO["Backoff · lịch chờ"]:::owned
  WAL["WAL Buffer · bản ghi có CRC"]:::owned
  MEM["Hàng đợi RAM · dự phòng"]:::owned
  TRN["Transport Client · HTTPS"]:::owned
  ST[("Thống kê · bộ đếm chung")]:::datastore
  DISK[("Tệp đoạn và checkpoint")]:::datastore
  COLL(["Collector · /agent/v1"]):::infra

  RT -->|"gọi thu một chu kỳ"| ENG
  RT -->|"Enqueue rồi Tick"| SND
  SND -->|"Append, Oldest, Remove"| WAL
  SND -.->|"dùng khi WAL không mở được"| MEM
  WAL -->|"ghi và fsync"| DISK
  SND -->|"hỏi thời gian chờ"| BO
  SND -->|"POST metrics đã nén"| TRN
  TRN -->|"HTTPS protobuf"| COLL
  SND -.->|"ghi lỗi, độ lệch, WAL"| ST

Chú giải: nét liền là lời gọi đồng bộ trên cùng goroutine (riêng Transport là HTTP chặn tới send_timeout), nét đứt là lựa chọn lúc khởi động hoặc truy cập trạng thái dùng chung.

Bảng connector

ConnectorTừTớiCơ chếĐồng bộGhi chú
CN-W1Vòng cycleEngineEngine.Collect(ctx, now)Đồng bộXem L3 Collectors. Thời gian thu cộng vào chu kỳ
CN-W2Vòng cycleSenderEnqueue(now, samples) rồi Tick(ctx)Đồng bộ, cùng goroutineLỗi Enqueue được ghi log, Tick vẫn chạy
CN-W3SenderHàng đợi (buffer.Queue)Append(data), Oldest(), Remove(id), Stats()Đồng bộ. Append chặn tới khi fsync xongHiện thực là WAL hoặc Memory, chọn một lần lúc khởi động
CN-W4WALĐĩaos.File.Write, Sync, Truncate, Rename, RemoveĐồng bộThư mục 0700, tệp 0600
CN-W5SenderBackoffNext(retryAfter), Reset()Đồng bộBackoff không có trạng thái ngoài bước hiện tại. Nguồn ngẫu nhiên có thể tiêm khi kiểm thử
CN-W6SenderTransportInterface API: PostMetrics(ctx, token, gz), GetConfig(ctx, token, etag)Đồng bộ, chặn tới send_timeoutLỗi trả dạng *transport.APIError (mã trạng thái, Retry-After) hoặc ErrTooLarge hoặc lỗi mạng
CN-W7TransportCollectorHTTPS, HTTP/2 nếu được, application/x-protobuf, gzipĐồng bộTLS từ 1.2, không theo redirect
CN-W8Sender, WALThống kêatomic và sync.MutexĐồng bộSendFailures, DroppedSamples, WALBytes, WALBatches, độ lệch đồng hồ
CN-W9SenderMóc OnConfigETagHàm gọi lạiĐồng bộRuntime không gán (L2 D-01) nên không tới đâu

3.2. Kiến trúc module ​

mermaid
flowchart TB
  classDef pub fill:#1f3a5f,stroke:#4a90d9,color:#fff
  classDef intn fill:#3a3a3a,stroke:#888,color:#fff
  classDef ext fill:#3a3a3a,stroke:#888,color:#fff

  subgraph LA["Lớp điều phối: agent, cli"]
    AGT["Run, cycle, Reload, Status"]:::pub
    CLIRUN["cli/run: tín hiệu, GOMEMLIMIT"]:::intn
  end
  subgraph LB["Lớp giao nhận: sender"]
    SEND["Sender, API, State"]:::pub
    SPLIT["split, requeue, probe"]:::intn
  end
  subgraph LC["Lớp hạ tầng"]
    BUF["buffer: Queue, WAL, Memory"]:::pub
    BOFF["backoff: Backoff, PhaseOffset"]:::pub
    TRAN["transport: Client, APIError"]:::pub
    WIRE["wire: agentv1 MetricsBatch, MetricsAck"]:::pub
  end
  EXT(["stats, logx, config, creds, collector"]):::ext

  CLIRUN --> AGT
  AGT --> SEND
  SEND --> BUF
  SEND --> BOFF
  SEND --> TRAN
  SEND --> WIRE
  TRAN --> WIRE
  SEND --> SPLIT
  AGT -.-> EXT
  SEND -.-> EXT
  BUF -.-> EXT

Chú giải: xanh dương là bề mặt công khai của gói, xám là nội bộ hoặc bên ngoài. Mũi tên liền là phụ thuộc biên dịch, mũi tên đứt là phụ thuộc vào gói dùng chung. sender phụ thuộc vào buffer qua interface Queue và vào transport qua interface API (định nghĩa ở sender), nên kiểm thử thay được cả hai bằng bản giả. buffer không biết wire (nó lưu byte đã marshal, chưa nén).

3.2.1. Cấu trúc: gói buffer ​

Giao diệnNgười dùngHợp đồng
Queue (Append(data) (id, error), Oldest() (Entry, bool, error), Remove(id), Stats(), Close())sender, agentVào trước ra trước. Oldest không xóa. Remove xóa sau khi xác nhận. Entry{ID, Data} mang MetricsBatch đã marshal, chưa nén
OpenWAL(WALOptions)agent.openQueueWALOptions{Dir, MaxBytes, MaxAge, SegmentBytes, Now, Log}. SegmentBytes bằng 0 lấy mặc định 1 MiB. Tạo thư mục 0700
NewMemory(maxBytes)agent.openQueue (dự phòng)Sao chép dữ liệu, bỏ lô cũ khi đầy nhưng luôn giữ lô mới nhất
Stats{Batches, Bytes, Dropped}sender.syncStatsBatches là số lô còn sống, Dropped cộng dồn theo lô (không phải theo điểm)

3.2.2. Cấu trúc: gói sender và backoff ​

Thành phầnTệpVai tròTrạng thái giữa các lần gọi
Sendersender/sender.goĐóng lô, xếp hàng, gửi, xử lý mã trạng tháiseq, nextAttempt, nextProbe, lastETag, lastDropped, và trạng thái có mutex: state, upgrade, lastErr, lastSent
API (interface)sender/sender.goPostMetrics, GetConfigKhông
State (ok, backoff, unauthorized, agent_revoked)sender/sender.goMáy trạng thái giao nhận, xem 7.3Có
Backoffbackoff/backoff.goBase 5 s, Max 300 s, MaxRetryAfter 1 giờ, nguồn ngẫu nhiênBước hiện tại cur
PhaseOffset(agentID, interval)backoff/backoff.goHàm thuần, không trạng tháiKhông

3.2.3. Cấu trúc: gói transport, wire và vòng cycle ​

Thành phầnTệpVai tròGhi chú
Client (New, Ping, Enroll, PostMetrics, GetConfig)transport/transport.goClient HTTPS cho giao thức /agent/v1Enroll thuộc L3 Enroll. Ping chưa được vòng chạy dùng
APIError, ErrTooLargetransport/transport.goLỗi có kiểu để Sender phân nhánhThông điệp lỗi cắt 200 ký tự
agentv1wire/accesshub/agent/v1/agent.pb.goMã protobuf sinh từ agent.protoagent.proto.ref là bản chép hợp đồng của Collector, kiểm lệch bằng scripts/check-proto-drift.sh
Agent.Run, cycle, Reload, Statusagent/agent.goĐiều phối vòng chu kỳ (CMP-1, L2 D-14)halted khi lệch machine_id. Con trỏ cấu hình nguyên tử
cli/run.gocli/run.goĐặt GOMEMLIMIT, xử lý SIGINT, SIGTERM, SIGHUP, tín hiệu trạng tháiMã thoát ExitNotEnrolled khi chưa enroll

4. Domain model ​

mermaid
classDiagram
  namespace Vung_Giao_Nhan {
    class Sender {
      <<Aggregate Root>>
      +State DeliveryState
      +Seq uint64
      +NextAttempt time
      +CatchUp int
    }
    class Batch {
      <<Entity>>
      +Seq uint64
      +SentAtMs int64
      +Series list
      +Stats AgentStats
    }
    class WalRecord {
      <<Entity>>
      +Id uint64
      +TsMs int64
      +Done bool
      +Payload bytes
    }
    class Segment {
      <<Entity>>
      +FirstId uint64
      +Size int64
      +Live int
    }
    class Checkpoint {
      <<ValueObject>>
      +Watermark uint64
    }
    class DeliveryState {
      <<Enumeration>>
      ok
      backoff
      unauthorized
      agent_revoked
    }
    class BackoffSchedule {
      <<ValueObject>>
      +Current duration
      +Base duration
      +Max duration
    }
  }
  Sender "1" o-- "0..*" Batch : đóng và gửi
  Sender ..> DeliveryState : đang ở
  Sender ..> BackoffSchedule : hỏi thời gian chờ
  Batch "1" .. "0..1" WalRecord : được lưu thành
  Segment "1" *-- "1..*" WalRecord : chứa
  Segment "1..*" --o "1" Checkpoint : dọn tới mốc

Sơ đồ dùng Sender làm Aggregate Root vì mọi quyết định giữ hay bỏ lô đi qua nó. WalRecord, Segment và Checkpoint là phần bền do buffer sở hữu, Sender chỉ chạm tới chúng qua interface Queue.

Bất biến (invariants)

MãBất biếnNơi bảo đảm
INV-W1Id bản ghi tăng ngặt trong WAL và không bao giờ cấp lại, kể cả khi WAL rỗng rồi mở lại (nextID = max(watermark+1, maxid+1))recover, Append. TestWALIdsNeverRestartAfterEmptyAndReopen
INV-W2Một lô chỉ bị Remove khỏi hàng đợi khi (a) Collector trả 200 hoặc 202, hoặc (b) bị quyết định bỏ có log (400, 422, không chia được, không giải mã được, đã tách xong), hoặc (c) WAL tự xóa theo tuổi hoặc dung lượng. Không có đường xóa nào khácsender.handle, sendEntry, wal.enforce
INV-W3Mọi bản ghi đọc ra đều đã qua kiểm CRC32C, id khớp và giải nén thành công. Bản ghi hỏng bị bỏ và đếm, không bao giờ được gửiscanRecords, Oldest
INV-W4Watermark chỉ tiến, và bằng id lớn nhất mà mọi bản ghi từ đầu tới đó đã xong. WAL rỗng thì bằng nextID - 1settle
INV-W5Đoạn cuối không bao giờ bị xóa bởi giới hạn dung lượng. Sau mỗi lần ghi, tổng dung lượng không vượt MaxBytes trừ khi chỉ còn một đoạnenforce
INV-W6seq của Sender tăng ngặt trong một phiên. Khởi đầu bằng unix ms lúc chạy nên không lặp qua các lần khởi động (nếu đồng hồ không lùi). Hai nửa của một lô đã tách dùng chung seq gốcBuildBatch, SeedSeq, split
INV-W7Dấu thời gian của điểm là thời điểm thu, không đổi khi thử lại hay gửi bùBuildBatch
INV-W8Khi agent_revoked, Sender.Token rỗng và Sender không gọi mạng nữa cho tới khi tiến trình khởi động lạirevoke, Tick
INV-W9Backoff Next không bao giờ trả về nhỏ hơn min(Retry-After, 1 giờ)backoff.Next. TestRetryAfterWinsWhenLargerAndIsCapped

5. API Contract Specification ​

Cụm này không mở cổng mạng và không có API cho bên ngoài gọi vào. Hợp đồng gồm hai phần: (1) interface Go trong tiến trình (Queue, API) và (2) hợp đồng HTTPS mà cụm này đóng vai client gọi tới Collector (docs/10-wire-contract.md). Mục 5.1 đến 5.5 được điền theo hai phần đó (Phụ lục C).

5.1. Operations (Public API) ​

#Operation / Kênh EventLoại hìnhHướng gọi (Caller → BC)Ngữ nghĩa nghiệp vụ
1Sender.Enqueue(now, samples)Lời gọi hàmcycle → SenderĐóng lô (kể cả rỗng), marshal, Append vào hàng đợi, đồng bộ thống kê WAL
2Sender.Tick(ctx)Lời gọi hàmcycle → SenderGửi lô cũ nhất và tối đa CatchUp lô nữa. Áp dụng backoff, thăm dò 401, dừng ở lỗi đầu tiên
3Sender.SeedSeq(now)Lời gọi hàmRun → SenderĐặt seq khởi đầu bằng unix ms
4Sender.State(), String()Lời gọi hàmStatus() → SenderChuỗi delivery=<state> queued=N [upgrade_required=true], không chứa token
5buffer.Queue (5 thao tác)Interface GoSender → WAL hoặc MemoryXem 3.2.1
6POST /agent/v1/metricsHTTPS, protobuf nén gzipTransport → CollectorGửi một lô. Đồng thời là heartbeat
7GET /agent/v1/configHTTPSTransport → CollectorChỉ dùng để thăm dò khi 401 (và cho AGT-10 sau này). If-None-Match
8GET /agent/v1/pingHTTPSTransport → CollectorCó trong client, chưa được vòng chạy gọi
9POST /agent/v1/enrollHTTPSTransport → CollectorThuộc L3 Enroll. Ghi ở đây vì dùng chung Client
10Kênh sự kiện raKhông cóChỉ log

5.2. Request / Response Schema ​

[POST /agent/v1/metrics]

FieldKiểuGhi chú
ReqAuthorizationheader!Bearer <agent_token>. Không gửi khi token rỗng
ReqX-AH-Protoheader!1
ReqX-AH-Agent-Versionheader!Phiên bản agent
ReqX-Request-Idheader!16 ký tự hex ngẫu nhiên mỗi yêu cầu
ReqContent-Typeheader!application/x-protobuf
ReqContent-Encodingheader!gzip (D-09: giao thức cho phép gzip hoặc zstd)
ReqbodyMetricsBatch nén gzip!Tối đa 1 MiB nén và 20.000 điểm (kiểm cục bộ trước khi gửi)
ReqMetricsBatch.sequint64!Tăng dần, khởi đầu unix ms
ReqMetricsBatch.sent_at_msint64!Bằng thời điểm thu của chu kỳ, không phải lúc gửi thật (xem OQ-W9)
ReqMetricsBatch.series[]Series{name, labels, points[]}?Rỗng nghĩa là heartbeat. Mỗi series hiện có đúng một Point{ts_ms, value}
ReqMetricsBatch.statsAgentStats?cpu_percent, rss_bytes, wal_bytes, wal_batches, dropped_samples_total, send_failures_total, clock_skew_seconds. Có khi Stats được cấu hình
RespMetricsAck.server_time_msint64?Dùng tính độ lệch đồng hồ
RespMetricsAck.ack_sequint64?Bị bỏ qua (OQ-W8)
RespMetricsAck.config_etagstring?Khi đổi, gọi OnConfigETag một lần
RespMetricsAck.accepted_points, dropped_points, warnings[]uint32, uint32, []string?Chỉ ghi log (OQ-W8). Thân ack là tùy chọn
Errors có thể400, 401, 403, 413, 422, 426, 429, 5xx, lỗi mạng. Xem 5.3

[GET /agent/v1/config]

FieldKiểuGhi chú
ReqAuthorization, X-AH-Proto, X-Request-Idheader!Như trên
ReqIf-None-Matchheader?ETag đã biết
Resp200AgentConfig (JSON hoặc proto)!ETag lấy từ thân hoặc header ETag
Resp304không có thânClient trả nil, nil
Errors có thể401 (vẫn chưa hợp lệ), 403 (thu hồi), 429 (2 yêu cầu mỗi phút mỗi agent), 5xx

[buffer.Queue]

FieldKiểuGhi chú
ReqAppend(data)[]byte!MetricsBatch đã marshal. WAL nén deflate. Lỗi nếu bản ghi nén quá 16 MiB
Respiduint64!Id tăng ngặt
RespOldest()Entry, ok, error!ok sai khi rỗng. Đọc lại và kiểm CRC mỗi lần
ReqRemove(id)uint64!Id lạ bị bỏ qua
RespStats()Stats{Batches, Bytes, Dropped}!
Errors có thểwal: batch of N bytes is too large, lỗi I/O, "wal closed"

5.3. Error Codes ​

code của CollectorHTTPĐiều kiện phát sinhHành động của Sender
(không có)200, 202Chấp nhậnXóa lô, đặt lại backoff, trạng thái ok
bad_request400Thân sai định dạngBỏ cả lô, cộng điểm vào DroppedSamples, log Error
invalid_series422Dữ liệu không hợp lệBỏ cả lô (giao thức ghi "bỏ phần lỗi", lệch, OQ-W2)
unauthorized401Token sai hoặc hết hạnTrạng thái unauthorized, dừng gửi, thăm dò GET /config mỗi 10 phút
agent_revoked403Agent bị thu hồiTrạng thái agent_revoked, xóa token trong bộ nhớ, giữ dữ liệu, không thử lại
too_large413Lô vượt giới hạn của CollectorTách đôi, gửi mỗi nửa một lần
upgrade_required426Giao thức agent quá cũĐặt cờ upgrade, giữ lô, backoff (OQ-W3)
rate_limited429Vượt hạn mức tốc độGiữ lô, backoff, tôn trọng Retry-After
server_error, unavailable5xxCollector lỗiGiữ lô, backoff
Mọi mã khác (kể cả 409, 404, 3xx)khácNgoài bảng giao thứcGiữ lô, backoff (mã trong bảng giao thức của 409 chỉ dành cho enroll, OQ-W10)
(không có)Không áp dụngĐứt kết nối, hết send_timeout, DNS, TLSGiữ lô, backoff. Lỗi không mang token
ErrTooLarge (cục bộ)Không áp dụngNén quá 1 MiB hoặc quá 20.000 điểmĐi nhánh 413 mà không gọi mạng

Ghi chú: chuỗi code do Collector trả, Sender phân nhánh theo mã HTTP chứ không theo code. Nhánh "mọi mã khác" dựa trên đọc mã (mọi trạng thái không có nhánh riêng thành kRetry). Redirect 3xx không được theo (11) nên rơi vào nhánh này.

5.4. Versioning ​

Header X-AH-Proto: 1 trên mọi yêu cầu. Chỉ có một phiên bản giao thức nên N-1 chưa kiểm chứng được (L2 R-09). Lược đồ dây là agent.proto: agent.proto.ref trong agent là bản chép, nguồn sự thật là tệp của Collector. scripts/check-proto-drift.sh so hai tệp và trả mã 1 khi lệch. Thêm trường protobuf theo quy tắc tương thích ngược của protobuf (số trường không đổi). Client chấp nhận phản hồi JSON (protojson bỏ qua trường lạ) hoặc proto.

5.5. Authz - shared responsibility & permission matrix ​

Cụm này không phân quyền người dùng. Danh tính là bearer token của agent, do Collector cấp lúc enroll, và Collector xác thực ở mỗi yêu cầu.

Phân định trách nhiệm Auth

LớpAi sở hữuGồm
Xác thực agentCollectorKiểm token, gắn company_id và server_id từ token. Agent không tự khai hai giá trị này trong mẫu (L3 Collectors INV-2)
Giữ và gửi tokenCụm nàySender.Token trong RAM, header Authorization. Xóa token khi 403
Lưu token trên đĩaL3 Enroll, Credentials, Configcredentials.json 0600
Bảo vệ kênhCụm này (transport)TLS từ 1.2, xác minh chứng chỉ, ghim CA, không theo redirect
Quyền tệp WALCụm này, ServiceThư mục 0700, tệp 0600, tài khoản accesshub-agent

Permission matrix

Public API (5.1)service-principaluser-role
1 đến 5 (trong tiến trình)Mã cùng tiến trìnhKhông áp dụng
6, 7 (metrics, config)Agent có token hợp lệ. Sai token 401, thu hồi 403Không áp dụng
8, 9 (ping, enroll)Chưa có token cho enroll (dùng License một lần). ping không cần tokenKhông áp dụng

6. Data Schema (Physical) ​

Cụm này ghi đĩa ở một nơi duy nhất: thư mục state_dir/wal (mặc định theo cấu hình, chi tiết ở L3 Service và Packaging). Mục 6.1 mô tả bố cục tệp thay cho lược đồ cơ sở dữ liệu.

6.1. Cài đặt vật lý ​

Bảng ánh xạ

Phân hệ (3.2)Aggregate (4)CSDL (Store)Cấu trúc vật lý cốt lõi
buffer (WAL)WalRecord, SegmentTệp trên đĩa, state_dir/wal/%016x.seg (đặt tên theo id đầu tiên, tối đa khoảng 1 MiB)Chuỗi bản ghi: tiêu đề 24 byte (little-endian) rồi payload deflate
buffer (WAL)CheckpointTệp state_dir/wal/checkpointSố nguyên thập phân và xuống dòng, ghi bằng tệp .tmp, fsync, rename
buffer (Memory)BatchRAM tiến trìnhDanh sách các mảng byte, giới hạn MaxBytes
senderSenderRAM tiến trìnhseq, nextAttempt, nextProbe, lastETag, trạng thái

Lược đồ (bố cục WAL trên đĩa)

mermaid
classDiagram
  namespace Thu_Muc_WAL {
    class walDir {
      path state_dir_wal
      mode 0700
    }
    class segmentFile {
      name 16_hex_first_id.seg
      mode 0600
    }
    class recordHeader {
      length u32_LE
      crc32c u32_LE
      id u64_LE
      unix_ms i64_LE
    }
    class recordPayload {
      deflate_of_MetricsBatch bytes
    }
    class checkpointFile {
      watermark decimal_text
      mode 0600
    }
    class tmpFile {
      name checkpoint_dot_tmp
    }
  }
  walDir *-- segmentFile : chứa nhiều
  walDir *-- checkpointFile : chứa một
  segmentFile *-- recordHeader : mỗi bản ghi
  recordHeader *-- recordPayload : theo sau
  checkpointFile ..> tmpFile : ghi qua rồi đổi tên

Ghi chú lược đồ: trường crc32c (Castagnoli) bao phủ mọi byte đứng sau nó (id, thời gian, payload), không bao gồm length. length là độ dài payload nén, tối đa 16 MiB (walMaxRecord). Không có khóa ngoại. Bản ghi không có trường phiên bản định dạng (L2 D-15, OQ-W6).

6.2. Phân loại dữ liệu & retention ​

Phần tử / Trường dữ liệuPhân lớp dữ liệuThời hạn lưu trữ (Retention)Cơ chế bảo vệ kỹ thuật
Bản ghi WAL (lô số liệu)Nội bộĐến khi được ack, hoặc quá buffer.max_age (mặc định 24 giờ), hoặc bị xóa theo đoạn khi vượt buffer.max_bytes (mặc định 50 MiB)Quyền 0600 và 0700, CRC32C phát hiện hỏng, deflate, không mã hóa ở trạng thái nghỉ (ĐỀ XUẤT: không cần vì không chứa bí mật)
checkpointNội bộCập nhật mỗi lần settleGhi bằng tệp tạm, fsync rồi rename. Thư mục không được fsync sau rename nên sau mất điện có thể quay về giá trị cũ, hậu quả chỉ là gửi lặp (an toàn vì Collector khử trùng)
Hàng đợi RAM (dự phòng)Nội bộĐời tiến trìnhChỉ ở RAM, mất khi khởi động lại
Sender.TokenBí mậtĐời tiến trình (lưu bền ở credentials.json, L3 Enroll)Không vào log, String() hay thông báo lỗi. Xóa khi 403
seq, nextAttemptNội bộĐời tiến trìnhseq được gieo lại từ thời gian khi khởi động

7. Thuật toán & Luồng nghiệp vụ ​

7.1. Luồng nghiệp vụ chính (happy path) ​

Luồng 1: một chu kỳ bình thường (thu, ghi WAL, gửi, xóa)

mermaid
sequenceDiagram
  participant RT as cycle
  participant ENG as Engine
  participant SND as Sender
  participant WAL as WAL
  participant TRN as Transport
  participant COL as Collector

  RT->>ENG: Collect(now)
  ENG-->>RT: mẫu đã chuẩn hóa
  RT->>SND: Enqueue(now, mẫu)
  SND->>WAL: Append(lô đã marshal)
  WAL-->>SND: id sau khi fsync
  RT->>SND: Tick(ctx)
  SND->>WAL: Oldest()
  WAL-->>SND: Entry gồm id và lô
  SND->>TRN: PostMetrics(gzip của lô)
  TRN->>COL: POST /agent/v1/metrics
  COL-->>TRN: 200 kèm MetricsAck
  SND->>WAL: Remove(id)
  Note over SND: Backoff.Reset, ghi độ lệch đồng hồ, đồng bộ thống kê WAL

Thứ tự luôn là Enqueue rồi Tick: mẫu của chu kỳ được ghi bền trước khi gửi, nên mất mạng hay tắt máy giữa chừng không làm mất lô. Lô rỗng (heartbeat) đi qua đúng đường này.

Luồng 2: gửi bù khi có tồn đọng

mermaid
sequenceDiagram
  participant SND as Sender
  participant WAL as WAL
  participant COL as Collector

  Note over SND: Tick với 6 lô trong hàng đợi và CatchUp bằng 2
  SND->>WAL: Oldest() lô 1
  SND->>COL: POST metrics lô 1
  COL-->>SND: 200
  SND->>WAL: Remove(lô 1)
  SND->>WAL: Oldest() lô 2
  SND->>COL: POST metrics lô 2
  COL-->>SND: 200
  SND->>WAL: Remove(lô 2)
  SND->>WAL: Oldest() lô 3
  SND->>COL: POST metrics lô 3
  COL-->>SND: 200
  Note over SND: Dừng sau 1 cộng CatchUp lô, còn 3 lô cho tick sau

Mỗi tick gửi tối đa 1 + catch_up_batches lô (mặc định 3), cũ nhất trước, và dừng ở lỗi đầu tiên hoặc khi hàng đợi rỗng (TestCatchUpSendsOldestFirstWithinLimit). Xem 7.4.5 về hệ quả với hạn mức tốc độ.

7.2. Luồng thay thế và lỗi ​

Luồng 3: lỗi tạm thời (5xx, 429, mạng, 426) và backoff

mermaid
sequenceDiagram
  participant SND as Sender
  participant BO as Backoff
  participant WAL as WAL
  participant COL as Collector

  SND->>WAL: Oldest()
  SND->>COL: POST metrics
  COL-->>SND: 503 hoặc 429 kèm Retry-After hoặc mất kết nối
  SND->>BO: Next(Retry-After)
  BO-->>SND: thời gian chờ jitter, không dưới Retry-After
  Note over SND: SendFailures tăng, state backoff, nextAttempt bằng now cộng chờ
  Note over SND: Lô ở nguyên trong WAL, Tick sau đó bỏ qua tới nextAttempt
  SND->>COL: POST metrics (tick sau nextAttempt)
  COL-->>SND: 200
  SND->>WAL: Remove(id)
  Note over SND: Backoff.Reset, state ok

Với 426, Sender đặt thêm cờ upgrade trước khi đi cùng đường này. Cờ chỉ xóa sau một lần gửi thành công, và hiện ở String() dạng upgrade_required=true.

Luồng 4: 413, tách lô một lần

mermaid
sequenceDiagram
  participant SND as Sender
  participant WAL as WAL
  participant COL as Collector

  SND->>COL: POST metrics (lô nguyên)
  COL-->>SND: 413
  Note over SND: split theo series, hoặc theo điểm nếu chỉ một series
  SND->>COL: POST nửa 1
  COL-->>SND: 200
  SND->>COL: POST nửa 2
  COL-->>SND: 503
  SND->>WAL: Append(nửa 2 đã marshal) ở cuối hàng đợi
  SND->>WAL: Remove(lô gốc)
  Note over SND: failure cho nhánh 503, backoff, mất thứ tự FIFO

Mỗi nửa chỉ gửi một lần. Nửa vẫn 413 hoặc bị 400, 422 thì bị bỏ và đếm điểm. Nửa gặp lỗi tạm thời được ghi lại vào cuối hàng đợi (sau các lô mới hơn), rồi lô gốc bị xóa. Hai nửa dùng chung seq gốc. Lô không tách được (dưới 2 series và dưới 2 điểm) bị bỏ ngay (OQ-W4).

Luồng 5: 400, 422 (bỏ lô)

mermaid
sequenceDiagram
  participant SND as Sender
  participant WAL as WAL
  participant COL as Collector
  participant ST as Thống kê

  SND->>COL: POST metrics
  COL-->>SND: 400 bad_request hoặc 422 invalid_series
  SND->>ST: DroppedSamples cộng số điểm của lô
  Note over SND: log Error collector rejected a batch, dropping it, kèm seq
  SND->>WAL: Remove(id)
  Note over SND: không backoff, vòng gửi bù tiếp tục với lô kế

Đây là nơi hai rủi ro R-10 và OQ-W2 gặp nhau: lỗi hợp đồng (ví dụ một tên chỉ số lạ làm Collector trả 422) có thể làm rơi cả lô, dù giao thức chỉ yêu cầu bỏ phần lỗi.

Luồng 6: 401, dừng gửi và thăm dò cấu hình

mermaid
sequenceDiagram
  participant SND as Sender
  participant COL as Collector

  SND->>COL: POST metrics
  COL-->>SND: 401 unauthorized
  Note over SND: state unauthorized, nextProbe bằng now cộng 10 phút, log Error
  Note over SND: Enqueue vẫn ghi vào WAL, Tick không gửi metrics
  SND->>COL: GET /config (khi tới nextProbe)
  COL-->>SND: 200 hoặc 304
  Note over SND: log credentials accepted again, state ok
  SND->>COL: POST metrics (cùng tick)
  COL-->>SND: 200

Nếu thăm dò trả 403 thì chuyển sang thu hồi (luồng 7). Mọi lỗi thăm dò khác (401, 5xx, mạng) giữ nguyên trạng thái và hẹn lại sau 10 phút (DefaultProbeInterval). Số lô tích trong WAL bị giới hạn bởi 7.4.2.

Luồng 7: 403, thu hồi vĩnh viễn

mermaid
sequenceDiagram
  participant SND as Sender
  participant COL as Collector
  participant RT as cycle
  participant OS as Tiến trình

  SND->>COL: POST metrics hoặc GET config
  COL-->>SND: 403 agent_revoked
  Note over SND: Token bị xóa khỏi RAM, state agent_revoked, log Error
  RT->>SND: State()
  SND-->>RT: agent_revoked
  RT->>OS: chờ ctx hủy (idle)
  Note over RT: Tiến trình vẫn sống và systemd thấy active, dữ liệu giữ trong WAL

Không có đường tự phục hồi: cần enroll lại rồi khởi động lại dịch vụ (L3 Enroll). Đây là hành vi đã kiểm bằng TestRevokedStopsPermanentlyAndKeepsData và TestRevokedAgentStopsContactingTheCollector.

Luồng 8: khởi động, mở WAL, phục hồi sau mất điện

mermaid
sequenceDiagram
  participant RT as Run
  participant WAL as WAL
  participant FS as Hệ thống tệp

  RT->>WAL: OpenWAL(dir, MaxBytes, MaxAge)
  WAL->>FS: xóa tệp tmp, liệt kê đoạn theo id
  WAL->>FS: quét từng đoạn, kiểm CRC và id tăng
  Note over WAL: Đuôi rách của đoạn cuối bị cắt, đuôi hỏng ở đoạn cũ được giữ và tính là hỏng
  WAL->>FS: đọc checkpoint, đánh dấu id bằng hoặc dưới mốc là xong
  Note over WAL: nextID bằng max của mốc cộng 1 và id lớn nhất cộng 1
  WAL->>WAL: settle, enforce tuổi và dung lượng
  WAL-->>RT: hàng đợi sẵn sàng, log recovered buffered batches from disk

Nếu OpenWAL lỗi (thư mục không ghi được, hết chỗ), openQueue ghi log Error và chuyển sang Memory: agent vẫn chạy nhưng lô mất khi khởi động lại (TestUnusableStateDirFallsBackToMemory, ADR-W02).

Luồng 9: WAL đầy hoặc quá tuổi (loại bỏ lô cũ)

mermaid
sequenceDiagram
  participant SND as Sender
  participant WAL as WAL
  participant FS as Hệ thống tệp

  SND->>WAL: Append(lô mới)
  WAL->>FS: ghi và fsync vào đoạn hiện hành
  WAL->>WAL: enforce(now)
  Note over WAL: Bước 1, đánh dấu xong các bản ghi quá MaxAge từ đầu tới bản ghi tươi đầu tiên
  Note over WAL: Bước 2, khi còn từ 2 đoạn và tổng vượt MaxBytes thì xóa nguyên đoạn cũ nhất
  WAL->>FS: xóa đoạn, ghi checkpoint
  WAL-->>SND: id
  Note over SND: syncStats thấy Dropped tăng, log Warn buffer full, oldest batches dropped

Số lô bị xóa chỉ đi vào log (cả wal dropped old batches ở WAL và buffer full, oldest batches dropped ở Sender) và không vào DroppedSamples (L2 D-10, OQ-W5).

7.3. State machines ​

Trạng thái giao nhận của Sender (một thực thể: Sender.state)

mermaid
stateDiagram-v2
  [*] --> ok
  ok --> backoff : lỗi tạm [5xx, 429, mạng, 426] / SendFailures cộng 1, hẹn nextAttempt
  backoff --> backoff : lỗi tạm [tiếp tục thất bại] / tăng bước chờ
  backoff --> ok : gửi thành công / Backoff.Reset
  ok --> ok : gửi thành công / xóa lô
  ok --> unauthorized : 401 / hẹn nextProbe
  backoff --> unauthorized : 401 / hẹn nextProbe
  unauthorized --> ok : GET config thành công [tới nextProbe] / tiếp tục gửi
  unauthorized --> agent_revoked : 403 khi thăm dò / xóa token
  ok --> agent_revoked : 403 / xóa token
  backoff --> agent_revoked : 403 / xóa token
  agent_revoked --> [*] : tiến trình dừng
Đường (trigger)Nơi hiện thựcKiểm thử
ok sang backofffailure (kRetry), post (5xx, 429, 426, mạng)TestServerErrorKeepsBatchAndBacksOff, TestDroppedConnectionKeepsBatch, TestRetryAfterIsHonored, TestUpgradeRequiredIsFlaggedAndBatchKept
backoff sang oksuccessTestSuccessDeliversAndUpdatesSkew
ok hoặc backoff sang unauthorizedfailure (kUnauthorized)TestUnauthorizedStopsSendingAndProbesConfig
unauthorized sang okprobeTestUnauthorizedStopsSendingAndProbesConfig
bất kỳ sang agent_revokedrevoke (từ failure hoặc probe)TestRevokedStopsPermanentlyAndKeepsData
Đường 400, 422, 413Không đổi trạng thái (bỏ hoặc tách lô rồi xử tiếp)TestBadRequestAndUnprocessableAreDropped, TestTooLargeIsSplitAndSentOnce

Trạng thái halted (identity_mismatch) không thuộc Sender: nó nằm ở Agent.halted (internal/agent/agent.go), chỉ đặt một lần lúc khởi động khi machine_id khác, khi đó Sender chưa được tạo (TestMachineIDMismatchStopsDelivery). Trạng thái Status() hiển thị state=identity_mismatch.

Vòng đời một bản ghi WAL (một thực thể: WalRecord)

mermaid
stateDiagram-v2
  [*] --> live : Append thành công và fsync
  live --> done : Remove(id) [sau ack hoặc quyết định bỏ] / Live giảm
  live --> done : hết tuổi hoặc đoạn bị xóa / Dropped cộng 1
  live --> done : CRC hoặc id sai khi đọc / Dropped cộng 1, log
  done --> deleted : mọi bản ghi đầu đoạn xong / settle xóa tệp, ghi checkpoint
  deleted --> [*]
ĐườngNơi hiện thựcKiểm thử
live sang done do RemoveRemove (BinarySearch trong danh sách bản ghi của đoạn)TestWALFifoRoundTrip, TestWALOutOfOrderRemoveAndUnknownIDs
live sang done do hết tuổi hoặc dung lượngenforceTestWALAgeCapDropsExpiredBatches, TestWALSizeCapDropsOldestSegments
live sang done do hỏngOldest, scanRecordsTestWALDetectsCorruptionAfterOpen, TestWALSkipsRecordWithBadCRCOnRecovery
done sang deletedsettleTestWALRotatesAndDeletesFinishedSegments

Lưu ý: dấu done của bản ghi bị Remove sai thứ tự chỉ nằm trong RAM. Sau khởi động lại chỉ watermark (checkpoint) được khôi phục, nên bản ghi đã ack nhưng nằm sau một bản ghi chưa ack có thể gửi lại (L2 D-15). Trong vận hành bình thường Remove luôn theo thứ tự cũ nhất trước, riêng nhánh tách lô 413 và bản ghi bị bỏ vì hỏng là ngoại lệ.

7.4. Thuật toán, thuật toán cốt lõi ​

7.4.1. Ghi bền và phục hồi WAL ​

Vấn đề: ghi mẫu ra đĩa sao cho mất điện hay kill -9 không làm hỏng hàng đợi và không gửi lại lô đã ack, mà không cần thư viện ngoài.

Giải pháp:

  1. Mỗi lô: nén deflate, ghép tiêu đề 24 byte (độ dài, CRC32C của phần còn lại, id, thời gian ms), ghi một lần vào đoạn hiện hành, Sync. Nếu ghi lỗi giữa chừng thì Truncate về kích thước đoạn trước lần ghi.
  2. Đoạn mới khi size + rec > SegmentBytes (chỉ khi đoạn đã có dữ liệu), tạo bằng O_EXCL, rồi syncDir thư mục để tên đoạn bền.
  3. Ack: Remove chỉ đánh dấu, settle đẩy watermark qua các bản ghi xong ở đầu và xóa đoạn đã xong, sau đó ghi checkpoint bằng tệp tạm, fsync, rename.
  4. Phục hồi: quét từng đoạn, dừng ở bản ghi độ dài 0, độ dài quá 16 MiB, hoặc đuôi cụt. Bản ghi sai CRC hoặc id không tăng bị bỏ qua và đếm hỏng. Đuôi hỏng ở đoạn cuối được cắt, ở đoạn cũ hơn thì giữ lại và cảnh báo. nextID = max(watermark+1, id lớn nhất+1).

Trade-off: fsync mỗi lần Append (mỗi chu kỳ, mặc định 30 s) nên chi phí I/O thấp và độ bền cao. Đổi lại không có định dạng phiên bản (D-15), không fsync thư mục sau rename checkpoint (chỉ gây gửi lặp), và cắt đuôi bằng cách ghi đè đoạn cuối. Đoạn cũ có đuôi hỏng được giữ nên có thể để lại các bản ghi không đọc được ở giữa, Oldest sẽ bỏ qua chúng khi gặp.

7.4.2. Giới hạn tuổi và dung lượng ​

Vấn đề: đĩa không được đầy vì mất mạng dài, nhưng số liệu mới quan trọng hơn số liệu cũ.

Giải pháp: enforce(now) chạy sau mỗi Append, khi mở và mỗi lần Oldest.

  1. Tuổi: đi từ bản ghi cũ nhất, đánh dấu xong mọi bản ghi có thời gian nhỏ hơn now - MaxAge cho tới bản ghi tươi đầu tiên (dừng ở đó, không quét hết).
  2. Dung lượng: khi còn nhiều hơn một đoạn và tổng kích thước vượt MaxBytes, đánh dấu xong toàn bộ đoạn cũ nhất rồi settle (xóa tệp). Lặp lại.
  3. Ghi log Warn một lần mỗi lần loại bỏ, kèm expired và evicted_by_size.

Trade-off: xóa theo đoạn (khoảng 1 MiB, khoảng 2% của 50 MiB) là thô nhưng đơn giản và tránh ghi lại tệp, đổi lại có thể loại nhiều hơn mức cần tối đa một đoạn. Đoạn cuối không bao giờ bị xóa nên trần cứng chỉ không giữ được khi một bản ghi đơn lẻ lớn hơn MaxBytes (không xảy ra với dải cấu hình 4 MiB đến 1 GiB và lô tối đa 1 MiB nén khi gửi, nhưng Append chấp nhận tới 16 MiB). Số bị loại không vào DroppedSamples (D-10). Cơ chế tuổi dựa trên dấu thời gian của bản ghi lúc ghi, không phải thời điểm của các điểm bên trong (xem 7.4.4 về ràng buộc cửa sổ 24 giờ của Collector).

7.4.3. Backoff full jitter và phân pha ​

Vấn đề: hàng nghìn agent không được thử lại đồng loạt sau sự cố, và không được khởi động cùng giây.

Giải pháp: Next(retryAfter): bước hiện tại cur bằng 0 thì lấy Base (5 s), từ Max/2 trở lên thì lấy Max (300 s), ngược lại nhân đôi. Lấy d = rnd() * cur (đều trong [0, cur]), trả về max(d, min(retryAfter, 1 giờ)). Reset đặt cur về 0 sau thành công. PhaseOffset(agentID, interval) là 8 byte đầu của sha256(agentID) (big-endian) lấy dư cho interval, dùng làm độ trễ của timer đầu tiên.

Trade-off: full jitter phân tán tốt nhất nhưng có thể chờ rất ngắn. Vì Tick chỉ chạy mỗi interval, thời gian chờ ngắn hơn interval (ví dụ 5 s, 10 s, 20 s so với 30 s) thực tế không làm chậm thêm: lần thử kế là tick kế. Backoff chỉ có tác dụng thật khi bước chờ vượt interval (từ khoảng bước 40 s đến 80 s trở lên với 30 s) hoặc khi Retry-After lớn. Chu kỳ kế của timer là interval cộng thời gian chu kỳ (timer đặt lại sau cycle), nên pha lệch dần theo thời gian chạy (không ảnh hưởng độ đúng).

7.4.4. Tách lô và dấu thời gian điểm ​

Vấn đề: Collector giới hạn 1 MiB nén, 8 MiB giải nén, 20.000 điểm, 500 series mỗi giờ, và chỉ nhận điểm không cũ hơn 24 giờ và không quá 5 phút trong tương lai.

Giải pháp: kiểm điểm cục bộ (> 20.000 là kTooLarge ngay), nén gzip rồi kiểm 1 MiB (ErrTooLarge). Khi 413 (hoặc cục bộ), split chia đôi theo series (khi có từ 2 series) hoặc theo điểm (một series từ 2 điểm), mỗi nửa gửi một lần. Dấu thời gian điểm là now của chu kỳ thu (INV-W7), không đổi khi gửi lại.

Trade-off: dấu thời gian thu giữ đúng ngữ nghĩa khi gửi bù, nhưng lô bị đệm gần 24 giờ có thể bị Collector loại do cửa sổ thời gian (mặc định buffer.max_age 24 giờ đã bằng ngưỡng của Collector, nên các điểm sát ngưỡng có thể bị dropped_points mà agent chỉ ghi log, OQ-W8). sent_at_ms cũng bằng thời điểm thu chứ không phải lúc gửi, nên độ lệch đồng hồ phía Collector không dựa vào trường này (OQ-W9). Tách lô mất thứ tự FIFO (OQ-W4) nhưng an toàn vì khử trùng theo (series, ts).

7.4.5. Gửi bù và hạn mức tốc độ ​

Vấn đề: sau khi mạng phục hồi, xả nhanh tồn đọng nhưng không vượt hạn mức của Collector (giao thức: metrics 4 yêu cầu mỗi phút mỗi agent, cho phép bùng nổ 10, config 2 mỗi phút, vượt thì 429).

Giải pháp: mỗi tick gửi 1 + catch_up_batches lô (mặc định 3, dải 1 đến 10 cho catch_up_batches). Trạng thái ổn định chỉ có 1 lô mỗi tick (2 yêu cầu mỗi phút ở chu kỳ 30 s, dưới hạn mức 4).

Trade-off (suy ra từ số liệu, chưa đo): khi có tồn đọng ở cấu hình mặc định, mỗi tick dùng tối đa 3 yêu cầu, tức tối đa 6 mỗi phút, vượt 4 mỗi phút bền vững. Bùng nổ 10 cho phép chịu khoảng vài phút (10 chia cho mức vượt 2 mỗi phút, ước khoảng 5 phút), sau đó Collector trả 429 và Sender lùi bước, giữ nguyên lô. Kết quả vẫn không mất dữ liệu (429 là lỗi tạm), nhưng làm mất hiệu quả của gửi bù và có thể làm tăng agent_send_failures_total. Chưa rõ Collector đếm hạn mức theo cửa sổ nào. Đây là OQ-W1. Với catch_up_batches lớn hơn (đến 10) rủi ro này tăng.


8. Xử lý lỗi ​

8.1. Xử lý các nhánh lỗi (error branches) ​

NhánhĐiều kiệnXử lýQuan sát
Thu lỗi tạmEnqueue trả lỗi (marshal hoặc Append lỗi)Ghi log Error cycle failed, Tick vẫn chạy để xả tồn đọngLog
Append bản ghi quá 16 MiBLô nén vượt walMaxRecordTrả lỗi, lô mất, logLog, không có metric
Ghi WAL lỗi (đĩa đầy, I/O)Write hoặc Sync lỗiTruncate về cỡ cũ, trả lỗi, lô của chu kỳ đó mất. Agent tiếp tụcLog cycle failed
WAL không mở đượcOpenWAL lỗiDùng Memory, log ErrorLog. Nên có cảnh báo (OQ-W7 mở rộng)
Bản ghi WAL hỏngCRC, id hoặc giải nén saiBỏ, đếm Dropped, log dropping a corrupt wal recordLog Warn, Stats.Dropped
Lô không giải mã đượcproto.Unmarshal lỗiBỏ, log Error, Remove, thử lô kếLog Error
400, 422Xem 5.3Bỏ lô, cộng DroppedSamples, log Erroragent_dropped_samples_total
413 hoặc quá lớn cục bộXem luồng 4Tách một lầnLog khi bỏ
401Xem luồng 6Dừng gửi, thăm dòLog Error, trạng thái unauthorized
403Xem luồng 7Dừng vĩnh viễnLog Error, Status()
Lỗi tạm (5xx, 429, mạng, 426)Xem luồng 3Backoff, giữ lôagent_send_failures_total, log Warn delivery failed, backing off
Giải mã MetricsAck lỗiThân 200 không hợp lệCoi là lỗi tạm: lô giữ lại và gửi lặp dù Collector đã nhận (an toàn nhờ khử trùng, nhưng tính là lỗi gửi)Log Warn
Checkpoint không ghi đượcLỗi I/OChỉ log, gửi lặp có thể xảy ra sau khởi động lạiLog Warn
Ngữ cảnh bị hủy giữa lúc gửiSIGTERM khi đang PostMetricsLỗi mạng, lô giữ trong WAL, log delivery failed (không phải lỗi thật)Log

8.2. Fail-fast ​

Điều kiệnHành viMã thoát hoặc lỗi
Chưa enroll (không có credentials.json)Run trả ErrNotEnrolled, không chạyExitNotEnrolled (L3 Enroll)
machine_id khác lúc enrollKhông gửi, không thu, chờ ctx hủy, state=identity_mismatchTiến trình vẫn sống (không thoát). Cần enroll lại với --force
Dựng engine lỗi (regex sai)Run trả build collectors: ...Lỗi khởi động (TestBrokenEngineFailsRun)
Tùy chọn client sai (URL sai, tệp CA không đọc được)clientFor lỗi, Run trả lỗiLỗi khởi động (TestNewValidatesOptions). Sau SIGHUP thì chỉ giữ client cũ và log
Không đọc được machine_idBỏ qua kiểm tra, log WarnKhông fail (ưu tiên vận hành)
Không mở được WALKhông fail, dùng RAMXem 8.1

8.3. Race conditions và đồng thời nhẹ ​

Tình huốngCơ chế hoặc hậu quảGhi chú
Sender bị gọi từ nhiều goroutineKhông hỗ trợ: Enqueue và Tick phải cùng goroutineHiện đúng, mọi thứ trong cycle. Trạng thái đọc bởi Status() được mutex bảo vệ
SIGHUP nạp lại cấu hình lúc đang gửiReload thay con trỏ nguyên tử, vòng chính đọc con trỏ ở đầu chu kỳĐổi có hiệu lực từ chu kỳ kế. Không đổi engine hay kích thước WAL
Status() từ tín hiệu trạng tháiMutex trong Agent và SenderAn toàn
Đóng WAL giữa lúc ghiclosed kiểm dưới mutex, trả lỗiTestWALClosedRejectsUse
kill -9 lúc AppendĐuôi rách bị cắt lúc mở lạiTestWALSurvivesKill9
rename checkpoint chưa bền lúc mất điệnWatermark quay về giá trị cũ, các bản ghi đã ack gửi lạiChấp nhận (gửi lặp an toàn)
Hai tiến trình agent cùng state_dirKhông có khóa tệp (flock) trong mã đã đọc, hai tiến trình có thể giẫm lên WALNgoài phạm vi nhưng rủi ro nếu người dùng chạy tay song song với dịch vụ (OQ-W11)

9. Suy thoái dịch vụ & Khả năng phục hồi ​

9.1. Ma trận suy thoái theo phụ thuộc ​

Phụ thuộcLỗiSuy thoáiBảo vệPhục hồi
Collector (mạng, TLS, DNS, 5xx)Không tới đượcTích lô trong WAL, hàng đợi lớn dần tới giới hạnBackoff, WAL bền, giới hạn tuổi và dung lượngTự động: xả bằng gửi bù
Collector quá tải (429)Từ chốiNhư trên, tôn trọng Retry-AfterFull jitter, phân phaTự động
Collector từ chối token (401)Không xác thực đượcDừng gửi, vẫn ghi WAL, thăm dò mỗi 10 phútThăm dò GET /configTự động khi token hợp lệ lại (ví dụ Hub cấp lại). Nếu không thì cần enroll lại
Collector thu hồi (403)Vĩnh viễnNgừng hẳn, không ghi thêmGiữ dữ liệu cũ trên đĩaThủ công: enroll lại và khởi động lại
Đĩa (state_dir) đầy hoặc chỉ đọcGhi WAL lỗi hoặc không mở đượcMất lô của chu kỳ (nếu lỗi lúc chạy), hoặc dùng RAM (nếu lỗi lúc mở)Truncate phục hồi, dự phòng MemorySau khi khởi động lại nếu đĩa ổn
Đồng hồ hệ thống lệch hoặc nhảyDấu thời gian sai, seq có thể lùiĐiểm có thể bị Collector loại (ngoài cửa sổ)Ghi clock_skew_seconds để cảnh báo, chưa hiệu chỉnhThủ công (đồng bộ NTP)
Bộ nhớVượt GOMEMLIMIT 64 MiBGC gắt hơn, hết thì systemd MemoryMax=96M giếtlimits.memory_limit, hàng đợi RAM có trầnSystemd khởi động lại, WAL còn
Engine (thu)Bộ thu lỗiLô thiếu số liệu của bộ thu đóBulkhead theo bộ thu (L3 Collectors)Tự động
ProxyLỗi proxyCoi là lỗi mạngNhư Collector không tới đượcSửa cấu hình rồi SIGHUP (dựng lại client)

9.2. Backup, DR, RTO và RPO ​

WAL là bộ đệm tạm, không phải nguồn sự thật (nguồn sự thật là TSDB của Collector). Không sao lưu WAL. Mất WAL nghĩa là mất số liệu chưa gửi của máy đó, không ảnh hưởng máy khác.

Chỉ sốGiá trịGhi chú
RPO khi mất điệnTối đa lô đang ghi (khoảng một interval, mặc định 30 s)fsync mỗi Append (mã: WAL.Append gọi Sync). Mô phỏng 20 lần mất điện chưa chạy
RPO khi mất kết nốiKhông mất tới 24 giờ hoặc 50 MiB (mặc định), sau đó mất lô cũ nhấtCó thể tăng tới 7 ngày và 1 GiB
RTO của agent sau mất điệnThời gian khởi động cộng quét WAL. Mục tiêu L2 dưới 3 giây (L2-NFR-06)Chưa đo. Thời gian quét tỉ lệ với kích thước WAL (tối đa vài chục MiB)
Khôi phục sau khi mất state_dirEnroll lại (mất credentials.json) và mất WALL3 Enroll. Số liệu chưa gửi mất

10. Đồng thời & Toàn vẹn dữ liệu ​

10.1. Ranh giới giao dịch ​

Không có giao dịch cơ sở dữ liệu. Các đơn vị nguyên tử là thao tác tệp:

Đơn vịRanh giớiĐảm bảo
Ghi một bản ghiMột Write rồi Sync dưới WAL.muHoặc bản ghi đầy đủ, hoặc đuôi rách bị cắt lại lúc mở (CRC phát hiện). Lỗi thì Truncate
Ghi checkpointTệp tạm, fsync, renameĐọc thấy giá trị cũ hoặc mới, không bao giờ nửa vời
Xóa đoạnos.Remove sau khi checkpoint đã ghiMất điện giữa hai bước chỉ để lại đoạn thừa, dọn ở lần mở kế
Gửi một lôPostMetrics rồi RemoveÍt nhất một lần (at-least-once): mất điện giữa hai bước gây gửi lặp (an toàn nhờ khử trùng theo (series, ts))
Tách lô 413Append các nửa chưa gửi, rồi Remove lô gốcKhông nguyên tử: mất điện giữa hai bước có thể để cả lô gốc lẫn nửa đã ghi lại, gây gửi lặp nhẹ (an toàn)

10.2. Cơ chế đồng thời ​

Cơ chếNơi dùngMục đích
Một goroutine cho cycleAgent.RunLoại bỏ tranh chấp giữa thu, xếp hàng, gửi
sync.Mutex trong WALMọi phương thức WALAn toàn khi Status() hay kiểm thử gọi Stats()
sync.Mutex trong Sender (mu)state, upgrade, lastErr, lastSentĐọc bởi Status()
sync.Mutex trong Agent (mu)halted, sendĐọc bởi Status()
atomic.Pointer[config.Config]Agent.cfgNạp lại cấu hình không khóa
atomic trong StatsBộ đếmĐọc bởi bộ thu tự thân
Không dùng khóa tệpstate_dir/walXem OQ-W11
contextMọi lời gọi mạng và vòng gửi bùDừng êm khi SIGTERM

11. Bảo mật ​

11.1. Bảo mật ba lớp ​

LớpBiện phápBằng chứng
Kênh truyềnTLS tối thiểu 1.2, xác minh chứng chỉ (bỏ qua chỉ khi insecure_skip_verify bật rõ), ghim CA từ tệp PEM, HTTP/2, không theo redirect (tránh gửi token tới máy chủ khác), giới hạn thân phản hồi 1 MiBTestRedirectsAreNotFollowed, TestNewValidatesOptions
Danh tính và bí mậtBearer token chỉ trong RAM ở Sender và header. Lỗi mạng chỉ chứa URL, thông điệp lỗi API cắt 200 ký tự, String() không có tokenTestTokenNeverReachesTheLogs, TestNetworkErrorsNeverContainTheToken
Dữ liệu nghỉWAL thư mục 0700, tệp 0600, tài khoản accesshub-agent. Lô không chứa bí mật, không mã hóa ở trạng thái nghỉTestWALFilesAreOwnerOnly

11.2. Đường ống phân quyền năm bước ​

Cụm này không có yêu cầu vào. Với các yêu cầu ra tới Collector, năm bước theo trách nhiệm:

BướcAi làmGhi chú
1. Xác thực kênhCụm này (TLS, CA)Từ chối chứng chỉ không hợp lệ
2. Xác thực agentCollector (kiểm token)401 hoặc 403
3. Ràng buộc danh tínhCollector (gắn company_id, server_id từ token, không tin nhãn từ agent)Agent không thể giả nhãn (L3 Collectors INV-2)
4. Giới hạn tốc độCollector (429)Cụm này lùi bước
5. Kiểm tra nội dungCollector (catalog, cửa sổ thời gian)400 hoặc 422

11.3. Điểm neo zero-trust ​

Điểm neoÝ nghĩa
Không lắng nghe0 cổng mở (L2-NFR-08), chỉ client HTTP
Không tin redirect và không tin Collector là duy nhấtRedirect không theo. collector_url từ xa chưa được dùng (D-11)
Token không rời RAM và headerKhông log, không String(), xóa khi 403
Danh tính máyLệch machine_id thì dừng gửi (FR-W14), để ảnh máy ảo nhân bản không mạo danh
Ghim CA tùy chọnca_file dành cho CA riêng. insecure_skip_verify chỉ để thử nghiệm, và check-config đã in cảnh báo WARNING khi cờ này bật (printSummary trong internal/cli/checkconfig.go, ĐÃ HIỆN THỰC)

12. Cấu hình & Tinh chỉnh ​

12.1. Tunables ​

Nguồn cấu hình và thứ tự ưu tiên (mặc định, YAML, AH_*, cờ CLI) thuộc L3 Enroll, Credentials, Config. Bảng dưới chỉ liệt kê tham số tác động lên WAL và Sender. SIGHUP nạp lại tệp cục bộ: interval và catch_up_batches có hiệu lực từ chu kỳ kế, thông số Collector (URL, CA, proxy, send_timeout) dựng lại client (Reload, TestReloadSwitchesCollector). Riêng buffer.* chỉ được đọc lúc OpenWAL khi khởi động (WAL không đổi kích thước lúc chạy), và limits.memory_limit chỉ áp dụng lúc khởi động.

Tham sốMặc định (dải hợp lệ)Ý nghĩaMục liên quan
interval30 s (10 s đến 300 s)Chu kỳ thu và gửi. Cũng là mốc của PhaseOffset7.4.3, NFR-W06
send_timeout15 s (1 s đến 1 phút)Hạn cho một yêu cầu HTTP (ResponseHeaderTimeout và Timeout của client)Mục 5
buffer.max_bytes50 MiB (4 MiB đến 1 GiB)Trần dung lượng WAL, loại theo đoạn khi vượt7.4.2, NFR-W01
buffer.max_age24 h (1 phút đến 7 ngày)Tuổi tối đa của bản ghi WAL7.4.2, NFR-W01
catch_up_batches2 (1 đến 10)Số lô gửi bù thêm mỗi tick7.4.5, OQ-W1
limits.memory_limit64 MiB (16 MiB đến 4 GiB)debug.SetMemoryLimit (bỏ qua nếu đã đặt GOMEMLIMIT)9.1
collector_urlkhông có (bắt buộc, phải hợp lệ khi kiểm tra cấu hình)Đích gửi11.1
ca_file, proxy_url, insecure_skip_verifyrỗng, rỗng, falseCA riêng, proxy tường minh (mặc định lấy từ biến môi trường), bỏ kiểm chứng chỉ (chỉ để thử nghiệm)11.1
state_dirDefaultStateDir() theo nền tảngWAL nằm ở <state_dir>/wal, cạnh credentials.jsonMục 6

Tên khóa lấy từ internal/config/types.go, giá trị mặc định và dải từ defaults.go và validate.go. Ưu tiên nguồn cấu hình ở L3 Enroll, Credentials, Config.

Hằng số biên dịch (không cấu hình được)

HằngGiá trịVai trò
walMaxRecord16 MiBĐộ dài payload nén tối đa của một bản ghi
walDefaultSegment1 MiBKích thước đoạn WAL
DefaultProbeInterval10 phútChu kỳ thăm dò cấu hình khi unauthorized
Backoff.Base, Backoff.Max, MaxRetryAfter5 s, 300 s, 1 giờLịch backoff và trần Retry-After
MaxCompressedBytes, MaxPointsPerBatch1 MiB, 20.000Giới hạn gửi (theo giao thức)
maxResponseBytes1 MiBTrần thân phản hồi đọc vào bộ nhớ
TLSHandshakeTimeout, IdleConnTimeout, MaxIdleConns10 s, 90 s, 2Tham số kết nối
TLS tối thiểu1.2Không cấu hình được

12.2. Feature flags ​

Không có cờ tính năng nào cho WAL hoặc Sender. Tham số allow_remote_config được phân tích và mặc định true nhưng chưa được dùng (L2 D-01, D-11): OnConfigETag của Sender không được nối trong agent.go, thân 200 của GET /config khi thăm dò bị bỏ qua, nên không có cấu hình từ xa nào được áp dụng.


13. Telemetry & Vận hành ​

13.1. Metrics ​

Cụm này không có endpoint /metrics. Số liệu tự thân do bộ thu self (L3 Collectors) phát vào chính luồng gửi đi, kèm cùng dữ liệu ở trường AgentStats của mỗi lô. Mọi chỉ số là gauge, kể cả tên có _total (chứa tổng tích lũy từ lúc khởi động, Collector phải xử lý như bộ đếm).

TênNguồn cập nhậtNgữ nghĩaGhi chú
agent_wal_bytessyncStats sau Append và Remove (từ Queue.Stats)Byte đang có trên đĩaAgentStats.wal_bytes
agent_wal_batchesnhư trênSố lô chưa gửiAgentStats.wal_batches. Đây là "độ sâu hàng đợi"
agent_send_failures_totalfailure() nhánh lỗi tạmSố lần gửi thất bại tạm thờiKhông tăng khi 401, 403, 400, 422 (chúng có đường riêng). AgentStats.send_failures_total
agent_dropped_samples_totalEngine loại mẫu, cộng điểm của lô bị bỏ ở 400, 422 hoặc không tách đượcMẫu bị mất vì lỗi hợp đồng hoặc danh mụcKhông gồm lô bị WAL xóa vì đầy hoặc quá tuổi (D-10, OQ-W5). AgentStats.dropped_samples_total
agent_clock_skew_secondssuccess() khi ack có thời gian máy chủLệch đồng hồ so với CollectorAgentStats.clock_skew_seconds

Khoảng trống (ĐỀ XUẤT, OQ-W5): chưa có chỉ số riêng cho số lô bị WAL xóa và số lần vào trạng thái backoff, unauthorized, agent_revoked. Trạng thái giao nhận hiện chỉ nhìn được qua log và Status().

13.2. Log schema ​

Log dùng slog qua logx (định dạng text mặc định, json cấu hình được, ra stderr cho journald hoặc tệp xoay 10 MiB giữ 1 bản sao). maskingHandler che token và trường có tên chứa token, authorization, password, secret. Trường chung: time, level, msg.

Sự kiện (msg)MứcTrường thêmKhi nào
agent startedInfoversion, collector, ...Khởi động
recovered buffered batches from diskInfobatches, bytesMở WAL thấy lô tồn
cannot open the disk buffer, batches will be kept in memory only and lost on restartErrorerrDự phòng RAM
wal recovered with damageWarncorrupt_records, truncated_bytesPhục hồi thấy hỏng
wal segment has an unreadable tail, keeping the valid recordsWarnsegmentĐoạn cũ có đuôi hỏng
wal checkpoint is unreadable, acknowledged batches may be sent againWarnCheckpoint không đọc được
cannot store the wal checkpoint, acknowledged batches may be sent again after a restartWarnerrGhi checkpoint lỗi
cannot delete a finished wal segmentWarnsegment, errXóa đoạn xong lỗi
wal dropped old batchesWarnexpired, evicted_by_sizeLoại theo tuổi hoặc dung lượng
dropping a corrupt wal recordWarnidOldest gặp bản ghi hỏng
buffer full, oldest batches droppedWarnbatchesSender thấy Dropped tăng
delivery failed, backing offWarnerr, waitLỗi tạm
collector rejected a batch, dropping itErrorseq, points, err400, 422, hoặc không tách được
collector rejected the agent token, will retry periodicallyError401
credentials accepted again, resuming deliveryInfoThăm dò thành công
agent revoked by the collector, delivery stopped permanentlyError403
dropping unreadable buffered batchErrorerrLô trong WAL không giải mã được
collector warning, collector dropped pointsWarnwarning, pointsAck có cảnh báo hoặc điểm bị loại (OQ-W8)
requeue split batchErrorerrGhi lại nửa lô lỗi
machine id changed since enrollment (cloned or restored image), delivery stopped, re-enroll with --forceErrorLệch machine_id
cycle failedErrorerrLỗi thu hoặc ghi WAL
statusInfostateNhận SIGUSR1

Quan sát: thông điệp mã bằng tiếng Anh cố định, có thể dùng làm khóa cho luật cảnh báo log.

13.3. Alert to runbook ​

Cụm này không tự phát cảnh báo. Các điều kiện dưới đây do Collector hoặc hệ thống giám sát đánh giá (ĐỀ XUẤT, ngưỡng chưa hiệu chỉnh vì chưa có số đo thực tế).

Điều kiện (đề xuất)Runbook
agent_wal_batches tăng liên tục nhiều chu kỳCollector hoặc mạng có vấn đề: kiểm tra curl tới Collector từ máy, xem log delivery failed và trường err. Chưa mất dữ liệu khi còn dưới 24 giờ và 50 MiB
agent_wal_bytes gần buffer.max_bytesSắp mất lô cũ: khôi phục kết nối, hoặc tăng buffer.max_bytes rồi khởi động lại
Log wal dropped old batches hoặc buffer full xuất hiệnĐã mất số liệu (D-10). Đánh dấu khoảng trống khi phân tích. Xem OQ-W5 về việc thiếu chỉ số
agent_dropped_samples_total tăngLỗi hợp đồng: xem log collector rejected a batch, so phiên bản agent với catalog của Collector (R-10)
agent_clock_skew_seconds lớn (trên 5 phút là ngưỡng cửa sổ tương lai của Collector)Điểm bị loại: đồng bộ NTP trên máy
Log collector rejected the agent tokenToken bị vô hiệu hoặc rotate: Hub cấp lại hoặc enroll lại
Log agent revokedEnroll lại với token mới rồi khởi động lại dịch vụ
Log machine id changedMáy nhân bản: enroll lại với --force
Không có lô nào tới Collector trong vài chu kỳ và không thấy log lỗiKiểm systemctl status, SIGUSR1 để in trạng thái. Tiến trình agent_revoked vẫn active (OQ-W7 liên quan)

13.4. Probes ​

Không có endpoint sức khỏe (cụm không mở cổng). Hai kênh quan sát cục bộ:

KênhNội dung
Tín hiệu SIGUSR1Ghi log status với chuỗi Agent.Status(): version, uptime, collector, interval, và delivery=<state> queued=N [upgrade_required=true], hoặc state=identity_mismatch khi dừng vì lệch máy
Lệnh accesshub-agent statusChưa phải chẩn đoán đầy đủ (stub, AGT-10, L3 Enroll, Credentials, Config)

Sức khỏe từ xa: Collector suy ra từ việc lô đến đều (mỗi lô là heartbeat kể cả khi rỗng).

13.5. Trace propagation ​

Mỗi yêu cầu gắn X-Request-Id (16 ký tự hex ngẫu nhiên) và X-AH-Agent-Version. Chưa có trace phân tán. X-Request-Id chưa được ghi vào log phía agent, nên chưa thể đối chiếu trực tiếp log hai phía trừ khi Collector ghi lại (ĐỀ XUẤT, OQ-W13). Mã tương quan bền của lô là seq, xuất hiện trong log khi lô bị bỏ.


14. Kế hoạch kiểm thử ​

Loại Test & Phạm viÁnh xạ mục tiêuMục tiêu kỹ thuậtVí dụ kịch bản (Test ID)
Unit: WAL ghi, đọc, xóaAC-W01, W02, W07FIFO, khởi động lại không gửi lại lô đã ack, xóa sai thứ tựTestWALFifoRoundTrip, TestWALOldestDoesNotRemove, TestWALSurvivesRestartWithoutResendingAcknowledged, TestWALOutOfOrderRemoveAndUnknownIDs
Unit: WAL giới hạnAC-W03, W04, NFR-W01Xoay đoạn, loại theo tuổi và theo dung lượngTestWALRotatesAndDeletesFinishedSegments, TestWALSizeCapDropsOldestSegments, TestWALAgeCapDropsExpiredBatches, TestWALAgeCapAppliesOnOpenAndOldest
Phục hồi và hỏngAC-W05, W06, NFR-W02Cắt đuôi rách, bỏ CRC sai, phát hiện hỏng sau khi mở, chịu kill -9TestWALSurvivesKill9, TestWALTruncatesTornTail, TestWALSkipsRecordWithBadCRCOnRecovery, TestWALDetectsCorruptionAfterOpen, FuzzWALScanRecords
Quyền tệp, vòng đời và hàng đợi RAMAC-W08, W090700 và 0600, từ chối dùng sau khi đóng, RAM giữ lô mới nhấtTestWALFilesAreOwnerOnly, TestWALClosedRejectsUse, TestMemoryEvictsOldestWhenFull, TestMemoryKeepsNewestEvenWhenOversized
Sender: gửi và giao nhậnAC-W10 đến W17, W19, W20, NFR-W03, W08Thành công, lỗi tạm, Retry-After, 426, 401, 403, 400, 422, 413, gửi bùTestSuccessDeliversAndUpdatesSkew, TestServerErrorKeepsBatchAndBacksOff, TestDroppedConnectionKeepsBatch, TestRetryAfterIsHonored, TestUpgradeRequiredIsFlaggedAndBatchKept, TestUnauthorizedStopsSendingAndProbesConfig, TestRevokedStopsPermanentlyAndKeepsData, TestBadRequestAndUnprocessableAreDropped, TestTooLargeIsSplitAndSentOnce, TestCatchUpSendsOldestFirstWithinLimit, TestUnreadableEntryIsDropped, TestSequenceNumbersIncreaseAndSeedFromTime
Gọi lại OnConfigETagAC-W18Sender gọi lại một lần mỗi etag mới. Mới kiểm ở mức Sender, agent.go chưa nối (D-01)TestConfigETagCallbackFiresOncePerChange
TransportAC-W21 đến W26, NFR-W04, W05Header giao thức, không theo redirect, token không lộ, phân tích lỗi, ETag, tùy chọn saiTestRedirectsAreNotFollowed, TestTokenNeverReachesTheLogs, TestNetworkErrorsNeverContainTheToken, TestGetConfigETag, TestNewValidatesOptions
Backoff và phaAC-W27, W28, NFR-W06, W07Lịch nhân đôi, jitter trong khoảng, pha ổn địnhTestScheduleDoublesToCapWithFullJitter, TestPhaseOffsetIsStableBoundedAndSpread
Tích hợp AgentAC-W29 đến W32, NFR-W09Chưa enroll, lệch máy, thu hồi, khởi động lại giữ lô, dự phòng RAM, engine hỏngTestRunWithoutCredentialsIsNotEnrolled, TestMachineIDMismatchStopsDelivery, TestRevokedAgentStopsContactingTheCollector, TestBufferedBatchesSurviveRestart, TestUnusableStateDirFallsBackToMemory, TestBrokenEngineFailsRun
Chưa có: 20 lần mất điện mô phỏngNFR-W02Tiêu chí thoát giai đoạn 1 của L2-NFR-07ĐỀ XUẤT: kịch bản chạy lặp kill -9 và cắt đuôi ngẫu nhiên trên máy thử (OQ-W6, OQ-W7)
Chưa có: hạn mức tốc độ so với gửi bùNFR-W11Khẳng định tốc độ trung bình dưới 4 yêu cầu mỗi phút khi có tồn đọngĐỀ XUẤT: kiểm thử với Collector giả có token bucket (OQ-W1)
Chưa có: đo dừng êm 10 giâyNFR-W09Dừng êm trong lúc gửiĐỀ XUẤT: kiểm thử SIGTERM khi máy chủ giả chậm (OQ-W7)
Chưa có: thứ tự khi tách lôAC-W13Xác nhận hành vi hàng đợi khi nửa lô ghi lạiĐỀ XUẤT (OQ-W4)
Chưa có: FuzzWALScanRecords trong CINFR-W02make fuzz và fuzz-smoke chỉ chạy FuzzLoadNeverPanics. Bộ fuzz WAL chỉ chạy như kiểm thử hạt giống trong go testĐỀ XUẤT: thêm vào make fuzz (OQ-W12)
Chưa có: khóa đồng thời state_dirChưa có bất biến tương ứngHai tiến trình cùng thư mụcĐỀ XUẤT (OQ-W11)

Kiểm thử chạy cả với -race trong CI (go test -race -count=1 ./..., cần cgo).


15. Trình tự triển khai ​

15.1. Ma trận milestone ​

MilestoneNội dungPhụ thuộcĐóng góp nghiệm thu
M1WAL bền theo đoạn, CRC, checkpoint, hàng đợi RAM dự phòng. ĐÃ HIỆN THỰCKhôngAC-W01 đến W09
M2Transport HTTPS, Backoff, PhaseOffset. ĐÃ HIỆN THỰCKhôngAC-W21 đến W28
M3Sender: gửi, phân loại trạng thái, tách lô, thăm dò, thu hồi, gửi bù. ĐÃ HIỆN THỰCM1, M2AC-W10 đến W20
M4Nối Agent: vòng chu kỳ, openQueue, kiểm machine_id, SIGHUP. ĐÃ HIỆN THỰCM1, M2, M3AC-W29 đến W32
M5Khép khoảng trống quan sát và độ bền: chỉ số lô bị WAL xóa và trạng thái giao nhận, fsync thư mục checkpoint, khóa state_dir, nối OnConfigETag, xả khi tắt, tách lô giữ FIFO. ĐỀ XUẤTM4AC mới (chưa có)
M6Khép rủi ro hợp đồng: giảm gửi bù theo hạn mức (OQ-W1), bỏ phần lỗi thay cả lô (OQ-W2), dùng dropped_points của ack. ĐỀ XUẤT, cần quyết định phía CollectorM4 và CollectorAC mới (chưa có)
M7Đo và mô phỏng: 20 lần mất điện, dừng êm 10 giây, fuzz WAL trong CI. THIẾT KẾ CHƯA XÂYM4NFR-W02, W09

15.2. Sơ đồ phụ thuộc milestone ​

mermaid
flowchart LR
  M1["M1 · WAL"]
  M2["M2 · Transport và Backoff"]
  M3["M3 · Sender"]
  M4["M4 · Nối Agent"]
  M5["M5 · Khép độ bền"]
  M6["M6 · Khép hợp đồng"]
  M7["M7 · Đo và mô phỏng"]
  M1 --> M3
  M2 --> M3
  M3 --> M4
  M4 --> M5
  M4 --> M6
  M4 --> M7
  style M1 fill:#2d4a3e,stroke:#5fb37a,color:#fff
  style M2 fill:#2d4a3e,stroke:#5fb37a,color:#fff
  style M3 fill:#2d4a3e,stroke:#5fb37a,color:#fff
  style M4 fill:#2d4a3e,stroke:#5fb37a,color:#fff
  style M5 fill:#3a3320,stroke:#d9b84a,color:#fff
  style M6 fill:#3a3320,stroke:#d9b84a,color:#fff
  style M7 fill:#444,stroke:#aaa,color:#fff

Chú giải: xanh lá là đã hiện thực, vàng là đề xuất, xám là thiết kế chưa xây. Đường găng tới các khoản còn lại là M1 hoặc M2, M3, M4 rồi mới đến M5, M6, M7 (các khoản này độc lập nhau).


Phụ lục A: Open Questions ​

#Câu hỏiHành vi tạm thờiOwnerMã theo dõi
OQ-W1Gửi bù tối đa 3 yêu cầu mỗi tick (6 mỗi phút) vượt hạn mức bền vững 4 mỗi phút của giao thức. Có cần giảm mức gửi bù hoặc nhận biết 429 để tự hãm không?Chạy nguyên, 429 được xử lý như lỗi tạm và lùi bước, không mất dữ liệuchưa chỉ địnhL3-WAL-OQ1
OQ-W2422 làm rơi cả lô, trong khi giao thức ghi "bỏ phần lỗi". Collector có trả chi tiết phần lỗi để agent gửi lại phần còn lại không?Bỏ cả lô, đếm điểm vào DroppedSampleschưa chỉ địnhL3-WAL-OQ2
OQ-W3426 hiện chỉ đặt cờ và giữ lô để lùi bước, trong khi giao thức nói "tiếp tục gửi nếu có thể". Chọn hành vi nào?Giữ lô, backoff, hiện upgrade_required=true ở Status()chưa chỉ địnhL3-WAL-OQ3
OQ-W4Nửa lô của lần tách 413 ghi vào cuối hàng đợi, phá thứ tự FIFO, hai nửa chung seq. Có cần ghi lại ở đầu hoặc đổi seq?Chấp nhận, an toàn nhờ khử trùng theo (series, ts)chưa chỉ địnhL3-WAL-OQ4
OQ-W5Lô bị WAL xóa (đầy hoặc quá tuổi) không vào DroppedSamples, chỉ có log. Có thêm chỉ số riêng không?Chỉ log (D-10)chưa chỉ địnhL3-WAL-OQ5
OQ-W6WAL chưa có phiên bản định dạng (D-15). Khi đổi định dạng thì nâng cấp thế nào?Không có. Đổi định dạng cần xóa hoặc chuyển đổi WALchưa chỉ địnhL3-WAL-OQ6
OQ-W7Không xả lô khi tắt và giới hạn dừng 10 giây chưa được đo (D-16). Có cần bước xả có hạn, và có nên thoát hẳn khi bị thu hồi thay vì ngủ?Dừng ngay, lô ở lại WAL. Tiến trình bị thu hồi vẫn activechưa chỉ địnhL3-WAL-OQ7
OQ-W8dropped_points, warnings chỉ được log và ack_seq bị bỏ qua. Có dùng để hiệu chỉnh gửi hoặc cảnh báo không?Chỉ logchưa chỉ địnhL3-WAL-OQ8
OQ-W9sent_at_ms bằng thời điểm thu, không phải lúc gửi. Có đổi thành lúc gửi để Collector đo độ trễ tồn đọng không?Giữ nguyênchưa chỉ địnhL3-WAL-OQ9
OQ-W10Mã ngoài bảng (409, 404, 3xx) rơi vào nhánh thử lại, có thể lặp vô hạn với lô không bao giờ được nhận. Có cần bỏ lô sau số lần nhất định?Thử lại với backoff, WAL bị giới hạn tuổi và dung lượngchưa chỉ địnhL3-WAL-OQ10
OQ-W11Không có khóa tệp trên state_dir. Hai tiến trình cùng thư mục có thể làm hỏng WAL. Có cần flock?Không xử lý, dựa vào dịch vụ systemd một bảnchưa chỉ địnhL3-WAL-OQ11
OQ-W12FuzzWALScanRecords không nằm trong make fuzz hay CI fuzz-smoke. Có thêm không?Chỉ chạy như hạt giống trong go testchưa chỉ địnhL3-WAL-OQ12
OQ-W13X-Request-Id không được ghi vào log agent, khó đối chiếu hai phía. Có ghi vào log khi lỗi không?Không ghichưa chỉ địnhL3-WAL-OQ13

Phụ lục B: ADR nội bộ ​

Mã ADRQuyết địnhTrạng tháiĐộng lực
ADR-W01WAL tự hiện thực bằng thư viện chuẩn (đoạn, CRC32C, deflate, checkpoint), không dùng thư viện lưu trữ ngoàiĐã hiện thựcKhông thêm phụ thuộc, tệp nhị phân nhỏ, hành vi kiểm soát được. Đánh đổi: tự chịu trách nhiệm về độ bền (OQ-W6, OQ-W12)
ADR-W02Dự phòng hàng đợi RAM khi không mở được WALĐã hiện thựcƯu tiên agent vẫn chạy và gửi được. Đánh đổi: mất lô khi khởi động lại, chỉ có log Error báo (mục 8.1)
ADR-W03Một goroutine cho thu, xếp hàng và gửi, không có gửi nềnĐã hiện thựcLoại tranh chấp, đơn giản, xác định. Đánh đổi: lần gửi chậm làm trễ chu kỳ kế, tốc độ gửi bị chặn bởi interval
ADR-W04WAL nén deflate, phần truyền nén gzipĐã hiện thực (D-09)Deflate nhẹ cho tệp, gzip là yêu cầu của giao thức. Đánh đổi: nén hai lần khác nhau khi gửi lại từ đĩa
ADR-W05Giao nhận at-least-once, dựa vào khử trùng theo (series, ts) phía TSDBĐã hiện thựcCho phép gửi lặp an toàn nên không cần ack hai pha hay fsync thư mục checkpoint
ADR-W06Loại theo đoạn nguyên khi WAL đầy, bỏ cũ giữ mớiĐã hiện thựcSố liệu mới quan trọng hơn. Đơn giản hơn ghi lại tệp. Đánh đổi: thô, mất tới một đoạn
ADR-W07Thu hồi (403) dừng vĩnh viễn nhưng tiến trình không thoátĐã hiện thựcTránh systemd khởi động lại liên tục khi bị thu hồi. Đánh đổi: giám sát dịch vụ vẫn thấy active (OQ-W7)
ADR-W08400, 422 bỏ cả lô, không thử lạiĐã hiện thực, cần xác nhận (OQ-W2)Lô hỏng không bao giờ được nhận, thử lại chỉ tốn tài nguyên. Đánh đổi: mất cả phần hợp lệ
ADR-W09Tách lô 413 một lần, ghi nửa lỗi vào cuối hàng đợiĐã hiện thực, cần xác nhận (OQ-W4)Không bao giờ lặp tách. Đánh đổi: mất FIFO

Phụ lục C: Section Profile ​

Phân mụcHồ sơ quy chuẩnTrạng thái điềnGiải trình
§0 Metadata & Sign-offBắt buộcĐã điềnNgười ký để "chưa chỉ định"
§1 ScopeBắt buộcĐã điền
§2 Yêu cầuBắt buộcĐã điền
§3 Kiến trúcBắt buộcĐã điền
§4 Domain modelBắt buộcĐã điền
§5 API contractBắt buộcĐiền thu gọnCụm là client, không có API mạng phục vụ. Hợp đồng gồm interface Go và HTTPS gọi ra
§6 Data schemaTùy chọnĐã điềnLược đồ tệp WAL và checkpoint thay cho CSDL
§7 Thuật toánBắt buộcĐã điền
§8 Xử lý lỗiBắt buộcĐã điền
§9 Suy thoáiBắt buộcĐã điền9.2 mô tả WAL là bộ đệm tạm, không sao lưu
§10 Đồng thờiBắt buộcĐiền thu gọnKhông có giao dịch CSDL, dùng ranh giới thao tác tệp
§11 Bảo mậtBắt buộcĐiền thu gọn11.2 chỉ mô tả phía gọi ra, không có yêu cầu vào
§12 Cấu hìnhBắt buộcĐã điềnKhông có feature flag hoạt động
§13 TelemetryBắt buộcĐiền thu gọnKhông có probe mạng và trace phân tán. 13.5 chỉ có X-Request-Id
§14 Kiểm thửBắt buộcĐã điềnCó 6 khoảng trống đề xuất
§15 Triển khaiBổ sungĐã điền
Trang này có giúp được bạn không?
Sửa trang này

Nội dung đồng bộ từ kho mã access-hub-agent lúc 15:42, 03/10/2026. Khi tài liệu và mã khác nhau, mã thắng.