Skip to content
01Case studyReal-time infrastructure

Two gigabytes a minute,and nobody waitsfor the screen.

ESPx watches 120+ power plants. I own the real-time path: telemetry in, alarms judged as the data arrives, and the result on a control-room screen seconds later. Underneath it all sits a ten-year-old Web Forms application that we're taking apart while it keeps running.

Product
ESPx · plant monitoring
Client
BaxEnergy (Yokogawa)
Engaged via
Intellisync
Role
Senior Software Engineer
Since
Nov 2024
01The problem

A decade-old system, asked to work in real time

ESPx began as an ASP.NET Web Forms and WCF application. It was reliable and it was slow, and the request was not 'make it faster' — it was 'make it live'. Turbine data had to land, be judged against alarm rules, and reach a control-room screen while the operator still cared. Rewriting from scratch was never on the table: the estate runs 120+ sites and cannot go dark for a migration.

  • Ten years of Web Forms and WCF, still in production, still carrying the business
  • ~2 GB of turbine telemetry arriving every minute, from heterogeneous SCADA systems
  • Alarms that mattered were being noticed minutes late
  • No freeze window — the modernisation had to ship alongside normal feature work

“120+ sites cannot go dark for a migration.”

The constraint that shaped everything

01The pipeline

Ingest, judge, push — in that order, every time

Telemetry lands in Kafka, is written to TimescaleDB for history, and flows through Redis streams for the hot path. Alarm evaluation happens on the stream rather than on a timer against the database, which is the whole difference between a dashboard that reports the past and one that reports now. Results are pushed to React dashboards over SignalR. Exactly-once delivery throughout — a duplicated alarm costs an operator's trust, and you only get to spend that once.

  • Kafka for ingest, TimescaleDB for time-series history, Redis streams for the live path
  • Alarm rules evaluated on the stream, not polled against storage
  • SignalR push to React dashboards, so screens update rather than refresh
  • Event-driven gRPC and REST APIs on .NET 8 behind it all
The evaluator

Alarms are judgedon the stream —not after it.

A dashboard that polls storage reports the past. Evaluating rules against the stream as it arrives is what makes the control room's screen the present tense — and it is the difference between an operator noticing a gearbox at 82°C now or in four minutes.

Raw channelslive
Vibration2.3mm/s
Gearbox temp66.2°C
Pitch angle10.6°
Output1801kW
Rule evaluation
Evaluated
48,213
Window
rolling 60s
Pushed to the screenSignalR
  • waiting for the stream…
01The platform

Infrastructure that scales without a meeting

The AWS side is Terraform and Helm on EKS with auto-scaling node groups, so a windy week in Texas doesn't require anyone to provision anything. The same pass that made it elastic made it cheaper: careful query work and Redis caching cut average API latency by 65%, and performance tuning took nearly half off the monthly EC2 bill. Automated builds and integration tests brought regressions down 35%.

  • Terraform and Helm on EKS, auto-scaling node groups
  • 65% lower average API latency from query optimisation and Redis caching
  • ~50% lower monthly EC2 cost from performance tuning
  • 35% fewer regression bugs via automated builds and integration tests
  • Azure AD OAuth2 SSO, mTLS between services, ISO 27001 controls
Impact
2 GB/min
Telemetry ingested, exactly once
120+
Power-plant sites monitored
−65%
Average API latency
−50%
Monthly EC2 spend
−35%
Regression bugs
Where it landed

Operators now see what's happening rather than what happened. And the old system gets a bit smaller every quarter, without anyone ever giving up a weekend to a migration.

Stack

.NET 8C#KafkaTimescaleDBPostgreSQLRedisgRPCSignalRReactTypeScriptAWS EKSTerraformHelmGitHub Actions