QuestDB Enterprise 4.0.1 is a patch release on the 4.0.0 line. It carries two user-visible additions — OIDC sign-in via the OAuth 2.0 device flow and a retryable, deadline-aware shutdown contract that now spans Enterprise role-switch, WAL-transfer and backup teardown — plus cold-storage support for Azure Blob object metadata and a replication fix for an idle rebased table that never settled in the uploader index. The bundled OSS engine advances seven commits past 10.0.1 (the 10.0.2-SNAPSHOT line; there is no OSS 10.0.2 release), two of which are breaking changes inherited from the engine that should be reviewed before upgrading.
Breaking Changes
An overflowing
INTexpression now wraps modulo 2^32 in every context, including under a 64-bit cast and on the way into a 64-bit column, so anINTexpression carries exactly one value at its declared type. To compute at 64 bits, widen an operand, for exampleto_utc(secs * 1_000_000L, tz)ori::long * j. Consequences of this rule:WHERE secs * 1_000_000 > <timestamp>,(i*j)::longandINSERTinto a 64-bit column all read the wrapped value; inserting an overflowingINTexpression into a designated timestamp now fails withdesignated timestamp before 1970-01-01 is not allowed;SELECT <overflowing constant INT arithmetic>returnsINTrather thanLONG, so over the PostgreSQL Wire Protocol the column descriptor moves fromint8toint4andCREATE TABLE ... AS SELECTcreates anINTcolumn;INTarithmetic landing on -2^31 reads asNULLeverywhere; window frames,WINDOW JOINbounds,SAMPLE BYintervals andFROM/TObounds, andDROP/DETACH/CONVERT PARTITIONWHEREfilters use the final wrapped value instead of inspecting arithmetic provenance;long_sequence(100_000 * 30_000)wraps negative and returns no rows;dateadd('u', 3600*1000000, ts)computes from the wrapped stride instead of failing overload resolution; and timestamp predicates andUPDATEfilters acceptINTexpressions and sign-extend the wrapped value, with a WALUPDATEwhoseWHEREclause fails intrinsic extraction now failing synchronously at sequencing time instead of suspending the table at apply. Constant reassociation is also disabled for numeric constant pairs, so a chain such asi + 1 + 4evaluates left to right at its declared width, which changesEXPLAINoutput and costs throughput on Java-evaluated constant arithmetic. Alongside the rule, this change fixes a large family of defects:HORIZON JOINandWINDOW JOINwrong results including a constant-falseWINDOW JOINreading an unrelated column on a Parquet master, undercounted past-only keyed windows, lazily bound owner group-by functions, splicedNULLcolumn handling forDECIMAL, varchar andLONG256, and keyed join maps escapingcairo.query.memory.limit.bytes;LATEST ONsilently dropping an indexed-symbolWHEREfilter and pushing aLIMITbelow the operator;NULLIFreturning the value it had just excluded andINT/IPv4 casts evaluating their argument twice;json_extractanswering different values at different widths and casts toSYMBOLclosing a symbol table their caller still owned;INover integer keys widening, leaking or mis-ordering elements; numerous JIT compiled-filter crashes, fail-open native backends and width divergences against the Java filter; an inclusive tolerance boundary shared by the compiled filter and Parquet row-group pruning; Parquet row-group pruning false-pruning numeric, non-finite and column-top rows; timestamp offset pushdown dropping rows for month and year offsets, wrapped shifts and hand-writtenand_offsetcalls; aNullPointerExceptionfrom'x' || concat('y'); and several factory-construction and interval-model resource leaks. Performance work amortizes the keyedWINDOW JOINslave index (up to 2000 rebuilds down to 17), restores vectorization for mixed-width JIT filters, and makes the expression reassociation and JIT width-marking passes linear rather than quadratic.
New Features
This feature lets deployment controllers and embedders bound a single shutdown attempt without freeing objects that a worker, lifecycle component, or engine extension may still be using.
ServerMain.closeBy(long deadlineNanos)takes an absolute deadline on the monotonicSystem.nanoTime()clock and returnstruewhen shutdown completed and the close graph was released, orfalsewhen the deadline or an interrupt stopped the attempt, in which case every resource that may still be in use remains owned for a later retry with a fresh deadline; completed stages stay idempotent andclose()keeps the existing terminal behaviour. Concurrent callers do not tear down in parallel — one caller owns shutdown while others wait up to their own deadlines, restoring any consumed interrupt status — and unexpected component failures still propagate because an explicit incomplete-shutdown signal is used rather than classifying arbitrary exceptions as timeouts. One deadline flows through boot completion, lifecycle pre-stop work, component stops, worker-pool halts, timer shards, engine readiness, and logging, with final resource release beginning only after those stages complete; components first stop admitting work and request cooperative cancellation,WorkerPool.haltBy(long)accepts the shared absolute deadline whilehaltWithin(long)remains for relative budgets, a worker pool counts down startup and halt latches for workers thatstart()never created so rollback cannot wait on impossible acknowledgements, andCairoEngine.isCloseReady(long)gives engine extensions a pre-free phase after worker pools stop. The deadline bounds only QuestDB's coordination and cooperative waits, so it cannot forcibly stop application or native code that ignores cancellation, and an incomplete attempt intentionally retains memory, file descriptors and native handles in exchange for memory safety; existing no-argument shutdown entry points and legacy worker-pool descriptors remain available, and deadline checks run only on startup rollback and shutdown paths.
Improvements
This improvement extends the deadline-aware shutdown contract to Enterprise lifecycle code, so a single monotonic absolute deadline flows through role-switch rendezvous, role-state close, lifecycle components, worker pools, WAL transfer, the engine pre-free phase, and retryable backup teardown. A successful stage releases its ownership, while an incomplete stage returns a retryable incomplete-shutdown signal and keeps every still-live Java and native owner so a later attempt can resume with a fresh deadline. During shutdown, restore and backup cancellation are signalled before waiting for long-running work, the lifecycle orchestrator coordinates with any in-flight role switch and repeatedly requests cooperative cancellation while time remains, and the final close graph is released only after the engine reports readiness, which prevents a role switch or native task from observing engine-owned state after the engine frees it. Native backup teardown closes admission first and then uses the deadline remainder to acquire its teardown write lock and drain the Rust manager, rounding the native wait down to whole milliseconds so conversion cannot extend the caller's deadline; on timeout the native handles, Tokio runtime, backup agent and scheduler state are retained for retry, and restore cancellation is mapped to a structured
RestoreCancelledExceptionso a shutdown-requested cancellation ends boot cleanly while an unrequested one preserves the original failure. A bounded timeout can therefore leave the server stopped but still holding Java and native resources until a retry or the terminal process-exit policy reclaims them, cancellation is cooperative rather than forced, and deployment termination grace must coverrole.switch.timeoutplus the Enterprise teardown reserve when the shutdown hook should finish in-process.
Bug Fixes
The uploader's notified-transaction watermark was overwritten by every transaction notification instead of only being raised. Because events are not delivered in transaction order — a rebased table can drain data for transactions 1 and 2 followed by a table-created event carrying transaction 0 — a trailing lower value dragged the watermark down, so the transaction log reader was never remapped and an idle rebased table retried forever instead of settling in the uploader index. This fix only raises the watermark in the drain path, allowing the uploader to tolerate any event order while leaving the rebase ordering unchanged.
A queue segment published its
frozenForEnqueuesflag before bumping the tail by the freeze offset, while the empty check intryDequeue()reads the flag before the tail. In the window between the two writes a dequeuer could pair a set flag with the pre-freeze tail and subtract the freeze offset from a tail that was never bumped, making the frozen clause always true and reporting the queue empty while it still held committed items; the full fence only ordered visibility, so the race occurred on x86 as well as ARM. This fix bumps the tail before publishing the flag, so a set flag always implies a bumped tail and a clear flag with a bumped tail simply retries, adding no additional synchronization. It also repairs the sizedConcurrentQueueconstructor, which validated itssizeargument and then ignored it, always allocating a 32-slot first segment.This fix addresses two symbol-map correctness issues that could produce invalid symbol mappings. Previously,
SymbolMapReaderImpl.updateSymbolCount()removed the wrong cache range when the visible symbol count shrank and leftmaxOffsetat the previous high-water mark, sokeyOf()could return a key outside the current symbol count; the symbol cache andmaxOffsetare now shrunk together with the visible symbol count. Additionally,WalTxnDetails.saveSymbols()relied on an assertion to require dense Write-Ahead Log symbol diff keys, which production builds do not enforce, allowing sparse or invalid diffs to pass through without a useful diagnostic. Symbol diff keys are now validated at runtime, and if a diff is sparse, Write-Ahead Log apply reports the relevant transaction and symbol metadata and suspends the table instead of applying a potentially inconsistent mapping, favoring containment over continuing with an invalid mapping. The validation adds a single integer comparison per new symbol diff entry on valid input and no per-row work, but a suspended table requires operator intervention.