100k clients at ONCE: the Campfire rewrite, in Ruby
TL;DR: I ran Basecamp’s official Pi benchmark for ONCE on a Pi-class server. In 1 hour I achieved 10x the clients the Rails reference handled, with ~22x the deliveries per second and ~4x less memory per connection. And it was still Ruby.
HEY is moving to a Rust backend written entirely by agents, and Basecamp published a Rust port of ONCE Campfire that holds 100,000 chat clients on a Raspberry Pi 5’s budget.
The reasoning is sound - if agents deal with the language’s complexity, why not pick the fastest language? I don’t think this should negate your team’s expertise though.
Whenever a teammate asked for my input on a problem, my first reply would usually be “Show me the code.” Natural language is good at explaining intent, but code is still the most clear and precise way to describe technical solutions.
Programming in English works for company owners, but for developers who run this code in production, being able to read and reason about it is what allows us to understand the system, direct agents, and make informed decisions.
So can you get most of the efficiency without leaving Ruby?
The benchmark
Basecamp's Pi benchmark opens 100,000 WebSocket connections, each with six Cable subscriptions. After holding them idle for 30 seconds, it posts 10 messages roughly 200 ms apart to measure delivery latency, then uses two HTTP clients to post continuously for 10 seconds to measure throughput. Each post goes through database writes, background scheduling, full message rendering, and message/unread broadcasts.
To run the benchmark, I extracted the slice of Campfire that the benchmark exercises and migrated it to Rage with:
- GPT-6.1-Sol high
- 4 sequential agents
- the official Rage AI skills
An hour later (which included tests, documentation, and several fixes to the load generator script) the migration was done. Compared to the reference implementation, Rage has two differences:
- Background tasks run in-process through Rage::Deferred - same shape the Rust port uses.
- Presence writes are batched and flushed to the database every 100ms through a dedicated Rage::Daemon; although this only affects subscription times.
The first version reused the existing Active Record models. Then I gave the agent another 30 minutes to migrate them to Sequel.
On the Rails side, I upgraded the image from Ruby 3.4 to Ruby 4. Both apps were patched to compress WebSocket messages with Zlib, as the Rust port does.
Setup
- App server: EC2 c6g.xlarge - roughly similar performance to a Raspberry Pi, close to Basecamp’s own setup which uses a Ryzen capped at 1.2 cores.
- Load generator: a separate EC2 instance over a real network.
Results
|Metric|Rails 10k|Rage AR 50k|Rage Sequel 50k|Rage Sequel 100k|
|:-|:-|:-|:-|:-|
|Subscriptions/s|340|1,012|1,798|1,630|
|RAM per connection|245 KiB|98 KiB|64 KiB|64 KiB|
|Peak RAM usage|2.55 GiB|4.71 GiB|3.14 GiB|5.95 GiB|
|Saturated deliveries/s|8,709|206,282|192,149|187,964|
|Paced post → everyone, p50 / p99|2,386 / 3,346 ms|344 / 477 ms|422 / 556 ms|865 / 1,112 ms|
|Saturated post → everyone, p50 / p99|2,998 / 13,754 ms|1,074 / 1,723 ms|1,190 / 1,695 ms|1,891 / 2,224 ms|
|Saturated HTTP POST, p50 / p99|950 / 13,779 ms|362 / 1,172 ms|445 / 786 ms|947 / 1,478 ms|
At 10x the clients, Rage uses ~4x less memory per connection and matches or beats Rails on every latency metric. Swapping Active Record for Sequel cut memory per connection from 98 to 64 KiB and made subscriptions ~1.8x faster, although latencies stayed roughly the same.
Best part? It’s still the Ruby you know.
What about Rust?
The Rust version is both faster and more memory efficient. No surprise here. However, Basecamp estimates that a realistic scenario of 100k people in rooms of 100 needs about 33,000 deliveries per second. Rage does 187,000. On real hardware, both Rage and Rust ports would hit the same network bandwidth wall much sooner than the benchmark shows.
Source code
The migrated app, benchmark scripts and raw results: github.com/rage-rb/once-campfire-rust/tree/rage-b…
—
AI opens a lot of new possibilities, but none of them replace your expertise. You can make your app drastically more efficient without leaving Ruby. All it takes is to leave Rails.
67 points · 31 comments
Mentions
Hot Cell、こういうのもあるのか。 basecamp/hotcell
fizzy - Kanban tak, jak má být. Ne tak, jaký býval.
HEY も CLI とおしてエージェントから操作できるようになってる
@dhh There is also a pull request from @ZachSDaniel1 to add a reliability benchmark: once-campfire-verification#6 Rust is dropping more than 99% of messages under load, delivering fewer messages than Rails or Elixir.
Grifters gonna protect other grifters at all costs. Here's @DHH's literal first commit to Rust rewrite repo: github.com/basecamp/once-campfire-rust/blob/f2936… That's *before* 300+ commits to make it work. For some reason Elixir isn't allowed to get rid of Redis and other bullshit, or have any commits t.co
Also extracted a separate verification project that's responsible for the benchmarking and checking of new implementations and improvements to the existing ones. The full report from the latest runs is there too. github.com/basecamp/once-campfire-verification/bl…
I'm still seething about `.dev`. For those not in the know, Google lobbied ICANN to get the name and in their application they stated (repeatedly) that the intent was to buy it so that it could be reserved; as .dev was already used by developers and if someone bought it for commercial purposes it would harm the developer community.[0] .... they then proceeded to start selling them. Leading to all kinds of issues, the exact issues that they raised... pow#397 valet#433 danielbachhuber.com community.localwp.com forums.theregister.com [0]: gtldresult.icann.org
Hot Cell v1.1.0 is out with a new client class that works on legacy Ruby versions back to 1.9.3 for folks with legacy Rails apps to secure. basecamp.github.io This version also improves error handling, see the changelog at github.com/basecamp/hotcell/releases/tag/v1.1.0
100k clients at ONCE: the Campfire rewrite, in Ruby TL;DR: I ran Basecamp’s official Pi benchmark for ONCE on a Pi-class server. In 1 hour I achieved 10x the clients the Rails reference handled, with ~22x the deliveries per second and ~4x less memory per connection. And it was still Ruby. HEY is moving to a Rust backend written entirely by agents, and Basecamp published a Rust port of ONCE Campfire that holds 100,000 chat clients on a Raspberry Pi 5’s budget. The reasoning is sound - if agents deal with the language’s complexity, why not pick the fastest language? I don’t think this should negate your team’s expertise though. Whenever a teammate asked for my input on a problem, my first reply would usually be “Show me the code.” Natural language is good at explaining intent, but code is still the most clear and precise way to describe technical solutions. Programming in English works for company owners, but for developers who run this code in production, being able to read and reason about it is what allows us to understand the system, direct agents, and make informed decisions. So can you get most of the efficiency without leaving Ruby? The benchmark Basecamp's Pi benchmark opens 100,000 WebSocket connections, each with six Cable subscriptions. After holding them idle for 30 seconds, it posts 10 messages roughly 200 ms apart to measure delivery latency, then uses two HTTP clients to post continuously for 10 seconds to measure throughput. Each post goes through database writes, background scheduling, full message rendering, and message/unread broadcasts. To run the benchmark, I extracted the slice of Campfire that the benchmark exercises and migrated it to Rage with: - GPT-6.1-Sol high - 4 sequential agents - the official Rage AI skills An hour later (which included tests, documentation, and several fixes to the load generator script) the migration was done. Compared to the reference implementation, Rage has two differences: - Background tasks run in-process through Rage::Deferred - same shape the Rust port uses. - Presence writes are batched and flushed to the database every 100ms through a dedicated Rage::Daemon; although this only affects subscription times. The first version reused the existing Active Record models. Then I gave the agent another 30 minutes to migrate them to Sequel. On the Rails side, I upgraded the image from Ruby 3.4 to Ruby 4. Both apps were patched to compress WebSocket messages with Zlib, as the Rust port does. Setup - App server: EC2 c6g.xlarge - roughly similar performance to a Raspberry Pi, close to Basecamp’s own setup which uses a Ryzen capped at 1.2 cores. - Load generator: a separate EC2 instance over a real network. Results |Metric|Rails 10k|Rage AR 50k|Rage Sequel 50k|Rage Sequel 100k| |:-|:-|:-|:-|:-| |Subscriptions/s|340|1,012|1,798|1,630| |RAM per connection|245 KiB|98 KiB|64 KiB|64 KiB| |Peak RAM usage|2.55 GiB|4.71 GiB|3.14 GiB|5.95 GiB| |Saturated deliveries/s|8,709|206,282|192,149|187,964| |Paced post → everyone, p50 / p99|2,386 / 3,346 ms|344 / 477 ms|422 / 556 ms|865 / 1,112 ms| |Saturated post → everyone, p50 / p99|2,998 / 13,754 ms|1,074 / 1,723 ms|1,190 / 1,695 ms|1,891 / 2,224 ms| |Saturated HTTP POST, p50 / p99|950 / 13,779 ms|362 / 1,172 ms|445 / 786 ms|947 / 1,478 ms| At 10x the clients, Rage uses ~4x less memory per connection and matches or beats Rails on every latency metric. Swapping Active Record for Sequel cut memory per connection from 98 to 64 KiB and made subscriptions ~1.8x faster, although latencies stayed roughly the same. Best part? It’s still the Ruby you know. What about Rust? The Rust version is both faster and more memory efficient. No surprise here. However, Basecamp estimates that a realistic scenario of 100k people in rooms of 100 needs about 33,000 deliveries per second. Rage does 187,000. On real hardware, both Rage and Rust ports would hit the same network bandwidth wall much sooner than the benchmark shows. Source code The migrated app, benchmark scripts and raw results: github.com/rage-rb/once-campfire-rust/tree/rage-b… — AI opens a lot of new possibilities, but none of them replace your expertise. You can make your app drastically more efficient without leaving Ruby. All it takes is to leave Rails.
How do you know that @DHH is a liar and a grifter? You just watch what he does. He (well, his AI slop bot) closed the PR that @ZachSDaniel1 made to fix Elixir with "doesn't maintain compatibility with Rails" when his own Rust slop doesn't either once-campfire-elixir#1 t.co