diff --git a/docs/superpowers/specs/2026-06-23-gb32960-service-split-design.md b/docs/superpowers/specs/2026-06-23-gb32960-service-split-design.md new file mode 100644 index 00000000..687bcaf3 --- /dev/null +++ b/docs/superpowers/specs/2026-06-23-gb32960-service-split-design.md @@ -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.