Skip to content

Delivery truth for QuestDB

Choose a tag to compare

@github-actions github-actions released this 15 Sep 05:43
· 6 commits to main since this release

💥 Breaking

  • localhost is refused in every adapter address: ilpUrl, pgUrl,
    brokerUrl, and the variables QUESTDB_ILP_URL, QUESTDB_PG_URL,
    and MQTT_BROKER_URL. The name can stand for two addresses, and a
    service may answer on only one. A flow that uses it fails at
    definition with INVALID_CONFIG. A variable that uses it stops the
    process at import. The QuestDB defaults move from localhost to
    127.0.0.1:9000 and 127.0.0.1:8812. Action: write the literal
    address, 127.0.0.1. pgUrl and brokerUrl also take a bracketed
    IPv6 literal such as [::1]:8812. ilpUrl does not, because the
    QuestDB client cannot read one.
  • maxBufSize now sets the client's byte ceiling, max_buf_size. It
    used to set the initial buffer size, init_buf_size. Action: use
    the new initBufSize for the old meaning.
  • The QuestDB onDeliveryFailure report has a new shape. The handler
    now receives ( err, { trigger, rowsLost, abandoned, probe } ),
    once per failed send. trigger is rows, timer, or recovery.
    The old keys idleFlush and recovery are gone. Action: read
    trigger instead.
  • handle.shutdown() now rejects when a sink loses buffered data at
    drain, with the sink's error, err.code, and err.dropped.count.
    It used to resolve over the loss, so a process could exit 0 over
    lost rows. Action: catch the rejection when your program calls
    shutdown() itself. The signal path exits 1 over the same loss.
  • The MQTT emitter factory, when called directly, now always returns
    a promise, and a configuration error rejects it with
    INVALID_CONFIG. It used to return the handle at once at
    connectGraceMs: 0 and throw. Flows are unaffected. Action:
    await the factory and catch the rejection.
  • A custom emitter's handle must have flush(), as a storage handle
    already must. Wiring refuses a handle without it. Action: add
    flush(). A trivial emitter can resolve at once.

✨ Features

  • Composer starts every QuestDB send itself, and the client's own
    trigger is off. A write that fills the buffer to flushRows starts
    a send at once, and a timer sends whatever is buffered every
    flushIntervalMs. One send runs at a time, so health and pressure
    report exact counts.
  • Four new QuestDB options, each with a QUESTDB_* variable:
    flushRows, flushIntervalMs, bufferCeilingRows, and
    flushDeadlineMs. When the rows buffered plus the rows in flight
    reach the ceiling, write() refuses new rows with STORAGE_FULL.
    The ceiling defaults to ten times flushRows and must be at least
    twice it.
  • Every send has a deadline, and delivery pauses while QuestDB is
    unreachable. After a failed or abandoned send, the adapter probes
    ilpUrl. While the probe fails, delivery stays paused and each
    interval probes again. When one passes, a single send carries
    everything held. So a restart costs only the batch on the wire when
    the port closed. Before, a 30-second restart lost every row written
    during it.
  • getHealth() on the QuestDB adapter now reads delivery. One failed
    send reads yellow. Two in a row, one abandoned, or a pause read
    red with connected: false. The next delivered send reads
    green. Five fields join the report: consecutiveFlushFailures,
    lastFlushAt, lastFlushError, pausedSince, and
    abandonedFlushes.
  • Each change of delivery state prints one line, with or without an
    onDeliveryFailure handler. The codes are DELIVERY_HEALTH for
    the ladder, CIRCUIT_OPEN for a pause or resume, and
    STORAGE_FULL for shedding. Nothing repeats while a state
    persists. The restored line names the outage length and the rows
    reported lost.
  • The QuestDB transport fails fast by default. stdlibHttp: true
    (QUESTDB_STDLIB_HTTP=on) selects the client's standard-library
    HTTP transport, whose requests always end. The client's own
    default, undici, retries a refused connection without end. A send
    into a stopped server then hung and held the process open. New
    requestTimeout (QUESTDB_REQUEST_TIMEOUT) and initBufSize
    (QUESTDB_INIT_BUF_SIZE) join the schema.
  • The QuestDB adapter checks pgUrl and ilpUrl at setup, with one
    TCP connect per address the name resolves to. An address that does
    not answer throws TRANSPORT_UNREACHABLE, and the message lists
    each address with its result. Every adapter warns once per address,
    with ADDRESS_IS_NAME, on any host name.
  • The MQTT source prints one line at every change of its health, with
    or without an onStatus handler. Yellow prints at warn, red at
    error, and the return to green at warn with the episode length.
  • Every repeating fault line is bounded: two lines in full per
    episode, then one summary a minute. That covers DELIVERY_FAILED,
    CALLBACK_FAILED, CONNECT_FAILED, the QuestDB default
    onWarning, and the MQTT source's decode and transform faults. A
    dead sensor that sends NaN all night costs two lines, then one a
    minute.
  • The MQTT emitter prints one DELIVERY_HEALTH line per change of
    the broker link. onCritical fires once when pressure climbs past
    80% and re-arms below 66%. Both MQTT adapters add a random share of
    up to 20% to the reconnect period, drawn once at startup. So a
    fleet that lost one broker does not retry in step.
  • The handbook gains sections on the address rule, MQTT client
    names, and the limits of the MQTT source. It also covers the yield
    rule for tight loops and the QuestDB delivery lines.

🐛 Fixes

  • A dead QuestDB endpoint no longer loses rows in silence while
    health reads green. In a long soak run, localhost stopped
    resolving to the answering address after 32 hours. The write path
    was then lost for four hours with no report. The refusal, the
    probes, the deadlines, and the delivery ladder above close it
    together.
  • A flow with several persistIf nodes on one storage built the
    adapter once per node. Each orphan kept a timer, an HTTP agent, and
    a sender for the life of the process. The storage is now built once.
  • .source(), .emitter(), and .storage() now throw
    INVALID_CONFIG when an adapter's schema rejects a config, in place
    of a plain error with no code.
  • The QuestDB red health line waited for a failed send that a paused
    delivery never starts. A pause is now the red edge, so the lines
    read degraded, red, paused, resumed, restored.
  • An empty recovery send after a mid-row throw stamped a delivery
    that never happened, so health read green over a poison write. It
    now stays red.
  • The MQTT source's stop() could leave the socket open, so a pending
    connect or a hung broker held the process. The socket now detaches
    at once, or a timer destroys it at the deadline. The MQTT emitter's
    shutdown got the same fix.
  • Two MQTT sources started in the same millisecond got the same
    generated client name, and the broker disconnected the older one.
    The name now carries a random part.
  • An MQTT emitter options.type of 'constructor' could poison the
    connection through the expiry table's prototype. The table has no
    prototype now.
  • Durations now read a stopwatch clock that a wall-clock step cannot
    move. Before, an NTP step after boot could fire the MQTT source's
    30-second red early. It could also expire every dedup entry at
    once, or report an hour for a five-second outage.
  • A decode failure on the MQTT source reports the topic and the byte
    count, never the payload text, which a parser's message can echo.
  • A dedup id that is not a string now bypasses the cache and counts
    in dedupBypassed.
  • tablePrefix must be an identifier, because QuestDB reads the
    unquoted table name as one token.
  • The environment validator printed QUESTDBRETRYTIMEOUT for
    QUESTDB_RETRY_TIMEOUT in its error line.
  • The handbook said the MQTT emitter keeps a persistent session. The
    session has been clean in every public release, and the page now
    says so.

⚙️ Updates

  • Five QuestDB options are deprecated and will be removed in 0.8.0:
    flushMode, idleFlushAfterMs, idleFlushCheckMs, autoFlushRows,
    and autoFlushIntervalMs, with their QUESTDB_* variables. Until
    then autoFlushRows maps to flushRows, and idleFlushCheckMs
    maps to flushIntervalMs. The other three are accepted and ignored.
    Setup prints one DEPRECATED_OPTION line naming the keys in use.
  • @questdb/nodejs-client is pinned to ~4.2.0. The adapter depends
    on the client's retry lists and timeouts.