SIENTIAPDE-1975: align the gateway write path with the Laborious
- GW1: write commands (C2) carry only the server_id; ServerConfigResolver resolves the connection (command override, live pooled connection, connectors, opc-servers) with a 60 s cache and a 10 s negative cache. - GW2: write_status keeps the worst outcome (ok < error < session_bad, pending/NULL replaced), same rule as the Laborious, built with SQLAlchemy Core (no SQL formatting). - GW3: update_write_status never raises; outcome (updated, kept, row_not_found, invalid, error) logged and counted (opc_write_status_update_total); runs off the event loop. - GW4: the reason of a failed write is logged; opc_write_status_by_model shows the last outcome; a bad timestamp is a failed write, not an exception. - GW5: the NATS first connect gives up after 5 s (nats-py's defaults block the main loop for about 4 minutes while NATS is down); reconnection after it is unbounded; idle_sweep restarts a connection closed for good; stop() is safe on a closed connection. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
36
README.md
36
README.md
@@ -56,7 +56,8 @@ Gateway for on-demand and command-driven interactions with OPC UA servers:
|
||||
|
||||
- **NATS write consumer**: consumes write commands from `opc.write` (see
|
||||
`NATS_WRITE_SUBJECT`) and executes them against the target OPC server
|
||||
(`ingestor/adapters/nats_consumer.py`, `ingestor/engines/write.py`).
|
||||
(`ingestor/adapters/nats_consumer.py`, `ingestor/engines/write.py`). See
|
||||
[Write commands](#write-commands-c2-and-write_status-c3) below.
|
||||
- **On-demand gRPC `ReadTag`**: a gRPC server (`ingestor/adapters/grpc_server.py`,
|
||||
`ingestor/proto/opc_gateway.proto`) for synchronous, single-tag reads.
|
||||
- **Scheduled active reads**: a polling read engine (`ingestor/engines/active_read.py`)
|
||||
@@ -70,6 +71,36 @@ See `docs/deploy-notes.md` for the deployment-facing details (env vars,
|
||||
health probe, NetworkPolicy, cert cache) and the `# OPC Gateway` block in
|
||||
`.env.example` for the full list of new environment variables.
|
||||
|
||||
### Write commands (C2) and `write_status` (C3)
|
||||
|
||||
The Laborious publishes one command per tag (SIENTIAPDE-1975):
|
||||
|
||||
```json
|
||||
{"server_id": "1", "addr": "ns=2;s=Pred", "value": 1.25, "data_type": "float",
|
||||
"model_id": "2", "model_name": "sin-approx", "schema": "sientia_data",
|
||||
"table_name": "predictions", "timestamp": "2026-10-13T12:00:00-03:00",
|
||||
"write_timestamp": false, "write_quality": false}
|
||||
```
|
||||
|
||||
- **Server connection**: the command carries only `server_id`. The connection is
|
||||
resolved by `ServerConfigResolver` (`ingestor/connection/servers.py`): `url`/
|
||||
`server_uri`/`use_security` in the command when present (override), else the
|
||||
live pooled connection opened by the reads, else the `connectors` document
|
||||
(`type: opc_ua`, `status: Active`), else the `opc-servers` document
|
||||
(`use_security = bool(cert_path)`) — the orchestrator's rule for the read slots.
|
||||
Lookups are cached for 60 s. An unknown server is a failed write (`server_not_found`).
|
||||
- **Outcome**: `UPDATE ... SET write_status` on the prediction row (by `timestamp` +
|
||||
`model_id`), keeping the worst outcome: `ok` < `error` < `session_bad`, and
|
||||
`pending`/NULL are replaced by any answer. The Laborious applies the same rule to
|
||||
the outcomes it records itself, so the order of the answers does not matter.
|
||||
The UPDATE never raises; its outcome (`updated`, `kept`, `row_not_found`,
|
||||
`invalid`, `error`) is logged and counted in `opc_write_status_update_total`.
|
||||
The reason of a failed write is logged by `WriteEngine`.
|
||||
- **NATS connection**: the first connection gives up after 5 s (it runs on the main
|
||||
loop: nats-py's defaults would block it for minutes while NATS is down); once
|
||||
connected, it reconnects forever. A connection closed for good is started again
|
||||
by `GatewayRuntime.idle_sweep`.
|
||||
|
||||
## 📋 Prerequisites
|
||||
|
||||
- Python 3.11+
|
||||
@@ -290,6 +321,9 @@ The OPC Ingestor exposes comprehensive Prometheus metrics for monitoring:
|
||||
### OPC Gateway Metrics
|
||||
- `opc_write_total`: Active write attempts (NATS-driven), by result
|
||||
- `opc_write_latency_seconds`: Active write latency
|
||||
- `opc_write_status_by_model`: Last write outcome per model (1 = ok, 0 = failed; label `status`)
|
||||
- `opc_write_status_update_total`: `write_status` UPDATEs, by result
|
||||
(`updated`, `kept`, `row_not_found`, `invalid`, `error`)
|
||||
- `opc_active_read_total`: Scheduled/on-demand read attempts, by result
|
||||
- `opc_error_by_type_total`: OPC errors by type (connection, conversion, session, ...)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user