# Apache Iggy 0.9.0 Release

> Apache Iggy 0.9.0 brings VSR clustering, per-topic durability, SDK and connector updates, and a first look at performance work for the next release.

Published: 2026-09-21

Rendered page: https://iggy.apache.org/blogs/2026/09/21/release-0.9.0/

Source: https://github.com/apache/iggy-website/blob/main/content/blog/release-0.9.0.mdx

## Release 0.9.0
We are proud to announce the release of **[Apache Iggy 0.9.0](https://iggy.apache.org/downloads/)**.
This release marks a historical milestone for the project: it is our very first as an official **Apache Top-Level Project** following our [graduation](https://iggy.apache.org/blogs/2026/08/24/apache-iggy-top-level-project-tlp-graduation/) from the Incubator on August 19, 2026. This isn't just another version increment; it is the flagship release we have been relentlessly building toward over the past year. And we can’t wait for you to try it out.

The official release is the **[source archive](https://www.apache.org/dyn/closer.lua/iggy/0.9.0/iggy-0.9.0-src.tar.gz)** distributed by the Apache Software Foundation, with its [PGP signature](https://downloads.apache.org/iggy/0.9.0/iggy-0.9.0-src.tar.gz.asc), [SHA-512 checksum](https://downloads.apache.org/iggy/0.9.0/iggy-0.9.0-src.tar.gz.sha512) and the project [KEYS](https://downloads.apache.org/iggy/KEYS). The [downloads page](https://iggy.apache.org/downloads/) explains how to verify it. The [GitHub release](https://github.com/apache/iggy/releases/tag/server-0.9.0) carries the full list of merged pull requests.

**Iggy now runs as a cluster.** 0.9.0 ships **Viewstamped Replication Revisited (VSR)** consensus in the standard `iggy-server` binary. Start three nodes, lose one, and the cluster keeps serving reads and writes. There is no separate build, feature flag, or clustered edition. The same server runs single-node or replicated, and `cluster.enabled` in the configuration decides which one you get.

This is the biggest release in the project's history: **over 600 merged pull requests from 60 contributors, 34 of them contributing to Iggy for the first time.** Alongside the server, we're releasing the Rust SDK 0.11.0, the CLI 0.14.0, the connectors runtime 0.5.0, the MCP server 0.5.0, the Web UI 0.4.0, the bench dashboard 0.8.0, and the Python 0.9.0, Node.js 0.10.0, Java 0.9.0, C# 0.9.0 and Go 0.9.0 SDKs, all from the same repository. Beyond clustering, 0.9.0 brings **per-topic durability policies**, an extensible **topic options** catalog, a new **PHP SDK**, **eight new connectors** and an **HTTP webhook source**, the foundation of a **Kafka protocol gateway** for existing Kafka clients, a redesigned **benchmarks dashboard**, cluster-aware **Helm charts**, and hundreds of fixes and performance improvements across the server, SDKs and connectors.

Before the changelog, two topics deserve their own space: **clustering** and **performance**.

---

## Clustering with VSR consensus

For three releases the roadmap section of these posts has ended the same way: complete the clustering implementation with VSR consensus. 0.7.0 laid the consensus foundations. 0.8.0 added the shard crate, the plane abstractions, the persistent WAL journal, and the experimental `iggy-server-ng` binary. In 0.9.0 that work became the server. **[server-ng was promoted to the Apache Iggy server](https://github.com/apache/iggy/pull/3856)**, the `vsr` feature flag is gone, the legacy server and its wire format were deleted, and every SDK and CI lane runs against the replicated server. Single-node deployment remains the default, and clustering is opt-in through the `[cluster]` section of the configuration.

### What a cluster gives you

Apache Iggy replicates with **[Viewstamped Replication Revisited](https://github.com/apache/iggy/blob/master/assets/vsr.pdf)**. VSR keeps an ordered state machine consistent across replicas and elects a new primary when the current one fails:

- **Normal operation**: the primary assigns an operation number, sends `Prepare`, and commits after a quorum answers `PrepareOk`.
- **View change**: when replicas stop hearing from the primary, they exchange `StartViewChange` and `DoViewChange`, and the new primary sends `StartView`. Failover needs no operator.
- **Recovery**: a restarted or lagging replica probes the current view and repairs the missing WAL ranges from its peers before it serves current state.

**Three replicas tolerate one unavailable replica, five tolerate two.** Three nodes is the recommended minimum for a highly available deployment. A two-node cluster is useful for development, but it cannot make progress after either node fails.

Replication is split by namespace rather than run as one global log:

| Plane | Consensus group | Replicated state |
| --- | --- | --- |
| Metadata | One group on shard 0 | Streams, topics, users, permissions, consumer groups, access tokens |
| Partition | **One group per partition** | Messages and consumer offsets |

Each group runs its own consensus and elects its own primary. Metadata writes route through the metadata primary, and partition writes route through the primary of that partition. Reads are served from the local replicated state on any node, followers included.

### How it was built

The consensus work spans well over a hundred pull requests. The milestones, in roughly the order they landed:

- **[Consensus per partition](https://github.com/apache/iggy/pull/3071)** and the **[consensus group mechanism](https://github.com/apache/iggy/pull/3138)** made every partition its own replicated state machine
- **[Session registration with combined login and register](https://github.com/apache/iggy/pull/3108)**, later **[reworked so sessions resume across restarts](https://github.com/apache/iggy/pull/3753)**, backed by the client table for at-most-once request handling
- **[Async fire-and-forget message bus](https://github.com/apache/iggy/pull/3134)** for consensus traffic, plus **[QUIC, TCP-TLS, WebSocket and WSS transports](https://github.com/apache/iggy/pull/3192)** for clients
- **[Replica bootstrap](https://github.com/apache/iggy/pull/3163)** and **[multi-shard cross-shard communication](https://github.com/apache/iggy/pull/3269)**, so all shards of a node take part in consensus
- **[Partition reconciliation loop](https://github.com/apache/iggy/pull/3436)**, **[partition background tasks](https://github.com/apache/iggy/pull/3564)**, and **[replica rejoin via view probe and message repair](https://github.com/apache/iggy/pull/3667)**
- **[Metadata VSR state persisted in a durable superblock](https://github.com/apache/iggy/pull/3767)**, **[state transfer for the metadata snapshot and client table](https://github.com/apache/iggy/pull/3765)**, and **[state transfer for partitions](https://github.com/apache/iggy/pull/3808)**, which let a wiped replica refill itself from its peers
- **[Committed sends confirmed with partition and offset](https://github.com/apache/iggy/pull/3781)**, so producers learn exactly where their batch landed
- **[Deduplication of partition writes with per-group client table slices](https://github.com/apache/iggy/pull/3959)**, so a produce retried after a timeout does not write twice
- **[Follower HTTP requests forwarded to the primary](https://github.com/apache/iggy/pull/3744)**, so any node can answer any control-plane request
- **[Inbox split into consensus and client-reply lanes](https://github.com/apache/iggy/pull/3915)**, so a burst of client traffic cannot starve replication
- **[Metadata reads served at or above the client's own writes](https://github.com/apache/iggy/pull/4024)**

Then came the correctness fixes found by the simulator, the crash tests and real cluster runs:

- **[View change no longer discards committed ops](https://github.com/apache/iggy/pull/3824)**
- **[A replica is kept off a hole in its committed prefix](https://github.com/apache/iggy/pull/4073)**
- **[Repaired prepares can't rewind the WAL parent](https://github.com/apache/iggy/pull/3978)**
- **[The dedup fence survives capacity eviction](https://github.com/apache/iggy/pull/3934)**
- **[Writes recover after a fast primary rejoin](https://github.com/apache/iggy/pull/3987)**
- **[A late partition materialiser can't wedge its group](https://github.com/apache/iggy/pull/4007)**
- **[Partition and metadata repair are driven from the tick](https://github.com/apache/iggy/pull/4006)**
- **[An unservable header counts as a nack](https://github.com/apache/iggy/pull/4109)**
- **[Frames no plane claims are refused](https://github.com/apache/iggy/pull/4103)**
- **[Torn segment tails are truncated instead of resurrected](https://github.com/apache/iggy/pull/3946)**
- **[Spurious heartbeat elections are prevented](https://github.com/apache/iggy/pull/4194)**, the last change before the tag

### Durability you choose per topic

Replication forced a precise answer to a simple question: what does an acknowledgement mean? 0.9.0 answers it with two create-only topic options, introduced in **[let topics require durable acks](https://github.com/apache/iggy/pull/4092)**:

| Policy | Required before success |
| --- | --- |
| `replicated` (default) | VSR quorum commit and local application, without an additional stable-storage barrier |
| `persisted` | VSR quorum commit backed by recoverable stable-storage copies at the required quorum |

`durability` governs message production and `consumer_offset_durability` governs explicit offset stores and deletes. They default independently and neither inherits the other. Both policies write data to disk. With `persisted`, each multi-replica partition keeps a bounded on-disk prepare WAL, sized by `partition.wal_bytes_max` (256 MiB by default), and a replica cannot release its `PrepareOk` until its message bodies, WAL history and durable frontier are recoverable. The old `enforce_fsync` topic option and the server-wide `consumer_offset_enforce_fsync` setting are gone. Every SDK, the CLI, the HTTP API and `iggy-bench` expose both options.

The details are in [Durability](https://iggy.apache.org/docs/server/durability) and [Cluster Durability](https://iggy.apache.org/docs/clustering/durability).

### Secure replica traffic

The replica-to-replica port is authenticated and, optionally, encrypted:

- **[PSK + BLAKE3 handshake](https://github.com/apache/iggy/pull/3425)**: every peer proves possession of a cluster-wide pre-shared key of at least 32 bytes through a three-message keyed-MAC handshake before it can register as a replica. A `previous_shared_secret` enables rolling key rotation without an authentication outage.
- **[TLS 1.3 on the replica plane with PSK channel binding](https://github.com/apache/iggy/pull/3461)**: the opt-in `[cluster.tls]` table wraps replica connections in TLS 1.3 (ALPN `iggy-replica`) with CA and self-signed modes. The PSK MAC absorbs the TLS exporter value, so a relay that terminates both legs fails the handshake.
- **[Cluster metadata is auth-gated and registration is forwarded to the primary](https://github.com/apache/iggy/pull/3872)**: unauthenticated clients can no longer read the roster, and a client that dials a backup completes its login there. Credentials never cross the replica interconnect.
- **[Incompatible clients are rejected at login via protocol version](https://github.com/apache/iggy/pull/3454)**

See [Cluster Security](https://iggy.apache.org/docs/clustering/security).

### Client failover

Every SDK speaks the VSR wire protocol and can connect to any node. Leader-aware clients fetch cluster metadata after login, reconnect to the advertised leader when they landed on a follower, poll for a new leader through an election, and cap the number of redirects so a flapping roster cannot bounce them forever:

- Rust: **[fail over to a surviving node when the current one dies](https://github.com/apache/iggy/pull/3944)**, **[rejoin consumer groups after membership loss](https://github.com/apache/iggy/pull/3703)**, **[heartbeat handle](https://github.com/apache/iggy/pull/3852)**
- Java: **[cluster metadata and leader redirection in the TCP client](https://github.com/apache/iggy/pull/3745)**
- C#: **[leader redirection BDD scenario](https://github.com/apache/iggy/pull/3193)**
- Rust, Go, C# and Java run the shared `leader_redirection` BDD suite against a real cluster

Writes return after the required VSR commit, or return a **retryable** error while a replica is changing view or catching up. `TransientNotAccepted` proves the request was never admitted and permits a retry on another node. `TransientNotCommitted` has an uncertain outcome and is replayed with the same session and request identity, and the server-side dedup takes care of the rest. Details in [Client Failover](https://iggy.apache.org/docs/clustering/client-failover).

Per-node **[advertised addresses](https://github.com/apache/iggy/pull/3757)** and **[per-client-network address selectors](https://github.com/apache/iggy/pull/3818)** let a node publish different endpoints to clients on different networks, which matters for containers and cloud deployments.

### Running a cluster

Every node loads the same configuration and is told apart only by `--replica-id`. Save this configuration as `iggy-vsr.toml`:

```toml
[cluster]
enabled = true
name = "iggy-vsr-dev"

[cluster.auth]
enabled = true
shared_secret = ""

[[cluster.nodes]]
name = "iggy-node-1"
ip = "127.0.0.1"
replica_id = 0
ports = { tcp = 8090, quic = 8080, http = 3000, websocket = 8093, tcp_replica = 9090 }

[[cluster.nodes]]
name = "iggy-node-2"
ip = "127.0.0.1"
replica_id = 1
ports = { tcp = 8091, quic = 8081, http = 3001, websocket = 8094, tcp_replica = 9091 }

[[cluster.nodes]]
name = "iggy-node-3"
ip = "127.0.0.1"
replica_id = 2
ports = { tcp = 8092, quic = 8082, http = 3002, websocket = 8095, tcp_replica = 9092 }
```

Set the shared configuration path, root credentials and replica authentication secret in each terminal. Use the same credentials and secret for all three nodes. Root credentials are required on the first boot, and the secret must contain at least 32 bytes:

```bash
export IGGY_CONFIG_PATH="$PWD/iggy-vsr.toml"
export IGGY_ROOT_USERNAME=iggy
export IGGY_ROOT_PASSWORD="replace-with-your-root-password"
export IGGY_CLUSTER_AUTH_SHARED_SECRET="replace-with-at-least-32-random-bytes"
```

Start each node in a separate terminal:

```bash
IGGY_PATH=local_data/node-0 iggy-server --replica-id 0
IGGY_PATH=local_data/node-1 iggy-server --replica-id 1
IGGY_PATH=local_data/node-2 iggy-server --replica-id 2
```

Every roster field can also be set through typed `IGGY_CLUSTER_*` environment variables, which is what the new **[Helm chart cluster mode](https://github.com/apache/iggy/pull/4035)** renders. The **[Deploy a Cluster](https://iggy.apache.org/docs/clustering/deploy)** guide walks through the whole setup, and the **[Configuration](https://iggy.apache.org/docs/clustering/configuration)** reference covers every `[cluster]` key, including consensus timing (`heartbeat_timeout`, view change and repair intervals, all on a fixed 10ms tick).

### Testing a consensus protocol

We trust this implementation because of how much effort went into breaking it. Most of that effort is deterministic simulation:

- **[Seed-based workload fuzz harness](https://github.com/apache/iggy/pull/3272)** and the **[workload fuzzer with consensus oracles](https://github.com/apache/iggy/pull/3591)**: randomized, fully seed-deterministic workloads run against a predictive shadow, crash replicas, and assert consensus invariants on every tick and at quiesce. Every failure reproduces from its seed.
- **[Deterministic future executor and server dispatch shell](https://github.com/apache/iggy/pull/3671)**: the real server dispatch code runs inside the simulator
- **[Workloads under network and replica faults](https://github.com/apache/iggy/pull/3942)**: message loss, delays, partitions, crashes with stability windows, client resends with target rotation. This one alone found two real bugs before any user could.
- **[Durability failures no existing test could reach](https://github.com/apache/iggy/pull/4132)**: a storage model injects crashes, power loss and torn writes into the WAL tests
- **[SIGKILL crash-durability cluster tests](https://github.com/apache/iggy/pull/3905)** and **[client table durability across node restart](https://github.com/apache/iggy/pull/3736)** on real processes
- **[On-disk storage format breaks caught pre-merge](https://github.com/apache/iggy/pull/4003)**: a baseline data directory boots against every pull request
- **[Miri undefined behavior detection](https://github.com/apache/iggy/pull/3284)** for the `binary_protocol` and `consensus` crates
- Cross-SDK BDD suites running against a real two-node cluster

The [Viewstamped Replication](https://iggy.apache.org/docs/clustering/vsr) page is the entry point to all of this.

---

## Performance

Efficiency has always been the point of Iggy: thread-per-core, shared-nothing, `io_uring`, and no copies where the format allows it. 0.9.0 carried that into the replicated server and shipped a series of hot-path improvements:

- **[Poll replies served without copying record bytes](https://github.com/apache/iggy/pull/4025)**: a poll reply used to be three copies plus a memset, roughly four times the payload in memory traffic. It is now a 272-byte head fragment followed by the journal or disk fragments as they are, written with a single `writev`.
- **[Persisted-mode disk traffic per acknowledgment cut](https://github.com/apache/iggy/pull/4146)**: publishing the WAL frontier took three barriers on three inodes per batch. It is now two slots in one file, overwritten in place, and the body and WAL barriers land in one journal window.
- **[Log and index persisted concurrently](https://github.com/apache/iggy/pull/3970)**: the two serialized `fdatasync` calls per flush became one `join`. On the fsync-gated benchmark used in that pull request, throughput went from 13.4k to 26.1k msg/s and p50 from 5.83 to 3.01 ms.
- **[Batch checksum computed in a single produce pass](https://github.com/apache/iggy/pull/3746)**, **[user headers decoded without copying the blob](https://github.com/apache/iggy/pull/3708)**, **[message header written directly into the target buffer](https://github.com/apache/iggy/pull/3707)**
- **[Sealed-segment read handles capped with a per-partition LRU](https://github.com/apache/iggy/pull/3806)**, and the active segment no longer pays one `openat` per poll
- **[16 MiB per active segment index no longer reserved](https://github.com/apache/iggy/pull/3974)**, **[consumer offset state bounded per partition](https://github.com/apache/iggy/pull/4063)**
- **[Unnecessary backpressure under concurrent clients eliminated](https://github.com/apache/iggy/pull/3692)**, **[produce and poll overhead reduced in cluster mode](https://github.com/apache/iggy/pull/4169)**
- **[TCP-TLS and WSS connections distributed across shards](https://github.com/apache/iggy/pull/4193)**
- **[mimalloc restored as the global allocator](https://github.com/apache/iggy/pull/3971)**
- **[CPU affinity and cgroup limits respected](https://github.com/apache/iggy/pull/3615)** in shards and stats, so a container gets the shard count it was given
- Foreign SDKs **[generate message ids from random bytes instead of UUIDs](https://github.com/apache/iggy/pull/4076)**, and the Go SDK **[pools its request-path wire payload](https://github.com/apache/iggy/pull/3442)**

### Benchmarks: 0.9.0 and what comes next

We're not done. A dedicated performance effort started right after the 0.9.0 code freeze, and the first results are in. The tables below compare **the 0.9.0 release against a development snapshot**, both measured with `iggy-bench` on the same host with identical workload parameters. Each version ran its own build of `iggy-bench`, because the development client sends the new deferred poll. These results therefore compare the server and client together. Each cell reads `0.9.0 → next`, where *next* is the development snapshot. Latencies are in milliseconds. *p50* is the median, *p99* the 99th percentile, and *p999* the 99.9th percentile.

**Durability legend**

- **Replicated** (`durability=replicated`, the default for producer rows without a `persisted` label): a successful write waits for quorum commit and local application, but does not wait for stable storage. Acknowledged data can still be in memory.
- **Persisted** (`durability=persisted`): a successful write also requires recoverable copies on stable storage at the replication quorum. On a three-node cluster, that means at least two replicas.

Both policies write data to disk. On a single node, the quorum is one: *replicated* provides no extra copy, while *persisted* waits for local stable storage. These labels describe producer acknowledgements. Consumer rows measure reads. See [Durability](https://iggy.apache.org/docs/server/durability) for the full guarantees.

**Workload legend**

The comparisons below cover rate-limited workloads.

- **Pinned producer / consumer**: each actor owns its own stream and partition. *Actors* counts concurrent producers or consumers.
- **800 MB/s / 500 MB/s**: the configured aggregate rate limit. *Achieved MB/s* is the measured throughput, which can be lower. Compare latency alongside achieved throughput.
- **Cold / warm**: consumers read uncached data or re-read cached data, respectively.

**Chart: Replicated vs. Persisted.** Side-by-side panels compare the 0.9.0 release and the next-version development snapshot. Each panel shows Replicated and Persisted producer latency on the same linear scale starting at zero. Controls select single node or 3-node cluster and p50, p99 or p999. All four measurements use 20 pinned producers with an aggregate rate limit of 800 MB/s. Replicated waits for quorum commit and local application. Persisted also requires recoverable stable-storage copies at the quorum. The full measurements appear in the expandable tables below.

**Chart: latency, 0.9.0 versus a development snapshot, per iggy-bench workload.** Three headline tiles (3-node cluster, persisted, 20 producers at p999 and p99, and single node, 20 producers at p999), then an interactive chart limited to workloads with lower p50, p99 and p999 latency, with two dots per row on a log scale, one for the 0.9.0 release and one for the development branch that follows it. It switches between single node and 3-node cluster, between p50, p99 and p999, and between table order and biggest percentage reduction. Each version uses its own iggy-bench client. The expandable “Full benchmark results” section below contains all measurements, including regressions and achieved throughput.

<details className="my-6 rounded-xl border border-fd-border px-4 sm:px-6">
<summary className="cursor-pointer py-4 font-semibold text-fd-foreground">Full benchmark results: 0.9.0 → next version</summary>
<div className="min-w-0 overflow-x-auto pb-4 [&_td]:whitespace-nowrap [&_th]:whitespace-nowrap">

#### Single node: 0.9.0 → next version

Each cell shows **0.9.0 → next version with optimizations**. The next-version results are from a development snapshot.

| Workload (configured rate limit) | Achieved MB/s | p50 ms | p99 ms | p999 ms |
| --- | ---: | ---: | ---: | ---: |
| pinned producer, 20 actors, 800 MB/s | 800.1 → 800.1 | 0.650 → 0.588 | 1.313 → 1.145 | 11.087 → 2.956 |
| pinned producer, 20 actors, persisted, 800 MB/s | 800.0 → 800.1 | 1.232 → 1.098 | 1.601 → 1.424 | 2.249 → 1.967 |
| pinned consumer, 20 actors, cold, 800 MB/s | 800.1 → 800.1 | 0.498 → 0.447 | 4.698 → 3.313 | 5.156 → 3.777 |
| pinned consumer, 20 actors, warm, 800 MB/s | 800.1 → 800.1 | 0.521 → 0.544 | 1.022 → 0.923 | 1.573 → 3.920 |
| pinned producer, 1 actor, 500 MB/s | 500.0 → 500.0 | 0.347 → 0.352 | 0.679 → 0.683 | 1.356 → 1.046 |
| pinned consumer, 1 actor, warm, 500 MB/s | 500.0 → 500.0 | 0.272 → 0.296 | 0.468 → 0.478 | 0.491 → 0.502 |
| pinned producer, 1 actor, persisted, 500 MB/s | 372.1 → 335.8 | 0.665 → 0.733 | 0.825 → 0.901 | 0.995 → 1.151 |

#### 3-node cluster: 0.9.0 → next version

Each cell shows **0.9.0 → next version with optimizations**. The next-version results are from a development snapshot.

| Workload (configured rate limit) | Achieved MB/s | p50 ms | p99 ms | p999 ms |
| --- | ---: | ---: | ---: | ---: |
| pinned producer, 20 actors, persisted, 800 MB/s | 800.0 → 800.0 | 2.905 → 2.243 | 7.073 → 2.938 | 12.403 → 3.889 |
| pinned producer, 1 actor, persisted, 500 MB/s | 234.4 → 233.6 | 1.047 → 1.062 | 1.200 → 1.223 | 10.857 → 1.759 |
| pinned consumer, 20 actors, cold, 800 MB/s | 800.1 → 800.0 | 0.477 → 0.436 | 3.831 → 2.346 | 4.241 → 2.675 |
| pinned consumer, 20 actors, warm, 800 MB/s | 800.1 → 797.9 | 0.484 → 0.451 | 0.933 → 0.812 | 1.233 → 1.216 |
| pinned producer, 20 actors, 800 MB/s | 800.0 → 800.0 | 1.782 → 1.679 | 3.186 → 3.250 | 3.683 → 3.660 |
| pinned producer, 1 actor, 500 MB/s | 337.2 → 325.4 | 0.716 → 0.735 | 0.973 → 1.016 | 1.057 → 1.130 |
| pinned consumer, 1 actor, warm, 500 MB/s | 500.0 → 500.3 | 0.297 → 0.326 | 0.493 → 0.536 | 0.533 → 0.574 |

The single-node warm consumer with 20 actors saw p999 rise from 1.6 ms to 3.9 ms. Some single-actor rows also lost throughput or added latency. **These numbers are a snapshot of work in progress, not a final result.** The branch is being profiled and tuned as we write this, with further changes aimed at throughput and tail latency.

</div>
</details>

The tails are where the work shows. On a three-node cluster with `persisted` topics, the **p99 of a 20-producer workload dropped from 7.07 ms to 2.94 ms and the p999 from 12.4 ms to 3.9 ms**. The single-producer p999 on the same cluster went from 10.9 ms to 1.8 ms. On a single node, the 20-producer **p999 fell from 11.1 ms to 3.0 ms**. Cold consumer reads improved on both setups. In this development snapshot, 20 persisted producers on a three-node cluster achieved 800 MB/s with a median latency of 2.243 ms.

### Three changes behind the numbers

Three changes are in flight, and the tables above measure them together against 0.9.0. These runs do not isolate how much each change contributes. A fourth is on the drawing board.

#### Consumers stop asking for nothing

A polling consumer pays a round trip for nothing on an idle topic, and on a busy one its interval trades log latency against empty replies.

`pollMessagesDeferred` (commands 105 and 106, tracked in [#3470](https://github.com/apache/iggy/issues/3470)) appends a 24-byte trailer to the poll request: a maximum wait, a minimum message count, a byte cap for the reply, and a total request budget. The server parks the request and answers when the minimum is readable, the cap is reached, or the wait expires. The Rust client speaks it on every transport, the Java client over TCP and HTTP, and the Rust consumer now defaults to bounded long polling with manual commits. It answers a [community request for deferred responses](https://github.com/apache/iggy/discussions/2854).

#### File I/O leaves the shard loop

A shard owns many partitions and used to wait for each one's file writes and durability barriers before serving the next, so one slow disk delayed every partition on the shard.

Now each file job runs as its own task, up to 16 in flight by default, and its result returns to the shard for identity matching and ordered publication. Storage work now overlaps with network work.

#### Replica links read ahead

Every pair of nodes shares one TCP connection for consensus traffic, owned by a single link shard. Reading one frame took two `io_uring` reads, one for the 256-byte header and one for the body, and frames already in the kernel receive queue waited for the next call. Under load that shard bottlenecked the cluster write path.

A 256 KiB read-ahead buffer ([#4224](https://github.com/apache/iggy/pull/4224)) now serves a whole burst with one socket read. A frame at least as large as the buffer skips it and lands in its own allocation, so large messages pay no extra copy.

#### The next one: more replica links

The read-ahead makes each read cheaper, but every partition group a pair of nodes replicates still goes through one link shard.

The next change gives a peer several links, spread over shards. An operator picks the link count per peer, each link carries a contiguous range of shards, and the default stays at today's one. The upgrade rolls, but changing the link count takes a coordinated restart, and nodes that disagree on it refuse each other.

This one is a design, not code, and the read-ahead has to justify it first. More links only make sense when the link shard stays saturated behind the buffer. Otherwise the cheaper fix has already won.

All three measured changes are still in development, and every number above will move before they ship. A driver for the OpenMessaging Benchmark is in progress as well, and its results will follow.

Beyond these, in the order we expect them to land:

- **Explicit group commit**: several acknowledgements share one disk barrier by design, rather than by whatever happened to queue up during the previous one
- **Direct I/O** for the storage engine, writing past the page cache instead of paying for `fsync` barriers
- **Multi-leader clusters**, further out: a leader per partition, with leaders spread across all nodes of the cluster, so write traffic and replication work are shared by every machine instead of concentrating on one

And a long tail of smaller changes in the same direction. Efficiency is the reason Iggy exists, and it stays the priority now that clustering is in.

A like-for-like comparison of 0.8.0 against 0.9.0 with this parameter set is planned for the [benchmarking platform](https://benchmarks.iggy.apache.org), together with the final numbers for the next release. The [benchmarking guide](https://iggy.apache.org/docs/server/benchmarking) has the commands to reproduce these workloads on your own hardware, and the [Linux tuning guide](https://iggy.apache.org/docs/server/linux-tuning) covers the host preparation.

---

## Iggy Server

### One server, one wire protocol

- **[server-ng promoted to the Apache Iggy server](https://github.com/apache/iggy/pull/3856)**: the thread-per-core, `io_uring` server is now the only server. The `iggy-server` binary, the `server` package, the configs tree and the systemd integration are the former server-ng, and all transports are kept: TCP with TLS, QUIC, WebSocket and HTTP. The legacy server, its compat lane and the classic TCP, QUIC and WebSocket framing were deleted (**BREAKING**).
- **[Legacy message wire format and v2 bridge APIs removed](https://github.com/apache/iggy/pull/3904)** from the SDK. The new message wire format is encoded on the client side (**BREAKING**).
- **[Namespace removed from client headers](https://github.com/apache/iggy/pull/3836)**, a smaller header on every request
- **[Server-only types extracted into `server_common`](https://github.com/apache/iggy/pull/3298)**, **[shard allocator extracted into `cpu_allocation`](https://github.com/apache/iggy/pull/3578)**, **[compio executor and io_uring diagnostics extracted into a shared crate](https://github.com/apache/iggy/pull/3331)**, the server's **[module graph turned into an enforced DAG](https://github.com/apache/iggy/pull/4023)**, and the dispatch spine **[split into plane-named leaves](https://github.com/apache/iggy/pull/4026)**

### Topic options

- **[Extensible key-value options for topics, streams and users](https://github.com/apache/iggy/pull/3880)** (**BREAKING**): retention, durability and segment layout are per-topic decisions made at creation. `segment_size`, `messages_required_to_save`, `size_of_messages_required_to_save` and the new `preallocate_segments` moved from server-wide `[system.*]` keys to the topic. Unknown keys are rejected at the edge, never silently ignored, and `iggy options topic` (or `GET /options/topic`) lists what the server accepts. The server refuses to boot on the relocated configuration keys. See [Topic Options](https://iggy.apache.org/docs/server/topic-options).
- **[Durability policies](https://github.com/apache/iggy/pull/4092)** as described above (**BREAKING**: `enforce_fsync` is rejected, `[system]` is flattened into the root, and `IGGY_SYSTEM_*` variables lose the `SYSTEM_` segment)

### Storage and recovery

- **[Log and index persisted concurrently, recovery hardened](https://github.com/apache/iggy/pull/3970)**: an index is derived from the log and is never evidence about the log. Recovery drops a divergent index whole and rebuilds it from a byte-0 walk, checksumming every batch it accepts. A local persist failure now stops the whole server instead of silently killing one shard.
- **[Torn segment tails truncated instead of resurrected](https://github.com/apache/iggy/pull/3946)**, **[WAL suffix truncated above the state-transfer floor](https://github.com/apache/iggy/pull/3918)**, **[recovery slot collisions rejected](https://github.com/apache/iggy/pull/4009)**
- **[Offsets reserved before they are confirmed](https://github.com/apache/iggy/pull/3975)**, **[polls completed on the owning shard](https://github.com/apache/iggy/pull/4119)**, **[producer batches preserved across replication](https://github.com/apache/iggy/pull/3859)**
- **[Data persisted on VSR backups, with a data integrity test](https://github.com/apache/iggy/pull/3512)**
- **[Deleted partitions and topics rolled out of parent stats](https://github.com/apache/iggy/pull/4046)**, **[namespace caps enforced at admission](https://github.com/apache/iggy/pull/3907)**
- **[Expired personal access token cleaner](https://github.com/apache/iggy/pull/3448)**

### HTTP, security and operations

- **[Cluster-correct HTTP listener with per-operation RBAC](https://github.com/apache/iggy/pull/3622)**, **[HTTPS on the REST listener](https://github.com/apache/iggy/pull/3672)**, **[CORS honored](https://github.com/apache/iggy/pull/3646)**, **[Web UI served by the server](https://github.com/apache/iggy/pull/3676)**
- **[PAT limits enforced and Prometheus metrics exposed](https://github.com/apache/iggy/pull/3773)**
- **[Wildcard bind without an advertised address rejected](https://github.com/apache/iggy/pull/3923)** (**BREAKING**): a `0.0.0.0` bind says where a node listens, not where a client can reach it. Deployments that bind a wildcard must set `node.advertised_address`. The Helm chart derives it from the Service DNS name.
- **[Trusted-issuer A2A JWT verification with SDK token refresh](https://github.com/apache/iggy/pull/3626)**, **[message encryption in the replicated server](https://github.com/apache/iggy/pull/3644)**
- **[`read_servers` permission required for the system snapshot](https://github.com/apache/iggy/pull/3579)**, **[`get_user` self-read restored without `read_users`](https://github.com/apache/iggy/pull/3897)**
- **[systemd watchdog integration](https://github.com/apache/iggy/pull/3233)** for the server and the MCP server
- **[Listener, auth, shutdown and disk-poll paths hardened](https://github.com/apache/iggy/pull/3774)**, **[configuration surface hardened](https://github.com/apache/iggy/pull/3756)**, and the **[server config cleaned up after a full field audit](https://github.com/apache/iggy/pull/3895)**
- **[Sibling `IGGY` environment variables allowed](https://github.com/apache/iggy/pull/4149)**, **[deterministic environment variable name suggestions](https://github.com/apache/iggy/pull/3142)**
- **[io_uring panic from an unsupported opcode diagnosed](https://github.com/apache/iggy/pull/3589)**, **[shard startup kept off blocking fallbacks](https://github.com/apache/iggy/commit/8c4986ac545c1b75afd40b11c425d35b7fbbe058)**
- **[Partitions distributed evenly in cooperative rebalancing](https://github.com/apache/iggy/pull/3374)**
- **[Non-numeric filenames in consumer offset directories skipped instead of panicking](https://github.com/apache/iggy/pull/3135)**
- **[Iggy banner on server startup](https://github.com/apache/iggy/pull/4085)**
- Rust toolchain moved to **[1.98](https://github.com/apache/iggy/pull/3962)**, with an **[MSRV of 1.95 declared and enforced in CI](https://github.com/apache/iggy/pull/3558)**

### Breaking changes

This is a major upgrade. Read this list before moving an existing deployment:

- **Data directory**: the on-disk format changed, and there is no migration of 0.8.x data directories. Until 1.0.0, an upgrade means starting from a fresh data directory.
- **[Wire protocol](https://github.com/apache/iggy/pull/3856)**: VSR framing is the only binary wire protocol. 0.8.x clients cannot talk to a 0.9.0 server and vice versa. Upgrade the server and the SDKs together.
- **[Legacy message wire format and v2 bridge APIs removed](https://github.com/apache/iggy/pull/3904)** from the Rust SDK
- **[Server configuration](https://github.com/apache/iggy/pull/3856)**: the `[cluster]` and sharding sections were reworked. Review your `config.toml`.
- **[Topic options](https://github.com/apache/iggy/pull/3880)**: `segment_size`, `messages_required_to_save`, `size_of_messages_required_to_save` and related keys are per-topic. The server refuses to boot on the old `[system.*]` keys.
- **[Durability](https://github.com/apache/iggy/pull/4092)**: `enforce_fsync` and `consumer_offset_enforce_fsync` are rejected. `[system]` is flattened into the root of the config and `IGGY_SYSTEM_*` variables drop `SYSTEM_`. The HTTP `Iggy-Durability` header reports `replicated` or `persisted`.
- **[Cluster metadata requires authentication](https://github.com/apache/iggy/pull/3872)**: `PING` is the only pre-auth command
- **[Wildcard bind requires an advertised address](https://github.com/apache/iggy/pull/3923)**, and `cluster.nodes[*].ip` must be a literal IP
- **[Connector SDK `ConnectivityConfig` replaced by `RetryPolicy`](https://github.com/apache/iggy/pull/4104)** (Rust API only, built plugins are unaffected)

The release is available as a [signed source archive](https://iggy.apache.org/downloads/) from the Apache Software Foundation. Docker images are available on **[Docker Hub](https://hub.docker.com/u/apache?page=1&search=iggy)**: `apache/iggy:0.9.0` for the server and `apache/iggy-connect:0.5.0` for the connectors runtime.

---

## SDKs

Every SDK migrated to the VSR wire protocol and runs its test suite against the replicated server only. All of them expose the new durability options and a raw command API for custom commands.

### Rust

- **Rust client bumped to 0.11.0**
- **[VSR header framing](https://github.com/apache/iggy/pull/3254)**, the client side of the new protocol
- **[Fail over to a surviving node when the current one dies](https://github.com/apache/iggy/pull/3944)**, **[rejoin consumer groups after membership loss](https://github.com/apache/iggy/pull/3703)**, **[late offset stores no longer hit a left group](https://github.com/apache/iggy/pull/3668)**
- **[Raw requests for custom commands](https://github.com/apache/iggy/pull/3373)** and **[unknown command codes forwarded instead of rejected](https://github.com/apache/iggy/pull/3766)**, so the protocol can be extended without forking the client
- **[Consumer group polling and commits corrected in `IggyConsumer`](https://github.com/apache/iggy/pull/4034)**, **[consumer progress and error handling preserved](https://github.com/apache/iggy/pull/4151)**, **[stream terminates after consumer shutdown](https://github.com/apache/iggy/pull/3964)**
- **[`max_buffer_size` respected when merging batches](https://github.com/apache/iggy/pull/3933)**, **[error callback awaits shard flush on producer shutdown](https://github.com/apache/iggy/pull/3953)**
- **[`NonZeroIggyDuration` for retries and heartbeats](https://github.com/apache/iggy/pull/3891)**
- Documentation for **[`IggyClient`](https://github.com/apache/iggy/pull/3809)**, **[`IggyProducer`](https://github.com/apache/iggy/pull/3989)** and **[`IggyConsumer`](https://github.com/apache/iggy/pull/3913)**

### PHP (new)

A brand new SDK. **[The PHP SDK](https://github.com/apache/iggy/pull/3235)** is a native PHP extension built in Rust with `ext-php-rs`. It wraps the Rust SDK and exposes a synchronous `Iggy\Client` covering streams, topics, messages and consumer groups. It **[maps Iggy errors to typed exceptions](https://github.com/apache/iggy/pull/3382)**, ships a **[consumer message iterator](https://github.com/apache/iggy/pull/3463)** and **[examples](https://github.com/apache/iggy/pull/3368)**, and runs a **[BDD suite](https://github.com/apache/iggy/pull/3410)** with **[TLS tests in CI](https://github.com/apache/iggy/pull/3381)**. It is defined as the `apache/iggy-php` composer package, is not yet published to a package manager, and is built from source today. See the [PHP SDK documentation](https://iggy.apache.org/docs/sdk/php/intro).

### Python

Python received more pull requests than any other SDK in this release and closed most of its API gap with the Rust client:

- **[High-level producer API](https://github.com/apache/iggy/pull/4156)** and **[message partitioning strategies](https://github.com/apache/iggy/pull/3927)**
- **[TCP](https://github.com/apache/iggy/pull/3776)**, **[QUIC](https://github.com/apache/iggy/pull/3991)**, **[HTTP](https://github.com/apache/iggy/pull/3992)** and **[WebSocket](https://github.com/apache/iggy/pull/4000)** transport configuration
- Stream and topic management: **[stream listing, update, delete, purge](https://github.com/apache/iggy/pull/3701)**, **[topic listing, update, delete, purge](https://github.com/apache/iggy/pull/3572)**, **[remaining Topic fields and partitions](https://github.com/apache/iggy/pull/3623)**, **[partition management](https://github.com/apache/iggy/pull/4017)**
- Consumer groups: **[create and get](https://github.com/apache/iggy/pull/3581)**, **[delete, join and leave](https://github.com/apache/iggy/pull/3607)**, **[`poll_messages` takes a consumer](https://github.com/apache/iggy/pull/3877)**
- Users: **[user management](https://github.com/apache/iggy/pull/3695)**, **[permissions and remaining auth methods](https://github.com/apache/iggy/pull/3727)**, **[`update_user` options](https://github.com/apache/iggy/pull/4173)**
- **[User headers and origin timestamp](https://github.com/apache/iggy/pull/3613)**, **[`get_stats`](https://github.com/apache/iggy/pull/4018)**
- **[ruff configured, modern coding standards](https://github.com/apache/iggy/pull/3318)**, **[pyrefly type checking](https://github.com/apache/iggy/pull/3323)**, **[Windows wheels restored](https://github.com/apache/iggy/pull/4049)**

### Java

- **[TCP client migrated to the VSR wire protocol](https://github.com/apache/iggy/pull/3841)**
- **[Cluster metadata and leader redirection](https://github.com/apache/iggy/pull/3745)**
- **[TCP clients run one I/O thread or share a group](https://github.com/apache/iggy/pull/4096)**
- **[Wire strings sized by UTF-8 byte length](https://github.com/apache/iggy/pull/4068)**
- Java benchmark: **[CLI and resource provisioner](https://github.com/apache/iggy/pull/3159)**, **[actor and data batch generator](https://github.com/apache/iggy/pull/3218)**, **[reporting suite](https://github.com/apache/iggy/pull/3420)**
- **[Pinot connector E2E coverage](https://github.com/apache/iggy/pull/3922)**

### C\#

- **[Migrated to the VSR wire protocol](https://github.com/apache/iggy/pull/3800)**, with the **[TCP connection cleaned up afterwards](https://github.com/apache/iggy/pull/3858)**
- **[Heartbeat mechanism](https://github.com/apache/iggy/pull/3894)**
- **[Rented message polling](https://github.com/apache/iggy/pull/3250)** and **[rented payloads](https://github.com/apache/iggy/pull/3471)** for fewer allocations
- **[Encryption moved to the raw client](https://github.com/apache/iggy/pull/3639)**, **[missing metrics in `StatsResponse`](https://github.com/apache/iggy/pull/3221)**, **[integer properties made unsigned](https://github.com/apache/iggy/pull/4027)**
- **[UTF-8 byte count for string validation](https://github.com/apache/iggy/pull/4072)**, **[OS default socket buffer size](https://github.com/apache/iggy/pull/4013)**

### Go

- **[Migrated to the VSR wire protocol](https://github.com/apache/iggy/pull/3834)**
- **[`context.Context` on client methods](https://github.com/apache/iggy/pull/2964)**
- **[Transport state separated from session state](https://github.com/apache/iggy/pull/3652)**, **[structured logger](https://github.com/apache/iggy/pull/3379)** aligned with the Rust SDK
- **[Request-path wire payload pooled](https://github.com/apache/iggy/pull/3442)**, **[delete segments](https://github.com/apache/iggy/pull/3191)**
- Fixed **[bounds checking for payloads over 64 KB](https://github.com/apache/iggy/pull/3165)**, **[S2 decode returning an error instead of panicking](https://github.com/apache/iggy/pull/3166)**, **[teardown of only the failed connection](https://github.com/apache/iggy/pull/3986)**, **[`MaxPayloadSize` raised to 64 MB](https://github.com/apache/iggy/pull/4080)**

### Node.js (TypeScript)

- **[VSR framing](https://github.com/apache/iggy/pull/3763)** and **[classic framing dropped](https://github.com/apache/iggy/pull/3843)**
- **[Connection string syntax](https://github.com/apache/iggy/pull/3917)**
- **[TLS with VSR](https://github.com/apache/iggy/pull/3813)**, **[scoped permissions wire codec](https://github.com/apache/iggy/pull/3642)**, **[tokens no longer dropped in list responses](https://github.com/apache/iggy/pull/4171)**
- **[npm prereleases published under a channel tag, not `latest`](https://github.com/apache/iggy/pull/3370)**
- Node.js client bumped to 0.10.0

### C++

The low-level bindings from 0.8.0 grew into a client:

- **[Messaging FFI functions](https://github.com/apache/iggy/pull/3046)**, **[system functions](https://github.com/apache/iggy/pull/3102)**, **[consumer groups and topics](https://github.com/apache/iggy/pull/3506)**, **[offsets](https://github.com/apache/iggy/pull/3559)**, **[client lifecycle and message headers](https://github.com/apache/iggy/pull/3610)**
- **[User management and an initial high-level client](https://github.com/apache/iggy/pull/3733)**, extended with **[stream, topic and partition functions](https://github.com/apache/iggy/pull/4022)**
- **[BDD tests for basic messaging](https://github.com/apache/iggy/pull/3554)**, **[Bazel upgraded to 9.1.1](https://github.com/apache/iggy/pull/3511)**

### CLI

- **CLI bumped to 0.14.0**, **[with VSR support](https://github.com/apache/iggy/pull/3679)**
- **[`context show` and `session status` commands](https://github.com/apache/iggy/pull/3096)**
- **[Pure-Rust zbus keyring instead of libdbus](https://github.com/apache/iggy/pull/3326)**, one system dependency fewer
- **[Bounded ping retries](https://github.com/apache/iggy/pull/3903)**, **[CLI input validated](https://github.com/apache/iggy/pull/4150)**

---

## Connectors

The connectors runtime, now 0.5.0, gained eight new sinks, a new source, a new payload format, and a long list of reliability fixes.

### New connectors

- **[S3 sink](https://github.com/apache/iggy/pull/3103)**
- **[ClickHouse sink](https://github.com/apache/iggy/pull/2886)**
- **[Delta Lake sink](https://github.com/apache/iggy/pull/2889)**
- **[Apache Doris sink](https://github.com/apache/iggy/pull/3215)**, with **[in-request retries](https://github.com/apache/iggy/pull/3574)** and an **[opt-in CSV output format](https://github.com/apache/iggy/pull/3575)**
- **[Meilisearch sink](https://github.com/apache/iggy/pull/3497)**
- **[SurrealDB sink](https://github.com/apache/iggy/pull/3453)**
- **[Redshift sink](https://github.com/apache/iggy/pull/3654)**
- **[RabbitMQ sink](https://github.com/apache/iggy/pull/3973)**
- **[HTTP source webhook gateway](https://github.com/apache/iggy/pull/3798)**: Iggy can now receive webhooks directly. One listener serves many providers, each routed to its own topic, with bearer or HMAC authentication, secret URL endpoints, and a token-guarded admin API to register, re-key and revoke endpoints at runtime. Delivery is best-effort, and the connector's README says so up front.
- **[InfluxDB v2 and v3 connectors](https://github.com/apache/iggy/pull/3140)** rebuilt as separate source and sink crates

### Runtime and SDK

- **[Avro payload support](https://github.com/apache/iggy/pull/3141)** with separate encoder, decoder and transform crates
- **[`unwrap_envelope` transform and envelope detection](https://github.com/apache/iggy/pull/3197)**, fixing source-to-sink format mismatches
- **[Source batch acknowledgments](https://github.com/apache/iggy/pull/3855)**: a source's checkpoint is saved only after Iggy accepted the batch. **[Source checkpoints are deferred and sink failures surfaced](https://github.com/apache/iggy/pull/4153)** instead of swallowed, and the **[PostgreSQL source defers progress until ack](https://github.com/apache/iggy/pull/3957)**.
- **[Source state stored on an HTTP state server](https://github.com/apache/iggy/pull/3940)** as an alternative to a local file
- **[Agent docs, per-batch observability and atomic state](https://github.com/apache/iggy/pull/3321)**
- **[Shared `retry_async` helper](https://github.com/apache/iggy/pull/4104)**, replacing seven hand-rolled retry loops that all waited twice the configured delay before the first retry (**BREAKING** for the Rust connector SDK: `ConnectivityConfig` became `RetryPolicy`)
- **[Per-connector init failures isolated](https://github.com/apache/iggy/pull/3244)**, **[duplicate `iggy_sink_open` and `iggy_source_open` rejected](https://github.com/apache/iggy/pull/3179)**, **[connector key validated before it becomes a path](https://github.com/apache/iggy/pull/4083)**, **[warning when the runtime API is exposed without a key](https://github.com/apache/iggy/pull/3804)**
- **[Sink writes and startup readiness validated](https://github.com/apache/iggy/pull/4154)**, **[failures reported and format conversion corrected](https://github.com/apache/iggy/pull/4152)**
- **[Distinct error variants instead of an overloaded `InvalidRecord`](https://github.com/apache/iggy/pull/3194)**, **[FlatBuffer conversions no longer fail silently](https://github.com/apache/iggy/pull/3431)**, **[protobuf field lengths validated consistently](https://github.com/apache/iggy/pull/4069)**
- Fixed **[PostgreSQL CDC producing no messages](https://github.com/apache/iggy/pull/3640)**, **[Iceberg sink partitions on iceberg 0.10](https://github.com/apache/iggy/pull/3969)**, **[InfluxDB v3 cursor rollback](https://github.com/apache/iggy/pull/3434)**, **[doubled `_total` on counter metrics](https://github.com/apache/iggy/pull/3921)**
- **[Flink sink uses the TCP client](https://github.com/apache/iggy/pull/3657)**
- **[CPU and cgroup limits respected in connector stats](https://github.com/apache/iggy/pull/3647)**

Learn more in the [connectors documentation](https://iggy.apache.org/docs/connectors/introduction).

---

## Kafka gateway

One of the questions we hear most often is some form of "can I point my Kafka clients at Iggy?". Our answer has two parts. Iggy is not built on Kafka and will not reimplement the Kafka protocol inside the server: the Iggy wire protocol, storage engine and replication are their own design, and that is where the performance comes from. Interoperability instead lives in a **separate gateway process** that speaks the Kafka wire protocol on one side and Iggy's protocol on the other, so existing Kafka producers and consumers can reach Iggy streams without code changes and without touching the server's own wire surface.

0.9.0 ships the first layer of that gateway. **[The Kafka wire protocol gateway](https://github.com/apache/iggy/pull/3519)** is a new `gateways/kafka` workspace crate: a TCP listener on the Kafka port that decodes requests, enforces a version firewall, and answers `ApiVersions`, `Metadata`, `Produce`, `Fetch`, `ListOffsets` and `CreateTopics`, backed by 184 regression tests over golden wire fixtures. The community-driven rollout plan is public in the **[Kafka to Iggy bridge discussion](https://github.com/apache/iggy/discussions/3253)** and tracked in the **[protocol parity issue](https://github.com/apache/iggy/issues/3560)**: the bridge from `Produce` and `Fetch` into Iggy streams comes first, then consumer groups and offset management, then the admin and authentication APIs. Transactions will answer with an unsupported error until Iggy itself has transactional writes.

To be clear about the current state: **in 0.9.0 this is the foundation layer, not a working bridge yet.** No API persists or reads Iggy data. `Produce`, `Fetch` and `ListOffsets` return a retriable error so Kafka clients keep their data and retry, and `CreateTopics` does not create topics. The work has not stopped at the tag, though. The **[bridge core](https://github.com/apache/iggy/pull/4043)**, an Iggy SDK client with stream and topic mapping, provisioning, high watermarks and an Iggy-to-Kafka error map, merged the day after the 0.9.0 code was cut, and pull requests for **[mapping Kafka records to Iggy messages](https://github.com/apache/iggy/pull/4229)** and **[SASL/PLAIN authentication](https://github.com/apache/iggy/pull/4222)** are in review. Wiring `Produce` and `Fetch` through the bridge is next, the work is happening in the open, and contributions are very welcome.

---

## Benchmark Dashboard

- **[Redesigned dashboard with a landing page and compare mode](https://github.com/apache/iggy/pull/3145)**: the [benchmarking platform](https://benchmarks.iggy.apache.org) now opens on a showcase pick and compares any two runs side by side
- **[Unified benchmarks list and best-pick selection](https://github.com/apache/iggy/pull/3146)**, **[mobile layout and compare URLs](https://github.com/apache/iggy/pull/3148)** fixed
- **[Cluster topology recorded in reports and surfaced in the UI](https://github.com/apache/iggy/pull/3737)**, so a result says whether it came from one node or three
- `iggy-bench` **[exposes both durability policies](https://github.com/apache/iggy/pull/4092)** as `--durability` and `--consumer-offset-durability` flags
- Bench dashboard bumped to 0.8.0

---

## Web UI and MCP

- Web UI bumped to **0.4.0** and **[served directly by the server](https://github.com/apache/iggy/pull/3676)**
- **[Explicit CSS import for the latest Vite build](https://github.com/apache/iggy/pull/3502)**, frontend dependencies updated
- MCP server bumped to **0.5.0**, **[runs against the replicated server](https://github.com/apache/iggy/pull/3617)** and supports the **[systemd watchdog](https://github.com/apache/iggy/pull/3233)**

---

## Helm Charts

- **[Cluster deployment with one release per node](https://github.com/apache/iggy/pull/4035)**: `server.cluster` mirrors the server's `[[cluster.nodes]]` roster one for one and renders into `IGGY_CLUSTER_*` variables plus `--replica-id`. Each node is its own release overriding only `selfReplicaId`, with a three-node example to start from. Roster mistakes fail at render time. `server.replicaCount > 1` and autoscaling are now refused, because three independent servers behind one Service sharing a PVC was never a cluster.
- Fixed QUIC published as TCP, a missing WebSocket port, Kubernetes service links leaking `IGGY_*` variables into the server config, and the default image moved off 0.7.0
- **[Gateway API and Envoy Gateway replace ingress-nginx](https://github.com/apache/iggy/pull/3386)** in the smoke cluster

---

## CI/CD & Infrastructure

- **[Incubating references removed after TLP graduation](https://github.com/apache/iggy/pull/3928)**
- **[SECURITY.md with the ASF reporting process](https://github.com/apache/iggy/pull/3787)**, **[package tokens replaced with OIDC](https://github.com/apache/iggy/pull/3892)**, **[read-only permissions declared on reusable workflows](https://github.com/apache/iggy/pull/3243)**
- **[Miri undefined behavior detector](https://github.com/apache/iggy/pull/3284)** for `binary_protocol` and `consensus`, **[build fails on rustdoc and rustc warnings](https://github.com/apache/iggy/pull/3925)**
- **[Integration tests run when a binary they launch changes](https://github.com/apache/iggy/pull/4131)**, **[a plugin's integration suite runs when it changes](https://github.com/apache/iggy/pull/4077)**, **[Docker `:edge` refresh gated by the cargo-rail DAG](https://github.com/apache/iggy/pull/3369)**
- **[HawkEye replaces addlicense](https://github.com/apache/iggy/pull/3325)** for license header checks, later **[migrated to v7](https://github.com/apache/iggy/pull/3952)**
- PR triage automation: **[review state labels via slash commands](https://github.com/apache/iggy/pull/3231)**, **[`/pin` and `/unpin`](https://github.com/apache/iggy/pull/4060)**, **[welcome comment](https://github.com/apache/iggy/pull/3261)**, **[auto-assign volunteering issue authors](https://github.com/apache/iggy/pull/3400)**, **[fork-PR commands via `workflow_run` handoff](https://github.com/apache/iggy/pull/3268)**, and a stale bot that **[leaves PRs waiting on review alone](https://github.com/apache/iggy/pull/4172)**
- **[AI assistance expectations added to CONTRIBUTING](https://github.com/apache/iggy/pull/4081)** and a **[repo-wide team-review agent skill](https://github.com/apache/iggy/pull/4004)**
- **[Devcontainer for reproducible development](https://github.com/apache/iggy/pull/3656)**
- Most triage and status jobs moved to **[ARM runners](https://github.com/apache/iggy/pull/3270)**, runner disk cleanup **[shared across PHP, Rust, BDD and coverage](https://github.com/apache/iggy/pull/3330)**, **[apt-get bounded so a dead mirror cannot wedge a job](https://github.com/apache/iggy/pull/3924)**
- BDD runners **[fail on undefined steps](https://github.com/apache/iggy/pull/3680)**, **[stream CRUD coverage](https://github.com/apache/iggy/pull/3875)** added, **[integration suites duplicating existing coverage removed](https://github.com/apache/iggy/pull/3823)**
- **[Source release tarball includes gateways](https://github.com/apache/iggy/pull/4126)**, **[DEPENDENCIES.md dropped per ASF policy](https://github.com/apache/iggy/pull/3222)**

---

## What's Next

Clustering is in. The next releases are about making it faster and complete:

- **Performance**: the effort behind the benchmark tables above continues. Deferred polling so consumers stop paying for empty round trips, a task per read and write for more I/O concurrency, more replica links per node pair, explicit group commit, Direct I/O instead of `fsync`, and more copy-free paths of the kind 0.9.0 started. Further out, multi-leader clusters with a leader per partition spread across all nodes. A full 0.8.0 vs 0.9.0 vs next comparison is planned for the benchmarking platform.
- **Kafka gateway**: the bridge from the protocol listener into Iggy streams, so Kafka producers and consumers can talk to Iggy through the gateway. Consumer groups follow, as laid out in the [rollout discussion](https://github.com/apache/iggy/discussions/3253).
- **SDK parity**: leader redirection coverage for Python, Node.js and C++, and the PHP SDK on its way to a published package
- **Connectors**: more sources, bounded backpressure end to end, and runtime hardening
- **Agentic AI**: building on the A2A support and the MCP server

---

Thanks to our amazing community and contributors for making Apache Iggy better with every release. Sixty people contributed to 0.9.0, and 34 of them landed their first Iggy pull request in this cycle. Welcome aboard.

**Join us on [Discord](https://discord.gg/apache-iggy) and help shape the future of the project!**
