<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>InfluxData Blog - Charles Mahler</title>
    <description>Posts by Charles Mahler on the InfluxData Blog</description>
    <link>https://www.influxdata.com/blog/author/charles-mahler/</link>
    <language>en-us</language>
    <lastBuildDate>Wed, 02 Sep 2026 07:00:00 +0000</lastBuildDate>
    <pubDate>Wed, 02 Sep 2026 07:00:00 +0000</pubDate>
    <ttl>1800</ttl>
    <item>
      <title>Getting Started with InfluxDB 3 and Grafana Tutorial </title>
      <description>&lt;p&gt;InfluxDB and Grafana are the most common pairing in time series monitoring, and division of labor between them is simple. InfluxDB stores and queries the data: high-volume, time-stamped readings from sensors, servers, meters, vehicles, and anything else that emits time series. Grafana visualizes the data: dashboards, time series panels, stat tiles, state timelines, and alerts. 
Neither tool replaces the other, and many production monitoring stacks run both.&lt;/p&gt;

&lt;p&gt;Connecting them has gotten considerably simpler with InfluxDB 3. Grafana ships with a built-in InfluxDB datasource that speaks InfluxDB 3 SQL over Flight SQL, so there is no third-party plugin to install and no custom query language to learn.&lt;/p&gt;

&lt;p&gt;This guide walks through that integration end-to-end using a realistic dataset you generate yourself. You will learn the following in this tutorial:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Getting data in&lt;/strong&gt; - Writing line protocol and what tags, fields, and timestamps mean for how you will query later.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Transforming data as it arrives&lt;/strong&gt; - Using an InfluxDB 3 Python Processing Engine plugin to turn raw readings into a derived table of decisions. This pattern keeps dashboard queries simple and your source data untouched.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Connecting Grafana securely&lt;/strong&gt; - Read-only tokens and the datasource settings that actually matter.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Building a real dashboard&lt;/strong&gt; - Template variables, chained variables, &lt;code class="language-markup"&gt;$__timeFilter&lt;/code&gt;, &lt;code class="language-markup"&gt;date_bin()&lt;/code&gt; for downsampling, and eight panels across stats, time series, state timelines, bar gauges, and tables.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id="what-you-will-build-with-influxdb-3-and-grafana"&gt;What you will build with InfluxDB 3 and Grafana&lt;/h2&gt;

&lt;p&gt;To make all of that concrete, you will generate a week of smart-building data. The generator is deterministic, so your numbers will match the expected output at every step, and you can verify each stage rather than guessing.&lt;/p&gt;

&lt;p&gt;The raw table, &lt;code class="language-markup"&gt;building_telemetry&lt;/code&gt;, preserves the original readings. The derived table, &lt;code class="language-markup"&gt;iaq_assessment&lt;/code&gt;, adds an indoor-air-quality risk score, a severity label, a recommended action, and a conservative estimate of energy used above an idle baseline. The finished dashboard answers four questions an operator actually asks:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Which occupied rooms experienced unhealthy air?&lt;/li&gt;
  &lt;li&gt;Where did CO₂ and PM2.5 spike?&lt;/li&gt;
  &lt;li&gt;How much energy was consumed while rooms were empty?&lt;/li&gt;
  &lt;li&gt;Which building zones should someone investigate first?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The dataset has four incidents deliberately built into it. A stuffy Tuesday conference room, a lab drawing power on an empty Wednesday evening, a Thursday ventilation problem in the design studio, and a building-wide Friday particulate event, so every panel has something real to show.&lt;/p&gt;

&lt;p&gt;Buildings are just an example. The architecture in this tutorial applies unchanged to industrial equipment, fleet telemetry, energy metering, network devices, or application metrics. If you are evaluating InfluxDB and Grafana for any of those, this is a working template.&lt;/p&gt;

&lt;h4 id="prerequisites"&gt;Prerequisites&lt;/h4&gt;

&lt;p&gt;This walk-through assumes:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;A Debian- or Ubuntu-based Linux environment; Grafana also publishes installation instructions for &lt;a href="https://grafana.com/docs/grafana/latest/setup-grafana/installation/redhat-rhel-fedora/?utm_source=website&amp;amp;utm_medium=direct&amp;amp;utm_campaign=getting_started_influxdb_3_grafana&amp;amp;utm_content=blog"&gt;RHEL, Fedora&lt;/a&gt;, and other supported systems.&lt;/li&gt;
  &lt;li&gt;Python 3.9 (or newer).&lt;/li&gt;
  &lt;li&gt;&lt;code class="language-markup"&gt;sudo&lt;/code&gt; access for installing Grafana.&lt;/li&gt;
  &lt;li&gt;An InfluxDB 3 Enterprise trial, at-home, or commercial license.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id="why-use-grafanas-built-in-influxdb-datasource"&gt;Why use Grafana’s built-in InfluxDB datasource?&lt;/h2&gt;

&lt;p&gt;Grafana includes an InfluxDB datasource that supports InfluxDB 3 SQL out of the box. There’s no need to install anything else, which makes getting started easy.&lt;/p&gt;

&lt;p&gt;Other choices still have valid uses:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;InfluxQL is supported by the plugin as well, and is useful when migrating existing InfluxDB 1.x queries or working through an HTTP/1.1-only proxy.&lt;/li&gt;
  &lt;li&gt;A separately hosted Grafana is useful for a shared observability platform, but its backend needs private, HTTP/2-capable access to the InfluxDB query node.&lt;/li&gt;
  &lt;li&gt;A custom API can add domain-specific authorization or reshape data, but it adds another service that a beginner tutorial does not need.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id="create-your-project-directory"&gt;Create Your Project Directory&lt;/h4&gt;

&lt;p&gt;Create a folder anywhere convenient and open it in your text editor:&lt;/p&gt;

&lt;pre class=""&gt;&lt;code class="language-bash"&gt;mkdir smart-building-demo
cd smart-building-demo
mkdir plugins influxdb-data&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The tutorial uses only these local paths:&lt;/p&gt;

&lt;pre class=""&gt;&lt;code class="language-bash"&gt;smart-building-demo/
├── generate.py
├── smart-building.lp
├── influxdb-data/
└── plugins/
    └── iaq_assessment.py&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Keep the terminal in &lt;code class="language-markup"&gt;smart-building-demo&lt;/code&gt; for the remaining commands.&lt;/p&gt;

&lt;h4 id="install-and-start-influxdb-3-enterprise"&gt;Install and Start InfluxDB 3 Enterprise&lt;/h4&gt;

&lt;p&gt;InfluxData provides a quick installer for Linux and macOS. Download the installer first so you can inspect it before running:&lt;/p&gt;

&lt;pre class=""&gt;&lt;code class="language-bash"&gt;curl -o /tmp/install_influxdb3.sh \
  https://www.influxdata.com/d/install_influxdb3.sh

# Optional: inspect the downloaded script before running it.
less /tmp/install_influxdb3.sh

sh /tmp/install_influxdb3.sh enterprise&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The installer tracks the latest InfluxDB 3 Enterprise release. For production, InfluxData recommends its DEB/RPM packages or a carefully secured container deployment; see the &lt;a href="https://docs.influxdata.com/influxdb3/enterprise/install/?utm_source=website&amp;amp;utm_medium=direct&amp;amp;utm_campaign=getting_started_influxdb_3_grafana&amp;amp;utm_content=blog"&gt;official installation guide&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Verify that the binary is available:&lt;/p&gt;

&lt;pre class=""&gt;&lt;code class="language-bash"&gt;influxdb3 --version&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;If the command is not found, open a new terminal or follow the installer’s instructions for adding the binary to your &lt;code class="language-markup"&gt;PATH&lt;/code&gt;, then return to the demo directory.&lt;/p&gt;

&lt;h4 id="start-the-database-with-the-processing-engine-enabled"&gt;Start the Database with the Processing Engine Enabled&lt;/h4&gt;

&lt;p&gt;Run InfluxDB in the first terminal and leave it running:&lt;/p&gt;

&lt;pre class=""&gt;&lt;code class="language-bash"&gt;influxdb3 serve \
  --node-id smart-building-node \
  --cluster-id smart-building-cluster \
  --object-store file \
  --data-dir "$PWD/influxdb-data" \
  --plugin-dir "$PWD/plugins"&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The &lt;code class="language-markup"&gt;--plugin-dir&lt;/code&gt; flag activates the Processing Engine and tells InfluxDB where to find the Python file you will create. The data directory persists the database between restarts. These options are documented in the &lt;a href="https://docs.influxdata.com/influxdb3/enterprise/plugins/?utm_source=website&amp;amp;utm_medium=direct&amp;amp;utm_campaign=getting_started_influxdb_3_grafana&amp;amp;utm_content=blog"&gt;Processing Engine guide&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;On the first start, InfluxDB asks you to choose and activate a license. A trial provides full Enterprise features for 30 days; the at-home license is intended for eligible hobbyist use. Follow the prompts and email-verification step described in the &lt;a href="https://docs.influxdata.com/influxdb3/enterprise/admin/license/?utm_source=website&amp;amp;utm_medium=direct&amp;amp;utm_campaign=getting_started_influxdb_3_grafana&amp;amp;utm_content=blog"&gt;license documentation&lt;/a&gt;. Do not include license files, license JWTs, or account details in screenshots or source code.&lt;/p&gt;

&lt;h4 id="create-and-store-the-administrator-token"&gt;Create and Store the Administrator Token&lt;/h4&gt;

&lt;p&gt;Open a second terminal, return to &lt;code class="language-markup"&gt;smart-building-demo&lt;/code&gt;, and create the initial administrator token:&lt;/p&gt;

&lt;pre class=""&gt;&lt;code class="language-bash"&gt;influxdb3 create token --admin&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;InfluxDB displays this token only once. Store it in a password manager. Then load it into the current terminal without placing it in shell history:&lt;/p&gt;

&lt;pre class=""&gt;&lt;code class="language-bash"&gt;read -rsp "Paste the InfluxDB administrator token: " INFLUXDB_TOKEN
echo
export INFLUXDB3_AUTH_TOKEN="$INFLUXDB_TOKEN"
export INFLUXDB3_HOST_URL="http://127.0.0.1:8181"&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Do not put the token in &lt;code class="language-markup"&gt;generate.py&lt;/code&gt;, the plugin, Grafana queries, screenshots, or a committed shell script. InfluxDB’s &lt;a href="https://docs.influxdata.com/influxdb3/enterprise/get-started/setup/?utm_source=website&amp;amp;utm_medium=direct&amp;amp;utm_campaign=getting_started_influxdb_3_grafana&amp;amp;utm_content=blog"&gt;authorization setup guide&lt;/a&gt; recommends the &lt;code class="language-markup"&gt;INFLUXDB3_AUTH_TOKEN&lt;/code&gt; environment variable for CLI authentication.&lt;/p&gt;

&lt;p&gt;Create a database for the demo:&lt;/p&gt;

&lt;pre class=""&gt;&lt;code class="language-bash"&gt;export INFLUXDB_DATABASE="smart_building"
influxdb3 create database "$INFLUXDB_DATABASE"&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Expected result: the command completes without an error. You can confirm the database exists with:&lt;/p&gt;

&lt;pre class=""&gt;&lt;code class="language-bash"&gt;influxdb3 show databases&lt;/code&gt;&lt;/pre&gt;

&lt;h4 id="generate-a-demo-telemetry-data"&gt;Generate a Demo Telemetry Data&lt;/h4&gt;

&lt;p&gt;A good tutorial dataset should be small, understandable, and repeatable. This generator creates six zones, sampled every five minutes for seven days.&lt;/p&gt;

&lt;p&gt;In your editor, create &lt;code class="language-markup"&gt;generate.py&lt;/code&gt; in &lt;code class="language-markup"&gt;smart-building-demo&lt;/code&gt; and paste the following code:&lt;/p&gt;

&lt;pre class=""&gt;&lt;code class="language-python"&gt;#!/usr/bin/env python3
"""Generate deterministic smart-building telemetry as InfluxDB line protocol."""

from __future__ import annotations

from datetime import datetime, timedelta, timezone
import math
from pathlib import Path
import random

START = datetime(2026, 8, 10, tzinfo=timezone.utc)
INTERVAL = timedelta(minutes=5)
POINTS_PER_ZONE = 7 * 24 * 12

ZONES = [
    {"building": "hq", "floor": "1", "zone": "open_office", "sensor": "hq-1-open", "capacity": 40, "offset": 0.0},
    {"building": "hq", "floor": "1", "zone": "conference_a", "sensor": "hq-1-conf-a", "capacity": 12, "offset": 0.3},
    {"building": "hq", "floor": "1", "zone": "cafe", "sensor": "hq-1-cafe", "capacity": 50, "offset": 0.7},
    {"building": "hq", "floor": "2", "zone": "lab", "sensor": "hq-2-lab", "capacity": 20, "offset": 0.5},
    {"building": "annex", "floor": "1", "zone": "design_studio", "sensor": "annex-1-design", "capacity": 24, "offset": -0.2},
    {"building": "annex", "floor": "2", "zone": "meeting_room", "sensor": "annex-2-meeting", "capacity": 10, "offset": 0.1},
]

def occupancy_for(zone: dict[str, object], ts: datetime, rng: random.Random) -&amp;gt; int:
    hour = ts.hour + ts.minute / 60
    weekday = ts.weekday() " 5
    capacity = int(zone["capacity"])
    name = str(zone["zone"])

    if not weekday:
        base = 0
    elif 8 "= hour " 18:
        profile = {
            "open_office": 0.64,
            "conference_a": 0.34,
            "cafe": 0.18,
            "lab": 0.48,
            "design_studio": 0.70,
            "meeting_room": 0.28,
        }[name]
        curve = 0.82 + 0.18 * math.sin((hour - 8) * math.pi / 10)
        base = round(capacity * profile * curve)
    else:
        base = 0

    if weekday and name == "cafe" and 11.5 "= hour " 13.5:
        base = round(capacity * 0.88)
    if ts.weekday() == 1 and name == "conference_a" and 14 "= hour " 16:
        base = capacity
    if ts.weekday() == 3 and name == "design_studio" and 9 "= hour " 12:
        base = round(capacity * 0.92)

    return max(0, min(capacity, base + rng.randint(-1, 1)))

def values_for(zone: dict[str, object], ts: datetime, rng: random.Random):
    occupancy = occupancy_for(zone, ts, rng)
    capacity = int(zone["capacity"])
    hour = ts.hour + ts.minute / 60
    zone_name = str(zone["zone"])
    diurnal = math.sin((hour - 7) * math.pi / 12)
    load = occupancy / capacity if capacity else 0

    temperature = 21.3 + float(zone["offset"]) + 1.3 * diurnal + 1.1 * load + rng.uniform(-0.18, 0.18)
    humidity = 43.0 - 4.0 * diurnal + 3.0 * load + rng.uniform(-0.8, 0.8)
    co2 = round(430 + 530 * load + rng.uniform(-18, 18))
    pm25 = 5.2 + 2.8 * load + rng.uniform(-0.7, 0.7)
    power = 0.65 + 0.10 * occupancy + max(0.0, temperature - 23.0) * 0.42 + rng.uniform(-0.12, 0.12)

    # Deterministic incidents used by the dashboard.
    if ts.weekday() == 1 and zone_name == "conference_a" and 14 "= hour " 16:
        co2 += round(720 + 80 * math.sin((hour - 14) * math.pi / 2))
    if ts.weekday() == 3 and zone_name == "design_studio" and 9 "= hour " 12:
        co2 += round(560 + 70 * math.sin((hour - 9) * math.pi / 3))
    if ts.weekday() == 4 and 9 "= hour " 14:
        pm25 += 20 + 4 * math.sin((hour - 9) * math.pi / 5)
    if ts.weekday() == 2 and zone_name == "lab" and 19 "= hour " 22:
        power += 5.5

    return (
        occupancy,
        round(temperature, 2),
        round(humidity, 2),
        int(co2),
        round(pm25, 2),
        round(max(power, 0.1), 2),
    )

def main() -&amp;gt; None:
    output = Path("smart-building.lp")
    rng = random.Random(20260821)
    rows = 0

    with output.open("w", encoding="utf-8") as handle:
        for tick in range(POINTS_PER_ZONE):
            ts = START + tick * INTERVAL
            epoch_s = int(ts.timestamp())

        for zone in ZONES:
                occupancy, temperature, humidity, co2, pm25, power = values_for(zone, ts, rng)
                tags = (
                    f"building={zone['building']},floor={zone['floor']},"
                    f"zone={zone['zone']},sensor_id={zone['sensor']}"
                )
                fields = (
                    f"temperature_c={temperature:.2f},humidity_pct={humidity:.2f},"
                    f"co2_ppm={co2}i,pm25_ug_m3={pm25:.2f},power_kw={power:.2f},"
                    f"occupancy={occupancy}i,capacity={zone['capacity']}i"
                )
                handle.write(f"building_telemetry,{tags} {fields} {epoch_s}\n")
                rows += 1

    print(f"wrote {rows} deterministic rows to {output}")

if __name__ == "__main__":
    main()&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Run the generator:&lt;/p&gt;

&lt;pre class=""&gt;&lt;code class="language-bash"&gt;python3 generate.py
wc -l smart-building.lp&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Expected output:&lt;/p&gt;

&lt;pre class=""&gt;&lt;code class="language-bash"&gt;wrote 12096 deterministic rows to smart-building.lp
12096 smart-building.lp&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;You can open &lt;code class="language-markup"&gt;smart-building.lp&lt;/code&gt; in your editor to inspect the line protocol. Each line contains:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Tags&lt;/strong&gt;: &lt;code class="language-markup"&gt;building&lt;/code&gt;, &lt;code class="language-markup"&gt;floor&lt;/code&gt;, &lt;code class="language-markup"&gt;zone&lt;/code&gt;, and &lt;code class="language-markup"&gt;sensor_id&lt;/code&gt; are low-cardinality dimensions used for filtering and grouping.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Fields&lt;/strong&gt;: temperature, humidity, CO₂, PM2.5, power, occupancy, and capacity are values that change over time.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Timestamp&lt;/strong&gt;: epoch seconds—the ingestion command will specify &lt;code class="language-markup"&gt;--precision s&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id="add-an-influxdb-python-processing-engine-plugin"&gt;Add an InfluxDB Python Processing Engine Plugin&lt;/h4&gt;

&lt;p&gt;Raw sensor values are valuable, but operators usually want a smaller set of decisions: Is the room healthy? How urgent is the problem? What should someone check?&lt;/p&gt;

&lt;p&gt;Create &lt;code class="language-markup"&gt;plugins/iaq_assessment.py&lt;/code&gt; in your editor and paste this code:&lt;/p&gt;

&lt;pre class=""&gt;&lt;code class="language-python"&gt;"""Create dashboard-ready indoor-air-quality assessments from raw telemetry.

InfluxDB 3 injects LineBuilder and influxdb3_local at runtime.
"""

def _classify(row):
    co2 = int(row["co2_ppm"])
    pm25 = float(row["pm25_ug_m3"])
    temperature = float(row["temperature_c"])
    humidity = float(row["humidity_pct"])
    occupancy = int(row["occupancy"])
    power_kw = float(row["power_kw"])

    co2_component = min(70, max(0, round((co2 - 800) / 8)))
    pm_component = min(30, max(0, round((pm25 - 8) * 2)))
    comfort_component = (
        10
        if temperature " 20
        or temperature &amp;gt; 25
        or humidity " 30
        or humidity &amp;gt; 60
        else 0
    )
    risk_score = min(100, co2_component + pm_component + comfort_component)

    if risk_score &amp;gt;= 70:
        severity = "critical"
    elif risk_score &amp;gt;= 45:
        severity = "high"
    elif risk_score &amp;gt;= 20:
        severity = "moderate"
    else:
        severity = "normal"

    if co2 &amp;gt; 1000 and occupancy &amp;gt; 0:
        recommendation = "increase_ventilation"
    elif pm25 &amp;gt; 25:
        recommendation = "check_filtration"
    elif comfort_component:
        recommendation = "balance_hvac"
    else:
        recommendation = "monitor"

    energy_waste_kw = max(0.0, power_kw - 1.5) if occupancy == 0 else 0.0
    return risk_score, severity, recommendation, energy_waste_kw

def process_writes(influxdb3_local, table_batches, args=None):
    processed = 0

    for table_batch in table_batches:
        if table_batch["table_name"] != "building_telemetry":
            continue

        for row in table_batch["rows"]:
            risk_score, severity, recommendation, energy_waste_kw = _classify(row)
            line = (
                LineBuilder("iaq_assessment")
                .tag("building", row["building"])
                .tag("floor", row["floor"])
                .tag("zone", row["zone"])
                .tag("severity", severity)
                .int64_field("risk_score", risk_score)
                .int64_field(
                    "co2_excess_ppm", max(0, int(row["co2_ppm"]) - 800)
                )
                .string_field("recommendation", recommendation)
                .float64_field("energy_waste_kw", energy_waste_kw)
                .bool_field("occupied", int(row["occupancy"]) &amp;gt; 0)
                .time_ns(int(row["time"]))
            )
            influxdb3_local.write(line)
            processed += 1

    influxdb3_local.info(
        f"iaq_assessment processed {processed} building_telemetry rows"
    )&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The plugin deliberately uses only the API InfluxDB injects and the Python standard library, so you don’t have to install any packages.&lt;/p&gt;

&lt;h4 id="understanding-the-risk-calculation"&gt;Understanding the Risk Calculation&lt;/h4&gt;

&lt;pre class=""&gt;&lt;code class="language-bash"&gt;CO₂ component     = clamp(round((co2_ppm - 800) / 8), 0, 70)
PM2.5 component   = clamp(round((pm25_ug_m3 - 8) × 2), 0, 30)
comfort component = 10 outside 20–25 °C or 30–60% humidity
risk score        = min(100, all three components added together)&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The score becomes one of four states:&lt;/p&gt;

&lt;div class="blog-html-table-wrapper"&gt;
 &lt;table class="blog-html-table"&gt;
  &lt;thead&gt;
   &lt;tr&gt;
    &lt;th&gt;Score&lt;/th&gt;
    &lt;th&gt;Severity&lt;/th&gt;
   &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
   &lt;tr&gt;
    &lt;td&gt;0–19&lt;/td&gt;
    &lt;td&gt;normal&lt;/td&gt;
   &lt;/tr&gt;
   &lt;tr&gt;
    &lt;td&gt;20–44&lt;/td&gt;
    &lt;td&gt;moderate&lt;/td&gt;
   &lt;/tr&gt;
   &lt;tr&gt;
    &lt;td&gt;45–69&lt;/td&gt;
    &lt;td&gt;high&lt;/td&gt;
   &lt;/tr&gt;
   &lt;tr&gt;
    &lt;td&gt;70–100&lt;/td&gt;
    &lt;td&gt;critical&lt;/td&gt;
   &lt;/tr&gt;
  &lt;/tbody&gt;
 &lt;/table&gt;
&lt;/div&gt;

&lt;p&gt;The rules are intentionally simple and transparent. They are tutorial logic, not a health, safety, or regulatory standard.&lt;/p&gt;

&lt;p&gt;The plugin also calculates &lt;code class="language-markup"&gt;energy_waste_kw&lt;/code&gt;. When occupancy is zero, it counts only power above a conservative 1.5 kW idle baseline. This is different from counting all energy used while a room is empty, which is shown in the dashboards.&lt;/p&gt;

&lt;h4 id="raw-and-derived-table-schemas"&gt;Raw and derived table schemas&lt;/h4&gt;

&lt;p&gt;The first write creates the raw &lt;code class="language-markup"&gt;building_telemetry&lt;/code&gt; table:&lt;/p&gt;

&lt;p&gt;The plugin creates &lt;code class="language-markup"&gt;iaq_assessment&lt;/code&gt;:&lt;/p&gt;

&lt;div class="blog-html-table-wrapper"&gt;
 &lt;table class="blog-html-table"&gt;
  &lt;thead&gt;
   &lt;tr&gt;
    &lt;th&gt;Columns&lt;/th&gt;
    &lt;th&gt;Stored as&lt;/th&gt;
   &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
   &lt;tr&gt;
    &lt;td&gt;building, floor, zone, sensor_id&lt;/td&gt;
    &lt;td&gt;Tags&lt;/td&gt;
   &lt;/tr&gt;
   &lt;tr&gt;
    &lt;td&gt;temperature_c, humidity_pct, pm25_ug_m3, power_kw&lt;/td&gt;
    &lt;td&gt;Float fields&lt;/td&gt;
   &lt;/tr&gt;
   &lt;tr&gt;
    &lt;td&gt;co2_ppm, occupancy, capacity&lt;/td&gt;
    &lt;td&gt;Integer fields&lt;/td&gt;
   &lt;/tr&gt;
   &lt;tr&gt;
    &lt;td&gt;time&lt;/td&gt;
    &lt;td&gt;Timestamp&lt;/td&gt;
   &lt;/tr&gt;
  &lt;/tbody&gt;
 &lt;/table&gt;
&lt;/div&gt;

&lt;p&gt;Keeping the raw and derived data separate makes the model easy to audit. You can inspect the original readings without reverse-engineering a transformed table, while Grafana can query the already classified states.&lt;/p&gt;

&lt;h4 id="test-the-plugin-before-connecting-it-to-writes"&gt;Test the Plugin Before Connecting it to Writes&lt;/h4&gt;

&lt;p&gt;InfluxDB can run a WAL plugin against a sample row without committing its output. This is the quickest way to catch syntax errors or confirm the scoring logic. The &lt;a href="https://docs.influxdata.com/influxdb3/enterprise/get-started/process/?utm_source=website&amp;amp;utm_medium=direct&amp;amp;utm_campaign=getting_started_influxdb_3_grafana&amp;amp;utm_content=blog"&gt;Processing Engine testing guide&lt;/a&gt; describes this behavior.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Run&lt;/strong&gt;:&lt;/p&gt;

&lt;pre class=""&gt;&lt;code class="language-bash"&gt;influxdb3 test wal_plugin \
  --database "$INFLUXDB_DATABASE" \
  --lp "building_telemetry,building=test,floor=1,zone=test,sensor_id=test temperature_c=22.0,humidity_pct=45.0,co2_ppm=1200i,pm25_ug_m3=10.0,power_kw=2.0,occupancy=4i,capacity=10i 1786406400000000000" \
  iaq_assessment.py&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;strong&gt;Expected&lt;/strong&gt;: one derived &lt;code class="language-markup"&gt;iaq_assessment&lt;/code&gt; line, no errors, risk score 54, severity &lt;code class="language-markup"&gt;high&lt;/code&gt;, and recommendation &lt;code class="language-markup"&gt;increase_ventilation&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The risk is 54 because CO₂ contributes 50 points, PM2.5 contributes 4, and temperature and humidity add zero.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Mistake to avoid&lt;/strong&gt;: The final argument is relative to the server’s &lt;code class="language-markup"&gt;--plugin-dir&lt;/code&gt;, not your terminal’s current directory. Because the server started with plugins as its plugin directory, the correct value is &lt;code class="language-markup"&gt;iaq_assessment.py&lt;/code&gt;.&lt;/p&gt;

&lt;h4 id="create-the-trigger-and-ingest-sample-data"&gt;Create the Trigger and Ingest Sample Data&lt;/h4&gt;

&lt;p&gt;Create a WAL trigger that watches only the raw table:&lt;/p&gt;

&lt;pre class=""&gt;&lt;code class="language-bash"&gt;export INFLUXDB_TRIGGER="smart_building_iaq_assessment"
influxdb3 create trigger \
  --database "$INFLUXDB_DATABASE" \
  --path iaq_assessment.py \
  --trigger-spec "table:building_telemetry" \
  --error-behavior disable \
  "$INFLUXDB_TRIGGER"&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The table scope is important. The plugin writes to &lt;code class="language-markup"&gt;iaq_assessment&lt;/code&gt;; because the trigger watches only &lt;code class="language-markup"&gt;building_telemetry&lt;/code&gt;, those derived writes cannot invoke the plugin recursively. The code also ignores batches from other tables as a second guard.&lt;/p&gt;

&lt;p&gt;Write all 12,096 source rows:&lt;/p&gt;

&lt;pre class=""&gt;&lt;code class="language-bash"&gt;influxdb3 write \
  --database "$INFLUXDB_DATABASE" \
  --precision s \
  --file smart-building.lp&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;InfluxDB acknowledges the source write separately from completing asynchronous Processing Engine work. Poll the derived count for up to 60 seconds:&lt;/p&gt;

&lt;pre class=""&gt;&lt;code class="language-bash"&gt;derived_rows=0
for attempt in $(seq 1 60); do
  derived_rows=$(
    influxdb3 query \
      --database "$INFLUXDB_DATABASE" \
      --format csv \
      "SELECT COUNT(*) AS derived_rows FROM iaq_assessment" |
      tail -n 1
  )

  if [[ "$derived_rows" == "12096" ]]; then
    break
  fi

  sleep 1
done

printf 'Derived rows: %s\n' "$derived_rows"&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Expected:&lt;/p&gt;

&lt;pre class=""&gt;&lt;code class="language-bash"&gt;Derived rows: 12096&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;If the count does not reach 12,096, do not continue to Grafana yet. Review the troubleshooting section and inspect the InfluxDB terminal for Processing Engine errors.&lt;/p&gt;

&lt;p&gt;Disable the trigger after enrichment finishes:&lt;/p&gt;

&lt;pre class=""&gt;&lt;code class="language-bash"&gt;influxdb3 disable trigger \
  --database "$INFLUXDB_DATABASE" \
  "$INFLUXDB_TRIGGER"&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;In a real streaming application, you would normally leave a healthy trigger enabled so future writes are enriched.&lt;/p&gt;

&lt;h4 id="query-the-results-with-sql"&gt;Query the Results with SQL&lt;/h4&gt;

&lt;p&gt;Start by validating the raw table:&lt;/p&gt;

&lt;pre class=""&gt;&lt;code class="language-bash"&gt;influxdb3 query \
  --database "$INFLUXDB_DATABASE" \
  "SELECT
     COUNT(*) AS source_rows,
     COUNT(DISTINCT building) AS buildings,
     COUNT(DISTINCT zone) AS zones,
     MIN(time) AS first_time,
     MAX(time) AS last_time
   FROM building_telemetry"&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Expected values:&lt;/p&gt;

&lt;pre class=""&gt;&lt;code class="language-bash"&gt;source_rows: 12096
buildings: 2
zones: 6
first_time: 2026-08-10T00:00:00
last_time: 2026-08-16T23:55:00&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Now summarize the derived table:&lt;/p&gt;

&lt;pre class=""&gt;&lt;code class="language-bash"&gt;influxdb3 query \
  --database "$INFLUXDB_DATABASE" \
  "SELECT
     COUNT(*) AS derived_rows,
     MIN(risk_score) AS min_risk,
     MAX(risk_score) AS max_risk,
     SUM(
       CASE
         WHEN severity IN ('high', 'critical') AND occupied THEN 1
         ELSE 0
       END
     ) AS unhealthy_occupied_intervals,
     ROUND(SUM(energy_waste_kw) * 5.0 / 60.0, 2) AS avoidable_kwh
   FROM iaq_assessment"&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Expected values:&lt;/p&gt;

&lt;pre class=""&gt;&lt;code class="language-bash"&gt;derived_rows: 12096
min_risk: 0
max_risk: 71
unhealthy_occupied_intervals: 62
avoidable_kwh: 9.29&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Finally, find the CO₂ hotspots and empty-zone consumption:&lt;/p&gt;

&lt;pre class=""&gt;&lt;code class="language-bash"&gt;influxdb3 query \
  --database "$INFLUXDB_DATABASE" \
  "SELECT
     building,
     zone,
     MAX(co2_ppm) AS peak_co2_ppm,
     ROUND(AVG(pm25_ug_m3), 2) AS avg_pm25_ug_m3&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The first two rows should be &lt;code class="language-markup"&gt;hq/conference_a&lt;/code&gt; at 1,770 ppm and &lt;code class="language-markup"&gt;annex/design_studio&lt;/code&gt; at 1,572 ppm. The &lt;code class="language-markup"&gt;hq/lab&lt;/code&gt; row should have the largest raw empty-zone consumption at 61.22 kWh.&lt;/p&gt;

&lt;h2 id="installing-grafana"&gt;Installing Grafana&lt;/h2&gt;

&lt;p&gt;Install Grafana OSS from Grafana Labs’ signed APT repository:&lt;/p&gt;

&lt;pre class=""&gt;&lt;code class="language-bash"&gt;sudo apt-get install -y apt-transport-https wget gnupg

sudo mkdir -p /etc/apt/keyrings
sudo wget -O /etc/apt/keyrings/grafana.asc \
  https://apt.grafana.com/gpg-full.key
sudo chmod 644 /etc/apt/keyrings/grafana.asc

echo "deb [signed-by=/etc/apt/keyrings/grafana.asc] https://apt.grafana.com stable main" |
  sudo tee /etc/apt/sources.list.d/grafana.list

sudo apt-get update
sudo apt-get install -y grafana&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Start Grafana and enable it at boot:&lt;/p&gt;

&lt;pre class=""&gt;&lt;code class="language-bash"&gt;sudo systemctl daemon-reload
sudo systemctl enable --now grafana-server.service
sudo systemctl status --no-pager grafana-server.service&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Open &lt;a href="http://localhost:3000"&gt;http://localhost:3000&lt;/a&gt; in your browser. On a new installation, the default username and password are both admin; Grafana immediately prompts you to choose a new password.&lt;/p&gt;

&lt;p&gt;Use a unique local password supplied as &lt;code class="language-markup"&gt;$GRAFANA_ADMIN_PASSWORD&lt;/code&gt;. Never reuse the InfluxDB token as a Grafana password.&lt;/p&gt;

&lt;p&gt;&lt;img src="//images.ctfassets.net/o7xu9whrs0u9/23hMx8HAxiYLKCbq8davHr/ec7c0d5bd384ab4c367bde047d86da68/0dc52148-672c-40bf-90ec-e6b6df97cabc.png" alt="InfluxDB 3 and Grafana 1" /&gt;&lt;/p&gt;

&lt;h4 id="create-a-read-only-token-for-grafana"&gt;Create a Read-Only Token for Grafana&lt;/h4&gt;

&lt;p&gt;Grafana only needs to query this one database. It should not receive the administrator token used for setup.&lt;/p&gt;

&lt;p&gt;In the terminal where &lt;code class="language-markup"&gt;INFLUXDB3_AUTH_TOKEN&lt;/code&gt; is still set, run:&lt;/p&gt;

&lt;p&gt;influxdb3 create token \
  –permission “db:smart_building:read” \
  –name “Grafana read-only access to smart_building”&lt;/p&gt;

&lt;p&gt;Store the returned value in your password manager as &lt;code class="language-markup"&gt;$INFLUXDB_GRAFANA_TOKEN&lt;/code&gt;. InfluxDB’s &lt;a href="https://docs.influxdata.com/influxdb3/enterprise/admin/tokens/resource/create/?utm_source=website&amp;amp;utm_medium=direct&amp;amp;utm_campaign=getting_started_influxdb_3_grafana&amp;amp;utm_content=blog"&gt;resource-token documentation&lt;/a&gt; explains the &lt;code class="language-markup"&gt;db:"database":read&lt;/code&gt; permission format.&lt;/p&gt;

&lt;p&gt;Do not paste this token into a text file or include it in a screenshot. Enter it once in Grafana’s secure token field.&lt;/p&gt;

&lt;h4 id="connect-grafana-to-influxdb-3"&gt;Connect Grafana to InfluxDB 3&lt;/h4&gt;

&lt;p&gt;In Grafana:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Open &lt;strong&gt;Connections → datasources&lt;/strong&gt;.&lt;/li&gt;
  &lt;li&gt;Click &lt;strong&gt;Add new datasource&lt;/strong&gt;.&lt;/li&gt;
  &lt;li&gt;Select &lt;strong&gt;InfluxDB&lt;/strong&gt;. This is the built-in datasource; do not install another plugin.&lt;/li&gt;
  &lt;li&gt;Enter &lt;code class="language-markup"&gt;InfluxDB 3 Smart Building&lt;/code&gt; as the name.&lt;/li&gt;
  &lt;li&gt;Configure the connection:&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="blog-html-table-wrapper"&gt;
 &lt;table class="blog-html-table"&gt;
  &lt;thead&gt;
   &lt;tr&gt;
    &lt;th&gt;Setting&lt;/th&gt;
    &lt;th&gt;Value&lt;/th&gt;
   &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
   &lt;tr&gt;
    &lt;td&gt;URL&lt;/td&gt;
    &lt;td&gt;http://127.0.0.1:8181&lt;/td&gt;
   &lt;/tr&gt;
   &lt;tr&gt;
    &lt;td&gt;Query language&lt;/td&gt;
    &lt;td&gt;SQL&lt;/td&gt;
   &lt;/tr&gt;
   &lt;tr&gt;
    &lt;td&gt;Database&lt;/td&gt;
    &lt;td&gt;smart_building&lt;/td&gt;
   &lt;/tr&gt;
   &lt;tr&gt;
    &lt;td&gt;Token&lt;/td&gt;
    &lt;td&gt;The securely stored $INFLUXDB_GRAFANA_TOKEN value&lt;/td&gt;
   &lt;/tr&gt;
   &lt;tr&gt;
    &lt;td&gt;Insecure Connection&lt;/td&gt;
    &lt;td&gt;Enabled&lt;/td&gt;
   &lt;/tr&gt;
  &lt;/tbody&gt;
 &lt;/table&gt;
&lt;/div&gt;

&lt;ol&gt;
  &lt;li&gt;Click &lt;strong&gt;Save &amp;amp; test&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;SQL uses Flight SQL over gRPC. Because both programs are on the same machine and the URL uses local HTTP without TLS, &lt;strong&gt;Insecure Connection&lt;/strong&gt; is required. This setting disables gRPC TLS; it is not a recommendation for traffic crossing an untrusted network.&lt;/p&gt;

&lt;p&gt;The URL is interpreted by the Grafana server, not by your browser. &lt;code class="language-markup"&gt;127.0.0.1&lt;/code&gt; works here because Grafana and InfluxDB run on the same machine. If you later move either service into a container or another machine, update the URL to an address reachable from the Grafana backend while keeping the connection private.&lt;/p&gt;

&lt;p&gt;&lt;img src="//images.ctfassets.net/o7xu9whrs0u9/1my3gCZyflm5Y4KeiqZ6CI/767f7fd0bc9a6186cd4a9e0cc685c135/2fbe33f9-0c51-4b9b-bb21-e50f656579fa.png" alt="InfluxDB 3 and Grafana 2" /&gt;&lt;/p&gt;

&lt;h4 id="create-the-grafana-dashboard-and-variables"&gt;Create the Grafana Dashboard and Variables&lt;/h4&gt;

&lt;p&gt;Create the dashboard first:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Open &lt;strong&gt;Dashboards&lt;/strong&gt;.&lt;/li&gt;
  &lt;li&gt;Click &lt;strong&gt;New → New Dashboard&lt;/strong&gt;.&lt;/li&gt;
  &lt;li&gt;Save it as &lt;strong&gt;Smart Building Operations&lt;/strong&gt;.&lt;/li&gt;
  &lt;li&gt;Set the dashboard timezone to &lt;strong&gt;UTC&lt;/strong&gt;.&lt;/li&gt;
  &lt;li&gt;Set the absolute time range from &lt;code class="language-markup"&gt;2026-08-10 00:00:00&lt;/code&gt; through &lt;code class="language-markup"&gt;2026-08-17 00:00:00&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The fixed range matters because this is historical data generated in a specific time range. A relative range such as “Last 7 days” will not include it.&lt;/p&gt;

&lt;h4 id="add-the-building-variable"&gt;Add the Building Variable&lt;/h4&gt;

&lt;p&gt;While editing the dashboard, click &lt;strong&gt;Add → Variable&lt;/strong&gt; and configure:&lt;/p&gt;

&lt;div class="blog-html-table-wrapper"&gt;
 &lt;table class="blog-html-table"&gt;
  &lt;thead&gt;
   &lt;tr&gt;
    &lt;th&gt;Option&lt;/th&gt;
    &lt;th&gt;Value&lt;/th&gt;
   &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
   &lt;tr&gt;
    &lt;td&gt;Variable type&lt;/td&gt;
    &lt;td&gt;Query&lt;/td&gt;
   &lt;/tr&gt;
   &lt;tr&gt;
    &lt;td&gt;Name&lt;/td&gt;
    &lt;td&gt;&lt;code&gt;building&lt;/code&gt;&lt;/td&gt;
   &lt;/tr&gt;
   &lt;tr&gt;
    &lt;td&gt;Label&lt;/td&gt;
    &lt;td&gt;Building&lt;/td&gt;
   &lt;/tr&gt;
   &lt;tr&gt;
    &lt;td&gt;datasource&lt;/td&gt;
    &lt;td&gt;InfluxDB 3 Smart Building&lt;/td&gt;
   &lt;/tr&gt;
   &lt;tr&gt;
    &lt;td&gt;Multi-value&lt;/td&gt;
    &lt;td&gt;Enabled&lt;/td&gt;
   &lt;/tr&gt;
   &lt;tr&gt;
    &lt;td&gt;Include All&lt;/td&gt;
    &lt;td&gt;Enabled&lt;/td&gt;
   &lt;/tr&gt;
  &lt;/tbody&gt;
 &lt;/table&gt;
&lt;/div&gt;

&lt;p&gt;Use this SQL query:&lt;/p&gt;

&lt;pre class=""&gt;&lt;code class="language-sql"&gt;SELECT DISTINCT
  building AS __text,
  building AS __value
FROM building_telemetry
ORDER BY building&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The preview should show &lt;code class="language-markup"&gt;annex&lt;/code&gt; and &lt;code class="language-markup"&gt;hq&lt;/code&gt;. Save the variable.&lt;/p&gt;

&lt;h4 id="add-the-zone-variable"&gt;Add the Zone Variable&lt;/h4&gt;

&lt;p&gt;Add another query variable:&lt;/p&gt;

&lt;div class="blog-html-table-wrapper"&gt;
 &lt;table class="blog-html-table"&gt;
  &lt;thead&gt;
   &lt;tr&gt;
    &lt;th&gt;Option&lt;/th&gt;
    &lt;th&gt;Value&lt;/th&gt;
   &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
   &lt;tr&gt;
    &lt;td&gt;Name&lt;/td&gt;
    &lt;td&gt;&lt;code&gt;zone&lt;/code&gt;&lt;/td&gt;
   &lt;/tr&gt;
   &lt;tr&gt;
    &lt;td&gt;Label&lt;/td&gt;
    &lt;td&gt;Zone&lt;/td&gt;
   &lt;/tr&gt;
   &lt;tr&gt;
    &lt;td&gt;datasource&lt;/td&gt;
    &lt;td&gt;InfluxDB 3 Smart Building&lt;/td&gt;
   &lt;/tr&gt;
   &lt;tr&gt;
    &lt;td&gt;Multi-value&lt;/td&gt;
    &lt;td&gt;Enabled&lt;/td&gt;
   &lt;/tr&gt;
   &lt;tr&gt;
    &lt;td&gt;Include All&lt;/td&gt;
    &lt;td&gt;Enabled&lt;/td&gt;
   &lt;/tr&gt;
  &lt;/tbody&gt;
 &lt;/table&gt;
&lt;/div&gt;

&lt;p&gt;Use:&lt;/p&gt;

&lt;pre class=""&gt;&lt;code class="language-sql"&gt;SELECT DISTINCT
  zone AS __text,
  zone AS __value
FROM building_telemetry
WHERE building IN (${building:sqlstring})
ORDER BY zone&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This is a chained variable: changing Building refreshes the relevant Zone choices. For multi-value SQL variables, use &lt;code class="language-markup"&gt;IN&lt;/code&gt; and &lt;code class="language-markup"&gt;${variable:sqlstring}&lt;/code&gt;. Grafana supplies correctly quoted SQL strings; do not add another pair of quotes around the variable. See &lt;a href="https://grafana.com/docs/grafana/latest/datasources/influxdb/template-variables/?utm_source=website&amp;amp;utm_medium=direct&amp;amp;utm_campaign=getting_started_influxdb_3_grafana&amp;amp;utm_content=blog"&gt;InfluxDB template variables&lt;/a&gt; for more examples.&lt;/p&gt;

&lt;p&gt;Set both variables to &lt;strong&gt;All&lt;/strong&gt; before creating the panels.&lt;/p&gt;

&lt;h2 id="building-the-grafana-dashboard-panels"&gt;Building the Grafana dashboard panels&lt;/h2&gt;

&lt;p&gt;For each panel, click &lt;strong&gt;Add → Visualization,&lt;/strong&gt; select &lt;strong&gt;InfluxDB 3 Smart Building&lt;/strong&gt;, switch the query 
editor to SQL code mode if necessary, paste the query, choose the indicated format and visualization, then apply the panel.&lt;/p&gt;

&lt;p&gt;Every query uses two Grafana features:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class="language-markup"&gt;$__timeFilter(time)&lt;/code&gt; expands to the dashboard’s active time range.&lt;/li&gt;
  &lt;li&gt;&lt;code class="language-markup"&gt;${building:sqlstring}&lt;/code&gt; and &lt;code class="language-markup"&gt;${zone:sqlstring}&lt;/code&gt; expand the multi-select variables into quoted SQL values.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Grafana documents the available SQL macros in its &lt;a href="https://grafana.com/docs/grafana/latest/datasources/influxdb/query-editor/?utm_source=website&amp;amp;utm_medium=direct&amp;amp;utm_campaign=getting_started_influxdb_3_grafana&amp;amp;utm_content=blog"&gt;InfluxDB query editor reference&lt;/a&gt;.&lt;/p&gt;

&lt;h4 id="peak-co-dashboard"&gt;Peak CO₂ Dashboard&lt;/h4&gt;

&lt;p&gt;This stat answers, “What was the highest CO₂ reading in the selected scope?”&lt;/p&gt;

&lt;pre class=""&gt;&lt;code class="language-sql"&gt;SELECT MAX(co2_ppm) AS "Peak CO₂ ppm"
FROM building_telemetry
WHERE $__timeFilter(time)
  AND building IN (${building:sqlstring})
  AND zone IN (${zone:sqlstring})&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Configure:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Visualization: &lt;strong&gt;Stat&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;Format: &lt;strong&gt;Table&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;Unit: &lt;strong&gt;ppm&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;Title: &lt;strong&gt;Peak CO₂&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With all buildings and zones selected, expect 1,770 ppm.&lt;/p&gt;

&lt;p&gt;&lt;img src="//images.ctfassets.net/o7xu9whrs0u9/5Jlq6geUXEzBO7Iaj0Xl7r/6fdb2591da2147c97b38e9f90322a637/c639889b-2964-473d-b275-d62bc14ec884.png" alt="InfluxDB 3 and Grafana 3" /&gt;&lt;/p&gt;

&lt;p&gt;Keep the color neutral unless you have documented, context-appropriate CO₂ thresholds. A peak value alone does not describe duration, sensor accuracy, or local health policy.&lt;/p&gt;

&lt;h4 id="unhealthy-occupied-intervals-dashboard"&gt;Unhealthy Occupied Intervals Dashboard&lt;/h4&gt;

&lt;p&gt;This panel counts five-minute zone intervals that the plugin classified as high or critical while someone was present:&lt;/p&gt;

&lt;pre class=""&gt;&lt;code class="language-sql"&gt;SELECT COUNT(*) AS "Unhealthy occupied intervals"
FROM iaq_assessment
WHERE $__timeFilter(time)
  AND building IN (${building:sqlstring})
  AND zone IN (${zone:sqlstring})
  AND severity IN ('high', 'critical')
  AND occupied = true&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Configure:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Visualization: &lt;strong&gt;Stat&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;Format: &lt;strong&gt;Table&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;Unit: short&lt;/li&gt;
  &lt;li&gt;Title: &lt;strong&gt;Unhealthy occupied intervals&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Expected result: 62. This means 62 five-minute zone records, not 62 separate incidents.&lt;/p&gt;

&lt;h4 id="energy-while-empty-dashboard"&gt;Energy While Empty Dashboard&lt;/h4&gt;

&lt;p&gt;The source cadence is five minutes, so kWh for one row is &lt;code class="language-markup"&gt;power_kw × 5/60&lt;/code&gt;. This query integrates every interval with zero occupancy:&lt;/p&gt;

&lt;pre class=""&gt;&lt;code class="language-sql"&gt;SELECT ROUND(
  SUM(
    CASE
      WHEN occupancy = 0 THEN power_kw * 5.0 / 60.0
      ELSE 0
    END
  ),
  2
) AS "Empty-zone kWh"
FROM building_telemetry
WHERE $__timeFilter(time)
  AND building IN (${building:sqlstring})
  AND zone IN (${zone:sqlstring})&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Configure:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Visualization: &lt;strong&gt;Stat&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;Format: &lt;strong&gt;Table&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;Unit: &lt;strong&gt;kWh&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;Title: &lt;strong&gt;Energy while empty&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id="co-by-zone-dashboard"&gt;CO₂ by Zone Dashboard&lt;/h4&gt;

&lt;p&gt;A raw five-minute series is readable, but a 15-minute average makes the incident shape easier to compare across zones:&lt;/p&gt;

&lt;pre class=""&gt;&lt;code class="language-sql"&gt;SELECT
  date_bin(
    INTERVAL '15 minutes',
    time,
    TIMESTAMP '1970-01-01T00:00:00Z'
  ) AS time,
  zone,
  AVG(co2_ppm) AS "CO₂ ppm"
FROM building_telemetry
WHERE $__timeFilter(time)
  AND building IN (${building:sqlstring})
  AND zone IN (${zone:sqlstring})
GROUP BY 1, 2
ORDER BY 1&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Configure:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Visualization: &lt;strong&gt;Time series&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;Format: &lt;strong&gt;Time series&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;Unit: ppm&lt;/li&gt;
  &lt;li&gt;Legend: table, with maximum displayed&lt;/li&gt;
  &lt;li&gt;Title: &lt;strong&gt;CO₂ by zone&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The result contains 672 series rows: 112 quarter-hour bins × six zones. A time series query must return a timestamp column; naming it time keeps the Grafana mapping straightforward.&lt;/p&gt;

&lt;p&gt;&lt;img src="//images.ctfassets.net/o7xu9whrs0u9/26p6fhSvn9DqLvFPSMbmqN/ef63172e1dc7bbdc1ca706344ae46a82/63d5c6b0-60b9-4b89-8972-e155d9fdc855.png" alt="InfluxDB 3 and Grafana 4" /&gt;&lt;/p&gt;

&lt;h4 id="indoor-air-quality-state-dashboard"&gt;Indoor Air Quality State Dashboard&lt;/h4&gt;

&lt;pre class=""&gt;&lt;code class="language-sql"&gt;SELECT time, zone, severity
FROM iaq_assessment
WHERE $__timeFilter(time)
  AND building IN (${building:sqlstring})
  AND zone IN (${zone:sqlstring})
ORDER BY time&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Configure:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Visualization: &lt;strong&gt;State timeline&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;Format: &lt;strong&gt;Time series&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;Title: &lt;strong&gt;Indoor-air-quality state&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Add value mappings:&lt;/p&gt;

&lt;div class="blog-html-table-wrapper"&gt;
 &lt;table class="blog-html-table"&gt;
  &lt;thead&gt;
   &lt;tr&gt;
    &lt;th&gt;Value&lt;/th&gt;
    &lt;th&gt;Label&lt;/th&gt;
    &lt;th&gt;Suggested color&lt;/th&gt;
   &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
   &lt;tr&gt;
    &lt;td&gt;&lt;code&gt;normal&lt;/code&gt;&lt;/td&gt;
    &lt;td&gt;Normal&lt;/td&gt;
    &lt;td&gt;Green&lt;/td&gt;
   &lt;/tr&gt;
   &lt;tr&gt;
    &lt;td&gt;&lt;code&gt;moderate&lt;/code&gt;&lt;/td&gt;
    &lt;td&gt;Moderate&lt;/td&gt;
    &lt;td&gt;Yellow&lt;/td&gt;
   &lt;/tr&gt;
   &lt;tr&gt;
    &lt;td&gt;&lt;code&gt;high&lt;/code&gt;&lt;/td&gt;
    &lt;td&gt;High&lt;/td&gt;
    &lt;td&gt;Orange&lt;/td&gt;
   &lt;/tr&gt;
   &lt;tr&gt;
    &lt;td&gt;&lt;code&gt;critical&lt;/code&gt;&lt;/td&gt;
    &lt;td&gt;Critical&lt;/td&gt;
    &lt;td&gt;Red&lt;/td&gt;
   &lt;/tr&gt;
  &lt;/tbody&gt;
 &lt;/table&gt;
&lt;/div&gt;

&lt;h4 id="wasted-energy-dashboard"&gt;Wasted Energy Dashboard&lt;/h4&gt;

&lt;p&gt;The plugin’s energy signal shows how much power exceeded the 1.5 kW baseline while the zone was empty.&lt;/p&gt;

&lt;pre class=""&gt;&lt;code class="language-sql"&gt;SELECT
  date_bin(
    INTERVAL '15 minutes',
    time,
    TIMESTAMP '1970-01-01T00:00:00Z'
  ) AS time,
  zone,
  AVG(energy_waste_kw) AS "Excess kW wasted"
FROM iaq_assessment
WHERE $__timeFilter(time)
  AND building IN (${building:sqlstring})
  AND zone IN (${zone:sqlstring})
GROUP BY 1, 2
ORDER BY 1&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Configure:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Visualization: &lt;strong&gt;Time series&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;Format: &lt;strong&gt;Time series&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;Draw style: bars&lt;/li&gt;
  &lt;li&gt;Stacking: normal&lt;/li&gt;
  &lt;li&gt;Unit: &lt;code class="language-markup"&gt;kW&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;Title: &lt;strong&gt;Energy wasted&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The derived weekly total is 9.29 kWh, concentrated in the lab’s Wednesday evening event. Panel 3’s 316.04 kWh and this 9.29 kWh are intentionally different—the second number is more conservative.&lt;/p&gt;

&lt;p&gt;&lt;img src="//images.ctfassets.net/o7xu9whrs0u9/5FJh5uuI4xD5lNIsypoI1L/7f57e792c902d8637c01c7f2206c8ff4/957a8adc-6ea9-4ec8-89f2-be452b3d0a0f.png" alt="InfluxDB 3 and Grafana 5" /&gt;&lt;/p&gt;

&lt;h4 id="risk-by-zone-dashboard"&gt;Risk by Zone Dashboard&lt;/h4&gt;

&lt;p&gt;This query compares each zone’s normal operating level with its worst interval:&lt;/p&gt;

&lt;pre class=""&gt;&lt;code class="language-sql"&gt;SELECT
  zone,
  ROUND(AVG(risk_score), 1) AS "Average risk",
  MAX(risk_score) AS "Peak risk"
FROM iaq_assessment
WHERE $__timeFilter(time)
  AND building IN (${building:sqlstring})
  AND zone IN (${zone:sqlstring})
GROUP BY zone
ORDER BY "Peak risk" DESC&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Configure:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Visualization: &lt;strong&gt;Bar gauge&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;Format: &lt;strong&gt;Table&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;Minimum: &lt;strong&gt;0&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;Maximum: &lt;strong&gt;100&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;Unit: &lt;strong&gt;none&lt;/strong&gt;—the score is bounded from 0 to 100 but is not a percentage&lt;/li&gt;
  &lt;li&gt;Title: &lt;strong&gt;Risk by zone&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src="//images.ctfassets.net/o7xu9whrs0u9/2yof0RI2DgWBhP4fw5K118/54fa0fdf1874038d7f36f2bd4f0a3fb3/46780643-dcb0-46ad-aa99-6345b4442361.png" alt="InfluxDB 3 and Grafana 6" /&gt;&lt;/p&gt;

&lt;h4 id="operational-hotspots-dashboard"&gt;Operational Hotspots Dashboard&lt;/h4&gt;

&lt;p&gt;Finish with a table that puts ventilation and energy context together:&lt;/p&gt;

&lt;pre class=""&gt;&lt;code class="language-sql"&gt;SELECT
  building,
  zone,
  MAX(co2_ppm) AS peak_co2_ppm,
  ROUND(AVG(pm25_ug_m3), 1) AS avg_pm25,
  ROUND(
    SUM(CASE WHEN occupancy = 0 THEN power_kw * 5.0 / 60.0 ELSE 0 END),
    2
  ) AS empty_zone_kwh
FROM building_telemetry
WHERE $__timeFilter(time)
  AND building IN (${building:sqlstring})
  AND zone IN (${zone:sqlstring})
GROUP BY building, zone
ORDER BY peak_co2_ppm DESC&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Configure:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Visualization: &lt;strong&gt;Table&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;Format: &lt;strong&gt;Table&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;Sort: &lt;code class="language-markup"&gt;peak_co2_ppm&lt;/code&gt;, descending&lt;/li&gt;
  &lt;li&gt;Title: &lt;strong&gt;Operational hotspots&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The table should contain six rows. conference_a ranks first for CO₂, while the lab leads in raw empty-zone energy, a useful reminder that air quality and energy efficiency reveal different operational problems. Save the dashboard after adding all eight panels.&lt;/p&gt;

&lt;p&gt;&lt;img src="//images.ctfassets.net/o7xu9whrs0u9/6YJqNgPGe7DkBc6PTWEr0Q/f4b74aeb992426952ac223247b8c0e7d/0ea255c9-2f4e-44e6-9ed3-bf34a6592cd1.png" alt="InfluxDB 3 and Grafana 7" /&gt;&lt;/p&gt;

&lt;h2 id="where-to-take-the-project-next"&gt;Where to take the project next?&lt;/h2&gt;

&lt;p&gt;You now have a complete InfluxDB 3 and Grafana stack running locally. Telemetry is written as line protocol, with a Python plugin enriching every row as it lands, a derived table storing decisions rather than measurements, and a Grafana dashboard querying both.
The pattern is worth more than just this project. The plugin is roughly fifty lines of standard-library Python. The trigger is table-scoped so it cannot recurse. The raw table is untouched, so if you decide next month that 800 ppm was the wrong CO₂ threshold, you can rewrite the scoring and recompute from source. And because the enrichment happens once at write time, every dashboard panel stays a short, readable query.&lt;/p&gt;

&lt;h4 id="grafana-features-to-try"&gt;Grafana Features to Try&lt;/h4&gt;

&lt;p&gt;You built eight panels in this tutorial, but that’s only a fraction of what Grafana can do:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Transformations&lt;/strong&gt; - Join &lt;code class="language-markup"&gt;building_telemetry&lt;/code&gt; and &lt;code class="language-markup"&gt;iaq_assessment&lt;/code&gt; on time and zone, then use Organize fields and Add field from calculation to build a combined table without writing a more complex SQL query.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Data links and drill-downs&lt;/strong&gt; - Turn each row of the Operational hotspots table into a link that opens a zone-scoped view with the zone variable pre-set. It’s a two-minute change that makes the dashboard feel like an application.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Ad hoc filters&lt;/strong&gt; - An ad hoc filter variable lets viewers add their own building = hq style constraints at view time without editing any query.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Canvas and Geomap&lt;/strong&gt; - Canvas can overlay live severity colors on a floor plan image; Geomap does the same on a real map if your zones carry coordinates. Both are far more legible to non-technical stakeholders than a time series.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Explore mode&lt;/strong&gt; - Prototype SQL against the datasource without creating a panel first—the fastest way to iterate on a query.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Dashboards as code&lt;/strong&gt; - Export the dashboard JSON and check it into version control, or use Grafana provisioning to deploy the datasource and dashboard together on a fresh machine.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id="influxdb-3-features-to-try"&gt;InfluxDB 3 Features to Try&lt;/h4&gt;

&lt;p&gt;The Processing Engine plugin you wrote is one trigger type among several, and InfluxDB has caching and lifecycle features that pair directly with what you just built:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Scheduled triggers&lt;/strong&gt; - WAL triggers run inline on the write path, so they should stay cheap. A scheduled trigger runs on an interval or cron expression instead. These are great for hourly and daily rollups, anomaly detection over a window, or anything that needs to look across many rows rather than one.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Rollup tables&lt;/strong&gt; - Have a scheduled trigger write a &lt;code class="language-markup"&gt;zone_hourly&lt;/code&gt; summary. Dashboards over long ranges then read a small pre-aggregated table instead of scanning weeks of five-minute data.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;HTTP request triggers&lt;/strong&gt; - Expose a plugin at an endpoint, and you have a small API—a current-status lookup for a room display, or a webhook that returns the top three zones to investigate.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Last Value Cache&lt;/strong&gt; - Keeps the most recent value per series in memory for very fast lookups, which suits “current state” stat panels and real-time displays far better than a &lt;code class="language-markup"&gt;MAX(time)&lt;/code&gt; subquery.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Distinct Value Cache&lt;/strong&gt; - Speeds up exactly the kind of query your Building and Zone template variables run, like &lt;code class="language-markup"&gt;SELECT DISTINCT&lt;/code&gt; over a tag column. On a large deployment, this is the difference between dashboards that load instantly and dashboards that stall on variable refresh.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Retention periods&lt;/strong&gt; - Set a retention period on the database so raw five-minute data expires while your rollup table keeps the long history. This is the standard way to control storage cost in time series systems.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;InfluxDB 3 Explorer&lt;/strong&gt; - A browser UI for browsing databases and prototyping queries, handy when you want to check a table’s shape without dropping into the CLI.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id="get-started-with-influxdb-3-and-grafana"&gt;Get started with InfluxDB 3 and Grafana&lt;/h2&gt;

&lt;p&gt;If you have not set up InfluxDB 3 yet, start a free InfluxDB 3 Enterprise trial or download InfluxDB 3 Core from the InfluxData downloads page, then follow the &lt;a href="https://docs.influxdata.com/influxdb3/enterprise/install/?utm_source=website&amp;amp;utm_medium=direct&amp;amp;utm_campaign=getting_started_influxdb_3_grafana&amp;amp;utm_content=blog"&gt;installation guide&lt;/a&gt;. The full Processing Engine reference is in the &lt;a href="https://docs.influxdata.com/influxdb3/enterprise/plugins/?utm_source=website&amp;amp;utm_medium=direct&amp;amp;utm_campaign=getting_started_influxdb_3_grafana&amp;amp;utm_content=blog"&gt;InfluxDB 3 documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Built something with this pattern? A different plugin, a better scoring model, a dashboard for a domain we didn’t think of? Share it in the &lt;a href="https://community.influxdata.com/?utm_source=website&amp;amp;utm_medium=direct&amp;amp;utm_campaign=getting_started_influxdb_3_grafana&amp;amp;utm_content=blog"&gt;InfluxData Community forums&lt;/a&gt;. Questions are welcome there too.&lt;/p&gt;
</description>
      <pubDate>Wed, 02 Sep 2026 07:00:00 +0000</pubDate>
      <link>https://www.influxdata.com/blog/getting-started-influxdb-3-grafana/</link>
      <guid isPermaLink="true">https://www.influxdata.com/blog/getting-started-influxdb-3-grafana/</guid>
      <category>Getting Started</category>
      <category>Developer</category>
      <author>Charles Mahler (InfluxData)</author>
    </item>
    <item>
      <title>A Guide to Downsampling Time Series Data with InfluxDB 3</title>
      <description>&lt;p&gt;This tutorial demonstrates both approaches using the InfluxDB 3 Processing Engine’s built-in bird tracking simulator plugin. You will generate telemetry, aggregate it into 10-second windows, and validate the result with SQL. The same pattern works for infrastructure metrics, industrial sensors, application telemetry, and other time series workloads.&lt;/p&gt;

&lt;h2 id="why-downsample-time-series-data"&gt;Why downsample time series data?&lt;/h2&gt;

&lt;p&gt;High-resolution data is valuable while diagnosing a recent event, but its value often changes as it ages. A temperature reading collected every second may be useful for an active incident, while a daily report may only need 10-minute or hourly averages.&lt;/p&gt;

&lt;p&gt;Downsampling helps you:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;scan fewer rows in long-range queries&lt;/li&gt;
  &lt;li&gt;make dashboards over weeks or months more responsive&lt;/li&gt;
  &lt;li&gt;retain useful historical trends at a lower resolution, reducing storage costs&lt;/li&gt;
  &lt;li&gt;calculate common summaries once instead of repeating the work&lt;/li&gt;
  &lt;li&gt;keep raw data only for as long as its full resolution is useful&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Downsampling is not the same as deleting raw data; it creates a summarized view or table at a lower granularity. Retention is a separate decision and process where data is fully deleted after a set period of time.&lt;/p&gt;

&lt;h4 id="choose-query-time-or-persisted-downsampling"&gt;Choose Query-Time or Persisted Downsampling&lt;/h4&gt;

&lt;p&gt;InfluxDB 3 gives you two practical patterns for downsampling:&lt;/p&gt;

&lt;div class="blog-html-table-wrapper"&gt;
 &lt;table class="blog-html-table"&gt;
  &lt;thead&gt;
   &lt;tr&gt;
    &lt;th&gt;Approach&lt;/th&gt;
    &lt;th&gt;How it works&lt;/th&gt;
    &lt;th&gt;Best for&lt;/th&gt;
    &lt;th&gt;Main tradeoff&lt;/th&gt;
   &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
   &lt;tr&gt;
    &lt;td&gt;Query-time SQL&lt;/td&gt;
    &lt;td&gt;Uses &lt;code&gt;DATE_BIN&lt;/code&gt; and aggregate functions in a &lt;code&gt;SELECT&lt;/code&gt; query&lt;/td&gt;
    &lt;td&gt;Exploration, flexible dashboards, and changing aggregation requirements&lt;/td&gt;
    &lt;td&gt;Recomputes the result each time and does not reduce stored raw data&lt;/td&gt;
   &lt;/tr&gt;
   &lt;tr&gt;
    &lt;td&gt;Python Processing Engine&lt;/td&gt;
    &lt;td&gt;Runs the official downsampler plugin on a schedule and writes aggregate rows to a target table&lt;/td&gt;
    &lt;td&gt;Repeated long-range queries, predictable rollups, and tiered retention&lt;/td&gt;
    &lt;td&gt;Requires you to choose aggregates and scheduling behavior in advance&lt;/td&gt;
   &lt;/tr&gt;
  &lt;/tbody&gt;
 &lt;/table&gt;
&lt;/div&gt;

&lt;p&gt;A useful starting point is to develop and validate an aggregate in SQL. If the same query becomes a frequent or expensive workload, then use the downsampler plugin.&lt;/p&gt;

&lt;h2 id="prerequisites"&gt;Prerequisites&lt;/h2&gt;

&lt;p&gt;To follow this tutorial, you need:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;InfluxDB 3 Core or Enterprise with the Processing Engine enabled&lt;/li&gt;
  &lt;li&gt;InfluxDB 3 CLI installed and connected to the server&lt;/li&gt;
  &lt;li&gt;An authorization token with permission to create a database and triggers&lt;/li&gt;
  &lt;li&gt;Outbound access to GitHub so InfluxDB can load the official plugins referenced by gh: paths&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The examples assume a database named &lt;code class="language-markup"&gt;bird_demo&lt;/code&gt;:&lt;/p&gt;

&lt;pre class=""&gt;&lt;code class="language-bash"&gt;influxdb3 create database bird_demo&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;If you are using the local Docker environment that accompanies this article, InfluxDB is available at &lt;code class="language-markup"&gt;http://localhost:8181&lt;/code&gt;. InfluxDB 3 Explorer is available separately at &lt;code class="language-markup"&gt;http://localhost:8888&lt;/code&gt; for running the SQL and capturing visual results.&lt;/p&gt;

&lt;h4 id="generate-sample-telemetry-with-the-official-bird-simulator"&gt;Generate Sample Telemetry with the Official Bird Simulator&lt;/h4&gt;

&lt;p&gt;InfluxData’s &lt;a href="https://github.com/influxdata/influxdb3_plugins/tree/main/influxdata/bird_data_simulator/?utm_source=website&amp;amp;utm_medium=direct&amp;amp;utm_campaign=downsampling_guide_influxdb_3&amp;amp;utm_content=blog"&gt;bird data simulator&lt;/a&gt; is a convenient fit for this tutorial. It creates repeatable time series behavior without requiring a separate data generator or hardware sensor.&lt;/p&gt;

&lt;p&gt;The plugin writes to the &lt;code class="language-markup"&gt;bird_tracking&lt;/code&gt; table. Its tags include species and name, and its fields include &lt;code class="language-markup"&gt;body_temp&lt;/code&gt;, &lt;code class="language-markup"&gt;longitude&lt;/code&gt;, &lt;code class="language-markup"&gt;latitude&lt;/code&gt;, &lt;code class="language-markup"&gt;speed&lt;/code&gt;, and &lt;code class="language-markup"&gt;heading&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;First, install the plugin’s &lt;code class="language-markup"&gt;Faker&lt;/code&gt; dependency:&lt;/p&gt;

&lt;pre class=""&gt;&lt;code class="language-bash"&gt;influxdb3 install package Faker&lt;/code&gt;&lt;/pre&gt;

&lt;pre class=""&gt;&lt;code class="language-bash"&gt;influxdb3 create trigger \
  --database bird_demo \
  --path gh:influxdata/bird_data_simulator/bird_data_simulator.py \
  --trigger-spec every:1s \
  --trigger-arguments bird_count=10 \
  bird_tracking_demo&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;After the trigger has run for several seconds, confirm that data is arriving:&lt;/p&gt;

&lt;pre class=""&gt;&lt;code class="language-sql"&gt;SELECT *
FROM bird_tracking
ORDER BY time DESC
LIMIT 5;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;At one write per bird per second, the simulator produces approximately 600 rows per minute for 10 birds.&lt;/p&gt;

&lt;h4 id="downsample-at-query-time-with-sql"&gt;Downsample at Query Time with SQL&lt;/h4&gt;

&lt;p&gt;InfluxDB 3 SQL supports time bucketing with &lt;code class="language-markup"&gt;DATE_BIN()&lt;/code&gt;. The following query groups the raw bird readings into 10-second intervals and calculates speed and body-temperature statistics for each bird:&lt;/p&gt;

&lt;pre class=""&gt;&lt;code class="language-sql"&gt;SELECT
  DATE_BIN(INTERVAL '10 seconds', time) AS time,
  species,
  name,
  AVG(speed) AS avg_speed,
  MIN(speed) AS min_speed,
  MAX(speed) AS max_speed,
  AVG(body_temp) AS avg_body_temp,
  COUNT(*) AS record_count
FROM bird_tracking
WHERE time &amp;gt;= now() - INTERVAL '2 minutes'
GROUP BY 1, species, name
ORDER BY 1 DESC, species, name;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;code class="language-markup"&gt;DATE_BIN&lt;/code&gt; aligns each timestamp to a 10-second boundary. &lt;code class="language-markup"&gt;GROUP BY 1&lt;/code&gt; refers to the first expression in the &lt;code class="language-markup"&gt;SELECT&lt;/code&gt; list while &lt;code class="language-markup"&gt;species&lt;/code&gt; and &lt;code class="language-markup"&gt;name&lt;/code&gt; preserve one series per bird.&lt;/p&gt;

&lt;p&gt;This is true query-time downsampling: the server returns fewer, summarized rows, but it does not write them to another table. Change the interval or aggregates whenever the question changes. For example, a long-range dashboard might replace &lt;code class="language-markup"&gt;INTERVAL '10 seconds'&lt;/code&gt; with &lt;code class="language-markup"&gt;INTERVAL '1 hour'&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Always include a bounded time predicate. It reduces the data scanned and makes the query’s intended resolution explicit.&lt;/p&gt;

&lt;h4 id="persist-downsampled-data-with-the-python-processing-engine"&gt;Persist Downsampled Data with the Python Processing Engine&lt;/h4&gt;

&lt;p&gt;For an aggregate that many users or dashboards repeatedly request, calculate it on a schedule with InfluxData’s official &lt;a href="https://docs.influxdata.com/influxdb3/core/plugins/library/official/downsampler/?utm_source=website&amp;amp;utm_medium=direct&amp;amp;utm_campaign=downsampling_guide_influxdb_3&amp;amp;utm_content=blog"&gt;downsampler plugin&lt;/a&gt;. The plugin queries the source table, computes aggregates, and writes the results to a target table.&lt;/p&gt;

&lt;p&gt;This trigger creates one average speed and body-temperature row per bird for every 10-second interval:&lt;/p&gt;

&lt;pre class=""&gt;&lt;code class="language-bash"&gt;influxdb3 create trigger \
  --database bird_demo \
  --path gh:influxdata/downsampler/downsampler.py \
  --trigger-spec every:10s \
  --trigger-arguments \
  'source_measurement=bird_tracking,target_measurement=bird_tracking_10s,interval=10s,window=2min,offset=10s,calculations=speed:avg.body_temp:avg,specific_fields=speed.body_temp' \
  bird_tracking_downsample&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The trigger arguments control the rollup:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class="language-markup"&gt;source_measurement&lt;/code&gt; is the raw source table.&lt;/li&gt;
  &lt;li&gt;&lt;code class="language-markup"&gt;target_measurement&lt;/code&gt; is the table that receives aggregates&lt;/li&gt;
  &lt;li&gt;&lt;code class="language-markup"&gt;interval=10s&lt;/code&gt; defines the time-bin width.&lt;/li&gt;
  &lt;li&gt;&lt;code class="language-markup"&gt;calculations&lt;/code&gt; assigns an aggregate function to each field&lt;/li&gt;
  &lt;li&gt;&lt;code class="language-markup"&gt;specific_fields&lt;/code&gt; limits processing to the fields needed by this rollup.&lt;/li&gt;
  &lt;li&gt;&lt;code class="language-markup"&gt;offset=10s&lt;/code&gt; delays the queried window so its newest bin has time to close.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id="why-the-offset-matters"&gt;Why the Offset Matters&lt;/h4&gt;

&lt;p&gt;The offset is not cosmetic. Without it, the simulator and downsampler could execute on the same 10-second scheduler boundary. Without an offset, the downsampler may query the newest bin while writes are still arriving, producing partial aggregates.&lt;/p&gt;

&lt;p&gt;With &lt;code class="language-markup"&gt;offset=10s&lt;/code&gt;, the preceding completed bin and every aggregate in the validation window should have the expected 10 source points. In production, set the offset to at least the maximum delay you expect between an event occurring and becoming queryable. Workloads with late or out-of-order data may need a larger offset and lookback window.&lt;/p&gt;

&lt;h4 id="inspect-the-persisted-result"&gt;Inspect the Persisted Result&lt;/h4&gt;

&lt;p&gt;The downsampler names calculated fields by appending the aggregate function, so &lt;code class="language-markup"&gt;speed&lt;/code&gt; becomes &lt;code class="language-markup"&gt;speed_avg&lt;/code&gt; and &lt;code class="language-markup"&gt;body_temp&lt;/code&gt; becomes &lt;code class="language-markup"&gt;body_temp_avg&lt;/code&gt;. It also writes &lt;code class="language-markup"&gt;record_count&lt;/code&gt;, &lt;code class="language-markup"&gt;time_from&lt;/code&gt;, and &lt;code class="language-markup"&gt;time_to&lt;/code&gt; metadata.&lt;/p&gt;

&lt;p&gt;The plugin stores the data with nanosecond precision and converts it with &lt;code class="language-markup"&gt;TO_TIMESTAMP_NANOS()&lt;/code&gt; when you want readable timestamps:&lt;/p&gt;

&lt;pre class=""&gt;&lt;code class="language-sql"&gt;SELECT
  time,
  species,
  name,
  ROUND(speed_avg, 2) AS avg_speed_mph,
  ROUND(body_temp_avg, 2) AS avg_body_temp_c,
  record_count,
  TO_TIMESTAMP_NANOS(time_from) AS source_start,
  TO_TIMESTAMP_NANOS(time_to) AS source_end
FROM bird_tracking_10s
ORDER BY time DESC, species, name
LIMIT 20;&lt;/code&gt;&lt;/pre&gt;

&lt;h4 id="sample-output"&gt;Sample Output&lt;/h4&gt;

&lt;div class="blog-html-table-wrapper"&gt;
 &lt;table class="blog-html-table"&gt;
  &lt;thead&gt;
   &lt;tr&gt;
    &lt;th&gt;Time (UTC)&lt;/th&gt;
    &lt;th&gt;Species&lt;/th&gt;
    &lt;th&gt;Bird&lt;/th&gt;
    &lt;th&gt;Avg. speed&lt;/th&gt;
    &lt;th&gt;Avg. body temp.&lt;/th&gt;
    &lt;th&gt;Source points&lt;/th&gt;
    &lt;th&gt;Source start&lt;/th&gt;
   &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
   &lt;tr&gt;
    &lt;td&gt;2026-08-14 21:21:00&lt;/td&gt;
    &lt;td&gt;American Robin&lt;/td&gt;
    &lt;td&gt;Tracy&lt;/td&gt;
    &lt;td&gt;22.47&lt;/td&gt;
    &lt;td&gt;41.94&lt;/td&gt;
    &lt;td&gt;10&lt;/td&gt;
    &lt;td&gt;21:21:00&lt;/td&gt;
   &lt;/tr&gt;
   &lt;tr&gt;
    &lt;td&gt;2026-08-14 21:21:00&lt;/td&gt;
    &lt;td&gt;Blue Jay&lt;/td&gt;
    &lt;td&gt;Sara&lt;/td&gt;
    &lt;td&gt;21.59&lt;/td&gt;
    &lt;td&gt;41.67&lt;/td&gt;
    &lt;td&gt;10&lt;/td&gt;
    &lt;td&gt;21:21:00&lt;/td&gt;
   &lt;/tr&gt;
   &lt;tr&gt;
    &lt;td&gt;2026-08-14 21:21:00&lt;/td&gt;
    &lt;td&gt;Cactus Wren&lt;/td&gt;
    &lt;td&gt;Brandon&lt;/td&gt;
    &lt;td&gt;18.78&lt;/td&gt;
    &lt;td&gt;41.74&lt;/td&gt;
    &lt;td&gt;10&lt;/td&gt;
    &lt;td&gt;21:21:00&lt;/td&gt;
   &lt;/tr&gt;
   &lt;tr&gt;
    &lt;td&gt;2026-08-14 21:21:00&lt;/td&gt;
    &lt;td&gt;Great Blue Heron&lt;/td&gt;
    &lt;td&gt;Brenda&lt;/td&gt;
    &lt;td&gt;6.38&lt;/td&gt;
    &lt;td&gt;41.10&lt;/td&gt;
    &lt;td&gt;10&lt;/td&gt;
    &lt;td&gt;21:21:00&lt;/td&gt;
   &lt;/tr&gt;
   &lt;tr&gt;
    &lt;td&gt;2026-08-14 21:21:00&lt;/td&gt;
    &lt;td&gt;Great Blue Heron&lt;/td&gt;
    &lt;td&gt;Pamela&lt;/td&gt;
    &lt;td&gt;24.97&lt;/td&gt;
    &lt;td&gt;41.35&lt;/td&gt;
    &lt;td&gt;10&lt;/td&gt;
    &lt;td&gt;21:21:00&lt;/td&gt;
   &lt;/tr&gt;
  &lt;/tbody&gt;
 &lt;/table&gt;
&lt;/div&gt;

&lt;h4 id="validate-the-reduction-with-sql"&gt;Validate the Reduction with SQL&lt;/h4&gt;

&lt;p&gt;Do not judge a downsampling job only by whether the target table contains rows. Compare a fixed, completed interval so active writes cannot change the counts during validation.&lt;/p&gt;

&lt;p&gt;The following query compares two completed minutes of source data with the corresponding persisted rollups. &lt;strong&gt;Replace the timestamps with a completed interval from your own run&lt;/strong&gt;:&lt;/p&gt;

&lt;pre class=""&gt;&lt;code class="language-sql"&gt;WITH raw AS (
  SELECT COUNT(*) AS raw_rows, COUNT(DISTINCT name) AS birds
  FROM bird_tracking
  WHERE time &amp;gt;= TIMESTAMP '2026-08-14T21:20:00Z'
    AND time " TIMESTAMP '2026-08-14T21:22:00Z'
),
downsampled AS (
  SELECT
    COUNT(*) AS downsampled_rows,
    SUM(record_count) AS represented_raw_rows,
    AVG(record_count) AS avg_points_per_row
  FROM bird_tracking_10s
  WHERE time &amp;gt;= TIMESTAMP '2026-08-14T21:20:00Z'
    AND time " TIMESTAMP '2026-08-14T21:22:00Z'
)
SELECT
  raw_rows,
  birds,
  downsampled_rows,
  represented_raw_rows,
  avg_points_per_row,
  ROUND(
    100.0 * (
      1.0 - CAST(downsampled_rows AS DOUBLE) / CAST(raw_rows AS DOUBLE)
    ),
    1
  ) AS row_reduction_percent
FROM raw CROSS JOIN downsampled;&lt;/code&gt;&lt;/pre&gt;

&lt;h4 id="example-output"&gt;Example Output&lt;/h4&gt;

&lt;p&gt;The equality between &lt;code class="language-markup"&gt;raw_rows&lt;/code&gt; and &lt;code class="language-markup"&gt;represented_raw_rows&lt;/code&gt; is an important completeness check. The 90% figure is a reduction in rows for this query shape, not a promise of an identical reduction in bytes on disk. Actual storage depends on schema, tags, fields, compression, and retention settings.&lt;/p&gt;

&lt;p&gt;You should also compare aggregates against the query-time SQL version before applying any raw-data retention policy.&lt;/p&gt;

&lt;h2 id="downsampling-best-practices-for-production"&gt;Downsampling best practices for production&lt;/h2&gt;

&lt;p&gt;The tutorial uses short intervals so you can see results quickly. A production workload might keep second-level raw data briefly, create 5-minute summaries for operational dashboards, and return hourly summaries for long-term reporting.&lt;/p&gt;

&lt;p&gt;When designing that pipeline:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class="language-markup"&gt;window=2min&lt;/code&gt; gives each execution a lookback window large enough to reprocess recent data.&lt;/li&gt;
  &lt;li&gt;Start with the questions users need to answer, then choose the interval. A bin that is too wide can hide short-lived behavior.&lt;/li&gt;
  &lt;li&gt;Preserve the tags you need for filtering and grouping. Removing a dimension during aggregation cannot be reversed later.&lt;/li&gt;
  &lt;li&gt;Select aggregates that match the signal. Gauges often need average, minimum, and maximum; counters may need sums or rates.&lt;/li&gt;
  &lt;li&gt;Account for ingestion delay with &lt;code class="language-markup"&gt;offset&lt;/code&gt;, and use a lookback window that can catch expected late data.&lt;/li&gt;
  &lt;li&gt;Validate counts and values over multiple completed intervals before shortening raw-data retention.&lt;/li&gt;
  &lt;li&gt;Monitor the Processing Engine logs and query latency after deployment.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id="troubleshoot-a-scheduled-downsampler"&gt;Troubleshoot a scheduled downsampler&lt;/h2&gt;

&lt;p&gt;If the target table remains empty, inspect the Processing Engine logs:&lt;/p&gt;

&lt;pre class=""&gt;&lt;code class="language-sql"&gt;SELECT
  trigger_name,
  log_level,
  log_text,
  event_time
FROM system.processing_engine_logs
ORDER BY event_time DESC
LIMIT 20;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Then check the most common causes:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;The source table or a field name does not match the trigger arguments.&lt;/li&gt;
  &lt;li&gt;The source does not contain data within the plugin’s window and offset.&lt;/li&gt;
  &lt;li&gt;A Python dependency required by a plugin has not been installed.&lt;/li&gt;
  &lt;li&gt;The server cannot retrieve a remote &lt;code class="language-markup"&gt;gh:&lt;/code&gt; plugin.&lt;/li&gt;
  &lt;li&gt;The selected aggregate is not valid for a field’s data type.&lt;/li&gt;
  &lt;li&gt;The newest interval is incomplete because the offset is too small.&lt;/li&gt;
  &lt;li&gt;Getting started with downsampling with InfluxDB 3&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;InfluxDB 3 makes it possible to use the same SQL aggregation logic at two stages of a workload: interactively at query time and operationally as a persisted rollup. Start with &lt;code class="language-markup"&gt;DATE_BIN&lt;/code&gt; to confirm the right interval and dimensions. When the query becomes a stable, repeated access pattern, schedule the official Python downsampler and include an offset so it works on completed data.&lt;/p&gt;

&lt;p&gt;Try the tutorial with &lt;a href="https://www.influxdata.com/influxdb-signup/?utm_source=website&amp;amp;utm_medium=direct&amp;amp;utm_campaign=downsampling_guide_influxdb_3&amp;amp;utm_content=blog"&gt;InfluxDB 3&lt;/a&gt; and the &lt;a href="https://github.com/influxdata/influxdb3_plugins/tree/main/influxdata/?utm_source=website&amp;amp;utm_medium=direct&amp;amp;utm_campaign=downsampling_guide_influxdb_3&amp;amp;utm_content=blog"&gt;official InfluxDB 3 plugin library&lt;/a&gt;. Once your results match the raw source over a completed interval, adapt the interval, aggregates, offset, and retention strategy to your production workload.&lt;/p&gt;

&lt;h2 id="faqs"&gt;FAQs&lt;/h2&gt;

&lt;div id="accordion_second"&gt;
    &lt;article class="message"&gt;
        &lt;a href="javascript:void(0)" data-action="collapse" data-target="collapsible-message-accordion-second-1"&gt;
            &lt;div class="message-header"&gt;
                &lt;h3&gt;What is downsampling?&lt;/h3&gt;
                &lt;span class="icon"&gt;
                    &lt;i class="fas fa-angle-down" aria-hidden="true"&gt;&lt;/i&gt;
                &lt;/span&gt;
            &lt;/div&gt;&lt;/a&gt;
        &lt;div id="collapsible-message-accordion-second-1" class="message-body is-collapsible is-active" data-parent="accordion_second" data-allow-multiple="true"&gt;
            &lt;div class="message-body-content"&gt;
                Downsampling is the process of grouping time series points into wider time intervals and calculating summaries such as averages, minimums, maximums, sums, or counts. InfluxDB 3 can calculate those summaries at query time with SQL or persist them with a Python Processing Engine.
            &lt;/div&gt;
        &lt;/div&gt;
    &lt;/article&gt;

    &lt;article class="message"&gt;
        &lt;a href="javascript:void(0)" data-action="collapse" data-target="collapsible-message-accordion-second-2"&gt;
            &lt;div class="message-header"&gt;
                &lt;h3&gt;How do I downsample data with InfluxDB 3 SQL?&lt;/h3&gt;
                &lt;span class="icon"&gt;
                    &lt;i class="fas fa-angle-down" aria-hidden="true"&gt;&lt;/i&gt;
                &lt;/span&gt;
            &lt;/div&gt;&lt;/a&gt;
        &lt;div id="collapsible-message-accordion-second-2" class="message-body is-collapsible" data-parent="accordion_second" data-allow-multiple="true"&gt;
            &lt;div class="message-body-content"&gt;
              Use &lt;code class="language-markup"&gt;DATE_BIN&lt;/code&gt; to place timestamps into fixed intervals, apply aggregate functions such as &lt;code class="language-markup"&gt;AVG&lt;/code&gt; or &lt;code class="language-markup"&gt;MAX&lt;/code&gt;, and group by the binned time plus any tags you want to preserve. SQL downsampling returns aggregate rows but does not automatically write them to another table.
            &lt;/div&gt;
        &lt;/div&gt;
    &lt;/article&gt;

    &lt;article class="message"&gt;
        &lt;a href="javascript:void(0)" data-action="collapse" data-target="collapsible-message-accordion-second-3"&gt;
            &lt;div class="message-header"&gt;
                &lt;h3&gt;When should I use the Python Processing Engine instead of SQL alone?&lt;/h3&gt;
                &lt;span class="icon"&gt;
                    &lt;i class="fas fa-angle-down" aria-hidden="true"&gt;&lt;/i&gt;
                &lt;/span&gt;
            &lt;/div&gt;&lt;/a&gt;
        &lt;div id="collapsible-message-accordion-second-3" class="message-body is-collapsible" data-parent="accordion_second" data-allow-multiple="true"&gt;
            &lt;div class="message-body-content"&gt;
                Use the Processing Engine when you want to precompute and store a rollup on a schedule. It is a good fit for aggregates used repeatedly by dashboards or long-range reports. Use query-time SQL when you need flexible intervals, are still exploring the data, or do not want another stored representation.
            &lt;/div&gt;
        &lt;/div&gt;
    &lt;/article&gt;

    &lt;article class="message"&gt;
        &lt;a href="javascript:void(0)" data-action="collapse" data-target="collapsible-message-accordion-second-4"&gt;
            &lt;div class="message-header"&gt;
                &lt;h3&gt;How do I prevent partial downsampling windows?&lt;/h3&gt;
                &lt;span class="icon"&gt;
                    &lt;i class="fas fa-angle-down" aria-hidden="true"&gt;&lt;/i&gt;
                &lt;/span&gt;
            &lt;/div&gt;&lt;/a&gt;
        &lt;div id="collapsible-message-accordion-second-4" class="message-body is-collapsible" data-parent="accordion_second" data-allow-multiple="true"&gt;
            &lt;div class="message-body-content"&gt;
                Set the downsampler plugin’s `offset` so it queries intervals that have finished receiving data. The offset should cover normal ingestion latency and expected late arrivals. Validate completeness by comparing the sum of `record_count` in the aggregate table with the raw row count over the same closed interval.
            &lt;/div&gt;
        &lt;/div&gt;
    &lt;/article&gt;

    &lt;article class="message"&gt;
        &lt;a href="javascript:void(0)" data-action="collapse" data-target="collapsible-message-accordion-second-5"&gt;
            &lt;div class="message-header"&gt;
                &lt;h3&gt;Does downsampling delete the original data?&lt;/h3&gt;
                &lt;span class="icon"&gt;
                    &lt;i class="fas fa-angle-down" aria-hidden="true"&gt;&lt;/i&gt;
                &lt;/span&gt;
            &lt;/div&gt;&lt;/a&gt;
        &lt;div id="collapsible-message-accordion-second-5" class="message-body is-collapsible" data-parent="accordion_second" data-allow-multiple="true"&gt;
            &lt;div class="message-body-content"&gt;
                No. Both a SQL aggregate query and the downsampler plugin leave the source data intact. The plugin writes additional rows to a target table. Raw-data deletion or expiration is controlled separately through retention settings.
            &lt;/div&gt;
        &lt;/div&gt;
    &lt;/article&gt;

    &lt;article class="message"&gt;
        &lt;a href="javascript:void(0)" data-action="collapse" data-target="collapsible-message-accordion-second-6"&gt;
            &lt;div class="message-header"&gt;
                &lt;h3&gt;What does record_count mean in the downsampled table?&lt;/h3&gt;
                &lt;span class="icon"&gt;
                    &lt;i class="fas fa-angle-down" aria-hidden="true"&gt;&lt;/i&gt;
                &lt;/span&gt;
            &lt;/div&gt;&lt;/a&gt;
        &lt;div id="collapsible-message-accordion-second-6" class="message-body is-collapsible" data-parent="accordion_second" data-allow-multiple="true"&gt;
            &lt;div class="message-body-content"&gt;
                `record_count` is the number of source records represented by an aggregate row. It is useful for detecting incomplete bins and for comparing the target table with the source over the same period.
            &lt;/div&gt;
        &lt;/div&gt;
    &lt;/article&gt;

    &lt;article class="message"&gt;
        &lt;a href="javascript:void(0)" data-action="collapse" data-target="collapsible-message-accordion-second-7"&gt;
            &lt;div class="message-header"&gt;
                &lt;h3&gt;Can I calculate more than one aggregate per interval?&lt;/h3&gt;
                &lt;span class="icon"&gt;
                    &lt;i class="fas fa-angle-down" aria-hidden="true"&gt;&lt;/i&gt;
                &lt;/span&gt;
            &lt;/div&gt;&lt;/a&gt;
        &lt;div id="collapsible-message-accordion-second-7" class="message-body is-collapsible" data-parent="accordion_second" data-allow-multiple="true"&gt;
            &lt;div class="message-body-content"&gt;
                Yes. A SQL query can return multiple functions such as AVG(speed), MIN(speed), and MAX(speed) in the same group. The official downsampler accepts calculation mappings for multiple fields. Choose only the summaries your queries need to avoid unnecessary write and storage overhead.
            &lt;/div&gt;
        &lt;/div&gt;
    &lt;/article&gt;

    &lt;article class="message"&gt;
        &lt;a href="javascript:void(0)" data-action="collapse" data-target="collapsible-message-accordion-second-8"&gt;
            &lt;div class="message-header"&gt;
                &lt;h3&gt;Does this approach work with InfluxDB 3 Cloud Serverless or Cloud Dedicated?&lt;/h3&gt;
                &lt;span class="icon"&gt;
                    &lt;i class="fas fa-angle-down" aria-hidden="true"&gt;&lt;/i&gt;
                &lt;/span&gt;
            &lt;/div&gt;&lt;/a&gt;
        &lt;div id="collapsible-message-accordion-second-8" class="message-body is-collapsible" data-parent="accordion_second" data-allow-multiple="true"&gt;
            &lt;div class="message-body-content"&gt;
                The embedded Python Processing Engine workflow in this tutorial applies to InfluxDB 3 Core and Enterprise. For InfluxDB Cloud Serverless, follow the documented client-library downsampling pattern: query aggregates with SQL, write the results back, and schedule the client externally. For Cloud Dedicated, you can use the same general query-and-write pattern with an InfluxDB 3 client library and an external scheduler. 
            &lt;/div&gt;
        &lt;/div&gt;
    &lt;/article&gt;

    &lt;article class="message"&gt;
        &lt;a href="javascript:void(0)" data-action="collapse" data-target="collapsible-message-accordion-second-9"&gt;
            &lt;div class="message-header"&gt;
                &lt;h3&gt;How much storage will downsampling save?&lt;/h3&gt;
                &lt;span class="icon"&gt;
                    &lt;i class="fas fa-angle-down" aria-hidden="true"&gt;&lt;/i&gt;
                &lt;/span&gt;
            &lt;/div&gt;&lt;/a&gt;
        &lt;div id="collapsible-message-accordion-second-9" class="message-body is-collapsible" data-parent="accordion_second" data-allow-multiple="true"&gt;
            &lt;div class="message-body-content"&gt;
                There is no universal percentage. The tutorial reduced the number of rows returned for its fixed test window by 90%, but byte-level storage depends on the schema, data types, tag cardinality, compression, the aggregates stored, and how long you retain each resolution.
            &lt;/div&gt;
        &lt;/div&gt;
    &lt;/article&gt;

&lt;/div&gt;
</description>
      <pubDate>Wed, 19 Aug 2026 08:00:00 +0000</pubDate>
      <link>https://www.influxdata.com/blog/downsampling-guide-influxdb-3/</link>
      <guid isPermaLink="true">https://www.influxdata.com/blog/downsampling-guide-influxdb-3/</guid>
      <category>Developer</category>
      <author>Charles Mahler (InfluxData)</author>
    </item>
    <item>
      <title>AI-Powered Spacecraft Operations with InfluxDB 3</title>
      <description>&lt;p&gt;When a satellite is drifting toward a fault, operators don’t need another dashboard full of disconnected charts. They need to know what changed, what it means, and what to check before the next ground pass closes. That’s the idea behind our &lt;a href="https://www.influxdata.com/solutions/by-industries/satellite-telemetry-monitoring/?utm_source=website&amp;amp;utm_medium=direct&amp;amp;utm_campaign=ai-powered-spacecraft-ops&amp;amp;utm_content=blog"&gt;satellite telemetry&lt;/a&gt; demo: a live mission-control experience built on &lt;a href="https://www.influxdata.com/products/influxdb/?utm_source=website&amp;amp;utm_medium=direct&amp;amp;utm_campaign=ai-powered-spacecraft-ops&amp;amp;utm_content=blog"&gt;InfluxDB 3&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The demo monitors a simulated fleet of 12 satellites, continuously ingesting telemetry such as onboard computer temperature, battery voltage, solar current, reaction wheel RPM, power draw, RF link margin, ground station, orbit pass, and attitude status. One satellite is actively degrading, while historical incidents provide useful context for comparison.&lt;/p&gt;

&lt;p&gt;You can check out the live demo &lt;a href="https://www.influxdata.com/demos/satellite-demo/?utm_source=website&amp;amp;utm_medium=direct&amp;amp;utm_campaign=ai-powered-spacecraft-ops&amp;amp;utm_content=blog"&gt;here&lt;/a&gt; or watch the video to see it in action:&lt;/p&gt;

&lt;div class="youtube-container"&gt;
  &lt;iframe class="responsive-iframe" src="https://www.youtube.com/embed/G20XWaESNjk?si=C4bjYRbucMsyVttr" title="Real-Time Satellite Monitoring with InfluxDB 3 &amp;amp; Claude" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen=""&gt;
  &lt;/iframe&gt;
&lt;/div&gt;

&lt;h2 id="demo-overview"&gt;Demo overview&lt;/h2&gt;

&lt;h4 id="from-telemetry-stream-to-operator-signal"&gt;From Telemetry Stream to Operator Signal&lt;/h4&gt;

&lt;p&gt;The main dashboard provides a fleet overview showing which satellites are online, which are currently in contact, and where anomalies are occurring. Operators can then drill into individual spacecraft to get a more specific view and look at historical data.&lt;/p&gt;

&lt;p&gt;&lt;img src="//images.ctfassets.net/o7xu9whrs0u9/7Hnc2hAafjqsncgkQWS8eS/d84f83fd4ff1284ada15b36a79720489/c6c08772-315c-43b5-89b8-45e4ebf0aa86.png" alt="AI-Powered Spacecraft Operations with InfluxDB 3 #1" /&gt;&lt;/p&gt;

&lt;h4 id="detecting-anomalies-as-data-arrives"&gt;Detecting Anomalies as Data Arrives&lt;/h4&gt;

&lt;p&gt;Behind the scenes, the &lt;a href="https://docs.influxdata.com/influxdb3/enterprise/plugins/?utm_source=website&amp;amp;utm_medium=direct&amp;amp;utm_campaign=ai-powered-spacecraft-ops&amp;amp;utm_content=blog"&gt;InfluxDB 3 Processing Engin&lt;/a&gt;e observes incoming data as it is written to the database and automatically creates alerts based on conditional thresholds or sensor states.&lt;/p&gt;

&lt;p&gt;That means anomaly detection happens without waiting for a separate pipeline to catch up. Another processing engine task incorporates NOAA data for environmental context alongside vehicle behavior.&lt;/p&gt;

&lt;p&gt;This pattern can be extended beyond satellites. Any connected system with high-volume and time-sensitive data, such as industrial equipment, energy infrastructure, production systems, or logistics networks, will benefit.&lt;/p&gt;

&lt;h4 id="accessing-operational-truth-using-ai"&gt;Accessing Operational Truth Using AI&lt;/h4&gt;

&lt;p&gt;This demo also includes an AI agent powered by the &lt;a href="https://docs.influxdata.com/influxdb3/enterprise/admin/mcp-server/?utm_source=website&amp;amp;utm_medium=direct&amp;amp;utm_campaign=ai-powered-spacecraft-ops&amp;amp;utm_content=blog"&gt;InfluxDB 3 MCP server&lt;/a&gt;. Instead of generating answers based on generic knowledge, the agent can access data stored in InfluxDB. You can ask about fleet health, a specific satellite, or the root cause of a suspected issue. The agent can then query InfluxDB throughout a conversation and summarize what it found.&lt;/p&gt;

&lt;p&gt;&lt;img src="//images.ctfassets.net/o7xu9whrs0u9/2PBcQAMHMzcDTGf2NnK5yY/ae6cc4af4ce194aead3e22739d3cc6a2/34b36852-d14e-403a-99f9-4b3151bf1893.png" alt="AI-Powered Spacecraft Operations with InfluxDB 3 #2" /&gt;&lt;/p&gt;

&lt;h2 id="under-the-hood-from-raw-telemetry-to-operational-context"&gt;Under the hood: From raw telemetry to operational context&lt;/h2&gt;

&lt;p&gt;This demo goes beyond the dashboard; it illustrates how InfluxDB 3 can transform live data into enriched operational data without external tools, as well as how to integrate InfluxDB into LLM-powered workflows.&lt;/p&gt;

&lt;h4 id="alerting-on-real-time-data"&gt;Alerting on Real-Time Data&lt;/h4&gt;

&lt;p&gt;Each incoming batch of satellite telemetry data triggers a processing engine plugin that evaluates data, such as:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Onboard computer temperature&lt;/li&gt;
  &lt;li&gt;Reaction wheel RPMs&lt;/li&gt;
  &lt;li&gt;Power draw&lt;/li&gt;
  &lt;li&gt;Attitude status&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If an anomaly is found, an alert is written to a separate InfluxDB table. The row contains all relevant information for managing an incident, both in the short term and for historical tracking. Instead of the AI agent burning tokens querying and searching raw telemetry, it works from a dedicated alert stream that preserves the signal behind the alert.&lt;/p&gt;

&lt;p&gt;&lt;img src="//images.ctfassets.net/o7xu9whrs0u9/43el1jBplLhRWvsGw5f6Sa/3527873b6deeea2f88aef524f882a88a/1a54ebbd-6fa3-4427-9315-df8624cd8135.png" alt="AI-Powered Spacecraft Operations with InfluxDB 3 #3" /&gt;&lt;/p&gt;

&lt;h4 id="enriching-telemetry-with-third-party-data"&gt;Enriching Telemetry with Third Party Data&lt;/h4&gt;

&lt;p&gt;Another way the demo uses the Processing Engine collecting NOAA planetary Kp index data with an external API call. This data is then stored in InfluxDB where operators can analyze how satellites are being impacted by external conditions and surface any correlated relationship between them.&lt;/p&gt;

&lt;p&gt;Scheduled Processing Engine jobs help enrich operational data. Depending on your use case, this could include weather, energy prices, traffic, or financial market data. It can also calculate rollups, forecast, or downsample data at a scheduled interval.&lt;/p&gt;

&lt;h4 id="integrated-ai-agent-using-the-influxdb-3-mcp-server"&gt;Integrated AI Agent Using the InfluxDB 3 MCP Server&lt;/h4&gt;

&lt;p&gt;The AI agent is connected to InfluxDB via the MCP server. Rather than relying on a static prompt or blindly summarizing a dashboard, it can query recent telemetry and alerts, inspect the result, and explain its conclusion.&lt;/p&gt;

&lt;p&gt;The UI exposes that query/tool trace so operators can see what evidence informed the answer. The agent effectively becomes an interface to your data model.&lt;/p&gt;

&lt;h2 id="putting-the-processing-engine-to-work"&gt;Putting the processing engine to work&lt;/h2&gt;

&lt;p&gt;InfluxDB 3’s Processing Engine runs Python inside the database and supports triggers on data writes, schedules, and HTTP requests. That allows it to do far more than simple threshold alerts. You can check the &lt;a href="https://docs.influxdata.com/influxdb3/enterprise/plugins/?utm_source=website&amp;amp;utm_medium=direct&amp;amp;utm_campaign=ai-powered-spacecraft-ops&amp;amp;utm_content=blog"&gt;documentation&lt;/a&gt; for more detail on the three trigger types and how plugins can retain state between executions.&lt;/p&gt;

&lt;p&gt;Here are a few interesting ways you can take advantage of the Processing Engine:&lt;/p&gt;

&lt;div class="blog-html-table-wrapper"&gt;
 &lt;table class="blog-html-table"&gt;
  &lt;thead&gt;
   &lt;tr&gt;
    &lt;th&gt;Trigger type&lt;/th&gt;
    &lt;th&gt;Use case&lt;/th&gt;
    &lt;th&gt;Outcome&lt;/th&gt;
   &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
   &lt;tr&gt;
    &lt;td&gt;On write&lt;/td&gt;
    &lt;td&gt;Stateful anomaly detection&lt;/td&gt;
    &lt;td&gt;Detect drift against a rolling baseline, not just a static threshold.&lt;/td&gt;
   &lt;/tr&gt;
   &lt;tr&gt;
    &lt;td&gt;On write&lt;/td&gt;
    &lt;td&gt;Schema validation and enrichment&lt;/td&gt;
    &lt;td&gt;Reject or flag malformed data; add additional metadata.&lt;/td&gt;
   &lt;/tr&gt;
   &lt;tr&gt;
    &lt;td&gt;On write&lt;/td&gt;
    &lt;td&gt;Alert routing&lt;/td&gt;
    &lt;td&gt;Write an alert record and then kick off an incident management workflow with related context.&lt;/td&gt;
   &lt;/tr&gt;
   &lt;tr&gt;
    &lt;td&gt;Scheduled&lt;/td&gt;
    &lt;td&gt;Deadman check&lt;/td&gt;
    &lt;td&gt;Identify data sources that have stopped writing data.&lt;/td&gt;
   &lt;/tr&gt;
   &lt;tr&gt;
    &lt;td&gt;Scheduled&lt;/td&gt;
    &lt;td&gt;Forecasting and error evaluation&lt;/td&gt;
    &lt;td&gt;Generate expected readings, compare them to actual behavior, and detect changes before a hard limit is crossed.&lt;/td&gt;
   &lt;/tr&gt;
   &lt;tr&gt;
    &lt;td&gt;Scheduled&lt;/td&gt;
    &lt;td&gt;Data lifecycle&lt;/td&gt;
    &lt;td&gt;Downsample or export selected data.&lt;/td&gt;
   &lt;/tr&gt;
   &lt;tr&gt;
    &lt;td&gt;HTTP request&lt;/td&gt;
    &lt;td&gt;Operational runbooks&lt;/td&gt;
    &lt;td&gt;Expose an API endpoint that an app can use to retrieve data or start a workflow.&lt;/td&gt;
   &lt;/tr&gt;
  &lt;/tbody&gt;
 &lt;/table&gt;
&lt;/div&gt;

&lt;p&gt;If you don’t want to start from scratch building with the Processing Engine, there are a number of &lt;a href="https://docs.influxdata.com/influxdb3/enterprise/plugins/library/official/?utm_source=website&amp;amp;utm_medium=direct&amp;amp;utm_campaign=ai-powered-spacecraft-ops&amp;amp;utm_content=blog"&gt;plugins built and supported by the InfluxDB team&lt;/a&gt;. Some plugin examples:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Anomaly detection&lt;/li&gt;
  &lt;li&gt;Schema validation&lt;/li&gt;
  &lt;li&gt;Notifications&lt;/li&gt;
  &lt;li&gt;Iceberg export&lt;/li&gt;
  &lt;li&gt;Integrations for Kafka, MQTT, and AMQP&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The bigger opportunity is to compose these pieces. You could ingest data, validate it on write, detect drift against a baseline, write a contextual alert, enrich it with scheduled external data, and let an MCP-enabled AI agent explain the event in plain language.&lt;/p&gt;

&lt;h2 id="check-out-the-demo-and-start-building"&gt;Check out the demo and start building&lt;/h2&gt;

&lt;p&gt;While this demo is built specifically for satellites, the architecture is designed for any use case where &lt;a href="https://www.influxdata.com/what-is-time-series-data/?utm_source=website&amp;amp;utm_medium=direct&amp;amp;utm_campaign=ai-powered-spacecraft-ops&amp;amp;utm_content=blog"&gt;time series data&lt;/a&gt; is used to take action and direct important decisions.&lt;/p&gt;

&lt;p&gt;InfluxDB 3 stores the live signal. The Processing Engine detects, enriches, and routes meaningful events. The MCP server gives an AI agent a grounded way to investigate the data and show its work. Together, they turn telemetry from data teams merely collect into something they can understand and act on.&lt;/p&gt;

&lt;h2 id="resources"&gt;Resources&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href="https://www.influxdata.com/demos/satellite-demo/?utm_source=website&amp;amp;utm_medium=direct&amp;amp;utm_campaign=ai-powered-spacecraft-ops&amp;amp;utm_content=blog"&gt;Satellite Telemetry demo&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://www.influxdata.com/products/signup/?utm_source=website&amp;amp;utm_medium=direct&amp;amp;utm_campaign=ai-powered-spacecraft-ops&amp;amp;utm_content=blog"&gt;Download InfluxDB 3&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://docs.influxdata.com/influxdb3/explorer/?utm_source=website&amp;amp;utm_medium=direct&amp;amp;utm_campaign=ai-powered-spacecraft-ops&amp;amp;utm_content=blog"&gt;InfluxDB 3 Explorer&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://docs.influxdata.com/influxdb3/enterprise/admin/mcp-server/?utm_source=website&amp;amp;utm_medium=direct&amp;amp;utm_campaign=ai-powered-spacecraft-ops&amp;amp;utm_content=blog"&gt;InfluxDB 3 MCP Server&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://docs.influxdata.com/influxdb3/enterprise/plugins/library/official/?utm_source=website&amp;amp;utm_medium=direct&amp;amp;utm_campaign=ai-powered-spacecraft-ops&amp;amp;utm_content=blog"&gt;InfluxDB 3 official processing engine plugins&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id="faqs"&gt;FAQs&lt;/h2&gt;

&lt;div id="accordion_second"&gt;
    &lt;article class="message"&gt;
        &lt;a href="javascript:void(0)" data-action="collapse" data-target="collapsible-message-accordion-second-1"&gt;
            &lt;div class="message-header"&gt;
                &lt;h3&gt;How does the demo detect anomalies in real-time?&lt;/h3&gt;
                &lt;span class="icon"&gt;
                    &lt;i class="fas fa-angle-down" aria-hidden="true"&gt;&lt;/i&gt;
                &lt;/span&gt;
            &lt;/div&gt;&lt;/a&gt;
        &lt;div id="collapsible-message-accordion-second-1" class="message-body is-collapsible is-active" data-parent="accordion_second" data-allow-multiple="true"&gt;
            &lt;div class="message-body-content"&gt;
                The InfluxDB 3 processing Engine watches incoming data as it is written and automatically generates alerts based on conditional thresholds or sensor states. This removes the need for a separate processing pipeline.
            &lt;/div&gt;
        &lt;/div&gt;
    &lt;/article&gt;

    &lt;article class="message"&gt;
        &lt;a href="javascript:void(0)" data-action="collapse" data-target="collapsible-message-accordion-second-2"&gt;
            &lt;div class="message-header"&gt;
                &lt;h3&gt;What is the InfluxDB 3 Processing Engine and what can it do beyond alerting? &lt;/h3&gt;
                &lt;span class="icon"&gt;
                    &lt;i class="fas fa-angle-down" aria-hidden="true"&gt;&lt;/i&gt;
                &lt;/span&gt;
            &lt;/div&gt;&lt;/a&gt;
        &lt;div id="collapsible-message-accordion-second-2" class="message-body is-collapsible" data-parent="accordion_second" data-allow-multiple="true"&gt;
            &lt;div class="message-body-content"&gt;
               The Processing Engine allows InfluxDB 3 to run custom Python code inside your database. This code can be activated by three different trigger types: data writes, schedules, or via HTTP request. Beyond threshold alerts, it can be used for schema validation, deadman checks, downsampling, data transformation, and many other use cases.
            &lt;/div&gt;
        &lt;/div&gt;
    &lt;/article&gt;

    &lt;article class="message"&gt;
        &lt;a href="javascript:void(0)" data-action="collapse" data-target="collapsible-message-accordion-second-3"&gt;
            &lt;div class="message-header"&gt;
                &lt;h3&gt;How can InfluxDB be used with an AI agent?&lt;/h3&gt;
                &lt;span class="icon"&gt;
                    &lt;i class="fas fa-angle-down" aria-hidden="true"&gt;&lt;/i&gt;
                &lt;/span&gt;
            &lt;/div&gt;&lt;/a&gt;
        &lt;div id="collapsible-message-accordion-second-3" class="message-body is-collapsible" data-parent="accordion_second" data-allow-multiple="true"&gt;
            &lt;div class="message-body-content"&gt;
               This demo uses the InfluxDB MCP server to connect to an AI agent, giving the LLM context directly from InfluxDB to answer questions about the current state of the satellite fleet. InfluxDB also has a CLI and REST API that can be utilized by AI agents.
            &lt;/div&gt;
        &lt;/div&gt;
    &lt;/article&gt;

    &lt;article class="message"&gt;
        &lt;a href="javascript:void(0)" data-action="collapse" data-target="collapsible-message-accordion-second-4"&gt;
            &lt;div class="message-header"&gt;
                &lt;h3&gt;Is this demo specific to satellites or can the architecture be reused elsewhere?&lt;/h3&gt;
                &lt;span class="icon"&gt;
                    &lt;i class="fas fa-angle-down" aria-hidden="true"&gt;&lt;/i&gt;
                &lt;/span&gt;
            &lt;/div&gt;&lt;/a&gt;
        &lt;div id="collapsible-message-accordion-second-4" class="message-body is-collapsible" data-parent="accordion_second" data-allow-multiple="true"&gt;
            &lt;div class="message-body-content"&gt;
              The architecture is designed for any use case built on time series data that is used to drive decisions. It could be used for monitoring industrial equipment, energy infrastructure, or logistics networks. The same combination of InfluxDB for storage, Processing Engine for anomaly detection and enrichment, and MCP for AI-powered investigation generalizes for all of these use cases.
            &lt;/div&gt;
        &lt;/div&gt;
    &lt;/article&gt;

    &lt;article class="message"&gt;
        &lt;a href="javascript:void(0)" data-action="collapse" data-target="collapsible-message-accordion-second-5"&gt;
            &lt;div class="message-header"&gt;
                &lt;h3&gt;What is InfluxDB 3?&lt;/h3&gt;
                &lt;span class="icon"&gt;
                    &lt;i class="fas fa-angle-down" aria-hidden="true"&gt;&lt;/i&gt;
                &lt;/span&gt;
            &lt;/div&gt;&lt;/a&gt;
        &lt;div id="collapsible-message-accordion-second-5" class="message-body is-collapsible" data-parent="accordion_second" data-allow-multiple="true"&gt;
            &lt;div class="message-body-content"&gt;
               InfluxDB 3 is a time series database used to store and analyze time series data for high performance workloads. It has a built-in Python VM for low latency data analysis and an MCP server for integration with AI agents.

            &lt;/div&gt;
        &lt;/div&gt;
    &lt;/article&gt;

&lt;/div&gt;
</description>
      <pubDate>Wed, 29 Jul 2026 08:00:00 +0000</pubDate>
      <link>https://www.influxdata.com/blog/ai-powered-spacecraft-ops/</link>
      <guid isPermaLink="true">https://www.influxdata.com/blog/ai-powered-spacecraft-ops/</guid>
      <category>Developer</category>
      <author>Charles Mahler (InfluxData)</author>
    </item>
    <item>
      <title>What’s New in InfluxDB 3: 5 New Processing Engine Plugins</title>
      <description>&lt;p&gt;The InfluxDB team has released five new Processing Engine plugins. They range from making it easy to call a hosted ML model to pulling in stock market data in real-time. Every one of them can be activated with a few terminal commands. No external services or tools—they all run inside your existing InfluxDB instance.&lt;/p&gt;

&lt;p&gt;Here’s what’s new, when you’d reach for them, and a few quickstart examples to get you going in minutes.&lt;/p&gt;

&lt;h2 id="processing-engine-primer"&gt;Processing Engine primer&lt;/h2&gt;

&lt;p&gt;If you aren’t familiar, the &lt;a href="https://docs.influxdata.com/influxdb3/enterprise/plugins/?utm_source=website&amp;amp;utm_medium=direct&amp;amp;utm_campaign=5_new_processing_engine_plugins&amp;amp;utm_content=blog"&gt;Processing Engine&lt;/a&gt; is an embedded Python runtime inside InfluxDB 3 that allows you to execute Python code on the following triggers:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Schedule&lt;/strong&gt; - Run code at scheduled intervals&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Data write&lt;/strong&gt; - Runs whenever data is written to a table&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Request&lt;/strong&gt; - A custom HTTP endpoint that handles incoming requests&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Plugins can query InfluxDB, transform your data, and write it back to a new table. They can also be used to hit external services outside InfluxDB.&lt;/p&gt;

&lt;h2 id="sagemaker-inference"&gt;SageMaker inference&lt;/h2&gt;

&lt;p&gt;The SageMaker plugin pulls rows of data, formats them how your SageMaker model expects, calls the model endpoint, and writes the prediction back to InfluxDB. It can build CSVs or 8 different JSON schema options, so it supports TensorFlow Serving, AWS’ built-in algorithms, and Hugging Face models.&lt;/p&gt;

&lt;p&gt;This plugin allows you to use your deployed SageMaker models without having to create a custom plugin. Just point the plugin at your endpoint to get anomaly scores, classifications, forecasts, or whatever else your endpoint returns, and have it written directly to InfluxDB.&lt;/p&gt;

&lt;h4 id="example-anomaly-scoring-with-built-in-aws-model"&gt;Example: Anomaly Scoring with Built-In AWS Model&lt;/h4&gt;

&lt;pre class=""&gt;&lt;code class="language-bash"&gt;influxdb3 install package boto3
influxdb3 install package pandas

influxdb3 create trigger \
  --database iot \
  --path "sagemaker.py" \
  --trigger-spec "every:30s" \
  --trigger-arguments 'endpoint_name=rcf-anomaly,source_measurement=metrics,feature_order={cpu}|{mem}|{rps}|{p99_ms},json_shape=instances_features,output_fields=anomaly_score=scores[*].score,interval=2min,limit=20' \
  rcf_score&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;With this setup, every 30 seconds the last 20 rows of metrics will get batched into a request to your Random Cut Forest endpoint. Each score comes back as a new row in the specified output table.&lt;/p&gt;

&lt;h2 id="value-counter"&gt;Value counter&lt;/h2&gt;

&lt;p&gt;This plugin is a port of the Telegraf value counter aggregator plugin. Point it at a field, and it will count how many times each unique value shows up in your data. It can be used with a data write trigger that counts as rows arrive or a scheduled trigger that aggregates the time window since it last ran. Common use cases for this plugin are HTTP status codes or log-level counts.&lt;/p&gt;

&lt;h2 id="chronos-forecasting"&gt;Chronos forecasting&lt;/h2&gt;

&lt;p&gt;The Chronos forecasting plugin allows you to use Amazon’s Chronos time series foundation models directly against your InfluxDB data. Chronos models don’t require a training step; you point the plugin at a measurement, and it returns a median forecast and prediction interval between 50% and 80%. This plugin works as a scheduled trigger for recurring forecasts and as an HTTP trigger for on-demand forecasting.&lt;/p&gt;

&lt;p&gt;The benefit of using Chronos models is that you don’t need to mess around with training data or tuning per series. If you want to start getting forecasts on your data as soon as possible, this is the fastest path.&lt;/p&gt;

&lt;h4 id="example-on-demand-forecast-via-http"&gt;Example: On-Demand Forecast via HTTP&lt;/h4&gt;

&lt;pre class=""&gt;&lt;code class="language-bash"&gt;influxdb3 install package chronos-forecasting
influxdb3 install package torch

influxdb3 create trigger \
  --database mydb \
  --path chronos_forecasting.py \
  --trigger-spec "request:forecast_series" \
  chronos_forecast_http

influxdb3 enable trigger --database mydb chronos_forecast_http

curl -X POST "http://localhost:8181/api/v3/engine/forecast_series" \
  -H "Content-Type: application/json" \
  -d '{"table": "sensor_data", "field": "temperature", "horizon": 28, "context_limit": 128}'&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This will return your historical context and a 28-step forecast, with each point having a median value and 50%-80% confidence bounds.&lt;/p&gt;

&lt;h2 id="simple-data-replicator"&gt;Simple data replicator&lt;/h2&gt;

&lt;p&gt;This plugin replicates data from your local InfluxDB 3 instance to a remote instance over HTTP, with built-in table/field filtering and renaming. It can be run on a schedule or on every data write. Data is buffered in a queue with automatic retries to support unreliable network connectivity.&lt;/p&gt;

&lt;p&gt;The data replicator makes it easier to set up edge-to-cloud deployments, sync dev and staging environments, or deploy InfluxDB in multiple regions.&lt;/p&gt;

&lt;h5 id="example-replicate-data-on-every-write"&gt;Example: Replicate Data on Every Write&lt;/h5&gt;

&lt;pre class=""&gt;&lt;code class="language-bash"&gt;influxdb3 install package influxdb3-python

influxdb3 create trigger \
  --database mydb \
  --trigger-spec "all_tables" \
  --plugin-filename gh:influxdata/simple_data_replicator/simple_data_replicator.py \
  --trigger-arguments host=example.com,remote_token=apiv3_token,database=remote_db,tables="home home2",unique_file_suffix=wxyz5678 \
  simple_data_replicator_trigger

influxdb3 enable trigger --database mydb simple_data_replicator_trigger&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id="stock-portfolio-tracker"&gt;Stock portfolio tracker&lt;/h2&gt;

&lt;p&gt;With the stock portfolio tracker plugin, you can fetch live stock prices from Yahoo Finance. Define the tickers you want to track in a TOML configuration file and monitor values over time. This plugin is mostly an example of how versatile the Processing Engine is, showing how you can make external API calls, aggregate data on a schedule, and roll up your data.&lt;/p&gt;

&lt;h2 id="mix-and-match-your-plugins"&gt;Mix and match your plugins&lt;/h2&gt;

&lt;p&gt;These five plugins are just some of the most recent &lt;a href="https://docs.influxdata.com/influxdb3/enterprise/plugins/library/official/?utm_source=website&amp;amp;utm_medium=direct&amp;amp;utm_campaign=5_new_processing_engine_plugins&amp;amp;utm_content=blog"&gt;created by the InfluxDB team&lt;/a&gt;. The official plugin library already has plugins for data transformation, downsampling, anomaly detection, alerting, and more. The biggest benefit of the Processing Engine is the ability to combine these out-of-the box tools. Here is some inspiration:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;SageMaker Inference + &lt;a href="https://docs.influxdata.com/influxdb3/enterprise/plugins/library/official/notifier/?utm_source=website&amp;amp;utm_medium=direct&amp;amp;utm_campaign=5_new_processing_engine_plugins&amp;amp;utm_content=blog"&gt;Notifier&lt;/a&gt;&lt;/strong&gt; - SageMaker writes anomaly scores to InfluxDB as time series data. You can then use the Notifier plugin to send alerts to Slack, email, or a webhook if a threshold is crossed without needing a separate alert stack.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Value Counter + &lt;a href="https://docs.influxdata.com/influxdb3/enterprise/plugins/library/official/state-change/?utm_source=website&amp;amp;utm_medium=direct&amp;amp;utm_campaign=5_new_processing_engine_plugins&amp;amp;utm_content=blog"&gt;State Change&lt;/a&gt;&lt;/strong&gt; - If you are tracking the status of something, you can use the value counter to tell you how many times a value has appeared and combine it with the state change to tell you when it changed. Together you can track frequency counts and status-change events for alerting, giving you a complementary view of the same data.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Simple data replicator + Downsampler&lt;/strong&gt; - Downsampling your data locally before sending it to your remote InfluxDB instance will help to reduce bandwidth usage.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Simple data replicator + InfluxDB to Iceberg&lt;/strong&gt; - This combination allows you to move hot data from InfluxDB to another instance for redundancy or regional access, and then export cold data to Iceberg for long-term storage.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All five are live now in the &lt;a href="https://github.com/influxdata/influxdb3_plugins/tree/main/influxdata/?utm_source=website&amp;amp;utm_medium=direct&amp;amp;utm_campaign=5_new_processing_engine_plugins&amp;amp;utm_content=blog"&gt;influxdb3_plugins repository&lt;/a&gt;. Grab one, wire up a trigger, and see what you can build with the InfluxDB 3 Processing Engine.&lt;/p&gt;

&lt;h2 id="related-resources"&gt;Related resources&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href="https://www.influxdata.com/influxdb-signup/?utm_source=website&amp;amp;utm_medium=direct&amp;amp;utm_campaign=5_new_processing_engine_plugins&amp;amp;utm_content=blog"&gt;InfluxDB 3 download&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://github.com/influxdata/influxdb3_plugins/tree/main/influxdata/?utm_source=website&amp;amp;utm_medium=direct&amp;amp;utm_campaign=5_new_processing_engine_plugins&amp;amp;utm_content=blog"&gt;Official plugins Github repo&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://docs.influxdata.com/influxdb3/enterprise/plugins/?utm_source=website&amp;amp;utm_medium=direct&amp;amp;utm_campaign=5_new_processing_engine_plugins&amp;amp;utm_content=blog"&gt;Processing Engine docs&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://docs.influxdata.com/influxdb3/explorer/?utm_source=website&amp;amp;utm_medium=direct&amp;amp;utm_campaign=5_new_processing_engine_plugins&amp;amp;utm_content=blog"&gt;InfluxDB 3 Explorer&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id="faqs"&gt;FAQs&lt;/h2&gt;

&lt;h5 id="q-do-i-need-to-write-python-to-use-these-plugins"&gt;&lt;strong&gt;Q: Do I need to write Python to use these plugins?&lt;/strong&gt;&lt;/h5&gt;
&lt;p&gt;No, each plugin is ready to run after you install its dependencies with &lt;code class="language-markup"&gt;influxdb3 install package&lt;/code&gt;, then wire it up with &lt;code class="language-markup"&gt;influxdb3 create trigger&lt;/code&gt;. You only need to touch Python if you want to modify the plugin’s behavior or create a custom plugin.&lt;/p&gt;

&lt;h5 id="q-do-i-need-to-write-python-to-use-these-plugins-1"&gt;Q: Do I need to write Python to use these plugins?&lt;/h5&gt;
&lt;p&gt;No, each plugin is ready to run after you install its dependencies with &lt;code class="language-markup"&gt;influxdb3 install package&lt;/code&gt;, then wire it up with &lt;code class="language-markup"&gt;influxdb3 create trigger&lt;/code&gt;. You only need to touch Python if you want to modify the plugin’s behavior or create a custom plugin.&lt;/p&gt;

&lt;h5 id="q-can-i-configure-a-plugin-with-a-file-instead-of-inline-commands"&gt;Q: Can I configure a plugin with a file instead of inline commands?&lt;/h5&gt;
&lt;p&gt;Yes, every plugin supports using a TOML configuration file via the &lt;code class="language-markup"&gt;config_file_path&lt;/code&gt; argument. Using a configuration file is recommended for setups that require more than a few parameters.&lt;/p&gt;

&lt;h5 id="q-where-do-i-see-plugin-errors-or-logs"&gt;Q: Where do I see plugin errors or logs?&lt;/h5&gt;
&lt;p&gt;Every plugin writes to the &lt;code class="language-markup"&gt;system.processing_engine_logs&lt;/code&gt; table in the trigger’s database.&lt;/p&gt;

&lt;h5 id="q-do-these-plugins-work-with-all-versions-of-influxdb-3"&gt;Q: Do these plugins work with all versions of InfluxDB 3?&lt;/h5&gt;
&lt;p&gt;All five run on both InfluxDB 3 Core and InfluxDB 3 Enterprise; you just need the Processing Engine enabled (&lt;code class="language-markup"&gt;--plugin-dir /path/to/plugins&lt;/code&gt; when you start the server).&lt;/p&gt;
</description>
      <pubDate>Thu, 23 Jul 2026 08:00:00 +0000</pubDate>
      <link>https://www.influxdata.com/blog/5-new-processing-engine-plugins/</link>
      <guid isPermaLink="true">https://www.influxdata.com/blog/5-new-processing-engine-plugins/</guid>
      <category>Developer</category>
      <author>Charles Mahler (InfluxData)</author>
    </item>
    <item>
      <title>How Mumu Migrated From Prometheus to InfluxDB and Tripled Their Metric Coverage</title>
      <description>&lt;p&gt;When a team uses an internal Slack channel for everything from contact form submissions to deployment alerts and server warnings, the notification engine quickly becomes critical infrastructure. When the same team builds that engine as a product for other teams to use, the bar gets even higher.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://usemumu.com/"&gt;Mumu&lt;/a&gt; is an all-in-one productivity platform for modern teams. While most companies stitch together separate SaaS tools for org charts, agile estimation, internal Q&amp;amp;A, skill mapping, recognition, and notifications, Mumu offers all of those as connected modules under a single subscription. The premise is that your organizational structure shouldn’t be replicated across five different databases; it should live in one place and flow into every workflow your team uses.&lt;/p&gt;

&lt;p&gt;In this blog, we will go over why the Mumu team rebuilt their monitoring stack on &lt;a href="https://www.influxdata.com/products/influxdb/?utm_source=website&amp;amp;utm_medium=direct&amp;amp;utm_campaign=core-ai-user-mumu&amp;amp;utm_content=blog"&gt;InfluxDB 3&lt;/a&gt; and how the migration went.&lt;/p&gt;

&lt;h2 id="why-pull-based-monitoring-stopped-making-sense"&gt;Why pull-based monitoring stopped making sense&lt;/h2&gt;

&lt;p&gt;Like many teams running their own infrastructure, Mumu started off using Prometheus as its primary monitoring solution. The main problem over time was the fundamental mismatch between Prometheus’s pull-based data collection model and the type of data Mumu was working with. Rather than telemetry data that can be scraped at a regular interval, Mumu often needs to track discrete events like user triggered actions, scripts completing, and a pipeline finishing. As a result, push-based delivery for tracking events made more sense.&lt;/p&gt;

&lt;p&gt;That architectural mismatch wasn’t the only problem. As Mumu evaluated alternatives like Betterstack, VictoriaMetrics, PostHog, Graphite, Datadog, and New Relic, issues related to transparency became a concern. Several of the SaaS solutions came with documentation that made it genuinely hard to understand what was happening under the hood, particularly around how metrics were ingested and stored. For a team that ships fast and needs to be able to debug its own pipeline, that was a dealbreaker.&lt;/p&gt;

&lt;h2 id="why-influxdb-3-was-the-right-option"&gt;Why InfluxDB 3 was the right option&lt;/h2&gt;

&lt;p&gt;Two things about InfluxDB stood out during evaluation. The first was that self-hosting was effortless. Mumu runs its own dedicated servers, and spinning up an InfluxDB 3 Core instance using the official Docker image took almost no time or configuration overhead.&lt;/p&gt;

&lt;p&gt;The second factor was the push-based HTTP API. &lt;a href="https://docs.influxdata.com/influxdb3/core/api/"&gt;InfluxDB’s Line Protocol HTTP API&lt;/a&gt; lets Mumu’s services emit metrics at the exact line of code where an event occurred with no sidecar, no exposition format, no scrape interval, just a POST request at the moment an event happened.&lt;/p&gt;

&lt;p&gt;In hindsight, the team’s biggest evaluation lesson was that they should have built a small proof of concept with InfluxDB earlier. The time spent evaluating other tools wasn’t wasted as it gave them context and confidence in the final decision, but InfluxDB’s simplicity would have been apparent within an afternoon.&lt;/p&gt;

&lt;h2 id="migration-process"&gt;Migration process&lt;/h2&gt;

&lt;p&gt;The migration involved three phases over a 3-month period: dual writing to InfluxDB and the existing Prometheus setup, validation, and finally, decommissioning the Prometheus infrastructure.&lt;/p&gt;

&lt;h4 id="phase-1-dual-writing-via-vector"&gt;Phase 1: Dual-Writing via Vector&lt;/h4&gt;

&lt;p&gt;The first move was to make the same metrics flow into both systems at once. Mumu added InfluxDB as a second sink alongside Prometheus in their existing Vector pipeline, so every metric was being written to both simultaneously. That parallel run is what made the eventual cutover risk-free by allowing the team to confirm performance and validate both systems against each other.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Migrating to InfluxDB is made easy using AI agents.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;About 80% of the migration work, such as Vector configuration changes, the dual-write sink setup, and the boilerplate around the new HTTP drivers in Go and TypeScript, was  generated by coding agents.&lt;/p&gt;

&lt;p&gt;A migration becomes a lot less daunting when the routine work compresses into hours, but what really made this work was what the agent had to work with on the InfluxDB side. InfluxDB 3 exposes a full REST API with a published &lt;a href="https://docs.influxdata.com/influxdb3/enterprise/api/"&gt;OpenAPI specification&lt;/a&gt;. When an AI agent can read that contract directly, it doesn’t have to guess at parameter names from stale blog posts or hallucinate endpoint shapes from vague documentation. It reads the spec, generates correct client code, and gets the integration right on the first pass. InfluxDB also has an &lt;a href="https://docs.influxdata.com/influxdb3/enterprise/admin/mcp-server/"&gt;MCP server&lt;/a&gt; for integrating with AI agents, although it wasn’t used by Mumu.&lt;/p&gt;

&lt;p&gt;This is an important property in a world where agents are doing more and more of the integration work. The systems that will be easiest to adopt over the next few years are not necessarily the ones with the most features, they’re the ones whose APIs are legible to machines.&lt;/p&gt;

&lt;h4 id="phase-2-validation"&gt;Phase 2: Validation&lt;/h4&gt;

&lt;p&gt;Running two systems in parallel only helps if you actually compare them, and this is where the team spent its caution wisely. The validation approach was deliberately simple: they duplicated their Grafana panels side by side, with one panel pulling from Prometheus and an identical panel pulling from InfluxDB. When two panels showing the same metric look identical for weeks on end, confidence accumulates quickly.&lt;/p&gt;

&lt;p&gt;Beyond visual parity, four things got specific attention:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Retention policies&lt;/strong&gt;: Confirming data was being stored at the right granularity and for the expected duration.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Tag cardinality&lt;/strong&gt;: Making sure the tagging strategy wouldn’t cause write-performance problems at scale. Keeping cardinality low on high-volume metric streams is a lesson the team internalized early.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Batch write behavior&lt;/strong&gt;: Validating that the NestJS batching logic produced correct time series data with no gaps or duplicates.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Dashboard parity&lt;/strong&gt;: Rebuilding key Grafana dashboards from scratch against InfluxDB to confirm they told the same story as their Prometheus equivalents.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id="phase-3-cutover"&gt;Phase 3: Cutover&lt;/h4&gt;

&lt;p&gt;Because Mumu’s Go and TypeScript codebases already had proper abstractions and interfaces for metric delivery, writing a new driver that sent metrics to InfluxDB via the HTTP API required almost no changes to the application code. The abstraction layer in their app code meant that swapping the metrics backend was a contained, well-scoped task rather than a sprawling refactor.&lt;/p&gt;

&lt;p&gt;The TypeScript driver came in at around 160 lines of code, largely because the team leaned on the official InfluxDB 3 client library package. The Go implementation was slightly longer due to manual HTTP handling, retry logic, and error handling, but was still a straightforward, bounded piece of work. Once the drivers were in place, the team decommissioned Prometheus for business metrics and declared the migration complete.&lt;/p&gt;

&lt;h2 id="benefits-of-influxdb-3from-150-to-560-metrics"&gt;Benefits of InfluxDB 3—from 150 to 560 metrics&lt;/h2&gt;

&lt;p&gt;Before InfluxDB, Mumu collected around 150 metrics. Today, they collect 560 metrics, and that number is constantly increasing.&lt;/p&gt;

&lt;p&gt;That growth didn’t come from a dedicated instrumentation initiative. There was no mandate, no quarter-long observability push, it happened organically because &lt;strong&gt;adding a new metric became a one-line HTTP call&lt;/strong&gt;. When friction drops that far, engineers instrument things they would previously have skipped.&lt;/p&gt;

&lt;p&gt;The number is less a measure of throughput than a measure of how much the team’s relationship with its own data changed once the cost of asking a question fell to nearly zero. And because metric delivery was suddenly cheap, Mumu started instrumenting things that would have seemed impractical before:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Operational automation&lt;/strong&gt;: Mumu sends a metric for every command executed on their servers, with automations built on top using MsgGO, and certain commands automatically trigger a Slack alert. The result is passive visibility into operational activity with no manual reporting required.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;CI/CD observability&lt;/strong&gt;: They emit metrics from their Bitbucket pipelines, including how long each pipeline runs. Over time, this has established a baseline for normal build duration, making regressions easy to spot.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Per-developer environments&lt;/strong&gt;: Every developer tags their metrics with an &lt;code class="language-markup"&gt;env&lt;/code&gt; field set to their local environment name, such as &lt;code class="language-markup"&gt;local:john&lt;/code&gt;, &lt;code class="language-markup"&gt;local:kate&lt;/code&gt;, and so on. Each developer can observe their own environment in Grafana, test new instrumentation locally before it ships, and build personal dashboards, all without polluting shared production data.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;General script instrumentation&lt;/strong&gt;: Bash scripts, custom CLI commands, and database migration durations during deployments are all now tracked, where before each would have demanded disproportionate effort.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id="sql-on-time-series-data"&gt;SQL on time series data&lt;/h2&gt;

&lt;p&gt;Volume was not the only shift. InfluxDB 3’s SQL support meaningfully improved Mumu’s ability to build dashboards and debug metric data. Before the migration, querying time series data meant learning a specialized query language and reasoning about its particular semantics. With SQL, any engineer on the team can write an ad-hoc query to investigate a metric anomaly, validate that a new event is being tracked correctly, or prototype a Grafana panel without consulting documentation.&lt;/p&gt;

&lt;p&gt;The qualitative win is harder to put a number on, but is equally important: the metrics are now trusted. Engineering time that used to go into questioning whether a dashboard was telling the truth now goes into acting on what it shows.&lt;/p&gt;

&lt;h2 id="architecture-overview"&gt;Architecture overview&lt;/h2&gt;

&lt;p&gt;Mumu runs entirely on its own dedicated servers, giving the team full control, room for hardware-level optimization, and predictable costs. The application layer runs on Docker, with services written in Go and NestJS; Go handles core infrastructure-level work while NestJS handles application layer operations.&lt;/p&gt;

&lt;p&gt;Metrics reach InfluxDB along two paths. Vector collects and transforms log-based metrics from the server environment and forwards them to InfluxDB. The Go and NestJS services send business and application metrics directly over the HTTP API.&lt;/p&gt;

&lt;p&gt;The tagging strategy reflects that split. Server-level and infrastructure metrics carry richer tag sets like &lt;code class="language-markup"&gt;env&lt;/code&gt;, &lt;code class="language-markup"&gt;container&lt;/code&gt;, &lt;code class="language-markup"&gt;process_name&lt;/code&gt;, &lt;code class="language-markup"&gt;process_instance&lt;/code&gt;, and &lt;code class="language-markup"&gt;service&lt;/code&gt;. Business and application metrics are tagged more lightly, typically just &lt;code class="language-markup"&gt;env&lt;/code&gt; plus a small number of domain-specific identifiers. On the NestJS side, metrics are batched and flushed either every 60 seconds or when the batch size crosses a configured threshold, which is a configuration the team continues to tune to balance data freshness against RAM usage and write overhead. Grafana sits on top of it all, querying InfluxDB directly.&lt;/p&gt;

&lt;h2 id="future-plans-for-utilizing-influxdb-3"&gt;Future plans for utilizing InfluxDB 3&lt;/h2&gt;

&lt;p&gt;For Mumu, InfluxDB has unlocked more than just an internal observability story. The team is now actively building toward making it a first-class part of their product surface.&lt;/p&gt;

&lt;p&gt;The most immediate project is integrating InfluxDB as a delivery target inside MsgGO. Today, MsgGO routes messages to Slack, Telegram, Discord, Email, SMS, and Webhooks. Adding InfluxDB as a target means any system already sending events through MsgGO like  contact forms, deployment notifications, server alerts, and application events, can now route structured event data directly into InfluxDB with no additional integration work. Since Mumu uses MsgGO heavily inside its own infrastructure, this would pay off immediately in its own workflows, with the customer-facing benefits coming close behind.&lt;/p&gt;

&lt;p&gt;Further out, the team is evaluating whether to move user-activity statistics that are currently kept as activity records in a NoSQL database into InfluxDB. That data is inherently time series in nature, and putting it in InfluxDB would let them expose richer usage analytics inside the Mumu dashboard without needing a separate query infrastructure. And they want to lean on InfluxDB’s SQL interface to drive product decisions, using internal usage metrics to understand which modules see the most engagement, where users drop off, and how feature adoption shifts after a release.&lt;/p&gt;
</description>
      <pubDate>Thu, 25 Jun 2026 08:00:00 +0000</pubDate>
      <link>https://www.influxdata.com/blog/core-ai-user-mumu/</link>
      <guid isPermaLink="true">https://www.influxdata.com/blog/core-ai-user-mumu/</guid>
      <category>Developer</category>
      <category>Use Cases</category>
      <author>Charles Mahler (InfluxData)</author>
    </item>
    <item>
      <title>Building a Predictive Maintenance Plugin with the InfluxDB 3 Processing Engine </title>
      <description>&lt;p&gt;Predictive maintenance is one of the most compelling use cases for time series data. Instead of waiting for equipment to fail or servicing it on a fixed calendar regardless of condition, you watch the live sensor data and act when it indicates that a failure is coming. That “watch the data and act” loop is exactly what the InfluxDB 3 Processing Engine was built for.&lt;/p&gt;

&lt;p&gt;In this tutorial, we’ll build a working predictive maintenance plugin from scratch. We’ll install &lt;a href="https://docs.influxdata.com/influxdb3/core/install/?utm_source=website&amp;amp;utm_medium=direct&amp;amp;utm_campaign=predictive_maintenance_plugin_tutorial&amp;amp;utm_content=blog"&gt;InfluxDB 3 Core&lt;/a&gt;, load a well-known public dataset of jet engine sensor data, write a Python plugin that runs inside the database to estimate each engine’s Remaining Useful Life (RUL), and have it raise maintenance alerts automatically. By the end, you’ll have an end-to-end system that you can adapt to pumps, motors, HVAC units, CNC machines, or any other instrumented asset.&lt;/p&gt;

&lt;h2 id="what-were-building"&gt;What we’re building&lt;/h2&gt;

&lt;p&gt;Here’s the architecture at a glance:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Sensor data lands in InfluxDB 3 Core. We’ll use &lt;a href="https://www.kaggle.com/datasets/bishals098/nasa-turbofan-engine-degradation-simulation"&gt;NASA’s C-MAPSS turbofan engine degradation dataset&lt;/a&gt;, replayed into a &lt;code class="language-markup"&gt;sensors&lt;/code&gt; table as if it were arriving live from a fleet of engines.&lt;/li&gt;
  &lt;li&gt;A &lt;a href="https://docs.influxdata.com/influxdb3/core/plugins/"&gt;scheduled plugin&lt;/a&gt; runs every minute. It queries the most recent sensor readings per engine, computes a health/degradation score, and converts that into an estimated Remaining Useful Life.&lt;/li&gt;
  &lt;li&gt;The plugin writes its conclusions back into the database. RUL estimates go into a &lt;code class="language-markup"&gt;rul_estimates&lt;/code&gt; table, and when an engine crosses a danger threshold, the plugin writes a row into a &lt;code class="language-markup"&gt;maintenance_alerts&lt;/code&gt; table and logs a warning.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The key idea is that the analysis logic lives &lt;em&gt;embedded in the database&lt;/em&gt;. There’s no separate service to deploy, scale, or keep in sync. When data arrives, the engine acts on it.&lt;/p&gt;

&lt;h2 id="prerequisites"&gt;Prerequisites&lt;/h2&gt;

&lt;p&gt;Before starting, make sure you have:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;A Linux or macOS machine (Windows works too via the installer or Docker; commands below assume a Unix-like shell)&lt;/li&gt;
  &lt;li&gt;Command-line access&lt;/li&gt;
  &lt;li&gt;Python 3 installed locally&lt;/li&gt;
  &lt;li&gt;The &lt;code class="language-markup"&gt;train_FD001.txt&lt;/code&gt; file from the C-MAPSS dataset&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That’s it. InfluxDB 3 Core itself is a single binary and brings its own bundled Python for plugins.&lt;/p&gt;

&lt;h2 id="install-influxdb-3-core"&gt;Install InfluxDB 3 Core&lt;/h2&gt;

&lt;p&gt;The quickest path is the official install script, which always pulls the latest release:&lt;/p&gt;

&lt;pre class=""&gt;&lt;code class="language-bash"&gt;curl -O https://www.influxdata.com/d/install_influxdb3.sh \
  &amp;amp;&amp;amp; sh install_influxdb3.sh core&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;When the script finishes, confirm it worked:&lt;/p&gt;

&lt;pre class=""&gt;&lt;code class="language-bash"&gt;influxdb3 --version&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;If the &lt;code class="language-markup"&gt;influxdb3&lt;/code&gt; command isn’t found, the installer’s output tells you what to do—usually it’s a matter of sourcing your shell config (for example &lt;code class="language-markup"&gt;source ~/.bashrc or source ~/.zshrc&lt;/code&gt;) so the new binary is on your &lt;code class="language-markup"&gt;PATH&lt;/code&gt;.&lt;/p&gt;

&lt;h4 id="docker"&gt;Docker&lt;/h4&gt;

&lt;p&gt;If you’d rather containerize, the Processing Engine is enabled by default in the Docker image (the plugin directory defaults to &lt;code class="language-markup"&gt;/plugins&lt;/code&gt;):&lt;/p&gt;

&lt;pre class=""&gt;&lt;code class="language-bash"&gt;docker run -it -p 8181:8181 --name influxdb3-core \
  --volume ~/.influxdb3_data:/var/lib/influxdb3 \
  --volume ~/.influxdb3_plugins:/plugins \
  influxdb:3-core influxdb3 serve \
  --node-id my_host \
  --object-store file \
  --data-dir /var/lib/influxdb3 \
  --plugin-dir /plugins&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;For the rest of this tutorial we’ll assume the local binary install. The commands translate directly to Docker by prefixing &lt;code class="language-markup"&gt;docker exec -it influxdb3-core&lt;/code&gt;.&lt;/p&gt;

&lt;h2 id="start-influxdb-with-the-processing-engine-enabled"&gt;Start InfluxDB with the Processing Engine enabled&lt;/h2&gt;

&lt;p&gt;The Processing Engine activates only when you tell InfluxDB where your plugins live, using the &lt;code class="language-markup"&gt;--plugin-dir&lt;/code&gt; flag.&lt;/p&gt;

&lt;p&gt;First create a directory to hold plugins, then start the server:&lt;/p&gt;

&lt;pre class=""&gt;&lt;code class="language-bash"&gt;mkdir -p ~/influxdb3/plugins

influxdb3 serve \
  --node-id host01 \
  --object-store file \
  --data-dir ~/.influxdb3 \
  --plugin-dir ~/influxdb3/plugins&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;A few notes on these flags:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class="language-markup"&gt;--node-id&lt;/code&gt; is a unique name for this server instance. It forms part of the storage path.&lt;/li&gt;
  &lt;li&gt;&lt;code class="language-markup"&gt;--object-store file&lt;/code&gt; keeps everything on a local disk under&lt;/li&gt;
  &lt;li&gt;&lt;code class="language-markup"&gt;--data-dir&lt;/code&gt;. In production you’d point this at S3 or another object store; InfluxDB 3 uses a “diskless” architecture where object storage is the source of truth.&lt;/li&gt;
  &lt;li&gt;&lt;code class="language-markup"&gt;--plugin-dir&lt;/code&gt; is the directory the engine scans for plugin files. This is the switch that turns the Processing Engine on.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Leave this server running in its own terminal. Open a second terminal for the remaining commands.&lt;/p&gt;

&lt;p&gt;It’s worth noting that the &lt;code class="language-markup"&gt;influxdb3&lt;/code&gt; binary depends on an adjacent &lt;code class="language-markup"&gt;python/&lt;/code&gt; directory that ships alongside it. If you extracted from a tarball manually, keep the binary and that &lt;code class="language-markup"&gt;python/&lt;/code&gt; folder in the same parent directory and add the parent to your &lt;code class="language-markup"&gt;PATH&lt;/code&gt;, don’t move the binary out on its own, or plugins won’t run.&lt;/p&gt;

&lt;h2 id="create-an-admin-token"&gt;Create an admin token&lt;/h2&gt;

&lt;p&gt;InfluxDB 3 Core uses token authentication. Several operations require an admin token. Create one with the following command:&lt;/p&gt;

&lt;pre class=""&gt;&lt;code class="language-bash"&gt;influxdb3 create token --admin&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This prints a token string once. Copy it somewhere safe; you can’t recover it later (you’d have to regenerate). For convenience in this session, export it:&lt;/p&gt;

&lt;pre class=""&gt;&lt;code class="language-bash"&gt;export INFLUXDB3_AUTH_TOKEN="paste-your-token-here"&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The CLI automatically picks up &lt;code class="language-markup"&gt;INFLUXDB3_AUTH_TOKEN&lt;/code&gt;, so you won’t have to pass &lt;code class="language-markup"&gt;--token&lt;/code&gt; on every command.&lt;/p&gt;

&lt;h2 id="create-a-database"&gt;Create a database&lt;/h2&gt;

&lt;p&gt;Create a database to hold our data:&lt;/p&gt;

&lt;p&gt;&lt;code class="language-markup"&gt;influxdb3 create database engine_fleet&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Verify it exists:&lt;/p&gt;

&lt;p&gt;&lt;code class="language-markup"&gt;influxdb3 show databases&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;You should see &lt;code class="language-markup"&gt;engine_fleet&lt;/code&gt; in the list.&lt;/p&gt;

&lt;h2 id="load-the-dataset-into-influxdb"&gt;Load the dataset into InfluxDB&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://www.kaggle.com/datasets/bishals098/nasa-turbofan-engine-degradation-simulation"&gt;C-MAPSS&lt;/a&gt; file is a flat, headerless, space-separated table. We need to turn each row into a time series point. There’s one wrinkle worth thinking through: the data has a &lt;code class="language-markup"&gt;time_cycles&lt;/code&gt; counter per engine, not real timestamps. To make this behave like a live stream, we’ll synthesize timestamps by mapping each engine cycle to one second of wall-clock time, anchored to “now minus the engine’s lifetime.” That way recent cycles look recent, which is what a scheduled “look at the last N minutes” plugin expects.&lt;/p&gt;

&lt;p&gt;Here’s a small loader script. It uses the InfluxDB 3 Python client to write line protocol in batches. First install the client into your local Python (this is separate from the engine’s bundled Python), using &lt;a href="https://docs.astral.sh/uv/"&gt;uv&lt;/a&gt; or &lt;a href="https://docs.python.org/3/library/venv.html"&gt;venv&lt;/a&gt; for package management if desired::&lt;/p&gt;

&lt;pre class=""&gt;&lt;code class="language-bash"&gt;pip install influxdb3-python pandas&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Save the following as &lt;code class="language-markup"&gt;load_cmapss.py&lt;/code&gt;, adjusting &lt;code class="language-markup"&gt;DATA_FILE&lt;/code&gt; and &lt;code class="language-markup"&gt;TOKEN&lt;/code&gt; as needed:&lt;/p&gt;

&lt;pre class=""&gt;&lt;code class="language-python"&gt;# load_cmapss.py
import time
from datetime import datetime, timedelta, timezone

import pandas as pd
from influxdb_client_3 import InfluxDBClient3, Point

# ---- Configuration ----
DATA_FILE = "train_FD001.txt"
HOST = "http://localhost:8181"
DATABASE = "engine_fleet"
TOKEN = "paste-your-admin-token"   # or read from env

# ---- Column names (NASA C-MAPSS layout) ----
index_cols = ["unit_number", "time_cycles"]
setting_cols = ["setting_1", "setting_2", "setting_3"]
sensor_cols = [f"s_{i}" for i in range(1, 22)]
col_names = index_cols + setting_cols + sensor_cols

# ---- Read the space-separated file ----
df = pd.read_csv(DATA_FILE, sep=r"\s+", header=None, names=col_names)
df = df.astype({"unit_number": int, "time_cycles": int})

print(f"Loaded {len(df)} rows across {df['unit_number'].nunique()} engines")

# ---- Synthesize timestamps: 1 cycle == 1 second, anchored so the
#      last cycle of each engine lands at 'now'. ----
now = datetime.now(timezone.utc)
client = InfluxDBClient3(host=HOST, database=DATABASE, token=TOKEN)

points = []
BATCH = 5000

# Per-engine max cycle so we can anchor each engine's final cycle to "now"
max_cycle = df.groupby("unit_number")["time_cycles"].transform("max")
df["ts"] = [
    now - timedelta(seconds=int(mc - tc))
    for mc, tc in zip(max_cycle, df["time_cycles"])
]

for row in df.itertuples(index=False):
    p = (
        Point("sensors")
        .tag("unit_number", str(row.unit_number))
        .field("time_cycles", int(row.time_cycles))
    )
    for c in setting_cols + sensor_cols:
        p = p.field(c, float(getattr(row, c)))
    p = p.time(row.ts)
    points.append(p)

    if len(points) &amp;gt;= BATCH:
        client.write(points)
        points = []

if points:
    client.write(points)

client.close()
print("Done writing sensor data.")&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Run it:&lt;/p&gt;

&lt;pre class=""&gt;&lt;code class="language-bash"&gt;python load_cmapss.py&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;A few design choices worth calling out:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;&lt;code class="language-markup"&gt;unit_number&lt;/code&gt; is a tag, everything else is a field.&lt;/strong&gt; Tags are indexed and are how you separate one engine’s series from another. Sensor values and the cycle counter are fields.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;We anchor each engine’s final cycle to “now.”&lt;/strong&gt; This means every engine in the fleet looks like it just reached end-of-life, which is convenient for demonstrating alerts. In a real deployment your data already has real timestamps and you’d skip all of this.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Batching matters.&lt;/strong&gt;  Writing 20,000+ points one at a time is slow; batches of a few thousand keep the loader quick.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Confirm the data landed:&lt;/p&gt;

&lt;pre class=""&gt;&lt;code class="language-bash"&gt;influxdb3 query --database engine_fleet \
  "SELECT COUNT(*) FROM sensors"
And take a peek at one engine's recent readings:
influxdb3 query --database engine_fleet \
  "SELECT time, unit_number, time_cycles, s_2, s_4, s_11 \
   FROM sensors WHERE unit_number = '1' \
   ORDER BY time DESC LIMIT 5"&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id="an-overview-of-the-plugin-model"&gt;An overview of the plugin model&lt;/h2&gt;

&lt;p&gt;Before writing code, let’s get the mental model straight.&lt;/p&gt;

&lt;p&gt;A plugin is a Python file (or a directory with an &lt;code class="language-markup"&gt;__init__.py&lt;/code&gt; for multi-file plugins) placed in your plugin directory. It defines a function whose signature matches the trigger type:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Data-write plugin: &lt;code class="language-markup"&gt;def process_writes(influxdb3_local, table_batches, args=None):&lt;/code&gt;&amp;lt;/pre&amp;gt;&lt;/li&gt;
  &lt;li&gt;Scheduled plugin: &lt;code class="language-markup"&gt;def process_scheduled_call(influxdb3_local, call_time, args=None):&lt;/code&gt;&amp;lt;/pre&amp;gt;&lt;/li&gt;
  &lt;li&gt;HTTP-request plugin: &lt;code class="language-markup"&gt;def process_request(influxdb3_local, query_parameters, request_headers, request_body, args=None):&lt;/code&gt;&amp;lt;/pre&amp;gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A trigger is the database resource that connects an event to a plugin. You create it with &lt;code class="language-markup"&gt;influxdb3 create trigger&lt;/code&gt;, specifying a &lt;em&gt;trigger spec&lt;/em&gt; that defines when the plugin runs.&lt;/p&gt;

&lt;p&gt;Whatever the type, every plugin receives &lt;code class="language-markup"&gt;influxdb3_local&lt;/code&gt; which is the shared API object that’s your gateway to the database. The methods we’ll use:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class="language-markup"&gt;influxdb3_local.query(sql)&lt;/code&gt; - run a SQL query and get results back as a list of dictionaries.&lt;/li&gt;
  &lt;li&gt;&lt;code class="language-markup"&gt;influxdb3_local.write(line)&lt;/code&gt; - write a point back into the database, built with the &lt;code class="language-markup"&gt;LineBuilder&lt;/code&gt; helper.&lt;/li&gt;
  &lt;li&gt;&lt;code class="language-markup"&gt;influxdb3_local.info(msg)&lt;/code&gt;/ &lt;code class="language-markup"&gt;.warn(msg)&lt;/code&gt; / &lt;code class="language-markup"&gt;.error(msg)&lt;/code&gt; - log to stdout and the system.processing_engine_logs table.&lt;/li&gt;
  &lt;li&gt;The engine also offers an in-memory cache for keeping state between runs (useful for things like tracking a rolling baseline), though we’ll keep our first version stateless.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code class="language-markup"&gt;LineBuilder&lt;/code&gt; is the recommended way to construct a point inside a plugin:&lt;/p&gt;

&lt;pre class=""&gt;&lt;code class="language-python"&gt;line = LineBuilder("my_table")
line.tag("device", "pump_7")
line.float64_field("value", 42.0)
line.int64_field("count", 3)
influxdb3_local.write(line)&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Plugins receive trigger arguments as a &lt;code class="language-markup"&gt;Dict[str, str]&lt;/code&gt; in &lt;code class="language-markup"&gt;args&lt;/code&gt;, which is how we’ll pass tunable thresholds without editing code.&lt;/p&gt;

&lt;h2 id="predictive-maintenance-plugin-logic"&gt;Predictive maintenance plugin logic&lt;/h2&gt;

&lt;p&gt;We need to turn raw sensor readings into a Remaining Useful Life estimate. A full production system might run a trained LSTM or gradient-boosted model, but a tutorial plugin should be transparent and dependency-light, so we’ll use a degradation-index approach that captures the real idea behind RUL prediction without a black box:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Pick the sensors that carry degradation signals. In FD001, several sensors drift steadily as the high-pressure compressor wears. Sensors s_2, s_3, s_4, s_8, s_11, s_13, and s_15 trend upward over an engine’s life; s_7, s_12, s_20, and s_21 trend downward. (Some sensors in FD001 are flat and carry no information—we ignore those.)&lt;/li&gt;
  &lt;li&gt;Normalize each chosen sensor against the healthy-baseline range so they’re comparable, flipping the downward-trending ones so “more degraded” always means “higher.”&lt;/li&gt;
  &lt;li&gt;Average them into a single health index between roughly 0 (factory-fresh) and 1 (failure imminent).&lt;/li&gt;
  &lt;li&gt;Map the index to an RUL estimate. Using the dataset convention that degradation becomes meaningfully predictable in roughly the final 125 cycles, we estimate &lt;code class="language-markup"&gt;RUL ≈ 125 × (1 − health_index)&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;Alert when the estimated RUL drops below a configurable threshold.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is intentionally simple and explainable. The plugin structure is identical whether the scoring function is ten lines of arithmetic or a 50-megabyte neural network,  you’d just swap the body of &lt;code class="language-markup"&gt;compute_health_index&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;To normalize, the plugin needs each sensor’s healthy and degraded reference values. Rather than hard-code magic numbers, we’ll derive them once at the top of each run from the fleet itself with the early-life readings approximate “healthy” and the latest readings approximate “degraded.”&lt;/p&gt;

&lt;h2 id="creating-the-plugin"&gt;Creating the plugin&lt;/h2&gt;

&lt;p&gt;Create the plugin file directly in your plugin directory. Save this as &lt;code class="language-markup"&gt;~/influxdb3/plugins/predictive_maintenance.py&lt;/code&gt;.&lt;/p&gt;

&lt;pre class=""&gt;&lt;code class="language-python"&gt;# predictive_maintenance.py
#
# Scheduled plugin: estimates Remaining Useful Life (RUL) for each engine
# from the most recent sensor readings, writes the estimates back into the
# database, and raises alerts when RUL drops below a threshold.

# Sensors that rise as the engine degrades
RISING = ["s_2", "s_3", "s_4", "s_8", "s_11", "s_13", "s_15"]
# Sensors that fall as the engine degrades
FALLING = ["s_7", "s_12", "s_20", "s_21"]

# Convention from the C-MAPSS literature: degradation becomes meaningfully
# predictable in roughly the final 125 cycles.
MAX_PREDICTABLE_RUL = 125.0

def _safe_float(value, default=0.0):
    try:
        return float(value)
    except (TypeError, ValueError):
        return default

def compute_baselines(influxdb3_local):
    """Derive healthy and degraded reference values for each sensor.

    Healthy  ~ average of the earliest cycles across the fleet.
    Degraded ~ average of the latest cycles across the fleet.
    """
    sensors = RISING + FALLING
    avg_cols = ", ".join([f"AVG({s}) AS {s}" for s in sensors])

    healthy_rows = influxdb3_local.query(
        f"SELECT {avg_cols} FROM sensors WHERE time_cycles = 15"
    )
    degraded_rows = influxdb3_local.query(
        f"SELECT {avg_cols} FROM sensors WHERE time_cycles = 175"
    )

    if not healthy_rows or not degraded_rows:
        return None

    healthy = healthy_rows[0]
    degraded = degraded_rows[0]

    baselines = {}
    for s in sensors:
        lo = _safe_float(healthy.get(s))
        hi = _safe_float(degraded.get(s))
        baselines[s] = (lo, hi)
    return baselines

def normalize(value, lo, hi):
    """Scale a reading to 0..1 between the healthy (lo) and degraded (hi)
    references. Clamps to the [0, 1] range."""
    if hi == lo:
        return 0.0
    frac = (value - lo) / (hi - lo)
    return max(0.0, min(1.0, frac))

def compute_health_index(reading, baselines):
    """Combine the signal-bearing sensors into a single 0..1 degradation
    index. 0 = healthy, 1 = failure imminent."""
    scores = []

    for s in RISING:
        lo, hi = baselines[s]
        scores.append(normalize(_safe_float(reading.get(s)), lo, hi))

    for s in FALLING:
        lo, hi = baselines[s]
        # Falling sensors: degraded value is lower, so invert the scale.
        scores.append(1.0 - normalize(_safe_float(reading.get(s)), hi, lo))

    if not scores:
        return 0.0
    return sum(scores) / len(scores)

def process_scheduled_call(influxdb3_local, call_time, args=None):
    # --- Read tunables from trigger arguments ---
    args = args or {}
    rul_threshold = float(args.get("rul_threshold", "30"))
    lookback = args.get("lookback", "10m")

    influxdb3_local.info(
        f"Predictive maintenance run starting. "
        f"rul_threshold={rul_threshold}, lookback={lookback}"
    )

    # --- Build per-sensor baselines from the fleet ---
    baselines = compute_baselines(influxdb3_local)
    if baselines is None:
        influxdb3_local.warn("Not enough data to compute baselines; skipping run.")
        return

    # --- Get the most recent reading per engine ---
    sensor_select = ", ".join(RISING + FALLING)
    latest = influxdb3_local.query(
        f"""
        SELECT unit_number, time_cycles, {sensor_select}
        FROM sensors
        WHERE time &amp;gt;= now() - INTERVAL '{lookback}'
        ORDER BY time DESC
        """
    )

    if not latest:
        influxdb3_local.warn("No recent sensor data in lookback window.")
        return

    # Keep only the newest row per engine (results are time-desc ordered).
    seen = set()
    newest_per_engine = []
    for row in latest:
        unit = row.get("unit_number")
        if unit not in seen:
            seen.add(unit)
            newest_per_engine.append(row)

    alerts = 0
    for row in newest_per_engine:
        unit = str(row.get("unit_number"))
        cycles = int(_safe_float(row.get("time_cycles")))

        health = compute_health_index(row, baselines)
        est_rul = round(MAX_PREDICTABLE_RUL * (1.0 - health), 1)

        # --- Write the RUL estimate ---
        est = LineBuilder("rul_estimates")
        est.tag("unit_number", unit)
        est.float64_field("health_index", round(health, 4))
        est.float64_field("estimated_rul", est_rul)
        est.int64_field("time_cycles", cycles)
        influxdb3_local.write(est)

        # --- Raise an alert if the engine is in the danger zone ---
        if est_rul = rul_threshold:
            alerts += 1
            severity = "critical" if est_rul = rul_threshold / 2 else "warning"

            alert = LineBuilder("maintenance_alerts")
            alert.tag("unit_number", unit)
            alert.tag("severity", severity)
            alert.float64_field("estimated_rul", est_rul)
            alert.float64_field("health_index", round(health, 4))
            influxdb3_local.write(alert)

            influxdb3_local.warn(
                f"[{severity.upper()}] Engine {unit}: estimated RUL "
                f"{est_rul} cycles (health index {round(health, 2)}). "
                f"Schedule maintenance."
            )

    influxdb3_local.info(
        f"Run complete. Scored {len(newest_per_engine)} engines, "
        f"raised {alerts} alert(s)."
    )&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Let’s walk through the important parts.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Baselines from the data help us avoid any magic numbers.&lt;/strong&gt; - &lt;code class="language-markup"&gt;compute_baselines&lt;/code&gt; asks the database for the average sensor values during early life (cycles ≤ 15, “healthy”) and late life (cycles ≥ 175, “degraded”). This adapts automatically to your fleet and means you don’t have to know the absolute scale of s_4 in advance.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;A single, explainable health index.&lt;/strong&gt; &lt;code class="language-markup"&gt;compute_health_index&lt;/code&gt; normalizes each signal-bearing sensor onto a 0–1 scale and averages them. Rising and falling sensors are both oriented so that 1 always means “more degraded.” This is the piece you’d replace with a trained model in production — the surrounding plumbing stays identical.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Newest reading per engine.&lt;/strong&gt; The query pulls everything in the lookback window ordered newest-first, then we keep the first row we see for each &lt;code class="language-markup"&gt;unit_number&lt;/code&gt;. Because of how we loaded the data (each engine’s last cycle anchored to “now”), the most recent rows are the most degraded.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Writing conclusions back.&lt;/strong&gt; Every engine gets a row in &lt;code class="language-markup"&gt;rul_estimates&lt;/code&gt;. Engines past the threshold also get a row in &lt;code class="language-markup"&gt;maintenance_alerts&lt;/code&gt;, tagged with a &lt;code class="language-markup"&gt;severity&lt;/code&gt; derived from how deep into the danger zone they are, plus a logged warning you’ll see in the server terminal.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Configurable via trigger arguments.&lt;/strong&gt; &lt;code class="language-markup"&gt;rul_threshold&lt;/code&gt; and &lt;code class="language-markup"&gt;lookback&lt;/code&gt; come from &lt;code class="language-markup"&gt;args&lt;/code&gt;, so you can retune behavior by recreating the trigger.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id="creating-the-trigger"&gt;Creating the trigger&lt;/h2&gt;

&lt;p&gt;Now connect the plugin to a schedule. We’ll run it every minute, with a 30-cycle RUL alert threshold and a 10-minute lookback window:&lt;/p&gt;

&lt;pre class=""&gt;&lt;code class="language-bash"&gt;influxdb3 create trigger \
  --trigger-spec "every:1m" \
  --path "predictive_maintenance.py" \
  --trigger-arguments "rul_threshold=30,lookback=10m" \
  --database engine_fleet \
  pdm_scheduler&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Breaking that down:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class="language-markup"&gt;--trigger-spec "every:1m"&lt;/code&gt; runs the plugin once a minute. You could also use &lt;code class="language-markup"&gt;cron:&lt;/code&gt; for calendar schedules, for example &lt;code class="language-markup"&gt;cron:0 0 8 * * *&lt;/code&gt; for 8am daily (the format includes seconds).&lt;/li&gt;
  &lt;li&gt;&lt;code class="language-markup"&gt;--path "predictive_maintenance.py"&lt;/code&gt; is the filename relative to your plugin directory. (For a multi-file plugin you’d point this at the directory containing &lt;code class="language-markup"&gt;__init__.py&lt;/code&gt; instead.)&lt;/li&gt;
  &lt;li&gt;&lt;code class="language-markup"&gt;--trigger-arguments&lt;/code&gt; passes our tunables as key=value pairs.&lt;/li&gt;
  &lt;li&gt;&lt;code class="language-markup"&gt;pdm_scheduler&lt;/code&gt; is the trigger’s name.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The trigger is enabled by default and starts running on the next interval boundary.&lt;/p&gt;

&lt;h2 id="testing-out-the-plugin"&gt;Testing out the plugin&lt;/h2&gt;

&lt;p&gt;Within a minute, the plugin fires. Check the server terminal and you’ll see the &lt;code class="language-markup"&gt;info&lt;/code&gt; and &lt;code class="language-markup"&gt;warn&lt;/code&gt; log lines. Now query what it produced.&lt;/p&gt;

&lt;p&gt;The latest RUL estimates, most-degraded first:&lt;/p&gt;

&lt;pre class=""&gt;&lt;code class="language-bash"&gt;influxdb3 query --database engine_fleet \
  "SELECT unit_number, estimated_rul, health_index, time_cycles \
   FROM rul_estimates \
   ORDER BY time DESC, estimated_rul ASC LIMIT 15"&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The maintenance alerts:&lt;/p&gt;

&lt;pre class=""&gt;&lt;code class="language-bash"&gt;influxdb3 query --database engine_fleet \
  "SELECT time, unit_number, severity, estimated_rul, health_index \
   FROM maintenance_alerts \
   ORDER BY time DESC LIMIT 20"&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;If you want to confirm the plugin is registered and see its file details:&lt;/p&gt;

&lt;pre class=""&gt;&lt;code class="language-bash"&gt;influxdb3 show plugins&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;And the engines flagged critical right now:&lt;/p&gt;

&lt;pre class=""&gt;&lt;code class="language-bash"&gt;influxdb3 query --database engine_fleet \
  "SELECT unit_number, estimated_rul FROM maintenance_alerts \
   WHERE severity = 'critical' ORDER BY estimated_rul ASC"&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;You should see a spread of RUL estimates across the fleet, with the most-degraded engines surfacing as warnings or criticals.&lt;/p&gt;

&lt;h2 id="inspecting-logs-and-iterating"&gt;Inspecting logs and iterating&lt;/h2&gt;

&lt;p&gt;Plugin logs go both to the server’s stdout and to a system table, which is handy for debugging without scrolling the terminal:&lt;/p&gt;

&lt;pre class=""&gt;&lt;code class="language-bash"&gt;influxdb3 query --database engine_fleet \
  "SELECT * FROM system.processing_engine_logs ORDER BY time DESC LIMIT 20"&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;When you change the plugin code, you don’t need to recreate the trigger. Edit the file and push the update, preserving the trigger’s configuration and history:&lt;/p&gt;

&lt;pre class=""&gt;&lt;code class="language-bash"&gt;influxdb3 update trigger \
  --database engine_fleet \
  --trigger-name pdm_scheduler \
  --path "predictive_maintenance.py"&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;For local development you can also develop a plugin on your own machine and upload it with &lt;code class="language-markup"&gt;--upload&lt;/code&gt; when creating or updating a trigger, which copies the file to the server for you (this requires an admin token). And if you want to dry-run a scheduled plugin without waiting for the interval, there’s &lt;code class="language-markup"&gt;influxdb3 test schedule_plugin&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;To pause the system without deleting anything:&lt;/p&gt;

&lt;pre class=""&gt;&lt;code class="language-bash"&gt;influxdb3 disable trigger --database engine_fleet pdm_scheduler
# ...and later...
influxdb3 enable trigger --database engine_fleet pdm_scheduler&lt;/code&gt;&lt;/pre&gt;

&lt;h4 id="error-handling"&gt;Error Handling&lt;/h4&gt;

&lt;p&gt;By default, plugin errors are logged and the trigger keeps going. For a critical pipeline you might prefer automatic retries or auto-disable. Set this when creating the trigger with &lt;code class="language-markup"&gt;--error-behavior retry&lt;/code&gt; or &lt;code class="language-markup"&gt;--error-behavior disable&lt;/code&gt; (the default is log).&lt;/p&gt;

&lt;h2 id="what-to-build-next"&gt;What to build next&lt;/h2&gt;

&lt;p&gt;You now have a complete, self-contained predictive maintenance loop running inside InfluxDB 3 
Core. Some natural extensions:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Swap in a real model.&lt;/strong&gt; Replace &lt;code class="language-markup"&gt;compute_health_index&lt;/code&gt; with a trained regressor or classifier. Install the library with &lt;code class="language-markup"&gt;influxdb3 install package&lt;/code&gt;, load your serialized model at the top of the plugin, and predict per engine. The trigger, the writes, and the alerts don’t change.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Add a notifier.&lt;/strong&gt; Have the alert branch call an external service like Slack, PagerDuty, email directly from Python. InfluxData also publishes an official notifier plugin you can compose with.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Use the engine’s cache for stateful detection.&lt;/strong&gt; Track a rolling baseline or a per-engine trend slope across runs instead of recomputing fleet baselines each time, using the in-memory cache to persist state between executions.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Lower the latency.&lt;/strong&gt; Move from a scheduled trigger to a data-write trigger if you need to react the moment a reading crosses a line.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Browse the official plugin library.&lt;/strong&gt; InfluxData maintains a public repository of plugins (anomaly detection via MAD, threshold/deadman checks, Prophet forecasting, downsampling, and more) that you can reference directly in a trigger with the &lt;code class="language-markup"&gt;gh:&lt;/code&gt; prefix, or copy and adapt.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The broader point is the pattern: with the InfluxDB 3 Processing Engine, the intelligence lives in the database, next to the data, reacting as the data moves. For time-series-heavy domains like industrial IoT and predictive maintenance, that proximity is exactly what you want.&lt;/p&gt;
</description>
      <pubDate>Tue, 09 Jun 2026 08:00:00 +0000</pubDate>
      <link>https://www.influxdata.com/blog/predictive-maintenance-plugin-tutorial/</link>
      <guid isPermaLink="true">https://www.influxdata.com/blog/predictive-maintenance-plugin-tutorial/</guid>
      <category>Developer</category>
      <author>Charles Mahler (InfluxData)</author>
    </item>
    <item>
      <title>How to Use Time Series Autoregression (With Examples)</title>
      <description>&lt;p&gt;Time series autoregression is a powerful statistical technique that uses past values of a variable to predict its future values. This approach is particularly valuable for forecasting applications where historical patterns can inform future trends.&lt;/p&gt;

&lt;p&gt;In this hands-on tutorial, you’ll learn how to implement autoregressive (AR) models using Python and see how InfluxDB can enhance your time series analysis workflow.&lt;/p&gt;

&lt;h2 id="understanding-time-series-autoregression"&gt;Understanding time series autoregression&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.ibm.com/think/topics/autoregressive-model"&gt;Autoregression models&lt;/a&gt; represent one of the fundamental approaches to time series forecasting, based on the principle that past behavior can predict future outcomes. The “auto” in &lt;a href="https://www.influxdata.com/blog/guide-regression-analysis-time-series-data/"&gt;autoregression&lt;/a&gt; means the variable is regressed on itself—essentially, we’re using the variable’s own historical values as predictors.&lt;/p&gt;

&lt;p&gt;This concept is intuitive: yesterday’s temperature influences today’s temperature and last month’s sales figures can indicate this month’s performance.&lt;/p&gt;

&lt;p&gt;An autoregressive model of order p, denoted as AR(p), uses the previous p observations to predict the next value:
&lt;img src="//images.ctfassets.net/o7xu9whrs0u9/50y9E1BxjOVQKkCJINlRHt/7988c5c42a7e5913447a4dab7253c9a3/Screenshot_2026-04-09_at_12.36.02â__PM.png" alt="AR SS 1" /&gt;
X(t) = c + φ₁X(t-1) + φ₂X(t-2) + … + φₚX(t-p) + ε(t)&lt;/p&gt;

&lt;p&gt;Where:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;X(t) is the value at time t&lt;/li&gt;
  &lt;li&gt;c is a constant term representing the baseline level&lt;/li&gt;
  &lt;li&gt;φ₁, φ₂, …, φₚ are the autoregressive coefficients indicating the influence of each lag&lt;/li&gt;
  &lt;li&gt;ε(t) is white noise representing random, unpredictable fluctuations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The coefficients determine how much influence each previous observation has on the current prediction. Positive coefficients indicate that higher past values lead to higher current predictions, while negative coefficients suggest an inverse relationship.&lt;/p&gt;

&lt;h2 id="types-of-autoregressive-models-and-their-applications"&gt;Types of autoregressive models and their applications&lt;/h2&gt;

&lt;h4 id="ar1-first-order-autoregression"&gt;AR(1) First-Order Autoregression&lt;/h4&gt;

&lt;p&gt;The simplest autoregressive model uses only the immediately previous value:
X(t) = c + φ₁X(t-1) + ε(t)&lt;/p&gt;

&lt;p&gt;AR(1) models are particularly effective for data with strong short-term dependencies, such as daily stock returns or temperature variations. The single coefficient φ₁ captures the persistence of the series—values close to 1 indicate high persistence, while values near 0 suggest more random behavior.&lt;/p&gt;

&lt;h4 id="arp-higher-order-models"&gt;AR(p) Higher-Order Models&lt;/h4&gt;

&lt;p&gt;More complex temporal patterns often require multiple lags:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;AR(2) models: Capture oscillating patterns where the current value depends on both the previous value and the value two periods ago.&lt;/li&gt;
  &lt;li&gt;AR(3) and beyond: Useful for data with complex patterns that extend beyond immediate past values.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id="seasonal-autoregressive-models"&gt;Seasonal Autoregressive Models&lt;/h4&gt;

&lt;p&gt;Real-world time series often exhibit seasonal patterns that repeat at regular intervals. Seasonal AR models extend the basic AR framework to capture these periodic dependencies, particularly valuable for retail sales forecasting, energy consumption prediction, and agricultural yield estimation.&lt;/p&gt;

&lt;h4 id="model-selection-and-diagnostic-considerations"&gt;Model Selection and Diagnostic Considerations&lt;/h4&gt;

&lt;p&gt;Selecting the appropriate AR model order requires careful analysis of the data’s autocorrelation structure. The &lt;a href="https://www.influxdata.com/blog/autocorrelation-in-time-series-data/"&gt;autocorrelation&lt;/a&gt; function (ACF) shows how correlated the series is with its own lagged values, while the partial autocorrelation function (PACF) reveals the direct relationship between observations at different lags.&lt;/p&gt;

&lt;p&gt;For AR models, the PACF is particularly informative because it cuts off sharply after the true model order. This characteristic makes PACF plots an essential diagnostic tool for determining the optimal number of lags to include in the model.&lt;/p&gt;

&lt;h2 id="setting-up-your-environment"&gt;Setting up your environment&lt;/h2&gt;

&lt;p&gt;Before implementing our AR model, let’s set up the necessary tools and data infrastructure to analyze time series data with InfluxDB.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.influxdata.com/products/influxdb-core/?utm_source=website&amp;amp;utm_medium=direct&amp;amp;utm_campaign=time_series_autoregression&amp;amp;utm_content=blog"&gt;InfluxDB Core&lt;/a&gt; is designed to handle time-series data with an optimized storage engine and powerful query capabilities. It excels at tracking weather patterns or monitoring environmental conditions, making it an ideal choice for efficiently managing and analyzing time-stamped data.&lt;/p&gt;

&lt;h4 id="installing-required-libraries"&gt;Installing Required Libraries&lt;/h4&gt;

&lt;p&gt;&lt;code class="language-markup"&gt;uv add pandas numpy matplotlib statsmodels influxdb3-python scikit-learn&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Or setup a python virtual environment and install with the following:&lt;/p&gt;

&lt;p&gt;&lt;code class="language-markup"&gt;python -m venv .venv&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;For Mac or Linux activate your virtual environment with the following:&lt;/p&gt;

&lt;p&gt;&lt;code class="language-markup"&gt;source .venv/bin/activate&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;For Window run this:&lt;/p&gt;

&lt;p&gt;&lt;code class="language-markup"&gt;.venv\Scripts\activate.bat # Windows (PowerShell) .venv\Scripts\Activate.ps1&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;And finally, install the required libraries:&lt;/p&gt;

&lt;p&gt;&lt;code class="language-markup"&gt;pip install pandas numpy matplotlib statsmodels influxdb3-python scikit-learn&lt;/code&gt;&lt;/p&gt;

&lt;h4 id="connecting-to-influxdb"&gt;Connecting to InfluxDB&lt;/h4&gt;

&lt;p&gt;First, let’s establish a connection to your local InfluxDB instance:&lt;/p&gt;

&lt;pre class=""&gt;&lt;code class="language-python"&gt;from influxdb_client_3 import InfluxDBClient3, Point
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from statsmodels.tsa.ar_model import AutoReg
from statsmodels.graphics.tsaplots import plot_acf, plot_pacf
from sklearn.metrics import mean_squared_error, mean_absolute_error

# InfluxDB connection parameters
INFLUXDB_HOST = "localhost:8181"
INFLUXDB_TOKEN = "your_token_here"  # Replace with your actual token
INFLUXDB_DATABASE = "weather"       # Database name for InfluxDB 3

# Initialize client
client = InfluxDBClient3(
    host=INFLUXDB_HOST,
    database=INFLUXDB_DATABASE,
    token=INFLUXDB_TOKEN
)&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id="implementing-ar-models-for-predicting-temperature"&gt;Implementing AR models for predicting temperature&lt;/h2&gt;

&lt;p&gt;Let’s walk through a practical example using temperature data to demonstrate autoregressive modeling.&lt;/p&gt;

&lt;h4 id="loading-and-preprocessing-the-data"&gt;Loading and Preprocessing the Data&lt;/h4&gt;

&lt;p&gt;First, we’ll generate sample temperature data and store it in InfluxDB, then retrieve it for analysis:&lt;/p&gt;

&lt;pre class=""&gt;&lt;code class="language-python"&gt;def generate_sample_temperature_data():
    """Generate realistic temperature data with seasonal patterns"""
    np.random.seed(42)
    dates = pd.date_range(start='2023-01-01', end='2024-01-01', freq='D')

    # Create temperature data with trend and seasonality
    trend = np.linspace(15, 18, len(dates))
    seasonal = 10 * np.sin(2 * np.pi * np.arange(len(dates)) / 365.25)
    noise = np.random.normal(0, 2, len(dates))
    temperature = trend + seasonal + noise

    return pd.DataFrame({
        'timestamp': dates,
        'temperature': temperature
    })

def store_data_in_influxdb(df):
    """Store temperature data in InfluxDB"""
    records = [
        Point("temperature")
            .field("value", row['temperature'])
            .time(row['timestamp'])
        for _, row in df.iterrows()
    ]
    client.write(record=records)
    print(f"Stored {len(df)} temperature readings in InfluxDB")

def load_data_from_influxdb():
    """Retrieve temperature data from InfluxDB"""
    query = """
        SELECT time, value
        FROM temperature
        WHERE time &amp;gt;= now() - INTERVAL '1 year'
        ORDER BY time
    """
    table = client.query(query=query, mode="pandas")
    table['time'] = pd.to_datetime(table['time'])
    table = table.set_index('time').sort_index()
    return table['value']

# Generate and store sample data
sample_data = generate_sample_temperature_data()
store_data_in_influxdb(sample_data)

# Load data for analysis
temperature_series = load_data_from_influxdb()
print(f"Loaded {len(temperature_series)} temperature observations")&lt;/code&gt;&lt;/pre&gt;

&lt;h4 id="exploring-autocorrelation-and-determining-model-order"&gt;Exploring Autocorrelation and Determining Model Order&lt;/h4&gt;

&lt;p&gt;Before fitting an AR model, we need to understand the autocorrelation structure:&lt;/p&gt;

&lt;p&gt;&lt;img src="//images.ctfassets.net/o7xu9whrs0u9/1if3YOBZ3cdnk2Mm0jSqkl/76ce3e78181ab2336a0d9635037d39b2/Screenshot_2026-04-09_at_12.44.09â__PM.png" alt="autocorrelation SS" /&gt;&lt;/p&gt;

&lt;p&gt;The Partial Autocorrelation Function (PACF) helps determine the optimal AR order by showing the correlation between observations at different lags, controlling for shorter lags.&lt;/p&gt;

&lt;h4 id="building-and-training-the-ar-model"&gt;Building and Training the AR Model&lt;/h4&gt;

&lt;p&gt;Now let’s implement the autoregressive model:&lt;/p&gt;

&lt;p&gt;&lt;img src="//images.ctfassets.net/o7xu9whrs0u9/3G2y0GY250RZSOEL7zJgTj/e43ca0040107d949fe7e760a3824654c/Screenshot_2026-04-09_at_12.45.52â__PM.png" alt="AR Model SS" /&gt;&lt;/p&gt;

&lt;p&gt;Visualization is crucial for understanding model performance:&lt;/p&gt;

&lt;p&gt;&lt;img src="//images.ctfassets.net/o7xu9whrs0u9/3GXiWDP36MjuLhMHHHs3HI/f1cd3397f608d8ad02ed6ff1b493ce95/Screenshot_2026-04-09_at_12.47.57â__PM.png" alt="Visualization SS 1" /&gt;
&lt;img src="//images.ctfassets.net/o7xu9whrs0u9/4P3vmJqDvTMx1ny8DSwuxF/c9916f312c2c9c1fe05c401195023a9b/Screenshot_2026-04-09_at_12.48.12â__PM.png" alt="Visulization SS 2" /&gt;&lt;/p&gt;

&lt;h2 id="benefits-and-limitations-of-autoregressive-models"&gt;Benefits and limitations of autoregressive models&lt;/h2&gt;

&lt;h4 id="advantages-of-ar-models"&gt;Advantages of AR Models&lt;/h4&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Computational Efficiency&lt;/strong&gt;: AR models are computationally lightweight compared to complex machine learning approaches. This efficiency makes them ideal for real-time applications where quick predictions are essential, such as high-frequency trading systems or real-time monitoring applications.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Interpretability&lt;/strong&gt;: Unlike black-box machine learning models, AR models provide clear, interpretable coefficients that reveal the influence of each lagged value. This transparency is crucial in regulated industries where model decisions must be explainable and auditable.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Strong Theoretical Foundation&lt;/strong&gt;: AR models rest on well-established statistical theory with known properties and assumptions. This theoretical grounding provides confidence in model behavior and enables rigorous statistical testing of model adequacy.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Excellent Baseline Performance&lt;/strong&gt;: AR models often serve as effective baseline models against which more complex approaches are compared. Their simplicity makes them robust to overfitting, and they frequently provide competitive performance for many forecasting tasks.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id="limitations-and-challenges"&gt;Limitations and Challenges&lt;/h4&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Linear Relationship Assumptions&lt;/strong&gt;: AR models assume linear relationships between past and future values, which may not capture complex nonlinear patterns present in many real-world time series.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Stationarity Requirements&lt;/strong&gt;: The assumption of stationarity can be restrictive for many practical applications. Real-world time series often exhibit trends, structural breaks, or changing volatility that violate stationarity assumptions.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Limited Complexity Handling&lt;/strong&gt;: AR models struggle with complex seasonal patterns, multiple interacting factors, or regime changes. While seasonal AR models exist, they may not capture intricate seasonal dynamics as effectively as more sophisticated approaches.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id="practical-implementation-considerations"&gt;Practical Implementation Considerations&lt;/h4&gt;

&lt;p&gt;When implementing AR models in practice, several key considerations ensure successful deployment. Data preprocessing often requires careful attention to stationarity testing and transformation.&lt;/p&gt;

&lt;p&gt;Model validation requires time-aware cross-validation techniques that respect the temporal structure of the data. Traditional random sampling approaches can introduce data leakage, where future information inadvertently influences past predictions.&lt;/p&gt;

&lt;p&gt;Parameter selection involves balancing model complexity with predictive accuracy. Information criteria like AIC and BIC provide systematic approaches to order selection, while out-of-sample testing validates the chosen specification.&lt;/p&gt;

&lt;h2 id="time-series-analysis-with-influxdb"&gt;Time series analysis with InfluxDB&lt;/h2&gt;

&lt;p&gt;InfluxDB provides several critical advantages for time series autoregression workflows that extend beyond simple data storage. As a purpose-built time series database, InfluxDB addresses many challenges associated with managing and analyzing temporal data at scale.&lt;/p&gt;

&lt;h4 id="optimized-storage-and-performance"&gt;Optimized Storage and Performance&lt;/h4&gt;

&lt;p&gt;InfluxDB’s columnar storage format and specialized compression algorithms reduce storage requirements for time series data. This efficiency becomes crucial when working with high-frequency data or maintaining long historical records necessary for robust AR model training.&lt;/p&gt;

&lt;h4 id="real-time-data-processing"&gt;Real-time Data Processing&lt;/h4&gt;

&lt;p&gt;Modern forecasting applications often require real-time model updates as new data arrives. InfluxDB’s streaming capabilities enable continuous data ingestion, allowing AR models to incorporate the latest observations immediately.&lt;/p&gt;

&lt;h4 id="scalable-query-operations"&gt;Scalable Query Operations&lt;/h4&gt;

&lt;p&gt;As time series datasets grow, query performance becomes a limiting factor. InfluxDB’s indexing strategies and query optimization target temporal queries, enabling fast aggregations and data retrieval operations common in AR model preprocessing.&lt;/p&gt;

&lt;h4 id="native-time-series-functions"&gt;Native Time Series Functions&lt;/h4&gt;

&lt;p&gt;InfluxDB includes built-in functions for common time series operations like moving averages and lag calculations. These functions can preprocess data directly within the database.&lt;/p&gt;

&lt;h2 id="production-deployment-and-best-practices"&gt;Production deployment and best practices&lt;/h2&gt;

&lt;p&gt;Deploying AR models in production environments requires attention to several operational aspects. Model monitoring becomes crucial as data patterns evolve over time, potentially degrading model performance. InfluxDB’s ability to store both input data and model predictions simplifies the creation of monitoring dashboards.&lt;/p&gt;

&lt;p&gt;Performance considerations include monitoring prediction accuracy over time and detecting concept drift.&lt;/p&gt;

&lt;h2 id="capping-it-off"&gt;Capping it off&lt;/h2&gt;

&lt;p&gt;Time series autoregression provides a powerful and interpretable foundation for forecasting applications across diverse domains. The combination of statistical rigor, computational efficiency, and clear interpretability makes AR models an essential tool in the time series analyst’s toolkit.&lt;/p&gt;

&lt;p&gt;While AR models have limitations in handling complex nonlinear patterns, their strengths in capturing temporal dependencies make them invaluable for both standalone applications and as components in more complex forecasting systems.&lt;/p&gt;

&lt;p&gt;The integration of AR modeling with modern time series infrastructure like &lt;a href="https://www.influxdata.com/?utm_source=website&amp;amp;utm_medium=direct&amp;amp;utm_campaign=time_series_autoregression&amp;amp;utm_content=blog"&gt;InfluxDB&lt;/a&gt; creates opportunities for robust, scalable forecasting solutions. By leveraging InfluxDB’s specialized capabilities alongside the proven statistical foundations of autoregressive modeling, practitioners can build production-ready forecasting systems that deliver reliable predictions.&lt;/p&gt;
</description>
      <pubDate>Wed, 22 Apr 2026 08:00:00 +0000</pubDate>
      <link>https://www.influxdata.com/blog/time-series-autoregression/</link>
      <guid isPermaLink="true">https://www.influxdata.com/blog/time-series-autoregression/</guid>
      <category>Developer</category>
      <author>Charles Mahler (InfluxData)</author>
    </item>
    <item>
      <title>What is MRO? Maintenance, Repair, and Operations Explained</title>
      <description>&lt;p&gt;MRO stands for &lt;strong&gt;maintenance, repair, and operations&lt;/strong&gt;. It refers to the activities, supplies, and services that keep equipment, facilities, and infrastructure running safely and efficiently. Every industry that relies on physical assets depends on MRO, whether that means replacing a worn bearing on a production line, restocking safety gloves in a warehouse, or servicing an HVAC system in a hospital.&lt;/p&gt;

&lt;p&gt;Despite being one of the largest categories of indirect spending in most organizations, MRO is chronically under-managed. This article explains what MRO covers, why it matters, how maintenance strategies differ, and how it plays out across industries.&lt;/p&gt;

&lt;h2 id="what-is-mro"&gt;What is MRO?&lt;/h2&gt;

&lt;p&gt;MRO is a broad category that encompasses the indirect materials, maintenance activities, and operational support required to keep a business functioning. MRO includes everything from spare parts and lubricants to safety equipment, cleaning supplies, and the labor required to inspect, fix, and service physical assets.&lt;/p&gt;

&lt;p&gt;The scope of MRO varies by organization, but it always sits outside of direct production. A replacement motor for a conveyor belt is an MRO item. The raw steel that travels on that conveyor is not. This distinction matters for accounting, procurement strategy, and inventory management.&lt;/p&gt;

&lt;h4 id="common-mro-supplies-and-activities"&gt;Common MRO Supplies and Activities&lt;/h4&gt;

&lt;p&gt;MRO is easier to understand through concrete examples:&lt;/p&gt;

&lt;div&gt;
  &lt;table&gt;
    &lt;thead&gt;
      &lt;tr&gt;
        &lt;th&gt;Category&lt;/th&gt;
        &lt;th&gt;Description&lt;/th&gt;
        &lt;th&gt;Examples&lt;/th&gt;
      &lt;/tr&gt;
    &lt;/thead&gt;
    &lt;tbody&gt;
      &lt;tr&gt;
        &lt;td&gt;MRO supplies&lt;/td&gt;
        &lt;td&gt;Parts, materials, and consumables used to maintain equipment and facilities.&lt;/td&gt;
        &lt;td&gt;Spare parts (bearings, seals, belts, filters, motors), lubricants and greases, fasteners, hand and power tools, electrical components (fuses, contactors, wiring), safety equipment (gloves, goggles, hard hats, respirators), cleaning and janitorial products, adhesives and tapes, and facility consumables (light bulbs, HVAC filters).&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;MRO activities&lt;/td&gt;
        &lt;td&gt;Hands-on maintenance and repair work performed on assets.&lt;/td&gt;
        &lt;td&gt;Routine inspections, lubrication, electrical testing, equipment alignment, welding repairs, painting and corrosion protection, calibration, and full equipment rebuilds.&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;MRO services&lt;/td&gt;
        &lt;td&gt;Outsourced or contracted maintenance support.&lt;/td&gt;
        &lt;td&gt;Third-party maintenance contracts, on-call repair technicians, specialized inspections (non-destructive testing), and outsourced maintenance for complex assets.&lt;/td&gt;
      &lt;/tr&gt;
    &lt;/tbody&gt;
  &lt;/table&gt;
&lt;/div&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;h2 id="why-mro-matters"&gt;Why MRO matters&lt;/h2&gt;

&lt;p&gt;MRO spending often accounts for a significant share of an organization’s operating costs, yet it receives a fraction of the strategic attention that direct materials get. The numbers make a compelling case for changing that.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;The market is massive&lt;/strong&gt;. The global MRO market was valued at roughly $715 billion in 2025 and is projected to grow steadily through the next decade, driven by aging infrastructure, the rise of predictive maintenance, and increasing demand for operational efficiency.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Downtime is extraordinarily expensive&lt;/strong&gt;. &lt;a href="https://www.ismworld.org/supply-management-news-and-reports/news-publications/inside-supply-management-magazine/blog/2024/2024-08/the-monthly-metric-unscheduled-downtime/"&gt;A 2024 Siemens report&lt;/a&gt; found that unplanned downtime costs the world’s 500 largest companies a combined $1.4 trillion per year, roughly 11% of their annual revenues. At a facility level, costs vary by industry, but the averages are sobering: approximately $260,000 per hour in general manufacturing, and over $2 million per hour in automotive production. Even smaller manufacturers typically lose over $100,000 per hour of unexpected downtime.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Equipment failure is the leading cause of downtime&lt;/strong&gt;. The average manufacturer faces an estimated 800 hours of equipment downtime annually. Equipment failure accounts for roughly 42% of unplanned downtime incidents, and base components like bearings, seals, and motors are the most common culprits. These are precisely the kinds of failures that a well-run MRO program is designed to prevent.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Proactive maintenance pays for itself&lt;/strong&gt;. Research from McKinsey and others consistently shows that organizations implementing predictive maintenance programs see &lt;a href="https://www.iiot-world.com/predictive-analytics/predictive-maintenance/predictive-maintenance-cost-savings/"&gt;18–25% reductions&lt;/a&gt; in overall maintenance costs and 30–50% reductions in unplanned downtime. The U.S. Department of Energy has reported a potential &lt;strong&gt;ROI of up to 10x on predictive maintenance investments&lt;/strong&gt;. Reactive repairs, by contrast, cost three to five times more than planned maintenance once you account for emergency labor, expedited parts shipping, and cascading production losses.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Safety and compliance depend on it&lt;/strong&gt;. Regulatory bodies across industries mandate specific maintenance activities and intervals. Falling behind on MRO creates safety hazards for workers, compliance risk for the organization, and potential legal liability.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id="maintenance-strategies-preventive-predictive-planned-and-condition-based"&gt;Maintenance strategies: preventive, predictive, planned, and condition-based&lt;/h2&gt;

&lt;p&gt;Organizations typically employ a mix of strategies, and the trend across industries is a steady shift from reactive to proactive, data-driven approaches.&lt;/p&gt;

&lt;p&gt;&lt;img src="//images.ctfassets.net/o7xu9whrs0u9/3xBRG5cCTK4CqGAImWHorU/6d8cafbd1630cb9d3bfdddcd1218e482/Diagram_01.png" alt="Reactive to Predictive MRO" /&gt;&lt;/p&gt;

&lt;h4 id="preventive-maintenance"&gt;Preventive Maintenance&lt;/h4&gt;

&lt;p&gt;Preventive maintenance is scheduled work performed at fixed intervals to reduce the likelihood of failure. Oil changes every 500 operating hours, filter replacements every quarter, and belt inspections every month are all preventive activities. The advantage is predictability: you know what work is coming and can plan parts and labor accordingly. The drawback is that you may be replacing components that still have significant useful life remaining, which wastes money and materials.&lt;/p&gt;

&lt;h4 id="planned-maintenance"&gt;Planned Maintenance&lt;/h4&gt;

&lt;p&gt;Planned maintenance is a broader category that includes any maintenance activity scheduled in advance, whether it follows a calendar-based interval, a usage-based trigger, or a condition-based alert. The defining characteristic is that the work is anticipated and resourced before it begins, as opposed to reactive or emergency maintenance. Planned maintenance also encompasses scheduled shutdowns and turnarounds, where equipment is taken offline deliberately for extensive servicing.&lt;/p&gt;

&lt;h4 id="condition-based-maintenance"&gt;Condition-Based Maintenance&lt;/h4&gt;

&lt;p&gt;Condition-based maintenance (CBM) uses real-time monitoring of equipment health indicators like vibration, temperature, oil quality, and electrical signatures to trigger maintenance only when those indicators show that maintenance is actually needed. Rather than replacing a bearing on a fixed schedule, CBM replaces it when vibration analysis shows degradation has reached a threshold. This approach eliminates much of the waste inherent in time-based schedules while still catching problems before failure.&lt;/p&gt;

&lt;h4 id="predictive-maintenance"&gt;Predictive Maintenance&lt;/h4&gt;

&lt;p&gt;Predictive maintenance takes condition-based monitoring a step further by applying machine learning, statistical models, and trend analysis to forecast when a component is likely to fail. Where CBM reacts to current conditions, predictive maintenance anticipates future conditions based on patterns in historical and real-time data. Sensors tracking vibration, temperature, pressure, and acoustic signatures feed data into analytics platforms that can predict failures days or weeks in advance.&lt;/p&gt;

&lt;p&gt;The results are striking: organizations with mature predictive maintenance programs report 35–45% reductions in unplanned downtime and an average ROI of around 250% within the first 18 months.&lt;/p&gt;

&lt;p&gt;The movement from reactive to predictive maintenance is one of the defining trends in MRO. As IIoT sensors become cheaper and more accessible, even smaller manufacturers can begin shifting toward condition-based and predictive approaches.&lt;/p&gt;

&lt;h3 id="mro-in-manufacturing"&gt;MRO in manufacturing&lt;/h3&gt;

&lt;p&gt;In the manufacturing industry, MRO encompasses all indirect materials and maintenance activities required to keep a production facility running. It is everything that supports the production process without becoming part of the finished product.&lt;/p&gt;

&lt;p&gt;Manufacturing MRO spending is often highly fragmented. A single plant might purchase thousands of distinct SKUs, such as motor drives, conveyor belts, lubricants, rags, and safety boots, from dozens of suppliers. The proportion of organizations using more than 250 MRO suppliers has grown from 6% to 15% in recent years. This fragmentation makes it difficult to negotiate volume discounts, track usage, or identify waste.&lt;/p&gt;

&lt;p&gt;Common MRO priorities in manufacturing include reducing unplanned downtime on production lines, maintaining critical spares inventory for high-impact equipment, shifting from reactive to preventive or predictive maintenance, standardizing parts and suppliers to simplify procurement, and ensuring compliance with OSHA and environmental regulations.&lt;/p&gt;

&lt;p&gt;Manufacturers that invest in structured MRO programs typically see improvements in overall equipment effectiveness (OEE), lower maintenance costs per unit of output, and fewer safety incidents.&lt;/p&gt;

&lt;h3 id="mro-in-aviation"&gt;MRO in aviation&lt;/h3&gt;

&lt;p&gt;Aviation has one of the most rigorous and regulated MRO environments of any industry. Aircraft MRO is governed by strict regulatory frameworks like the FAA in the United States and EASA in Europe. Every maintenance activity must be performed by certified repair stations, documented in detail, and traceable.&lt;/p&gt;

&lt;p&gt;The four main categories of aviation MRO are airframe maintenance, engine maintenance, component maintenance, and line maintenance.&lt;/p&gt;

&lt;p&gt;Aviation MRO is also where data-driven maintenance has seen some of its most advanced applications. Airlines use predictive maintenance platforms that analyze sensor data from aircraft systems to forecast component failures before they occur, minimizing aircraft-on-ground events and improving safety.&lt;/p&gt;

&lt;h3 id="mro-in-energy-and-utilities"&gt;MRO in energy and utilities&lt;/h3&gt;

&lt;p&gt;Energy and utilities represent one of the most asset-intensive sectors for MRO. Power plants, refineries, pipelines, water treatment facilities, and electrical grids all require continuous maintenance to remain operational and safe.&lt;/p&gt;

&lt;p&gt;The consequences of downtime in energy are particularly severe. Utilities face additional complexity from regulatory oversight and public safety requirements; a failed transformer or water treatment system affects entire communities.&lt;/p&gt;

&lt;p&gt;This sector has been an early adopter of IIoT and predictive maintenance technologies. Real-time monitoring of turbines, generators, transformers, and pipeline infrastructure allows operators to detect degradation early and schedule maintenance during planned outages rather than responding to emergencies.&lt;/p&gt;

&lt;h2 id="mro-procurement-inventory-and-software"&gt;MRO procurement, inventory, and software&lt;/h2&gt;

&lt;p&gt;Three operational areas determine how well an MRO program actually performs on a day-to-day basis.&lt;/p&gt;

&lt;div&gt;
  &lt;table&gt;
    &lt;thead&gt;
      &lt;tr&gt;
        &lt;th&gt;Area&lt;/th&gt;
        &lt;th&gt;Description and Key Strategies&lt;/th&gt;
      &lt;/tr&gt;
    &lt;/thead&gt;
    &lt;tbody&gt;
      &lt;tr&gt;
        &lt;td&gt;Procurement&lt;/td&gt;
        &lt;td&gt;The process of sourcing and purchasing indirect materials. High transaction volume but low individual dollar value. Improvement strategies include consolidating suppliers, using blanket purchase orders, and implementing e-procurement platforms.&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;Inventory&lt;/td&gt;
        &lt;td&gt;Balancing part availability against carrying costs. Effective management relies on criticality-based stocking, min/max levels, and regular cycle counts. MRO inventory supports production but is not part of the finished product.&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;Software&lt;/td&gt;
        &lt;td&gt;Tools to plan, track, and optimize maintenance. Includes CMMS for work orders, EAM for lifecycle planning, and e-procurement tools to streamline purchasing.&lt;/td&gt;
      &lt;/tr&gt;
    &lt;/tbody&gt;
  &lt;/table&gt;
&lt;/div&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;The process of sourcing and purchasing indirect materials. High transaction volume but low individual dollar value. Improvement strategies include consolidating suppliers, using blanket purchase orders, and implementing e-procurement platforms.&lt;/p&gt;

&lt;h4 id="inventory"&gt;Inventory&lt;/h4&gt;

&lt;p&gt;Balancing part availability against carrying costs. Effective management relies on criticality-based stocking, min/max levels, and regular cycle counts. MRO inventory supports production but is not part of the finished product.&lt;/p&gt;

&lt;h4 id="software"&gt;Software&lt;/h4&gt;

&lt;p&gt;Tools to plan, track, and optimize maintenance. Includes CMMS for work orders, EAM for lifecycle planning, and e-procurement tools to streamline purchasing.&lt;/p&gt;

&lt;h2 id="where-time-series-databases-fit-in-an-mro-strategy"&gt;Where time series databases fit in an MRO strategy&lt;/h2&gt;

&lt;p&gt;The shift toward predictive maintenance creates a data infrastructure challenge that traditional systems were never designed to handle. A modern manufacturing facility with thousands of IIoT sensors can generate billions of data points daily. This is time series data, and it requires specialized tools at scale.&lt;/p&gt;

&lt;p&gt;Traditional relational databases and legacy data historians struggle with the volume, velocity, and query patterns of high-frequency sensor data. Time series databases are built for this workload. They are designed to ingest large volumes of timestamped data at high speed, compress it efficiently for long-term storage, and support the kinds of queries that maintenance and operations teams actually need: trend analysis over time windows, anomaly detection, and correlation across multiple sensor streams.&lt;/p&gt;

&lt;p&gt;&lt;img src="//images.ctfassets.net/o7xu9whrs0u9/5GIp6lyhNY9PPBrYRlO000/d5336a5398aa3ae4137af83384c737db/Diagram_02.png" alt="Telegraf Agent MRO" /&gt;&lt;/p&gt;

&lt;p&gt;InfluxDB is one of the most widely adopted time series databases in industrial environments. It is built to handle the data patterns that MRO and predictive maintenance generate, and it fits into the maintenance technology stack in several important ways.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Real-time equipment monitoring&lt;/strong&gt;: InfluxDB ingests data from PLCs, SCADA systems, and IIoT sensors via standard industrial protocols like MQTT, OPC UA, and Modbus through its Telegraf agent. This creates a live feed of equipment health data that maintenance teams can use to spot anomalies as they develop.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Historical context for predictive models&lt;/strong&gt;: Effective predictive maintenance depends on having deep historical data to train machine learning models. InfluxDB stores years of sensor data in a compressed columnar format, making it practical and cost-effective to retain the historical depth that ML models need to identify failure patterns.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Bridging OT and IT systems&lt;/strong&gt;: One of the persistent challenges in MRO is that operational technology and information technology often exist in separate silos. InfluxDB integrates with both sides of this divide, connecting industrial data sources at the edge with analytics tools, cloud platforms, and AI/ML pipelines on the IT side.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Edge-to-cloud flexibility&lt;/strong&gt;: Not every facility has the same infrastructure. Some need on-premises data processing for latency or security reasons; others want cloud-based analytics. InfluxDB supports deployment at the edge, in private clouds, or in fully-managed cloud environments, allowing organizations to match their data architecture to their operational reality.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The practical impact is tangible. &lt;a href="https://www.influxdata.com/resources/how-seadrill-transformed-billions-sensor-data-into-actionable-insights-with-influxdb/"&gt;Seadrill&lt;/a&gt; has reported saving over $1.6 million in a single year by using InfluxDB as its time series database for equipment monitoring. &lt;a href="https://www.influxdata.com/blog/siemens-energy-standardizes-predictive-maintenance-influxdb/"&gt;Siemens Energy uses InfluxDB to monitor 23,000 battery modules across more than 70 sites&lt;/a&gt;, analyzing billions of sensor readings to prevent downtime and ensure quality.&lt;/p&gt;

&lt;p&gt;For operations and maintenance teams evaluating their data infrastructure, the key question is whether their current systems can handle the data volumes that condition-based and predictive maintenance demand. If the answer is no, a time series database is the foundational layer that makes advanced maintenance strategies possible.&lt;/p&gt;

&lt;h2 id="common-mro-challenges"&gt;Common MRO challenges&lt;/h2&gt;

&lt;p&gt;Even well-intentioned MRO programs run into recurring problems.&lt;/p&gt;

&lt;h4 id="fragmented-spending"&gt;Fragmented Spending&lt;/h4&gt;

&lt;p&gt;This is the most widespread issue. When every department or site purchases MRO supplies independently, organizations lose leverage with suppliers and have no visibility into total spend.&lt;/p&gt;

&lt;h4 id="reactive-maintenance-culture"&gt;Reactive Maintenance Culture&lt;/h4&gt;

&lt;p&gt;This culture remains entrenched in many organizations. ABB’s Value of Reliability research found that two-thirds of companies experience unplanned downtime at least once per month, and a full third have not undertaken motor or drive modernization projects in the past two years, even though upgrading obsolete equipment can generate ROI in less than two years.&lt;/p&gt;

&lt;h4 id="poor-data-quality"&gt;Poor Data Quality&lt;/h4&gt;

&lt;p&gt;Poor data quality undermines almost every MRO improvement effort. Incomplete asset records, mislabeled parts, and patchy work-order histories make it difficult to decide what to stock, when to maintain, and where to invest. This problem compounds as organizations try to implement predictive maintenance, which depends entirely on clean, structured, time-stamped data.&lt;/p&gt;

&lt;h4 id="excess-and-obsolete-inventory"&gt;Excess and Obsolete Inventory&lt;/h4&gt;

&lt;p&gt;Excess and obsolete inventory tie up capital and warehouse space. Parts ordered for equipment that has since been retired, or spares stocked based on outdated failure rates, accumulate quietly until someone audits the stockroom.&lt;/p&gt;

&lt;h2 id="how-to-improve-an-mro-strategy"&gt;How to improve an MRO strategy&lt;/h2&gt;

&lt;p&gt;There is no single playbook for MRO improvement, but a few principles apply broadly.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Start with visibility&lt;/strong&gt;. Before you optimize anything, you need a clear picture of what you are spending, where your inventory sits, and how your assets are performing. Consolidating data from procurement, maintenance, and inventory systems is almost always the first step.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Classify assets by criticality&lt;/strong&gt;. Not all equipment deserves the same level of attention. Focus preventive and predictive maintenance resources on the assets whose failure would cause the greatest impact on safety, production, or cost.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Consolidate suppliers and standardize parts&lt;/strong&gt;. Reducing the number of MRO suppliers simplifies procurement, improves negotiating leverage, and makes it easier to manage inventory. Standardizing on common parts across similar equipment reduces the total number of SKUs you need to carry.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Shift from reactive to proactive maintenance&lt;/strong&gt;. This is a long-term cultural change, not a one-time project. Start with the highest-criticality assets, prove the value with condition monitoring and predictive analytics, and then scale. Organizations that make this transition consistently report dramatic reductions in both downtime and total maintenance cost.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Invest in the right data infrastructure&lt;/strong&gt;. Advanced maintenance strategies are only as good as the data infrastructure behind them. This means CMMS/EAM software for work order management, time series databases for high-frequency sensor data, and integration layers that connect these systems so that insights flow from the sensor to the decision-maker without friction.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Measure what matters&lt;/strong&gt;. Track metrics that connect MRO performance to business outcomes: planned vs. unplanned maintenance ratio, spare parts availability, mean time between failures (MTBF), overall equipment effectiveness (OEE), and maintenance cost as a percentage of asset replacement value.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id="wrapping-up"&gt;Wrapping up&lt;/h2&gt;

&lt;p&gt;MRO may not be the most glamorous line item in an operating budget, but it is one of the most consequential. The organizations that treat maintenance, repair, and operations as a strategic function consistently outperform those that don’t. As sensor technology gets cheaper, predictive analytics gets smarter, and the data infrastructure to support them becomes more accessible, the gap between reactive and proactive organizations will only widen. The best time to invest in your MRO strategy was five years ago. The second-best time is now.&lt;/p&gt;

&lt;h2 id="mro-faqs"&gt;MRO FAQs&lt;/h2&gt;

&lt;div id="accordion_second"&gt;
    &lt;article class="message"&gt;
        &lt;a href="javascript:void(0)" data-action="collapse" data-target="collapsible-message-accordion-second-1"&gt;
            &lt;div class="message-header"&gt;
                &lt;h3&gt;What does MRO stand for?&lt;/h3&gt;
                &lt;span class="icon"&gt;
                    &lt;i class="fas fa-angle-down" aria-hidden="true"&gt;&lt;/i&gt;
                &lt;/span&gt;
            &lt;/div&gt;&lt;/a&gt;
        &lt;div id="collapsible-message-accordion-second-1" class="message-body is-collapsible is-active" data-parent="accordion_second" data-allow-multiple="true"&gt;
            &lt;div class="message-body-content"&gt;
                MRO most commonly stands for maintenance, repair, and operations—the activities, supplies, and services that keep equipment and facilities running. In aviation and heavy industry, MRO can also stand for maintenance, repair, and overhaul, where "overhaul" refers to the complete teardown, inspection, and rebuild of a component or system to original specifications. Both meanings describe the same core concept: sustaining operational readiness of physical assets.
            &lt;/div&gt;
        &lt;/div&gt;
    &lt;/article&gt;

    &lt;article class="message"&gt;
        &lt;a href="javascript:void(0)" data-action="collapse" data-target="collapsible-message-accordion-second-2"&gt;
            &lt;div class="message-header"&gt;
                &lt;h3&gt;What is MRO in business?&lt;/h3&gt;
                &lt;span class="icon"&gt;
                    &lt;i class="fas fa-angle-down" aria-hidden="true"&gt;&lt;/i&gt;
                &lt;/span&gt;
            &lt;/div&gt;&lt;/a&gt;
        &lt;div id="collapsible-message-accordion-second-2" class="message-body is-collapsible" data-parent="accordion_second" data-allow-multiple="true"&gt;
            &lt;div class="message-body-content"&gt;
                In a business context, MRO refers to all indirect spending related to keeping operations running. This includes everything from preventive maintenance schedules and spare parts to safety equipment, cleaning supplies, and facility consumables. MRO sits outside of direct production costs but has a significant impact on uptime, safety, and total operating expense.
            &lt;/div&gt;
        &lt;/div&gt;
    &lt;/article&gt;

    &lt;article class="message"&gt;
        &lt;a href="javascript:void(0)" data-action="collapse" data-target="collapsible-message-accordion-second-3"&gt;
            &lt;div class="message-header"&gt;
                &lt;h3&gt;What is the difference between MRO inventory and production inventory?&lt;/h3&gt;
                &lt;span class="icon"&gt;
                    &lt;i class="fas fa-angle-down" aria-hidden="true"&gt;&lt;/i&gt;
                &lt;/span&gt;
            &lt;/div&gt;&lt;/a&gt;
        &lt;div id="collapsible-message-accordion-second-3" class="message-body is-collapsible" data-parent="accordion_second" data-allow-multiple="true"&gt;
            &lt;div class="message-body-content"&gt;
                Production inventory consists of raw materials and components that become part of the finished product. MRO inventory includes spare parts, tools, consumables, and supplies used to maintain equipment and facilities; items that support production but never appear in the final product. Both require management, but they serve different purposes and are often handled by different teams with different procurement strategies.
            &lt;/div&gt;
        &lt;/div&gt;
    &lt;/article&gt;

    &lt;article class="message"&gt;
        &lt;a href="javascript:void(0)" data-action="collapse" data-target="collapsible-message-accordion-second-4"&gt;
            &lt;div class="message-header"&gt;
                &lt;h3&gt;What is MRO in manufacturing?&lt;/h3&gt;
                &lt;span class="icon"&gt;
                    &lt;i class="fas fa-angle-down" aria-hidden="true"&gt;&lt;/i&gt;
                &lt;/span&gt;
            &lt;/div&gt;&lt;/a&gt;
        &lt;div id="collapsible-message-accordion-second-4" class="message-body is-collapsible" data-parent="accordion_second" data-allow-multiple="true"&gt;
            &lt;div class="message-body-content"&gt;
                In manufacturing, MRO covers the indirect materials (lubricants, filters, PPE, tools, electrical components) and maintenance activities (inspections, repairs, preventive maintenance) required to keep production equipment operational. It is one of the largest categories of indirect spending in most manufacturing organizations.
            &lt;/div&gt;
        &lt;/div&gt;
    &lt;/article&gt;

    &lt;article class="message"&gt;
        &lt;a href="javascript:void(0)" data-action="collapse" data-target="collapsible-message-accordion-second-5"&gt;
            &lt;div class="message-header"&gt;
                &lt;h3&gt;What is MRO in aviation?&lt;/h3&gt;
                &lt;span class="icon"&gt;
                    &lt;i class="fas fa-angle-down" aria-hidden="true"&gt;&lt;/i&gt;
                &lt;/span&gt;
            &lt;/div&gt;&lt;/a&gt;
        &lt;div id="collapsible-message-accordion-second-5" class="message-body is-collapsible" data-parent="accordion_second" data-allow-multiple="true"&gt;
            &lt;div class="message-body-content"&gt;
                In aviation, MRO stands for maintenance, repair, and overhaul. It is a heavily regulated segment that includes line maintenance, airframe and engine maintenance, component repair, and full overhauls of aircraft systems. Aviation MRO is essential for airworthiness certification and passenger safety, and it is governed by regulatory bodies like the FAA and EASA.
            &lt;/div&gt;
        &lt;/div&gt;
    &lt;/article&gt;

    &lt;article class="message"&gt;
        &lt;a href="javascript:void(0)" data-action="collapse" data-target="collapsible-message-accordion-second-6"&gt;
            &lt;div class="message-header"&gt;
                &lt;h3&gt;What are MRO supplies?&lt;/h3&gt;
                &lt;span class="icon"&gt;
                    &lt;i class="fas fa-angle-down" aria-hidden="true"&gt;&lt;/i&gt;
                &lt;/span&gt;
            &lt;/div&gt;&lt;/a&gt;
        &lt;div id="collapsible-message-accordion-second-6" class="message-body is-collapsible" data-parent="accordion_second" data-allow-multiple="true"&gt;
            &lt;div class="message-body-content"&gt;
                MRO supplies are the materials purchased to support maintenance and operational activities. Common examples include spare parts, fasteners, lubricants, hand tools, safety gear, cleaning products, electrical components, and facility consumables like light bulbs and HVAC filters. These items are consumed during the maintenance process rather than incorporated into a finished product.
            &lt;/div&gt;
        &lt;/div&gt;
    &lt;/article&gt;

    &lt;article class="message"&gt;
        &lt;a href="javascript:void(0)" data-action="collapse" data-target="collapsible-message-accordion-second-7"&gt;
            &lt;div class="message-header"&gt;
                &lt;h3&gt;Why is MRO important?&lt;/h3&gt;
                &lt;span class="icon"&gt;
                    &lt;i class="fas fa-angle-down" aria-hidden="true"&gt;&lt;/i&gt;
                &lt;/span&gt;
            &lt;/div&gt;&lt;/a&gt;
        &lt;div id="collapsible-message-accordion-second-7" class="message-body is-collapsible" data-parent="accordion_second" data-allow-multiple="true"&gt;
            &lt;div class="message-body-content"&gt;
                MRO directly affects equipment uptime, workplace safety, regulatory compliance, and operating costs. Unplanned downtime alone costs U.S. manufacturers an estimated $50 billion per year. Organizations that manage MRO effectively experience fewer breakdowns, lower total maintenance costs, longer asset lifespans, and better safety records. As maintenance strategies evolve from reactive to predictive, the strategic importance of MRO continues to grow.
            &lt;/div&gt;
        &lt;/div&gt;
    &lt;/article&gt;

    &lt;article class="message"&gt;
        &lt;a href="javascript:void(0)" data-action="collapse" data-target="collapsible-message-accordion-second-8"&gt;
            &lt;div class="message-header"&gt;
                &lt;h3&gt;What is the difference between preventive and predictive maintenance?&lt;/h3&gt;
                &lt;span class="icon"&gt;
                    &lt;i class="fas fa-angle-down" aria-hidden="true"&gt;&lt;/i&gt;
                &lt;/span&gt;
            &lt;/div&gt;&lt;/a&gt;
        &lt;div id="collapsible-message-accordion-second-8" class="message-body is-collapsible" data-parent="accordion_second" data-allow-multiple="true"&gt;
            &lt;div class="message-body-content"&gt;
                Preventive maintenance follows a fixed schedule. For example, replacing a filter every 90 days regardless of its condition. Predictive maintenance uses real-time data from sensors to forecast when maintenance is actually needed, based on the condition and performance trends of the equipment. Predictive approaches reduce both unnecessary maintenance and unexpected failures, but they require investment in sensors, data infrastructure, and analytics tools.
            &lt;/div&gt;
        &lt;/div&gt;
    &lt;/article&gt;

    &lt;article class="message"&gt;
        &lt;a href="javascript:void(0)" data-action="collapse" data-target="collapsible-message-accordion-second-9"&gt;
            &lt;div class="message-header"&gt;
                &lt;h3&gt;What is a CMMS and how does it relate to MRO?&lt;/h3&gt;
                &lt;span class="icon"&gt;
                    &lt;i class="fas fa-angle-down" aria-hidden="true"&gt;&lt;/i&gt;
                &lt;/span&gt;
            &lt;/div&gt;&lt;/a&gt;
        &lt;div id="collapsible-message-accordion-second-9" class="message-body is-collapsible" data-parent="accordion_second" data-allow-multiple="true"&gt;
            &lt;div class="message-body-content"&gt;
                A CMMS (computerized maintenance management system) is software used to schedule, track, and document maintenance activities. It is one of the core tools in an MRO program, helping teams manage work orders, track asset history, plan preventive maintenance schedules, and monitor spare parts inventory. More advanced platforms (often called EAM, or enterprise asset management systems) add lifecycle planning, capital project tracking, and integration with other enterprise systems.
            &lt;/div&gt;
        &lt;/div&gt;
    &lt;/article&gt;

&lt;/div&gt;
</description>
      <pubDate>Tue, 31 Mar 2026 08:00:00 +0000</pubDate>
      <link>https://www.influxdata.com/blog/mro-explained-influxdb/</link>
      <guid isPermaLink="true">https://www.influxdata.com/blog/mro-explained-influxdb/</guid>
      <category>Developer</category>
      <author>Charles Mahler (InfluxData)</author>
    </item>
    <item>
      <title>A Practical Guide to SCADA Security</title>
      <description>&lt;p&gt;Critical infrastructure is under siege. The systems that control our power grids, water treatment plants, and oil pipelines weren’t designed for a connected world. This post covers what security measures teams need to understand and how &lt;a href="https://www.influxdata.com/what-is-time-series-data/?utm_source=website&amp;amp;utm_medium=direct&amp;amp;utm_campaign=scada_security_guide&amp;amp;utm_content=blog"&gt;time series&lt;/a&gt; monitoring can help turn SCADA’s weaknesses into a security advantage.&lt;/p&gt;

&lt;h2 id="the-stakes-for-scada-security-have-never-been-higher"&gt;The stakes for SCADA security have never been higher&lt;/h2&gt;

&lt;p&gt;Somewhere right now, a programmable logic controller is opening a valve, adjusting a turbine’s speed, or regulating the chlorine levels in a city’s drinking water. These actions are orchestrated by Supervisory Control and Data Acquisition (SCADA) systems. They run power grids, water treatment facilities, oil and gas pipelines, manufacturing plants, and transportation networks.&lt;/p&gt;

&lt;p&gt;For decades, these systems operated in relative obscurity. They sat on isolated networks, spoke proprietary protocols, and were managed by operational technology (OT) engineers who rarely crossed paths with the IT security team.&lt;/p&gt;

&lt;p&gt;The convergence of IT and OT networks, driven by the demand for remote access, operational analytics, and cost efficiency, has dragged &lt;a href="https://www.influxdata.com/glossary/SCADA-supervisory-control-and-data-acquisition/"&gt;SCADA&lt;/a&gt; systems into a threat landscape they were never built to survive. The results have been dramatic. In 2015 and 2016, coordinated cyberattacks took down portions of Ukraine’s power grid, leaving hundreds of thousands without electricity. In 2021, the Colonial Pipeline ransomware attack shut down fuel distribution across the U.S. East Coast, triggering panic buying and fuel shortages.&lt;/p&gt;

&lt;p&gt;These aren’t theoretical risks. They’re documented events, and they only represent the incidents that became public. The reality is that SCADA systems are being probed, scanned, and targeted every day, and many operators lack the visibility to even know it’s happening.&lt;/p&gt;

&lt;h2 id="scada-security-challenges"&gt;SCADA security challenges&lt;/h2&gt;

&lt;p&gt;Securing SCADA and industrial control systems is fundamentally different from securing a corporate IT environment. The assumptions, priorities, and constraints are almost inverted.&lt;/p&gt;

&lt;h4 id="availability-over-confidentiality"&gt;Availability Over Confidentiality&lt;/h4&gt;

&lt;p&gt;In IT security, the classic triad is confidentiality, integrity, and availability, usually prioritized in roughly that order. In OT, the priorities flip. A power plant cannot tolerate downtime. A water treatment facility cannot go offline for a patch cycle. The consequences of a disrupted industrial process aren’t a lost spreadsheet; they’re potential physical harm, environmental damage, or loss of life. This means that many standard IT security practices, such as aggressive patching, frequent reboots, and network scanning, can be dangerous or even impossible in OT environments.&lt;/p&gt;

&lt;h4 id="legacy-systems-and-long-lifecycles"&gt;Legacy Systems and Long Lifecycles&lt;/h4&gt;

&lt;p&gt;SCADA components often have operational lifecycles of 20 to 30 years. It’s not uncommon to find PLCs running firmware from the early 2000s, human-machine interfaces (HMIs) on Windows XP, or historians on unsupported database platforms. These systems were engineered for reliability and determinism, not security. Replacing them is expensive and operationally risky, so they persist despite the vulnerabilities.&lt;/p&gt;

&lt;h4 id="protocols-without-security"&gt;Protocols Without Security&lt;/h4&gt;

&lt;p&gt;Modbus, DNP3, and &lt;a href="https://www.influxdata.com/glossary/opc-ua/"&gt;OPC&lt;/a&gt; Classic are the workhorses of industrial communication, but they were designed in an era when network isolation was considered sufficient protection. Modbus, for instance, has no authentication, no encryption, and no way to verify the identity of a device sending commands. These protocols are deeply embedded in operational infrastructure and cannot be easily replaced.&lt;/p&gt;

&lt;h4 id="the-air-gap-myth"&gt;The Air Gap Myth&lt;/h4&gt;

&lt;p&gt;Many organizations still believe their OT networks are air-gapped. In practice, true air gaps are rare. Remote access solutions, vendor support connections, shared file servers, USB drives, and even cellular modems on RTUs create pathways between networks.&lt;/p&gt;

&lt;h2 id="key-strategies-for-scada-security"&gt;Key strategies for SCADA security&lt;/h2&gt;

&lt;p&gt;Effective SCADA security is layered, OT-aware, and built around the operational realities of industrial environments. There is no single solution, but a combination of strategies dramatically reduces risk.&lt;/p&gt;

&lt;h4 id="network-segmentation"&gt;Network Segmentation&lt;/h4&gt;

&lt;p&gt;The foundation of SCADA security is proper network architecture. At a minimum, there should be a demilitarized zone (DMZ) between the corporate IT network and the OT network, with no direct traffic flowing between them. Within the OT network, further segmentation between supervisory systems, control systems, and field devices helps limit lateral movement.&lt;/p&gt;

&lt;h4 id="asset-inventory-and-visibility"&gt;Asset Inventory and Visibility&lt;/h4&gt;

&lt;p&gt;You cannot protect what you don’t know exists. Many organizations lack a complete, accurate inventory of their OT assets, including &lt;a href="https://www.influxdata.com/resources/overcoming-iiot-data-challenges-data-injection-from-plcs-to-influxdb/"&gt;PLCs&lt;/a&gt;, RTUs, HMIs, &lt;a href="https://www.influxdata.com/glossary/data-historian/"&gt;historians&lt;/a&gt;, network switches, and communication links. Passive network discovery tools designed for OT environments can build and maintain this inventory without disrupting operations.&lt;/p&gt;

&lt;h4 id="access-control-and-authentication"&gt;Access Control and Authentication&lt;/h4&gt;

&lt;p&gt;Every access point into the OT environment should require strong authentication, ideally multi-factor. Least-privilege principles should govern who can access what, and remote access should be tightly controlled, monitored, and time-limited. Shared accounts should be eliminated wherever possible.&lt;/p&gt;

&lt;h4 id="ot-aware-patch-management"&gt;OT-Aware Patch Management&lt;/h4&gt;

&lt;p&gt;Patching in OT requires a risk-based approach. Not every vulnerability needs an immediate patch, and not every system can be patched without operational impact. Organizations need a process that evaluates vulnerability severity in the context of their specific environment, tests patches in a staging environment where possible, and schedules maintenance windows that align with operational needs.&lt;/p&gt;

&lt;h4 id="deep-packet-inspection-for-industrial-protocols"&gt;Deep Packet Inspection for Industrial Protocols&lt;/h4&gt;

&lt;p&gt;Traditional firewalls see Modbus traffic as TCP on port 502 and nothing more. OT-aware firewalls and intrusion detection systems can parse the actual protocol content to inspect function codes and register addresses to enforce policies.&lt;/p&gt;

&lt;h4 id="incident-response-planning"&gt;Incident Response Planning&lt;/h4&gt;

&lt;p&gt;OT incident response is not IT incident response, the playbook must account for the physical consequences of containment actions. Isolating a network segment might stop an attacker, but could also trip a safety system or halt a process. Response plans need to be developed collaboratively between security teams, OT engineers, and plant operations.&lt;/p&gt;

&lt;h2 id="continuous-monitoring-for-scada-security"&gt;Continuous monitoring for SCADA security&lt;/h2&gt;

&lt;p&gt;All of the strategies above are essential, but there’s a fundamental truth about SCADA security that defenders can exploit: &lt;strong&gt;industrial processes are inherently predictable&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;A temperature sensor in a chemical reactor reports a value every second. A PLC cycles through its logic on a fixed schedule. A pump runs at a consistent speed. Network traffic between a SCADA server and its RTUs follows regular, repeatable patterns. This predictability means that anomalies like equipment failure, operator error, or a cyberattack create detectable deviations from established baselines.&lt;/p&gt;

&lt;p&gt;This is where time series data becomes a security team’s most powerful tool.&lt;/p&gt;

&lt;h4 id="baselining-normal-behavior"&gt;Baselining Normal Behavior&lt;/h4&gt;

&lt;p&gt;By collecting and storing high-resolution time series data from sensors, PLCs, network flows, and protocol logs, you can build a detailed behavioral profile of “normal” for every asset and process in your environment. What does normal Modbus traffic look like between the SCADA server and PLC-07? What’s the typical temperature range for reactor vessel 3 during a batch run? How often does the engineering workstation initiate write commands?&lt;/p&gt;

&lt;p&gt;With enough historical data, these baselines become remarkably precise, and deviations become immediately apparent.&lt;/p&gt;

&lt;h4 id="detecting-process-manipulation"&gt;Detecting Process Manipulation&lt;/h4&gt;

&lt;p&gt;An attacker who gains access to a SCADA system may try to subtly alter process parameters, such as changing a setpoint, opening a valve, or adjusting a chemical dosing rate. If you’re monitoring time series data from those processes, you can detect changes that fall outside historical norms.&lt;/p&gt;

&lt;h4 id="spotting-anomalous-network-behavior"&gt;Spotting Anomalous Network Behavior&lt;/h4&gt;

&lt;p&gt;Industrial network traffic is highly structured. By logging protocol-level metadata, you can detect unusual patterns. A “write multiple registers” command from an IP address that has only ever issued read commands is suspicious. A burst of DNP3 unsolicited responses at an unusual time deserves investigation. These signals are only visible if you’re capturing and analyzing this data.&lt;/p&gt;

&lt;h4 id="correlating-across-it-and-ot"&gt;Correlating Across IT and OT&lt;/h4&gt;

&lt;p&gt;The most sophisticated attacks traverse the IT/OT boundary. Detecting them requires correlating events across both domains on a unified timeline. For example, a failed VPN login attempt at 1:47 AM, followed by a successful login at 1:52 AM, followed by an unusual engineering workstation session at 1:55 AM, followed by a PLC configuration change at 2:03 AM. While each of these events in isolation might not trigger an alert, together, on a single timeline, the pattern is unmistakable. Time series data makes this correlation possible.&lt;/p&gt;

&lt;h2 id="why-a-time-series-database-beats-a-siem-or-relational-database-for-ot-security-data"&gt;Why a time series database beats a SIEM or relational database for OT security data&lt;/h2&gt;

&lt;p&gt;If you’re convinced that this kind of monitoring is critical for SCADA security, the next question is where to store and analyze all this data. The three common options are a traditional relational database, a Security Information and Event Management (SIEM) platform, or a time series database like InfluxDB. For OT security data, the &lt;a href="https://www.influxdata.com/time-series-database/?utm_source=website&amp;amp;utm_medium=direct&amp;amp;utm_campaign=scada_security_guide&amp;amp;utm_content=blog"&gt;time series database&lt;/a&gt; wins decisively. Here’s why.&lt;/p&gt;

&lt;h4 id="data-volume"&gt;Data Volume&lt;/h4&gt;

&lt;p&gt;A single SCADA environment can generate enormous volumes of data. Consider a modest facility with 500 sensors reporting every second, 20 PLCs, a network tap capturing protocol metadata, and authentication logs from access points. That’s easily millions of data points per day, and larger environments generate orders of magnitude more.&lt;/p&gt;

&lt;p&gt;Relational databases like PostgreSQL or MySQL were designed for transactional workloads: inserts, updates, deletes, and joins across normalized tables. They handle time series data poorly at scale. Write throughput degrades as tables grow, and time-based queries over millions of rows become expensive without careful indexing and partitioning, which creates operational complexity.
SIEMs are built for log ingestion, but they’re optimized for text-based event logs, not numerical telemetry. Ingesting raw sensor data at one-second intervals into a SIEM is technically possible, but economically painful, as SIEM licensing is typically based on data volume, and the cost of ingesting OT data can be prohibitive. Many organizations end up sampling or aggregating data before it reaches the SIEM, losing the granularity needed for effective &lt;a href="https://www.influxdata.com/blog/IOT-anomaly-detection-primer-influxdb/"&gt;anomaly detection&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;InfluxDB and other time series databases are built for this workload. They use storage engines optimized for high-volume writes of timestamped data and compressed, columnar storage that keeps disk usage manageable even at scale. InfluxDB can handle hundreds of thousands of writes per second on modest hardware.&lt;/p&gt;

&lt;h4 id="query-performance"&gt;Query Performance&lt;/h4&gt;

&lt;p&gt;OT security analysis is fundamentally time-focused. You need to answer questions like: “What was the average pressure in vessel 4 between 2:00 and 2:15 AM?” or “Show me all Modbus write commands to PLC-12 in the last 24 hours alongside the corresponding sensor readings.” or “Alert me if the rate of change of this temperature exceeds the 99th percentile of its 30-day historical distribution.”&lt;/p&gt;

&lt;p&gt;In a relational database, these queries require careful SQL with window functions, CTEs, and often materialized views to perform well. The query language wasn’t designed for time series operations, and performance tuning is an ongoing burden.&lt;/p&gt;

&lt;p&gt;SIEMs offer search languages that handle event correlation well but are awkward for continuous numerical analysis. Calculating rolling averages, derivatives, or statistical distributions over sensor data in a SIEM is possible but cumbersome.&lt;/p&gt;

&lt;p&gt;Time series databases provide native query primitives for exactly these operations. InfluxDB includes built-in functions for windowed aggregation, moving averages, derivatives, percentiles, and histogram analysis. A query that would require 30 lines of carefully optimized SQL can often be expressed in a few lines with InfluxDB. This matters not just for convenience but for enabling security analysts and OT engineers to explore data and build detection logic without being database specialists.&lt;/p&gt;

&lt;h4 id="data-retention"&gt;Data Retention&lt;/h4&gt;

&lt;p&gt;OT security data has a natural tiered value structure. The last 24 hours of raw sensor data are extremely valuable for investigating an active incident. The last 30 days at full resolution are important for anomaly detection baselines. Data from six months ago is useful for trend analysis, but doesn’t need high granularity. Data from a year ago might only need hourly averages for compliance purposes.&lt;/p&gt;

&lt;p&gt;Relational databases require you to manage this lifecycle manually by writing ETL jobs to downsample old data, archive tables, and manage storage. SIEMs typically offer hot/warm/cold storage tiers, but with limited control over how data is aggregated as it ages.
InfluxDB has retention policies and downsampling built into the database itself. You can define policies that automatically downsample data from one-second to one-minute resolution after 30 days, then to five-minute resolution after 90 days, and delete raw data after a year. This happens transparently, without external tooling, and keeps storage costs predictable while preserving long-term visibility.&lt;/p&gt;

&lt;h2 id="moving-forward"&gt;Moving forward&lt;/h2&gt;

&lt;p&gt;SCADA security is not a problem that can be solved with a single product, a one-time assessment, or a policy document. It requires sustained commitment to understanding your environment, monitoring it continuously, and building the organizational capacity to detect and respond to threats.&lt;/p&gt;

&lt;p&gt;The good news is that the same characteristic that makes SCADA systems vulnerable, like their reliance on predictable, deterministic processes, is also what makes them uniquely defensible through data-driven monitoring. Industrial processes generate time series data that reveals anomalies clearly when you have the right tools to capture and analyze it.&lt;/p&gt;

&lt;p&gt;A time series database like &lt;a href="https://www.influxdata.com/products/influxdb-overview/?utm_source=website&amp;amp;utm_medium=direct&amp;amp;utm_campaign=scada_security_guide&amp;amp;utm_content=blog"&gt;InfluxDB&lt;/a&gt;, paired with a well-designed collection pipeline and visualization layer, enables security teams to see their OT environment with a level of clarity that was previously impractical. Not as a replacement for network segmentation, access control, and the other foundational security measures, but as the monitoring layer that ties everything together and ensures that when something goes wrong, you know about it in seconds rather than weeks.&lt;/p&gt;

&lt;h2 id="faq"&gt;FAQ&lt;/h2&gt;

&lt;div id="accordion_second"&gt;
    &lt;article class="message"&gt;
        &lt;a href="javascript:void(0)" data-action="collapse" data-target="collapsible-message-accordion-second-1"&gt;
            &lt;div class="message-header"&gt;
                &lt;h3&gt;What regulatory frameworks apply to SCADA security, and does time series monitoring help with compliance?&lt;/h3&gt;
                &lt;span class="icon"&gt;
                    &lt;i class="fas fa-angle-down" aria-hidden="true"&gt;&lt;/i&gt;
                &lt;/span&gt;
            &lt;/div&gt;&lt;/a&gt;
        &lt;div id="collapsible-message-accordion-second-1" class="message-body is-collapsible is-active" data-parent="accordion_second" data-allow-multiple="true"&gt;
            &lt;div class="message-body-content"&gt;
                Depending on the sector, SCADA operators typically need to align with frameworks like NERC CIP for North American electric utilities, IEC 62443 for industrial automation and control systems broadly, and NIST SP 800-82 for ICS security guidance more generally. NERC CIP stands out because it's legally binding with financial penalties, not just voluntary guidance. Continuous monitoring and internal network visibility directly support requirements found in newer mandates such as NERC CIP-015-1, which specifically calls for internal network security monitoring inside electronic security perimeters. That said, compliance still requires documentation, access controls, and audit-ready evidence beyond just having a monitoring system in place.
            &lt;/div&gt;
        &lt;/div&gt;
    &lt;/article&gt;

    &lt;article class="message"&gt;
        &lt;a href="javascript:void(0)" data-action="collapse" data-target="collapsible-message-accordion-second-2"&gt;
            &lt;div class="message-header"&gt;
                &lt;h3&gt;Does time series monitoring replace the need for a SIEM in an OT environment?&lt;/h3&gt;
                &lt;span class="icon"&gt;
                    &lt;i class="fas fa-angle-down" aria-hidden="true"&gt;&lt;/i&gt;
                &lt;/span&gt;
            &lt;/div&gt;&lt;/a&gt;
        &lt;div id="collapsible-message-accordion-second-2" class="message-body is-collapsible" data-parent="accordion_second" data-allow-multiple="true"&gt;
            &lt;div class="message-body-content"&gt;
               No. A time series database outperforms a SIEM specifically for high-volume numerical telemetry (sensor readings, protocol metadata), not SIEM functionality entirely. Most mature OT security programs run both: a time series database for continuous, high-resolution sensor and network data where SIEM licensing costs become prohibitive, and a SIEM or SOAR platform for event correlation, alerting workflows, and case management. .
            &lt;/div&gt;
        &lt;/div&gt;
    &lt;/article&gt;

    &lt;article class="message"&gt;
        &lt;a href="javascript:void(0)" data-action="collapse" data-target="collapsible-message-accordion-second-3"&gt;
            &lt;div class="message-header"&gt;
                &lt;h3&gt;How long does it typically take to establish a reliable behavioral baseline for anomaly detection?&lt;/h3&gt;
                &lt;span class="icon"&gt;
                    &lt;i class="fas fa-angle-down" aria-hidden="true"&gt;&lt;/i&gt;
                &lt;/span&gt;
            &lt;/div&gt;&lt;/a&gt;
        &lt;div id="collapsible-message-accordion-second-3" class="message-body is-collapsible" data-parent="accordion_second" data-allow-multiple="true"&gt;
            &lt;div class="message-body-content"&gt;
                This varies significantly by process type and existing historical data, but industrial processes with regular production cycles generally need coverage that captures normal seasonal and operational variation, including shift patterns, batch cycles, or seasonal load changes. A baseline built on only a few days of data risks flagging normal but infrequent operations (like a weekly maintenance cycle) as anomalies. Most organizations treat baselining as an ongoing, refined process rather than a one-time setup step.
            &lt;/div&gt;
        &lt;/div&gt;
    &lt;/article&gt;

    &lt;article class="message"&gt;
        &lt;a href="javascript:void(0)" data-action="collapse" data-target="collapsible-message-accordion-second-4"&gt;
            &lt;div class="message-header"&gt;
                &lt;h3&gt;Who should own SCADA security monitoring: the IT security team or OT engineers?&lt;/h3&gt;
                &lt;span class="icon"&gt;
                    &lt;i class="fas fa-angle-down" aria-hidden="true"&gt;&lt;/i&gt;
                &lt;/span&gt;
            &lt;/div&gt;&lt;/a&gt;
        &lt;div id="collapsible-message-accordion-second-4" class="message-body is-collapsible" data-parent="accordion_second" data-allow-multiple="true"&gt;
            &lt;div class="message-body-content"&gt;
                Effective OT security monitoring generally requires shared ownership. OT engineers bring the process knowledge needed to interpret whether a deviation is a genuine threat or a legitimate operational event, while IT security teams typically bring the tooling, threat intelligence, and incident response expertise. 
            &lt;/div&gt;
        &lt;/div&gt;
    &lt;/article&gt;

    &lt;article class="message"&gt;
        &lt;a href="javascript:void(0)" data-action="collapse" data-target="collapsible-message-accordion-second-5"&gt;
            &lt;div class="message-header"&gt;
                &lt;h3&gt;What's a realistic first step for an organization with little to no existing OT visibility?&lt;/h3&gt;
                &lt;span class="icon"&gt;
                    &lt;i class="fas fa-angle-down" aria-hidden="true"&gt;&lt;/i&gt;
                &lt;/span&gt;
            &lt;/div&gt;&lt;/a&gt;
        &lt;div id="collapsible-message-accordion-second-5" class="message-body is-collapsible" data-parent="accordion_second" data-allow-multiple="true"&gt;
            &lt;div class="message-body-content"&gt;
                Security practitioners generally recommend starting with passive asset discovery and network monitoring before implementing active defenses, since you need an accurate inventory of what's actually running before you can meaningfully segment networks or set access policies. This is typically lower-risk than jumping straight into patching or active scanning, which can disrupt sensitive control systems. From there, organizations usually layer in basic network segmentation and monitoring before tackling more complex initiatives like deep packet inspection or formal compliance certification.
            &lt;/div&gt;
        &lt;/div&gt;
    &lt;/article&gt;

    &lt;article class="message"&gt;
        &lt;a href="javascript:void(0)" data-action="collapse" data-target="collapsible-message-accordion-second-6"&gt;
            &lt;div class="message-header"&gt;
                &lt;h3&gt;Can legacy SCADA equipment, like PLCs running decades-old firmware, be monitored without modification?&lt;/h3&gt;
                &lt;span class="icon"&gt;
                    &lt;i class="fas fa-angle-down" aria-hidden="true"&gt;&lt;/i&gt;
                &lt;/span&gt;
            &lt;/div&gt;&lt;/a&gt;
        &lt;div id="collapsible-message-accordion-second-6" class="message-body is-collapsible" data-parent="accordion_second" data-allow-multiple="true"&gt;
            &lt;div class="message-body-content"&gt;
                Yes, this is one of the appeals of a monitoring-based approach over replacing equipment. Passive network monitoring and protocol-aware inspection tools can observe traffic to and from legacy PLCs and RTUs without installing anything on the devices themselves, which matters because many older industrial components can't run modern security agents or tolerate the performance overhead of active scanning. This lets organizations gain security visibility into decades-old infrastructure without the cost and operational risk of a hardware replacement project.
            &lt;/div&gt;
        &lt;/div&gt;
    &lt;/article&gt;

&lt;/div&gt;
</description>
      <pubDate>Tue, 03 Mar 2026 08:00:00 +0000</pubDate>
      <link>https://www.influxdata.com/blog/scada-security-guide/</link>
      <guid isPermaLink="true">https://www.influxdata.com/blog/scada-security-guide/</guid>
      <category>Developer</category>
      <author>Charles Mahler (InfluxData)</author>
    </item>
  </channel>
</rss>
