O(log N) range queries
Binary search on sorted columns. 100-1000x speedup for selective range queries on million-row partitions.
Range queries are the bread and butter of time-series analytics. A quant running WHERE price BETWEEN 15000 AND 16000 on a million-row partition shouldn’t scan all million rows when only 1% match. This post covers how ZeptoDB’s s# sorted column attribute eliminates that waste — and how connection hooks bring session-level observability to the HTTP layer.
ZeptoDB partitions are columnar and append-only. Timestamps are always sorted (by construction), so timestamp_range() already used binary search. But every other column — price, quantity, sequence number — required a linear scan through all N rows, even when the result set was tiny.
For a 1M-row partition with 1% selectivity, that’s 990,000 wasted row evaluations.
kdb+‘s s# attribute marks a vector as sorted, enabling bin (binary search) for range lookups. ZeptoDB now supports the same concept at the partition level:
// Mark a column as sortedpartition.set_sorted("price");
// Check attributebool sorted = partition.is_sorted("price"); // O(1) lookup
// Binary search for range [lo, hi)auto [begin, end] = partition.sorted_range("price", 15000.0, 16000.0);// Uses std::lower_bound + std::upper_bound internallyThe sorted_columns_ set is a lightweight metadata annotation — no additional data structure is built. The column data itself is already in a contiguous array, so std::lower_bound and std::upper_bound operate directly on it with O(log N) comparisons.
The executor’s exec_simple_select calls extract_sorted_col_range(), which scans the WHERE clause AST for patterns that can exploit sorted order:
col BETWEEN lo AND hicol >= lo AND col <= hi (any combination of >=, >, <=, <, =)SQL: SELECT * FROM trades WHERE price BETWEEN 15000 AND 16000 AND side = 'B'
AST analysis: → price is s# sorted → extract bounds: [15000, 16000] → sorted_range("price", 15000, 16000) → row range [42000, 52000)
Execution: → eval_where_ranged() on rows 42000..52000 only → remaining predicate (side = 'B') evaluated within pruned rangeInstead of scanning 1,000,000 rows, the engine evaluates ~10,000 — the binary search itself touches only ~20 rows (log₂ 1M ≈ 20).
| Scenario | Without s# | With s# | Speedup |
|---|---|---|---|
| 1M rows, 1% selectivity | 1,000,000 rows scanned | ~10,000 rows scanned | ~100x |
| 1M rows, 0.1% selectivity | 1,000,000 rows scanned | ~1,000 rows scanned | ~1000x |
| 1M rows, 100% selectivity | 1,000,000 rows scanned | 1,000,000 rows scanned | 1x (no benefit) |
The speedup scales inversely with selectivity. For the narrow range queries common in financial analytics — “show me trades in this price band in the last 5 minutes” — the improvement is dramatic.
exec_agg, exec_agg_parallel) do not yet use sorted_rangeOR / NOT conditions cannot be optimized with a single contiguous rangeg# (hash index) and p# (parted index) are planned but not yet implementedkdb+ users rely on .z.po (port open) and .z.pc (port close) callbacks for audit logging, rate limiting, and session cleanup. ZeptoDB now provides equivalent hooks at the HTTP layer.
Since httplib operates over HTTP/1.1 (not raw TCP), true socket-level events aren’t available. Instead, we track logical sessions by remote_addr:
struct ConnectionInfo { std::string remote_addr; std::string user; int64_t connected_at_ns; int64_t last_active_ns; uint64_t query_count;};
server.set_on_connect([](const ConnectionInfo& ci) { ZEPTO_INFO("Session opened: {}", ci.remote_addr);});
server.set_on_disconnect([](const ConnectionInfo& ci) { ZEPTO_INFO("Session closed: {} (queries={})", ci.remote_addr, ci.query_count);});remote_addrConnection: close header or when evict_idle_sessions() removes a stale sessionGET /admin/sessions returns the active session list:
[{ "remote_addr": "127.0.0.1:54321", "connected_at_ns": 1742000000000000000, "last_active_ns": 1742000001000000000, "query_count": 3}]O(log N) range queries
Binary search on sorted columns. 100-1000x speedup for selective range queries on million-row partitions.
Zero overhead annotation
s# is metadata only — no secondary index structure. Works directly on the existing columnar array.
Session lifecycle hooks
on_connect / on_disconnect callbacks with ConnectionInfo. /admin/sessions endpoint for operational visibility.
20 new tests
13 sorted index tests (range, exact match, boundary conditions) + 7 connection hook tests. 467/467 total passing.
Related: SIMD JIT Optimization → · Parallel Query Engine → · Composite Index →