Architecture
QUBIQ is a distributed system that ships as one file. Understanding that sentence explains most of its behaviour.
One binary, many processes
One file contains every service. Starting it with no arguments boots the supervisor, which:
- Starts a message bus that nothing outside the machine can reach.
- Brings the configuration database up to date, before anything else runs.
- Starts each service as its own process, named for the service it runs —
QBQ-CoreServer,QBQ-Historianand so on, which is how they appear in Task Manager orps. - Supervises them — restarting a service that dies, and reporting health.
Why it is built this way
| Property | Consequence |
|---|---|
| One file to distribute | No container orchestration and no dependency matrix. |
| Separate OS processes | A protocol driver that crashes or leaks cannot take the web server with it. The supervisor restarts just that service. |
| Bus-only inter-service traffic | Services do not call each other over HTTP and do not share memory. Adding a service does not open a port. |
| Loopback + random port | The bus is not addressable from the network. There is no broker port to firewall or authenticate. |
| Single migration point | The supervisor migrates before spawning children, so services never race each other on schema. |
The trade-off is deliberate: you cannot spread services across machines. QUBIQ targets a site-scoped deployment, not a cluster.
The services
| Service | Owns |
|---|---|
| CoreServer | The only public HTTP surface: REST API, web UI, authentication, the configuration database, all repositories and handlers. |
| RealtimeGateway | WebSocket fan-out of telemetry and status to browsers. |
| Engine | Pipeline execution — scheduling and running node graphs. |
| Bridge | The pipeline runtime and its protocol adapters; owns the DAG processor and persisted pipeline state. |
| Historian | Time-series ingestion into QuestDB, rollups, retention, ownership and schema repair. |
| Alarm | Alarm condition evaluation, the ISA-18.2 state machine, the journal. |
| Audit | Writes the tamper-evident security journal to an external SQL database. |
| ConnMonitor | Connection liveness — passive observation with an active probe as a safety net. |
| OpcWorker / MqttWorker / ModbusWorker / SnmpWorker / TcpUdpWorker / DbWorker | Protocol-specific execution, one process per family. |
| RestGateway | Serves REST ingress endpoints that pipelines define. |
| ScriptGateway | Hosts script execution, including the Python workers. |
| ImportService | Bulk namespace/tag import. |
| AI | The AI assistant's tool execution. |
Only CoreServer, RealtimeGateway, RestGateway and ScriptGateway bind ports. Everything else is reachable only over the bus.
→ The gateway and its services
Data flow, device to screen
Points worth internalising:
- Scaling happens once, at the edge. A raw count becomes an engineering value before it is published, so history, alarms and screens all see the same number.
- Deadband is applied before publish. Noise is dropped at the source, not filtered by each consumer.
- The browser subscribes to what is on screen. Fan-out cost tracks visible tags, not namespace size.
Configuration versus runtime state
QUBIQ keeps a hard line between the two, and it explains where things are stored.
| Configuration | Runtime state | |
|---|---|---|
| Examples | Connections, tags, bindings, alarm definitions, views, pipelines, users | Live values, quality, alarm state, execution logs, history |
| Stored in | The configuration database | The live state store, QuestDB, memory |
| Survives restart | Yes | History and alarm state yes; live values are re-read |
| In backups | Yes | No |
A tag row never holds a value. That separation is what lets configuration be exported, diffed, restored on a different machine and audited.
Storage
| Store | Contains | Notes |
|---|---|---|
| Configuration database | All configuration | Embedded, with full-text search, encrypted at rest. |
| QuestDB | Tag history | External, designated by a single system setting so two historians cannot be configured. |
| External SQL | The audit journal | Deliberately outside the app, so QUBIQ cannot rewrite its own audit trail. |
| Project directories | Views, scripts, named queries, assets | On disk under the data directory, per project. |
| Live state store | Live tag values, internal tag values, alarm state | Memory-backed, with durability where it matters. |
Encryption at rest
The configuration database is encrypted with a key bound to the machine it runs on — protected by
Windows itself where available, otherwise derived from ENCRYPTION_KEY. Connection credentials are
encrypted a second time with the same key material before being written into rows.
Practical consequence: copying the database to another machine is not enough. Either carry the key, or move configuration with Backup & restore, which decrypts with the source key on export and re-encrypts under the target key on import.
Two identity realms
QUBIQ runs two independent sign-in systems. They share no accounts, and neither recognises the other's sign-in.
| Platform auth | Runtime auth (LiveView) | |
|---|---|---|
| Who | Engineers, administrators | Plant operators |
| Users stored in | The configuration database | An external User Database you point at, per project |
| Authorises | Every design-time and administrative endpoint | Viewing published views and writing permitted tags |
An operator signed in to LiveView cannot open the Designer at all. This is not a permission that happens to be switched off — the two systems simply do not accept each other's sign-ins, so there is no setting that could cross the line by mistake.
Reliability behaviours
Three mechanisms do most of the work of keeping data intact when something downstream fails:
- Store-and-forward. Outgoing data writes (history, SQL logging, REST push) are buffered to a durable per-connection stream when the target is unreachable, then drained in order when it returns. A latch guarantees a live write can never overtake buffered ones, and items that fail permanently are quarantined for operator action rather than dropped or retried forever. → Store and forward
- Passive liveness. Connection health comes primarily from the protocol libraries' own callbacks (broker connect/disconnect, driver errors, subscription notifications), with an active probe only for connections whose passive signal has gone stale. Health monitoring does not generate load.
- Self-healing history. The historian detects schema drift, suspended write-ahead logs and ownership conflicts on its QuestDB store, alarms on them, and can repair or rebuild without losing the timeline.
Extending the system
| To add… | Do this |
|---|---|
| A protocol | Add a worker service and a pool; bind tags to it through the standard binding model. |
| A widget | Register it in the component registry; it appears in the palette and the catalog. |
| A pipeline node | Add a node template; it appears in the node palette. |
| An API surface | Add a handler on CoreServer behind a permission. Never open a port from another service. |
| Server-side logic | A Gateway script, callable from views and pipelines. → Gateway scripts |