docs: design gb32960 service split
This commit is contained in:
@@ -0,0 +1,404 @@
|
||||
# GB32960 Service Split Design
|
||||
|
||||
Date: 2026-06-23
|
||||
|
||||
## Goal
|
||||
|
||||
Split the current all-in-one vehicle ingest runtime into lower-coupled, higher-cohesion services. The first production slice focuses on GB/T 32960 because it is the active high-pressure path: persistent TCP connections, protocol ACKs, raw frame volume, history queries, and downstream analytics are currently assembled in one `bootstrap-all` process.
|
||||
|
||||
The target outcome is:
|
||||
|
||||
- Keep GB32960 ingest reliable and fast.
|
||||
- Move disk writes and history queries out of the protocol process.
|
||||
- Move statistics and analysis out of the protocol process.
|
||||
- Use Kafka as the durable boundary between ingest, history, and analytics.
|
||||
- Preserve `bootstrap-all` during migration as a local development and rollback entry point.
|
||||
|
||||
## Decision
|
||||
|
||||
Use ACK semantics A:
|
||||
|
||||
> A GB32960 frame is ACKed successfully after the protocol service parses/accepts it and writes the required Kafka message(s) successfully.
|
||||
|
||||
The protocol service must not wait for raw archive, DuckDB/Parquet writes, snapshot generation, or statistics computation before sending the GB32960 response frame.
|
||||
|
||||
## Service Boundaries
|
||||
|
||||
### `gb32960-ingest-app`
|
||||
|
||||
Purpose: own GB32960 connection handling and Kafka production.
|
||||
|
||||
Responsibilities:
|
||||
|
||||
- Listen on the GB32960 TCP port.
|
||||
- Handle platform login, VIN authorization, TLS if enabled, and session state required for protocol responses.
|
||||
- Decode GB32960 frames and apply vendor extension parsing.
|
||||
- Produce raw frame records and normalized event records to Kafka.
|
||||
- ACK successful reports only after Kafka production succeeds.
|
||||
- Publish malformed or rejected frames to a DLQ when possible.
|
||||
- Expose operational health and metrics for connections, decode rate, Kafka latency, ACK latency, and DLQ count.
|
||||
|
||||
Non-responsibilities:
|
||||
|
||||
- No local raw archive writes.
|
||||
- No DuckDB or Parquet history writes.
|
||||
- No snapshot query API.
|
||||
- No vehicle statistics or analysis.
|
||||
- No long-running analytical calculations.
|
||||
|
||||
Initial module dependencies:
|
||||
|
||||
- `ingest-api`
|
||||
- `ingest-core`
|
||||
- `ingest-codec-common`
|
||||
- `session-core`
|
||||
- `protocol-gb32960`
|
||||
- `sink-mq`
|
||||
- `observability`
|
||||
|
||||
### `vehicle-history-app`
|
||||
|
||||
Purpose: own raw storage, historical index storage, and history query APIs.
|
||||
|
||||
Responsibilities:
|
||||
|
||||
- Consume GB32960 raw records and normalized event records from Kafka.
|
||||
- Write raw `.bin` payloads through `sink-archive`.
|
||||
- Write queryable event indexes through `event-file-store`.
|
||||
- Provide APIs for raw frame lookup, decoded frame lookup, history query, and snapshot/source-frame lookup.
|
||||
- Rebuild or backfill the DuckDB sidecar index from stored Parquet files when needed.
|
||||
- Track consumer lag, write latency, archive failures, index failures, and query latency.
|
||||
|
||||
Non-responsibilities:
|
||||
|
||||
- No TCP protocol listener.
|
||||
- No GB32960 ACK decisions.
|
||||
- No online statistics computation.
|
||||
- No direct coupling to `gb32960-ingest-app` internals.
|
||||
|
||||
Initial module dependencies:
|
||||
|
||||
- `ingest-api`
|
||||
- `sink-mq`
|
||||
- `sink-archive`
|
||||
- `event-file-store`
|
||||
- `event-history-service`
|
||||
- `protocol-gb32960` only for raw-frame decode/query support
|
||||
- `observability`
|
||||
|
||||
### `vehicle-analytics-app`
|
||||
|
||||
Purpose: own vehicle state, daily statistics, alarms, and later analytics.
|
||||
|
||||
Responsibilities:
|
||||
|
||||
- Consume normalized vehicle event records from Kafka.
|
||||
- Maintain latest vehicle state if enabled.
|
||||
- Compute daily vehicle statistics and alarm-derived metrics.
|
||||
- Persist analytical outputs to the selected backend.
|
||||
- Support reprocessing from Kafka offsets when analytical logic changes.
|
||||
- Track consumer lag, per-VIN processing latency, aggregation failures, and output write latency.
|
||||
|
||||
Non-responsibilities:
|
||||
|
||||
- No protocol listener.
|
||||
- No GB32960 ACK decisions.
|
||||
- No raw archive writes.
|
||||
- No normal-path dependency on raw archive or history APIs.
|
||||
|
||||
Initial module dependencies:
|
||||
|
||||
- `ingest-api`
|
||||
- `sink-mq`
|
||||
- `vehicle-state-service`
|
||||
- `vehicle-stat-service`
|
||||
- `observability`
|
||||
|
||||
## Kafka Contract
|
||||
|
||||
Kafka is the service boundary. Messages must be stable enough that `vehicle-history-app` and `vehicle-analytics-app` do not depend on protocol service internals.
|
||||
|
||||
### Topics
|
||||
|
||||
Use versioned topics:
|
||||
|
||||
- `vehicle.raw.gb32960.v1`
|
||||
- `vehicle.event.gb32960.v1`
|
||||
- `vehicle.dlq.gb32960.v1`
|
||||
|
||||
The current topic names under `lingniu.ingest.sink.mq.topics` can remain temporarily for compatibility, but the split apps should converge on versioned topic names.
|
||||
|
||||
### Keys
|
||||
|
||||
Use VIN as the Kafka key whenever a VIN is available.
|
||||
|
||||
Benefits:
|
||||
|
||||
- Preserves per-vehicle ordering within a partition.
|
||||
- Lets history and analytics scale by partition.
|
||||
- Keeps stateful analytics simpler.
|
||||
|
||||
When VIN is unavailable, use a stable fallback key such as platform account plus peer address plus receive time bucket.
|
||||
|
||||
### Raw Record
|
||||
|
||||
Topic: `vehicle.raw.gb32960.v1`
|
||||
|
||||
Minimum fields:
|
||||
|
||||
- `schemaVersion`
|
||||
- `protocol = GB32960`
|
||||
- `eventId`
|
||||
- `receiveTime`
|
||||
- `eventTime` if decoded
|
||||
- `peer`
|
||||
- `platformAccount`
|
||||
- `vin` if decoded
|
||||
- `command`
|
||||
- `protocolVersion`
|
||||
- `rawBytes`
|
||||
- `checksumStatus`
|
||||
- `decodeStatus`
|
||||
- `metadata`
|
||||
|
||||
Consumers:
|
||||
|
||||
- `vehicle-history-app` writes archive `.bin` files and raw-frame query indexes.
|
||||
- Future replay tools can regenerate normalized events from raw records.
|
||||
|
||||
### Normalized Event Record
|
||||
|
||||
Topic: `vehicle.event.gb32960.v1`
|
||||
|
||||
Minimum fields:
|
||||
|
||||
- `schemaVersion`
|
||||
- `protocol = GB32960`
|
||||
- `eventId`
|
||||
- `sourceRawEventId`
|
||||
- `receiveTime`
|
||||
- `eventTime`
|
||||
- `platformAccount`
|
||||
- `vin`
|
||||
- `command`
|
||||
- `eventType`
|
||||
- `normalizedFields`
|
||||
- `vendorFields`
|
||||
- `alarmFields`
|
||||
- `metadata`
|
||||
|
||||
Consumers:
|
||||
|
||||
- `vehicle-history-app` writes queryable history records.
|
||||
- `vehicle-analytics-app` updates state and statistics.
|
||||
|
||||
### DLQ Record
|
||||
|
||||
Topic: `vehicle.dlq.gb32960.v1`
|
||||
|
||||
Minimum fields:
|
||||
|
||||
- `schemaVersion`
|
||||
- `stage`
|
||||
- `errorCode`
|
||||
- `errorMessage`
|
||||
- `receiveTime`
|
||||
- `peer`
|
||||
- `platformAccount` if known
|
||||
- `vin` if known
|
||||
- `rawBytes` when available
|
||||
- `metadata`
|
||||
|
||||
Typical stages:
|
||||
|
||||
- `FRAME_DECODE`
|
||||
- `BODY_PARSE`
|
||||
- `AUTH`
|
||||
- `KAFKA_PRODUCE`
|
||||
- `ACK_WRITE`
|
||||
|
||||
## ACK and Failure Semantics
|
||||
|
||||
### Success Path
|
||||
|
||||
1. Receive TCP bytes.
|
||||
2. Decode a complete GB32960 frame.
|
||||
3. Validate checksum/auth/session rules.
|
||||
4. Build raw and normalized Kafka records.
|
||||
5. Produce required Kafka records successfully.
|
||||
6. Send GB32960 success ACK.
|
||||
|
||||
### Kafka Produce Failure
|
||||
|
||||
If required Kafka production fails, the service must not send a success ACK.
|
||||
|
||||
Allowed behavior:
|
||||
|
||||
- Retry within a bounded timeout.
|
||||
- Return a GB32960 failure response when the protocol allows it.
|
||||
- Close the channel after repeated produce failures.
|
||||
- Emit local error logs and metrics.
|
||||
|
||||
Do not silently ACK frames that have not crossed the Kafka durability boundary.
|
||||
|
||||
### History or Analytics Failure
|
||||
|
||||
Failures in `vehicle-history-app` or `vehicle-analytics-app` must not affect GB32960 ACKs. They are handled by Kafka offset retry, DLQ, operational alerting, and backfill/replay.
|
||||
|
||||
## Current Coupling to Remove
|
||||
|
||||
`bootstrap-all` currently assembles protocol modules, Kafka producer/consumer, archive sink, event file store, history API, vehicle state, vehicle statistics, command gateway, and multiple inbound protocols in one application.
|
||||
|
||||
The split should remove these production couplings:
|
||||
|
||||
- Protocol runtime directly containing archive and event-file-store writers.
|
||||
- Protocol runtime directly containing statistics processors.
|
||||
- History query API sharing the same process as TCP ingest.
|
||||
- Kafka consumer workers living in the same all-in-one app as protocol listeners.
|
||||
- Operational scaling tied to one JVM for ingest, storage, queries, and analytics.
|
||||
|
||||
## Migration Plan
|
||||
|
||||
### Phase 1: Add Split App Entrypoints
|
||||
|
||||
Add new app modules:
|
||||
|
||||
- `modules/apps/gb32960-ingest-app`
|
||||
- `modules/apps/vehicle-history-app`
|
||||
- `modules/apps/vehicle-analytics-app`
|
||||
|
||||
Keep `modules/apps/bootstrap-all` unchanged as the fallback runtime.
|
||||
|
||||
### Phase 2: GB32960 Ingest App
|
||||
|
||||
Create a production profile for `gb32960-ingest-app`:
|
||||
|
||||
- Enable `protocol-gb32960`.
|
||||
- Enable Kafka producer.
|
||||
- Disable local archive.
|
||||
- Disable event-file-store.
|
||||
- Disable history API.
|
||||
- Disable vehicle state/stat services.
|
||||
- Disable unrelated protocols.
|
||||
|
||||
Acceptance:
|
||||
|
||||
- GB32960 TCP port starts.
|
||||
- Platform login works.
|
||||
- Realtime report frames are parsed.
|
||||
- Kafka records are produced with VIN keys.
|
||||
- ACK is sent only after Kafka success.
|
||||
|
||||
### Phase 3: History App
|
||||
|
||||
Create `vehicle-history-app`:
|
||||
|
||||
- Enable Kafka consumer.
|
||||
- Enable archive sink.
|
||||
- Enable event-file-store.
|
||||
- Enable event-history HTTP API.
|
||||
- Disable protocol listeners.
|
||||
- Disable analytics processors.
|
||||
|
||||
Acceptance:
|
||||
|
||||
- Consumes `vehicle.raw.gb32960.v1`.
|
||||
- Writes raw `.bin` files.
|
||||
- Consumes `vehicle.event.gb32960.v1`.
|
||||
- Writes Parquet/DuckDB indexes.
|
||||
- Queries can locate raw frames and decoded snapshots.
|
||||
|
||||
### Phase 4: Analytics App
|
||||
|
||||
Create `vehicle-analytics-app`:
|
||||
|
||||
- Enable Kafka consumer.
|
||||
- Enable vehicle state if a state backend is configured.
|
||||
- Enable vehicle statistics.
|
||||
- Disable protocol listeners.
|
||||
- Disable archive and event-file-store.
|
||||
|
||||
Acceptance:
|
||||
|
||||
- Consumes `vehicle.event.gb32960.v1`.
|
||||
- Maintains expected per-VIN state/stat outputs.
|
||||
- Can be restarted and resume from Kafka offsets.
|
||||
- Does not affect ingest ACK latency.
|
||||
|
||||
### Phase 5: Parallel Run and Cutover
|
||||
|
||||
Run old and new paths in parallel for a bounded validation window.
|
||||
|
||||
Compare:
|
||||
|
||||
- Received frame count.
|
||||
- Kafka produced record count.
|
||||
- Raw archive file count.
|
||||
- Unique VIN count.
|
||||
- History query count and sample query correctness.
|
||||
- Analytics output count and sample values.
|
||||
- GB32960 ACK latency before and after split.
|
||||
|
||||
Cut over only after counts and sample queries match within the agreed tolerance.
|
||||
|
||||
## Operational Guidance
|
||||
|
||||
Scale services independently:
|
||||
|
||||
- Scale `gb32960-ingest-app` by connection count and Kafka produce latency.
|
||||
- Scale `vehicle-history-app` by disk throughput, consumer lag, and query latency.
|
||||
- Scale `vehicle-analytics-app` by consumer lag and computation latency.
|
||||
|
||||
Monitor:
|
||||
|
||||
- Kafka producer error rate and p99 produce latency.
|
||||
- ACK p99 latency.
|
||||
- TCP active connections.
|
||||
- Decode failures and DLQ counts.
|
||||
- Consumer lag per group.
|
||||
- Archive write latency and failure rate.
|
||||
- Event-file-store flush latency and failure rate.
|
||||
- Analytics processing latency.
|
||||
|
||||
## Risks and Mitigations
|
||||
|
||||
Risk: Kafka outage blocks GB32960 ACKs.
|
||||
|
||||
Mitigation: bounded producer retry, clear failure ACK/close behavior, Kafka cluster monitoring, and optional local emergency spool only as a later explicit design.
|
||||
|
||||
Risk: Two Kafka topics for raw and normalized events can diverge.
|
||||
|
||||
Mitigation: include `eventId` and `sourceRawEventId`, produce records transactionally if required, or publish a single envelope containing both raw and normalized sections in the first implementation if transaction support is not ready.
|
||||
|
||||
Risk: History app reprocessing duplicates archive/index records.
|
||||
|
||||
Mitigation: deterministic archive keys and idempotent index writes keyed by `eventId` or `rawArchiveUri`.
|
||||
|
||||
Risk: Analytics logic changes require backfill.
|
||||
|
||||
Mitigation: keep raw and normalized topics with sufficient retention; allow analytics consumers to reset offsets or run a backfill group.
|
||||
|
||||
Risk: Query APIs accidentally depend on ingest internals.
|
||||
|
||||
Mitigation: history APIs should depend on `event-file-store`, `sink-archive`, and decode libraries only, not `gb32960-ingest-app`.
|
||||
|
||||
## Open Decisions
|
||||
|
||||
These are intentionally left for implementation planning:
|
||||
|
||||
- Whether Kafka production uses two independent sends or a transaction for raw + normalized records.
|
||||
- Exact protobuf/JSON schema shape for `vehicle.raw.gb32960.v1` and `vehicle.event.gb32960.v1`.
|
||||
- Whether latest snapshot state belongs only to `vehicle-analytics-app` or is also materialized by `vehicle-history-app` for query convenience.
|
||||
- Initial Kafka partition count and retention duration.
|
||||
- Whether command downlink stays with `command-gateway` or gets a separate command service later.
|
||||
|
||||
## Completion Criteria
|
||||
|
||||
The split is complete when:
|
||||
|
||||
- `gb32960-ingest-app`, `vehicle-history-app`, and `vehicle-analytics-app` are separate runnable app modules.
|
||||
- `gb32960-ingest-app` can receive live GB32960 data and ACK after Kafka success.
|
||||
- `vehicle-history-app` can consume Kafka and provide raw/history/snapshot query APIs.
|
||||
- `vehicle-analytics-app` can consume Kafka and compute state/stat outputs.
|
||||
- `bootstrap-all` remains available for development and rollback.
|
||||
- Verification covers build, app startup, Kafka production/consumption, raw archive writes, history queries, analytics outputs, and ACK latency.
|
||||
Reference in New Issue
Block a user