Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/questdb/questdb/llms.txt

Use this file to discover all available pages before exploring further.

QuestDB’s ILP/UDP receiver provides fire-and-forget ingestion of time-series data over UDP datagrams. This method offers the lowest latency and overhead but does not guarantee message delivery.

Overview

The UDP receiver listens on port 4567 by default and supports:
  • Connectionless - No TCP handshake or connection overhead
  • Fire-and-forget - Send data without waiting for acknowledgment
  • Multicast support - Send to multiple QuestDB instances simultaneously
  • High throughput - Minimal protocol overhead for maximum ingestion speed
  • No authentication - No built-in security (use network-level controls)
UDP provides no delivery guarantees. Messages may be lost, duplicated, or arrive out of order. Use TCP for production workloads where data integrity is critical.

Quick Start

Basic Ingestion

Send ILP messages over UDP:
# Using netcat (nc)
echo "sensors,location=london temperature=23.5,humidity=65.2 $(date +%s)000000000" | nc -u localhost 4567
# Using Python socket
import socket
import time

sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

timestamp = int(time.time() * 1_000_000_000)  # nanoseconds
message = f"sensors,location=london temperature=23.5,humidity=65.2 {timestamp}\n"
sock.sendto(message.encode(), ('localhost', 4567))

sock.close()

Batch Ingestion

Send multiple measurements in a single UDP datagram:
import socket
import time

sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

# Build batch message (multiple lines)
batch = []
for i in range(100):
    timestamp = int(time.time() * 1_000_000_000) + i * 1_000_000  # 1ms apart
    line = f"sensors,sensor_id=sensor_{i%10} temperature={20 + i%10},humidity={60 + i%5} {timestamp}"
    batch.append(line)

message = '\n'.join(batch) + '\n'
sock.sendto(message.encode(), ('localhost', 4567))

sock.close()
Keep UDP datagrams under the MTU size (typically 1500 bytes) to avoid fragmentation. Fragmented packets are more likely to be lost.

Protocol Details

Message Format

UDP uses the same ILP text format as TCP:
table_name,tag1=value1,tag2=value2 field1=value1,field2=value2 timestamp
Example:
sensors,location=london,device=sensor_001 temperature=23.5,humidity=65.2 1609459200000000000

Multiple Measurements

Send multiple measurements in a single UDP datagram by separating them with newlines:
sensors,location=london temperature=23.5 1609459200000000000
sensors,location=paris temperature=18.2 1609459201000000000
sensors,location=berlin temperature=15.7 1609459202000000000

Message Termination

Each measurement must be terminated with a newline (\n):
# Correct: single measurement with newline
message = "sensors,location=london temperature=23.5 1609459200000000000\n"

# Correct: multiple measurements, each with newline
message = "sensors,location=london temperature=23.5 1609459200000000000\n" + \
          "sensors,location=paris temperature=18.2 1609459201000000000\n"

Unicast vs. Multicast

Unicast (Default)

Send data to a single QuestDB instance:
import socket

sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.sendto(message.encode(), ('localhost', 4567))
sock.close()

Multicast

Send data to multiple QuestDB instances using multicast:
import socket
import struct

MULTICAST_GROUP = '224.1.1.1'
MULTICAST_PORT = 4567

sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 2)

message = "sensors,location=london temperature=23.5 1609459200000000000\n"
sock.sendto(message.encode(), (MULTICAST_GROUP, MULTICAST_PORT))

sock.close()
Server Configuration for Multicast:
line.udp.unicast=false
line.udp.bind.to=0.0.0.0:4567
line.udp.join=224.1.1.1

Performance Characteristics

Advantages

Lowest latency - No connection setup or acknowledgment delays
Minimal overhead - No TCP handshake, retransmission, or flow control
High throughput - Can saturate network bandwidth efficiently
No back-pressure - Client never blocks waiting for server

Limitations

No delivery guarantee - Messages may be lost due to network congestion or errors
No error feedback - Client has no way to know if data was rejected
No ordering guarantee - Messages may arrive out of order
No authentication - Anyone who can reach the port can write data
Size limitations - UDP datagrams exceeding MTU may be fragmented or dropped

Error Handling

Unlike TCP, UDP provides no error feedback. Invalid messages are silently dropped.

Best Practices

Test messages - Validate ILP message format using TCP before switching to UDP.
Monitor ingestion - Query QuestDB regularly to verify data is being received.
Keep messages small - Stay under 1400 bytes per datagram to avoid fragmentation.
Use UDP for telemetry - Ideal for metrics, sensors, and logs where occasional data loss is acceptable.
Avoid UDP for critical data - Use TCP for financial data, transactions, or any data that must not be lost.

Configuration

UDP receiver configuration is set in server.conf:
# Enable/disable ILP over UDP
line.udp.enabled=true

# UDP listener configuration
line.udp.bind.to=0.0.0.0:4567

# Multicast configuration
line.udp.unicast=true
line.udp.join=224.1.1.1

# Buffer configuration
line.udp.msg.buffer.size=2048
line.udp.msg.count=10000
line.udp.receive.buffer.size=-1

# Commit configuration
line.udp.commit.rate=1048576
line.udp.commit.mode=nosync

# Schema configuration
line.udp.auto.create.new.tables=true
line.udp.auto.create.new.columns=true
line.udp.default.partition.by=DAY

# Data type defaults
line.udp.default.column.type.integer=LONG
line.udp.default.column.type.float=DOUBLE
line.udp.timestamp.unit=nanos

# Threading
line.udp.own.thread=true
line.udp.own.thread.affinity=-1

Configuration Properties

line.udp.enabled
boolean
default:"true"
Enable or disable the ILP/UDP receiver.
line.udp.bind.to
string
default:"0.0.0.0:4567"
Network interface and port to bind. Use 0.0.0.0 for all interfaces.
line.udp.unicast
boolean
default:"true"
Use unicast mode. Set to false for multicast.
line.udp.join
string
default:"224.1.1.1"
Multicast group address. Only used when line.udp.unicast=false.
line.udp.msg.buffer.size
integer
default:"2048"
Size of the buffer for each UDP message. Increase for larger messages.
line.udp.msg.count
integer
default:"10000"
Number of messages to buffer before committing. Higher values increase throughput but delay commits.
line.udp.receive.buffer.size
integer
default:"-1"
OS-level UDP receive buffer size. -1 uses OS default.
line.udp.commit.rate
integer
default:"1048576"
Commit data after this many rows. Larger values increase throughput.
line.udp.commit.mode
string
default:"nosync"
Commit mode: nosync (fastest), async, or sync (safest).
line.udp.auto.create.new.tables
boolean
default:"true"
Automatically create tables from ILP messages. If false, tables must be pre-created.
line.udp.auto.create.new.columns
boolean
default:"true"
Automatically add columns to existing tables. If false, messages with unknown columns are dropped.
line.udp.default.partition.by
string
default:"DAY"
Default partitioning strategy for auto-created tables: NONE, DAY, WEEK, MONTH, YEAR.
line.udp.timestamp.unit
string
default:"nanos"
Default timestamp unit when no suffix is provided: nanos, micros, millis.
line.udp.own.thread
boolean
default:"true"
Run UDP receiver on a dedicated thread. Improves performance on multi-core systems.
line.udp.own.thread.affinity
integer
default:"-1"
CPU core affinity for UDP receiver thread. -1 for no affinity.

Use Cases

High-Frequency Telemetry

UDP is ideal for high-frequency sensor data where occasional loss is acceptable:
import socket
import time
import random

sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

# Send 10,000 sensor readings
for i in range(10_000):
    timestamp = int(time.time() * 1_000_000_000) + i * 100_000  # 100μs apart
    temp = 20 + random.random() * 10
    humidity = 60 + random.random() * 20
    
    message = f"sensors,sensor_id=sensor_001 temperature={temp},humidity={humidity} {timestamp}\n"
    sock.sendto(message.encode(), ('localhost', 4567))

sock.close()

Application Metrics

Collect application performance metrics:
import socket
import time

def send_metric(metric_name, value, tags=None):
    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    
    timestamp = int(time.time() * 1_000_000_000)
    tag_str = ',' + ','.join(f"{k}={v}" for k, v in tags.items()) if tags else ''
    message = f"{metric_name}{tag_str} value={value} {timestamp}\n"
    
    sock.sendto(message.encode(), ('localhost', 4567))
    sock.close()

# Usage
send_metric('http.requests', 150, {'host': 'server1', 'status': '200'})
send_metric('cpu.usage', 45.2, {'host': 'server1', 'core': '0'})

IoT Sensor Networks

Collect data from distributed IoT devices:
import socket
import time

def report_sensor_reading(device_id, location, temperature, humidity):
    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    
    timestamp = int(time.time() * 1_000_000_000)
    message = f"iot_sensors,device_id={device_id},location={location} " \
              f"temperature={temperature},humidity={humidity} {timestamp}\n"
    
    sock.sendto(message.encode(), ('localhost', 4567))
    sock.close()

# Simulated sensor readings from multiple devices
for device_id in range(100):
    report_sensor_reading(
        device_id=f"device_{device_id}",
        location=f"zone_{device_id % 10}",
        temperature=20 + (device_id % 15),
        humidity=55 + (device_id % 20)
    )

Monitoring

Since UDP provides no feedback, monitor ingestion through QuestDB:
-- Check row count
SELECT count() FROM sensors;

-- Check latest timestamp
SELECT max(timestamp) FROM sensors;

-- Monitor ingestion rate
SELECT 
    timestamp,
    count() OVER (ORDER BY timestamp RANGE BETWEEN 1 SECOND PRECEDING AND CURRENT ROW) AS rows_per_second
FROM sensors
WHERE timestamp > now() - INTERVAL '1' MINUTE
ORDER BY timestamp DESC;

When to Use UDP

Telemetry and monitoring - Metrics where 99% accuracy is acceptable
High-frequency sensors - IoT devices sending frequent updates
Application logs - Non-critical log data for observability
Network-constrained environments - Minimal overhead important

When to Use TCP Instead

Financial data - Transactions, trades, orders require guaranteed delivery
Critical events - Alarms, alerts, audit logs must not be lost
Authentication needed - UDP has no built-in authentication
Error feedback required - Application needs to know about rejected data