<?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: Wilbur Suero</title>
    <description>The latest articles on DEV Community by Wilbur Suero (@travelingwilbur).</description>
    <link>https://dev.to/travelingwilbur</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F38352%2Fb3f7db58-dbe2-42d2-a48e-fbaa9768b36e.jpg</url>
      <title>DEV Community: Wilbur Suero</title>
      <link>https://dev.to/travelingwilbur</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/travelingwilbur"/>
    <language>en</language>
    <item>
      <title>More Threads, Less Throughput: When AI Servers Fight the Scheduler</title>
      <dc:creator>Wilbur Suero</dc:creator>
      <pubDate>Sat, 04 Jul 2026 15:45:46 +0000</pubDate>
      <link>https://dev.to/travelingwilbur/more-threads-less-throughput-when-ai-servers-fight-the-scheduler-3oif</link>
      <guid>https://dev.to/travelingwilbur/more-threads-less-throughput-when-ai-servers-fight-the-scheduler-3oif</guid>
      <description>&lt;p&gt;Our inference server had 24 CPU cores, a mostly idle GPU, and only 10 concurrent requests.&lt;/p&gt;

&lt;p&gt;Somehow, average latency exceeded eight seconds.&lt;/p&gt;

&lt;p&gt;That shouldn't have been possible.&lt;/p&gt;

&lt;p&gt;We were running a Rails background worker system that dispatched embedding and classification requests to an internal Python inference service. As our background queue grew, we did what any sensible team would do: we increased the concurrency of our background workers and scaled up the inference web server.&lt;/p&gt;

&lt;p&gt;Instead of seeing our throughput scale linearly, the system ground to a halt. Latency spiked from milliseconds to over eight seconds, background queues backed up, and our server's CPU utilization pegged at 100%. Yet, looking at the GPU telemetry, it was practically idling, waiting for work.&lt;/p&gt;

&lt;p&gt;The CPUs weren't spending their time multiplying matrices. They were spending it deciding which thread should run next. As we dug deeper, we realized we were victims of a hidden performance killer: &lt;strong&gt;thread oversubscription&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Benchmark That Didn't Make Sense
&lt;/h2&gt;

&lt;p&gt;To diagnose the bottleneck, we isolated the inference service and ran a controlled load test. We simulated a modest concurrency of 10 concurrent requests, sending a total of 100 API requests to the Python inference server.&lt;/p&gt;

&lt;p&gt;While an individual, isolated inference request took around 912 milliseconds, putting a concurrent load of just 10 requests pushed the average response time out to over 8 seconds. Even worse, the CPU cores were completely saturated, but the GPU was only at 15% utilization.&lt;/p&gt;

&lt;p&gt;In a healthy system, if the CPU is at 100%, throughput should be maximized. But here, the CPU was working incredibly hard to produce almost no output.&lt;/p&gt;

&lt;p&gt;Here is what the initial baseline benchmark looked like:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Baseline&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Throughput&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;7.28 req/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Average Latency&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;8.43 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Inference Time&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;912 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Active Threads&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;~240&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;CPU Utilization&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;98%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Our Initial Hypotheses
&lt;/h2&gt;

&lt;p&gt;We thought it was:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;GPU Saturation&lt;/strong&gt;: The model was too heavy for our hardware.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Insufficient Workers&lt;/strong&gt;: We needed to increase Rails concurrency to queue more work.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Slow Model&lt;/strong&gt;: The embedding algorithm itself was inherently slow.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Network Overhead&lt;/strong&gt;: The data transfer between Rails and the inference API was lagging.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It turned out to be none of them.&lt;/p&gt;




&lt;h2&gt;
  
  
  Following the CPU: The 240-Thread Discovery
&lt;/h2&gt;

&lt;p&gt;We jumped onto the inference server during the load test and ran &lt;code&gt;htop&lt;/code&gt; to see what the CPU was actually doing. What we saw was a wall of red and green bars representing 24 individual CPU cores (48 logical threads) pegged to their absolute limits.&lt;/p&gt;

&lt;p&gt;Then we checked the active thread count for the Python inference process:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Count the number of lightweight processes (threads) for our service pid&lt;/span&gt;
ps &lt;span class="nt"&gt;-o&lt;/span&gt; nlwp &lt;span class="nt"&gt;-p&lt;/span&gt; &amp;lt;PID&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The output was &lt;strong&gt;240&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;A single process handling 10 concurrent web requests had spawned 240 active threads. Where did they come from?&lt;/p&gt;

&lt;p&gt;Our Python code was simple. It was a standard FastAPI application using Uvicorn. We weren't manually spawning threads. We were just loading a sentence-transformer model using PyTorch and running inference:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# The seemingly innocent endpoint
&lt;/span&gt;&lt;span class="nd"&gt;@app.post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/embed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;embed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;TextPayload&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# Under the hood, this call is not single-threaded
&lt;/span&gt;    &lt;span class="n"&gt;embeddings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;texts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;embeddings&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;embeddings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;tolist&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The culprit wasn't our application. It was the stack of native libraries underneath it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Native Libraries Lie to You
&lt;/h2&gt;

&lt;p&gt;To understand why 240 threads were running, we have to look at the architectural layers beneath PyTorch. &lt;/p&gt;

&lt;p&gt;When you call &lt;code&gt;model.encode()&lt;/code&gt; in Python, your request travels down a vertical stack of abstractions before hitting the CPU:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                Request
                   │
         FastAPI / Uvicorn
                   │
              PyTorch
                   │
      OpenMP / MKL / BLAS
                   │
            OS Scheduler
                   │
              CPU Cores
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At the top, we have FastAPI/Uvicorn, which handles concurrent web connections. Beneath it is PyTorch, which coordinates the neural network graph execution. But PyTorch itself doesn't actually perform the core matrix arithmetic (like matrix multiplication). For that, it delegates to low-level, highly optimized native C and C++ math libraries:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;BLAS (Basic Linear Algebra Subprograms)&lt;/strong&gt;: The standardized API specification for low-level vector and matrix operations.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;OpenBLAS / MKL (Intel Math Kernel Library)&lt;/strong&gt;: Concrete, highly optimized implementations of the BLAS API. They use hand-crafted assembly code, CPU vector extensions (like AVX-512), and internal threading to run matrix math at the absolute limits of the physical silicon.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;OpenMP (Open Multi-Processing)&lt;/strong&gt;: The concurrency engine used by these math libraries to split loops and parallelize matrix calculations across multiple threads.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These native libraries were designed for High-Performance Computing (HPC) environments, where a single program runs on a dedicated machine and expects to utilize every core for a single calculation. MKL and OpenMP query the host core count and spawn a worker thread pool matching it. They assume they own the hardware.&lt;/p&gt;

&lt;p&gt;While this behavior is perfect for a Jupyter notebook running on a developer's workstation, it is hostile in a web server or background job runner. Web servers scale by handling independent requests concurrently using separate processes or threads.&lt;/p&gt;

&lt;p&gt;If Uvicorn or Rails runs 10 workers on our 24-core server, each worker process makes its own PyTorch calls. PyTorch delegates to OpenMP, which independently spawns 24 threads per request. None of the workers are aware of each other.&lt;/p&gt;

&lt;p&gt;Repeat this for all 10 concurrent requests, and the math compounding is brutal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;10 Concurrent Requests 
  × 24 Threads per Request 
  = 240 Active Threads competing for 24 Physical Cores
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To visualize the scale of this duplication:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fb4rkjzx42bnkggp2ia87.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fb4rkjzx42bnkggp2ia87.png" alt=" " width="686" height="556"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The native libraries lied to us by assuming they were alone. By defaulting to the total core count, they optimized for single-task speed at the expense of multi-task system throughput. Instead of cooperating, the threads began to fight.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Cost of Concurrency: Scheduler Contention
&lt;/h2&gt;

&lt;p&gt;When you have 240 active threads screaming for time on 24 physical CPU cores, the operating system is forced to step in. The OS scheduler must slice up CPU time and constantly swap threads in and out of the CPU registers.&lt;/p&gt;

&lt;p&gt;This is known as &lt;strong&gt;thread oversubscription&lt;/strong&gt;, and it introduces a massive overhead called &lt;strong&gt;scheduler contention&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxi1phvkya1jcrkfuierk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxi1phvkya1jcrkfuierk.png" alt=" " width="659" height="681"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Every context switch is work the application didn't ask for. The scheduler saves registers, restores another thread's state, and the CPU begins executing a completely different workload. Caches become less effective, memory must be fetched again, and the processor spends less time multiplying matrices and more time preparing to multiply matrices.&lt;/p&gt;

&lt;p&gt;When the cache is constantly invalidated, the CPU cores spend most of their time waiting for data to be fetched from main memory (RAM) instead of performing floating-point math. The CPU is "busy," but it is busy managing its own metadata rather than executing your code.&lt;/p&gt;

&lt;p&gt;In our case, the scheduler contention was so severe that the overhead of coordinating the 240 threads completely wiped out the benefits of parallelizing the matrix math. Ten requests, each trying to run on 24 threads, took 912ms. If they had run sequentially on a single thread each, they would have finished in a fraction of the time.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Fix: Global Concurrency Coordination
&lt;/h2&gt;

&lt;p&gt;The solution was counterintuitive but incredibly simple: we had to force the native libraries to stop parallelizing internal operations. We wanted each request to run on exactly &lt;strong&gt;one thread&lt;/strong&gt;, allowing the web server's concurrency model (10 worker processes) to match the physical hardware limits.&lt;/p&gt;

&lt;p&gt;We added the following configuration to the very top of our application entry point, before any deep learning library was imported:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;

&lt;span class="c1"&gt;# Limit OpenMP threads
&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;OMP_NUM_THREADS&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;MKL_NUM_THREADS&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;OPENBLAS_NUM_THREADS&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;VECLIB_MAXIMUM_THREADS&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;NUMEXPR_NUM_THREADS&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="c1"&gt;# Force PyTorch to use a single thread for intra-op and inter-op parallelism
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;
&lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set_num_threads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set_num_interop_threads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By setting these variables to &lt;code&gt;1&lt;/code&gt;, we instructed PyTorch and its underlying math libraries to execute operations sequentially on a single thread per request. &lt;/p&gt;

&lt;p&gt;We then restarted our server and ran the exact same load test of 10 concurrent requests. &lt;/p&gt;

&lt;p&gt;The results were night and day:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Baseline (Default Threads)&lt;/th&gt;
&lt;th&gt;Optimized (1 Thread)&lt;/th&gt;
&lt;th&gt;Improvement&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Throughput&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;7.28 req/s&lt;/td&gt;
&lt;td&gt;11.45 req/s&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;+57%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Average Latency&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;8.43 s&lt;/td&gt;
&lt;td&gt;5.24 s&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;-38%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Inference Time&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;912 ms&lt;/td&gt;
&lt;td&gt;560 ms&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;-38%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Active Threads&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;~240&lt;/td&gt;
&lt;td&gt;~10&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;95% Reduction&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;CPU Utilization&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;98%&lt;/td&gt;
&lt;td&gt;45%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Slashed in Half&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;By reducing the thread count from 240 to 10, throughput increased by 57%, average latency dropped by nearly 4 seconds, and CPU utilization was cut in half. &lt;/p&gt;

&lt;p&gt;The CPU was no longer thrashing. It spent its cycles executing matrix multiplication instead of shuffling threads in and out of registers.&lt;/p&gt;




&lt;h2&gt;
  
  
  When Multi-Threaded Math Libraries Are Actually Good
&lt;/h2&gt;

&lt;p&gt;This doesn't mean &lt;code&gt;OMP_NUM_THREADS=1&lt;/code&gt; is always correct. It is a design decision based entirely on your application's concurrency model.&lt;/p&gt;

&lt;p&gt;If you are running an offline batch job, training a model, or running a single-threaded daemon processing one task at a time, you &lt;em&gt;want&lt;/em&gt; PyTorch to use all available cores. In this scenario (data-level concurrency), letting MKL parallelize the matrix calculations makes your process run as fast as possible.&lt;/p&gt;

&lt;p&gt;However, if you are running a web server or background workers processing many independent requests in parallel (request-level concurrency), each process should use exactly &lt;code&gt;1&lt;/code&gt; thread. The operating system is already parallelizing the work across the cores at the process level. Adding internal library threads only causes them to fight for the scheduler's attention.&lt;/p&gt;

&lt;p&gt;If you run both workloads on the same server, you must configure them separately. For example, keep &lt;code&gt;OMP_NUM_THREADS=1&lt;/code&gt; on your web servers, but allow it to scale to the core count on your batch processing workers.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Broader Engineering Lesson
&lt;/h2&gt;

&lt;p&gt;Every layer of the stack was trying to optimize itself independently. None of them were wrong. Together, they were inefficient.&lt;/p&gt;

&lt;p&gt;Modern web frameworks and background workers are designed to scale by running multiple isolated processes or threads. Native math libraries are also designed to scale by running multiple threads. If you don't coordinate these two layers, they will fight each other for the scheduler's attention.&lt;/p&gt;

&lt;p&gt;Systems don't become fast because every component is individually optimized. They become fast when every component cooperates.&lt;/p&gt;

&lt;p&gt;Before you scale your servers horizontally or buy larger GPUs, inspect your process thread count. The best performance optimizations don't always come from writing faster code—sometimes they come from simply stopping your libraries from fighting each other.&lt;/p&gt;

&lt;p&gt;No magic. Just systems.&lt;/p&gt;

</description>
      <category>performance</category>
      <category>systems</category>
      <category>ai</category>
    </item>
    <item>
      <title>Stop Sending Embeddings as JSON: A Faster Binary Serialization Pattern for AI APIs</title>
      <dc:creator>Wilbur Suero</dc:creator>
      <pubDate>Sat, 04 Jul 2026 13:51:37 +0000</pubDate>
      <link>https://dev.to/travelingwilbur/stop-sending-embeddings-as-json-a-faster-binary-serialization-pattern-for-ai-apis-e86</link>
      <guid>https://dev.to/travelingwilbur/stop-sending-embeddings-as-json-a-faster-binary-serialization-pattern-for-ai-apis-e86</guid>
      <description>&lt;p&gt;Every embedding API I've worked with sends vectors as JSON.&lt;/p&gt;

&lt;p&gt;That's convenient.&lt;/p&gt;

&lt;p&gt;It's also one of the most expensive ways to move numerical data between services. JSON isn't expensive simply because it's text. It's expensive because every floating-point value must be converted from binary to decimal, transmitted as characters, and parsed back into binary on the receiving side. You pay that cost on every dimension of every vector, in both directions.&lt;/p&gt;

&lt;p&gt;By replacing JSON arrays with Base64-encoded binary float buffers, we reduced payload size by &lt;strong&gt;75%&lt;/strong&gt; and cut serialization latency by over &lt;strong&gt;98%&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Here's the benchmark, measured against 1,000 embeddings at 1,536 dimensions each:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;JSON Float Array&lt;/th&gt;
&lt;th&gt;Base64 Float Buffer&lt;/th&gt;
&lt;th&gt;Improvement&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Payload Size&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;120.3 MB&lt;/td&gt;
&lt;td&gt;30.1 MB&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;-75.0%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Python Serialization Time&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;3,140 ms&lt;/td&gt;
&lt;td&gt;45 ms&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;-98.5%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Ruby Deserialization Time&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;4,820 ms&lt;/td&gt;
&lt;td&gt;110 ms&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;-97.7%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Total Roundtrip Latency&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;9.42 s&lt;/td&gt;
&lt;td&gt;1.84 s&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;-80.4%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Benchmark environment:&lt;/strong&gt; Ruby 3.3.4, Python 3.12, NumPy 1.26, FastAPI 0.111, Apple M3 Pro (development) and x86_64 Linux (production). Each format was measured over 1,000 iterations with GC disabled during measurement using Ruby's &lt;code&gt;Benchmark::IPS&lt;/code&gt; and Python's &lt;code&gt;timeit&lt;/code&gt;. Payload sizes were measured as raw HTTP body bytes before compression.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reproduce it yourself:&lt;/strong&gt; The &lt;a href="https://gist.github.com/wilburhimself/9acedeb7bc3c90606450f0db8aa070b3" rel="noopener noreferrer"&gt;Python serialization and Ruby deserialization benchmark scripts&lt;/a&gt; are available as a Gist. Clone, run, and compare on your own hardware.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Keep reading for the explanation, the implementation, and the cases where JSON is still the right answer.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Numerical Data Doesn't Belong in JSON
&lt;/h2&gt;

&lt;p&gt;Computers do not store floating-point numbers as text. In memory, a single-precision float (&lt;code&gt;float32&lt;/code&gt; in Python, &lt;code&gt;float&lt;/code&gt; in C) is stored as a 4-byte (32-bit) binary value.&lt;/p&gt;

&lt;p&gt;When you serialize a float to JSON, you pay a conversion cost in both directions:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;JSON pipeline (what everyone does):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;float32 in memory (4 bytes)
    ↓  binary-to-decimal conversion
"-0.23142091" as ASCII string (11+ bytes)
    ↓  HTTP (24 KB per 1,536-dim vector)
JSON parser reads characters
    ↓  string-to-float conversion (strtof)
float in Ruby memory
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Binary pipeline (what we switched to):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;float32 in memory (4 bytes)
    ↓  tobytes() — zero conversion, O(1)
raw bytes
    ↓  Base64 encode (fast C implementation)
ASCII string (8 KB per 1,536-dim vector)
    ↓  HTTP
Base64 decode → unpack("e*") in Ruby
    ↓  single C pass over raw bytes
float in Ruby memory
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The math is straightforward: in binary, a &lt;code&gt;float32&lt;/code&gt; occupies exactly &lt;strong&gt;4 bytes&lt;/strong&gt;. In JSON, the same number &lt;code&gt;-0.23142091&lt;/code&gt; occupies &lt;strong&gt;11 bytes as text&lt;/strong&gt; plus separators—a 3x–4x size multiplier.&lt;/p&gt;

&lt;p&gt;For a single 1,536-dimension embedding, a binary payload is &lt;strong&gt;6,144 bytes&lt;/strong&gt; (~6 KB). The equivalent JSON array exceeds &lt;strong&gt;24 KB&lt;/strong&gt;. Multiply across a batch of 50 documents and you're transmitting over a megabyte of ASCII where 300 KB of binary would have been sufficient.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjnaosczdbzs1kgukl7nb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjnaosczdbzs1kgukl7nb.png" alt=" " width="716" height="321"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The network payload is only part of the problem. The CPU cost is often worse. To produce the JSON, Python must format each float as a string and concatenate them; Ruby must then locate delimiters, allocate memory for each token, and run string-to-float conversion for every dimension. That loop scales linearly with batch size and embedding dimensions, and at high throughput it dominates request latency.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;For services generating a few embeddings per minute, JSON is perfectly adequate. The break-even point depends on batch sizes and hardware; profile before optimizing.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  The Naive Solution (and Why it Fails at Scale)
&lt;/h2&gt;

&lt;p&gt;Here is how our original system looked.&lt;/p&gt;

&lt;p&gt;On the Python inference side (FastAPI), we returned embeddings using standard NumPy-to-list conversion:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# FastAPI endpoint (Slow &amp;amp; Heavy)
&lt;/span&gt;&lt;span class="nd"&gt;@app.post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/v1/embeddings&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create_embeddings&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;RequestPayload&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;embeddings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;texts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# numpy array of float32
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;# This implicitly converts the numpy array to a list of Python floats,
&lt;/span&gt;        &lt;span class="c1"&gt;# which FastAPI's JSON encoder serializes to a string.
&lt;/span&gt;        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;embeddings&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;embeddings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;tolist&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On the Rails side, we used standard HTTP clients and JSON parsing:&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="c1"&gt;# Rails Client (Slow &amp;amp; Heavy)&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;InferenceClient&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;fetch_embeddings&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;texts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;HTTParty&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="s2"&gt;"http://inference-server/v1/embeddings"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;body: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;texts: &lt;/span&gt;&lt;span class="n"&gt;texts&lt;/span&gt; &lt;span class="p"&gt;}.&lt;/span&gt;&lt;span class="nf"&gt;to_json&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;headers: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"application/json"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# JSON parsing allocates thousands of strings and converts them back to floats&lt;/span&gt;
    &lt;span class="no"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="s2"&gt;"embeddings"&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;During profiling, we discovered that Rails spent up to &lt;strong&gt;40% of its execution time&lt;/strong&gt; inside &lt;code&gt;JSON.parse&lt;/code&gt; when processing large batches of embeddings. We were spending more CPU power parsing strings than executing business logic.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Better Approach: Binary Float Buffers via Base64
&lt;/h2&gt;

&lt;p&gt;Rather than trying to parse JSON faster, we eliminated JSON serialization of the embedding vector itself.&lt;/p&gt;

&lt;p&gt;We serialize the float array as a raw binary buffer, then encode that buffer into a single Base64 string. Base64 introduces about 33% size overhead compared to raw binary, but it lets us embed binary data safely inside standard JSON payloads—no changes to content-type negotiation, no custom binary HTTP framing, no breaking changes to existing API consumers.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Python Inference Server (Encoding)
&lt;/h3&gt;

&lt;p&gt;Instead of calling &lt;code&gt;.tolist()&lt;/code&gt;, we extract the raw bytes from the underlying C-contiguous memory block:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;base64&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;numpy&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;serialize_embeddings&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;embeddings&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ndarray&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# Ensure the array is single-precision float32
&lt;/span&gt;    &lt;span class="n"&gt;float_array&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;embeddings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;astype&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;float32&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Get raw C-compatible memory bytes
&lt;/span&gt;    &lt;span class="n"&gt;raw_bytes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;float_array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;tobytes&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c1"&gt;# Base64 encode the bytes and decode to an ASCII string
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;base64&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;b64encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;raw_bytes&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ascii&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Our API response now returns a single string instead of an array of numbers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"embedding"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"MzMzMzPz8/M+MzMzMzPz8z8zMzMz..."&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Rails Application Server (Decoding)
&lt;/h3&gt;

&lt;p&gt;On the Ruby side, we decode the Base64 string back into raw bytes, then unpack them into Ruby Floats using &lt;code&gt;String#unpack&lt;/code&gt;:&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;"base64"&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;EmbeddingDecoder&lt;/span&gt;
  &lt;span class="c1"&gt;# Decodes a Base64-encoded float32 binary buffer into a Ruby array of floats&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;base64_string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# 1. Decode Base64 string back to binary string (raw bytes)&lt;/span&gt;
    &lt;span class="n"&gt;binary_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Base64&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strict_decode64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;base64_string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# 2. Unpack the binary buffer.&lt;/span&gt;
    &lt;span class="c1"&gt;# 'e' = little-endian single-precision (32-bit) float&lt;/span&gt;
    &lt;span class="c1"&gt;# '*' = unpack all remaining data in the string&lt;/span&gt;
    &lt;span class="n"&gt;binary_data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;unpack&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"e*"&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;The key is &lt;code&gt;unpack("e*")&lt;/code&gt;. Unlike iterating over a Ruby array, &lt;code&gt;unpack&lt;/code&gt; runs entirely in optimized C inside the Ruby VM. It reads raw bytes directly from memory, offsets the pointer by 4 bytes at a time, and constructs the corresponding Ruby Float objects in a single pass—no string parsing, no delimiter scanning.&lt;/p&gt;




&lt;h2&gt;
  
  
  Under the Hood: Endianness and the &lt;code&gt;unpack&lt;/code&gt; Directive
&lt;/h2&gt;

&lt;p&gt;When moving binary data across languages and hardware, two things matter: &lt;strong&gt;precision&lt;/strong&gt; and &lt;strong&gt;endianness&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Precision (Float32 vs. Float64)
&lt;/h3&gt;

&lt;p&gt;NumPy defaults to &lt;code&gt;float64&lt;/code&gt; (8 bytes per float) for many operations, but AI embeddings are almost universally &lt;code&gt;float32&lt;/code&gt; (4 bytes per float). We explicitly call &lt;code&gt;.astype(np.float32)&lt;/code&gt; before serializing. If you serialize as &lt;code&gt;float64&lt;/code&gt; and unpack as &lt;code&gt;float32&lt;/code&gt;, the decoded numbers are meaningless.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Endianness (Byte Ordering)
&lt;/h3&gt;

&lt;p&gt;Endianness determines the order bytes are written to memory:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Little-Endian&lt;/strong&gt;: Least significant byte first. Used by x86_64 and ARM (Apple Silicon, modern Linux ARM).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Big-Endian&lt;/strong&gt;: Most significant byte first. Used by network protocols and older RISC architectures.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Ruby's &lt;code&gt;String#unpack&lt;/code&gt; directives are explicit about this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;f*&lt;/code&gt; — native CPU endianness (fragile across architectures)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;g*&lt;/code&gt; — big-endian single-precision floats&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;e*&lt;/code&gt; — little-endian single-precision floats&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We use &lt;code&gt;e*&lt;/code&gt; because both our Python inference servers (x86 Linux) and Rails app servers (x86 Linux and ARM macOS) are little-endian. More importantly, being explicit about endianness means the decoding is correct by definition, regardless of what machine runs the code in the future.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Tradeoffs
&lt;/h2&gt;

&lt;p&gt;This optimization is not universal. Here's an honest accounting of the alternatives we evaluated before landing here.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why not gRPC or Protocol Buffers?
&lt;/h3&gt;

&lt;p&gt;Protocol Buffers with gRPC is the canonical answer to this problem in infrastructure-heavy organizations. It solves serialization, adds schema evolution, streaming, and bidirectional communication. We evaluated it. The operational cost—generated stubs for every language, strict schema registration, gRPC infrastructure—was high for an internal service consumed in one place. Base64 binary over HTTP let us ship in an afternoon with no new infrastructure. For systems with multiple consumers or where schema evolution matters, protobuf is the better long-term answer.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why not MessagePack?
&lt;/h3&gt;

&lt;p&gt;MessagePack is an excellent binary serialization format and would have worked here. In our case, the embedding vector is the only binary payload in the response—everything else is standard JSON. Introducing a full MessagePack codec for a single field felt like over-engineering. If you're serializing entire response objects in binary, MessagePack or protobuf is more appropriate.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why not Apache Arrow?
&lt;/h3&gt;

&lt;p&gt;Arrow's columnar in-memory format is optimized for batch numerical data and enables zero-copy access in many scenarios. For a dedicated ML pipeline where Ruby is doing heavy vector operations, Arrow (via the &lt;code&gt;red-arrow&lt;/code&gt; gem) is worth evaluating. In our case, embeddings pass directly into pgvector; the overhead of Arrow's columnar framing wasn't justified.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why not raw binary HTTP?
&lt;/h3&gt;

&lt;p&gt;A pure binary HTTP response (&lt;code&gt;Content-Type: application/octet-stream&lt;/code&gt;) would eliminate the ~33% Base64 overhead. We opted against it because it requires custom content-type handling on both sides, breaks standard HTTP debugging tooling, and complicates response envelope composition—status codes, metadata, and errors all live in the same JSON body. Base64-in-JSON is a pragmatic middle ground: it keeps the API contract intact while avoiding the text serialization cost for the high-volume field.&lt;/p&gt;

&lt;h3&gt;
  
  
  The real costs
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Loss of human readability.&lt;/strong&gt; You can no longer inspect embedding values in curl or Chrome DevTools. Debugging requires decoding the Base64 string out-of-band.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tight schema coupling.&lt;/strong&gt; Changing precision from &lt;code&gt;float32&lt;/code&gt; to &lt;code&gt;float16&lt;/code&gt; or &lt;code&gt;float64&lt;/code&gt; on the Python side requires a simultaneous update to the Ruby unpack directive. There is no self-describing schema to catch mismatches.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Array allocation overhead.&lt;/strong&gt; &lt;code&gt;unpack&lt;/code&gt; is fast, but Ruby still allocates 1,536 Float objects per vector. If you're passing embeddings directly into pgvector, benchmark whether you need to materialize the Ruby array at all—some clients accept binary blobs directly.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Conclusion: Text is for Humans, Binary is for Systems
&lt;/h2&gt;

&lt;p&gt;JSON has won because it's readable. For most web APIs, that readability is worth the overhead—payloads are small and parsing costs are negligible.&lt;/p&gt;

&lt;p&gt;Embeddings are different. A single vector from a modern model contains more numerical data than most entire API responses. When you serialize dense numerical arrays as strings, you're not just adding overhead—you're mismatching the data structure to its representation.&lt;/p&gt;

&lt;p&gt;Performance work often starts with algorithms, but the largest wins frequently come from changing representations instead. Once you stop treating dense numerical data like text, the CPU suddenly has much less work to do.&lt;/p&gt;

&lt;p&gt;That principle extends well beyond embeddings. Whenever you find yourself serializing audio buffers, image histograms, or numerical feature vectors as JSON, stop and ask whether the text representation is serving the system or just the developer who wrote the first version.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Set-Based Updates in Rails: 4 Hours to 8 Seconds</title>
      <dc:creator>Wilbur Suero</dc:creator>
      <pubDate>Tue, 12 May 2026 14:18:50 +0000</pubDate>
      <link>https://dev.to/travelingwilbur/set-based-updates-in-rails-4-hours-to-8-seconds-dkd</link>
      <guid>https://dev.to/travelingwilbur/set-based-updates-in-rails-4-hours-to-8-seconds-dkd</guid>
      <description>&lt;p&gt;I once inherited a background job to deactivate stale users. In production, a job processing 50,000 users that should have taken a minute was taking over 4 hours, consuming 2GB of RAM, and frequently timing out.&lt;/p&gt;

&lt;p&gt;The culprit was a classic Rails performance pitfall: the N+1 update loop. The code looked innocent:&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="c1"&gt;# Find users whose last login was more than 90 days ago&lt;/span&gt;
&lt;span class="n"&gt;users_to_deactivate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"last_login_at &amp;lt; ?"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;90&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;days&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ago&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;users_to_deactivate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;each&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;user&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="c1"&gt;# This runs one UPDATE query for every single user&lt;/span&gt;
  &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;active: &lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This generates a flood of queries, hammering the database with thousands of individual transactions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- The N+1 Update Hell&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="nv"&gt;"users"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="nv"&gt;"users"&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;last_login_at&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="s1"&gt;'2025-11-30...'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="nv"&gt;"users"&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="nv"&gt;"active"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;"updated_at"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'...'&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="nv"&gt;"users"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"id"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="nv"&gt;"users"&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="nv"&gt;"active"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;"updated_at"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'...'&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="nv"&gt;"users"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"id"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;-- ... 49,998 more UPDATE statements&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This approach is not just inefficient; it's hostile to your database. There is a much better way.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;A Note on Indexes&lt;/strong&gt;&lt;br&gt;
All performance advice in this post assumes your query conditions are indexed. The &lt;code&gt;User.where("last_login_at &amp;lt; ?", ...)&lt;/code&gt; query is only fast if you have a database index on the &lt;code&gt;last_login_at&lt;/code&gt; column. Without it, the &lt;code&gt;SELECT&lt;/code&gt; itself will be slow.&lt;/p&gt;

&lt;p&gt;You can check this with &lt;code&gt;User.where("last_login_at &amp;lt; ?", 90.days.ago).explain&lt;/code&gt;. If you see a &lt;code&gt;Seq Scan&lt;/code&gt; (Sequential Scan), you need an index:&lt;/p&gt;


&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# db/migrate/YYYYMMDDHHMMSS_add_index_to_users_last_login_at.rb&lt;/span&gt;
&lt;span class="n"&gt;add_index&lt;/span&gt; &lt;span class="ss"&gt;:users&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:last_login_at&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  The Power of &lt;code&gt;update_all&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Instead of pulling 50,000 records into memory, we can tell the database to update the entire set in a single command. Active Record's &lt;code&gt;update_all&lt;/code&gt; constructs one SQL &lt;code&gt;UPDATE&lt;/code&gt; statement and sends it directly to the database.&lt;/p&gt;

&lt;p&gt;Let's refactor the slow job:&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;count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"last_login_at &amp;lt; ?"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;90&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;days&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ago&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update_all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;active: &lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;updated_at: &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;current&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt; &lt;span class="s2"&gt;"Deactivated &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; users."&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This generates one beautiful, efficient SQL query and returns the number of rows affected. The result: 4 hours becomes 8 seconds.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Sharp Edges of &lt;code&gt;update_all&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;This performance comes with a critical trade-off: &lt;code&gt;update_all&lt;/code&gt; bypasses most of the ActiveRecord lifecycle.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;It ignores validations.&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;It skips callbacks.&lt;/strong&gt; This will break any business logic in &lt;code&gt;after_save&lt;/code&gt; or &lt;code&gt;after_commit&lt;/code&gt;, like sending notifications or invalidating caches.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  The &lt;code&gt;updated_at&lt;/code&gt; Trap
&lt;/h4&gt;

&lt;p&gt;The most insidious side effect is that &lt;strong&gt;&lt;code&gt;update_all&lt;/code&gt; does not automatically update the &lt;code&gt;updated_at&lt;/code&gt; timestamp.&lt;/strong&gt; This will break any downstream system that relies on it for cache invalidation, audit trails, or synchronization.&lt;/p&gt;

&lt;p&gt;Imagine a UI that caches a user's profile:&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;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"user-&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;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;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;updated_at&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_i&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="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you use &lt;code&gt;update_all(active: false)&lt;/code&gt;, &lt;code&gt;updated_at&lt;/code&gt; remains unchanged. The cache key stays the same, and your UI continues to show the user as active until the cache expires. The fix is to set it manually, as shown in the example above.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Middle Ground: &lt;code&gt;update_columns&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;update_columns&lt;/code&gt; is for when you &lt;em&gt;already have&lt;/em&gt; an object in memory and need to perform a targeted update while bypassing callbacks and validations. It's a scalpel for a single record.&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;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find_by&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;email: &lt;/span&gt;&lt;span class="s2"&gt;"some_user@example.com"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# ... some complex logic ...&lt;/span&gt;

&lt;span class="c1"&gt;# Now, update just one attribute without triggering callbacks.&lt;/span&gt;
&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update_columns&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;login_attempts: &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Unlike &lt;code&gt;update_all&lt;/code&gt;, &lt;code&gt;update_columns&lt;/code&gt; &lt;em&gt;does&lt;/em&gt; touch &lt;code&gt;updated_at&lt;/code&gt; by default (though this can be configured). It is &lt;strong&gt;not&lt;/strong&gt; for bulk operations; using it in a loop brings you right back to the N+1 problem.&lt;/p&gt;

&lt;h3&gt;
  
  
  War Story #2: The Death-by-a-Thousand-Increments
&lt;/h3&gt;

&lt;p&gt;I once debugged a Rails app where a popular blog post page was taking 20 seconds to load. The culprit? The controller was incrementing view counts like this for every recommended article on the page:&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="c1"&gt;# ANTI-PATTERN: 50 posts on the page = 50 UPDATE queries&lt;/span&gt;
&lt;span class="n"&gt;recommended_posts&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;post&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;increment!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:view_count&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;increment!&lt;/code&gt; is just &lt;code&gt;update&lt;/code&gt; under the hood. The fix was to switch to &lt;code&gt;update_all&lt;/code&gt; with a SQL fragment, which dropped the response time to 200ms.&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="c1"&gt;# GOOD: One query to increment all counters&lt;/span&gt;
&lt;span class="no"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;id: &lt;/span&gt;&lt;span class="n"&gt;recommended_posts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="ss"&gt;:id&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update_all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"view_count = view_count + 1"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Going Deeper: Safe Heterogeneous Updates with SQL &lt;code&gt;CASE&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;update_all&lt;/code&gt; is great for applying the &lt;em&gt;same&lt;/em&gt; change to many records. But what if you need to update a set of records, each with a &lt;em&gt;different&lt;/em&gt; value, like reordering items in a playlist?&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;⚠️ &lt;strong&gt;Security Warning: SQL Injection&lt;/strong&gt;&lt;br&gt;
Building raw SQL queries with string interpolation is extremely dangerous. The following example demonstrates a &lt;strong&gt;safe&lt;/strong&gt; approach using &lt;code&gt;sanitize_sql_array&lt;/code&gt;. &lt;strong&gt;Never&lt;/strong&gt; inject raw user input directly into SQL strings.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Use case: Reorder a list of tasks from user input&lt;/span&gt;
&lt;span class="c1"&gt;# updates = { "1" =&amp;gt; 1, "2" =&amp;gt; 2, "3" =&amp;gt; 3 } where key is task_id and value is new position&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reorder_tasks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;updates&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="c1"&gt;# Build the CASE statement using sanitize_sql_array for each condition&lt;/span&gt;
  &lt;span class="n"&gt;case_sql&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;updates&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;position&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="c1"&gt;# Ensure id and position are integers to prevent injection&lt;/span&gt;
    &lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sanitize_sql_array&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s2"&gt;"WHEN ? THEN ?"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;position&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_i&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;" "&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="c1"&gt;# Construct the final, safe update statement&lt;/span&gt;
  &lt;span class="n"&gt;update_sql&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s2"&gt;"position = CASE id &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;case_sql&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; END, updated_at = :now"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;now: &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;current&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;

  &lt;span class="no"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;id: &lt;/span&gt;&lt;span class="n"&gt;updates&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;update_all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;update_sql&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the most performant way to handle complex batch updates, combining the power of raw SQL with the safety of Rails' sanitization.&lt;/p&gt;

&lt;h3&gt;
  
  
  Production Considerations
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Locking and Concurrency
&lt;/h4&gt;

&lt;p&gt;A long-running &lt;code&gt;update_all&lt;/code&gt; can lock many rows, blocking other requests. This can cause timeouts in a high-traffic application. It's often better to process records in batches to reduce lock duration.&lt;/p&gt;

&lt;h4&gt;
  
  
  Processing in Batches
&lt;/h4&gt;

&lt;p&gt;For very large updates, use &lt;code&gt;in_batches&lt;/code&gt; to break the work into smaller chunks. This runs more queries but keeps transactions short and locks minimal.&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="c1"&gt;# Process 50,000 users in batches of 5,000&lt;/span&gt;
&lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"last_login_at &amp;lt; ?"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;90&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;days&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ago&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;in_batches&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;of: &lt;/span&gt;&lt;span class="mi"&gt;5000&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;batch&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="n"&gt;batch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update_all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;active: &lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;updated_at: &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;current&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="nb"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# Optional: yield to other processes&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The Decision Framework
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;&lt;code&gt;update&lt;/code&gt;&lt;/strong&gt;: The default for single-record updates where callbacks and validations &lt;strong&gt;must&lt;/strong&gt; run.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;&lt;code&gt;update_columns&lt;/code&gt;&lt;/strong&gt;: A scalpel for single-record updates where callbacks and validations &lt;strong&gt;must&lt;/strong&gt; be skipped.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;&lt;code&gt;update_all&lt;/code&gt;&lt;/strong&gt;: The workhorse for bulk-updating many records to the &lt;strong&gt;same&lt;/strong&gt; value.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;&lt;code&gt;update_all&lt;/code&gt; with &lt;code&gt;CASE&lt;/code&gt;&lt;/strong&gt;: The specialist tool for bulk-updating many records to &lt;strong&gt;different&lt;/strong&gt; values.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Avoid set-based updates entirely when you need to trigger side effects (like sending emails) for each updated record or when you need to run model validations.&lt;/p&gt;

&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;The next time you write &lt;code&gt;.each { |record| record.update(...) }&lt;/code&gt;, pause. Ask yourself: am I processing complex business logic for each record, or am I simply updating a set of records based on a condition?&lt;/p&gt;

&lt;p&gt;If you're just changing data, you're in set-based territory. Choosing the right tool for the job is the mark of a seasoned developer. Your database will thank you. And when your background job finishes in 8 seconds instead of timing out after 4 hours, you will too.&lt;/p&gt;

</description>
      <category>rails</category>
      <category>sql</category>
      <category>performance</category>
      <category>ruby</category>
    </item>
    <item>
      <title>How CLAUDE.md actually works</title>
      <dc:creator>Wilbur Suero</dc:creator>
      <pubDate>Sat, 09 May 2026 06:33:47 +0000</pubDate>
      <link>https://dev.to/travelingwilbur/how-claudemd-actually-works-3l1e</link>
      <guid>https://dev.to/travelingwilbur/how-claudemd-actually-works-3l1e</guid>
      <description>&lt;p&gt;Most engineers write &lt;code&gt;CLAUDE.md&lt;/code&gt; like a README. They put their stack, a few preferences, maybe a note about testing. The agent reads it, nods politely, and proceeds to write code shaped by its training data rather than their codebase.&lt;/p&gt;

&lt;p&gt;The file exists. It doesn't do much.&lt;/p&gt;

&lt;p&gt;The problem isn't effort. It's category. A README describes a project. A specification constrains behavior. &lt;code&gt;CLAUDE.md&lt;/code&gt; only works as the second thing, and most engineers have never written a specification for an agent before, so they reach for the format they know.&lt;/p&gt;

&lt;p&gt;This post covers three dimensions of getting it right: how to structure the file, how to write rules the agent actually follows, and how to keep the file current as the codebase evolves.&lt;/p&gt;




&lt;h2&gt;
  
  
  Structure: one file is the wrong model
&lt;/h2&gt;

&lt;p&gt;Claude Code reads &lt;code&gt;CLAUDE.md&lt;/code&gt; from the project root. What most engineers don't know is that it also reads &lt;code&gt;CLAUDE.md&lt;/code&gt; from any subdirectory it's working in, and those files inherit from the root.&lt;/p&gt;

&lt;p&gt;This matters because context is directional. The rules that apply when the agent is writing a migration are not the rules that apply when it's writing a Sidekiq job. A single root file that tries to encode both produces either redundancy or contradiction.&lt;/p&gt;

&lt;p&gt;A structure that works:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;project/
├── CLAUDE.md                    # global constraints, stack, communication protocol
├── app/
│   ├── models/CLAUDE.md         # schema rules, validation patterns, query constraints
│   ├── jobs/CLAUDE.md           # idempotency rules, retry logic, failure handling
│   └── services/CLAUDE.md       # when to extract, interface contracts, no god objects
└── db/
    └── migrate/CLAUDE.md        # migration rules, index requirements, reversibility
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each file answers: what does good work look like in this specific part of the codebase?&lt;/p&gt;

&lt;p&gt;The root file handles things true everywhere: pinned versions, architectural decisions already made, the communication protocol (plan before execute, ask before touching schema). The directory files encode local expertise. When the agent opens &lt;code&gt;app/jobs/&lt;/code&gt;, it reads both. The local file wins on conflicts.&lt;/p&gt;

&lt;p&gt;Write the root file last. You'll know what belongs there after writing the directory files, because you'll see what keeps repeating.&lt;/p&gt;




&lt;h2&gt;
  
  
  Structure: separate facts from rules
&lt;/h2&gt;

&lt;p&gt;Inside any &lt;code&gt;CLAUDE.md&lt;/code&gt;, two categories of content live at different distances from the agent's decisions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Facts&lt;/strong&gt; describe the environment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gu"&gt;## Stack&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Ruby 3.3.4
&lt;span class="p"&gt;-&lt;/span&gt; Rails 7.2.1
&lt;span class="p"&gt;-&lt;/span&gt; PostgreSQL 15
&lt;span class="p"&gt;-&lt;/span&gt; Sidekiq 7.3
&lt;span class="p"&gt;-&lt;/span&gt; pgvector 0.7 (used in app/models/document.rb, not widely available)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Rules&lt;/strong&gt; constrain behavior:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gu"&gt;## Database&lt;/span&gt;
Never update records in a loop. Use set-based updates.
Never call update_all without a WHERE condition.
Every query touching the documents table needs an EXPLAIN ANALYZE before merging.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Engineers mix these in paragraph form and the agent treats them with roughly equal weight. Separate them with explicit headers. Facts get a section. Rules get a section. The agent applies rules when deciding; it consults facts when writing. Conflating them produces a file that's harder to maintain and rules that get buried.&lt;/p&gt;




&lt;h2&gt;
  
  
  Content: negative rules outperform positive ones
&lt;/h2&gt;

&lt;p&gt;"Prefer set-based updates" loses to "never update records in a loop." Both encode the same intent. One requires the agent to weigh it against competing preferences. The other removes the decision entirely.&lt;/p&gt;

&lt;p&gt;Agents generalize from positive instructions; they treat negative ones as constraints. Constraints bind more consistently than preferences, especially when the agent is mid-task and optimizing for completion.&lt;/p&gt;

&lt;p&gt;The practical test: rewrite every "prefer X" in your &lt;code&gt;CLAUDE.md&lt;/code&gt; as "never Y." If you can't, the rule isn't specific enough to be useful.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# Weak&lt;/span&gt;
Prefer idiomatic Rails patterns where possible.

&lt;span class="gh"&gt;# Useful&lt;/span&gt;
Never extract a service object without a domain justification written in a comment above the class.
Never rescue StandardError without logging the exception and the calling context.
Never write a background job where perform is not safe to call twice with the same arguments.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first rule tells the agent something it already knows. The second, third, and fourth tell it something about your codebase specifically, stated as a constraint it can't argue with.&lt;/p&gt;




&lt;h2&gt;
  
  
  Content: attach the why to every constraint
&lt;/h2&gt;

&lt;p&gt;A rule without a reason the agent follows literally. A rule with a reason it generalizes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# Without why&lt;/span&gt;
Never use update_all without conditions.

&lt;span class="gh"&gt;# With why&lt;/span&gt;
Never use update_all without conditions. A bare update_all on the users table
has caused production data corruption twice in this codebase because background
jobs held stale references. Always scope it.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both rules produce the same behavior on the exact scenario described. The second one produces correct behavior on the novel scenario the agent hasn't seen yet, because it understands the failure mode. The agent can ask "does this situation have the same risk?" and answer correctly.&lt;/p&gt;

&lt;p&gt;This is the highest-leverage thing you can do to &lt;code&gt;CLAUDE.md&lt;/code&gt;. Every rule that currently has no attached reason is a rule the agent interprets locally and fails to generalize.&lt;/p&gt;

&lt;p&gt;The format that works:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gs"&gt;**Rule:**&lt;/span&gt; [constraint]
&lt;span class="gs"&gt;**Why:**&lt;/span&gt; [failure mode or past incident that produced the rule]
&lt;span class="gs"&gt;**Exception:**&lt;/span&gt; [when the rule doesn't apply, if any]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Not every rule needs a formal format. But every non-obvious rule needs a reason.&lt;/p&gt;




&lt;h2&gt;
  
  
  Content: priority ordering for conflicting rules
&lt;/h2&gt;

&lt;p&gt;Rules conflict. An instruction to keep methods short conflicts with an instruction to avoid multiple database queries. An instruction to match existing patterns conflicts with an instruction to apply a new convention to all new files.&lt;/p&gt;

&lt;p&gt;Without explicit priority, the agent resolves conflicts by training data, not by your intent.&lt;/p&gt;

&lt;p&gt;Add a section near the top of the root file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gu"&gt;## Rule priority&lt;/span&gt;

When instructions conflict, apply them in this order:
&lt;span class="p"&gt;
1.&lt;/span&gt; Correctness — no silent failures, no data loss
&lt;span class="p"&gt;2.&lt;/span&gt; Schema integrity — migrations reviewed, constraints in place before code
&lt;span class="p"&gt;3.&lt;/span&gt; Explicit rules in this file — over Rails defaults, over conventions
&lt;span class="p"&gt;4.&lt;/span&gt; Rails conventions — over agent judgment
&lt;span class="p"&gt;5.&lt;/span&gt; Agent judgment — last resort only
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This sounds obvious written out. It isn't obvious to an agent mid-task, and the cost of the agent resolving a conflict incorrectly is a file it has already changed.&lt;/p&gt;




&lt;h2&gt;
  
  
  Content: show examples inline
&lt;/h2&gt;

&lt;p&gt;Instructions describe intent. Examples demonstrate standard.&lt;/p&gt;

&lt;p&gt;For any rule where the wrong implementation is plausible, put both versions in the file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gu"&gt;## Background jobs&lt;/span&gt;

Jobs must survive duplicate execution. Test this by reading perform as if it runs twice in sequence with the same arguments.

Bad:

def perform(order_id)
  order = Order.find(order_id)
  order.update!(status: :processed)
  OrderMailer.confirmation(order).deliver_later
end&lt;span class="sb"&gt;


&lt;/span&gt;Good:

def perform(order_id)
  order = Order.find(order_id)
  return if order.processed?

  order.update!(status: :processed)
  OrderMailer.confirmation(order).deliver_later
end&lt;span class="sb"&gt;


&lt;/span&gt;The return guard is the idempotency checkpoint. Every job needs one.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The agent calibrates to examples faster than to instructions. It can see the difference between the two implementations immediately; it can't always infer the difference from a prose rule alone.&lt;/p&gt;




&lt;h2&gt;
  
  
  Workflow: let the agent update the file
&lt;/h2&gt;

&lt;p&gt;This is the trick most engineers skip entirely.&lt;/p&gt;

&lt;p&gt;After any session where the agent encountered a pattern it didn't handle correctly, or where you corrected its output more than once, end the session with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Before we close: what did you learn in this session that should be added to CLAUDE.md?
List specific rules or facts, not generalizations. Draft the additions in the format
already used in the file.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The agent has context you don't at the end of a long session. It knows which rules it consulted, which ones it had to interpret ambiguously, and where it made a decision it wasn't confident about. It can surface those explicitly if you ask.&lt;/p&gt;

&lt;p&gt;Review what it produces. Reject what's too general. Add what's specific and accurate.&lt;/p&gt;

&lt;p&gt;This loop does two things. First, it keeps &lt;code&gt;CLAUDE.md&lt;/code&gt; current without requiring you to remember what happened three sessions ago. Second, it produces rules written in a format the agent understands, because the agent wrote them.&lt;/p&gt;




&lt;h2&gt;
  
  
  Workflow: treat CLAUDE.md as a living specification under version control
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;CLAUDE.md&lt;/code&gt; should be in git. Commit messages should say why a rule changed, not just that it did.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Add explicit idempotency requirement to jobs/CLAUDE.md

Background job wrote duplicate notifications in production on 2024-11-12
because the retry logic ran perform twice. Added rule with failure mode attached.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Read the git history of &lt;code&gt;CLAUDE.md&lt;/code&gt; the same way you'd read the git history of a schema file. It tells you what the codebase has learned about itself. A rule with no commit context is a rule you'll forget to defend when someone asks why it exists.&lt;/p&gt;

&lt;p&gt;A practical schedule: review &lt;code&gt;CLAUDE.md&lt;/code&gt; every Friday, same as any other configuration that controls production behavior. Ask three questions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Which rules did the agent follow incorrectly this week? Rewrite them.&lt;/li&gt;
&lt;li&gt;Which decisions did we make that aren't encoded yet? Add them.&lt;/li&gt;
&lt;li&gt;Which rules are stale because the codebase has changed? Remove or update them.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Ten minutes. The agent's output quality is a direct function of how current the file is. Stale context produces output that was correct for the codebase you had six months ago.&lt;/p&gt;




&lt;h2&gt;
  
  
  Workflow: ask the agent to audit its own compliance
&lt;/h2&gt;

&lt;p&gt;At the start of a session where the work is consequential, run this before any task:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Read CLAUDE.md, then read the files you're about to modify.
Before writing any code, list:
1. Which rules in CLAUDE.md apply to this task
2. Any existing code in the target files that already violates those rules
3. Any ambiguity in the rules that you'll need me to resolve
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This surfaces two problems before they compound: rules the agent would have applied inconsistently, and existing violations it would have implicitly extended by matching surrounding code.&lt;/p&gt;

&lt;p&gt;The audit takes thirty seconds. It prevents the situation where the agent writes new code that follows your rules while the file it's modifying already breaks them, and now you have an inconsistency with the agent's fingerprints on both sides of it.&lt;/p&gt;




&lt;h2&gt;
  
  
  The underlying model
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;CLAUDE.md&lt;/code&gt; is a specification for a collaborator who has no memory between sessions, has read every Rails codebase that existed before its training cutoff, and defaults to the most common pattern when your instructions are ambiguous.&lt;/p&gt;

&lt;p&gt;The file's job is to replace "most common Rails pattern" with "this specific codebase's pattern." It can only do that job if the rules are negative and specific, the reasons are attached, the examples show the wrong and right version side by side, and the file is updated when the codebase learns something new.&lt;/p&gt;

&lt;p&gt;Write it like a contract your future self will have to enforce. Because every session, you're handing the codebase to someone who has never seen it before and has to be told everything that matters.&lt;/p&gt;

&lt;p&gt;The README version of &lt;code&gt;CLAUDE.md&lt;/code&gt; produces a generic collaborator. The specification version produces one that can be trusted with a schema migration.&lt;/p&gt;

</description>
      <category>claude</category>
      <category>ai</category>
    </item>
    <item>
      <title>Writing Maintainable AI Prompts in Rails with Promptly</title>
      <dc:creator>Wilbur Suero</dc:creator>
      <pubDate>Mon, 18 Aug 2025 22:03:48 +0000</pubDate>
      <link>https://dev.to/travelingwilbur/writing-maintainable-ai-prompts-in-rails-with-promptly-1dnf</link>
      <guid>https://dev.to/travelingwilbur/writing-maintainable-ai-prompts-in-rails-with-promptly-1dnf</guid>
      <description>&lt;p&gt;A few months ago, I was helping a Rails team add AI-generated onboarding emails to their app. It seemed simple at first: drop an &lt;code&gt;OpenAI.chat(...)&lt;/code&gt; call inside a mailer, pass in the user’s name, and let the model draft a warm welcome.&lt;/p&gt;

&lt;p&gt;By the second week, things got messy.&lt;/p&gt;

&lt;p&gt;Different controllers and jobs had their own inline prompts.&lt;/p&gt;

&lt;p&gt;Some prompts were copy-pasted with slight tweaks (“friendly tone”, “professional tone”, etc.).&lt;/p&gt;

&lt;p&gt;Marketing wanted localized versions for Spanish and Portuguese.&lt;/p&gt;

&lt;p&gt;QA asked, “How do we know the prompts didn’t change by accident?”&lt;/p&gt;

&lt;p&gt;We ended up with prompt spaghetti—hard to test, impossible to translate cleanly, and brittle whenever we tried to refactor.&lt;/p&gt;

&lt;p&gt;That experience sparked the idea for &lt;a href="https://github.com/wilburhimself/promptly" rel="noopener noreferrer"&gt;Promptly&lt;/a&gt;: a small gem that brings Rails conventions to AI prompt management.&lt;/p&gt;




&lt;h3&gt;
  
  
  The Problem: Prompts Don’t Belong Scattered in Code
&lt;/h3&gt;

&lt;p&gt;Rails developers already know the pain of hard-coded strings—why we use views, partials, and I18n instead of littering messages across controllers. Yet many of us are now hardcoding AI prompts directly in service objects or background jobs.&lt;/p&gt;

&lt;p&gt;It doesn’t scale.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Duplication: The same wording lives in multiple places.&lt;/li&gt;
&lt;li&gt;Localization headaches: Inline strings don’t play well with I18n.&lt;/li&gt;
&lt;li&gt;No safety net: Refactors can silently change how the AI behaves.&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  The Solution: Promptly
&lt;/h3&gt;

&lt;p&gt;Promptly lets you define AI prompts as ERB or Liquid templates, stored in a Rails-native directory structure. Just like you’d render a view, you render a prompt:&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="c1"&gt;# app/prompts/welcome_email.erb&lt;/span&gt;
&lt;span class="no"&gt;Hello&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sx"&gt;%= @user.name %&amp;gt;, welcome to our service!
We’re excited to have you join.

# In your mailer or service:
render_prompt("welcome_email", user: @user)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This unlocks the same benefits Rails developers already rely on elsewhere:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Maintainability&lt;/strong&gt; → All prompts live in one place.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Localization&lt;/strong&gt; → Templates can use I18n just like views.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Testability&lt;/strong&gt; → You can write RSpec tests that assert on rendered prompt output.&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Why Rails Conventions Matter
&lt;/h3&gt;

&lt;p&gt;Rails has always thrived on convention over configuration. Promptly doesn’t reinvent the wheel; it extends familiar concepts (views, templates, helpers) into the AI space.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Instead of partials, you have prompt templates.&lt;/li&gt;
&lt;li&gt;Instead of locals, you have prompt variables.&lt;/li&gt;
&lt;li&gt;Instead of ad-hoc strings, you have structured, versionable files.&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Getting Started
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Add it to your Gemfile:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s1"&gt;'promptly'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;2.Create your first template in app/prompts.&lt;br&gt;
3.Render it anywhere with render_prompt.&lt;br&gt;
4.Add tests to verify the output.&lt;/p&gt;

&lt;p&gt;Full docs are in the &lt;a href="https://github.com/wilburhimself/promptly" rel="noopener noreferrer"&gt;GitHub repo&lt;/a&gt;.&lt;/p&gt;




&lt;h3&gt;
  
  
  Looking Ahead
&lt;/h3&gt;

&lt;p&gt;Promptly is intentionally small. It’s not trying to be a full-blown AI orchestration platform. But by solving one narrow pain point—making prompts maintainable, testable, and Rails-friendly—it can help Rails apps adopt AI without chaos.&lt;/p&gt;

&lt;p&gt;The next steps may include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Prompt versioning.&lt;/li&gt;
&lt;li&gt;Preview workflows in Rails consoles.&lt;/li&gt;
&lt;li&gt;Deeper integration with related gems like semantic search adapters.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;If you’re a Rails developer experimenting with AI, try &lt;a href="https://github.com/wilburhimself/promptly" rel="noopener noreferrer"&gt;Promptly&lt;/a&gt;. It may save you from prompt spaghetti before it starts.&lt;/p&gt;

&lt;p&gt;Feedback and contributions are welcome, the best ideas usually come from real-world use.&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>rails</category>
      <category>promptengineering</category>
    </item>
    <item>
      <title>🚨 Introducing GemGuard: Automated Security for Ruby Gems (Scan, SBOM, Typosquat, Auto-Fix)</title>
      <dc:creator>Wilbur Suero</dc:creator>
      <pubDate>Mon, 11 Aug 2025 02:05:38 +0000</pubDate>
      <link>https://dev.to/travelingwilbur/introducing-gemguard-automated-security-for-ruby-gems-scan-sbom-typosquat-auto-fix-2p2g</link>
      <guid>https://dev.to/travelingwilbur/introducing-gemguard-automated-security-for-ruby-gems-scan-sbom-typosquat-auto-fix-2p2g</guid>
      <description>&lt;p&gt;&lt;strong&gt;Links:&lt;/strong&gt;  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GitHub: &lt;a href="https://github.com/wilburhimself/gem_guard" rel="noopener noreferrer"&gt;github.com/wilburhimself/gem_guard&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;RubyGems: &lt;a href="https://rubygems.org/gems/gem_guard" rel="noopener noreferrer"&gt;rubygems.org/gems/gem_guard&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;✅ Scan dependencies for known vulnerabilities (OSV.dev + Ruby Advisory DB)
&lt;/li&gt;
&lt;li&gt;🕵️ Detect typosquat packages before they bite
&lt;/li&gt;
&lt;li&gt;📜 Generate SPDX / CycloneDX SBOMs
&lt;/li&gt;
&lt;li&gt;🛠 Auto-fix vulnerable gems safely
&lt;/li&gt;
&lt;li&gt;⚡ Clean CLI + CI-ready
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Version:&lt;/strong&gt; 1.1.x&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Why GemGuard?
&lt;/h2&gt;

&lt;p&gt;Because security shouldn’t be an afterthought. It should be:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Pragmatic&lt;/strong&gt; – only what matters, no noise&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fast&lt;/strong&gt; – instant feedback in dev or CI&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Integrated&lt;/strong&gt; – works with your normal Ruby workflow&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  What is GemGuard?
&lt;/h2&gt;

&lt;p&gt;GemGuard is a lightweight Ruby security tool that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Scans your &lt;code&gt;Gemfile.lock&lt;/code&gt; for known vulnerabilities&lt;/li&gt;
&lt;li&gt;Detects typosquat risks via fuzzy matching&lt;/li&gt;
&lt;li&gt;Generates SBOMs (SPDX and CycloneDX)&lt;/li&gt;
&lt;li&gt;Auto-fixes vulnerable gems with safe version upgrades&lt;/li&gt;
&lt;li&gt;Plays nicely with CI/CD&lt;/li&gt;
&lt;/ul&gt;




&lt;h4&gt;
  
  
  Installation
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Add to your Gemfile (recommended for projects)&lt;/span&gt;
gem &lt;span class="s2"&gt;"gem_guard"&lt;/span&gt;, &lt;span class="s2"&gt;"~&amp;gt; 1.1"&lt;/span&gt;

&lt;span class="c"&gt;# Or install globally&lt;/span&gt;
gem &lt;span class="nb"&gt;install &lt;/span&gt;gem_guard
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Verify:
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gem_guard version
&lt;span class="c"&gt;# =&amp;gt; 1.1.x&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Quick Start
&lt;/h4&gt;

&lt;p&gt;Scan your project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gem_guard scan
&lt;span class="c"&gt;# ✅ No vulnerabilities found!&lt;/span&gt;
&lt;span class="c"&gt;# or exits non‑zero if issues are found&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Detect typosquats:
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gem_guard typosquat
&lt;span class="c"&gt;# No potential typosquat dependencies found.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Generate an SBOM:
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gem_guard sbom &lt;span class="nt"&gt;--format&lt;/span&gt; spdx &lt;span class="nt"&gt;--output&lt;/span&gt; sbom.spdx.json
gem_guard sbom &lt;span class="nt"&gt;--format&lt;/span&gt; cyclonedx &lt;span class="nt"&gt;--output&lt;/span&gt; bom.cdx.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Auto‑Fix Vulnerabilities
&lt;/h4&gt;

&lt;p&gt;Preview (dry run):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gem_guard fix &lt;span class="nt"&gt;--dry-run&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Apply fixes (creates a Gemfile.lock backup by default):
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gem_guard fix
&lt;span class="c"&gt;# 📦 Created backup: Gemfile.lock.backup.2025...&lt;/span&gt;
&lt;span class="c"&gt;# ✅ Updated nokogiri to 1.18.9&lt;/span&gt;
&lt;span class="c"&gt;# 🔄 Running bundle install to update lockfile...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Options:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;--interactive&lt;/code&gt;: confirm each update&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--no-backup&lt;/code&gt;: skip lockfile backup&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--gemfile&lt;/code&gt;, --lockfile: custom paths&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Tip: Re-scan after fixing&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gem_guard scan
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Clean CLI
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gem_guard &lt;span class="nt"&gt;--help&lt;/span&gt;
&lt;span class="c"&gt;# config, scan, typosquat, sbom, fix, version&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Exit codes:
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;0: success / no vulns&lt;/li&gt;
&lt;li&gt;1: vulnerabilities found&lt;/li&gt;
&lt;li&gt;2: errors (e.g., missing files)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  CI/CD Integration (GitHub Actions)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;security-scan&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;gemguard&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ruby/setup-ruby@v1&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;ruby-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;3.3'&lt;/span&gt;
          &lt;span class="na"&gt;bundler-cache&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;gem install gem_guard&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;gem_guard scan --format json &amp;gt; gemguard-report.json&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;gem_guard typosquat --format json &amp;gt; typosquat-report.json&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Upload reports&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/upload-artifact@v4&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;gemguard-reports&lt;/span&gt;
          &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
            &lt;span class="s"&gt;gemguard-report.json&lt;/span&gt;
            &lt;span class="s"&gt;typosquat-report.json&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Fail builds on vulnerabilities (default behavior). If you want non-blocking scans (e.g., on main), wrap with || true or use matrix strategies.&lt;/p&gt;

&lt;h4&gt;
  
  
  Configuration
&lt;/h4&gt;

&lt;p&gt;Create .gemguard.yml:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;lockfile&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Gemfile.lock&lt;/span&gt;
&lt;span class="na"&gt;output&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;format&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;table&lt;/span&gt;   &lt;span class="c1"&gt;# table | json&lt;/span&gt;
&lt;span class="na"&gt;typosquat&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;similarity_threshold&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.82&lt;/span&gt;
  &lt;span class="na"&gt;risk_levels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;high&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.9&lt;/span&gt;
    &lt;span class="na"&gt;medium&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.85&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;View current config:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gem_guard config &lt;span class="nt"&gt;--show&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Why GemGuard?
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Minimal setup, zero noise&lt;/li&gt;
&lt;li&gt;Pragmatic defaults, sensible exit codes&lt;/li&gt;
&lt;li&gt;Works offline for typosquat via fallback popular gems&lt;/li&gt;
&lt;li&gt;Well-tested (RSpec), standardrb formatting&lt;/li&gt;
&lt;li&gt;Designed for CI from day 1&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  How It Compares
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Bundler Audit: great for advisories; GemGuard adds typosquat + SBOM + auto-fix&lt;/li&gt;
&lt;li&gt;OSV-Scanner: broad ecosystem; GemGuard is Ruby-first with tighter UX and auto-fix&lt;/li&gt;
&lt;li&gt;Trivy/Grype: container focus; GemGuard slots into pure-Ruby pipelines easily&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Use GemGuard standalone or alongside your existing stack.&lt;/p&gt;

&lt;h3&gt;
  
  
  Roadmap
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Enriched advisories (GHSA/CVE links, CVSS)&lt;/li&gt;
&lt;li&gt;Optional dependency graph visualizations&lt;/li&gt;
&lt;li&gt;Interactive TUI&lt;/li&gt;
&lt;li&gt;More fix strategies and guards&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Contribute / Feedback
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Issues/PRs welcome: add tests, keep it minimal and intention-revealing&lt;/li&gt;
&lt;li&gt;Prefer failing test → minimal fix → refactor&lt;/li&gt;
&lt;li&gt;Security disclosures: see SECURITY.md&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Try It Now
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gem &lt;span class="nb"&gt;install &lt;/span&gt;gem_guard
gem_guard scan
gem_guard typosquat
gem_guard fix &lt;span class="nt"&gt;--dry-run&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If this helps you ship safer Ruby apps with less fuss, drop a ❤️ and share!&lt;/p&gt;

&lt;p&gt;— Built for Rubyists who like fast feedback, clean CLIs, and reliable automation.&lt;/p&gt;

&lt;p&gt;Issues and PRs welcome → &lt;a href="https://github.com/wilburhimself/gem_guard" rel="noopener noreferrer"&gt;github.com/wilburhimself/gem_guard&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>security</category>
      <category>devops</category>
      <category>opensource</category>
    </item>
    <item>
      <title>How I Built a RAG System in Rails Using Nomic Embeddings and OpenAI</title>
      <dc:creator>Wilbur Suero</dc:creator>
      <pubDate>Fri, 18 Jul 2025 19:48:41 +0000</pubDate>
      <link>https://dev.to/travelingwilbur/how-i-built-a-rag-system-in-rails-using-nomic-embeddings-and-openai-154e</link>
      <guid>https://dev.to/travelingwilbur/how-i-built-a-rag-system-in-rails-using-nomic-embeddings-and-openai-154e</guid>
      <description>&lt;p&gt;Retrieval-Augmented Generation (RAG) lets you bring your own data to LLMs—and get real answers. I’ll show how I used the open-source nomic-embed-text-v2-moe model for semantic search in a Rails app, while still using OpenAI for generation.&lt;/p&gt;

&lt;h3&gt;
  
  
  🧠 What is RAG?
&lt;/h3&gt;

&lt;p&gt;RAG (Retrieval-Augmented Generation) enhances LLMs by feeding them relevant chunks of your data before generating a response. Instead of fine-tuning, we give the model useful context.&lt;/p&gt;

&lt;p&gt;Here's the basic pipeline:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;User&lt;/span&gt; &lt;span class="nt"&gt;Question&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;
        &lt;span class="err"&gt;↓&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;Embed&lt;/span&gt; &lt;span class="nt"&gt;the&lt;/span&gt; &lt;span class="nt"&gt;Question&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;Nomic&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;
        &lt;span class="err"&gt;↓&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;Vector&lt;/span&gt; &lt;span class="nt"&gt;Search&lt;/span&gt; &lt;span class="nt"&gt;in&lt;/span&gt; &lt;span class="nt"&gt;PgVector&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;
        &lt;span class="err"&gt;↓&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;Retrieve&lt;/span&gt; &lt;span class="nt"&gt;Relevant&lt;/span&gt; &lt;span class="nt"&gt;Chunks&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;
        &lt;span class="err"&gt;↓&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;Assemble&lt;/span&gt; &lt;span class="nt"&gt;Prompt&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;
        &lt;span class="err"&gt;↓&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;Generate&lt;/span&gt; &lt;span class="nt"&gt;Answer&lt;/span&gt; &lt;span class="nt"&gt;with&lt;/span&gt; &lt;span class="nt"&gt;OpenAI&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  🧰 The Stack
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Rails&lt;/strong&gt; – Backend framework, routes, controllers, and persistence&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Nomic&lt;/strong&gt; Embedding Model – For semantic understanding of data&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;FastAPI&lt;/strong&gt; – Lightweight Python server to serve embeddings&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PgVector&lt;/strong&gt; – PostgreSQL extension to store and query vector data&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;OpenAI GPT-4 / GPT-3.5&lt;/strong&gt; – For the final response generation&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  🛠 Step 1: Run the Nomic Model Locally (Optional but Fast)
&lt;/h3&gt;

&lt;p&gt;You can run the &lt;a href="https://huggingface.co/nomic-ai/nomic-embed-text-v2-moe" rel="noopener noreferrer"&gt;nomic-embed-text-v2-moe&lt;/a&gt; model using &lt;a href="https://www.sbert.net/" rel="noopener noreferrer"&gt;sentence-transformers&lt;/a&gt; in a Python FastAPI app:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;fastapi&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;FastAPI&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Request&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;sentence_transformers&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;SentenceTransformer&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;FastAPI&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;SentenceTransformer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;nomic-ai/nomic-embed-text-v2-moe&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nd"&gt;@app.post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/embed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;embed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;input_text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;input&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;embedding&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input_text&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;tolist&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;embedding&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;embedding&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This becomes your internal embedding API, replacing OpenAI’s &lt;code&gt;/embeddings&lt;/code&gt;.&lt;/p&gt;




&lt;h3&gt;
  
  
  📄 Step 2: Chunk and Store Your Data
&lt;/h3&gt;

&lt;p&gt;Split your content into short passages (~100–300 words), embed them via your FastAPI endpoint, and store the results in PostgreSQL with &lt;code&gt;pgvector&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Add a vector column:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;psql &lt;span class="nt"&gt;-d&lt;/span&gt; your_db &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"CREATE EXTENSION IF NOT EXISTS vector;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AddEmbeddingToDocuments&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Migration&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;7.1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;change&lt;/span&gt;
    &lt;span class="n"&gt;add_column&lt;/span&gt; &lt;span class="ss"&gt;:documents&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:embedding&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:vector&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;limit: &lt;/span&gt;&lt;span class="mi"&gt;768&lt;/span&gt; &lt;span class="c1"&gt;# Nomic v2-moe size&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;h3&gt;
  
  
  🤖 Step 3: Embed User Queries via Nomic
&lt;/h3&gt;

&lt;p&gt;In your Rails controller:&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;get_embedding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Faraday&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"http://localhost:8000/embed"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;input: &lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="p"&gt;}.&lt;/span&gt;&lt;span class="nf"&gt;to_json&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                          &lt;span class="s2"&gt;"Content-Type"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"application/json"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="no"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="s2"&gt;"embedding"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use the same model for both document and query embeddings.&lt;/p&gt;




&lt;h3&gt;
  
  
  🔍 Step 4: Perform Vector Search with PgVector
&lt;/h3&gt;

&lt;p&gt;Search your documents for the closest matches using cosine distance:&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;Document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;order&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"embedding &amp;lt;-&amp;gt; cube(array[?])"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;query_vector&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These top chunks become the context for the LLM.&lt;/p&gt;




&lt;h3&gt;
  
  
  🧾 Step 5: Build a Smart Prompt for OpenAI
&lt;/h3&gt;

&lt;p&gt;Concatenate the top passages and feed them into OpenAI’s chat API:&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;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="ss"&gt;parameters: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="ss"&gt;model: &lt;/span&gt;&lt;span class="s2"&gt;"gpt-4"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;messages: &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;role: &lt;/span&gt;&lt;span class="s2"&gt;"system"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;content: &lt;/span&gt;&lt;span class="s2"&gt;"You are an assistant answering based on the provided context."&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;role: &lt;/span&gt;&lt;span class="s2"&gt;"user"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;content: &lt;/span&gt;&lt;span class="n"&gt;build_contextual_prompt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_input&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;top_chunks&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  ✅ Why Use Nomic for Embeddings?
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;High-quality, open-source, multilingual&lt;/li&gt;
&lt;li&gt;No token limits — runs locally or self-hosted&lt;/li&gt;
&lt;li&gt;Zero vendor lock-in at the embedding layer&lt;/li&gt;
&lt;li&gt;Great performance on MTEB and real-world retrieval&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  💡 Why I Still Use OpenAI for the LLM
&lt;/h3&gt;

&lt;p&gt;The generation step is where OpenAI shines. Instead of replacing it prematurely, I decoupled the embedding stage. Now I can experiment, optimize, and even switch LLMs later if needed.&lt;/p&gt;




&lt;h3&gt;
  
  
  🧠 Takeaways
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;RAG doesn’t need to be a heavyweight system.&lt;/li&gt;
&lt;li&gt;Open-source embeddings + OpenAI generation = powerful, flexible hybrid.&lt;/li&gt;
&lt;li&gt;PgVector + Rails makes vector search feel native and hackable.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>rag</category>
      <category>openai</category>
      <category>ai</category>
      <category>rails</category>
    </item>
    <item>
      <title>The Art of Idiomatic Ruby: Principles and Practices for Elegant Code</title>
      <dc:creator>Wilbur Suero</dc:creator>
      <pubDate>Mon, 17 Mar 2025 17:29:02 +0000</pubDate>
      <link>https://dev.to/travelingwilbur/the-art-of-idiomatic-ruby-principles-and-practices-for-elegant-code-53ej</link>
      <guid>https://dev.to/travelingwilbur/the-art-of-idiomatic-ruby-principles-and-practices-for-elegant-code-53ej</guid>
      <description>&lt;p&gt;As developers, we're constantly seeking to improve our craft and develop a coding style that's both effective and elegant. Ruby stands out among programming languages for its expressiveness and focus on developer happiness. The philosophy behind Ruby and its most popular framework, Rails, has shaped how countless developers think about software design, readability, and the overall development experience.&lt;/p&gt;

&lt;p&gt;In this post, I'll explore the key principles behind idiomatic Ruby, analyze what makes Ruby code truly "Rubyesque," and share practical ways to incorporate these principles into your own work.&lt;/p&gt;

&lt;h2&gt;
  
  
  Core Philosophy of Idiomatic Ruby
&lt;/h2&gt;

&lt;p&gt;Ruby's approach to programming is built on several foundational principles:&lt;/p&gt;

&lt;h3&gt;
  
  
  Convention over Configuration
&lt;/h3&gt;

&lt;p&gt;One of Ruby's most influential frameworks popularized the concept of sensible defaults and conventions. Rather than requiring developers to make countless trivial decisions, idiomatic Ruby emphasizes having established patterns that make assumptions about what developers need and provide streamlined pathways for common tasks.&lt;/p&gt;

&lt;p&gt;This philosophy extends beyond frameworks into coding style, where established patterns and clear conventions are favored over reinventing the wheel or introducing unnecessary complexity.&lt;/p&gt;

&lt;h3&gt;
  
  
  Beautiful Code
&lt;/h3&gt;

&lt;p&gt;In the Ruby community, code isn't just functional—it's a form of craft. There's a strong emphasis on the aesthetics of code, with the understanding that beautiful code is more maintainable, more enjoyable to work with, and ultimately more effective. This focus on beauty manifests in a preference for expressive, readable syntax that almost reads like natural language.&lt;/p&gt;

&lt;h3&gt;
  
  
  Programmer Happiness
&lt;/h3&gt;

&lt;p&gt;"Optimize for programmer happiness" is a consistent theme in the Ruby community. While some languages and frameworks optimize primarily for performance or flexibility, Ruby prioritizes the developer experience. This perspective views coding as a creative act that should be enjoyable rather than tedious.&lt;/p&gt;

&lt;h3&gt;
  
  
  Opinionated Software Design
&lt;/h3&gt;

&lt;p&gt;Idiomatic Ruby doesn't shy away from having strong opinions about how software should be built. Rather than trying to accommodate every possible approach, Ruby and its ecosystem often present "The Ruby Way" of doing things. Making strong choices about architecture and design patterns creates a more cohesive and understandable codebase.&lt;/p&gt;

&lt;h2&gt;
  
  
  Elements of Idiomatic Ruby Style
&lt;/h2&gt;

&lt;p&gt;When examining truly elegant Ruby code, several distinctive characteristics emerge:&lt;/p&gt;

&lt;h3&gt;
  
  
  Expressive Naming
&lt;/h3&gt;

&lt;p&gt;Idiomatic Ruby places tremendous importance on choosing the right names for variables, methods, and classes. It favors descriptive, sometimes longer names that clearly communicate intent over terse abbreviations. The goal is code that can be read and understood without extensive comments or documentation.&lt;/p&gt;

&lt;p&gt;For example, instead of:&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;calc_ttl_prc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="c1"&gt;# Calculate total price with discount&lt;/span&gt;
  &lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:price&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.9&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Idiomatic Ruby prefers:&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;total_price_with_discount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:price&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.9&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Embracing Ruby's Natural Expressiveness
&lt;/h3&gt;

&lt;p&gt;Truly Rubyesque code leans heavily into the language's syntax to create code that feels natural and reads almost like English prose. It makes full use of Ruby's blocks, optional parentheses, and symbol shortcuts to create concise yet readable 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="c1"&gt;# Less idiomatic&lt;/span&gt;
&lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;select&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscribed?&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;user&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send_newsletter&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# More idiomatic &lt;/span&gt;
&lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="ss"&gt;:subscribed?&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;&amp;amp;&lt;/span&gt;&lt;span class="ss"&gt;:send_newsletter&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Domain-Driven Design
&lt;/h3&gt;

&lt;p&gt;Idiomatic Ruby often designs code around domain concepts rather than technical implementation details. Classes and methods reflect the business domain, making the code not just functional but a representation of the problem space itself.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pragmatic Testing Approach
&lt;/h3&gt;

&lt;p&gt;The Ruby community has evolved a pragmatic approach to testing. While testing is valued, there's a focus on testing the outcomes that matter rather than implementation details. System tests that verify functionality from the user's perspective are often favored alongside focused unit tests.&lt;/p&gt;

&lt;h3&gt;
  
  
  Strategic Metaprogramming
&lt;/h3&gt;

&lt;p&gt;Ruby's powerful metaprogramming capabilities are a double-edged sword. Idiomatic Ruby uses metaprogramming strategically to eliminate boilerplate and create more elegant APIs, not as a showcase of technical wizardry.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Design Patterns in Ruby
&lt;/h2&gt;

&lt;p&gt;Several design patterns are central to idiomatic Ruby programming:&lt;/p&gt;

&lt;h3&gt;
  
  
  Active Record Pattern
&lt;/h3&gt;

&lt;p&gt;The Active Record pattern, which blends database access and business logic into a single object, is widely used in the Ruby ecosystem. While this approach breaks with strict separation of concerns, it creates an intuitive model that's easy to work with.&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;class&lt;/span&gt; &lt;span class="nc"&gt;Product&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="ss"&gt;:reviews&lt;/span&gt;
  &lt;span class="n"&gt;belongs_to&lt;/span&gt; &lt;span class="ss"&gt;:category&lt;/span&gt;

  &lt;span class="n"&gt;validates&lt;/span&gt; &lt;span class="ss"&gt;:name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;presence: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;
  &lt;span class="n"&gt;validates&lt;/span&gt; &lt;span class="ss"&gt;:price&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;numericality: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;greater_than: &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;discounted_price&lt;/span&gt;
    &lt;span class="n"&gt;price&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.9&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;h3&gt;
  
  
  Modules and Concerns for Shared Behavior
&lt;/h3&gt;

&lt;p&gt;Instead of deep inheritance hierarchies, idiomatic Ruby favors using modules as "Concerns" to share behavior across multiple classes. This creates more flexible composition without the rigid structures that deep inheritance can create.&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;module&lt;/span&gt; &lt;span class="nn"&gt;Trackable&lt;/span&gt;
  &lt;span class="kp"&gt;extend&lt;/span&gt; &lt;span class="no"&gt;ActiveSupport&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Concern&lt;/span&gt;

  &lt;span class="n"&gt;included&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="ss"&gt;:audit_logs&lt;/span&gt;
    &lt;span class="n"&gt;after_save&lt;/span&gt; &lt;span class="ss"&gt;:record_change&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;record_change&lt;/span&gt;
    &lt;span class="n"&gt;audit_logs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;action: &lt;/span&gt;&lt;span class="s2"&gt;"updated"&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;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Product&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;Trackable&lt;/span&gt;
  &lt;span class="c1"&gt;# Product-specific code...&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Convention-Based Routing
&lt;/h3&gt;

&lt;p&gt;RESTful routing establishes clear conventions for mapping HTTP verbs and URLs to controller actions, creating a predictable structure for web applications.&lt;/p&gt;

&lt;h3&gt;
  
  
  Service Objects for Complex Operations
&lt;/h3&gt;

&lt;p&gt;For operations that span multiple models or incorporate complex business logic, Ruby developers often employ service objects—dedicated classes that encapsulate a single operation or transaction.&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;class&lt;/span&gt; &lt;span class="nc"&gt;OrderProcessor&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;payment_details&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="vi"&gt;@order&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt;
    &lt;span class="vi"&gt;@payment_details&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;payment_details&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;process&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kp"&gt;false&lt;/span&gt; &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="n"&gt;valid?&lt;/span&gt;

    &lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;transaction&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;process_payment&lt;/span&gt;
      &lt;span class="n"&gt;update_inventory&lt;/span&gt;
      &lt;span class="n"&gt;send_confirmation&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="kp"&gt;true&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="c1"&gt;# Private methods for each step...&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  How to Write Idiomatic Ruby
&lt;/h2&gt;

&lt;p&gt;If you're inspired by Ruby's elegant approach and want to incorporate elements of idiomatic style into your own work, here are some practical steps:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Prioritize Readability Above All
&lt;/h3&gt;

&lt;p&gt;Write code for humans first, computers second. Invest time in creating clear, expressive code that future developers (including your future self) will easily understand.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Embrace Ruby's Unique Features
&lt;/h3&gt;

&lt;p&gt;Work with Ruby's strengths rather than fighting against them. Learn what makes Ruby special—blocks, procs, symbols, method_missing, etc.—and use those features to create more elegant 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="c1"&gt;# Instead of:&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"apple"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"banana"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"cherry"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;fruit&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;fruit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;upcase&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Write:&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"apple"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"banana"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"cherry"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="ss"&gt;:upcase&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Design Around Your Domain
&lt;/h3&gt;

&lt;p&gt;Structure your code to reflect the business or problem domain rather than technical considerations. Your classes and methods should speak the language of your users and stakeholders.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Follow Conventions but Break Them When Necessary
&lt;/h3&gt;

&lt;p&gt;Establish and follow conventions in your codebase, but be willing to break them when they don't serve your needs. Ruby isn't dogmatic about rules—it's pragmatic about results.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Value Simplicity Over Complexity
&lt;/h3&gt;

&lt;p&gt;Resist the urge to over-engineer. The Ruby community often advocates for the simplest solution that works, even if it's not the most theoretically pure or technically impressive.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Consider the Whole System
&lt;/h3&gt;

&lt;p&gt;Think holistically about your application rather than optimizing individual components in isolation. An idiomatic approach considers how all parts of a system work together to create a cohesive whole.&lt;/p&gt;

&lt;h3&gt;
  
  
  7. Make Development Enjoyable
&lt;/h3&gt;

&lt;p&gt;Finally, remember that coding should be enjoyable. Create tools, processes, and code that make development a pleasure rather than a chore—this is perhaps the most Ruby-like approach of all.&lt;/p&gt;

&lt;h3&gt;
  
  
  8. Pay Attention to Code Smells
&lt;/h3&gt;

&lt;p&gt;Learn to recognize and refactor common code smells:&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="c1"&gt;# Code smell: Long method&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;process_order&lt;/span&gt;
  &lt;span class="c1"&gt;# 100 lines of code doing many different things&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;# Refactored: Extracted methods with clear purposes&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;process_order&lt;/span&gt;
  &lt;span class="n"&gt;validate_order&lt;/span&gt;
  &lt;span class="n"&gt;process_payment&lt;/span&gt;
  &lt;span class="n"&gt;update_inventory&lt;/span&gt;
  &lt;span class="n"&gt;send_confirmation&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  9. Follow Community Style Guides
&lt;/h3&gt;

&lt;p&gt;Familiarize yourself with established Ruby style guides like the one from GitHub or Rubocop's defaults. These encapsulate years of community wisdom about what makes Ruby code clear and maintainable.&lt;/p&gt;




&lt;p&gt;Idiomatic Ruby represents a philosophy of software development that values human factors alongside technical considerations—code that's not just functional but beautiful, maintainable, and enjoyable to work with.&lt;/p&gt;

&lt;p&gt;By prioritizing readability, embracing Ruby's unique features, and focusing on developer happiness, you can create code that not only works but brings joy to those who interact with it. That's the true spirit of Ruby.&lt;/p&gt;

&lt;p&gt;What aspects of Ruby's style have influenced your own development approach? What patterns or practices do you find most valuable in your own Ruby projects? Share your thoughts and experiences in the comments below.&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>designpatterns</category>
      <category>softwareengineering</category>
    </item>
    <item>
      <title>Understanding Form Objects in Ruby on Rails</title>
      <dc:creator>Wilbur Suero</dc:creator>
      <pubDate>Thu, 13 Mar 2025 21:38:04 +0000</pubDate>
      <link>https://dev.to/travelingwilbur/understanding-form-objects-in-ruby-on-rails-3bf3</link>
      <guid>https://dev.to/travelingwilbur/understanding-form-objects-in-ruby-on-rails-3bf3</guid>
      <description>&lt;p&gt;In Ruby on Rails applications, handling complex forms can become messy when too much logic is stuffed into models or controllers. This is where &lt;strong&gt;Form Objects&lt;/strong&gt; come in—a design pattern that helps separate form-related logic from Active Record models, making our applications more maintainable and testable.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem with Traditional Forms
&lt;/h2&gt;

&lt;p&gt;By default, Rails encourages using Active Record models directly in forms. However, this approach has some downsides:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Fat Models&lt;/strong&gt;: Business logic and validation bloat the model, making it harder to maintain.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fat Controllers&lt;/strong&gt;: Controllers become responsible for handling complex form submissions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multiple Models in One Form&lt;/strong&gt;: Standard Rails forms work well with single models, but handling multiple related models can be cumbersome.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To address these issues, we use &lt;strong&gt;Form Objects&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is a Form Object?
&lt;/h2&gt;

&lt;p&gt;A &lt;strong&gt;Form Object&lt;/strong&gt; is a Plain Old Ruby Object (PORO) designed to handle form submissions. It encapsulates form-specific validations and persistence logic while keeping models and controllers clean.&lt;/p&gt;

&lt;h3&gt;
  
  
  When to Use a Form Object
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;When a form involves multiple models.&lt;/li&gt;
&lt;li&gt;When you want to keep your controllers thin and models focused.&lt;/li&gt;
&lt;li&gt;When form validation differs from the database schema.&lt;/li&gt;
&lt;li&gt;When reusing form logic across multiple places in your application.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Implementing a Form Object in Rails
&lt;/h2&gt;

&lt;p&gt;Let's walk through an example where we create a &lt;code&gt;UserRegistrationForm&lt;/code&gt; that handles user sign-ups along with profile information.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Create the Form Object
&lt;/h3&gt;

&lt;p&gt;Create a new file in &lt;code&gt;app/forms/user_registration_form.rb&lt;/code&gt;:&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;class&lt;/span&gt; &lt;span class="nc"&gt;UserRegistrationForm&lt;/span&gt;
  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;ActiveModel&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Model&lt;/span&gt;

  &lt;span class="nb"&gt;attr_accessor&lt;/span&gt; &lt;span class="ss"&gt;:name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:password&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:password_confirmation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:bio&lt;/span&gt;

  &lt;span class="n"&gt;validates&lt;/span&gt; &lt;span class="ss"&gt;:name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:password&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:password_confirmation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;presence: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;
  &lt;span class="n"&gt;validates&lt;/span&gt; &lt;span class="ss"&gt;:password&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;confirmation: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;
  &lt;span class="n"&gt;validates&lt;/span&gt; &lt;span class="ss"&gt;:email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;format: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;with: &lt;/span&gt;&lt;span class="no"&gt;URI&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;MailTo&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;EMAIL_REGEXP&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;attributes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt;
    &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;attributes&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;save&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kp"&gt;false&lt;/span&gt; &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="n"&gt;valid?&lt;/span&gt;

    &lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;transaction&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;email: &lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;password: &lt;/span&gt;&lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="no"&gt;Profile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;user: &lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;bio: &lt;/span&gt;&lt;span class="n"&gt;bio&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;true&lt;/span&gt;
  &lt;span class="k"&gt;rescue&lt;/span&gt; &lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;RecordInvalid&lt;/span&gt;
    &lt;span class="kp"&gt;false&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;h3&gt;
  
  
  Step 2: Use the Form Object in the Controller
&lt;/h3&gt;

&lt;p&gt;Modify the &lt;code&gt;UsersController&lt;/code&gt; to use the &lt;code&gt;UserRegistrationForm&lt;/code&gt;:&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;class&lt;/span&gt; &lt;span class="nc"&gt;UsersController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationController&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;new&lt;/span&gt;
    &lt;span class="vi"&gt;@form&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;UserRegistrationForm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&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;create&lt;/span&gt;
    &lt;span class="vi"&gt;@form&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;UserRegistrationForm&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;user_params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vi"&gt;@form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;
      &lt;span class="n"&gt;redirect_to&lt;/span&gt; &lt;span class="n"&gt;root_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;notice: &lt;/span&gt;&lt;span class="s1"&gt;'User registered successfully!'&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;
      &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="ss"&gt;:new&lt;/span&gt;
    &lt;span class="k"&gt;end&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;user_params&lt;/span&gt;
    &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:user_registration_form&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;permit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:password&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:password_confirmation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:bio&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;h3&gt;
  
  
  Step 3: Update the View
&lt;/h3&gt;

&lt;p&gt;Modify the &lt;code&gt;new.html.erb&lt;/code&gt; view:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight erb"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;form_with&lt;/span&gt; &lt;span class="ss"&gt;model: &lt;/span&gt;&lt;span class="vi"&gt;@form&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;url: &lt;/span&gt;&lt;span class="n"&gt;users_path&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;form&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;
    &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;label&lt;/span&gt; &lt;span class="ss"&gt;:name&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
    &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text_field&lt;/span&gt; &lt;span class="ss"&gt;:name&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;
    &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;label&lt;/span&gt; &lt;span class="ss"&gt;:email&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
    &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;email_field&lt;/span&gt; &lt;span class="ss"&gt;:email&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;
    &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;label&lt;/span&gt; &lt;span class="ss"&gt;:password&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
    &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;password_field&lt;/span&gt; &lt;span class="ss"&gt;:password&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;
    &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;label&lt;/span&gt; &lt;span class="ss"&gt;:password_confirmation&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
    &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;password_field&lt;/span&gt; &lt;span class="ss"&gt;:password_confirmation&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;
    &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;label&lt;/span&gt; &lt;span class="ss"&gt;:bio&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
    &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text_area&lt;/span&gt; &lt;span class="ss"&gt;:bio&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;
    &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;submit&lt;/span&gt; &lt;span class="s1"&gt;'Register'&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="cp"&gt;&amp;lt;%&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Benefits of Using Form Objects
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Separation of Concerns&lt;/strong&gt;: Keeps models and controllers focused on their primary responsibilities.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Improved Testability&lt;/strong&gt;: You can test form logic independently.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Easier Maintenance&lt;/strong&gt;: Avoids bloated models with unnecessary validations.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Form Objects in Ruby on Rails provide a clean way to manage complex form submissions while keeping code organized. By encapsulating form logic in a dedicated class, we improve maintainability, readability, and re-usability in our applications.&lt;/p&gt;

&lt;p&gt;If you're dealing with bloated models and controllers due to complex forms, consider adopting Form Objects as a structured solution!&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>rails</category>
      <category>designpatterns</category>
    </item>
    <item>
      <title>Background Jobs in Rails: A Look at SolidQueue</title>
      <dc:creator>Wilbur Suero</dc:creator>
      <pubDate>Wed, 12 Mar 2025 05:43:03 +0000</pubDate>
      <link>https://dev.to/travelingwilbur/background-jobs-in-rails-a-look-at-solidqueue-21nh</link>
      <guid>https://dev.to/travelingwilbur/background-jobs-in-rails-a-look-at-solidqueue-21nh</guid>
      <description>&lt;p&gt;Coming from a Rails background, one of the most common patterns developers rely on is background job processing. Traditionally, this has meant reaching for Sidekiq, Resque, or Delayed Job—external tools that require Redis or additional infrastructure. But what if you could handle background jobs natively within your database? Enter SolidQueue.&lt;/p&gt;

&lt;p&gt;In this post, I’ll explore how SolidQueue changes the game for Rails developers, how it compares to existing background job solutions, and how you can start using it today.&lt;/p&gt;

&lt;h2&gt;
  
  
  SolidQueue in Rails 8: Built-In Background Jobs
&lt;/h2&gt;

&lt;p&gt;With the release of Rails 8, SolidQueue is now bundled as the default background job system. This means that if you're starting a new Rails 8 application, you already have everything you need to manage background jobs without any additional dependencies.&lt;/p&gt;

&lt;h3&gt;
  
  
  Minimum Required Version
&lt;/h3&gt;

&lt;p&gt;SolidQueue requires Rails 7.1 or later to function. However, for full integration and the best experience, Rails 8 is recommended since it comes pre-configured.&lt;/p&gt;

&lt;h2&gt;
  
  
  Understanding SolidQueue: Background Jobs Without External Dependencies
&lt;/h2&gt;

&lt;p&gt;SolidQueue is a database-backed queuing system designed to work seamlessly with Active Job in Rails. Unlike Sidekiq or Resque, which rely on Redis, SolidQueue keeps everything within your application’s SQL database. The result? Simplified infrastructure, reduced operational complexity, and one less dependency to worry about.&lt;/p&gt;

&lt;h3&gt;
  
  
  Key Features of SolidQueue
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Native Database Integration&lt;/strong&gt;: Jobs are stored and managed using PostgreSQL, MySQL, or SQLite—whichever database your Rails app already uses.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Active Job Compatibility&lt;/strong&gt;: Works out of the box with Rails' Active Job framework.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No Additional Services Required&lt;/strong&gt;: Eliminates the need for Redis or external queueing systems.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mission Control UI&lt;/strong&gt;: Provides a web interface to monitor and manage jobs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automatic Job Retries &amp;amp; Error Handling&lt;/strong&gt;: Ensures reliable job execution.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  How to Set Up SolidQueue in Rails 8
&lt;/h2&gt;

&lt;p&gt;If you're using Rails 8, SolidQueue is already included, and you just need to enable it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Enabling SolidQueue in a New Rails 8 App
&lt;/h3&gt;

&lt;p&gt;For new Rails 8 applications, SolidQueue is the default Active Job adapter. To ensure it is enabled, check your &lt;code&gt;config/application.rb&lt;/code&gt;:&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;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;active_job&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;queue_adapter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="ss"&gt;:solid_queue&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, run the database migrations to set up the necessary tables:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rails db:migrate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Setting Up SolidQueue in an Existing Rails 7.1+ App
&lt;/h3&gt;

&lt;p&gt;If you're on Rails 7.1 or later and want to use SolidQueue, you'll need to install it manually:&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="c1"&gt;# Add to your Gemfile&lt;/span&gt;
&lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s1"&gt;'solid_queue'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, run the installation generator:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bundle &lt;span class="nb"&gt;exec &lt;/span&gt;rails generate solid_queue:install
rails db:migrate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Defining and Enqueueing Jobs
&lt;/h2&gt;

&lt;p&gt;Creating jobs with SolidQueue is no different from using Active Job:&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;class&lt;/span&gt; &lt;span class="nc"&gt;ExampleJob&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationJob&lt;/span&gt;
  &lt;span class="n"&gt;queue_as&lt;/span&gt; &lt;span class="ss"&gt;:default&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;perform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"Processing job for &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="no"&gt;ExampleJob&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;perform_later&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Alice"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Running Jobs
&lt;/h3&gt;

&lt;p&gt;SolidQueue requires a supervisor process to run jobs. Start it with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bundle &lt;span class="nb"&gt;exec &lt;/span&gt;solid_queue:start
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will process jobs from the database queue, similar to how Sidekiq works.&lt;/p&gt;

&lt;h2&gt;
  
  
  A More Detailed Example: Processing an Order in Rails 8
&lt;/h2&gt;

&lt;p&gt;Let’s say we’re building an e-commerce application and need to process orders asynchronously. With SolidQueue in Rails 8, we can handle this with ease.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Define the Job
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ProcessOrderJob&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationJob&lt;/span&gt;
  &lt;span class="n"&gt;queue_as&lt;/span&gt; &lt;span class="ss"&gt;:default&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;perform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;order&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;process_payment!&lt;/span&gt;
    &lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;status: &lt;/span&gt;&lt;span class="s2"&gt;"completed"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"Order #&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; has been processed."&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;h3&gt;
  
  
  Step 2: Enqueue the Job When an Order is Created
&lt;/h3&gt;

&lt;p&gt;Modify your &lt;code&gt;OrdersController&lt;/code&gt;:&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;class&lt;/span&gt; &lt;span class="nc"&gt;OrdersController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationController&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create&lt;/span&gt;
    &lt;span class="vi"&gt;@order&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order_params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="no"&gt;ProcessOrderJob&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;perform_later&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vi"&gt;@order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;redirect_to&lt;/span&gt; &lt;span class="vi"&gt;@order&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;notice: &lt;/span&gt;&lt;span class="s2"&gt;"Your order is being processed."&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;h3&gt;
  
  
  Step 3: Start the Worker Process
&lt;/h3&gt;

&lt;p&gt;To start processing jobs, simply run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bundle &lt;span class="nb"&gt;exec &lt;/span&gt;solid_queue:start
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, every time a new order is created, it will be processed in the background by SolidQueue.&lt;/p&gt;

&lt;h2&gt;
  
  
  Comparing SolidQueue to Sidekiq
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;SolidQueue (Database-Backed)&lt;/th&gt;
&lt;th&gt;Sidekiq (Redis-Backed)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Dependency&lt;/td&gt;
&lt;td&gt;Uses SQL database&lt;/td&gt;
&lt;td&gt;Requires Redis&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Infrastructure&lt;/td&gt;
&lt;td&gt;No extra services needed&lt;/td&gt;
&lt;td&gt;Needs Redis, Sidekiq&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Performance&lt;/td&gt;
&lt;td&gt;Good for most applications&lt;/td&gt;
&lt;td&gt;Optimized for high throughput&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;UI for Jobs&lt;/td&gt;
&lt;td&gt;Mission Control UI&lt;/td&gt;
&lt;td&gt;Sidekiq Web UI&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reliability&lt;/td&gt;
&lt;td&gt;Database transactions ensure safety&lt;/td&gt;
&lt;td&gt;Redis-based, requires persistence tuning&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;If you’re running a high-throughput application with millions of background jobs per day, Sidekiq may still be the best choice. But for most Rails applications, SolidQueue provides a much simpler, integrated solution without the extra operational burden.&lt;/p&gt;

&lt;h2&gt;
  
  
  When Should You Use SolidQueue?
&lt;/h2&gt;

&lt;p&gt;SolidQueue is a great choice if:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You want to avoid the overhead of running Redis.&lt;/li&gt;
&lt;li&gt;Your application already relies heavily on its database.&lt;/li&gt;
&lt;li&gt;You prefer an all-in-one solution with built-in job monitoring.&lt;/li&gt;
&lt;li&gt;You need Active Job compatibility with minimal setup.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;However, if you need extreme performance and high concurrency, Sidekiq might still be the better option.&lt;/p&gt;

&lt;p&gt;SolidQueue represents a shift in how Rails developers can handle background jobs. By leveraging the database as a queueing system, it removes the need for external services like Redis while maintaining reliability and simplicity. With Rails 8, SolidQueue is now the default, making background job processing more accessible than ever.&lt;/p&gt;

&lt;p&gt;If you’re using Rails 8, try it out today—it’s already built in! And if you're on an older version, upgrading to Rails 8 or installing SolidQueue manually can help simplify your background job processing.&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>rails</category>
      <category>programming</category>
    </item>
    <item>
      <title>Concurrency vs. Parallelism in Go: What Every Developer Should Know</title>
      <dc:creator>Wilbur Suero</dc:creator>
      <pubDate>Tue, 11 Mar 2025 16:05:45 +0000</pubDate>
      <link>https://dev.to/travelingwilbur/concurrency-vs-parallelism-in-go-what-every-developer-should-know-aec</link>
      <guid>https://dev.to/travelingwilbur/concurrency-vs-parallelism-in-go-what-every-developer-should-know-aec</guid>
      <description>&lt;p&gt;Following up with my previous post about &lt;a href="https://dev.to/travelingwilbur/concurrency-in-go-a-rails-developers-first-encounter-with-goroutines-3mfj"&gt;Concurrency in Go&lt;/a&gt;, many developers encounter a common misconception: that Go provides true parallelism by default. While Go excels at concurrency with its lightweight goroutines, achieving effective parallel execution requires understanding Go's runtime scheduler and how &lt;code&gt;GOMAXPROCS&lt;/code&gt; affects program behavior. This post explores how Go handles concurrency, when it achieves parallelism, and how it compares to other programming languages.&lt;/p&gt;

&lt;h2&gt;
  
  
  Concurrency vs. Parallelism: Fundamental Distinctions
&lt;/h2&gt;

&lt;p&gt;These terms are often conflated but represent distinct concepts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Concurrency&lt;/strong&gt;: The ability to structure a program to handle multiple tasks, potentially overlapping in time. It's about program design and decomposition of problems into independently executable units.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Parallelism&lt;/strong&gt;: The simultaneous execution of multiple computations, typically on separate CPU cores. It's about execution and hardware utilization.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As Rob Pike famously said: "Concurrency is about dealing with lots of things at once. Parallelism is about doing lots of things at once."&lt;/p&gt;

&lt;p&gt;Go's goroutines and channels provide elegant concurrency primitives, but actual parallel execution depends on runtime configuration and hardware capabilities.&lt;/p&gt;

&lt;h2&gt;
  
  
  Go's Concurrency Model: Goroutines and the Runtime Scheduler
&lt;/h2&gt;

&lt;p&gt;Go implements a CSP-based (Communicating Sequential Processes) concurrency model using goroutines—lightweight, user-space threads managed by Go's runtime rather than the operating system. Compared to OS threads which might require megabytes of stack space, goroutines start with only 2KB, allowing programs to spawn millions of them efficiently.&lt;/p&gt;

&lt;p&gt;Here's a simple example demonstrating goroutine creation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"fmt"&lt;/span&gt;
    &lt;span class="s"&gt;"time"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;sayHello&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Hello from a goroutine!"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;go&lt;/span&gt; &lt;span class="n"&gt;sayHello&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c"&gt;// Launch goroutine&lt;/span&gt;
    &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;100&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Millisecond&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c"&gt;// Give goroutine time to execute&lt;/span&gt;
    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Main function continues execution"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;While this code runs the &lt;code&gt;sayHello()&lt;/code&gt; function concurrently with the main function, it doesn't necessarily execute in parallel. Understanding why requires examining Go's scheduler architecture.&lt;/p&gt;

&lt;h2&gt;
  
  
  Go's Scheduler: The M:P:G Model
&lt;/h2&gt;

&lt;p&gt;Go's scheduler implements what's known as the M:P:G model:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;G (Goroutines)&lt;/strong&gt;: Application-level tasks&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;M (Machine)&lt;/strong&gt;: OS threads that execute code&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;P (Processor)&lt;/strong&gt;: Logical processors that manage execution contexts&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In this model:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Each P maintains a local queue of runnable goroutines&lt;/li&gt;
&lt;li&gt;Ms (OS threads) execute goroutines from the P they're assigned to&lt;/li&gt;
&lt;li&gt;When a P's queue is empty, it attempts to steal work from other Ps&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This sophisticated work-stealing scheduler efficiently distributes goroutines across available system resources, but the number of Ps is the key limiting factor for parallel execution.&lt;/p&gt;

&lt;h2&gt;
  
  
  Controlling Parallelism with GOMAXPROCS
&lt;/h2&gt;

&lt;p&gt;By default, Go sets &lt;code&gt;GOMAXPROCS&lt;/code&gt; equal to the number of available CPU cores since Go 1.5. This value determines the number of Ps (logical processors) in the runtime.&lt;/p&gt;

&lt;p&gt;You can explicitly control this setting in your code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"fmt"&lt;/span&gt;
    &lt;span class="s"&gt;"runtime"&lt;/span&gt;
    &lt;span class="s"&gt;"time"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;cpuIntensiveTask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Task %d starting on CPU %d&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;runtime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NumCPU&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="c"&gt;// Simulate CPU-intensive work&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="m"&gt;1e9&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Task %d completed&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;numCPU&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;runtime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NumCPU&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"System has %d CPU cores&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;numCPU&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;runtime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GOMAXPROCS&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;numCPU&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c"&gt;// Explicitly set to use all cores&lt;/span&gt;
    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"GOMAXPROCS set to %d&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;runtime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GOMAXPROCS&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c"&gt;// Launch CPU-intensive goroutines&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;numCPU&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;go&lt;/span&gt; &lt;span class="n"&gt;cpuIntensiveTask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c"&gt;// Wait for goroutines to complete (in production, use sync.WaitGroup)&lt;/span&gt;
    &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Second&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Execution time: %v&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Since&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When &lt;code&gt;GOMAXPROCS &amp;gt; 1&lt;/code&gt; and your system has multiple cores, Go can truly execute goroutines in parallel. However, several factors can still limit actual parallel performance.&lt;/p&gt;

&lt;h2&gt;
  
  
  Go's Parallelism: Capabilities and Limitations
&lt;/h2&gt;

&lt;p&gt;Go can achieve true parallelism, but with important caveats:&lt;/p&gt;

&lt;h3&gt;
  
  
  Strengths:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Automatic Scaling&lt;/strong&gt;: Go automatically distributes work across cores&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Low Overhead&lt;/strong&gt;: Goroutines and channel communication have minimal overhead&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Work Stealing&lt;/strong&gt;: Efficient distribution of tasks to prevent cores from idling&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Limitations:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cooperative Scheduling&lt;/strong&gt;: Goroutines yield control only at specific points (function calls, channel operations, etc.)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stop-the-World GC&lt;/strong&gt;: Garbage collection pauses can temporarily halt all execution&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scheduler Overhead&lt;/strong&gt;: The work-stealing algorithm adds some overhead&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Network-Bound Performance&lt;/strong&gt;: For I/O-heavy workloads, adding cores may not improve throughput&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Benchmarking Parallelism in Go
&lt;/h3&gt;

&lt;p&gt;To evaluate parallelism gains, you can use the &lt;code&gt;testing&lt;/code&gt; package with the &lt;code&gt;-cpu&lt;/code&gt; flag:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// parallelism_test.go&lt;/span&gt;
&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"runtime"&lt;/span&gt;
    &lt;span class="s"&gt;"testing"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;BenchmarkComputation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;testing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;B&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c"&gt;// CPU-intensive computation&lt;/span&gt;
        &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="m"&gt;10000000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run with: &lt;code&gt;go test -bench=. -cpu=1,2,4,8&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Comparative Analysis: Parallelism Across Languages
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Language&lt;/th&gt;
&lt;th&gt;Parallelism Model&lt;/th&gt;
&lt;th&gt;Strengths&lt;/th&gt;
&lt;th&gt;Limitations&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Go&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;M:P:G scheduler with goroutines&lt;/td&gt;
&lt;td&gt;Easy concurrency, low overhead, work stealing&lt;/td&gt;
&lt;td&gt;Cooperative scheduling, GC pauses&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Rust&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;OS threads + async/await&lt;/td&gt;
&lt;td&gt;Zero-cost abstractions, fine-grained control&lt;/td&gt;
&lt;td&gt;Steeper learning curve, manual synchronization&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;C++ (std::thread)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Direct OS thread mapping&lt;/td&gt;
&lt;td&gt;Maximum performance, precise control&lt;/td&gt;
&lt;td&gt;High thread creation overhead, manual resource management&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Java&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Thread pools, ForkJoinPool&lt;/td&gt;
&lt;td&gt;Rich ecosystem, mature tooling&lt;/td&gt;
&lt;td&gt;Higher memory overhead, complex thread management&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Python&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;GIL in CPython, multiprocessing&lt;/td&gt;
&lt;td&gt;Simple API, good for I/O&lt;/td&gt;
&lt;td&gt;GIL prevents true threading parallelism&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Node.js&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Event loop + worker threads&lt;/td&gt;
&lt;td&gt;Excellent for I/O, non-blocking&lt;/td&gt;
&lt;td&gt;Single-threaded main loop, callback complexity&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Optimizing Go for Parallel Workloads
&lt;/h2&gt;

&lt;p&gt;For CPU-bound tasks requiring maximum parallelism:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Profile First&lt;/strong&gt;: Use &lt;code&gt;go tool pprof&lt;/code&gt; to identify bottlenecks&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tune GOMAXPROCS&lt;/strong&gt;: Sometimes setting lower than available cores improves performance&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Optimize Work Distribution&lt;/strong&gt;: Divide work into equally sized chunks&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Minimize Contention&lt;/strong&gt;: Reduce lock contention and shared memory access&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Consider sync.Pool&lt;/strong&gt;: Reduce GC pressure for frequently allocated objects&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use Performance-Oriented Packages&lt;/strong&gt;: Consider &lt;code&gt;github.com/valyala/fasthttp&lt;/code&gt; over &lt;code&gt;net/http&lt;/code&gt; for web servers&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Example of balanced work distribution:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"fmt"&lt;/span&gt;
    &lt;span class="s"&gt;"runtime"&lt;/span&gt;
    &lt;span class="s"&gt;"sync"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;processRange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;end&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;wg&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;sync&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WaitGroup&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="n"&gt;wg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Done&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="c"&gt;// Process the assigned range&lt;/span&gt;
    &lt;span class="n"&gt;sum&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;sum&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Range %d-%d sum: %d&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;totalWork&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="m"&gt;1000000&lt;/span&gt;
    &lt;span class="n"&gt;numCPU&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;runtime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NumCPU&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;runtime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GOMAXPROCS&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;numCPU&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;wg&lt;/span&gt; &lt;span class="n"&gt;sync&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WaitGroup&lt;/span&gt;
    &lt;span class="n"&gt;chunkSize&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;totalWork&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;numCPU&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;numCPU&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;chunkSize&lt;/span&gt;
        &lt;span class="n"&gt;end&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;chunkSize&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;numCPU&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;end&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;totalWork&lt;/span&gt; &lt;span class="c"&gt;// Handle any remainder in the last chunk&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="n"&gt;wg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;go&lt;/span&gt; &lt;span class="n"&gt;processRange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;wg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;wg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Wait&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"All work completed"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  When to Emphasize Parallelism in Go
&lt;/h2&gt;

&lt;p&gt;Go's design philosophy prioritizes simplicity and maintainability over raw CPU performance. Consider these factors when deciding how much to invest in parallel optimizations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;I/O-Bound vs. CPU-Bound&lt;/strong&gt;: For I/O-bound applications, Go's concurrency model already provides excellent throughput without explicit parallelism tuning&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Development Time vs. Runtime&lt;/strong&gt;: Optimize only when performance requirements demand it&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scalability Requirements&lt;/strong&gt;: Consider future workload growth patterns&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Resource Constraints&lt;/strong&gt;: Memory limitations may favor alternative approaches&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Go provides sophisticated concurrency primitives that make parallel programming more accessible than many other languages. While Go can achieve true parallelism, understanding the nuances of its scheduler, the &lt;code&gt;GOMAXPROCS&lt;/code&gt; setting, and inherent limitations helps developers make informed architectural decisions.&lt;/p&gt;

&lt;p&gt;For most applications, Go's default configuration provides an excellent balance of throughput and simplicity. When optimization is necessary, profiling and benchmark-driven tuning yields the best results.&lt;/p&gt;

&lt;p&gt;Whether you're building high-performance web services, data processing pipelines, or distributed systems, Go's approach to concurrency and parallelism offers a compelling foundation for modern software development.&lt;/p&gt;

&lt;p&gt;What has been your experience with parallelism in Go? Share in the comments below!&lt;/p&gt;

</description>
      <category>go</category>
    </item>
    <item>
      <title>Concurrency in Go: A Rails Developer’s First Encounter with Goroutines</title>
      <dc:creator>Wilbur Suero</dc:creator>
      <pubDate>Wed, 05 Feb 2025 13:55:27 +0000</pubDate>
      <link>https://dev.to/travelingwilbur/concurrency-in-go-a-rails-developers-first-encounter-with-goroutines-3mfj</link>
      <guid>https://dev.to/travelingwilbur/concurrency-in-go-a-rails-developers-first-encounter-with-goroutines-3mfj</guid>
      <description>&lt;p&gt;Coming from a Ruby on Rails background, one of the most striking differences when switching to Go is how it handles concurrency. Rails applications often rely on external tools like Sidekiq, Resque, or Thread-based parallelism to manage background jobs and concurrent tasks. Go, on the other hand, has concurrency built into the language itself, using lightweight &lt;strong&gt;goroutines&lt;/strong&gt; and &lt;strong&gt;channels&lt;/strong&gt;.  &lt;/p&gt;

&lt;p&gt;In this post, I’ll walk through the key differences between Go’s concurrency model and Ruby’s threading model, why Go’s approach is so powerful, and how you can start writing concurrent Go programs.  &lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;Understanding Concurrency in Go&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Go was designed from the ground up to support concurrency as a first-class feature. The key components of its concurrency model are:  &lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;1. Goroutines: Lightweight, Managed Threads&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;A &lt;strong&gt;goroutine&lt;/strong&gt; is a function that runs concurrently with other functions. Unlike system threads, goroutines are extremely lightweight because they are managed by the Go runtime, not the OS. They start with just a few kilobytes of stack space and grow as needed.  &lt;/p&gt;

&lt;p&gt;Starting a goroutine is as simple as using the &lt;code&gt;go&lt;/code&gt; keyword:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"fmt"&lt;/span&gt;
    &lt;span class="s"&gt;"time"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;sayHello&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Hello from a Goroutine!"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;go&lt;/span&gt; &lt;span class="n"&gt;sayHello&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c"&gt;// This runs concurrently&lt;/span&gt;
    &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Second&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c"&gt;// Prevent main from exiting immediately&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, &lt;code&gt;sayHello()&lt;/code&gt; runs in a separate goroutine. The &lt;code&gt;main&lt;/code&gt; function needs a &lt;code&gt;Sleep&lt;/code&gt; to ensure the goroutine gets time to execute before the program exits.  &lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;2. Channels: Safe Communication Between Goroutines&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Goroutines don’t share memory by default. Instead, they communicate using &lt;strong&gt;channels&lt;/strong&gt;, which provide a safe way to pass data between them.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="s"&gt;"fmt"&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;ch&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;chan&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;go&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;ch&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="s"&gt;"Hello, World!"&lt;/span&gt; &lt;span class="c"&gt;// Send data into the channel&lt;/span&gt;
    &lt;span class="p"&gt;}()&lt;/span&gt;

    &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="n"&gt;ch&lt;/span&gt; &lt;span class="c"&gt;// Receive data from the channel&lt;/span&gt;
    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This approach eliminates many of the pitfalls of traditional multi-threading, such as race conditions and complex locking mechanisms.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;How Ruby Handles Concurrency&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Ruby’s concurrency model is different. By default, Ruby threads are OS-managed and can be affected by the &lt;strong&gt;Global Interpreter Lock (GIL)&lt;/strong&gt; in MRI (Matz’s Ruby Interpreter). This means only one thread executes at a time, limiting true parallelism.  &lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;1. Ruby Threads: OS-Managed and Heavyweight&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Ruby supports threads, but they are heavier than Go’s goroutines. Here’s a simple example of spawning a thread in Ruby:&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;thread&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Thread&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"Hello from a Thread!"&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;thread&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt; &lt;span class="c1"&gt;# Wait for the thread to finish&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;While this works, Ruby threads consume more memory than goroutines and are subject to OS scheduling.  &lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;2. Background Jobs: Offloading Work&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Since Ruby’s threading model isn’t great for high concurrency, Rails developers often turn to &lt;strong&gt;background job processing frameworks&lt;/strong&gt; like Sidekiq or Resque, which rely on Redis and external worker processes to handle concurrency.  &lt;/p&gt;

&lt;p&gt;Example of Sidekiq in Rails:&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;class&lt;/span&gt; &lt;span class="nc"&gt;HardWorker&lt;/span&gt;
  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;Sidekiq&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Worker&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;perform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"Doing hard work for &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="nb"&gt;name&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;count&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; times!"&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="no"&gt;HardWorker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;perform_async&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"John"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;While these tools work well, they introduce extra dependencies and infrastructure complexity compared to Go’s built-in concurrency model.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;Key Differences: Go vs. Ruby Concurrency&lt;/strong&gt;
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;Go (Goroutines &amp;amp; Channels)&lt;/th&gt;
&lt;th&gt;Ruby (Threads &amp;amp; Background Jobs)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Lightweight&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Yes, goroutines are extremely lightweight&lt;/td&gt;
&lt;td&gt;No, Ruby threads are OS-managed and heavier&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Managed by&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Go runtime&lt;/td&gt;
&lt;td&gt;OS scheduler&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Parallel Execution&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Yes, true parallelism possible&lt;/td&gt;
&lt;td&gt;Limited due to GIL in MRI&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Built-in Concurrency&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Yes, goroutines &amp;amp; channels are first-class citizens&lt;/td&gt;
&lt;td&gt;No, relies on third-party solutions like Sidekiq&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Memory Usage&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Low, goroutines share heap efficiently&lt;/td&gt;
&lt;td&gt;High, OS threads are heavier&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Scalability&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Scales well due to efficient concurrency model&lt;/td&gt;
&lt;td&gt;Requires external tools for large-scale concurrency&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;When to Use Concurrency in Go&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Now that you see how Go’s concurrency model differs, here are some real-world scenarios where goroutines and channels shine:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Handling thousands of simultaneous requests in a web server&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Streaming data processing (e.g., real-time analytics)&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Efficient background job execution (e.g., email processing, batch jobs)&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;High-performance APIs with non-blocking I/O&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here’s an example of a simple HTTP server in Go that handles requests concurrently using goroutines:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"fmt"&lt;/span&gt;
    &lt;span class="s"&gt;"net/http"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ResponseWriter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Hello, %s!"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;URL&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HandleFunc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;go&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ListenAndServe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;":8080"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c"&gt;// Runs in a goroutine&lt;/span&gt;
    &lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt; &lt;span class="c"&gt;// Keep the program running&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each request runs in its own goroutine, making the server highly scalable.&lt;/p&gt;




&lt;p&gt;For a Rails developer transitioning to Go, the built-in support for concurrency is a game-changer. Go’s &lt;strong&gt;goroutines&lt;/strong&gt; and &lt;strong&gt;channels&lt;/strong&gt; eliminate much of the complexity involved in traditional multi-threading. Unlike Ruby, where you often rely on background job queues and OS-managed threads, Go lets you write highly concurrent applications &lt;strong&gt;with minimal dependencies and better performance&lt;/strong&gt;.  &lt;/p&gt;

&lt;p&gt;If you're coming from Rails, expect some initial adjustment when learning Go’s concurrency patterns. However, once you get used to &lt;strong&gt;goroutines, channels, and Go’s structured simplicity&lt;/strong&gt;, you’ll see why Go has become the go-to language for scalable and high-performance systems.  &lt;/p&gt;

</description>
      <category>go</category>
      <category>rails</category>
    </item>
  </channel>
</rss>
