Use Case

Ingest, store, and stream sensor data in one place

IoT and metrics pipelines always fragment into three separate systems. Lux collapses the entire stack into native time-series commands with built-in aggregation and live streaming. One binary, one connection, zero glue code.

The Problem

IoT and metrics pipelines always end up as three separate systems: something to ingest the data (a custom service or Kafka), something to store and query it (InfluxDB or TimescaleDB), and something to stream live updates to dashboards (a WebSocket server or Grafana with polling). That is three services to deploy, three to monitor, three that can fail independently. And when you need to correlate time-series data with application state, you are writing cross-service queries that are brittle and slow. Every new sensor type means touching the ingestion layer, the storage schema, and the streaming config. Teams spend more time plumbing infrastructure than building the features their data should power.

Without Lux

Kafka / Custom Service

Data ingestion

InfluxDB / TimescaleDB

Time series storage

WebSocket Server

Live data streaming

Grafana

Visualization + polling

4 services to maintain, deploy, and monitor.

vs

With Lux

Just Lux

Ingest + store + stream + aggregate

One binary. One connection string.

Getting Started

import { Lux } from "@luxdb/sdk"

const db = new Lux(process.env.LUX_DIRECT_URL ?? "lux://localhost:6379")

await db.tsadd("sensor:temp:floor1", "*", 22.5, {
  labels: { building: "hq", floor: "1", type: "temperature" }
})

await db.tsadd("sensor:temp:floor1", "*", 23.1)

const hourlyAvg = await db.tsrange("sensor:temp:floor1", "-", "+", {
  aggregation: { type: "avg", bucketSize: 3600000 }
})

Ingest with labels, query with server-side aggregation. Three lines.

How It Works

Lux's time-series engine (TSADD/TSRANGE) handles ingestion and historical queries natively. Every data point gets a timestamp, either auto-generated with * or explicit, and is stored in a memory-efficient append-only structure optimized for range scans. There is no separate ingestion layer because the write path is the storage engine.

Labels let you tag data points for cross-series filtering. TSMRANGE queries across multiple time series by label, so you can ask "give me the average temperature for all sensors on floor 3" without knowing the key names ahead of time. Labels are indexed at write time, not scanned at query time.

Aggregation functions (avg, sum, min, max, count, std) run server-side so you are not pulling raw data over the wire. A query for hourly averages over a week of per-second data returns 168 data points instead of 604,800. Your dashboard stays fast and your network stays quiet.

Because KSUB fires events on every write, you can stream new readings to dashboards in real time without a separate service. Subscribe to sensor:* and every new data point triggers a callback. Time-series storage + realtime streaming + application state in one binary.

Full Pipeline

import { Lux } from "@luxdb/sdk"

const db = new Lux(process.env.LUX_DIRECT_URL ?? "lux://localhost:6379")

async function ingestSensor(sensorId, building, floor, type, value) {
  await db.tsadd(`sensor:${type}:${sensorId}`, "*", value, {
    labels: { building, floor, type, sensor: sensorId }
  })
}

await ingestSensor("t-001", "hq", "1", "temperature", 22.5)
await ingestSensor("t-002", "hq", "2", "temperature", 24.1)
await ingestSensor("h-001", "hq", "1", "humidity", 45.2)

const allTemps = await db.tsmrange("-", "+", {
  filter: { type: "temperature", building: "hq" },
  aggregation: { type: "avg", bucketSize: 3600000 }
})

const floor1Humidity = await db.tsmrange("-", "+", {
  filter: { type: "humidity", floor: "1" },
  aggregation: { type: "max", bucketSize: 60000 }
})

const sub = db.ksub(["sensor:*"], (event) => {
  pushToDashboard(event.key, event.operation)
})

Ingest from multiple sensors, query across series by label, stream live updates via KSUB.

Feature Deep-Dive

Native Time Series

TSADD and TSRANGE are first-class commands in Lux, not a plugin or module. Ingest timestamped data with automatic or explicit timestamps and query any range with sub-millisecond latency. The storage engine is optimized for append-heavy workloads and sequential range scans, exactly how sensor data behaves.

Label-Based Filtering

Tag every data point with arbitrary key-value labels at write time. TSMRANGE queries across all series matching a label filter, so you can slice data by building, floor, sensor type, region, or any dimension without restructuring keys. Labels are indexed, not scanned, so filtering stays fast at scale.

Realtime Dashboard Streaming

KSUB fires a callback every time a matching key is written. Subscribe to sensor:* and every new reading triggers your handler instantly. No polling, no separate WebSocket service, no Grafana refresh intervals. Your dashboards update the moment data lands.

Performance

18M

ops/sec on TSGET

6

built-in aggregation functions

avg / sum / min / max / count / std

KSUB

live streaming on every write

Star on GitHub