Skip to content

QuestDB

QuestDB is a high-performance open-source time-series database designed for fast ingestion and low-latency SQL queries. It supports multiple ingestion protocols including PostgreSQL wire protocol for queries and InfluxDB Line Protocol (ILP) for high-speed time-series data ingestion.

Add the following dependency to your project file:

NuGet
1
dotnet add package Testcontainers.QuestDb

You can start a QuestDB container instance from any .NET application. This example uses xUnit.net's IAsyncLifetime interface to manage the lifecycle of the container. The container is started in the InitializeAsync method before the test method runs, ensuring that the environment is ready for testing. After the test completes, the container is removed in the DisposeAsync method.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
private readonly QuestDbContainer _questDbContainer = new QuestDbBuilder(TestSession.GetImageFromDockerfile()).Build();

public async ValueTask InitializeAsync()
{
    await _questDbContainer.StartAsync()
        .ConfigureAwait(false);
}

public ValueTask DisposeAsync()
{
    return _questDbContainer.DisposeAsync();
}

[Fact]
[Trait(nameof(DockerCli.DockerPlatform), nameof(DockerCli.DockerPlatform.Linux))]
public void ConnectionStateReturnsOpen()
{
    // Given
    using var connection = new NpgsqlConnection(_questDbContainer.GetConnectionString());

    // When
    connection.Open();

    // Then
    Assert.Equal(ConnectionState.Open, connection.State);
}

The test example uses the following NuGet dependencies:

1
2
3
4
5
6
<PackageReference Include="Microsoft.NET.Test.Sdk"/>
<PackageReference Include="coverlet.collector"/>
<PackageReference Include="xunit.runner.visualstudio"/>
<PackageReference Include="xunit.v3"/>
<PackageReference Include="net-questdb-client"/>
<PackageReference Include="Npgsql"/>

To execute the tests, use the command dotnet test from a terminal.

Tip

For the complete source code of this example and additional information, please refer to our test projects.

Connection Methods

PostgreSQL Wire Protocol (SQL Queries)

QuestDB supports the PostgreSQL wire protocol for querying data:

1
2
3
4
5
6
7
8
var connectionString = questDbContainer.GetConnectionString();
// Returns: "Host=localhost;Port=xxxxx;Database=qdb;Username=admin;Password=quest"

using var connection = new NpgsqlConnection(connectionString);
connection.Open();

using var command = new NpgsqlCommand("SELECT * FROM sensors ORDER BY ts DESC LIMIT 10;", connection);
using var reader = command.ExecuteReader();

InfluxDB Line Protocol (High-Speed Ingestion)

For high-performance time-series data ingestion, use ILP over TCP:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
var ilpHost = questDbContainer.GetInfluxLineProtocolHost();
var ilpPort = questDbContainer.GetInfluxLineProtocolPort();

using var tcpClient = new TcpClient();
await tcpClient.ConnectAsync(ilpHost, ilpPort);

using var stream = tcpClient.GetStream();
using var writer = new StreamWriter(stream) { AutoFlush = true };

// Send ILP format: measurement,tags fields timestamp
await writer.WriteLineAsync("sensors,device_id=001,location=warehouse temperature=22.5,humidity=65.2");

ILP Format:

1
measurement,tag1=value1,tag2=value2 field1=value1,field2=value2 timestamp

REST API

For direct REST API access:

1
2
3
4
5
6
7
var restApiAddress = questDbContainer.GetRestApiAddress();

using var httpClient = new HttpClient();
httpClient.BaseAddress = new Uri(restApiAddress);

// Execute SQL via REST
var response = await httpClient.GetAsync("/exec?query=SELECT * FROM sensors");

Web Console

Access the interactive Web Console:

1
2
var webConsoleUrl = questDbContainer.GetWebConsoleUrl();
// Open in browser: http://localhost:xxxxx

Configuration

Custom Credentials

1
2
3
4
var questDbContainer = new QuestDbBuilder("questdb/questdb:9.2.3")
    .WithUsername("myuser")
    .WithPassword("mypassword")
    .Build();

Example: Combined SQL + ILP

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
// 1. Create table via SQL
await using var connection = new NpgsqlConnection(questDbContainer.GetConnectionString());
await connection.OpenAsync();

await using var createCommand = new NpgsqlCommand(
    "CREATE TABLE IF NOT EXISTS sensors (ts TIMESTAMP, device_id SYMBOL, temperature DOUBLE, humidity DOUBLE) timestamp(ts) PARTITION BY DAY;",
    connection);
await createCommand.ExecuteNonQueryAsync();

// 2. Ingest data via ILP (high-speed)
using var tcpClient = new TcpClient();
await tcpClient.ConnectAsync(
    questDbContainer.GetInfluxLineProtocolHost(),
    questDbContainer.GetInfluxLineProtocolPort());

using var stream = tcpClient.GetStream();
using var writer = new StreamWriter(stream) { AutoFlush = true };

for (int i = 0; i < 10000; i++)
{
    await writer.WriteLineAsync($"sensors,device_id=dev{i % 10} temperature={20 + i % 30},humidity={50 + i % 40}");
}

// 3. Query results via SQL
await using var queryCommand = new NpgsqlCommand(
    @"SELECT device_id,
             AVG(temperature) as avg_temp,
             AVG(humidity) as avg_humidity
      FROM sensors
      WHERE ts > dateadd('h', -1, now())
      GROUP BY device_id;",
    connection);

await using var reader = await queryCommand.ExecuteReaderAsync();
while (await reader.ReadAsync())
{
    var deviceId = reader.GetString(0);
    var avgTemp = reader.GetDouble(1);
    var avgHumidity = reader.GetDouble(2);
    Console.WriteLine($"{deviceId}: {avgTemp}°C, {avgHumidity}%");
}

Time-Series Features

QuestDB extends SQL with powerful time-series operators:

SAMPLE BY (Downsampling)

1
2
3
4
SELECT ts, AVG(temperature)
FROM sensors
WHERE ts > dateadd('d', -7, now())
SAMPLE BY 1h;

LATEST ON (Deduplication)

1
2
3
SELECT *
FROM sensors
LATEST ON ts PARTITION BY device_id;

ASOF JOIN (Time-series Join)

1
2
3
4
SELECT *
FROM trades
ASOF JOIN quotes
ON symbol;

Protocol Selection Guide

Protocol Use Case Performance Complexity
ILP (TCP) High-speed ingestion ⚡ Fastest Simple
PostgreSQL SQL queries, transactions Fast Standard SQL
REST API Ad-hoc queries, web apps Moderate JSON/HTTP

Recommendation: Use ILP for ingestion, PostgreSQL for queries.

References