When allow_direct is disabled on a JetStream stream, all read operations — including single message fetches and direct gets — are routed through the Raft consensus pipeline. This means every read request must be proposed to the stream’s leader, serialized alongside write operations, and committed through the replication log. For workloads that perform frequent reads, this adds unnecessary latency and contention. Most streams should have allow_direct enabled to allow reads to be served directly from any replica without consensus overhead.
JetStream’s Raft consensus pipeline exists to ensure write consistency across replicas. When a message is published, it goes through leader proposal, replication, and commit — this is necessary and expected. Reads, however, don’t modify state. Serving a read from any replica that has the data is safe for the vast majority of use cases.
With allow_direct disabled, read operations join the same queue as writes. Under write-heavy load, reads wait behind pending write proposals. Under read-heavy load, the Raft pipeline itself becomes a bottleneck — the leader must process every read request sequentially, even though the reads could be served in parallel from any replica. This contention increases both read and write latency.
The latency difference is measurable. A direct get from a local replica completes in microseconds — it’s a memory or disk lookup. A consensus-routed read adds the round-trip time to the leader, the proposal processing time, and any queuing delay behind pending writes. In a three-node cluster with moderate load, this can mean the difference between sub-millisecond reads and 5–20ms reads. At high throughput, the Raft pipeline can become the limiting factor for the entire stream’s operations.
Direct gets are especially important for key-value store workloads. NATS KV is built on JetStream streams, and every Get operation is a direct get under the hood. Disabling allow_direct on a KV backing stream turns every key lookup into a consensus operation, which defeats the performance characteristics that make KV useful.
Stream created with older NATS server defaults. Prior to NATS server v2.9, allow_direct defaulted to false. Streams created before this version retain the old default unless explicitly updated. After upgrading the server, existing stream configurations are not automatically migrated.
Explicit configuration for strong read-after-write consistency. Some applications require that a read immediately after a write always returns the latest value — linearizable reads. Disabling allow_direct achieves this by forcing reads through the leader, which has the most up-to-date state. This is a legitimate use case but is rarely needed outside of financial transactions or similar scenarios.
Copied configuration from restrictive templates. Infrastructure-as-code templates or Helm charts may set allow_direct: false as a conservative default. Teams copy these templates without evaluating whether the restriction is necessary for their workload.
Misunderstanding of the consistency model. Operators may disable direct gets out of concern that replicas might serve stale data. In practice, replica lag in a healthy cluster is measured in milliseconds. For the vast majority of consumer workloads — where the consumer is processing a stream of events — reading from a slightly behind replica is perfectly acceptable.
nats stream info STREAM --json | jq '{name: .config.name, allow_direct: .config.allow_direct, mirror_direct: .config.mirror_direct}'If allow_direct is false (or absent, which defaults to false on older streams), reads are going through consensus.
nats stream ls --json | jq -r '.[] | select(.config.allow_direct != true) | .config.name'This lists every stream in the account that is not using direct gets.
Compare read latency with the current configuration against the expected improvement:
# Current: reads go through consensustime nats stream get STREAM --last-for "subject.key"
# After enabling allow_direct, the same operation will bypass consensusnats kv ls --json | jq -r '.[].config.name'KV stores use streams with the KV_ prefix. If any KV backing stream has allow_direct: false, every key lookup is going through consensus — a significant performance regression for KV workloads.
1package main2
3import (4 "context"5 "fmt"6 "github.com/nats-io/nats.go/jetstream"7)8
9func checkDirectGets(js jetstream.JetStream) error {10 ctx := context.Background()11 streamNames := js.StreamNames(ctx)12 for name := range streamNames.Name() {13 stream, err := js.Stream(ctx, name)14 if err != nil {15 continue16 }17 info, err := stream.Info(ctx)18 if err != nil {19 continue20 }21 if !info.Config.AllowDirect {22 fmt.Printf("WARN: stream %s has allow_direct disabled — reads go through Raft\n",23 info.Config.Name)24 }25 }26 return nil27}Update the stream configuration to enable allow_direct:
nats stream edit STREAM --allow-directThis change takes effect immediately. Existing consumers and publishers are not affected — only the read path changes. No data migration or stream restart is required.
For streams that also serve as mirror sources, enable mirror_direct as well:
nats stream edit STREAM --allow-direct --allow-mirror-directnats stream info STREAM --json | jq '.config.allow_direct'Should return true.
If streams are managed by Terraform, Helm, or other IaC tools, update the template to include allow_direct: true:
1# Terraform NATS provider2resource "nats_stream" "orders" {3 name = "orders"4 subjects = ["orders.>"]5 storage = "file"6 replicas = 37 allow_direct = true8 mirror_direct = true9}There is a narrow set of scenarios where disabling direct gets is correct:
Linearizable read requirements. If your application requires that a read always reflects the most recent write (e.g., reading a balance after a debit), routing reads through the leader provides this guarantee. However, consider whether your application truly needs this — most event-streaming workloads do not.
Regulatory compliance mandates. Some compliance frameworks require all data access to go through a single consistent path. Disabling direct gets satisfies this by ensuring all reads are processed by the elected leader.
For all other workloads — event streams, logs, telemetry, KV caches, work queues — allow_direct should be enabled.
In a healthy cluster, replica lag is typically single-digit milliseconds. Direct gets read from any replica, so a read immediately after a write might return the previous value if the replica hasn’t caught up yet. For consumer-driven workloads (processing a stream sequentially), this is irrelevant — the consumer reads messages in order. For KV workloads where read-after-write consistency matters, the staleness window is almost always shorter than the client’s round-trip time. If true linearizable reads are required, keep allow_direct disabled for that specific stream.
No. Consumer message delivery (push or pull) uses its own code path and is not affected by the allow_direct setting. This setting specifically affects the direct get API — single message lookups by sequence, last message on a subject, or KV gets. Consumer subscriptions always receive messages through the standard delivery pipeline.
Yes. Mirrors support mirror_direct, which allows direct gets to be served from the mirror without forwarding to the source stream. This is particularly useful for read-heavy workloads where you want to offload reads to a mirror in a different cluster or availability zone.
In benchmarks on a three-node cluster: direct gets complete in 50–200µs. Consensus-routed reads take 2–20ms depending on load and cluster state. Under write-heavy load, consensus-routed reads can spike to 50ms+ due to queuing behind write proposals. For KV stores handling thousands of lookups per second, this difference is the gap between a responsive application and a sluggish one.
With 100+ always-on audit Checks from the NATS experts, Insights helps you find and fix problems before they become costly incidents.
No alert rules to write. No dashboards to maintain.
News and content from across the community