Nats Async
Published docs: https://artyomb.github.io/nats-async/
An async-compatible NATS client for Ruby using the Async gem for high-concurrency I/O.
Features
- Non-blocking I/O using Async gem
- High concurrency - Handle thousands of concurrent connections
- Thread-safe publishing with configurable batch flushing
- JetStream support
- Reconnection support
Performance
With flush_delay optimization, nats-async delivers low latency with competitive throughput:
| Configuration | Throughput | Latency | Notes |
|---|---|---|---|
| nats-pure.rb (single-threaded) | ~480k msg/s | ~0ms | Baseline |
| nats-pure.rb (multi-threaded) | ~630k msg/s | ~0ms | 16 threads |
| nats-async (default, verbose=true) | ~10k msg/s | ~10ms | Heavy logging |
| nats-async (default, verbose=false) | ~440k msg/s | ~10ms | Recommended |
| nats-async (delay=50ms) | ~460k msg/s | ~50ms | Max throughput |
Benchmark Example
bundle exec ruby benchmark_final.rb
Example output:
nats-pure.rb (single-threaded): 477762 msg/s
nats-pure.rb (multi-threaded): 628296 msg/s
nats-async (default, 10ms): 458232 msg/s ← ~1.04x faster
nats-async (delay=50ms): 477762 msg/s ← ~1.0x, max throughput
Installation
Add the gem to your bundle:
gem "nats-async"
Or install it directly:
gem install nats-async
Usage
Basic Publishing
require "nats-async"
Async do |task|
client = NatsAsync::Client.new(
url: "nats://127.0.0.1:4222",
verbose: false, # Critical for performance!
flush_delay: 0.01, # Flush after 10ms of no messages (default)
flush_max_buffer: 5000 # Safety: flush if 5000+ messages buffered
)
client.start(task: task)
1000.times do |i|
client.publish("test.subject", "Message #{i}")
end
ensure
client&.close
end
High-Throughput Publishing
For maximum throughput (higher latency), use flush_delay: 0.05:
client = NatsAsync::Client.new(
url: "nats://127.0.0.1:4222",
verbose: false,
flush_delay: 0.05, # 50ms max delay
flush_max_buffer: 5000 # Never buffer more than 5000 messages
)
Low-Latency Publishing
The default 10ms delay is already optimized for low latency:
client = NatsAsync::Client.new(
url: "nats://127.0.0.1:4222",
verbose: false,
flush_delay: 0.01, # 10ms max delay (default)
flush_max_buffer: 5000 # Safety limit
)
Receiving Messages
client = NatsAsync::Client.new(url: "nats://127.0.0.1:4222", verbose: false)
client.start(task: Async::Task.current)
client.subscribe("test.subject") do |msg|
puts "Received: #{msg.data}"
end
sleep 5 # Keep alive
client.close
Request/Reply
client = NatsAsync::Client.new(
url: "nats://127.0.0.1:4222",
verbose: false,
flush_delay: 0.01
)
client.start(task: Async::Task.current)
response = client.request("echo", "hello", timeout: 1)
puts "Response: #{response.data}"
client.close
JetStream
client = NatsAsync::Client.new(url: "nats://127.0.0.1:4222", verbose: false)
client.start(task: Async::Task.current)
js = client.jetstream
# Publish to JetStream
js.publish("orders", "order data", stream: "orders")
# Subscribe to JetStream
js.subscribe("orders", stream: "orders") do |msg|
puts "Received: #{msg.data}"
msg.ack
end
client.close
Configuration Options
| Option | Default | Description |
|---|---|---|
url |
'nats://127.0.0.1:4222' |
NATS server URL |
verbose |
true |
Enable debug logging (disable for production!) |
flush_delay |
0.01 |
Max time to buffer before flush (10ms recommended) |
flush_max_buffer |
5000 |
Safety limit: flush even if not full (prevents OOM) |
ping_interval |
30 |
Interval between ping messages |
ping_timeout |
5 |
Timeout for pong response |
tls |
nil |
Enable TLS |
tls_verify |
true |
Verify TLS certificates |
Tuning Recommendations
For Maximum Throughput (bulk publishing)
flush_delay: 0.05, # 50ms buffer
flush_max_buffer: 5000 # Up to 5000 messages
For Low Latency (default - real-time delivery)
flush_delay: 0.01, # 10ms buffer (default)
flush_max_buffer: 10000 # Safety limit
Key Tips
- Disable logging in production:
verbose: false- 10x+ improvement - Use the default 10ms delay - low latency with good throughput
- Always set
flush_max_buffer- prevents memory explosion - Increase delay for bulk publishing if latency isn't critical
Differences from nats-pure.rb
| Feature | nats-async | nats-pure.rb |
|---|---|---|
| Threading | Async (cooperative) | Ruby threads |
| Concurrency | Thousands of connections | Limited by threads |
| Single-threaded throughput | ~440k msg/s (10ms default) | ~480k msg/s |
| Single-threaded throughput | ~460k msg/s (50ms delay) | ~480k msg/s |
| Multi-connection efficiency | Excellent | Good |
| Blocking behavior | Non-blocking | Blocking |
| Latency | ~10ms default | ~0ms |
| Best use case | Many concurrent tasks, low latency | Bulk publishing |
Examples
Core pub/sub:
bundle exec ruby examples/core_lifecycle.rb
JetStream publish and pull:
bundle exec ruby examples/jetstream_roundtrip.rb
The integration spec boots the bundled bin/nats-server and runs these examples locally.
Documentation
The documentation site is an Astro/Starlight project in docs-site.
npm --prefix docs-site install
bundle exec rake docs:dev
npm --prefix docs-site run build
Development
bundle install
bundle exec rspec
rake build