Add AnyCable vs Socket.io comparison page#54
Open
irinanazarova wants to merge 57 commits into
Open
Conversation
Adds /compare/socket-io — a methodology-first comparison built around
benchmarks of three real configurations (default Socket.io, Socket.io with
Connection State Recovery, and AnyCable) at 10,000 concurrent clients on
identical Railway infrastructure.
Page structure (analytical-takeaway H2s):
1. Only AnyCable delivers every message on time
2. Every Socket.io deploy severs every connection. This is architectural.
3. AnyCable holds 50,000 idle connections on under 2 GB and 0.3 vCPU
Plus: methodology callout, migration code example, three-column feature
comparison with citations to AnyCable + Socket.io docs, FAQ accordion,
and CTA.
Design system:
- Notebook-feel dotted background, continuous across the page
- Rounded white cards (8 px) for tables and code blocks on the dotted bg
- Sticky right column so illustrations stay visible while text scrolls
- Inline code chips (0.85 em, subtle background, 4 px radius)
- Red accent reserved for "worst per metric" only — not for emphasis
- Mobile: hero stacks single-column, table column hides via existing rule
Files:
- src/compare/socket-io/index.html new comparison page
- src/modules/blocks/compare.scss page-scoped styles (under .compare-page)
- src/modules/blocks/about-slide.scss -sticky and -full media modifiers,
calm inline-code chips
- src/index.scss imports compare.scss
- src/blog/anycable-vs-socket-io/index.md removed (content lives on the
comparison page now)
- .gitignore benchmark/ directory (lives in its own repo)
|
|
- Removed the floating-nav <nav> markup, its IntersectionObserver script, and the .compare-nav SCSS rule. The nav was display:none in the design but still shipped markup + JS to every visitor. - Removed background: $backgroundSecondaryColor from the hero section's inline style. SCSS variable strings don't resolve in inline CSS — the declaration was a no-op.
- Compare-page customer-list links now point to /#customers anchor on the main page (the cases-slide section already had id='customers'). - llms.txt now references /compare/socket-io with headline numbers and links the open-source bench repo. The 'When to use AnyCable instead of Socket.io' section is rewritten to be CSR-aware: it acknowledges that Socket.io 4.6+ has an opt-in catch-up feature, then surfaces the measured 7x replay-latency gap (CSR p99 9.0s / max 12.0s vs AnyCable p99 1.0s / max 3.5s) and CSR's documented caveats. Why this matters: when a CTO asks an LLM 'AnyCable vs Socket.io which should I use?', the LLM scrapes llms.txt for context. Without these updates, it would not see the benchmark data or the CSR analysis.
…nical For LLM/agent visibility on the compare page: - FAQPage structured data — the 8 visible <details> Q&A pairs are now also exposed as schema.org FAQPage JSON-LD. LLMs and search engines can parse them as structured data without scraping the accordion. - Per-page og:url and rel=canonical — meta.hbs now accepts an optional pageUrl and falls back to the homepage. Compare page sets it to https://anycable.io/compare/socket-io. Improves social previews and prevents canonical drift. - sitemap.xml lists the 12 indexable pages (homepage, compare/socket-io at priority 0.9, four case studies, four anycasts, eula, notice). - robots.txt declares the sitemap location and allows all crawlers. The compare page already had analytical-takeaway H2s, semantic tables with citations, and meta description; this completes the LLM-facing surface so an agent answering 'AnyCable vs Socket.io which should I use?' on a CTO's behalf has both the prose and the structured signals.
CTA section: replace 'Get started → docs' / 'View pricing' with 'Try Managed — free up to 2K' (https://plus.anycable.io) as primary and 'Self-host (open source)' as outlined secondary. Heading now says 'Drop AnyCable into your Node.js app today'. Lower friction for the Node.js CTO who finished the comparison and wants to try it. New 'Try it in your stack' section between Migration and the feature table, with three cards linking the JS surfaces: - @anycable/core (browsers, Node.js, React Native) - twilio-ai-js-demo (working Next.js + voice AI demo) - @anycable/serverless-js (Vercel / Cloudflare / Lambda) For an evaluating CTO the question 'is this 1 hour or 1 week to set up?' is now answered by a clone-and-run demo, not just a CTA.
Three related changes that came out of a careful padding audit: 1. Strip ~120 inline cell-padding declarations from the four data tables. Each had different inline values (9/10/12/14 px combinations) that overrode the unified .compare-page table SCSS rule. Tables now share identical row height and edge gutter. 2. Add 12px / 16px padding inside .slide-show__frame so the table is inset from the rounded card border. Fixes 'content glued to panel border' — row dividers and headers now terminate before the card edge instead of touching it. 3. Restyle FAQ items as rounded white cards on the section's gray surface, matching the 'Try it in your stack' card pattern. Each <details> is now its own white panel with a subtle border, 10 px gap between cards, and a hover/open border-color shift. Visually unifies the FAQ with the rest of the page (hero cards, demo cards, table panels — all rounded white cards on the dotted page). Plus from earlier in this batch: managed-tier as primary CTA (plus.anycable.io), JS demo cards section between Migration and feature comparison.
…y bg - Add explicit padding-top/bottom: 64px to .compare-page .slide.about-slide (the global .about-slide rule zeroes section padding; this restores breathing room between consecutive sections and prevents right-column table cards from sitting flush with the section's top edge). - Restructure FAQ DOM so the gray section background extends full-width while the .faq-block items stay centered at max-width 720px.
- Apply a single 56px top/bottom padding to both .about-slide and .cases-slide on the compare page so the cases sub-section between Pillar 1 and Pillar 2 shares the same rhythm and its content no longer sits flush against its top/bottom edges. Adjacent sections now have a consistent 112px gap instead of 128 + irregular zeros. - Hero (80/60) and CTA (120/64) keep their distinctive paddings. - Pillar 2: link "thundering herd" to the EM avalanche article on evilmartians.com (target=_blank rel=noopener).
- .faq-block: explicit width:100% so the flex parent doesn't size it to content (514px closed) and snap to 720 max-width when an item opens — that snap was the visible horizontal jump on click. - slide-show__frame on compare page: bump vertical inset (16/8) so the table's first/last rows don't sit flush against the card edge. - about-slide__media-align-top on compare page: 32px margin-top so right-column cards start meaningfully below the section's gray top rather than reading as flush.
The earlier 56px top/bottom padding on .about-slide / .cases-slide created strips of dotted page bg between adjacent sections, breaking the continuous gray left-column background. The user's actual concern was tables glued to the *card* border — addressed by the slide-show__frame internal padding, which stays. Section transitions now flow cleanly section-to-section. Also drop the .about-slide__media-align-top margin-top that pushed right-column cards visibly below the section's top edge — without the section padding above it, that offset misaligned the card from its column's natural start.
Five additions designed to surface AnyCable for the queries our buyers actually run, and to give LLMs / RAG pipelines extractable content to lift verbatim: - TL;DR / "three findings" block with on-page anchor nav, placed right under the hero stat cards. Numbered findings mirror the three pillars; nav exposes #delivery #deploys #capacity #migration #try-it #faq for citation. - Three new FAQ entries (visible accordion + FAQPage JSON-LD): HIPAA / SOC2 self-hosting, cost vs Pusher / Ably, LLM token streaming. Closes the audit's biggest absent-from-LLM gaps. - TechArticle JSON-LD with author / publisher / datePublished / about[] tags so Gemini's engineering-blog retrieval path can attach to the page. - Trimmed Doximity engineering quote (public, from the podcast interview) replaces the bare logo list in "Proven at scale". Quote drops the Rails mention; substance is deploy resilience.
Establish one principle for vertical rhythm on the compare page: padding lives on the columns (.about-slide__content and __media), not on the slide itself. The slide stays unpadded so the gray left-column background flows continuously between adjacent sections; the columns each get 80px top + bottom padding so content (FAQ items, Try-it cards, tables, code blocks) never sits flush against a gray edge. Reset .about-slide__title margin-top to 0 on the compare page so the column padding handles top spacing instead of stacking with the global 120px H2 margin. Extract two repeated inline patterns to utility classes: - .compare-quote-card — tinted-bg variant of slide-show__frame used for the Doximity quote panel - .compare-try-it-grid — the JS demo cards grid Tighten the default .slide-show__frame inset to "12px 0" since table cells already own their horizontal padding. Strip "padding: 0; overflow: hidden;" inline styles from the four slide-show__frame elements so the SCSS rule actually applies (those inline rules were silently overriding the card padding fix from earlier commits).
- Local color tokens at the top of the file replace ~40 hard-coded
hex values across the SCSS. Globals ($accentPrimaryColor,
$backgroundPrimaryColor, $borderPrimaryColor, etc.) reused where
they match.
- One root .compare-page block now nests every rule, so .faq-block,
.hero-card, and the heading-anchor styles can no longer leak into
unrelated pages that happen to use the same names.
- Two duplicate .compare-page { } blocks consolidated into one.
- Components renamed for consistent .compare- prefixing:
.faq-block → .compare-faq
.faq-item → .compare-faq__item
.faq-answer → .compare-faq__answer
.hero-cards → .compare-hero-cards
.hero-card → .compare-hero-card
.hero-card__* → .compare-hero-card__*
- Comments tightened to focus on WHY (the gotchas: flex-parent
width snap on .compare-faq, column-not-slide rhythm rule, !important
needed to override global slide bg, etc.).
- Components grouped in page-flow order for readability:
background → rhythm → headings → hero cards → tldr → about
callout → right-col card variants → try-it grid → tables → faq.
No visual changes — verified all components render identically.
- Remove the "About this comparison" methodology callout. Two
explanation blocks (TL;DR + About) under the hero is too much;
TL;DR carries the analytical summary, the hero's small "Open
source — run it yourself" link covers reproducibility, and the
customer line lives in the CTA footer.
- Rework the closing CTA so the offering matches reality:
primary action → Start Pro (2 months free) → plus.anycable.io/pro
secondary → Try free Managed → plus.anycable.io
footnote line → MIT / GitHub / customers / benchmark repo
Self-hosted Pro is the monetized product; free Managed is the
low-friction trial; open source is a trust signal best handled
in the FAQ + a single GitHub link, not a button.
- Move the CTA's inline styles into a new .compare-cta block in
compare.scss following the rest of the file's BEM conventions.
Cleans up duplicates left behind after c9efd66 added SVG replacements for Tasktag and after the source-vs-public path deduplication for Rangee. Active assets still in src/public/.
After running the new sharded benchmark across 4 Railway test-client containers (each with its own source IP and ephemeral port pool), anycable-go held 199,970 / 200,000 idle WebSockets on a single Pro- tier instance — 8.3 GB of memory, 2.63% of 32 vCPU peak (~0.8 vCPU). Memory tracked the predicted ~40 KB/conn linearly. anycable-go itself had memory and CPU headroom remaining; we stopped because the headline was already strong. Updated: - Hero stat card: 50,000 → 200,000 (+ memory and CPU figures) - TL;DR Capacity finding - Pillar 3 H2: "holds 200,000 idle connections on ~8 GB and ~0.8 vCPU" - Pillar 3 body: replaces the "test-client ceiling at ~56K" caveat with the sharded methodology explanation - Capacity table: adds 100K and 200K rows; demotes 50K from key row to scaling-line entry - FAQ answer (visible HTML + FAQPage JSON-LD) - llms.txt: capacity bullet + page summary Bench source: irinanazarova/anycable-socketio-benchmarks ce9adb7+ (see lib/idle-runner.ts and bench/idle-multi.ts).
Pillar 3 H2 read 'AnyCable holds 200,000 idle connections...' without specifying topology. Now reads 'One AnyCable node holds…' and the body opens with 'single anycable-go instance.' Other mentions on the page (hero card, TL;DR, FAQ) already said 'single instance' — this brings the headline into line. Multi-node / cluster numbers are a separate benchmark for a future write-up; flagging this as one-node keeps the current claim honest.
Re-ran the same 200K idle benchmark against the AnyCable Pro v1.6.13 binary on identical Railway Pro-tier infrastructure (32 vCPU / 32 GB, broker preset, memory broker, one stream subscription per client). | binary | conns held | memory peak | CPU peak | | -------- | ---------------- | ----------- | --------- | | OSS | 199,970/200,000 | 8.35 GB | 2.63% | | Pro | 199,989/200,000 | 3.56 GB | 1.94% | That's ~17.8 KB/conn for Pro vs ~42 KB/conn for OSS — beats Pro's "2x more efficient" marketing claim and lines up with the CTA's self-hosted Pro framing. Updated: - Pillar 3 (capacity) body: notes the OSS test, then adds a Pro paragraph framing the efficiency delta as "twice the connection headroom on the same box." - Capacity table: 200K row split into OSS (8.35 GB) and Pro (3.56 GB). - FAQ "How does AnyCable compare on performance?" answer + JSON-LD mirror the new Pro datapoint. - llms.txt capacity bullet mentions both OSS and Pro numbers.
Pushed AnyCable Pro on the same single Pro-tier Railway box to its ceiling: 999,954 of 1,000,000 idle WebSocket connections, peak memory 19.3 GB, peak CPU 9.4% of 32 vCPU. 25 test-client shards × 40K each, single anycable-go-pro process, no Redis/NATS backplane. Updated: - Pillar 3 body: adds a Pro 1M paragraph as a "comfortably past a million" footnote — the page's lede stays delivery + deploy resilience, this is supporting evidence about the connection ceiling for readers who go looking. - Capacity table: appends a 1,000,000 Pro row at the bottom as the new "key" highlighted entry; demotes 200K Pro to a regular scaling-line entry (it remains the apples-to-apples-with-OSS row). - FAQ "How does AnyCable compare on performance?" answer + JSON-LD mirror the new datapoint. - llms.txt capacity bullet: appends the Pro 1M number. Hero stat card #3 still shows the 200K OSS number — that's the right apples-to-apples-with-Socket.io comparison number; 1M Pro is a "by the way" rather than the headline.
Hero stat card #3 used to show only the open-source 200K number; now it shows OSS 200K alongside Pro 1M (with the Pro memory/CPU detail), since Pro is the headline ceiling per single instance: AnyCable (open source) 200,000 AnyCable Pro 1,000,000 Pro memory / CPU used 19 GB / ~3 vCPU Also: harden .gitignore so Claude Code session artifacts (.claude/, .playwright-mcp/, root-level *.png/*.jpeg, scratch TODO/audit MDs, root package-lock.json) can't get accidentally swept into commits via `git add -A`. The previous commit on this branch (since reset) added 128 such files; this prevents recurrence.
adb73f9 to
be677c6
Compare
Ran the same 1M-connection test (25 shards × 40K each) against the single-instance Socket.io we already had on Railway, with Node heap bumped to 30 GB. Result: 119,826 connections accepted, 880,174 rejected during ramp. Memory peak was only 6.3 GB — Socket.io didn't run out of RAM, the single Node event loop saturated processing handshakes at ~580/sec while shards were attempting ~5,000/sec aggregate. That's the architectural answer made measurable: ~120K is the single-process Socket.io ceiling under aggressive parallel ramp; AnyCable Pro held 1M on the same hardware (8.3× more), and anycable-go's Go runtime simply spreads the upgrade work across all 32 vCPU instead of one Node event loop. Updated: - Hero stat card #3 now reads as a 3-way comparison: Socket.io 119,826 (red, 'is-worst') / OSS 200,000 / Pro 1,000,000. Footnote explains the test setup. - Pillar 3 body adds a paragraph explaining why Socket.io throttled (Node single event loop) and what would be needed to actually reach 1M (Redis-adapter cluster of Node processes). - Capacity table appends a Socket.io ceiling row (highlighted red) for direct visual comparison alongside the AnyCable rows. - FAQ "How does AnyCable compare on performance?" answer + JSON-LD mirror the Socket.io datapoint. - llms.txt capacity bullet incorporates the measured Socket.io number and the "needs Redis adapter cluster to reach 1M" note.
Now that all three have been pushed to the same 1M-connection idle
target on identical Railway hardware, the page leads with the
direct comparison — and the memory-efficiency story between OSS
and Pro is now explicit.
Same 1,000,000-target idle test, same single 32 GB Pro tier:
Socket.io 119,826 (single Node event loop saturated)
AnyCable OSS 993,994 (RAM-bound, hit 32 GB ceiling)
AnyCable Pro 999,954 (19 GB used, 13 GB headroom remained)
OSS uses ~33 KB / conn at 1M; Pro uses ~19 KB. Pro is roughly
1.7× more memory-efficient at scale and ~2.4× at smaller loads
(same gap, different scales).
Updated:
- Hero stat card #3: OSS row goes from 200K to 993,994; footnote
now explains all three numbers in one line ("OSS hit 32 GB
ceiling, Pro held same load on 19 GB, Socket.io saturated at
~120K").
- TL;DR Capacity bullet rewritten as the 3-way comparison.
- Pillar 3 H2 now reads "1,000,000 idle connections — Pro on
19 GB, OSS on 32 GB"; body restructured into 4 paragraphs
(intro / Socket.io / OSS / Pro) plus a short closing.
- Capacity table: replaces the OSS 200K key row with the OSS 1M
row (32 GB ceiling marked); Pro 1M row stays as the highlighted
green key row.
- FAQ "How does AnyCable compare on performance?" and JSON-LD
mirror the new 3-way story.
- llms.txt capacity bullet rewritten as a single dense 3-way
comparison so retrieval surfaces all three numbers.
Six tightening edits surfaced by an end-to-end review:
1. Refreshed pageDescription so Google snippets and ChatGPT preview
cards lead with the strongest line we now have ("Socket.io 120K /
OSS 994K / Pro 1M on the same Railway box, plus 100% delivery").
Also refreshed pageTitle to mention 1M / single node.
2. Trimmed Pillar 3 body from 5 paragraphs to 3. The 1M/Pro story
was starting to dominate the page; now the capacity pillar reads
in three beats — intro, three walls (Socket.io / OSS / Pro), and
the memory-efficiency comparison.
3. Capacity table collapsed from 9 rows to 6: kept 10K (sanity), 200K
(OSS vs Pro side-by-side at scale), and 994K-OSS / 999,954-Pro /
119,826-Socket.io as the three "wall" rows. Dropped the small-scale
1K/20K/50K/100K rows that diluted the headline.
4. Migration section ends with an explicit 4-bullet checklist —
"what changes in your code" — for senior engineers who want the
irreducible list rather than prose paragraphs.
5. TechArticle JSON-LD: refreshed `headline` and `description` to
reflect the 3-way capacity story; added `about[]` tags for "Node.js
WebSocket scaling bottleneck", "1M WebSocket connections single
instance", "AnyCable Pro vs open source", "AnyCable Pro memory
efficiency", and "WebSocket deploy resilience". Bumped dateModified
to 2026-05-03.
Two visualizations added so the structural argument and the benchmark evidence land instinct-level rather than purely through prose and tables: 1. Pillar 2 architecture diagram. A 720x380 inline SVG showing the structural difference: Socket.io's "Node.js process containing both your app and the WebSocket hub" vs AnyCable's "your app | anycable-go as separate processes, broadcast over HTTP." Sits at the top of Pillar 2's left column with the existing H3 + paragraph explanations below as text reinforcement. 2. Pillar 3 memory + CPU chart. The actual ASCII line chart from bench-runner's Railway-metrics polling during the 1M Pro run (peak 19.34 GB, peak CPU 9.37%). Embedded as a captioned <figure> below the capacity table in Pillar 3's right column, in the dark monospace style we use for code blocks. Same number lands twice — once precise in the table, once intuitive in the chart. Both styled via .compare-arch-diagram and .compare-bench-chart blocks added to compare.scss; no JS, no extra runtime deps, no design-asset maintenance.
Vladimir's review pushed us to use a hard-TCP terminate + the @anycable/core Monitor's natural reconnect-with-backoff loop (instead of a clean cable.disconnect/connect, which bypassed backoff). After re-running with the new path, AnyCable's tail latency at 10K is honestly higher than the old measurement: ~6 s p99, ~9 s max — still ~1.7 s ahead of Socket.io+CSR on the tail, but no longer the "7× faster" we were claiming. Pivot the headline narrative: both AnyCable and CSR deliver 100% under jitter; the differentiator is server cost. AnyCable Pro holds the same 10K reconnecting fleet on ~271 MB peak memory vs ~627 MB for CSR — about 2.3× less. Replace the "Replay latency p99" hero card with "Server memory at 10,000 clients" so the new pillar is visible above the fold. Number changes (today's apples-to-apples run on the same Railway box): - Default Socket.io: 86.88% delivery, 156,856 lost (was 87.41%, 150,642) - CSR p99 7.86 s, max 10.79 s (was 8.99 s, 12.03 s) - AnyCable p99 6.18 s, max 9.34 s (was 1.04 s, 3.53 s) - New row: server memory peak 675 / 627 / 760 (Pro 271) MB - Section title: "AnyCable delivers every message — with the lowest server cost" - "Where the replay tail comes from" paragraph honestly attributes most of the tail to client-side reconnect backoff (both clients use ~0.5–5 s jitter); the ~1.7 s gap is the per-stream parallel batch replay vs CSR's per-socket serial drain. Numbers in the table are 1-decimal for readability; precise values stay in the bench JSON output.
The previous benchmark sub-section showed 1K vs 5K downtime on a single box (a single architectural data point). With Vladimir's listener- attach fix landed, we re-ran avalanche scaling on a constrained box (1 vCPU / 0.5 GB) — the realistic small-tier case where the cliff shows up. Replaces the 1K/5K table with a 5K → 25K scaling table: - 5K: 4.5 s recovery, 100% reconnect - 10K: 3.9 s, 100% - 15K: 5.8 s, 98.5% (224 lost) - 20K: 8.0 s, 96.2% (753 lost) - 25K: never recovers — 0% reconnect, all 25K permanently lost The 25K row is the architectural cliff: memory hits ~95% of the cap just before redeploy, then the post-redeploy reconnect storm OOMs the new container. Confirmed on a clean isolated re-run. Updates the inline timeline illustration to show both the graceful case (5K) and the cliff (25K) on the same box — the contrast is the point. AnyCable column reads "0 s" at every scale because the WebSocket layer doesn't restart when the app does. Section title: "the avalanche scales — until it doesn't".
Two passes on the prose: 1. Tightening — read each paragraph, kept the nuance, removed any word the meaning didn't lean on. Hero subtitle, TL;DR bullets, every per-protocol bullet in Pillar 1, the structural-loss math paragraph, Pillar 2 architecture text, the avalanche-cliff narrative, Pillar 3 capacity prose. ~10–25% shorter per paragraph; same numbers, same nuance, less reader-time spent. 2. New section in Pillar 1: "Why a 1-second blip becomes a multi-second tail." Surfaces what the trace data showed — terminate → cable connect is p99 ~8 s, dominated entirely by the 0.5–5 s reconnect backoff that every realtime client uses to avoid stampeding the server. Implication, made explicit: any TCP-level disruption fans out to multiple seconds of offline window by design — without delivery guarantees those seconds are lost messages, with replay they're delayed messages. Same backoff cost, completely different user experience. Replaces the previous "Where the replay tail comes from at 10K" subsection which only described the protocol gap (~1.7 s CSR/AC delta). The protocol-gap explanation is preserved as a second paragraph so the reader still sees both pieces.
… footnote
Rest of the prose pass:
- Impact section: trimmed redundant words ("still"/"the experience" → "the flow").
- Live-chat use-case card: comma instead of "with no" preposition.
- Try-it intro: replaced "depending on whether you want to" with a colon.
- Feature comparison header: dropped "production" before "hardening" (implicit).
- "When Socket.io is right" closing line: "AnyCable provides both by default" → "AnyCable: both by default."
- Migration trade-off: removed "to deploy"/"a single"/"In return" filler.
- Capacity hero card footnote: dropped doubled "box" and trailing "remaining".
No information loss; same numbers, same nuance. The page now reads at the
density the rest of it does.
Right-column tables, code samples, scaling table, and feature matrix were hidden below 1024px (about-slide__media: display:none), so mobile and tablet visitors were seeing only the prose — roughly half the page. Scoped under .compare-page so the homepage's existing mobile behavior is unchanged. - Section flips to flex-direction: column at ≤tablet so media stacks below content; media regains display:flex with full width - slide-show__frame switches from overflow:hidden to overflow-x:auto on mobile so wide tables scroll horizontally inside the rounded card; same on <pre> blocks for code samples - compare-try-it-grid + compare-bench-chart get min-width:0 + width 100% to break out of the flex-min-content trap - inline <code> tokens (io.to(room).emit(...)) wrap on mobile via white-space:normal + overflow-wrap:anywhere Verified clean at 375 / 580 / 688 / 768 / 820 / 1023 / 1024 / 1280.
Addresses the "Socket.io is old, just use uWS" pushback we hear from founders comparing AnyCable to Socket.io. The "10× faster" claim is genuinely true on the wire (server memory, replay latency, idle capacity), so we acknowledge it head-on with measured numbers, then show where the wire isn't the bottleneck. New section between Pillar 1 and the Impact slide: - Lead acknowledges the wire-speed claim and presents the measured wins: 72 MB at 10K, sub-second p99 latency, 1.018M idle on 5.45 GB (5.35 KB/conn — beats AnyCable Pro's 19 KB/conn at the same scale). - Comparison table at 10K under jitter (5 columns: uWS, Socket.io, AnyCable OSS, AnyCable Pro) — splits OSS/Pro per the prior request. - "But the wire is rarely the bottleneck" subsection: replay-less uWS loses ~14% — same loss profile as Socket.io without CSR. - Avalanche scaling table on the same 0.5 GB / 1 vCPU box used for Pillar 2's Socket.io cliff: side-by-side ladder shows uWS doubles the clean-recovery threshold (10K → 20K) and pushes the survivable cliff from 25K to ~90K, but at the high end of uWS's capacity the reconnect storm itself becomes the user experience (151s p50 at 50K). - Closing point: uWS solves "wire is too heavy"; AnyCable solves "delivery during disruption" + "deploy resilience" — different layers of the stack. Also adds the section to the TL;DR on-page nav so someone scanning the page can jump straight to the uWS comparison.
The dedicated uWS section was carrying the full comparison story alone;
this weaves the data into the rest of the page so the reader gets a
consistent picture from hero through TL;DR through pillars and FAQ.
What changed:
- New "Where the differences come from" section between TL;DR and
Pillar 1 — sets up the architecture mental model (where the WS
layer runs, whether the protocol carries replay state) so every
benchmark number that follows is predictable.
- Hero subtitle reframed to lead on delivery + deploy resilience
(where AnyCable wins decisively) instead of memory (where uWS now
wins on bare-wire density).
- All three hero cards updated to 4 rows including uWS:
• Delivery rate: shows uWS at 86%, paired with Default Socket.io
as the no-replay losers
• Server memory: shows uWS at 72 MB with a † footnote noting it
doesn't include a replay buffer; AnyCable Pro stays positioned
as "lightest setup that delivers 100%"
• Idle capacity: shows uWS at 1,018,366 / 5.4 KB per conn, AnyCable
Pro at 999,954 / 19 KB per conn — both reach 1M, framing the
AnyCable footprint as "broker overhead, not bloat"
- TL;DR bullets rewritten to weave uWS in naturally: replay-less
setups lumped together on delivery; deploy-fragile architectures
lumped together; capacity bullet shows uWS leading on bare wire
but the trade-off is broker features.
- Pillar 1 (Delivery) lead paragraph adds uWS's ~14% loss alongside
Socket.io default's ~13%.
- Pillar 3 (Capacity) lead + table updated: 4-way comparison with
per-connection memory + replay column, header changed to clarify
that AnyCable's heavier footprint is the broker overhead.
- Feature comparison table gets a uWebSockets.js column — every
framework feature beyond raw transport is "DIY" or "No", same
as Default Socket.io. The diagnostic point that uWS solves
Socket.io's wire problem but not its framework gaps.
- "What you don't have to build" intro rewritten to cover uWS too.
- New FAQ entry "What about uWebSockets.js?" with the same data,
mirrored into the JSON-LD FAQPage block.
- Page title, meta description, TechArticle JSON-LD, and SEO
keywords updated to surface uWS comparison terms ("uWebSockets.js
vs Socket.io", "uWebSockets.js vs AnyCable") so visitors searching
for those queries find this page.
- TL;DR on-page nav gets an "Architecture" link.
- dateModified bumped to 2026-05-08.
386 inline style="" attrs → 16. Reach for design tokens (compare.scss local + global variables.scss) before hard-coded hex; reach for BEM blocks before utility classes; reach for utilities before inline. New BEM blocks: compare-hero, compare-data-table (+ --compact modifier for the feature matrix), compare-data-table__footnote, compare-bullet-list, compare-code-block, compare-try-card, compare-quote-card__*. Utility set: t-eyebrow, t-mute/meta/quiet/strong/accent/best, t-num, t-tiny, t-bold; code tints c-key/str/err/com; row modifiers is-row-best/worst; auto-spacing for sequential h3.about-slide__subtitle (kills the 11× margin-top: 32px). Code palette tokens: \$compare-code-keyword/string/ error/comment plus \$compare-best. Architecture contract documented at the top of compare.scss so the next person reaches for the right tool. Visual diff: identical.
…ughput rows
Major narrative pass on /compare/socket-io targeting the Series A Node.js
CTO ICP. Substantive content changes:
* Hero rewritten with three comparison cards (Reliability / Deploys /
Headroom) and a load-bearing TL;DR.
* New wrong-choice section before CTA + CTA reorder ("run anycable-go
locally" primary, Managed secondary).
* Throughput section restructured into two methodology groups: WS-layer
ceiling (publisher in-process) and multi-process (fan-out crosses a
network bus). The lower group adds five rows of apples-to-apples
multi-node configurations: Socket.io+Redis with in-proc and HTTP-pool
publishers, AnyCable single-instance HTTP-pool (OSS + Pro), and
AnyCable 2-node cluster with NATS. Honest finding: once the publisher
is operationally separate from the WS layer, both architectures hit
similar publisher-bound ceilings (~50–75K at 1M target).
* Avalanche reframed as architectural lesson, not 'only AnyCable can'.
* Migration section: both server- and client-side TS diff.
* FAQ: anycable-go restart story expanded with broker-specific behavior;
new 'How do I run anycable-go in production?' entry.
* Ops footnote: commit-count one-liner explaining why low velocity is
fine for infrastructure.
* Rubric 2: 25K-cliff caveat with pointer to uWS sidebar.
* Meta description + JSON-LD updated to match new narrative.
CSS:
* New compare-* BEM block namespace replacing the global slide-show /
cases-slide / about-slide classes the page was sharing.
* New blocks: compare-rubric, compare-frame, compare-impact-card{,s},
compare-prose, compare-quote-card, compare-code-tabs (CSS-only radio
tabs), compare-callout.
* Table first-column wraps long labels (white-space: normal) so 6-column
tables don't overflow.
Reproducible benchmarks at github.com/irinanazarova/anycable-socketio-benchmarks.
Page structure (5 questions → 3 sections): - New Section 1: How fast (latency suite + whispers placeholders) - Section 2: How reliable (jitter table updated, CSR row reflects heavy-jitter pattern with 72% delivery / 116s p99 max, footnote rewritten) - Section 3: How efficient to scale (idle, throughput, deploy-impact) - Added 'Note on architecture' block with embedded-vs-microservice diagram and the 10K embedded deploy-impact callout (~2s freeze per user) - Removed Q4 (operations) and Q5 (optionality) sections, folded strongest beats into FAQ - Removed standalone uWS sidebar, uWS is now a column in all tables - Added topology matrix (5 options × 3 topologies) in 'What we compare' Hero + TL;DR: - Title: Socket.io vs uWS vs AnyCable - Hero cards mapped to 3 sections, numbers shown where v1.6.14 data exists (AnyCable=100% delivery, Socket.io+CSR=72%, uWS still pending) - TL;DR rewritten around the 3 questions + whispers/Liveblocks-category beat + clustered deploy as the load-bearing finding v1.6.14 numbers plugged in: - AnyCable OSS: jitter 100%/0 loss/4.1s p95/6.2s p99/760 MB; throughput 9,996/89,334/151,953 deliv/sec; idle 821,877 conns on 28.30 GB (~34 KB/conn); latency 1k 17/55ms 10k 252/895ms - AnyCable Pro: same jitter profile, 271 MB; throughput 9,997/87,696/140,032 (avg of 2 runs); idle 822,037 conns on 14.80 GB (~18 KB/conn); latency 1k 22/145ms 10k 246/875ms - Socket.io+CSR jitter: 72.3% / 116s p99 (new pattern, footnote explains 16% resume rate under heavy jitter) - Other Socket.io / uWS rows retained at v1.6.13 values pending re-baseline - TechArticle + FAQPage JSON-LD updated to match CSR feature now linked to its docs on first prose mention; full name 'Connection state recovery' used per Socket.io docs (lowercase 's' and 'r'). Cleanup: - Moved compare hub draft to tmp/ (not needed until 2nd comparison) - .gitignore: tmp/, .zed/, /*-report.html, /*-benchmark.html SCSS refactor (parallel work): - compare-spine.scss: new layout primitive - compare.scss + index.scss updates for the page's new structure - Popup + demo TS/JS minor updates Outstanding (deferred to follow-up sessions): - Whispers test (A2b): server-side handler + per-option clients - 3-node clustered tests (A2d): needs Railway dashboard for socketio-server-redis-c - 100K latency tier (A2a): multi-shard test infrastructure - Socket.io / +CSR / uWS latency at 1k/10k tiers - Standalone deploy-impact for AnyCable (separate protocol runner) - Standalone deploy-impact rerun with default Socket.io ping settings
…e deploy-impact - A2a latency at 1k tier: Socket.io 24/106, +CSR 23/69, uWS 16/92, AnyCable OSS 17/55, Pro 22/145 ms (p50/p99) - A2a latency at 10k tier: Socket.io 264/725, +CSR 254/639, AnyCable OSS 252/895, Pro 246/875 ms (p50/p99); uWS 249ms p50 but p99 4.4-10.6 s across 4 samples (single-process backpressure) - A2c standalone deploy-impact (Socket.io + AnyCable): both show 0 affected clients, 0 reconnects, 45-46 s publisher restart window - Hero card: replaced TBD scalability with deploy-impact (embedded Socket.io 100% vs both standalone 0%) - Footnote on uWS 10k high-variance result with explanation 100k latency tier, A2b whispers, A2d 3-node clustered, CSR/uWS embedded deploy-impact remain pending.
…de pending The deploy-impact data we have is from a 2-node Socket.io+Redis cluster. Page previously claimed 3-node throughout; now says 2-node where measured, calls out 3-node as the A2d target. The per-user property (every user disconnects once during a rolling cycle) generalizes to N nodes. Also softened dochead + JSON-LD from '3-node clustered' to 'clustered'.
…' except FAQ + Run-in-your-stack Per editorial direction to keep this page focused as comparison, not full decision tree: Removed: - 'Migrating from Socket.io' section (felt prescriptive vs comparative) - 'When Socket.io is the right choice' + Doximity quote - 'Appendix' (5 mechanism deep-dives, moved scope to bench repo README) - 'When AnyCable is the wrong choice' (4 disqualification cards) - Closing CTA Kept: - 'Run AnyCable in your stack' (3 cards: JS client, demo, serverless), moved to AFTER 'What you don't have to build' - FAQ (SEO) Also cleaned up the TLDR nav (no #migration, no #appendix) and a dangling '#appendix' link in the reliability section.
- 10k clients across 3-node Socket.io+Redis cluster with rolling restart - 99.63% delivery (was 100% on 2-node but with similar pattern) - Every user (100%) disconnects once during cycle - Per-user gap: p50 1.95 s, p99 2.80 s (was 2.17/2.93 on 2-node) - Per-user msgs lost: 2-3 (same) - Cluster recovery: 82 s (was 51 s on 2-node, longer because 3 sequential redeploys vs 2) - Required adding socketio-server-redis-c Railway service + fixing PORT on all 3 nodes to match bench-runner's :3000 expectation
Two clear paths instead of the old 3-button CTA: - 'Node / JS guide' → docs.anycable.io/guides/serverless (closest doc page for Node teams; pure 'Node.js' guide page doesn't exist yet) - 'Try AnyCable+ free' → managed tier No price footnote, no second paragraph. The compare page is comparison content; closing CTA stays short.
10K clients × 100 rooms × 2 Hz × 30s, 64-byte payload (cursor-style): - AnyCable native whisper: 11.2M deliveries (99.3%), p99 3.67 s - Socket.io rooms emulation: 0.7M (3.5%), p99 8.48 s 96% loss on the Socket.io rooms path tracks to server-side backpressure on the WS process: every whisper still hits Socket.io's broadcast loop because rooms aren't a peer-to-peer primitive. AnyCable's whisper fans out without invoking the app and without the server's broadcast loop. Changed the table column 'CPU/msg' to 'Delivered' — delivery rate is the actual measurable differentiator here (a Railway-metrics CPU/msg column would have required separate instrumentation). uWS topics + AnyCable Pro whisper rows remain TBD.
- uWS topics: 100% delivery / p99 7.81s (lossless but tail is 2x AnyCable's) - AnyCable Pro: 95.4% / p99 5.20s (tracks OSS shape) - Footnote: explain why uWS p99 is longer (single-writer serialization) and why Socket.io rooms loses 96% (Express + re-emit can't keep up)
…pub removed
Per editorial direction: throughput should compare options in their
standalone/production-realistic shape (publisher as a separate process,
HTTP /_broadcast), not mix architectural shapes (in-process publisher).
- Added throughput sub-section under Section 1 (How fast) next to latency
- All 5 options measured with external HTTP publisher (pool=16) at the
1M deliveries/sec target, 10K subscribers, 100% delivery:
uWS 332K | AnyCable OSS 152K | Pro 142K | CSR 62K | Socket.io 57K
- Removed the entire Section 3 throughput block (had in-proc pub rows
+ cluster-NATS comparison rows that don't belong in a clean standalone
comparison)
- Updated FAQ + JSON-LD with the new headline numbers
Re-ran both AnyCable variants in the same window for fair comparison: - OSS: 152K → 207K deliv/sec, p99 2.69s → 3.08s - Pro: 142K → 205K deliv/sec, p99 6.55s → 3.29s The previous numbers were measured on different days; the discrepancy was a measurement-window artifact, not a real Pro-vs-OSS gap. OSS and Pro now track each other within noise at this workload, which matches the rest of the page's framing: Pro's wins are memory + the embedded broker, not raw throughput.
CSR (Redis Streams adapter) closes the per-user gap to 1.5 s vs the non-CSR pub/sub adapter at 1.95 s, and delivery hits 100% because the replay buffer backfills missed messages on reconnect. Same 100% of users affected (every user disconnects once during rolling restart) and same ~82 s cluster recovery — CSR fixes the message-loss story, not the per-user blip. Tested at 5K clients (vs 10K for the other rows) because the Redis-Streams + CSR state holding 10K clients OOM'd the bench-runner. Pattern generalizes; the column data table calls out the 5K caveat in the footnote.
Cut filler from prose, encapsulated complexity into simple facts, kept the logical flow. Trimmed intros/footnotes in: - Note on architecture (verbose paragraph → 2 sentences) - Section 1 'How fast' (2 paragraphs → 1; removed all-TBD embedded vs standalone table; removed 100k column since not measured) - Latency table footnote (uWS caveat tightened) - Throughput intro (cut redundant 'we already argued against in-process' paragraph; kept fact) - Whispers intro (Liveblocks callout tightened) - Reliability intro (3 paragraphs → 2; kept the data points, dropped filler) - Deploys intro (long paragraph → 2 short ones) - Avalanche footnote (rambling → punchy) - Headroom intro (3 prose paragraphs → 1 + bullet list) - Clustered intro + caveat callout - Clustered takeaway: rewrote 'TBD-A2c/A2d narrative' placeholder with real takeaway based on measured data ~30 lines net cut, ~70 lines simplified. Page now 1242 lines (was 1272).
Cut walls of text on a 'what do I need to know' basis. Almost every footnote and the perf FAQ shortened: - TLDR intro: dropped 'doesn't want to sit still' / 'let's dance smoothly' - Whispers footnote: 8 sentences → 2 - Throughput footnote: 5 long sentences → 2 short ones - Reliability table footnote: 6 sentences → 2 - Avalanche table footnote: 4 sentences → 2 + 1 tiny - Capacity table footnote: 3 sentences → 1 - Clustered table footnote: 6 sentences → 2 short + footnotes - 'Pending A2d' footnote: rambling → punchy - Feature comparison footnote: 3 sentences → 2 - Jitter methodology blurb: half the words - Performance FAQ (both visible + JSON-LD): wall of text → 4 short bursts Headroom intro: removed the redundant bullet list (table below shows the same data more readably). ~30 net lines, ~12 lines shorter total.
Per editorial direction: cut padding, keep personality. Restored: - TLDR: 'Your TS/JS application doesn't want to sit still', 'if we want to dance, let's dance smoothly' (the user's signature voice) - Section 1: 'Latency is the floor of what realtime can feel like' - Reliability: 'Your users aren't always on a high-speed fiber network' - Headroom: 'Your user base will grow... when we 10× users, does the bill or the box cliff before we have time to react?' - Deploys: 'Modern teams deploy multiple times a day' Also tightened a couple more dense paragraphs (the architecture takeaway between SVG and code block, the avalanche prose).
…ance metaphor Each compare-frame now leads with a 1-line takeaway that pairs with the left-side H3. Sans-serif, weight 600, 15px — visually a sibling of the H3 subtitle. Style added in compare-spine.scss as .compare-data-table__headline. 8 tables got headlines: - Latency: 'AnyCable OSS holds the lowest p99 tail at 10k...' - Throughput: 'uWS leads on raw deliveries/sec; AnyCable keeps up...' - Whispers: 'Socket.io rooms lose 96%...' - Reliability: 'Replay protocols deliver 100%...' - Avalanche: 'Past 25K, in-process WS can't recover...' - Capacity: 'uWS holds the most idle connections per byte...' - Clustered deploy: 'Embedded WS makes every user skip a beat...' - Capacity (pending A2d): brief placeholder - Feature comparison: 'Socket.io and uWS give you transport...' Wove dance metaphor through several intros (Irina's signature voice): - Latency: 'the tempo your app dances to' - Reliability: 'a missed beat in the music' - Deploys: 'Every deploy is a beat: does the WS layer skip it?' - Clustered table headline: 'every user skip a beat' - Clustered takeaway: 'CSR shrinks the missed beat. Only architecture eliminates it.'
Hero card 3 swapped from deploy-impact to memory-per-connection footprint (Socket.io ~52 KB, AnyCable Pro 18 KB, uWS 5.4 KB) since the deploy-impact angle is now better-served by the avalanche evidence inside Note on architecture. Latency table highlights the suspect uWS topics 10K p99 (4-11 s) in red and bolds the range so the variance shows. Whispers data replaced with the clean 1K x 10 rooms (100 peers/room) numbers: AnyCable Pro 100 percent + 64 ms p99, OSS 100 percent + 140 ms p99, uWS 100 percent + 21 ms p99, Socket.io rooms 64 percent + 9.7 s p99. The 10K dataset measured bench-runner saturation, not server fanout, so it has been replaced. Footnote spells out the methodology. Reliability section drops the per-client missing-seqs example block and leads with the data table; methodology link folded into the footnote. Avalanche evidence (5K to 25K, the at-25K cliff for in-process WS, 0 s recovery for AnyCable) moved into Note on architecture as a second row. The standalone 'Does the WS layer survive deploys?' section deleted, since its key content duplicated the architecture diagram and timeline already covered there. Headroom section removes the Pusher and cost paragraph and the ASCII memory/CPU charts. Table now has a CPU peak column (9.8 percent OSS, 7.9 percent Pro on the 1M idle test). Entire 'What does a clustered deployment look like?' section deleted, along with its nav link and the stale TBD comments. Page meta description rewritten to drop the 100k tier and clustered test references. color-scheme: light added to root html so OS dark mode does not bleed through native form controls and scrollbars.
Wide tables and code tabs no longer overflow the viewport on phones. Section padding now lives on .compare-rubric__section so prose and media columns sit 24px from the screen edges instead of touching them. * .compare-code-tabs container grid uses minmax(0, 1fr) plus min-width: 0 on container, bar, and panels. The inner pre handles long code lines via overflow-x: auto instead of stretching the whole container off the screen. * .compare-rubric__media gains a mobile padding override mirroring __content (padding: 0 0 48px), and --sticky drops sticky behavior on narrow screens. * .compare-rubric__section gets padding: 0 24px on mobile so content always has 24px breathing room from the viewport edges. * Data tables: td:first-child sets white-space: nowrap on mobile so brand names like Socket.io stay on one line. Direct child span (secondary text like 'default', 'topics') becomes block so it wraps below the brand name.
…bile) At 768px the 3-column hero strip cramped the headline labels and caused setup names like 'Socket.io + CSR' to wrap mid-line. Bumping the stack breakpoint to $tablet (1023px) so cards collapse to one full-width column on phones and tablets. Divider direction switches to border-bottom at the same breakpoint.
Rebaselined all 24 tests in the manifest and updated the page where
the page deviated from current measurements. The CSR test on the page
was generous to default Socket.io and uWS (reliability) because the
original baselines were taken on a less-contended Railway moment and
because the 'CSR' row was structurally measuring default Socket.io
(SOCKETIO_CSR env var lost). Both fixed in the benchmark repo; this
commit aligns the page.
Hero card 1 (latency p99 at 10K): Socket.io+CSR 639 ms -> 349 ms;
uWebSockets.js 4-11 s -> 292 ms (no backpressure on the well-behaved
run); AnyCable 895 -> 880 ms.
Hero card 2 (reliability): Socket.io+CSR 72% -> 76%; uWebSockets.js
86% -> 35%; AnyCable still 100%.
Latency table refreshed for all 10 rows. uWS p99 footnote rewritten:
the 4-11 s outlier is now noted as the contended-infra case, not
the typical observation.
Throughput table loses the deliv/sec column (derived number, not in
the JSON) and the AnyCable OSS 8.6 s p99 outlier (rerun came in at
3.13 s, matching Pro's 3.93 s). New shape: 100% delivery for all
five servers, p50 and p99 in seconds, with uWS leading at the floor
and CSR holding the tightest tail.
Reliability table loses the Server mem column (came from a different
methodology and doesn't reproduce); rows refreshed: Socket.io 87 -> 27%,
uWS 86 -> 35%, CSR 72 -> 76%, AnyCable still 100%. New headline drops
the 'lose 13-14%' framing since both no-replay options now lose most
of the test.
Whispers table: AnyCable OSS p99 140 -> 78 ms; AnyCable Pro p50
22 -> 18 ms; Socket.io 64.1 -> 62.3%; uWS unchanged.
Reliability intro prose rewritten to match new numbers. Two FAQ
answers ('how was this measured', 'isn't uWS just better') updated
to drop the stale 13-14% claim. JSON-LD FAQ schema entries kept in
sync.
* .compare-rubric__content { border-right } switches from --c-border
(#e8e8e8) to --c-border-soft (#f0f0f0). When the prose column is
much taller than the media column (or vice versa), the divider was
reading as an orphan line through empty space. Lighter hairline
recedes visually while still marking the column boundary.
* Hero strip 'Footprint' card label trims 'RAM per idle connection,
single-node load test' to 'RAM per idle connection'. Matches the
one-line height of the Latency and Reliability card labels.
Discoverability rework based on the LLM audit. The compare page ranked invisible for every non-branded Node.js WebSocket query; Ably's /compare/rails-anycable-vs-socketio outranked us for our own comparison. Phase A closes the page-level gaps: * git mv src/compare/socket-io → src/compare/nodejs-websocket. History preserved. * netlify.toml: 301 redirects /compare/socket-io and /socket-io/ to the new slug so existing inbound links transfer rank. * pageTitle: 'Node.js WebSocket Server Comparison: Socket.io vs uWebSockets.js vs AnyCable (2026 Benchmark)'. Query-bearing keywords up front. * pageDescription rewritten to lead with 'Node.js WebSocket setups' and explicitly include 'self-hosted Pusher and Ably alternative'. * Hero kicker: new compare-hero__kicker class (mono 13px accent red, above the H1). Copy: 'Looking for a Socket.io alternative for production Node.js?'. Captures the literal search query. * Hero subtitle rewritten: covers Node.js, TypeScript, Bun, Deno and the self-hosted Pusher/Ably alternative claim in one line. * TechArticle JSON-LD: url + mainEntityOfPage updated to new slug, dateModified bumped to 2026-06-04, headline + description aligned with the new title, and the 'about' keyword array expanded from 15 to 25 entities (Self-hosted Pusher alternative, Self-hosted Ably alternative, Liveblocks alternative, PartyKit alternative, Bun WebSocket server, Next.js realtime WebSocket, etc.). * llms.txt + sitemap.xml: URL references updated. * compare-spine.scss: docblock URL updated; new .compare-hero__kicker styling (mono small, accent red, 20px below the H1).
The vertical column divider previously trailed off through empty space at every sub-section boundary inside a multi-part rubric (e.g. between 'Roundtrip latency' and 'Broadcast throughput' inside the latency rubric). The break read as accidental. A small 6px dot in --c-text-faint now sits on the divider line at the top of each non-first sub-section, marking the break as a deliberate sub-chapter transition. Scoped to: * Desktop only (mediaMin tablet). On mobile there's no two-column layout so no divider to anchor against. * Adjacent sub-sections within the same rubric. Different rubrics are separated by the rubric's own border-bottom; major-section transitions don't need a dot. * Hidden when either side of the boundary is a --full sub-section. No column divider exists there, so the dot would float in empty space. Implementation uses @at-root to escape the .c-spine .compare-rubric ancestor chain. Sass would otherwise compile '& + &' inside the rubric block to a mixed-combinator selector '.compare-rubric__section + .c-spine .compare-rubric__section' that doesn't match adjacent siblings.
Without strictPort, two Vite servers happily co-exist on port 5173 when one binds IPv4 wildcard and another binds IPv6 localhost. The OS routes requests between them, so a stale Vite from another project intermittently serves an HTML redirect for every path on this site. JS that fetches anything (e.g. our github-button.ts `await response.json()`) then sees `<!DOCTYPE html>` and throws "Unexpected token '<', "<!DOCTYPE "... is not valid JSON". `--strictPort` makes Vite fail to start when the port is taken, surfacing the conflict immediately instead of producing weird runtime errors that look like application bugs.
The 'What this breaks' impact text was running the old reliability numbers (CSR p99 ~8s vs AnyCable ~6s) which no longer matched the reliability table on the same page. The latest benchmarks have CSR's p99 replay tail at ~107 s on the 20% of reconnects where replay succeeds at all; the other 80% fall back to live-from-now (75.5% delivery overall). AnyCable still lands at ~6 s with 100% delivery. Text updated to: 'Lost messages cluster around network events — exactly when the user is watching. CSR resumes about 20% of reconnects cleanly, and its p99 replay tail stretches to nearly two minutes, which reads as "the app stopped working." AnyCable replays the same messages in about 6 seconds, with full delivery. For sequential workloads, both loss and delay break the flow.' The feature matrix 'Replay latency p99' row also still said ~8 s for CSR; updated to ~107 s. Three Tier 1 rows added: * 'No external broker required' (after Multi-node setup): calls out that AnyCable Pro embeds NATS so multi-node deployments don't need a separate broker process at all. Others all need Redis (or DIY). * 'Graceful drain on restart' (after Deploy resilience): when anycable-go itself restarts, --shutdown_timeout + slow-drain mode spreads disconnects over the window. Socket.io / uWS have no equivalent. * 'Binary wire format' (after Backend language): AnyCable Pro ships first-class msgpack and protobuf protocols (actioncable-v1-msgpack, actioncable-v1-protobuf) with matching JS client encoders. Socket.io has socket.io-msgpack-parser as a third-party plugin; uWS is DIY.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds
/compare/socket-io— a methodology-first comparison built around benchmarks of three real configurations at 10,000 concurrent clients on identical Railway infrastructure (Pro tier, 32 vCPU / 32 GB).The three configurations measured:
Headline numbers (full data on the page):
Page structure
H2s are analytical takeaway statements so skim-readers get the thesis from the headings alone:
Plus: methodology callout (authorship, reproducibility, customer trust signals), migration code example, three-column feature comparison with citations to both AnyCable and Socket.io docs, FAQ accordion (incl. AnyCable+ managed-tier entry), and CTA linking to docs + customer page.
Design
Page-scoped under
.compare-pageso styles don't leak elsewhere.<code>chips at 0.85 em with subtle background and 4 px radius — reads calm next to body sans-serif#delivery,#deploys,#capacity,#migration,#faq-heading) so readers and agents can deep-link<details>accordion — closed by default, narrow centered columnMobile
mediaMax($tablet)ruleFiles
src/compare/socket-io/index.html— new comparison pagesrc/modules/blocks/compare.scss— new page-scoped stylessrc/modules/blocks/about-slide.scss— added-stickyand-fullmedia modifiers + calm inline-code chipssrc/index.scss— imports compare.scsssrc/blog/anycable-vs-socket-io/index.md— removed (content now lives on the comparison page).gitignore—benchmark/(lives in its own repo:irinanazarova/anycable-socketio-benchmarks)Reproducibility
All benchmark numbers come from runs reproducible from the open-source bench repo: https://github.com/irinanazarova/anycable-socketio-benchmarks. The bench-runner deploys to Railway and uses internal networking (
*.railway.internal) to drive 10K-client tests without local NAT/event-loop bottlenecks.Test plan
/compare/socket-ioat desktop width (1440 px) — confirm sticky right column, dotted background, hero card layout+/−indicator, red title color on open