Skip to content

v2.6.0

Choose a tag to compare

@bpacholek bpacholek released this 15 Jul 09:32

A performance-focused minor release: three throughput/efficiency improvements to the JetStream, KV, Object Store, and request/reply paths. No breaking API changes (one account-permission note below).

Added

  • Configurable transport read chunk sizeNatsOptions::$readChunkSizeBytes (default 128 KiB, up from Amp's 8 KiB) caps a single socket read on both the TCP and WebSocket transports, so large inbound payloads (e.g. an Object Store download) arrive in far fewer chunks — dividing the per-chunk syscall + fiber-spawn + parser-push overhead ~8–32×. It raises only the maximum a read may return; small-message behavior is unchanged (#119).
  • readIncoming() on NatsClient/NatsConnection exposes one read-and-dispatch cycle as an IncomingChunkResult (frame count plus whether bytes were consumed) — the progress signal the wait loops use. processIncoming() is unchanged (#119).

Changed (all performance, behavior-compatible)

  • Muxed request inbox (#118)request()/requestMany() now share ONE long-lived wildcard subscription _INBOX.<base>.* per connection instead of a fresh subscribe + unsubscribe per call; each request publishes reply-to _INBOX.<base>.<token> and the reply is routed back by token. This removes two control-plane frames and a server-side interest change per request — a throughput win for JetStream, KV, Object Store, and BatchPublisher, which all funnel through request(). Reply delivery, timeouts, no-responders handling, cancellation, and requestMany semantics are unchanged; a delayed/duplicate reply for a completed request can never reach a later one, and the shared reply queue is exempt from the slow-consumer drop. Permission note: the mux inbox subscribes to a wildcard under the inbox prefix, so an account permissioned to subscribe only to exact _INBOX.<...> subjects (not _INBOX.>) will have the mux subscription rejected and request/reply will time out — grant _INBOX.> subscribe permission (the same requirement as nats.go's muxed responder; see #167).
  • Batched, chunked KV/Object Store enumeration (#110)keys()/listKeys() enumerate names via a headers-only last_per_subject consumer instead of downloading every value; getAll() and Object Store list() fetch every record with one batched multi_last Direct Get (ADR-31) on NATS 2.11+ (with a version-gated fallback to the per-subject fan-out), chunked by the server's 1024-result cap and by max_payload so large buckets are enumerated across several requests rather than one the server would reject.
  • Read-path idle-sleep removed on partial-frame progress (#119) — the read-path wait loops (flush(), drain(), request(), requestMany(), JetStream pull-fetch and direct-get-batch, KeyValueBucket::history()/keys(), SubscriptionQueue, Service::run()) no longer pay a 1 ms idle sleep per partial socket chunk. A frame spanning N chunks previously cost ~N ms (~125 ms/MB at the old 8 KiB default); the loops now sleep only on a genuinely idle read. Delivery is byte-for-byte identical.

Full CI green: PHPStan level 8, unit tests on PHP 8.2–8.5, E2E + coverage on a live NATS 2.12 server, examples, and ≥90% mutation MSI.