---
title: "EV Charging Load Management"
description: "EV charging load management is a data problem before a hardware one. See the OCPP data architecture behind smart charging and load balancing."
section: "Postgres for IoT"
---

> **TimescaleDB is now Tiger Data.**

This article is for CPO (charge point operator) engineers and architects building or evaluating a load-management backend, not for a homeowner deciding which home charger to buy.

Tiger Data sells a database and has a point of view on how load-management data should be stored and queried. The goal here is a neutral, engineering-first look at the problem, including where a simpler setup is the right call.

**EV charging load management** is the practice of dynamically or statically limiting and allocating available electrical capacity across a set of chargers so total draw stays within a site's (or network's) capacity limit. Static (local) load management applies fixed per-charger caps with no live feedback loop; dynamic load management continuously adjusts those caps based on live meter and session data. Either way, the decision logic is only as good as the meter, status, and session data feeding it, which makes load management a data problem before it's a hardware one. This article covers that data architecture: the backend behind smart charging, load balancing, and OCPP-based (Open Charge Point Protocol-based) dynamic load management.

## Why load management depends on OCPP data

Load management logic runs on three OCPP message types: `MeterValues` (live meter readings from each connector), `StatusNotification` (charger and connector state changes), and `SetChargingProfile` (the command that enforces a calculated load limit on a charger).

That distinction matters because OCPP itself doesn't make load-balancing decisions. It's the communication protocol between chargers and the central system: it carries meter and status data up and accepts charging-profile commands down. The decision of what profile to set, and for whom, lives in the CSMS (charge point management system) backend, the software layer at the center of any EV charging management system. OCPP 2.0.1 added native smart-charging-profile support and more granular per-session and per-device control than 1.6; the full comparison is in the table below.

### What data load management actually needs

Four data inputs feed load-management logic:

1. High-frequency meter readings, at whatever interval the chargers report (often 15 seconds to a few minutes per connector)
2. Charger and connector status
3. Active session state
4. Site-level (or network-level) capacity limits

For the underlying schema for these four OCPP data types, see [<u>Tiger Data's guide to EV charging database architecture</u>](https://www.tigerdata.com/learn/ev-charging-station-data-management); this article picks up where that guide leaves off. The same high-frequency ingestion pattern applies to fleet telemetry, covered in Tiger Data's [<u>fleet telemetry database architecture</u>](https://www.tigerdata.com/learn/fleet-telemetry-database) guide, relevant for CPOs that also operate EV fleets.

The volume problem compounds quickly. One charger reporting meter values every 30 seconds is trivial; a site with a few dozen chargers, each with its own connector-level readings, status transitions, and active sessions, turns into a stream that must be read in aggregate and in real time on every relevant change, turning load management from a lookup into a genuine time-series data problem.

## Where naive approaches break down at scale

A common early-stage pattern: log meter and status data to CSV or flat files and reconcile later. This works fine for a handful of chargers at a single site, but breaks down once volume scales past that: there's no efficient way to ask "what's the current aggregate load across all active sessions right now" from flat files, and that's exactly the question dynamic load management needs answered in real time, not after the fact.

Data quality is a less obvious, and documented, failure mode. A GitHub issue on a widely used open-source OCPP library shows chargers that don't respect `MeterValuesSampledDataMaxLength` on OCPP 1.6, returning malformed or effectively random values when too many measurands are configured on that connection ([<u>lbbrhzn/ocpp#1487</u>](https://github.com/lbbrhzn/ocpp/issues/1487)). A separate issue documents chargers sending meter values with a transaction ID of `0`, even after a valid `StartTransaction` confirmation ([<u>mobilityhouse/ocpp#478</u>](https://github.com/mobilityhouse/ocpp/issues/478)).

Load-management logic that trusts malformed or misattributed meter data will make bad decisions: allocating capacity based on a reading that never happened, or attributing one to the wrong session. Data quality at ingestion is a load-management problem, not a separate concern bolted on afterward. Config-parameter mismatches between the CSMS and charger firmware, like the sampling-limit example above, are a common, underestimated source of bad data reaching the database in the first place. Catching it means validating and flagging anomalous data as it arrives rather than trusting it downstream.

## OCPP 1.6 vs. OCPP 2.0.1 for smart charging

| **Capability** | **OCPP 1.6** | **OCPP 2.0.1** |
| --- | --- | --- |
| Smart-charging profiles | Limited, vendor-extension dependent | Native `SetChargingProfile` support built into the spec |
| Session/device control granularity | Coarse, per-charger | Granular, per-device and per-session (Device Model) |
| ISO 15118 (Plug & Charge) | Not natively supported (requires a vendor-specific `DataTransfer` workaround) | Native support |

OCPP 2.0.1 is the version with native smart-charging-profile support, and it's the version that matters if dynamic load management is the goal. That doesn't make OCPP 1.6 a dead end: a CPO running static or local load management (fixed per-charger caps, no live feedback loop) can support that on 1.6 today. If the goal is dynamic, granular, or per-session load management, go to 2.0.1; if a fixed cap per charger is sufficient, 1.6 supports that without a protocol migration.

## Architecting the database layer for load management

This is the part most vendor and consumer content skips: what the database underneath the decision logic actually needs to do.

**Hypertables for high-frequency meter and status data.** A continuous `MeterValues` stream from many chargers is append-only, timestamped, and queried almost exclusively by time range. [<u>Hypertables</u>](https://www.tigerdata.com/docs/learn/hypertables/understand-hypertables) partition this data automatically by time, so a query scoped to "current session" or "last hour" only scans the relevant chunks, keeping ingest and query performance stable as charger count and history grow.

**Continuous aggregates for real-time load rollups.** The core question a load-management decision needs answered fast is: what's the current aggregate draw across active sessions at this site? [<u>Continuous aggregates</u>](https://www.tigerdata.com/docs/reference/timescaledb/continuous-aggregates) maintain a rolling summary that refreshes incrementally on a schedule, rather than rescanning raw data on every decision cycle. Enabling real-time aggregation blends the most recent raw readings into that rollup, so the query returns an up-to-the-moment total.

**Columnstore compression (hypercore) for long-retention meter history.** Load-management systems need historical meter data for billing reconciliation, demand-charge analysis, and capacity planning, but that history is rarely queried at full resolution once it ages past the current cycle. [<u>Columnstore compression</u>](https://www.tigerdata.com/docs/build/how-to/basic-compression) keeps it queryable without paying full row-store costs on data nobody reads at full fidelity.

**Late-arriving and out-of-order data.** Chargers with intermittent connectivity buffer locally and flush on reconnect, so meter readings can arrive out of sequence relative to when they were generated. Hypertables handle this correctly because each row carries its own timestamp; ingestion order doesn't need to match event order for the aggregate to be correct once it refreshes.

A simplified illustration of the shape:

`-- Meter readings: high-frequency time-series per connector

CREATE TABLE meter_readings (    time         TIMESTAMPTZ NOT NULL,
    site_id      TEXT        NOT NULL,
    charger_id   TEXT        NOT NULL,
    connector_id INTEGER     NOT NULL,
    session_id   UUID,
    power_kw     DOUBLE PRECISION
);

SELECT create_hypertable('meter_readings', by_range('time'));

-- Continuous aggregate: current aggregate load per site
CREATE MATERIALIZED VIEW site_load_current
WITH (timescaledb.continuous) AS
SELECT
    time_bucket('1 minute', time) AS bucket,
    site_id,
    SUM(power_kw) AS total_draw_kw
FROM meter_readings
WHERE session_id IS NOT NULL
GROUP BY bucket, site_id;`

For a single small site with a handful of chargers, this full architecture is likely more than needed; a well-indexed standard table can work fine until session and charger count grows. Treat the above as what becomes necessary once naive approaches start breaking down for the reasons covered earlier.

Self-hosted TimescaleDB and Tiger Cloud, Tiger Data's managed service, run the same architecture. Tiger Cloud partitions data into chunks automatically and, once compression and refresh policies are set, runs those jobs automatically, which matters for a CPO team who would rather not run that operational layer themselves.

## Single-site vs. multi-site load management

A single-site deployment keeps every charger behind one capacity limit, and the aggregation question is simple: what's the current load at this site. A distributed, multi-site CPO network changes the question: site-level limits still apply locally, but the data model needs a site dimension so the same rollups can answer "current load across all sites" and "which sites are near their capacity limit," for network-level visibility and, in some cases, cross-site coordination.

The underlying storage doesn't change. Queries shift from a single site-level rollup to one grouped by site, with network-level totals computed on top. It's a scaling dimension, not a different architecture.

## A note on demand response and utility programs

Load management is adjacent to, but distinct from, utility demand-response and grid programs, which can pay CPOs or site owners to shed or shift load at specific times. Program mechanics, incentive structures, and compliance requirements vary by utility and jurisdiction, and are outside this article's scope.

The focus here is the database and data-architecture question. Demand-response program participation is a downstream business decision layered on top of a working load-management pipeline, not a prerequisite for building one: a CPO can implement dynamic load management for its own capacity-limit reasons and decide separately whether to participate in a utility program.

## Decision framework: choosing a load-management data architecture

**Choose a single hypertable + continuous aggregate setup if:** you're running one site or a few sites, session and charger counts are modest, and you need real-time site-level load visibility without multi-site coordination.

**Choose a multi-site aggregation layer (site dimension + network-level rollups) if:** you operate a distributed CPO network and need both site-level enforcement and network-level visibility into aggregate load and capacity headroom.

**Choose OCPP 2.0.1 as a prerequisite if:** you need native smart-charging-profile support, granular per-session control, or ISO 15118 Plug & Charge integration.

**A simpler approach may be enough if:** you're running static or local load management only (fixed caps, no feedback loop) at very small scale. Don't over-build the architecture above if a fixed cap and a well-indexed table cover the actual requirement.

## Migrating into a load-management-ready data architecture

CPOs generally migrate toward a load-management-ready architecture from one of three starting points:

**Flat-file or CSV logging.** The most common early-stage pattern, and the first to break down. The migration path: consolidate meter, status, and session data into hypertables, add continuous aggregates for the rollups load-management logic needs, and apply columnstore compression to keep long-retention storage costs down.

**A single general-purpose relational database with no time-partitioning.** This works until meter-value volume outgrows what a standard table and B-tree index can handle. The migration is largely additive: hypertables slot in without a full schema rewrite, and existing session and billing tables generally stay as standard relational tables alongside the new ones.

**A split-stack setup** (a separate relational database for billing and session data plus a separate time-series database for meter data). The migration here is architectural: consolidating both into one system removes the cross-database join problem and the overhead of running two query languages and two connection pools for what's conceptually one dataset.

Teams migrating from OCPP 1.6 to 2.0.1 at the same time should expect to revisit their charging-profile and session-control data model alongside the database migration, since 2.0.1's native smart-charging-profile support changes what the backend needs to store and act on. Treat the protocol upgrade and the data-architecture work as one project, not two sequential ones.