Volt Active Data
← Back to All Guides

DDoS Protection Demo - Quick Start Guide

ML-powered real-time network traffic classification and attack detection

1
Architecture
2
Schema (DDL)
3
Stored Procedure
4
VoltSP Pipeline
5
See It Working
Step 1 of 5

Overview

The DDoS Protection demo showcases ML-powered network traffic classification using an ONNX neural network model integrated into a VoltSP streaming pipeline. Network flow events stream from Kafka, pass through real-time ONNX inference for classification, and are aggregated into VoltDB using time-windowed stored procedures.

The system classifies traffic into 7 attack types — DDOS, DOS, MIRAI, BRUTEFORCE, RECON, SPOOFING, WEB — plus BENIGN (normal) traffic, all with sub-millisecond latency.

Prerequisites

  • VoltDB 14.1+ — database engine with time-window functions and TTL support
  • VoltSP 1.7+ — stream processing engine with ONNX plugin and tumbling windows
  • Apache Kafka — message broker with the flow-events topic (4 partitions)
  • Java 21 — required for building stored procedures and pipeline classes
  • Maven — for building the project (mvn clean package -DskipTests)
  • ONNX model files — 7-class neural network classifier in onnx_models_7classes/
1

Architecture Overview

Understanding the Data Flow

The DDoS Detection system uses ML inference to classify network traffic in real-time, detecting attacks like DDoS, DoS, Mirai, and other intrusions.

  • Network flow events stream from Kafka in real-time
  • VoltSP applies ONNX ML model for traffic classification
  • Stored procedures aggregate results by traffic class
  • Results update continuously with sub-millisecond latency
Tip

The system classifies 7 attack types: DDOS, DOS, MIRAI, BRUTEFORCE, RECON, SPOOFING, WEB, plus BENIGN traffic.

2

Schema Definition (DDL)

Traffic Aggregates Table

The traffic_aggregates table stores time-windowed counts of traffic by classification.

  • traffic_class stores the ML classification (DDOS, DOS, BENIGN, etc.)
  • tickdate stores the 15-minute time window start
  • flow_count tracks events in current window
  • cumulative_flows tracks session totals
Tip

The composite PRIMARY KEY (traffic_class, tickdate, timescale) enables efficient time-series queries.

Router Connection Stats Table

This table tracks individual network flow events between routers with automatic cleanup.

  • connection_key identifies router pairs (e.g., "R3-R18")
  • traffic_class stores the ML-classified attack type
  • TTL 5 MINUTES automatically expires old data
  • Enables real-time connection monitoring
Tip

TTL (Time-To-Live) automatically removes data older than 5 minutes, keeping the table size bounded.

Partitioning Strategy

Tables are partitioned by traffic_class to enable parallel processing.

  • PARTITION TABLE enables distributed processing
  • All events for same traffic class go to same partition
  • Enables lock-free aggregation within partitions
  • Indexes accelerate time-based and connection queries
Tip

Partitioning by traffic_class means all DDOS events are processed by one core, all BENIGN by another, etc.

Views and Stored Procedures

Views provide real-time aggregations, and procedures are registered with partition info.

  • percentagesView shows traffic class distribution
  • current_router_connections shows active connections
  • ProcessTrafficAggregates handles windowed aggregation
  • ProcessRouterConnectionStats processes individual events
Tip

The PARAMETER 2 clause tells VoltDB which procedure argument contains the partition key.

SQL
-- VoltDB Schema for DDoS Detection with ML Classification

-- Create dummy table for time window calculations
CREATE TABLE dummy (x VARCHAR(1) NOT NULL, PRIMARY KEY (x));
INSERT INTO dummy VALUES ('X');

-- Traffic classification aggregates with time windows
CREATE TABLE traffic_aggregates (
    traffic_class VARCHAR(20) NOT NULL,
    tickdate TIMESTAMP NOT NULL,
    timescale VARCHAR(20) NOT NULL,
    flow_count BIGINT NOT NULL,
    cumulative_flows BIGINT NOT NULL,
    created_at TIMESTAMP DEFAULT NOW,
    PRIMARY KEY (traffic_class, tickdate, timescale)
);

-- State table for cumulative flows per traffic class
CREATE TABLE traffic_state (
    traffic_class VARCHAR(20) NOT NULL,
    window_start TIMESTAMP NOT NULL,
    cumulative_flows BIGINT NOT NULL,
    last_update TIMESTAMP NOT NULL,
    PRIMARY KEY (traffic_class, window_start)
);

-- Router connection events with 5-minute TTL
CREATE TABLE router_connection_stats (
    event_id BIGINT NOT NULL,
    connection_key VARCHAR(20) NOT NULL,
    router_a INTEGER NOT NULL,
    router_b INTEGER NOT NULL,
    traffic_class VARCHAR(20) NOT NULL,
    created_at TIMESTAMP DEFAULT NOW NOT NULL,
    PRIMARY KEY (event_id, connection_key, traffic_class)
) USING TTL 5 MINUTES ON COLUMN created_at;

-- Create indexes for efficient queries
CREATE INDEX idx_tickdate ON traffic_aggregates (tickdate);
CREATE INDEX idx_router_connection_key ON router_connection_stats (connection_key);
CREATE INDEX idx_router_created_at ON router_connection_stats (created_at);

-- Partition tables for parallel processing
PARTITION TABLE traffic_aggregates ON COLUMN traffic_class;
PARTITION TABLE traffic_state ON COLUMN traffic_class;
PARTITION TABLE router_connection_stats ON COLUMN traffic_class;

-- View for traffic class percentages
CREATE VIEW percentagesView AS
SELECT traffic_class, SUM(flow_count) as total_flows
FROM traffic_aggregates
WHERE timescale = 'MINUTES_15'
GROUP BY traffic_class;

-- View for current router connections (last 5 min)
CREATE VIEW current_router_connections AS
SELECT connection_key, router_a, router_b, traffic_class,
       COUNT(*) as event_count, MAX(created_at) as last_activity
FROM router_connection_stats
GROUP BY connection_key, router_a, router_b, traffic_class;

-- Register stored procedures
CREATE PROCEDURE PARTITION ON TABLE traffic_aggregates COLUMN traffic_class
  FROM CLASS com.ddos.detection.ProcessTrafficAggregates;
CREATE PROCEDURE PARTITION ON TABLE router_connection_stats
  COLUMN traffic_class PARAMETER 2
  FROM CLASS com.ddos.detection.ProcessRouterConnectionStats;
3

Java Stored Procedure

Package & Imports

Define the package and import necessary VoltDB classes.

  • Package matches your project structure
  • Import VoltDB core classes (SQLStmt, VoltProcedure, VoltTable)
  • TimestampType for VoltDB timestamps
  • Map for handling multiple traffic classes
Tip

VoltDB procedures must extend VoltProcedure base class.

Constants & SQL Builders

Define constants for valid traffic classes and SQL templates.

  • VALID_TRAFFIC_CLASSES lists all 8 classifications
  • MINUTES_15 defines the time window granularity
  • INSERT_TRAFFIC_START/END build parameterized INSERT statements
  • String concatenation creates complete SQL at compile time
Tip

Using constants for traffic classes enables validation and prevents typos.

Query Statements

SQL statements to query time windows and existing aggregates.

  • getRolling15Minutes computes current 15-minute window
  • get15MinuteAggregate queries existing aggregates
  • TIME_WINDOW() function buckets timestamps
  • SQLStmt objects are pre-compiled at load time
Tip

Pre-compiled statements eliminate SQL parsing overhead at runtime.

State Management

SQL statements for managing cumulative flow state.

  • SELECT_TRAFFIC_STATE fetches current cumulative totals
  • UPSERT_TRAFFIC_STATE updates running totals
  • State tracks cumulative flows per traffic class
  • Enables session-based aggregation
Tip

UPSERT atomically inserts or updates, avoiding race conditions.

Cumulative Flow Helper

Helper method to fetch, update, and persist cumulative flow counts.

  • Fetches existing cumulative count from state table
  • Adds new flow count to cumulative total
  • Queues UPSERT to persist updated state
  • Returns new cumulative total for aggregation
Tip

Cumulative flows enable real-time attack trending over time.

Run Method - Main Logic

The entry point processes traffic aggregates from ML inference.

  • Receives traffic class, count, timestamp from VoltSP
  • Queries time window boundaries and existing state
  • Inserts new or updates existing aggregate records
  • Single transaction ensures ACID consistency
Tip

The procedure handles both new windows (INSERT) and existing windows (UPDATE).

Java — ProcessTrafficAggregates.java
/*
 * VoltDB Stored Procedure for DDoS Traffic Classification Aggregates
 * Uses time windowing for 15-minute aggregations with ML inference results
 */
package com.ddos.detection;

import java.util.Map;
import java.util.HashMap;
import org.voltdb.*;
import org.voltdb.types.TimestampType;

public class ProcessTrafficAggregates extends VoltProcedure {

    private static final String TRAFFIC_COLUMNS =
        "(traffic_class, tickdate, timescale, flow_count, cumulative_flows) ";
    private static final String MINUTES_15 = "MINUTES_15";
    private static final String INSERT_TRAFFIC_START =
        "INSERT INTO traffic_aggregates " + TRAFFIC_COLUMNS + "VALUES (?,TIME_WINDOW(";
    private static final String INSERT_TRAFFIC_END = ",?,?,?);";
    private static final String MINUTES_15_INSERT = "MINUTE,15,?)";

    // Valid traffic classes from ML model
    private static final String[] VALID_TRAFFIC_CLASSES = {
        "DDOS", "DOS", "MIRAI", "BRUTEFORCE",
        "RECON", "SPOOFING", "WEB", "BENIGN"
    };

    // SQL statements for time window calculations
    public static final SQLStmt getRolling15Minutes = new SQLStmt(
        "SELECT TIME_WINDOW(MINUTE,15,?) TICKDATE FROM dummy WHERE x = 'X';"
    );

    public static final SQLStmt getWindowStart = new SQLStmt(
        "SELECT TIME_WINDOW(MINUTE,15,?) window_start FROM dummy WHERE x = 'X';"
    );

    // SQL statements for aggregates
    public static final SQLStmt get15MinuteAggregate = new SQLStmt(
        "SELECT * FROM traffic_aggregates WHERE traffic_class = ? " +
        "AND tickdate = TIME_WINDOW(MINUTE,15,?) AND timescale = '" + MINUTES_15 + "';"
    );

    public static final SQLStmt insertTraffic15Minutes = new SQLStmt(
        INSERT_TRAFFIC_START + MINUTES_15_INSERT + INSERT_TRAFFIC_END
    );

    public static final SQLStmt updateTrafficAggregate = new SQLStmt(
        "UPDATE traffic_aggregates SET flow_count = ?, cumulative_flows = ? " +
        "WHERE traffic_class = ? AND tickdate = ? AND timescale = ?;"
    );

    // State management
    public final SQLStmt SELECT_TRAFFIC_STATE = new SQLStmt(
        "SELECT traffic_class, window_start, cumulative_flows " +
        "FROM traffic_state WHERE traffic_class = ? " +
        "AND window_start = TIME_WINDOW(MINUTE,15,?);"
    );

    public final SQLStmt UPSERT_TRAFFIC_STATE = new SQLStmt(
        "UPSERT INTO traffic_state (traffic_class, window_start, " +
        "cumulative_flows, last_update) VALUES (?, TIME_WINDOW(MINUTE,15,?), ?, ?);"
    );

    private long getAndUpdateCumulativeFlows(String trafficClass,
            TimestampType tickdate, long newFlowCount, VoltTable stateResults) {
        long cumulativeFlows = 0;
        if (stateResults.getRowCount() > 0) {
            VoltTableRow row = stateResults.fetchRow(0);
            cumulativeFlows = row.getLong("cumulative_flows");
        }
        cumulativeFlows += newFlowCount;
        voltQueueSQL(UPSERT_TRAFFIC_STATE, trafficClass, tickdate,
                     cumulativeFlows, tickdate);
        return cumulativeFlows;
    }

    public VoltTable[] run(String aggregateName, Integer aggrCount,
            TimestampType tickdate, int performCleanupInt) throws VoltAbortException {

        Map<String, Integer> aggregates = Map.of(aggregateName, aggrCount);

        if (aggregates.isEmpty()) {
            throw new VoltAbortException("No valid aggregates provided");
        }

        // Get time window information
        voltQueueSQL(getRolling15Minutes, tickdate);
        voltQueueSQL(getWindowStart, tickdate);

        // Queue state queries for all traffic classes
        for (String trafficClass : aggregates.keySet()) {
            voltQueueSQL(SELECT_TRAFFIC_STATE, trafficClass, tickdate);
        }

        for (String trafficClass : aggregates.keySet()) {
            voltQueueSQL(get15MinuteAggregate, trafficClass, tickdate);
        }

        VoltTable[] initialResults = voltExecuteSQL();

        initialResults[0].advanceRow();
        final TimestampType windowTickdate =
            initialResults[0].getTimestampAsTimestamp("TICKDATE");

        // Process each traffic class
        int stateResultIndex = 2;
        int aggregateResultIndex = 2 + aggregates.size();

        for (Map.Entry<String, Integer> entry : aggregates.entrySet()) {
            String trafficClass = entry.getKey();
            Integer flowCount = entry.getValue();

            if (flowCount == null || flowCount <= 0) {
                stateResultIndex++;
                aggregateResultIndex++;
                continue;
            }

            VoltTable stateResult = initialResults[stateResultIndex++];
            long cumulativeFlows = getAndUpdateCumulativeFlows(
                trafficClass, tickdate, flowCount, stateResult);

            VoltTable existingAggregate = initialResults[aggregateResultIndex++];

            if (!existingAggregate.advanceRow()) {
                voltQueueSQL(insertTraffic15Minutes, trafficClass, tickdate,
                             MINUTES_15, flowCount, cumulativeFlows);
            } else {
                final TimestampType actualTickdate =
                    existingAggregate.getTimestampAsTimestamp("TICKDATE");
                final long existingFlowCount = existingAggregate.getLong("flow_count");
                voltQueueSQL(updateTrafficAggregate, existingFlowCount + flowCount,
                             cumulativeFlows, trafficClass, actualTickdate, MINUTES_15);
            }
        }

        return voltExecuteSQL(true);
    }
}
4

VoltSP Pipeline (Java)

Pipeline Class & Imports

Java pipeline configuration using VoltSP fluent API. Required for tumbling window support.

  • Implements VoltPipeline interface for stream definition
  • Imports VoltSP streaming API and window components
  • Kafka, ONNX, and VoltDB plugins for source/sink/processing
  • TumblingTimeWindowConfiguratorBuilder for time windows
Tip

Java config is required for tumbling windows - YAML config does not support this feature.

Kafka Source & ONNX ML

Configure Kafka source and ONNX ML inference in the stream pipeline.

  • KafkaSourceConfigBuilder configures the flow-events topic
  • FlowEventDeserializer converts Kafka messages to FlowEvent
  • OnnxProcessorConfigBuilder applies ML model inference
  • float_input tensor feeds network flow features to model
Tip

The ONNX model classifies traffic into 7 attack types plus BENIGN.

ML Result Transform

Transform ML inference results to VoltDB procedure calls.

  • Lambda extracts classification from ONNX output_label
  • Builds VoltProcedureRequest with router and class info
  • Converts timestamp to VoltDB TimestampType (microseconds)
  • Calls ProcessRouterConnectionStats for each event
Tip

Each flow event is stored with its ML-classified traffic type.

Tumbling Window Aggregation

5-second tumbling windows aggregate events by traffic class.

  • TumblingTimeWindowConfiguratorBuilder defines window behavior
  • withWindowSpan(Duration.ofSeconds(5)) sets 5-second windows
  • withKeyExtractor groups by traffic classification
  • Aggregates count events per class per window
Tip

Tumbling windows provide non-overlapping time buckets for consistent aggregation.

Window Emit & Sink

Emit aggregates at window end and sink to VoltDB.

  • TumblingWindowTrigger.atEndOfWindow triggers emit
  • Calls ProcessTrafficAggregates with class name and count
  • emitTimestamp provides window boundary time
  • terminateWithSink sends to VoltDB procedure
Tip

Run with: java -jar voltsp.jar --pipeline com.ddos.detection.DDoSProtectionStream

Java — DDoSProtectionStream.java
/*
 * VoltSP Java Pipeline for DDoS Detection with ML Classification
 * Uses tumbling windows for time-based aggregation (requires Java config)
 */
package com.ddos.detection;

import org.voltdb.stream.api.*;
import org.voltdb.stream.api.pipeline.*;
import org.voltdb.stream.api.pipeline.window.*;
import org.voltdb.stream.plugin.kafka.api.*;
import org.voltdb.stream.plugin.onnx.api.*;
import org.voltdb.stream.plugin.volt.api.*;
import org.voltdb.stream.plugin.window.api.tumbling.*;
import org.voltdb.types.TimestampType;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.time.Duration;

public class DDoSProtectionStream implements VoltPipeline {

    @Override
    public void define(VoltStreamBuilder stream) {
        VoltProcedureSinkConfigBuilder sinkBuilder =
            VoltProcedureSinkConfigBuilder.builder();
        VoltProcedureEmitterConfigBuilder emitterBuilder =
            VoltProcedureEmitterConfigBuilder.builder()
                .withComputeFunction((request, result) -> result);

        // Configure Kafka source with FlowEvent deserializer
        var dataStream = stream.withName("Process network flows")
            .configureResource("voltdb-database", VoltDBResourceConfigBuilder.class)
            .consumeFromSource(KafkaSourceConfigBuilder.<FlowEvent>builder()
                .withKeyDeserializer(StringDeserializer.class)
                .withValueDeserializer(FlowEventDeserializer.class)
                .withStartingOffset(KafkaStartingOffset.EARLIEST))
            .processWith(kafkaRequest -> kafkaRequest.getValue())

            // ONNX ML inference for traffic classification
            .processWith(OnnxProcessorConfigBuilder.<FlowEvent>builder()
                .withInputTensorName("float_input"))

            // Transform inference result to procedure call
            .processWith(inferenceResult -> {
                FlowEvent flowEvent = inferenceResult.request();
                int routerA = Integer.parseInt(flowEvent.getSourceRouter());
                int routerB = Integer.parseInt(flowEvent.getDestinationRouter());

                // Get ML classification from ONNX output
                String trafficClass = ((String[]) inferenceResult
                    .outputTensor().get("output_label"))[0];

                TimestampType timestamp = new TimestampType(
                    flowEvent.getEventTime().toEpochMilli() * 1000);
                long eventId = flowEvent.getEventId();

                // Serialize raw feature data as JSON string
                ObjectMapper mapper = new ObjectMapper();
                String dataRaw = mapper.writeValueAsString(
                    flowEvent.getDataRaw());

                return new VoltProcedureRequest("ProcessRouterConnectionStats",
                    new Object[]{routerA, routerB, trafficClass,
                                 timestamp, dataRaw, eventId});
            })
            .terminateWithEmitter(emitterBuilder);

        // Tumbling window aggregation (5-second windows by traffic class)
        var aggregator = dataStream
            .emit("emit-cached-data", VoltProcedureTrigger.onEachResponse)
            .processWith(table -> getFlowEvent(table))
            .aggregateWithWindow(
                TumblingTimeWindowConfiguratorBuilder.<FlowEvent>builder()
                    .withWindowSpan(Duration.ofSeconds(5))
                    .withEventTimeSupplier(FlowEvent::getEventTime)
                    .withKeyExtractor(FlowEvent::getClassification)
                    .withAggregateDefinition(builder -> {
                        builder.count()
                               .distinct("label", FlowEvent::getClassification);
                    })
            );

        // Emit aggregates at end of each window
        aggregator.emit("at-the-end", TumblingWindowTrigger.atEndOfWindow)
            .processWith((aggregate, consumer, ctx) -> {
                if (aggregate.count() == 0) return;

                String aggregateName = aggregate.getKey(String.class);
                int aggregateCount = aggregate.count();
                TimestampType emitTime = new TimestampType(
                    aggregate.emitTimestamp().toEpochMilli() * 1000);

                consumer.consume(new VoltProcedureRequest(
                    "ProcessTrafficAggregates",
                    new Object[]{aggregateName, aggregateCount, emitTime, 0}
                ));
            })
            .terminateWithSink(sinkBuilder);
    }
}
5

See It Working

Live Traffic Classification

Watch real-time ML classification of network traffic as flow events stream in from Kafka.

  • Each row shows traffic class, time window, and flow counts
  • Attack types: DDOS, DOS, MIRAI, BRUTEFORCE, RECON, SPOOFING, WEB
  • BENIGN traffic represents normal network activity
  • Query traffic_aggregates for windowed stats, router_connection_stats for events
Try this query

SELECT traffic_class, flow_count, cumulative_flows, tickdate FROM traffic_aggregates WHERE timescale = 'MINUTES_15' ORDER BY tickdate DESC, flow_count DESC LIMIT 10;