You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Unified management of scalar time-series and binary file data (LiDAR scans, camera images, audio, point clouds, model checkpoints) through the QuestDB client. The client routes files to object storage and scalar data to QuestDB, while QuestDB tracks file metadata and manages lifecycle. On egress, the client resolves file references in query results and fetches files from object storage transparently.
The application sends all data through one API. The client handles routing, storage, synchronization, and retrieval.
The Problem
Physical AI systems produce two fundamentally different kinds of data:
Type
Examples
Size per item
Rate
Natural storage
Scalar telemetry
Joint angles, temperatures, forces, voltages
Bytes
1 kHz+
Database
Dense binary
LiDAR point clouds, camera frames, audio, depth maps, radar returns
KB – MB
10–60 Hz
Object storage
Today these are managed separately. Teams build custom pipelines: ROS bags → scripts → S3 + metadata in Postgres. MCAP files → custom indexer → data lake + separate TSDB. This takes months and the result is fragile — file references break, retention isn't coordinated, temporal correlation requires glue code.
Uploads to object store (local directory on edge, S3/GCS/Azure remotely)
Writes metadata row to QuestDB:
INSERT INTO lidar_scans (ts, robot_id, object_path, size_bytes, content_type, format, frame_id, num_points)
VALUES (ts, 'robot-7', 's3://bucket/lidar_scans/robot-7/2026-01-15/...pcd', 262144, 'application/pcd', 'pcd', 'base_link', 65536)
The metadata table is a regular QuestDB table. Compound sort keys (#119), retention policies (#100), ASOF JOIN — everything applies to it.
File routing is explicit
The application calls send() for scalar data and send_file() for binary data. No content-type detection or size-based heuristics. The application knows what its data is.
#108 (native SQL drivers) delivers high-performance binary protocol for scalar query results. But when a query result contains object_path columns referencing files in object storage, the consumer still has to:
Parse the paths from the result set
Open a separate connection to the object store
Download each file individually
Correlate files back to their result rows
The client should handle this transparently.
File-resolving query mode
# Regular query — returns object_path as stringsresults=client.query("SELECT * FROM lidar_scans WHERE robot_id = 'robot-7'")
# results[0].object_path → "s3://bucket/lidar_scans/robot-7/..."# File-resolving query — fetches files, returns bytesresults=client.query_with_files(
"SELECT * FROM lidar_scans WHERE robot_id = 'robot-7'",
resolve=["object_path"] # columns to resolve
)
# results[0].object_path → bytes(...) (actual file contents)
Client batch-fetches files from object store (parallel, connection pooling)
Client returns unified result: scalar values + file bytes in one result set
Training data assembly
The most important use case — assembling multi-modal training datasets:
# One query produces a complete training datasetresults=client.query_with_files(""" SELECT j.ts, j.position, j.velocity, j.torque, l.object_path AS lidar_path, c.object_path AS camera_path FROM joint_telemetry j ASOF JOIN lidar_scans l ON (j.robot_id = l.robot_id) ASOF JOIN camera_front c ON (j.robot_id = c.robot_id) WHERE j.episode_id = 'ep_0047' ORDER BY j.ts""", resolve=["lidar_path", "camera_path"])
# Each row has: scalar telemetry + aligned LiDAR bytes + aligned camera bytesforrowinresults:
training_sample= {
"state": [row.position, row.velocity, row.torque],
"lidar": decode_pointcloud(row.lidar_path),
"image": decode_jpeg(row.camera_path),
}
Without this, assembling multi-modal training data requires custom ETL pipelines, S3 listing scripts, and manual temporal alignment. With this, it's one SQL query.
Data Logistics
Edge to cloud file sync
On the edge device, files are written to local storage first (same store-and-forward pattern as #109 for scalar data):
Edge device:
send_file() → local staging dir → async upload → remote object store
→ metadata row → local QuestDB → sync → remote QuestDB
Concern
Approach
Local staging
Files written to local directory before upload
Async upload
Background thread uploads to remote object store when connected
Retry on failure
Track upload state per file, retry from staging on reconnect
Priority
Table-level priority from metadata (#109) applies — safety camera frames before routine LiDAR bulk
Bandwidth awareness
Large files are chunked; upload pauses when bandwidth is needed for high-priority scalar data
Local cleanup
Delete local staging files after confirmed upload to remote
Object store abstraction
The client abstracts over storage backends:
Environment
"Object store" is
Config
Edge (disconnected)
Local directory
file:///data/objects/
Edge (connected)
Direct S3/GCS upload
s3://bucket/
Cloud
S3/GCS/Azure Blob
s3://bucket/
Development
Local directory
file:///tmp/questdb-objects/
Metadata-driven file organization
Object paths are deterministic and derived from table metadata:
Object store lifecycle rules aligned with QuestDB retention
Bulk operations by prefix (delete all files for a robot, a date, a table)
External tools can browse the object store and understand the layout
Retention Cascading
When QuestDB drops metadata rows (via retention policies #100), the corresponding files in object storage must also be cleaned up:
-- Retention policy on metadata table-- Dropping rows triggers file deletion from object storeALTERTABLE lidar_scans SET STORAGE POLICY (
DROP LOCAL 7d, -- delete local files after 7 days
DROP REMOTE 90d -- delete from object store after 90 days
);
Event
Database action
Object store action
DROP LOCAL TTL expires
Drop partition from local QuestDB
Delete files from local staging
DROP REMOTE TTL expires
Drop metadata rows
Delete files from object store
Table dropped
Drop all metadata
Delete all files under table prefix
Manual DELETE
Remove metadata rows
Queue file deletion
The database is the single source of truth for lifecycle. No orphaned files, no dangling references.
Incident replay — Query all data around a failure event: scalar telemetry from QuestDB, camera footage and LiDAR scans from object storage, temporally aligned.
Fleet-wide model evaluation — "Get all camera frames where the vision model confidence was below 0.5" — filter on scalar metrics, retrieve associated files.
Autonomous vehicle data management — Petabytes of camera, LiDAR, radar data with metadata in QuestDB. Query by route, time, conditions. Download matching data for retraining.
Industrial inspection — Thermal images, acoustic recordings, vibration waveforms with timestamps and equipment IDs. Query anomalous readings, retrieve associated scans.
Defense / aerospace — Sensor fusion data from multiple modalities with coordinated lifecycle and access control.
Summary
Unified management of scalar time-series and binary file data (LiDAR scans, camera images, audio, point clouds, model checkpoints) through the QuestDB client. The client routes files to object storage and scalar data to QuestDB, while QuestDB tracks file metadata and manages lifecycle. On egress, the client resolves file references in query results and fetches files from object storage transparently.
The application sends all data through one API. The client handles routing, storage, synchronization, and retrieval.
The Problem
Physical AI systems produce two fundamentally different kinds of data:
Today these are managed separately. Teams build custom pipelines: ROS bags → scripts → S3 + metadata in Postgres. MCAP files → custom indexer → data lake + separate TSDB. This takes months and the result is fragile — file references break, retention isn't coordinated, temporal correlation requires glue code.
Architecture
Ingestion: send_file()
Client API
What the client does for send_file()
The metadata table is a regular QuestDB table. Compound sort keys (#119), retention policies (#100), ASOF JOIN — everything applies to it.
File routing is explicit
The application calls
send()for scalar data andsend_file()for binary data. No content-type detection or size-based heuristics. The application knows what its data is.Egress: file-aware queries
The gap in current egress (#108)
#108 (native SQL drivers) delivers high-performance binary protocol for scalar query results. But when a query result contains
object_pathcolumns referencing files in object storage, the consumer still has to:The client should handle this transparently.
File-resolving query mode
How it works
Training data assembly
The most important use case — assembling multi-modal training datasets:
Without this, assembling multi-modal training data requires custom ETL pipelines, S3 listing scripts, and manual temporal alignment. With this, it's one SQL query.
Data Logistics
Edge to cloud file sync
On the edge device, files are written to local storage first (same store-and-forward pattern as #109 for scalar data):
Object store abstraction
The client abstracts over storage backends:
file:///data/objects/s3://bucket/s3://bucket/file:///tmp/questdb-objects/Metadata-driven file organization
Object paths are deterministic and derived from table metadata:
This structure enables:
Retention Cascading
When QuestDB drops metadata rows (via retention policies #100), the corresponding files in object storage must also be cleaned up:
DROP LOCALTTL expiresDROP REMOTETTL expiresThe database is the single source of truth for lifecycle. No orphaned files, no dangling references.
Use Cases
Related