Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 11 additions & 11 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ members = [
exclude = ["fuzz"]

[workspace.package]
version = "0.8.2"
version = "0.8.3"
edition = "2021"
license = "Apache-2.0"
rust-version = "1.88"
Expand Down
4 changes: 4 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,10 @@ helm-lint:
--set queue.imagePullPolicy=Never
! helm template rustqueue deploy/helm/rustqueue \
--set queue.kodoCompatibility.cleanupEnabled=true
! helm template rustqueue deploy/helm/rustqueue \
--set queue.kodoCompatibility.enabled=true \
--set queue.imagePullPolicy=Never \
--set queue.publishAckMode=nsq_relaxed
! rg -n 'x-kubernetes-preserve-unknown-fields:[[:space:]]*false' \
deploy/helm/rustqueue/crds

Expand Down
108 changes: 56 additions & 52 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,17 @@
[![Kubernetes](https://img.shields.io/badge/kubernetes-1.28%2B-326CE5.svg)](https://kubernetes.io/)

[Architecture](docs/architecture/share-nothing-v7.md) ·
[NSQ performance boundaries](docs/architecture/nsq-performance.md) ·
[Kubernetes operations](docs/operations/kubernetes.md) ·
[Console operations](docs/operations/console.md) ·
[v0.8.2 release](https://github.com/SamuelSupe/rustqueue/releases/tag/v0.8.2)
[v0.8.3 release](https://github.com/SamuelSupe/rustqueue/releases/tag/v0.8.3)

RustQueue 0.8.2 is a Kubernetes-native, NSQ V2-compatible message queue for
RustQueue 0.8.3 is a Kubernetes-native, NSQ V2-compatible message queue for
trusted internal networks. It is written in Rust and uses a deliberately
simple share-nothing model: each Broker owns one durable RWO PVC, while
Kubernetes provides scheduling, rollout and discovery.

> Current release: [v0.8.2](https://github.com/SamuelSupe/rustqueue/releases/tag/v0.8.2).
> Current release: [v0.8.3](https://github.com/SamuelSupe/rustqueue/releases/tag/v0.8.3).
> RustQueue is a production candidate for workloads that accept single-PVC
> durability and at-least-once delivery. It does not replicate messages between
> Brokers and is not an HA replacement for a replicated log.
Expand All @@ -29,7 +30,7 @@ The complete architecture and reliability contract is documented in

| Area | Contract |
| --- | --- |
| Durability | `PUB`/`MPUB`/`DPUB` return only after local segment `fsync`; `FIN`/`REQ` use a durable channel WAL |
| Durability | Default `PUB`/`MPUB`/`DPUB` return after local segment `fsync`; opt-in `write_ack` and `nsq_relaxed` return after append with explicit crash-loss windows; `FIN`/`REQ` use a durable channel WAL |
| Delivery | At least once; a restart may redeliver a message without a durable `FIN` |
| Compatibility | NSQ V2 core commands, lookup, standard Stats fields, TLS/mTLS, AUTH, Snappy, Deflate, fan-out and ephemeral channels |
| Kodo | Default-off compatibility profile: stable publish Gateways from `/nodes`, real Broker owners from `/lookup`, and no upstream Kodo change |
Expand All @@ -43,56 +44,45 @@ messages stored on that Broker are lost. Configure disk pressure protection,
monitor the exported metrics, and choose PVC/storage failure policies that fit
your workload before deploying to production.

## What's new in 0.8.2

- **NSQ-aligned no-Channel durability.** A Topic with no durable Channel now
persists its unrouted start position and normal GC cannot cross it. The first
durable Channel receives every acknowledged publish from that interval, even
when creation happens after the bootstrap window or a Broker restart.
- **Direct-Broker preflight.** Reproducible OrbStack tooling compares the exact
`v0.8.1` tag with one candidate commit using fresh volumes, fixed
2 vCPU / 2 GiB limits and alternating paired runs. RustQueue 0.8.2 completed
short correctness and regression preflights but does not claim completion of
the optional 60-run performance qualification.
- **Bounded Channel coalescing.** The durable Channel worker now keeps
collecting `FIN` and `REQ` requests throughout its existing bounded 1 ms
window instead of committing at the first transient queue gap. The
64-request ceiling, channel WAL `fsync` boundary and at-least-once contract
are unchanged. The mechanism and short preflight are not a formal throughput
guarantee.
- **Reliable benchmark shutdown and warmup.** The benchmark preserves a
partially read NSQ frame while closing consumers, and a consumer warmup is
fully drained before measurement. Missing, duplicate or non-drained delivery
remains a hard failure.
- **Explicit regression policy.** Raw write and end-to-end sustainable
throughput fail only when a one-sided paired 95% bootstrap interval is
wholly below `0.95`. Fixed-rate PUB ACK p99 and comparable fixed-rate peak
RSS fail only when the interval is wholly above `1.10`.

The patch keeps disk format v7 and remains wire-compatible with the NSQ/Kodo
contract from 0.8.1. See the
[v0.8.2 release notes](https://github.com/SamuelSupe/rustqueue/releases/tag/v0.8.2)
for the validation boundaries.

## Download 0.8.2
## What's new in 0.8.3

- **Deadline-indexed delivery.** Channel and TCP-session leases now use an
ordered deadline index instead of rescanning every in-flight delivery on
each fetch or session event. `TOUCH`, `FIN`, `REQ`, completion, and disconnect
update the same index, so high-RDY consumers avoid stale timer buildup.
- **NSQ scheduler parity.** NSQ uses an in-flight priority queue; RustQueue now
matches that scheduler shape while keeping token-checked at-least-once
delivery and the durable Channel WAL acknowledgement boundary.
- **Durability-aware comparison.** Benchmark documentation now distinguishes
RustQueue's acknowledgement-after-fsync semantics from NSQ diskqueue writes
and its optional memory queue. `--sync-every=1` is reported as an NSQ write
profile, not as an equal durability claim.
- **No format migration.** The disk format remains v7, and the NSQ/Kodo wire
contract is unchanged from 0.8.2.

See the [v0.8.3 release notes](https://github.com/SamuelSupe/rustqueue/releases/tag/v0.8.3)
and [NSQ performance boundaries](docs/architecture/nsq-performance.md) for
the contract and benchmark interpretation.

## Download 0.8.3

Every release contains native Linux binaries, the Console UI, source, the Helm
Chart and a checksum manifest:

| Asset | Contents |
| --- | --- |
| `rustqueue-0.8.2-linux-x86_64.tar.gz` | Linux x86_64 binaries, Console UI and example configuration |
| `rustqueue-0.8.2-linux-aarch64.tar.gz` | Linux ARM64 binaries, Console UI and example configuration |
| `rustqueue-0.8.2-source.tar.gz` | Source archive for the tagged commit |
| `rustqueue-0.8.2.tgz` | Helm Chart |
| `SHA256SUMS-0.8.2` | SHA-256 checksums for every downloadable artifact |
| `rustqueue-0.8.3-linux-x86_64.tar.gz` | Linux x86_64 binaries, Console UI and example configuration |
| `rustqueue-0.8.3-linux-aarch64.tar.gz` | Linux ARM64 binaries, Console UI and example configuration |
| `rustqueue-0.8.3-source.tar.gz` | Source archive for the tagged commit |
| `rustqueue-0.8.3.tgz` | Helm Chart |
| `SHA256SUMS-0.8.3` | SHA-256 checksums for every downloadable artifact |

```sh
arch="$(uname -m)"
curl -LO "https://github.com/SamuelSupe/rustqueue/releases/download/v0.8.2/rustqueue-0.8.2-linux-${arch}.tar.gz"
curl -LO "https://github.com/SamuelSupe/rustqueue/releases/download/v0.8.2/SHA256SUMS-0.8.2"
sha256sum --check --ignore-missing SHA256SUMS-0.8.2
tar -xzf "rustqueue-0.8.2-linux-${arch}.tar.gz"
curl -LO "https://github.com/SamuelSupe/rustqueue/releases/download/v0.8.3/rustqueue-0.8.3-linux-${arch}.tar.gz"
curl -LO "https://github.com/SamuelSupe/rustqueue/releases/download/v0.8.3/SHA256SUMS-0.8.3"
sha256sum --check --ignore-missing SHA256SUMS-0.8.3
tar -xzf "rustqueue-0.8.3-linux-${arch}.tar.gz"
```

## Architecture
Expand All @@ -111,7 +101,10 @@ consumer -> discovery /lookup -> every broker that owns the topic
operator -> eligible nodes -> StatefulSet ordinal + retained RWO PVC
```

- A successful `PUB`, `MPUB`, or `DPUB` has passed local segment `fsync`.
- In the default `durable` mode, a successful `PUB`, `MPUB`, or `DPUB` has
passed local segment `fsync`. `write_ack` returns after append but delays
consumption until background fsync; `nsq_relaxed` returns and exposes the
append immediately. Both relaxed modes can lose their unsynced tail.
- Concurrent publishes to one topic use a bounded group commit (up to 64
requests or 8 MiB, with at most 1 ms coalescing delay) and all wait for the
same durable boundary before receiving `OK`.
Expand Down Expand Up @@ -230,7 +223,7 @@ kubectl label node worker-1 rustqueue.io/eligible=true

helm upgrade --install rustqueue deploy/helm/rustqueue \
--namespace rustqueue --create-namespace \
--set queue.image=registry.example/rustqueue:0.8.2 \
--set queue.image=registry.example/rustqueue:0.8.3 \
--set queue.storageClassName=ssd-rwo
```

Expand Down Expand Up @@ -467,7 +460,7 @@ test-only direct Pod placement; production anti-affinity is unchanged. A unit
fixture covers discovery indexing for 500 brokers. No 500-broker deployment or
load test is part of the functional gate.

The v0.8.2 CI/CD workflow publishes a Release only after the non-Kubernetes
The v0.8.3 CI/CD workflow publishes a Release only after the non-Kubernetes
release gate, both native Linux builds, packaging and checksum verification
succeed. The v0.8.0 Kodo compatibility baseline additionally passed the
unmodified Kodo source replay, an exact 104,857,500-byte `PUB`/`DPUB` with one
Expand Down Expand Up @@ -546,14 +539,25 @@ transfers only small revision/readiness heads; nevertheless a consumer still
needs one connection per actual Topic owner. This is a share-nothing cost, not
an unbounded or zero-cost scaling claim.

Latency histograms cover publish and channel-WAL fsync, publish and FIN/REQ
group-commit queueing, publish and channel ACK, payload reads, scrub/GC, proxy
backend calls, and discovery registry polling. Queue aggregates have fixed
cardinality by default; `[metrics].detailed_queue_metrics` enables bounded
Latency histograms cover publish and channel-WAL fsync, Topic-lock wait/hold
for publish and delivery reservation, publish and FIN/REQ group-commit
queueing, publish and channel ACK, payload reads, scrub/GC, proxy backend
calls, and discovery registry polling. Queue aggregates have fixed cardinality
by default; `[metrics].detailed_queue_metrics` enables bounded
per-topic/channel series up to `max_detailed_series`.
Delivery-budget bytes, waiters and cumulative waits are exported as bounded
aggregate gauges/counters.

`[queue].publish_ack_mode` defaults to `"durable"`: successful publish commands
follow local segment `fsync`. `"write_ack"` returns after append while consumers
remain bounded by `last_durable_position`; `"nsq_relaxed"` also exposes the
appended tail immediately. Background sync runs at the first of
`relaxed_sync_messages`, `relaxed_sync_bytes`, or `relaxed_sync_interval_ms`.
`rustqueue_publish_unsynced_messages`, `rustqueue_publish_unsynced_bytes`, and
`rustqueue_publish_sync_lag_seconds` expose the crash-loss window, with bounded
per-Topic variants when detailed metrics are enabled. A sync failure stops new
writes. Keep both relaxed profiles separate from durable-PUB results.

## Storage and upgrades

RustQueue 0.8 keeps disk format v7. Format v7 is a clean break: a v6 or older
Expand Down
8 changes: 4 additions & 4 deletions benchmarks/qualifications/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@ qualification. Run it on OrbStack from a committed candidate:
make benchmark-qualify
```

The default protocol compares the exact `v0.8.1` tag with `HEAD`, uses fresh
The default protocol compares the exact `v0.8.2` tag with `HEAD`, uses fresh
Docker volumes, fixes Broker and load-generator containers at 2 vCPU / 2 GiB,
and runs all three cases as 10 alternating pairs. A full run writes the
reviewable artifact to `v0.8.2-orbstack.json`. Consumer cases must drain
completely and have a fixed 1,800-second timeout so the v0.8.1 durable `FIN`
reviewable artifact to `v0.8.3-orbstack.json`. Consumer cases must drain
completely and have a fixed 1,800-second timeout so the v0.8.2 durable `FIN`
baseline is not rejected merely for exceeding a short operational timeout.

The RustQueue 0.8.2 release does not make this optional 60-run artifact a
The RustQueue 0.8.3 release does not make this optional 60-run artifact a
release metadata requirement. Short development preflights can detect hard
correctness failures and obvious regressions, but they do not substantiate a
formal performance claim.
Expand Down
2 changes: 1 addition & 1 deletion console-ui/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "rustqueue-console-ui",
"private": true,
"version": "0.8.2",
"version": "0.8.3",
"packageManager": "pnpm@11.9.0",
"type": "module",
"scripts": {
Expand Down
4 changes: 4 additions & 0 deletions crates/console/src/model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,10 @@ mod tests {
message_count: 1,
segment_count: 1,
segment_bytes: 128,
last_durable_position: 1,
unsynced_messages: 0,
unsynced_bytes: 0,
sync_lag_ms: 0,
channels: Vec::new(),
});
let mut head = BrokerObservationHead {
Expand Down
21 changes: 19 additions & 2 deletions crates/operator/src/controller/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1366,10 +1366,20 @@ fn validate(cluster: &RustQueue, active_feature_floor: u32) -> anyhow::Result<()
|| cluster.spec.max_topics == 0
|| cluster.spec.max_publish_workers == 0
|| cluster.spec.publish_worker_idle_seconds == 0
|| (cluster.spec.publish_ack_mode != "durable"
&& (cluster.spec.relaxed_sync_messages == 0
|| cluster.spec.relaxed_sync_bytes < 4096
|| cluster.spec.relaxed_sync_interval_ms == 0))
|| cluster.spec.max_detailed_metric_series == 0
{
bail!("queue limits are outside the stable v7 contract");
}
if !matches!(
cluster.spec.publish_ack_mode.as_str(),
"durable" | "write_ack" | "nsq_relaxed"
) {
bail!("publishAckMode must be durable, write_ack, or nsq_relaxed");
}
validate_message_storage_contract(
cluster.spec.max_message_bytes,
effective_storage_feature_level(cluster.spec.storage_feature_level, active_feature_floor),
Expand All @@ -1381,13 +1391,14 @@ fn validate(cluster: &RustQueue, active_feature_floor: u32) -> anyhow::Result<()
|| cluster.spec.bootstrap_retention_seconds < KODO_BOOTSTRAP_RETENTION_SECONDS
|| cluster.spec.max_message_bytes != 100 * 1024 * 1024
|| cluster.spec.connection_delivery_inflight_bytes < 128 * 1024 * 1024
|| cluster.spec.node_delivery_inflight_bytes < 512 * 1024 * 1024)
|| cluster.spec.node_delivery_inflight_bytes < 512 * 1024 * 1024
|| cluster.spec.publish_ack_mode != "durable")
{
bail!(
"Kodo compatibility requires exactly 3 brokers, storageFeatureLevel 2, \
bootstrapRetentionSeconds >= 180, \
maxMessageBytes 104857600, connectionDeliveryInflightBytes >= 134217728, \
and nodeDeliveryInflightBytes >= 536870912"
nodeDeliveryInflightBytes >= 536870912, and publishAckMode durable"
);
}
if !(630..=86_400).contains(&cluster.spec.kodo_compatibility.cutover_grace_seconds) {
Expand Down Expand Up @@ -2037,5 +2048,11 @@ mod tests {
.unwrap_err()
.to_string()
.contains("bootstrapRetentionSeconds >= 180"));
cluster.spec.bootstrap_retention_seconds = KODO_BOOTSTRAP_RETENTION_SECONDS;
cluster.spec.publish_ack_mode = "nsq_relaxed".into();
assert!(validate(&cluster, 2)
.unwrap_err()
.to_string()
.contains("publishAckMode durable"));
}
}
4 changes: 4 additions & 0 deletions crates/operator/src/controller/status.rs
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,10 @@ mod tests {
max_topics: 100,
max_publish_workers: 32,
publish_worker_idle_seconds: 60,
publish_ack_mode: "durable".into(),
relaxed_sync_messages: 2_500,
relaxed_sync_bytes: 8 * 1024 * 1024,
relaxed_sync_interval_ms: 10,
detailed_queue_metrics: false,
max_detailed_metric_series: 1_000,
registry_secret_name: None,
Expand Down
Loading
Loading