You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
docs(storage): document Bulk write semantics, retries, and data-loss cases
Update the storage best-practice docs to reflect the BulkIndexer-based
remote write path: flush triggers (5 MB / 1 s / shutdown), two-tier
retry policy (whole-batch 429/5xx with exponential backoff; per-item
failures dropped via OnFailure), the three data-loss scenarios, and
the four problem classes this design addresses (TLS handshake CPU,
RTT ceiling, transient jitter, capture/backend decoupling).
The HUATUO Storage module runs on each node. It writes kernel events captured by the Tracer concurrently to the local directory and to Elasticsearch or OpenSearch. Both storage backends share the same `[Storage.ES]` configuration interface and are differentiated by address.
392
+
The HUATUO Storage module runs on each node. It writes kernel events captured by the Tracer to the local directory and to Elasticsearch or OpenSearch. Both backends share the same `[Storage.ES]` configuration interface and are differentiated by address.
393
+
394
+
The remote write path uses the ES/OpenSearch **Bulk API** (`_bulk`): events are queued in an in-memory buffer and submitted in batches by background workers based on size and time thresholds, with transport-layer retries on transient failures.
393
395
394
396
```mermaid
395
397
graph TB
@@ -401,7 +403,7 @@ graph TB
401
403
subgraph huatuo["HUATUO Agent (node-level)"]
402
404
T["Tracer Layer"]
403
405
L["Local Directory\nhuatuo-local/"]
404
-
S["Storage Module\n(concurrent write)"]
406
+
S["Storage Module\nBulkIndexer Buffer"]
405
407
end
406
408
407
409
subgraph backends["Storage Backends"]
@@ -412,41 +414,80 @@ graph TB
412
414
kernel --> T
413
415
T --> L
414
416
T --> S
415
-
S -->|Index API| ES
416
-
S -->|Index API| OS
417
+
S -->|Bulk API + auto retry| ES
418
+
S -->|Bulk API + auto retry| OS
417
419
```
418
420
419
421
### Write Flow
420
422
421
-
After the Tracer captures a kernel event, the Storage module writes it concurrently to the local directory and the remote storage backend. The two write paths execute in parallel — the local directory retains a copy while the remote backend provides durable storage and query capabilities.
423
+
`Save` returns immediately after the event is buffered. Background workers flush the buffer to the remote backend when **any** of the following triggers fire: byte threshold, time threshold, or process shutdown. The local directory write is synchronous and independent of the remote Bulk path.
422
424
423
425
```mermaid
424
426
sequenceDiagram
425
427
participant T as Tracer Layer
426
428
participant L as Local Directory (huatuo-local/)
427
-
participant S as Storage Module
429
+
participant S as Storage Module (BulkIndexer)
428
430
participant B as ES / OpenSearch
429
431
430
432
T->>S: Kernel event captured, serialized to JSON
431
-
par concurrent write
433
+
par Local path (sync)
432
434
S->>L: Write to local file
433
-
and
434
-
S->>B: Write to remote storage (Index API)
435
-
B-->>S: Write acknowledged (200 OK)
435
+
and Remote path (async batch)
436
+
S->>S: Enqueue into bulk buffer, return immediately
437
+
Note over S: Flush on 5 MB / 1 s / shutdown
438
+
S->>B: POST /_bulk (multiple records)
439
+
B-->>S: 200 OK + per-item results
440
+
Note over S: Failed items reported via OnFailure callback
436
441
end
437
442
```
438
443
439
-
### Storage Pipeline
444
+
### Bulk Write Mechanism
440
445
441
-
From kernel event to storage backend, the process involves three stages: capture, serialization, and concurrent write. The local directory and remote backend are written to in parallel without blocking each other.
446
+
#### Buffering and Flush Triggers
442
447
443
-
```mermaid
444
-
flowchart LR
445
-
A([Kernel Event]) --> B["Tracer Capture\nSerialize to JSON"]
446
-
B --> C["Storage Module\n(concurrent write)"]
447
-
C --> D["Write to Local Directory\nhuatuo-local/"]
|**Whole-batch retry**| Transport error (connect / timeout / TLS)<br>HTTP status: `429 / 502 / 503 / 504`| Client retries with exponential backoff: 100 ms → 200 ms → 400 ms → 800 ms, up to **3 attempts**| ✅ auto |
462
+
|**Whole-batch reject**| HTTP status: `400 / 401 / 403 / 404 / 413`, etc. | Not retried; all records in the batch are dropped, an error is logged via `OnError`| ❌ drop |
463
+
|**Per-item failure**| 200 OK with per-item error: version conflict, mapping error, document too large| Not retried; only the failed item is dropped, `OnFailure` logs `index/id/status/type/reason`| ❌ drop |
464
+
|**Per-item success**| 200 OK with per-item success | Considered durably indexed | — |
465
+
466
+
**Why this design**: 429/5xx and transport errors signal transient remote unavailability where retries are effective; 4xx (except 429) and per-item errors are client-side semantic issues (data shape, permissions) where retries would only amplify the failure — they should be surfaced via logs for human investigation.
467
+
468
+
#### Data-Loss Scenarios
469
+
470
+
In all three scenarios below, `Save` returns `nil` but the event never reaches the index:
471
+
472
+
1.**Abnormal process exit**: `SIGKILL` or host power loss drops whatever is still buffered in the BulkIndexer (the local directory still keeps a copy).
473
+
- Mitigation: SIGTERM/SIGINT trigger graceful shutdown; `Close` force-flushes the buffer with a 10 s deadline.
474
+
2.**Whole-batch permanent rejection**: 4xx (non-429) errors discard every record in the batch. Common causes: disabled index, expired credentials, document exceeding the cluster's `http.max_content_length`.
475
+
- Diagnosis: `OnError` log includes ES's `type` and `reason`.
476
+
3.**Permanent per-item failure**: mapping conflict, version conflict, malformed document.
477
+
- Diagnosis: `OnFailure` log identifies the record by `index/id`.
478
+
479
+
> **The local directory is always a fallback**: even if remote writes are lost, events remain available in `huatuo-local/` as the eventual-consistency safety net.
480
+
481
+
#### Problems This Solves
482
+
483
+
Replacing per-event Index API calls with a buffered BulkIndexer + auto-retry addresses four classes of problems:
484
+
485
+
| Problem | Old approach bottleneck | Bulk approach improvement |
|**TLS handshake CPU cost**| One HTTPS handshake per event saturated CPU under FIPS/RSA-PSS | Many events share one connection and one handshake; TLS PSK tickets cached |
488
+
|**Remote RTT throughput ceiling**| One round-trip per event capped node-level write rate | One Bulk request carries up to 5 MB; throughput scales with batch size |
489
+
|**Transient remote jitter / 429 throttle**| A single failure dropped the event with no retry | Client-level retry absorbs short-lived faults |
490
+
|**Decoupling tracer layer from backend**| Slow remote backed pressure into capture, delaying tracing | Async buffer decouples capture from network — capture is no longer blocked on remote latency |
0 commit comments