<!-- mobian-agent-page publisher="dailydev" canonical="https://daily.dev/posts/benchmarking-campfire-with-many-users-rr37zdios" -->

---
title: Benchmarking Campfire with Many Users | daily.dev
description: Building a many-user load test for Campfire exposed caching problems that only show up under real traffic, not single-viewer benchmarks. With 250 simulated...
canonical: https://daily.dev/posts/benchmarking-campfire-with-many-users-rr37zdios
twitter:card: summary_large_image
twitter:site: @dailydotdev
og:type: website
og:site_name: daily.dev
og:title: Benchmarking Campfire with Many Users | daily.dev
og:description: Building a many-user load test for Campfire exposed caching problems that only show up under real traffic, not single-viewer benchmarks. With 250 simulated...
og:url: https://daily.dev/posts/benchmarking-campfire-with-many-users-rr37zdios
og:image: https://api.daily.dev/og/posts/RR37ZDios.png
og:image:alt: Benchmarking Campfire with Many Users
og:image:width: 1200
og:image:height: 630
og:locale: en
---

> ## Documentation Index
> Fetch the complete documentation index at: https://daily.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Benchmarking Campfire with Many Users

**[RUBYLAND](https://daily.dev/sources/rubyla)** · 12 min read · 1 upvotes · 0 comments

## Summary

Building a many-user load test for Campfire exposed caching problems that only show up under real traffic, not single-viewer benchmarks. With 250 simulated people, Campfire's whole-page cache hit rate was effectively zero because presence updates commit to the database every 50 seconds, clearing it constantly. A second fix removed the viewer's id from fragment cache keys that didn't need it, raising the fragment hit rate to 78 to 88%. A third moved cache-key computation into database columns kept current by SQLite triggers. Together, three pull requests cut the room page from 46 ms to 11 ms at the median while halving server CPU. The post also traces a 99th-percentile delay to Action Cable processing one connection's commands out of order.

## Full article

daily.dev links to this article rather than hosting it. Read it at the original source: <https://intertwingly.net/blog/2026/10/25/Benchmarking-Campfire-with-Many-Users.html>

## Questions this post answers

### Why does my Rails fragment cache hit rate drop under real multi-user load even though it looks fine in a single-user benchmark?

The fragment cache key likely includes per-viewer data like the user id or a session token digest, so the same content gets cached separately for every viewer. In Campfire, overriding combined_fragment_cache_key to add the user's id meant a message rendered once per viewer instead of once total. Removing the viewer from keys that already fingerprint their content raised the fragment hit rate from near zero to 78 to 88%.

_Developers chasing cache-hit regressions under real traffic can track fixes like this fragment-key change on daily.dev._

### Can Action Cable process commands from the same WebSocket connection out of order?

Yes. Action Cable dispatches each command a connection sends, such as subscribe, unsubscribe, or a channel action, to a thread pool of four threads by default, with nothing enforcing per-connection order. If a navigation's unsubscribe and resubscribe to the same channel run out of order, the server can reject the subscribe as a duplicate and leave the page with no subscription until the client's 500ms resend kicks in.

_Teams debugging flaky WebSocket reconnects in Rails can follow this Action Cable ordering issue on daily.dev._

### Why would a content-based cache key (like a SHA-256 hash) be slower than a timestamp-based one in Rails?

Computing a content-based key, such as a SHA-256 hash of a message, its room, creator, rendered body, and boosts, requires loading all those associations and hashing them before the cache can even be checked, so a cache hit still pays most of the render cost. Moving the version into a database column kept current by triggers let a hit read the key straight from already-loaded columns, cutting the room page median from 19.9 ms to 11.4 ms.

_Developers weighing cache-key strategies for Rails apps can dig into this trigger-based approach on daily.dev._

## Similar posts on daily.dev

- [From latency to instant: Modernizing GitHub Issues navigation performance](https://daily.dev/posts/from-latency-to-instant-modernizing-github-issues-navigation-performance-t1jb6lu6h) · GitHub Blog · 14 upvotes · 1 comments
- [Processing 11 million rows in minutes instead of hours](https://daily.dev/posts/processing-11-million-rows-in-minutes-instead-of-hours-jgagdggzi) · stitcher.io · 277 upvotes · 5 comments

---

Tags: [#performance](https://daily.dev/tags/performance), [#ruby](https://daily.dev/tags/ruby), [#rails](https://daily.dev/tags/rails)

[View this post on daily.dev](https://daily.dev/posts/benchmarking-campfire-with-many-users-rr37zdios)

```json
{"@context":"https://schema.org","@graph":[{"@type":"Organization","@id":"https://daily.dev/#organization","name":"daily.dev","url":"https://daily.dev","logo":{"@type":"ImageObject","url":"https://daily.dev/apple-touch-icon.png","width":180,"height":180},"sameAs":["https://twitter.com/dailydotdev","https://github.com/dailydotdev","https://www.linkedin.com/company/daily-dev-ltd"]},{"@type":"WebSite","@id":"https://daily.dev/#website","url":"https://daily.dev","name":"daily.dev","publisher":{"@id":"https://daily.dev/#organization"},"potentialAction":{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https://daily.dev/search?q={search_term_string}"},"query-input":"required name=search_term_string"}}]}
{"@context":"https://schema.org","@type":"TechArticle","headline":"Benchmarking Campfire with Many Users","url":"https://daily.dev/posts/benchmarking-campfire-with-many-users-rr37zdios","mainEntityOfPage":{"@type":"WebPage","@id":"https://daily.dev/posts/benchmarking-campfire-with-many-users-rr37zdios"},"datePublished":"2026-10-25T12:26:31.226Z","dateModified":"2026-10-25T12:27:06.576Z","description":"Building a many-user load test for Campfire exposed caching problems that only show up under real traffic, not single-viewer benchmarks. With 250 simulated...","image":"https://media.daily.dev/image/upload/s--0_ODbtD2--/f_auto/v1722860399/public/Placeholder%2008","thumbnailUrl":"https://media.daily.dev/image/upload/s--0_ODbtD2--/f_auto/v1722860399/public/Placeholder%2008","isAccessibleForFree":true,"articleSection":"RUBYLAND","inLanguage":"en","publisher":{"@type":"Organization","name":"daily.dev","url":"https://daily.dev","logo":{"@type":"ImageObject","url":"https://daily.dev/apple-touch-icon.png","width":180,"height":180}},"author":{"@type":"Organization","name":"RUBYLAND","logo":"https://media.daily.dev/image/upload/t_logo,f_auto/v1/logos/rubyla","url":"https://daily.dev/sources/rubyla"},"commentCount":0,"discussionUrl":"https://daily.dev/posts/benchmarking-campfire-with-many-users-rr37zdios","interactionStatistic":[{"@type":"InteractionCounter","interactionType":{"@type":"LikeAction"},"userInteractionCount":1},{"@type":"InteractionCounter","interactionType":{"@type":"CommentAction"},"userInteractionCount":0}],"keywords":"performance,ruby,rails","timeRequired":"PT12M"}
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://daily.dev"},{"@type":"ListItem","position":2,"name":"RUBYLAND","item":"https://daily.dev/sources/rubyla"},{"@type":"ListItem","position":3,"name":"Benchmarking Campfire with Many Users"}]}
{"@context":"https://schema.org","@type":"FAQPage","@id":"https://daily.dev/posts/benchmarking-campfire-with-many-users-rr37zdios#faq","mainEntity":[{"@type":"Question","name":"Why does my Rails fragment cache hit rate drop under real multi-user load even though it looks fine in a single-user benchmark?","acceptedAnswer":{"@type":"Answer","text":"The fragment cache key likely includes per-viewer data like the user id or a session token digest, so the same content gets cached separately for every viewer. In Campfire, overriding combined_fragment_cache_key to add the user's id meant a message rendered once per viewer instead of once total. Removing the viewer from keys that already fingerprint their content raised the fragment hit rate from near zero to 78 to 88%. Developers chasing cache-hit regressions under real traffic can track fixes like this fragment-key change on daily.dev."}},{"@type":"Question","name":"Can Action Cable process commands from the same WebSocket connection out of order?","acceptedAnswer":{"@type":"Answer","text":"Yes. Action Cable dispatches each command a connection sends, such as subscribe, unsubscribe, or a channel action, to a thread pool of four threads by default, with nothing enforcing per-connection order. If a navigation's unsubscribe and resubscribe to the same channel run out of order, the server can reject the subscribe as a duplicate and leave the page with no subscription until the client's 500ms resend kicks in. Teams debugging flaky WebSocket reconnects in Rails can follow this Action Cable ordering issue on daily.dev."}},{"@type":"Question","name":"Why would a content-based cache key (like a SHA-256 hash) be slower than a timestamp-based one in Rails?","acceptedAnswer":{"@type":"Answer","text":"Computing a content-based key, such as a SHA-256 hash of a message, its room, creator, rendered body, and boosts, requires loading all those associations and hashing them before the cache can even be checked, so a cache hit still pays most of the render cost. Moving the version into a database column kept current by triggers let a hit read the key straight from already-loaded columns, cutting the room page median from 19.9 ms to 11.4 ms. Developers weighing cache-key strategies for Rails apps can dig into this trigger-based approach on daily.dev."}}]}
```

