Skip to main content
Version: Next

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:

  1. Starts a message bus that nothing outside the machine can reach.
  2. Brings the configuration database up to date, before anything else runs.
  3. Starts each service as its own process, named for the service it runs — QBQ-CoreServer, QBQ-Historian and so on, which is how they appear in Task Manager or ps.
  4. Supervises them — restarting a service that dies, and reporting health.
Block diagram of one QUBIQ machine: a browser on the left, the core server and realtime gateway as the only reachable services, a message bus down the middle, and the runtime services and protocol workers on the right connecting out to plant devices and stores.Block diagram of one QUBIQ machine: a browser on the left, the core server and realtime gateway as the only reachable services, a message bus down the middle, and the runtime services and protocol workers on the right connecting out to plant devices and stores.
Everything inside the box meets at the message bus. Only the core server and the realtime gateway are reachable from a browser.

Why it is built this way

PropertyConsequence
One file to distributeNo container orchestration and no dependency matrix.
Separate OS processesA protocol driver that crashes or leaks cannot take the web server with it. The supervisor restarts just that service.
Bus-only inter-service trafficServices do not call each other over HTTP and do not share memory. Adding a service does not open a port.
Loopback + random portThe bus is not addressable from the network. There is no broker port to firewall or authenticate.
Single migration pointThe 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

ServiceOwns
CoreServerThe only public HTTP surface: REST API, web UI, authentication, the configuration database, all repositories and handlers.
RealtimeGatewayWebSocket fan-out of telemetry and status to browsers.
EnginePipeline execution — scheduling and running node graphs.
BridgeThe pipeline runtime and its protocol adapters; owns the DAG processor and persisted pipeline state.
HistorianTime-series ingestion into QuestDB, rollups, retention, ownership and schema repair.
AlarmAlarm condition evaluation, the ISA-18.2 state machine, the journal.
AuditWrites the tamper-evident security journal to an external SQL database.
ConnMonitorConnection liveness — passive observation with an active probe as a safety net.
OpcWorker / MqttWorker / ModbusWorker / SnmpWorker / TcpUdpWorker / DbWorkerProtocol-specific execution, one process per family.
RestGatewayServes REST ingress endpoints that pipelines define.
ScriptGatewayHosts script execution, including the Python workers.
ImportServiceBulk namespace/tag import.
AIThe 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

Sequence diagram following one reading from a device through the protocol worker and message bus to the historian, alarm engine, realtime gateway and finally the browser.Sequence diagram following one reading from a device through the protocol worker and message bus to the historian, alarm engine, realtime gateway and finally the browser.
The same reading feeds history, alarms and the screen — it is read once.

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.

ConfigurationRuntime state
ExamplesConnections, tags, bindings, alarm definitions, views, pipelines, usersLive values, quality, alarm state, execution logs, history
Stored inThe configuration databaseThe live state store, QuestDB, memory
Survives restartYesHistory and alarm state yes; live values are re-read
In backupsYesNo

A tag row never holds a value. That separation is what lets configuration be exported, diffed, restored on a different machine and audited.

Storage

StoreContainsNotes
Configuration databaseAll configurationEmbedded, with full-text search, encrypted at rest.
QuestDBTag historyExternal, designated by a single system setting so two historians cannot be configured.
External SQLThe audit journalDeliberately outside the app, so QUBIQ cannot rewrite its own audit trail.
Project directoriesViews, scripts, named queries, assetsOn disk under the data directory, per project.
Live state storeLive tag values, internal tag values, alarm stateMemory-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 authRuntime auth (LiveView)
WhoEngineers, administratorsPlant operators
Users stored inThe configuration databaseAn external User Database you point at, per project
AuthorisesEvery design-time and administrative endpointViewing 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.

Security overview

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 protocolAdd a worker service and a pool; bind tags to it through the standard binding model.
A widgetRegister it in the component registry; it appears in the palette and the catalog.
A pipeline nodeAdd a node template; it appears in the node palette.
An API surfaceAdd a handler on CoreServer behind a permission. Never open a port from another service.
Server-side logicA Gateway script, callable from views and pipelines. → Gateway scripts

Next

The gateway and its services