# Kiến trúc hệ thống

> Cách X-Edge AI box được lắp ghép — các dịch vụ, plugin datasource, pipeline suy luận, sự kiện thời gian thực và ghi log.

Một X-Edge AI box chạy một stack ba dịch vụ tự chứa. Mọi thứ — cấu hình, phiên bản
mô hình, lịch sử, log — đều nằm trên box; không có phụ thuộc cloud nào ở thời điểm
chạy.

```mermaid
flowchart LR
  subgraph Box["X-Edge AI Box (Docker)"]
    FE["Frontend\nnginx + React"] -->|REST + SignalR| BE["Backend\nASP.NET orchestration"]
    BE -->|gRPC :50051| INF["Inference runtime\nPython + ONNX Runtime"]
    BE --- DB[("SQLite\nconfig · history · logs")]
    PLUG["Datasource plugins\nOPC UA · MQTT · CSV"] --- BE
  end
  SENSORS["PLCs · brokers · devices"] --> PLUG
  PLUG -->|predictions written back| SENSORS
  USER["Operator browser"] --> FE
```

| Dịch vụ | Vai trò | Cổng |
|---|---|---|
| Frontend | Giao diện bảng điều khiển, phục vụ bởi nginx | 80 (443 với TLS) |
| Backend | Điều phối: datasource, mô hình, vòng đời suy luận, xác thực, lưu trữ | 5000 |
| Inference | Thực thi mô hình ONNX trên CPU/CUDA/TensorRT | 50051 (gRPC) |

## Plugin datasource

Các giao thức công nghiệp được hiện thực dưới dạng **plugin** được backend nạp lúc
khởi động. Mỗi plugin mô tả các thiết lập kết nối của nó dưới dạng schema có kiểu,
mà UI render thành một biểu mẫu động — thêm một giao thức không bao giờ đòi hỏi
build lại UI.

- **Đầu vào (đường đọc):** OPC UA (poll mỗi tick), MQTT (push kèm bộ bảo vệ dữ liệu
  cũ), và phát lại CSV.
- **Đầu ra (đường ghi):** OPC UA, MQTT, và các file CSV xoay vòng. Địa chỉ của một
  tag đầu ra được dùng nguyên văn (một topic MQTT, một OPC UA NodeId, một cột CSV),
  và mỗi giá trị được co giãn theo `value × scale + offset`.

Hai đường này hỏng theo cách khác nhau **một cách có chủ đích**: một tag *đầu vào*
chết sẽ dừng suy luận một cách rõ ràng (cấp dữ liệu cũ cho mô hình là kiểu hỏng tệ
nhất đối với phát hiện bất thường), trong khi một sink *đầu ra* lỗi không bao giờ
làm nghẽn suy luận — thay vào đó nó phát một thông báo **Ghi đầu ra thất bại** đã
được gộp.

## Pipeline suy luận

Một datasource đầu vào được bật cấp dữ liệu cho một mô hình được ghép cặp:

1. Backend lấy mẫu các tag đã ánh xạ theo **tốc độ lấy mẫu** của datasource và
   truyền chúng tới runtime suy luận qua gRPC.
2. Runtime duy trì một cửa sổ trượt gồm `window size × tag count` mẫu; khi đầy, mỗi
   mẫu mới tạo ra một dự đoán
   (`health_score`, `failure_probability`, `rul_normalized`).
3. Các dự đoán chảy ngược lại và phân tán tới bảng điều khiển (SignalR), kho lịch
   sử, các sink đầu ra đã cấu hình, và audit trail SQLite trên box.

Do đó thông lượng co giãn theo **tốc độ lấy mẫu × số datasource**, không theo tốc độ
của bên publish — một cảm biến 100 Hz được lấy mẫu ở 10 Hz cho ra 10 dự đoán/giây.

**Backpressure** theo từng consumer: các bảng điều khiển chậm không bao giờ được
phép làm nghẽn suy luận (sự kiện của chúng bị bỏ trước), các sink đầu ra nhận một
hàng đợi chặn không mất dữ liệu, làm chậm pipeline end-to-end khi một sink không
theo kịp, và các dự đoán không hữu hạn (NaN/Inf) bị lọc và đếm. Tất cả những thứ
này đều hiển thị dưới dạng bộ đếm — xem [Giám sát](/vi/operate/monitoring/).

## Vòng đời mô hình

Các mô hình được soạn ngoài box với [modelctl](/vi/configure/prepare-models/)
(convert → validate → quantize → package), rồi được upload qua UI. Việc upload xác
thực hợp đồng ONNX qua runtime suy luận (shape + SHA-256) mà không nạp nó; mô hình
chỉ vào engine khi datasource của nó được **bật**. Nạp là một hot swap không thời
gian chết, và các phiên bản là bất biến — upload lại một file đã thay đổi dưới cùng
phiên bản sẽ bị từ chối. Rollback kích hoạt lại phiên bản hợp lệ trước đó.

## Sự kiện thời gian thực

Các cập nhật bảng điều khiển trực tiếp (lô cảm biến, dự đoán, tình trạng, thay đổi
trạng thái, thông báo) đi qua một hub WebSocket SignalR. Luồng này là
**fire-and-forget và trong bộ nhớ** — nó không bao giờ là nguồn sự thật. Nếu một
trình duyệt ngắt kết nối, các sự kiện bị bỏ lỡ sẽ không được phát lại; trạng thái
có thẩm quyền luôn đến từ REST API và cơ sở dữ liệu trên box.

Backplane sự kiện nằm trong bộ nhớ, điều này chỉ đúng **cho triển khai box đơn
node**. Đừng chạy hai instance backend đối với một box (ví dụ sau một load
balancer) — các client sẽ âm thầm bỏ lỡ sự kiện. Một fleet gồm nhiều box thì không
sao: mỗi box là một hệ thống đơn node của riêng nó.

## Ghi log — ba lớp có giới hạn

| Lớp | Mục đích | Giới hạn |
|---|---|---|
| stdout của container (`docker logs`) | Gỡ lỗi trực tiếp | 10 MB × 3 file mỗi dịch vụ |
| File xoay vòng trên volume bền vững (Warning+) | Điều tra hậu sự cố — sống sót qua việc tạo lại container và hỏng cơ sở dữ liệu | 10 MB × 7 (backend) / × 6 (inference) |
| Kho log SQLite | Trang LogViewer và cảnh báo thông báo | 10.000 mục, quét mỗi 60 giây |

Các dòng pipeline theo từng tick được ghi ở mức Debug; luồng Info mặc định chỉ mang
các sự kiện vòng đời, heartbeat và cảnh báo.

## Nhập / xuất cấu hình

Admin có thể xuất toàn bộ cấu hình suy luận (các datasource đầu vào và đầu ra bao
gồm ánh xạ tag) thành một phong bì JSON có phiên bản và nhập lại nó trên một box
khác. Secret được che khi xuất; việc nhập chạy trong một giao dịch duy nhất — bất
kỳ lỗi theo từng mục nào cũng roll back toàn bộ file và trả về danh sách lỗi đầy đủ.

## Bước tiếp theo

  - [Tổng quan sản phẩm](/vi/product-overview/) — Box làm gì và dành cho ai.
  - [Kết nối nguồn dữ liệu đầu vào](/vi/configure/input-datasources/) — Đấu nối OPC UA, MQTT hoặc CSV vào pipeline.
  - [Giám sát](/vi/operate/monitoring/) — Phân rã độ trễ, bộ đếm backpressure và tình trạng.
  - [gRPC Inference API](/vi/api-reference/grpc-inference/) — Hợp đồng giữa backend ↔ runtime suy luận.
