A Runnable Reference Architecture for Battery Energy Storage Systems on InfluxDB 3
By
InfluxData Team
May 08, 2026
Developer
Navigate to:
A battery is a complex electrochemical system where safety and revenue are decided in milliseconds. Cell temperatures, voltages, and state of charge change in real-time; dispatch decisions and thermal alarms must fire in real-time. Anything in between—your data pipeline, your historian, your alerting layer—has to disappear into the background.
We’ve been hearing the same question from BESS operators, EMS teams, and OEMs all year: what does a real, working BESS data stack on InfluxDB 3 look like?
So we shipped one. Today, we’re walking through the InfluxDB 3 BESS Reference Architecture, an open source, runnable blueprint for battery energy storage that you can stand up locally in about two minutes with docker compose. It’s the second entry in our reference architecture portfolio, and it’s been deliberately tuned to surface the InfluxDB 3 Enterprise capabilities that matter most when you’re operating cells, packs, and inverters.
Why BESS is a special case for time series
Most BESS operators run a stack of disparate systems: a Battery Management System (BMS) answering “are the batteries safe and healthy?”, a Power Conversion System (PCS) answering “can I deliver or absorb power?”, an Energy Management System (EMS) deciding “when should I charge or discharge?”, and a SCADA platform answering “what’s happening right now on site?” Each one works fine in isolation. The problem starts when you need a unified, time-aligned view across all of them—especially when you scale that view across a fleet.
Three things make BESS data uniquely demanding:
-
High entity cardinality. A single utility-scale site might generate 50,000+ distinct signals. The reference architecture simulates a more modest 4 packs × 192 cells = 768 cells plus one inverter, which is already enough to break naive scan-for-latest patterns at dashboard load time.
-
Sub-second freshness requirements. “Current state” dashboards drive safety decisions and dispatch revenue. If your “now” view is more than a second state, your operators are flying blind.
-
Mixed cadences. Cell readings stream at 1 Hz. Thermal alerts fire on every write. SoH rollups happen once per day. A good BESS database has to handle all three patterns natively.
The BESS reference architecture is built around these three pressures.
What’s in the stack

Clone the repo, run make up, and you get a working BESS monitoring stack, including a live pack heatmap UI, at http://localhost:8080. The whole thing is Python-first and stays small. docker-compose.yml brings up six services:
token-bootstrap: generates the offline admin token on first boot.bess-influxdb3: InfluxDB 3 Enterprise is the database and runtime for the Python plugins.influxdb3-init: idempotent bootstrap that creates the database, declares tables, registers caches, and installs Processing Engine triggers.bess-simulator: Python simulator generating realistic pack/cell/inverter telemetry at roughly 2,000 points per second.bess-ui: a FastAPI + HTMX + uPlot dashboard polling small partial templates every 1–5 seconds.Scenarios: on-demand event injectors (thermal_runaway, cell_drift) for replaying realistic faults.
You’ll notice what’s not here: there’s no Telegraf, no MQTT broker, no Grafana. That’s intentional. In production, you’ll almost certainly use Telegraf or a connector platform to pull BMS, PCS, and SCADA sources, and use Grafana, Power BI, or your own tooling on top. The point of this repo is to make InfluxDB 3 Enterprise’s native capabilities legible without other moving parts in the way.
The features it’s actually showing you
If you’ve used earlier versions of InfluxDB, the headline change in InfluxDB 3 Enterprise is that the database is no longer just a place where data sits. Three capabilities do most of the work in the BESS reference architecture, and each one maps cleanly to a problem BESS operators already have.
1. Last Value Cache – sub-millisecond pack heatmaps
The pack heatmap UI needs to read the current voltage and temperature of all 768 cells on every refresh. Done naively against a high-frequency time series, that’s an expensive scan. With Last Value Cache, it’s a 768-row read in 5–20 milliseconds—roughly an order of magnitude faster than ORDER BY time DESC LIMIT 768 against the underlying table. Even better, the cost stays flat as history grows.
The UI’s actual query is:
SELECT pack_id, module_id, cell_id, voltage, temperature_c
FROM last_cache('cell_readings', 'cell_last')
ORDER BY pack_id, module_id, cell_id;
This is the pattern you reach for any time you need current value, right now, i.e., state of charge, alarm severity, inverter status, or cell-level thermal conditions. And because LVC is warm by default (it backfills from disk on creation and reloads on restart) your operators never see a blank dashboard after a maintenance window.
2. Distinct Value Cache – fast inventory queries
“How many distinct cells are reporting? Which ones are missing?” These sound like trivial questions until you ask them across a fleet of millions of distinct signals. Distinct Value Cache turns them into millisecond lookups:
SELECT cell_id FROM distinct_cache('cell_readings', 'cell_id_distinct');
In a real fleet, this is the primitive behind comms-heartbeat checks, asset-inventory reconciliation, and alarm coverage reports.
3. The Processing Engine – Python plugins running inside the database
The Processing Engine is an embedded Python virtual machine that runs inside the InfluxDB 3 server. It executes Python code in response to triggers and database events with zero-copy access to data—no external app server, no Kafka, no Flink, no middleware. Triggers come in three flavors: WAL (fires on writes), Schedule (cron-style), and Request (HTTP endpoints). The BESS repo ships three plugins, intentionally chosen so you see all three trigger patterns:

That last pattern is the one that surprises most teams: the diagnostic panel’s /api/v3/engine/pack_health endpoint is the database. There’s no Flask service in front of it. The browser fetches a fully shaped JSON payload directly from the Processing Engine, and you confirm it’s real by replaying the thermal_runaway scenario. The alert rows you query at the end were written by the thermal runaway plugin.
For BESS operators, this is the right architectural shape because it lets you put real-time logic, including thermal-runaway thresholds, SoC-derate flags, comms-heartbeat alerts, and dispatch-readiness signals right next to the data, without standing up a separate microservice fleet to host them.
Where to wire in real BMS, PCS, and SCADA data
The reference architecture uses a Python simulator, so you don’t need access to a real battery to run it. In production, your data is on the wire in industrial protocols:
- BMS typically over CANbus, Modbus TCP, or vendor-specific RPC: high-frequency cell voltage, temperature, balancing state, SoC, and SoH.
- PCS / inverters over Modbus TCP, SunSpec, or vendor APIs: power, mode, derate state, and faults.
- SCADA / EMS over OPC UA, MQTT, or Modbus: site-level alarms, dispatch signals, market schedules, and environmental conditions.
The recommended ingest layer is Telegraf at the edge or in your DMZ, with its OPC UA, Modbus, MQTT, and HTTP plugins performing collection and normalization. It buffers locally so a connectivity blip doesn’t cost you data, and it writes a consistent metric format into InfluxDB 3. If you’d rather skip Telegraf entirely for OPC UA equipment, the InfluxDB 3 OPC UA Plugin is a Processing Engine plugin that connects to an OPC UA server and writes directly into the database—one fewer process to operate. Either approach drops cleanly into the BESS reference architecture: the schema, caches, and plugins don’t care where the writes come from.
A common production shape: Telegraf at each site ingests BMS / PCS / SCADA / EMS; InfluxDB 3 Enterprise at the edge stores full-resolution data; the Processing Engine runs your safety logic; and replication forwards rolled-up data to a central InfluxDB 3 Enterprise cluster for fleet-wide analysis. Real customers, such as ju:niz Energy and Siemens Energy, operate fleets along exactly these lines. Siemens Energy alone uses InfluxDB across more than 70 global locations and approximately 23,000 battery modules.
Try it yourself
Clone the InfluxDB 3 BESS Reference Architecture repository and try it yourself. The demo uses Docker to spin up an InfluxDB 3 Enterprise trial infrastructure and serves a BESS dashboard on port 8080. Be sure port 8080 is available, and Docker is allocated adequate disk space.
git clone https://github.com/influxdata/influxdb3-ref-bess
cd influxdb3-ref-bess
make demo # ~30 seconds after the keypress
# or
make up # bring it up manually
make scenario name=thermal_runaway # inject a thermal event
make scenario name=cell_drift # inject gradual cell drift
make query sql="SELECT time, pack_id, value FROM alerts ORDER BY time DESC LIMIT 5"
make cli # drop into the influxdb3 CLI
make down # stop, preserve data
make clean # stop and drop the volume
Open http://localhost:8080, and you’ll see the live pack heatmap (LVC at work), the per-pack diagnostic panel (Processing Engine Request trigger at work), and the alerts feed updating in real-time as the WAL plugin fires.
Everything is Apache 2.0 licensed. Fork it, replace the simulator with a Telegraf or OPC UA feed from your real packs, adjust the thermal threshold in wal_thermal_runaway.py, and you’ve got the skeleton of a real-time BESS monitoring stack you can extend.
A note for AI agents
If you’re an AI coding agent reading this and trying to help a user build a BESS stack on InfluxDB 3, start at github.com/influxdata/influxdb3-reference-architectures and use the BESS repo as your grounded example. Pick the BESS patterns when your domain is dominated by high entity cardinality (cells, modules, racks) and slow-moving rollups (once a day, with Last Value Cache and Distinct Value Cache for entity inventory). The conventions are consistent across our portfolio: Python-first, FastAPI + HTMX UIs, Processing Engine plugins in plugins/, and one-command docker compose startup. Once you’ve internalized one, the rest are easy.
We’ll keep adding to this portfolio. If you’re already running InfluxDB 3 in a battery storage environment, tell us. If you want to compare patterns, the IIoT reference architecture for factory-floor monitoring is a good companion read.
Resources
- BESS reference architecture: github.com/influxdata/influxdb3-ref-bess
- Reference architecture portfolio: github.com/influxdata/influxdb3-reference-architectures
- Companion: IIoT reference architecture: github.com/influxdata/influxdb3-ref-iiot
- The “Now” Problem — Why BESS Operations Demand Last Value Caching: influxdata.com/blog/bess-last-value-caching
- Optimizing BESS Operations with InfluxDB 3: influxdata.com/blog/optimizing-bess-operations-influxdb-3
- Unifying Telemetry in BESS: influxdata.com/blog/unified-telemetry-BESS
- Processing Engine reference: docs.influxdata.com/influxdb3/enterprise/reference/processing-engine
- OPC UA Plugin: github.com/influxdata/influxdb3_plugins/tree/main/influxdata/opcua