<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Roman Samoilov</title>
    <description>The latest articles on DEV Community by Roman Samoilov (@roman_samoilov_152a8ec4ca).</description>
    <link>https://dev.to/roman_samoilov_152a8ec4ca</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1208724%2F6fc8926c-b431-4b76-9c37-c1e843a58919.jpeg</url>
      <title>DEV Community: Roman Samoilov</title>
      <link>https://dev.to/roman_samoilov_152a8ec4ca</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/roman_samoilov_152a8ec4ca"/>
    <language>en</language>
    <item>
      <title>If Rails Was Designed Today: The Operational Monolith</title>
      <dc:creator>Roman Samoilov</dc:creator>
      <pubDate>Tue, 27 Jan 2026 14:59:54 +0000</pubDate>
      <link>https://dev.to/roman_samoilov_152a8ec4ca/if-rails-was-designed-today-the-operational-monolith-44lh</link>
      <guid>https://dev.to/roman_samoilov_152a8ec4ca/if-rails-was-designed-today-the-operational-monolith-44lh</guid>
      <description>&lt;p&gt;Rails didn't get it wrong.&lt;/p&gt;

&lt;p&gt;It got it right for the world it was born into.&lt;/p&gt;

&lt;p&gt;In the mid-2000s, backend applications were mostly synchronous, request/response systems. Background jobs were rare, WebSockets didn't exist, observability wasn't a discipline, and "scale" usually meant adding more app servers behind a load balancer. In that world, Rails' core bet - optimizing relentlessly for developer productivity - was exactly the right one.&lt;/p&gt;

&lt;p&gt;But the world changed. Modern backend systems are no longer just HTTP request handlers. They're long-lived processes juggling async I/O, background execution, real-time communication, eventing, and deep observability requirements. And most of that complexity didn't disappear - it just moved outside the framework.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rails' quiet assumption
&lt;/h2&gt;

&lt;p&gt;One of Rails' early assumptions is:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Production complexity lives outside the application.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Need background jobs? Add Sidekiq.&lt;br&gt;
Need coordination? Add Redis.&lt;br&gt;
Need concurrency? Add Async.&lt;br&gt;
Need structured logging? Add Lograge.&lt;/p&gt;

&lt;p&gt;This wasn't a mistake. It was a pragmatic trade-off at a time when Ruby had no mature async primitives and when keeping the framework small mattered more than absorbing operational concerns.&lt;/p&gt;

&lt;p&gt;But the long-term consequence is familiar to anyone running a complex Rails backend today: your app is no longer a single system. It's a constellation of processes, queues, and coordination layers that must all be reasoned about together.&lt;/p&gt;

&lt;p&gt;Rails stayed elegant by pushing complexity outward. Teams paid for that elegance later, in operations.&lt;/p&gt;

&lt;h2&gt;
  
  
  A different assumption
&lt;/h2&gt;

&lt;p&gt;What if we start from a different premise?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Backend complexity is inevitable - so the framework should absorb as much of it as possible.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For a long time, the Ruby ecosystem didn't have a specific answer for this. We had Rails for productivity, and we had micro-frameworks for raw simplicity. But we lacked a framework designed specifically to handle the modern, high-concurrency, operationally complex world without abandoning the ergonomics of Ruby.&lt;/p&gt;

&lt;p&gt;This is the philosophy behind &lt;a href="https://github.com/rage-rb/rage" rel="noopener noreferrer"&gt;Rage&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Rage is an API-only Ruby framework designed to explore what backend development looks like when we treat modern operational concerns as first-class instead of external integrations.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this looks like in practice
&lt;/h2&gt;

&lt;p&gt;If Rails was designed today - with fiber schedulers, async I/O, structured logging, and real-time APIs as givens - the architectural choices would look very different.&lt;/p&gt;

&lt;h3&gt;
  
  
  Concurrency as a foundation, not an escape hatch
&lt;/h3&gt;

&lt;p&gt;Rage is fiber-first. HTTP handling, background jobs, and WebSockets all run inside the same async runtime, with the same object model and failure semantics. Async work isn't something you hand off to another system - it's just another execution path.&lt;/p&gt;

&lt;h3&gt;
  
  
  Background jobs as part of the application
&lt;/h3&gt;

&lt;p&gt;Instead of assuming a separate worker fleet and queue infrastructure, Rage treats background execution as an in-process capability by default. Jobs are persisted to a write-ahead log on disk, providing delivery guarantees without Redis or a database.&lt;/p&gt;

&lt;p&gt;That means fewer moving parts, fewer failure modes, and durability with zero setup - a backend that can start simple and scale outward only when necessary.&lt;/p&gt;

&lt;h3&gt;
  
  
  Observability as a framework contract
&lt;/h3&gt;

&lt;p&gt;Rage provides a dedicated observability interface that lets developers measure and monitor what's happening inside the application - request handling, job execution, WebSocket connections. The framework sandboxes observability code: if your instrumentation has a bug, it won't crash your request handler or background job. Observability becomes a safe, first-class capability rather than something you hope doesn't interfere with production.&lt;/p&gt;

&lt;p&gt;The unified runtime also enables deeper logging semantics. Request IDs aren’t just an HTTP concept - they automatically propagate to any background jobs enqueued during a request, ensuring all logs produced are tagged with the same parent request ID. This kind of cross-cutting observability is automatic in a unified runtime, but requires deliberate coordination when stitching together separate tools.&lt;/p&gt;

&lt;p&gt;This isn't about more features. It's about acknowledging that observability is part of what a backend is, not something bolted on later.&lt;/p&gt;

&lt;h3&gt;
  
  
  Documentation as code
&lt;/h3&gt;

&lt;p&gt;In a distributed world, the API contract is everything. Rage generates OpenAPI documentation through static analysis of your code. That means your API schema can be generated and validated in CI without spinning up the application. The schema isn't a separate file you have to maintain; it's a reflection of your actual routes and controllers, verifiable at build time.&lt;/p&gt;

&lt;h3&gt;
  
  
  One system first, distributed later
&lt;/h3&gt;

&lt;p&gt;Thanks to a fiber-based architecture and direct inter-process communication, Rage can run a full-fledged backend - HTTP, jobs, WebSockets - in a single process or a multi-process cluster, without introducing Redis just to coordinate state.&lt;/p&gt;

&lt;p&gt;Distribution becomes a scaling decision, not a starting requirement.&lt;/p&gt;

&lt;h3&gt;
  
  
  The monolith, redefined
&lt;/h3&gt;

&lt;p&gt;In the Ruby community, monoliths are often praised as an antidote to microservice sprawl. But "monolith" is usually defined in terms of code structure rather than system behavior.&lt;/p&gt;

&lt;p&gt;A Rails app with Sidekiq workers, Redis coordination, and WebSocket servers may live in one repository - but operationally, it's already distributed.&lt;/p&gt;

&lt;p&gt;Rage starts from a different definition:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A monolith is a system that can be deployed, understood, and operated as a single unit.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Because HTTP handling, background jobs, async I/O, and WebSockets all live inside the same fiber-based runtime, a Rage backend can remain genuinely monolithic far longer - running comfortably on a single server without external coordination infrastructure.&lt;/p&gt;

&lt;p&gt;That doesn't push teams toward microservices. It does the opposite. It allows teams to delay distribution until it's forced by scale, not assumed from day one.&lt;/p&gt;

&lt;p&gt;In that sense, Rage is less API-first and more &lt;strong&gt;monolith-first&lt;/strong&gt; - just without a template renderer attached. That's not a limitation. It's the entire point.&lt;/p&gt;




&lt;p&gt;Follow along at &lt;a href="https://x.com/codewithrage" rel="noopener noreferrer"&gt;https://x.com/codewithrage&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>ruby</category>
      <category>rails</category>
    </item>
    <item>
      <title>Impractical Ruby Optimisations</title>
      <dc:creator>Roman Samoilov</dc:creator>
      <pubDate>Tue, 30 Sep 2025 17:13:53 +0000</pubDate>
      <link>https://dev.to/roman_samoilov_152a8ec4ca/impractical-ruby-optimisations-2f4g</link>
      <guid>https://dev.to/roman_samoilov_152a8ec4ca/impractical-ruby-optimisations-2f4g</guid>
      <description>&lt;p&gt;Earlier this year, I was working on an &lt;a href="https://www.akamai.com/glossary/what-is-an-event-bus" rel="noopener noreferrer"&gt;event bus&lt;/a&gt; for the &lt;a href="https://github.com/rage-rb/rage" rel="noopener noreferrer"&gt;Rage framework&lt;/a&gt;. The event bus was designed to allow publishing both synchronous and asynchronous events. With synchronous events, the request would wait for event subscribers to finish. With asynchronous events, subscribers would be executed after the request had been served.&lt;/p&gt;

&lt;p&gt;But what happens if an application publishes a bunch of asynchronous events and then receives a SIGTERM? To prevent losing these events and to ensure they can be processed after a server restart, the framework needed to store asynchronous events in persistent storage.&lt;/p&gt;

&lt;p&gt;By default, Rage would store asynchronous events on disk in an append-only log. This allowed for a seamless setup, eliminating the need for any specific configurations for the event bus to function.&lt;/p&gt;

&lt;p&gt;Let's explore how the storage was implemented, walk through possible optimisations for an otherwise straightforward code, and see how seemingly minor code choices can significantly impact performance.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;While working on the event bus, it evolved into something very different - a &lt;a href="https://github.com/rage-rb/rage/wiki/Background-Tasks-Guide" rel="noopener noreferrer"&gt;message queue&lt;/a&gt;. However, the code described in this article is still largely used within the framework.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Saving Data on Disk
&lt;/h2&gt;

&lt;p&gt;Let's examine the following code, which stores asynchronous events on disk:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"zlib"&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DiskBackend&lt;/span&gt;
 &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;
   &lt;span class="vi"&gt;@file&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"storage/&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="no"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strftime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"%Y%m%d"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="no"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pid&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"a+b"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
   &lt;span class="vi"&gt;@entries&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
 &lt;span class="k"&gt;end&lt;/span&gt;

 &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;subscribers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
   &lt;span class="n"&gt;entry_ids&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;subscribers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;length&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;generate_entry_id&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
   &lt;span class="n"&gt;serialized_event&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Marshal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dump&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

   &lt;span class="n"&gt;subscribers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;zip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;entry_ids&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;subscriber&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;entry_id&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
     &lt;span class="n"&gt;entry&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"add:&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;entry_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;subscriber&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;serialized_event&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
     &lt;span class="n"&gt;crc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Zlib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;crc32&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to_s&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;rjust&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"0"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

     &lt;span class="vi"&gt;@file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;crc&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
   &lt;span class="k"&gt;end&lt;/span&gt;

   &lt;span class="n"&gt;entry_ids&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;entry_id&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="vi"&gt;@entries&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;entry_id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
 &lt;span class="k"&gt;end&lt;/span&gt;

 &lt;span class="kp"&gt;private&lt;/span&gt;

 &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;generate_entry_id&lt;/span&gt;
   &lt;span class="no"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;clock_gettime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Process&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;CLOCK_MONOTONIC&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to_s&lt;/span&gt;
 &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;DiskBackend&lt;/code&gt; class stores event subscribers in a file. Since each event can have multiple subscribers, the class generates a unique entry for each subscriber.&lt;/p&gt;

&lt;p&gt;In the constructor, we open the storage file and initialise the &lt;code&gt;@entries&lt;/code&gt; hash (more on this later). The &lt;code&gt;add&lt;/code&gt; method then performs the following steps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Generates unique IDs for all entries;&lt;/li&gt;
&lt;li&gt;Serialises the event;&lt;/li&gt;
&lt;li&gt;For each subscriber:

&lt;ul&gt;
&lt;li&gt;Generates an entry in the format of &lt;code&gt;add:&amp;lt;entryID&amp;gt;:&amp;lt;subscriber&amp;gt;:&amp;lt;event&amp;gt;&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;Generates a CRC32 signature for the entry;&lt;/li&gt;
&lt;li&gt;Writes the entry to the file;&lt;/li&gt;
&lt;li&gt;Updates the &lt;code&gt;@entries&lt;/code&gt; hash with the list of generated entry IDs. This information is used during storage file rotation to identify in-progress events that need to be copied to a new storage file.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;Let's now run a simple benchmark to see how the code performs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;RubyVM&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;YJIT&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;enable&lt;/span&gt;

&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"benchmark/ips"&lt;/span&gt;
&lt;span class="nb"&gt;require_relative&lt;/span&gt; &lt;span class="s2"&gt;"disk_backend"&lt;/span&gt;

&lt;span class="n"&gt;backend&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;DiskBackend&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;

&lt;span class="no"&gt;Benchmark&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ips&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
 &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;report&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"add"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
   &lt;span class="n"&gt;backend&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;subscribers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
 &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On my machine, with Ruby 3.3.8, I get the following results:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ruby 3.3.8 (2025-04-09 revision b200bad6cd) +YJIT [arm64-darwin24]
Warming up --------------------------------------
                 add    44.335k i/100ms
Calculating -------------------------------------
                 add    226.405k (±16.5%) i/s    (4.42 μs/i) -      1.153M in   5.224209s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;226k operations per second! That's good. But I think I can make it better.&lt;/p&gt;

&lt;h2&gt;
  
  
  Profiling
&lt;/h2&gt;

&lt;p&gt;Let's profile this code and see if we can extract some useful information from there:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;%self      total      self      wait     child     calls  name
26.80      1.343     1.343     0.000     0.000  1000000   &amp;lt;Module::Marshal&amp;gt;#dump        
16.33      1.286     0.818     0.000     0.468  1000000   Array#zip                     
 9.24      4.604     0.463     0.000     4.141  1000000   DiskBackend#add
 7.63      0.382     0.382     0.000     0.000  1000000   Float#to_s                    
 6.85      0.343     0.343     0.000     0.000  1000000   Hash#[]=                      
 6.15      5.011     0.308     0.000     4.703        1   Integer#times
 3.83      0.192     0.192     0.000     0.000  1000000   IO#write                      
 3.68      0.640     0.184     0.000     0.455  1000000   DiskBackend#generate_entry_id
 3.62      0.821     0.181     0.000     0.640  1000000   Array#initialize              
 3.44      0.516     0.172     0.000     0.343  1000000   Array#each                    
 2.46      0.944     0.123     0.000     0.821  1000000   &amp;lt;Class::Array&amp;gt;#new            
 1.90      0.095     0.095     0.000     0.000  1000000   Integer#to_s                  
 1.82      0.091     0.091     0.000     0.000  1000000   &amp;lt;Module::Zlib&amp;gt;#crc32          
 1.79      0.090     0.090     0.000     0.000  1000000   String#rjust                  
 1.45      0.073     0.073     0.000     0.000  1000000   &amp;lt;Module::Process&amp;gt;#clock_gettime
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As expected, most of the time is spent serialising the event with &lt;code&gt;Marshal.dump&lt;/code&gt;. One surprising observation, however, is the &lt;code&gt;Float#to_s&lt;/code&gt; call coming from &lt;code&gt;Process.clock_gettime(Process::CLOCK_MONOTONIC).to_s&lt;/code&gt;. This code consumes 7.63% of the total execution time.&lt;/p&gt;

&lt;p&gt;On the one hand, this is understandable - we are converting the float to a string to then use it as part of the event entry written to the file. On the other hand, we don't &lt;em&gt;have&lt;/em&gt; to do this, as the conversion will happen automatically when the number is written to a string anyway. What if we removed the &lt;code&gt;to_s&lt;/code&gt; call then?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="p"&gt;@@ -23,7 +23,7 @@&lt;/span&gt; class DiskBackend
   private
&lt;span class="err"&gt;
&lt;/span&gt;   def generate_entry_id
&lt;span class="gd"&gt;-    Process.clock_gettime(Process::CLOCK_MONOTONIC).to_s
&lt;/span&gt;&lt;span class="gi"&gt;+    Process.clock_gettime(Process::CLOCK_MONOTONIC)
&lt;/span&gt;   end
 end
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's run the benchmark again:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ruby 3.3.8 (2025-04-09 revision b200bad6cd) +YJIT [arm64-darwin24]
Warming up --------------------------------------
                add    64.575k i/100ms
Calculating -------------------------------------
                add    625.264k (± 6.5%) i/s    (1.60 μs/i) -      3.164M in   5.093242s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Whoa! Removing &lt;code&gt;to_s&lt;/code&gt;, which doesn't affect functionality at all, and allowing Ruby to implicitly convert the float, increased the performance of our code by 2.76 times!&lt;/p&gt;

&lt;p&gt;If you think this is the quirk of YJIT, it's not; the numbers are roughly the same without YJIT. And it's not even about implicit string conversions; the speed-up comes from this code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;entry_ids&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;entry_id&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="vi"&gt;@entries&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;entry_id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Turns out, setting elements with number keys in a hash is much more performant than setting elements with string keys. Less is more - lesson learnt.&lt;/p&gt;

&lt;h2&gt;
  
  
  Constant-time Writes
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;Reading the following section can lead to uncontrollable anger if you're a Product Manager.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Let's see if we can push the code a bit further. In our profiling results, we also see that the &lt;code&gt;Hash#[]=&lt;/code&gt; call, used when populating the &lt;code&gt;@entries&lt;/code&gt; hash, consumes 6.85% of the time.&lt;/p&gt;

&lt;p&gt;Hashes are fast; setting elements in a hash has O(1) complexity. Moreover, we've already optimised that code by removing &lt;code&gt;to_s&lt;/code&gt;. But what if we comment out the loop entirely?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="p"&gt;@@ -17,7 +17,7 @@&lt;/span&gt; class DiskBackend
       @file.write("#{crc}:#{entry}\n")
     end
&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="gd"&gt;-    entry_ids.each { |entry_id| @entries[entry_id] = true }
&lt;/span&gt;&lt;span class="gi"&gt;+    # entry_ids.each { |entry_id| @entries[entry_id] = true }
&lt;/span&gt;   end
&lt;span class="err"&gt;
&lt;/span&gt;   private
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The benchmark shows the following results:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ruby 3.3.8 (2025-04-09 revision b200bad6cd) +YJIT [arm64-darwin24]
Warming up --------------------------------------
                add    66.647k i/100ms
Calculating -------------------------------------
                add    702.800k (± 2.4%) i/s    (1.42 μs/i) -      3.532M in   5.029299s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's quite an improvement, but how do we get there if we still need the information in the &lt;code&gt;@entries&lt;/code&gt; hash? We change the requirements, of course!&lt;/p&gt;

&lt;p&gt;Instead of copying pending events during storage file rotation, we can modify the logic to rotate the storage file only when there are no in-progress events. This would require changes to the storage file rotation mechanism. On the plus side, we can now maintain a simple counter instead of a hash:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="p"&gt;@@ -3,7 +3,7 @@&lt;/span&gt; require "zlib"
 class DiskBackend
   def initialize
     @file = File.open("storage/#{Time.now.strftime("%Y%m%d")}-#{Process.pid}", "a+b")
&lt;span class="gd"&gt;-    @entries = {}
&lt;/span&gt;&lt;span class="gi"&gt;+    @pending_events = 0
&lt;/span&gt;   end
&lt;span class="err"&gt;
&lt;/span&gt;   def add(event, subscribers)
&lt;span class="p"&gt;@@ -17,7 +17,7 @@&lt;/span&gt; class DiskBackend
       @file.write("#{crc}:#{entry}\n")
     end
&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="gd"&gt;-    entry_ids.each { |entry_id| @entries[entry_id] = true }
&lt;/span&gt;&lt;span class="gi"&gt;+    @pending_events += entry_ids.length
&lt;/span&gt;   end
&lt;span class="err"&gt;
&lt;/span&gt;   private
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The benchmark results for this version:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ruby 3.3.8 (2025-04-09 revision b200bad6cd) +YJIT [arm64-darwin24]
Warming up --------------------------------------
                add    67.455k i/100ms
Calculating -------------------------------------
                add    704.674k (± 0.7%) i/s    (1.42 μs/i) -      3.575M in   5.073673s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Micro-optimisations
&lt;/h2&gt;

&lt;p&gt;There's one final optimisation I'd like to implement. Thinking about event buses, events are often quite specific. This means that, most of the time, an event has only one subscriber. Let's optimise the &lt;code&gt;add&lt;/code&gt; method for this particular use case:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;subscribers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;serialized_event&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Marshal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dump&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;subscribers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;length&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="n"&gt;entry&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"add:&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;generate_entry_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;subscribers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;serialized_event&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;crc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Zlib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;crc32&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to_s&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;rjust&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"0"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="vi"&gt;@file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;crc&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="vi"&gt;@pending_events&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
  &lt;span class="k"&gt;else&lt;/span&gt;
    &lt;span class="n"&gt;entry_ids&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;subscribers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;length&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;generate_entry_id&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;subscribers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;zip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;entry_ids&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;subscriber&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;entry_id&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="n"&gt;entry&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"add:&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;entry_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;subscriber&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;serialized_event&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
      &lt;span class="n"&gt;crc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Zlib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;crc32&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to_s&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;rjust&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"0"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

      &lt;span class="vi"&gt;@file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;crc&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="vi"&gt;@pending_events&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;entry_ids&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;length&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Yes, the code looks terrible, but this provides an additional 10% boost:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ruby 3.3.8 (2025-04-09 revision b200bad6cd) +YJIT [arm64-darwin24]
Warming up --------------------------------------
                add    75.275k i/100ms
Calculating -------------------------------------
                add    770.936k (± 0.7%) i/s    (1.30 μs/i) -      3.914M in   5.077604s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;With several simple (and one ugly) changes, we improved the performance of our code by 3.4 times! As it often happens with performance optimisations, the most significant improvement came from the smallest and simplest change.&lt;/p&gt;

&lt;p&gt;Does it matter though? Aren't all of these micro-optimisations? Well, yes and no.&lt;/p&gt;

&lt;p&gt;Let's consider our &lt;code&gt;@entries&lt;/code&gt; hash example, where we were inserting elements into a hash:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;entry_ids&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;entry_id&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="vi"&gt;@entries&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;entry_id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In theory, Ruby can insert 16M elements per second into a hash:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;RubyVM&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;YJIT&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;enable&lt;/span&gt;

&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"benchmark/ips"&lt;/span&gt;

&lt;span class="n"&gt;entries&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

&lt;span class="no"&gt;Benchmark&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ips&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
 &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;report&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Hash#[]="&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
   &lt;span class="n"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;clock_gettime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Process&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;CLOCK_MONOTONIC&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;
 &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Results:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ruby 3.3.8 (2025-04-09 revision b200bad6cd) +YJIT [arm64-darwin24]
Warming up --------------------------------------
            Hash#[]     1.450M i/100ms
Calculating -------------------------------------
            Hash#[]     16.066M (± 6.4%) i/s   (62.24 ns/i) -     81.173M in   5.083554s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It can't possibly affect our code, which makes less than 1M ops/s, can it? Yet, this very call slows down our code by 12%! So much for micro-optimisations.&lt;/p&gt;

&lt;p&gt;The good news is that you likely don't need to worry about this. When it comes to web development, it's far more important to write maintainable code that is easy to read and change. Performance is not nearly as critical as maintainability. Gems, however, are a very different thing.&lt;/p&gt;

&lt;p&gt;Gems provide abstractions that should be not only convenient but &lt;a href="https://en.cppreference.com/w/cpp/language/Zero-overhead_principle.html" rel="noopener noreferrer"&gt;highly efficient&lt;/a&gt;, too. This enables applications to scale and handle more load using the same slow-but-maintainable user-level code.&lt;/p&gt;

&lt;p&gt;While most optimisations might seem insignificant, their cumulative impact can be significant, especially in areas like gem development. The difference between a few microseconds can translate into thousands of operations per second, directly influencing an application's ability to scale and handle increased load efficiently.&lt;/p&gt;

</description>
      <category>ruby</category>
    </item>
    <item>
      <title>How I spent my summer vacation: making Rails 15 times faster</title>
      <dc:creator>Roman Samoilov</dc:creator>
      <pubDate>Tue, 14 Nov 2023 14:34:14 +0000</pubDate>
      <link>https://dev.to/roman_samoilov_152a8ec4ca/how-i-spent-my-summer-vacation-making-rails-15-times-faster-8fg</link>
      <guid>https://dev.to/roman_samoilov_152a8ec4ca/how-i-spent-my-summer-vacation-making-rails-15-times-faster-8fg</guid>
      <description>&lt;p&gt;Everyone knows Ruby is slow. But is that actually right? &lt;/p&gt;




&lt;p&gt;Last summer, I started thinking about things I dislike about Rails. This is not something I usually think about, as Rails is one of my favourite technologies. Mostly because I think I’d end up with something very similar to Rails if I had to write my own framework.&lt;/p&gt;

&lt;p&gt;Still, I wasn’t entirely happy with the changes made to the framework over the last few years. And with the ongoing issues like poor performance, I decided to think about what I would do differently.&lt;/p&gt;

&lt;h2&gt;
  
  
  Inspiration
&lt;/h2&gt;

&lt;p&gt;One thing that has inspired me a lot is the talk Ryan Dahl gave at JSConf EU in 2018.&lt;/p&gt;

&lt;p&gt;In that talk, Ryan, the creator of Node.js, talks about some of the mistakes he has made throughout the development of Node. He also talks about the fact that JavaScript today is almost another language compared to JavaScript from when Node.js was started.&lt;/p&gt;

&lt;p&gt;He attempts to rethink the approach Node.js introduced and presents &lt;a href="https://deno.com" rel="noopener noreferrer"&gt;Deno&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/M3BM9TB-8yA"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Today, Deno is a mature project used by Slack and Github. But back in 2018, it was a barely working concept that nevertheless allowed Ryan to share his vision with the community.&lt;/p&gt;

&lt;p&gt;I work as a Technical Lead and know that vision is key. This is something that caught me by surprise when I started leading teams - you don’t have to be the best developer on the team to lead. You don’t even have to be the biggest expert in the technology you work with. What matters most is knowing for a fact what needs to be done and how exactly this can be accomplished - a vision.&lt;/p&gt;

&lt;h2&gt;
  
  
  Vision
&lt;/h2&gt;

&lt;p&gt;I spent last summer trying to shape my vision of modern web development with Ruby and defining principles to encompass in a new framework. A framework that would rethink some of the concepts we are all used to. And I have finally settled on the four points:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Rails-compatible API&lt;/strong&gt; - there’s no need to invent a wheel because Rails’ public API is clean and easy to use. Creating something new would steepen the learning curve and result in people refusing to use the framework.&lt;/li&gt;
&lt;/ul&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;High performance&lt;/strong&gt; - this point is the most controversial one. A popular belief in the Ruby world says, “We should use Ruby when we don’t need high performance and other technologies otherwise”. Unfortunately, this view naturally transforms into “we should use other technologies all the time”. An application likely won’t start working faster once you use a faster framework. However, a framework with less overhead would enable much easier and more effective scaling. And while Ruby is not the fastest language, it’s not as slow as it looks like when using Rails.&lt;/li&gt;
&lt;/ul&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;API-only&lt;/strong&gt; - technologies like &lt;a href="https://hotwired.dev" rel="noopener noreferrer"&gt;Hotwire&lt;/a&gt; are pretty exciting, but one part of building sustainable and reliable applications is using standard approaches. Using JavaScript to create Web UI is not only an industry standard but also an incredibly simple and fun way to build modern interactive applications.&lt;/li&gt;
&lt;/ul&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Acceptance of modern Ruby&lt;/strong&gt; - &lt;a href="https://www.ruby-lang.org/en/news/2020/12/25/ruby-3-0-0-released/" rel="noopener noreferrer"&gt;Ruby 3.0&lt;/a&gt; with the support for non-blocking IO was released almost three years ago. Non-blocking IO is essential for every modern technology, and it’s a shame we still block an entire server thread while waiting on IO.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Result
&lt;/h2&gt;

&lt;p&gt;These principles ended up being implemented in a project called &lt;a href="https://github.com/rage-rb/rage" rel="noopener noreferrer"&gt;rage-rb&lt;/a&gt;. With lean code, critical parts implemented in C, and async IO out of the box, it offers much greater performance than Rails while providing a similar API.&lt;/p&gt;

&lt;p&gt;As much as my ultimate goal is to make it production-ready and get companies to use the framework, it is essentially a concept I’d like to use to share my vision with the community and find people who think the same. &lt;/p&gt;

&lt;p&gt;I’ve got no illusions - Rails will be with us for a long time. But is there something that could be done better? Or maybe just differently? &lt;a href="https://github.com/rage-rb/rage" rel="noopener noreferrer"&gt;rage-rb&lt;/a&gt; is my take on answering these questions.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.dev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/rage-rb" rel="noopener noreferrer"&gt;
        rage-rb
      &lt;/a&gt; / &lt;a href="https://github.com/rage-rb/rage" rel="noopener noreferrer"&gt;
        rage
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Fast web framework compatible with Rails.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://private-user-images.githubusercontent.com/2270393/270969229-9d06e0a4-5c20-49c7-b51d-e16ce8f1e1b7.svg?jwt=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3NTkyNTI5MjcsIm5iZiI6MTc1OTI1MjYyNywicGF0aCI6Ii8yMjcwMzkzLzI3MDk2OTIyOS05ZDA2ZTBhNC01YzIwLTQ5YzctYjUxZC1lMTZjZThmMWUxYjcuc3ZnP1gtQW16LUFsZ29yaXRobT1BV1M0LUhNQUMtU0hBMjU2JlgtQW16LUNyZWRlbnRpYWw9QUtJQVZDT0RZTFNBNTNQUUs0WkElMkYyMDI1MDkzMCUyRnVzLWVhc3QtMSUyRnMzJTJGYXdzNF9yZXF1ZXN0JlgtQW16LURhdGU9MjAyNTA5MzBUMTcxNzA3WiZYLUFtei1FeHBpcmVzPTMwMCZYLUFtei1TaWduYXR1cmU9NjVkNGY0MzIwMDU2MzJlMmVmYjkyOWI0YTk2MTlmZGJjNDcyZDQxOTM1YjA2NTQwYWIzOWEyMzJlYTQ2MDQ5MiZYLUFtei1TaWduZWRIZWFkZXJzPWhvc3QifQ.iS9D3N1cMfNnogqBD678o0gzPwnVnBtpTeb_XxerXBk"&gt;&lt;img height="200" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fprivate-user-images.githubusercontent.com%2F2270393%2F270969229-9d06e0a4-5c20-49c7-b51d-e16ce8f1e1b7.svg%3Fjwt%3DeyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3NTkyNTI5MjcsIm5iZiI6MTc1OTI1MjYyNywicGF0aCI6Ii8yMjcwMzkzLzI3MDk2OTIyOS05ZDA2ZTBhNC01YzIwLTQ5YzctYjUxZC1lMTZjZThmMWUxYjcuc3ZnP1gtQW16LUFsZ29yaXRobT1BV1M0LUhNQUMtU0hBMjU2JlgtQW16LUNyZWRlbnRpYWw9QUtJQVZDT0RZTFNBNTNQUUs0WkElMkYyMDI1MDkzMCUyRnVzLWVhc3QtMSUyRnMzJTJGYXdzNF9yZXF1ZXN0JlgtQW16LURhdGU9MjAyNTA5MzBUMTcxNzA3WiZYLUFtei1FeHBpcmVzPTMwMCZYLUFtei1TaWduYXR1cmU9NjVkNGY0MzIwMDU2MzJlMmVmYjkyOWI0YTk2MTlmZGJjNDcyZDQxOTM1YjA2NTQwYWIzOWEyMzJlYTQ2MDQ5MiZYLUFtei1TaWduZWRIZWFkZXJzPWhvc3QifQ.iS9D3N1cMfNnogqBD678o0gzPwnVnBtpTeb_XxerXBk"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Rage&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;&lt;a href="https://badge.fury.io/rb/rage-rb" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/d1139bee8b4e901e5a8e6e200813a82455fdf266fb2da71974be4a2602c1945b/68747470733a2f2f62616467652e667572792e696f2f72622f726167652d72622e737667" alt="Gem Version"&gt;&lt;/a&gt;
&lt;a rel="noopener noreferrer" href="https://github.com/rage-rb/rage/actions/workflows/main.yml/badge.svg"&gt;&lt;img src="https://github.com/rage-rb/rage/actions/workflows/main.yml/badge.svg" alt="Tests"&gt;&lt;/a&gt;
&lt;a rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/be4799551337330ba6b3aeb0fee19f47c289caced00de9ad31032b878849b7bb/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f527562792d332e322532422d253233663430303030"&gt;&lt;img src="https://camo.githubusercontent.com/be4799551337330ba6b3aeb0fee19f47c289caced00de9ad31032b878849b7bb/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f527562792d332e322532422d253233663430303030" alt="Ruby Requirement"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Rage is a high-performance framework compatible with Rails, featuring &lt;a href="https://github.com/rage-rb/rage/wiki/WebSockets-guide" rel="noopener noreferrer"&gt;WebSocket&lt;/a&gt; support and automatic generation of &lt;a href="https://github.com/rage-rb/rage/wiki/OpenAPI-Guide" rel="noopener noreferrer"&gt;OpenAPI&lt;/a&gt; documentation for your APIs. The framework is built on top of &lt;a href="https://github.com/rage-rb/iodine" rel="noopener noreferrer"&gt;Iodine&lt;/a&gt; and is based on the following design principles:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Rails compatible API&lt;/strong&gt; - Rails' API is clean, straightforward, and simply makes sense. It was one of the reasons why Rails was so successful in the past.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;High performance&lt;/strong&gt; - some think performance is not a major metric for a framework, but it's not true. Poor performance is a risk, and in today's world, companies refuse to use risky technologies.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;API-only&lt;/strong&gt; - separation of concerns is one of the most fundamental principles in software development. Backend and frontend are very different layers with different goals and paths to those goals. Separating BE code from FE code results in a much more sustainable architecture compared with classic Rails monoliths.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Acceptance of modern Ruby&lt;/strong&gt; -…&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/rage-rb/rage" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


</description>
      <category>webdev</category>
      <category>ruby</category>
      <category>rails</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
