Skip to main content

TDengine Query Performance Testing with JMeter

Background

In Internet of Things (IoT) and industrial monitoring scenarios, the query performance of a Time Series Database is critical. As a high-performance time-series database, TDengine's query performance directly impacts real-time monitoring, data analysis, and decision-making efficiency.

This document addresses the following issues:

  • How to test TDengine query performance using a standard tool (JMeter).
  • How to simulate real business scenarios for stress testing.
  • How to analyze test results and optimize database configuration.

Target Audience: Developers, test engineers, and IT Operations. This document assumes readers have basic database knowledge but do not require an in-depth understanding of JMeter or TDengine.

Note: This document does not cover TDengine write performance testing or query performance tuning.


Environment Preparation

Environment List & Driver Download

Required Environment:

Note: JMeter 5.6.3 supports Java 8 and above.

Verified Environment for This Document:

  • JDK Version: JDK 11 (performance verification)
  • JMeter Version: 5.6.3
  • TDengine Version: 3.4.0.4.enterprise
  • TDengine JDBC Driver: 3.8.1

JDBC Driver Configuration

After downloading the TDengine JDBC Driver dist package, two configuration methods are available:

  • Method 1: Place into JMeter lib/ext directory (recommended)

    # JMETER_HOME = JMeter install dir, e.g. /opt/apache-jmeter-5.6.3/
    cp taos-jdbcdriver-3.8.1-dist.jar $JMETER_HOME/lib/ext/
  • Method 2: Specify Classpath in JMX file (not recommended, fallback only)

    Configure in the TestPlan element:

    <stringProp name="TestPlan.user_define_classpath">/absolute/path/to/taos-jdbcdriver-3.8.1-dist.jar</stringProp>

Note: Method 1 is strongly recommended for CLI mode.

JDBC Connection Method

  • Connection Method: WebSocket
  • Driver Class: com.taosdata.jdbc.ws.WebSocketDriver
  • URL Format: jdbc:TAOS-WS://host1:port1,host2:port2,host3:port3/database?params
  • Default Port: 6041
  • Features:
    • ✅ No TDengine client driver required
    • Multi-endpoint supported (comma-separated, connection-level load balancing)
    • ✅ Auto-reconnection & failover
    • ✅ Good cross-platform compatibility

Note: Multi-endpoint selects a node via the minimum connection count algorithm at connection setup. A single JDBC connection binds to one node for its lifetime — not request-level load balancing.

URL Examples:

# Single endpoint (test env)
jdbc:TAOS-WS://127.0.0.1:6041/test?user=root&password=taosdata&timezone=Asia/Shanghai

# Three endpoints (production, recommended) ⭐
jdbc:TAOS-WS://192.168.1.100:6041,192.168.1.101:6041,192.168.1.102:6041/test?user=root&password=taosdata&timezone=Asia/Shanghai

Performance Test Environment Checklist

Verify these system settings before performance testing to avoid bottlenecks:

Check ItemRecommended ValueCommand
File handle limit65535ulimit -n
TCP connection optimizationAdjust by concurrencysysctl net.ipv4.tcp_max_tw_buckets
Swap statusDisabledswapon --show or free -h
Swappiness value1–10 (server)sysctl vm.swappiness

Note: See your OS official docs for system tuning. For TDengine server config, refer to taosd Configuration.


Test Data Preparation

Generate Test Data with taosgen

Use taosgen to generate smart meter test data.

Create Configuration File

Sample config taosgen_meters.yaml:

tdengine:
dsn: taos+ws://root:taosdata@127.0.0.1:6041/test
drop_if_exists: true
props: precision 'ms' vgroups 32 cachemodel 'last_row'

schema:
name: meters
tbname:
prefix: d
count: 1000000
from: 0
columns:
- name: ts
type: timestamp
start: 1704067200000 # 2024-01-01
precision: ms
step: 60s # 1-min interval
- name: current
type: float
min: 0
max: 100
- name: voltage
type: int
expr: '220 * math.sqrt(2) * math.sin(_i)'
- name: phase
type: float
min: 0
max: 360
tags:
- name: location
type: binary(24)
values:
- Beijing
- Shanghai
- Guangzhou
- Shenzhen
- Chengdu
- name: group_id
type: int
min: 1
max: 1000000
generation:
interlace: 1
rows_per_table: 100
rows_per_batch: 10000
num_cached_batches: 0

jobs:
insert:
steps:
- uses: tdengine/create-super-table
- uses: tdengine/create-child-table
with:
batch:
size: 100
concurrency: 10
- uses: tdengine/insert
with:
concurrency: 20

Run Data Generation

# Generate data from config
taosgen -c taosgen_meters.yaml

Data Scale

  • Super Table: meters
  • Child Tables: 1,000,000 (d0 ~ d999999)
  • Rows per Child Table: 100
  • Total Rows: 100 million

Note:

  • rows_per_batch = batch write buffer size, not rows per table. No problem if rows_per_batch > rows_per_table.
  • Adjust count and rows_per_table for your needs.

Small-Scale Version

For quick validation:

schema:
name: meters
tbname:
prefix: d
count: 10000 # 10k child tables
from: 0
generation:
rows_per_table: 100

Total: 10k tables × 100 rows = 1 million rows, good for quick tests.

CACHEMODEL Cache Settings

TDengine caches last_row results, which must be set at database creation and strongly affects test results.

CACHEMODEL Parameter:

ValueDescriptionUse Case
noneNo cache (default)Test real disk I/O for last_row
last_rowCache last row per child tableSpeed up LAST_ROW queries
last_valueCache last non-NULL value per columnSpeed up LAST function
bothBoth caches enabledMax cache effect

How to Configure:

-- Create DB with cache (recommended)
CREATE DATABASE test CACHEMODEL 'last_row';

-- Alter existing DB
ALTER DATABASE test CACHEMODEL 'last_row';

-- Show current config
show create database test;

Notes:

  1. Default = none (cache off)
  2. Frequent CACHEMODEL changes may break last/last_row accuracy
  3. Production: use last_row or both
  4. Test behavior:
    • Cache on: last_row tests connection & scheduling
    • Cache off: last_row tests random disk I/O
    • Disk I/O test: use range / full-scan queries

taosgen Example:

tdengine:
dsn: taos+ws://root:taosdata@127.0.0.1:6041/test
drop_if_exists: true
props: precision 'ms' vgroups 32 cachemodel 'last_row'

Docs: TDengine Database Configuration


Minimal Query Test & Result Analysis

JDBC Connection Configuration

JMeter Modes

  • GUI Mode: For editing/debugging only — not for performance testing
  • CLI Mode (recommended): For performance testing, lightweight & stable

All tests use CLI Mode. Edit in GUI by importing the JMX file.

Sample baseline_test.jmx:

<?xml version="1.0" encoding="UTF-8"?>
<jmeterTestPlan version="1.2" properties="5.0" jmeter="5.6.3">
<hashTree>
<TestPlan guiclass="TestPlanGui" testclass="TestPlan" testname="TDengine Baseline Test Plan">
<stringProp name="TestPlan.comments">Baseline test for TDengine database query performance</stringProp>
<boolProp name="TestPlan.functional_mode">false</boolProp>
<boolProp name="TestPlan.tearDown_on_shutdown">true</boolProp>
<boolProp name="TestPlan.serialize_threadgroups">false</boolProp>
<elementProp name="TestPlan.user_defined_variables" elementType="Arguments" guiclass="ArgumentsPanel" testclass="Arguments" testname="User Defined Variables">
<collectionProp name="Arguments.arguments"/>
</elementProp>
</TestPlan>
<hashTree>
<ThreadGroup guiclass="ThreadGroupGui" testclass="ThreadGroup" testname="Thread Group">
<stringProp name="ThreadGroup.on_sample_error">continue</stringProp>
<intProp name="ThreadGroup.num_threads">1</intProp>
<intProp name="ThreadGroup.ramp_time">1</intProp>
<boolProp name="ThreadGroup.same_user_on_next_iteration">true</boolProp>
<elementProp name="ThreadGroup.main_controller" elementType="LoopController" guiclass="LoopControlPanel" testclass="Loop Controller" testname="Loop Controller">
<boolProp name="LoopController.continue_forever">false</boolProp>
<stringProp name="LoopController.loops">100</stringProp>
</elementProp>
<boolProp name="ThreadGroup.scheduler">false</boolProp>
<stringProp name="ThreadGroup.duration"></stringProp>
<stringProp name="ThreadGroup.delay"></stringProp>
</ThreadGroup>
<hashTree>
<JDBCDataSource guiclass="TestBeanGUI" testclass="JDBCDataSource" testname="JDBC Connection Configuration">
<stringProp name="dataSource">taos</stringProp>
<stringProp name="dbUrl">jdbc:TAOS-WS://192.168.1.100:6041,192.168.1.101:6041,192.168.1.102:6041/test?timezone=Asia/Shanghai</stringProp>
<stringProp name="driver">com.taosdata.jdbc.ws.WebSocketDriver</stringProp>
<stringProp name="username">root</stringProp>
<stringProp name="password">taosdata</stringProp>
<stringProp name="poolMax">10</stringProp>
<stringProp name="connectionAge">0</stringProp>
<stringProp name="trimInterval">60000</stringProp>
<boolProp name="preinit">true</boolProp>
<intProp name="timeout">10000</intProp>
<stringProp name="queryTimeout">60</stringProp>
<boolProp name="keepAlive">true</boolProp>
</JDBCDataSource>
<hashTree/>

<JDBCSampler guiclass="TestBeanGUI" testclass="JDBCSampler" testname="Query Latest Data">
<stringProp name="dataSource">taos</stringProp>
<stringProp name="queryType">Select Statement</stringProp>
<stringProp name="query">SELECT last_row(*) FROM d1001</stringProp>
<stringProp name="queryTimeout">60</stringProp>
<stringProp name="resultSetHandler">Count Records</stringProp>
</JDBCSampler>
<hashTree/>
</hashTree>
</hashTree>
</hashTree>
</jmeterTestPlan>

Key Parameters

ParamDescriptionRecommended Value
dbUrlJDBC URLUpdate IP/port/DB name
driverJDBC Driver ClassFixed: com.taosdata.jdbc.ws.WebSocketDriver
poolMaxMax connections< 200
connectionAgeConnection lifetime (ms)0 (unlimited)
trimIntervalIdle connection cleanup (ms)60000
preinitPre-init pooltrue
timeoutConnect timeout (ms)10000
keepAliveKeep connection alivetrue
queryTimeoutQuery timeout (s)60 (large queries)

Run the Test

Standard Mode (full report):

# Set JVM memory
export JVM_ARGS="-Xms4g -Xmx4g -XX:MaxMetaspaceSize=256m -XX:+UseG1GC"

# Run CLI test
jmeter -n -t baseline_test.jmx -l baseline_results.jtl

# Generate HTML report
jmeter -g baseline_results.jtl -o baseline_report/

# Open baseline_report/index.html in browser

High-Concurrency Mode (minimize I/O):

Create perf.properties:

jmeter.save.saveservice.latency=false
jmeter.save.saveservice.connect_time=false
jmeter.save.saveservice.idle_time=false
sampleresult.timestamp.start=false
jmeter.save.saveservice.response_code=false
jmeter.save.saveservice.response_message=false
jmeter.save.saveservice.url=false
jmeter.save.saveservice.label=false
jmeter.save.saveservice.thread_name=false
jmeter.save.saveservice.data_type=false
jmeter.save.saveservice.bytes=false
jmeter.save.saveservice.sent_bytes=false
jmeter.save.saveservice.assertions=false
jmeter.save.saveservice.assertion_results_failure_message=false
jmeter.save.saveservice.subresults=false
jmeter.save.saveservice.thread_counts=false
jmeter.save.saveservice.autoflush=false

Run:

# With reduced logging
jmeter -n -t baseline_test.jmx -l result.jtl -q perf.properties

# No JTL at all (recommended for QPS)
jmeter -n -t baseline_test.jmx

Summary Output Example:

summary +    500 in 00:00:01 =  500.2/s Avg:    2 Min:     1 Max:     5 Err:     0 (0.00%) Active: 4
summary + 1000 in 00:00:02 = 500.0/s Avg: 2 Min: 1 Max: 6 Err: 0 (0.00%) Active: 4
summary = 1500 in 00:00:03 = 500.0/s Avg: 2 Min: 1 Max: 6 Err: 0 (0.00%)

JVM Args:

  • -Xms4g -Xmx4g: fixed heap to avoid resizing
  • -XX:MaxMetaspaceSize=256m: metadata space
  • -XX:+UseG1GC: low-latency GC

Analyze Results

Open baseline_report/index.html.

HTML Report

Key Metrics:

MetricMeaningExample
ThroughputQueries per second (QPS)1850.3/sec
AverageAvg response time15.3ms
90% Line90% requests finished78ms
95% Line95% requests finished95ms
99% Line99% requests finished150ms
Error%Error rate0.002%

Percentile times (90%/95%/99%) better reflect user experience than average.

JDBC Connection Properties

Basic Example (timezone):

<stringProp name="dbUrl">jdbc:TAOS-WS://127.0.0.1:6041/test?timezone=Asia/Shanghai</stringProp>
<stringProp name="driver">com.taosdata.jdbc.ws.WebSocketDriver</stringProp>

Common URLs:

ScenarioJDBC URL Example
Test (single)jdbc:TAOS-WS://localhost:6041/test?timezone=Asia/Shanghai
Production (3 nodes)jdbc:TAOS-WS://td1:6041,td2:6041,td3:6041/prod_db?timezone=Asia/Shanghai

See TDengine JDBC Docs for more options.


CSV Parameterized Testing

CSV parameterization simulates real multi-device / multi-scenario queries.

Use Cases

  • Query millions of smart meters
  • Test different time ranges
  • Mix aggregation functions
  • Avoid identical requests

Advantages:

  • Simple text data
  • No JMX changes to update data
  • Recycle & share across threads
  • Multi-variable support

Create CSV File

# Export child table names (remove quotes)
taos -s "SELECT tags tbname FROM test.meters >> './meters.csv'" && sed -i 's/"//g' meters.csv

# Top 1000 only
taos -s "SELECT tags tbname FROM test.meters limit 1000 >> './meters.csv'" && sed -i 's/"//g' meters.csv

head meters.csv

Sample:

tbname
d37
d47
d87
d130
...

JMX Config

Sample csv_param_test.jmx:

<?xml version="1.0" encoding="UTF-8"?>
<jmeterTestPlan version="1.2" properties="5.0" jmeter="5.6.3">
<hashTree>
<TestPlan guiclass="TestPlanGui" testclass="TestPlan" testname="TDengine CSV Param Test" enabled="true">
<boolProp name="TestPlan.functional_mode">false</boolProp>
<boolProp name="TestPlan.tearDown_on_shutdown">true</boolProp>
<boolProp name="TestPlan.serialize_threadgroups">false</boolProp>
</TestPlan>
<hashTree>
<ThreadGroup guiclass="ThreadGroupGui" testclass="ThreadGroup" testname="5min Stress Test" enabled="true">
<stringProp name="ThreadGroup.on_sample_error">continue</stringProp>
<elementProp name="ThreadGroup.main_controller" elementType="LoopController">
<boolProp name="LoopController.continue_forever">true</boolProp>
<stringProp name="LoopController.loops">-1</stringProp>
</elementProp>
<stringProp name="ThreadGroup.num_threads">200</stringProp>
<stringProp name="ThreadGroup.ramp_time">60</stringProp>
<boolProp name="ThreadGroup.scheduler">true</boolProp>
<stringProp name="ThreadGroup.duration">300</stringProp>
<stringProp name="ThreadGroup.delay">0</stringProp>
</ThreadGroup>
<hashTree>
<JDBCDataSource guiclass="TestBeanGUI" testclass="JDBCDataSource" testname="JDBC Config">
<stringProp name="dataSource">taos</stringProp>
<stringProp name="dbUrl">jdbc:TAOS-WS://192.168.1.100:6041,192.168.1.101:6041,192.168.1.102:6041/test?timezone=Asia/Shanghai</stringProp>
<stringProp name="driver">com.taosdata.jdbc.ws.WebSocketDriver</stringProp>
<stringProp name="username">root</stringProp>
<stringProp name="password">taosdata</stringProp>
<stringProp name="poolMax">200</stringProp>
<stringProp name="connectionAge">0</stringProp>
<stringProp name="trimInterval">60000</stringProp>
<boolProp name="preinit">true</boolProp>
<intProp name="timeout">10000</intProp>
<boolProp name="keepAlive">true</boolProp>
</JDBCDataSource>
<hashTree/>

<CSVDataSet guiclass="TestBeanGUI" testclass="CSVDataSet" testname="CSV Data Set">
<stringProp name="filename">meters.csv</stringProp>
<stringProp name="variableNames">table_name</stringProp>
<stringProp name="delimiter">,</stringProp>
<boolProp name="ignoreFirstLine">true</boolProp>
<boolProp name="recycle">true</boolProp>
<stringProp name="shareMode">shareMode.all</stringProp>
</CSVDataSet>
<hashTree/>

<JDBCSampler guiclass="TestBeanGUI" testclass="JDBCSampler" testname="Param Query">
<stringProp name="dataSource">taos</stringProp>
<stringProp name="queryType">Select Statement</stringProp>
<stringProp name="query">SELECT last_row(*) FROM ${table_name}</stringProp>
<stringProp name="queryTimeout">10</stringProp>
</JDBCSampler>
<hashTree/>
</hashTree>
</hashTree>
</hashTree>
</jmeterTestPlan>

CSV Config:

ParamDescriptionRecommend
filenameCSV pathAbsolute/relative
variableNamesVariable namesMatch CSV columns
ignoreFirstLineSkip headertrue
recycleLoop datatrue
shareModeThread sharingshareMode.all

Parameterization Scenarios

Multi-Device Query

CSV:

table_name
d0
d1
d2

Query:

SELECT last_row(*) FROM ${table_name}

Time-Range Query

CSV:

hours
1h
6h
24h

Query:

SELECT * FROM d1001 WHERE ts > NOW - ${hours}

Aggregation Types

CSV:

agg_type
avg(current)
max(voltage)
min(phase)

Query:

SELECT ${agg_type} FROM d1001 WHERE ts > NOW - 1h

Run Test

export JVM_ARGS="-Xms4g -Xmx4g -XX:+UseG1GC"
jmeter -n -t csv_param_test.jmx

Multi-Variable Parameterization

CSV:

table_name,group_id
d0,1
d1,1
d2,2

JMX:

<CSVDataSet>
<stringProp name="variableNames">table_name,group_id</stringProp>
</CSVDataSet>

<JDBCSampler>
<stringProp name="query">SELECT * FROM ${table_name} WHERE group_id = ${group_id}</stringProp>
</JDBCSampler>

Random Parameter Testing

Random parameters simulate unpredictable real-world query patterns.

Use Cases

  • Random meter queries
  • Random time ranges
  • Avoid hot-spot queries

JMX Config

Sample random_param_test.jmx:

<?xml version="1.0" encoding="UTF-8"?>
<jmeterTestPlan version="1.2" properties="5.0" jmeter="5.6.3">
<hashTree>
<TestPlan guiclass="TestPlanGui" testclass="TestPlan" testname="TDengine Random Param Test">
</TestPlan>
<hashTree>
<ThreadGroup guiclass="ThreadGroupGui" testclass="ThreadGroup" testname="Thread Group">
<intProp name="ThreadGroup.num_threads">200</intProp>
<intProp name="ThreadGroup.ramp_time">5</intProp>
<elementProp name="ThreadGroup.main_controller" elementType="LoopController">
<boolProp name="LoopController.continue_forever">false</boolProp>
<stringProp name="LoopController.loops">10000</stringProp>
</elementProp>
</ThreadGroup>
<hashTree>
<JDBCDataSource guiclass="TestBeanGUI" testclass="JDBCDataSource" testname="JDBC Config">
<stringProp name="dataSource">taos</stringProp>
<stringProp name="dbUrl">jdbc:TAOS-WS://192.168.1.100:6041,192.168.1.101:6041,192.168.1.102:6041/test?timezone=Asia/Shanghai</stringProp>
<stringProp name="driver">com.taosdata.jdbc.ws.WebSocketDriver</stringProp>
<stringProp name="username">root</stringProp>
<stringProp name="password">taosdata</stringProp>
<stringProp name="poolMax">200</stringProp>
</JDBCDataSource>
<hashTree/>

<JDBCSampler guiclass="TestBeanGUI" testclass="JDBCSampler" testname="Random Query">
<stringProp name="dataSource">taos</stringProp>
<stringProp name="query">SELECT * FROM d${__Random(1001,2000)} WHERE ts > NOW - ${__Random(1,24)}h LIMIT 100</stringProp>
</JDBCSampler>
<hashTree/>
</hashTree>
</hashTree>
</hashTree>
</jmeterTestPlan>

Adjust Parameters

GoalChange
Random table range__Random(1001,2000)
Random time range__Random(1,24)
Result rowsLIMIT 100

Run Test

export JVM_ARGS="-Xms4g -Xmx4g -XX:+UseG1GC"
jmeter -n -t random_param_test.jmx

Command-Line Property Parameterization

Parameterize via CLI without editing JMX.

Use Cases

  • Quick config switching
  • CI/CD automation
  • Batch testing

Basic Usage

Use ${__P(prop, default)} in JMX.

Sample param_test.jmx:

<?xml version="1.0" encoding="UTF-8"?>
<jmeterTestPlan version="1.2" properties="5.0" jmeter="5.6.3">
<hashTree>
<TestPlan guiclass="TestPlanGui" testclass="TestPlan" testname="TDengine CLI Param Test">
<elementProp name="TestPlan.user_defined_variables" elementType="Arguments">
<collectionProp name="Arguments.arguments">
<elementProp name="THREADS" elementType="Argument">
<stringProp name="Argument.name">THREADS</stringProp>
<stringProp name="Argument.value">${__P(threads,10)}</stringProp>
</elementProp>
<elementProp name="LOOPS" elementType="Argument">
<stringProp name="Argument.name">LOOPS</stringProp>
<stringProp name="Argument.value">${__P(loops,100)}</stringProp>
</elementProp>
</collectionProp>
</elementProp>
</TestPlan>
<hashTree>
<JDBCDataSource guiclass="TestBeanGUI" testclass="JDBCDataSource">
<stringProp name="dataSource">taos</stringProp>
<stringProp name="dbUrl">jdbc:TAOS-WS://192.168.1.100:6041,192.168.1.101:6041,192.168.1.102:6041/test?timezone=Asia/Shanghai</stringProp>
<stringProp name="driver">com.taosdata.jdbc.ws.WebSocketDriver</stringProp>
<stringProp name="poolMax">200</stringProp>
</JDBCDataSource>
<hashTree/>

<ThreadGroup guiclass="ThreadGroupGui" testclass="ThreadGroup">
<stringProp name="ThreadGroup.num_threads">${__P(threads,10)}</stringProp>
<elementProp name="ThreadGroup.main_controller" elementType="LoopController">
<stringProp name="LoopController.loops">${__P(loops,100)}</stringProp>
</elementProp>
</ThreadGroup>
<hashTree>
<JDBCSampler guiclass="TestBeanGUI" testclass="JDBCSampler">
<stringProp name="query">SELECT last_row(*) FROM d${__Random(1,200000)}</stringProp>
</JDBCSampler>
<hashTree/>
</hashTree>
</hashTree>
</hashTree>
</jmeterTestPlan>

Run Different Tests

# Default: 10 threads × 100 loops
jmeter -n -t param_test.jmx

# 100 threads × 20000 loops
jmeter -n -t param_test.jmx -Jthreads=100 -Jloops=20000

JMeter Functions

FunctionMeaning
${__P(name, default)}Read JMeter property
${__threadNum}Current thread number
${__machineName}Hostname

Batch Test Script

Sample batch_test.sh:

#!/bin/bash
echo "=== Batch Concurrency Test ==="

CONFIGS=(
"10:100:Light"
"50:100:Medium"
"100:100:Heavy"
"200:50:Stress"
)

for cfg in "${CONFIGS[@]}"; do
IFS=':' read -r threads loops desc <<< "$cfg"
echo "Running: $desc ($threads threads × $loops loops)"
export JVM_ARGS="-Xms8g -Xmx8g -XX:+UseG1GC"
jmeter -n -t param_test.jmx -Jthreads=$threads -Jloops=$loops
done
chmod +x batch_test.sh
./batch_test.sh

Concurrency Configuration Test

Test system behavior under different loads.

Use Cases

  • 10 / 50 / 100 / 200 threads
  • Find bottlenecks
  • Determine max safe concurrency

Run Concurrency Tests

export JVM_ARGS="-Xms8g -Xmx8g -XX:MaxMetaspaceSize=512m -XX:+UseG1GC"

jmeter -n -t param_test.jmx
jmeter -n -t param_test.jmx -Jthreads=50
jmeter -n -t param_test.jmx -Jthreads=100
jmeter -n -t param_test.jmx -Jthreads=200

Step Stress Test

for threads in 10 50 100 200; do
echo "Testing $threads threads..."
jmeter -n -t param_test.jmx -Jthreads=$threads
done

Result Analysis Example

Threads | Avg RT | 90% RT | QPS  | Error%
--------|--------|--------|------|-------
10 | 15ms | 20ms | 650 | 0%
50 | 45ms | 78ms | 1100 | 0%
100 | 120ms | 250ms | 830 | 0.01%
200 | 450ms | 800ms | 440 | 5%
  • 10–50 threads: linear scaling
  • 100 threads: RT rises but errors low
  • 200 threads: bottleneck reached

Recommendation: 50–100 threads for production.


Mixed Scenario Test

Simulate real mixed query traffic.

Scenario Ratio

  • 70% real-time (last_row)
  • 25% historical (time range)
  • 5% aggregation (group by)

JMX Config

Sample mixed_scenario_test.jmx:

<jmeterTestPlan version="1.2" properties="5.0" jmeter="5.6.3">
<hashTree>
<TestPlan name="Mixed Query 5min Test"></TestPlan>
<hashTree>
<JDBCDataSource name="JDBC Config">
<stringProp name="dataSource">taos</stringProp>
<stringProp name="dbUrl">jdbc:TAOS-WS://192.168.1.100:6041,192.168.1.101:6041,192.168.1.102:6041/test?timezone=Asia/Shanghai</stringProp>
<stringProp name="driver">com.taosdata.jdbc.ws.WebSocketDriver</stringProp>
<stringProp name="poolMax">100</stringProp>
</JDBCDataSource>
<hashTree/>

<CSVDataSet name="CSV Data">
<stringProp name="filename">meters.csv</stringProp>
<stringProp name="variableNames">table_name</stringProp>
<boolProp name="ignoreFirstLine">true</boolProp>
<boolProp name="recycle">true</boolProp>
</CSVDataSet>
<hashTree/>

<ThreadGroup name="Mixed Load">
<intProp name="ThreadGroup.num_threads">100</intProp>
<intProp name="ThreadGroup.ramp_time">15</intProp>
<boolProp name="ThreadGroup.scheduler">true</boolProp>
<stringProp name="ThreadGroup.duration">300</stringProp>
</ThreadGroup>
<hashTree>
<!-- 70% realtime -->
<ThroughputController name="70% Realtime">
<intProp name="ThroughputController.style">1</intProp>
<stringProp name="ThroughputController.percentThroughput">70.0</stringProp>
</ThroughputController>
<hashTree>
<JDBCSampler name="Last Row Query">
<stringProp name="query">SELECT last_row(*) FROM ${table_name}</stringProp>
</JDBCSampler>
</hashTree>

<!-- 25% history -->
<ThroughputController name="25% History">
<stringProp name="ThroughputController.percentThroughput">25.0</stringProp>
</ThroughputController>
<hashTree>
<JDBCSampler name="History Query">
<stringProp name="query">SELECT * FROM ${table_name} WHERE ts > NOW - 24h LIMIT 1000</stringProp>
</JDBCSampler>
</hashTree>

<!-- 5% aggregation -->
<ThroughputController name="5% Aggregation">
<stringProp name="ThroughputController.percentThroughput">5.0</stringProp>
</ThroughputController>
<hashTree>
<JDBCSampler name="Agg Query">
<stringProp name="query">SELECT location, avg(current), max(voltage)
FROM meters
WHERE ts > NOW - 24h and group_id = ${__Random(1,200)}
PARTITION BY location</stringProp>
</JDBCSampler>
</hashTree>
</hashTree>
</hashTree>
</hashTree>
</jmeterTestPlan>

Run & Analyze

export JVM_ARGS="-Xms8g -Xmx8g -XX:MaxMetaspaceSize=512m -XX:+UseG1GC"
jmeter -n -t mixed_scenario_test.jmx

Sample result:

Scene        | Avg RT | QPS   | %
-------------|--------|-------|-----
Realtime | 25ms | 2400 | 70%
History | 180ms | 165 | 25%
Aggregation | 450ms | 22 | 5%

How to Identify TDengine Bottlenecks

Judge bottlenecks from JMeter metrics and server stats.

From JMeter Results

1. QPS Stagnates / Drops:

  • Threads ↑ but QPS flat or down → bottleneck

2. Response Time Surges:

  • 99% line jumps > 500ms → saturation

3. Error Rate Rises:

  • 0.1% and rising → overloaded

From TDengine Server Metrics

Recommended tools:

  • taosKeeper (built-in metrics exporter)
  • TDinsight (Grafana dashboard)

Key Server Metrics:

  1. CPU: taosd + taosAdapter > 80% → CPU bound
  2. Memory: high RSS / OOM → memory pressure
  3. Disk I/O: util > 80% → storage bottleneck
  4. Network: near bandwidth limit → network bound

Bottleneck Decision Flow

  1. JMeter: QPS flat, RT up, errors up
  2. Check server: CPU / memory / disk / net
  3. Classify bottleneck
  4. Tune accordingly

Practical Tips

  • Step pressure: 10 → 50 → 100 → 200 threads
  • Fixed-duration tests (5+ min)
  • Watch for stable QPS

Common Mistakes

  • Only look at average RT (use percentiles)
  • Chase 0% errors (small errors acceptable at very high concurrency)
  • Draw conclusions from one run (repeat tests)

FAQ & Troubleshooting

Connection Issues

1. JDBC Driver Not Found:

Cannot load JDBC driver class 'com.taosdata.jdbc.ws.WebSocketDriver'

Fix:

cp taos-jdbcdriver-*.jar $JMETER_HOME/lib/ext/

2. Connection Timeout:

java.sql.SQLException: TDengine Error: connection timeout

Fix:

  • Check TDengine/taosAdapter status
  • Increase timeout in JMX: <intProp name="timeout">30000</intProp>
  • Use port 6041 (WebSocket), not 6030

3. Too Many Open Files:

java.io.IOException: Too many open files

Fix:

ulimit -n 65535

Performance Issues

1. OutOfMemoryError:

java.lang.OutOfMemoryError: Java heap space

Fix:

export JVM_ARGS="-Xms8g -Xmx8g -XX:MaxMetaspaceSize=512m -XX:+UseG1GC"

2. Connection Pool Exhausted:

java.sql.SQLException: Timeout waiting for idle connection

Fix:

  • Increase poolMax in JDBC config
  • Tune taosAdapter concurrent limits

3. Query Timeout:

java.sql.SQLException: TDengine Error: query timeout

Fix:

  • Raise queryTimeout
  • Add LIMIT / time-range filters
  • Optimize SQL

References