<?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: Katz Sakai</title>
    <description>The latest articles on DEV Community by Katz Sakai (@katz).</description>
    <link>https://dev.to/katz</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F192880%2Fa0f93e5d-730c-476d-835f-e071842ccc96.jpg</url>
      <title>DEV Community: Katz Sakai</title>
      <link>https://dev.to/katz</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/katz"/>
    <language>en</language>
    <item>
      <title>GKE's Noisy Neighbor Problem Can Be Invisible in Metrics Explorer</title>
      <dc:creator>Katz Sakai</dc:creator>
      <pubDate>Sun, 12 Apr 2026 06:53:14 +0000</pubDate>
      <link>https://dev.to/katz/gkes-noisy-neighbor-problem-can-be-invisible-in-metrics-explorer-1bf4</link>
      <guid>https://dev.to/katz/gkes-noisy-neighbor-problem-can-be-invisible-in-metrics-explorer-1bf4</guid>
      <description>&lt;p&gt;Google Cloud's Metrics Explorer has plenty of metrics, and for most monitoring needs, it's more than enough.&lt;/p&gt;

&lt;p&gt;However, the sampling interval of those metrics can hide real problems. I once ran into a situation where an API server on Google Kubernetes Engine (GKE) had intermittent response time spikes, yet Metrics Explorer showed nothing abnormal. The root cause turned out to be short-lived batch jobs on the same Node eating up all the CPU, a classic Noisy Neighbor problem.&lt;/p&gt;

&lt;p&gt;Here's how I fell into that trap.&lt;/p&gt;

&lt;h2&gt;
  
  
  An API server that was mysteriously slow from time to time
&lt;/h2&gt;

&lt;p&gt;I had a development API server running on GKE that would occasionally slow down for no obvious reason.&lt;/p&gt;

&lt;p&gt;A request that normally completed in around 200 ms would sometimes take about 4 seconds, even under the same conditions. The slowdown was random/intermittent, and I could not find a clear pattern in when it happened.&lt;/p&gt;

&lt;p&gt;When the issue occurred, CPU usage for the two GKE Nodes looked like this in Metrics Explorer:&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.amazonaws.com%2Fuploads%2Farticles%2Fbon0bu441t2a3i2k2bqz.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.amazonaws.com%2Fuploads%2Farticles%2Fbon0bu441t2a3i2k2bqz.png" alt="CPU usage for the two GKE Nodes" width="800" height="517"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;CPU utilization was sitting around 35%. Nothing suggested the CPUs were being saturated.&lt;/p&gt;

&lt;p&gt;I then checked the Load Average (1m) for the same Nodes:&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.amazonaws.com%2Fuploads%2Farticles%2Fpk7vm07tgn9qy885y8xx.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.amazonaws.com%2Fuploads%2Farticles%2Fpk7vm07tgn9qy885y8xx.png" alt="Load Average (1m) for the same Nodes" width="800" height="516"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There were some spikes, but with a 2-core CPU Node averaging around 1.5 to 2, and combined with the CPU utilization graph, it was hard to conclude that the CPU was saturated.&lt;/p&gt;

&lt;p&gt;(In hindsight, the Load Average spikes might have been a clue that something was happening in short bursts. But at the time, I couldn't connect the dots.)&lt;/p&gt;

&lt;h2&gt;
  
  
  What was actually happening on the API server node
&lt;/h2&gt;

&lt;p&gt;Metrics Explorer wasn't giving me any clues. The database wasn't overloaded, there were no notable error logs from the API server. I was stuck. Since this was a development environment, I let it sit for a while.&lt;/p&gt;

&lt;p&gt;One day, I finally decided to log in to the affected node over SSH and watch it in real time with &lt;code&gt;htop&lt;/code&gt;&lt;sup id="fnref1"&gt;1&lt;/sup&gt;.&lt;/p&gt;

&lt;p&gt;That was the turning point.&lt;/p&gt;

&lt;p&gt;At some moments, both CPU cores were pinned at 100% for around 20 to 30 seconds, and the load average reached 7.44.&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.amazonaws.com%2Fuploads%2Farticles%2Fuj8i9g54c4ynsarhqrj4.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.amazonaws.com%2Fuploads%2Farticles%2Fuj8i9g54c4ynsarhqrj4.png" alt="htop" width="800" height="212"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The process list showed multiple Rails batch tasks running at the same time. These batch jobs were consuming all the CPU, starving the API Pod running on the same Node. &lt;/p&gt;

&lt;p&gt;That was the noisy neighbor.&lt;/p&gt;

&lt;p&gt;When the batch jobs were not running, CPU usage dropped back down to around 6% to 11%.&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.amazonaws.com%2Fuploads%2Farticles%2F9iri63kr9dv5tntrsxoq.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.amazonaws.com%2Fuploads%2Farticles%2F9iri63kr9dv5tntrsxoq.png" width="800" height="324"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So why didn't Metrics Explorer show this?&lt;/p&gt;

&lt;p&gt;Because the CPU-hungry batch jobs were short-lived. Each run finished in around 20 to 30 seconds. The VM CPU utilization metric in Metrics Explorer is sampled at 60-second intervals&lt;sup id="fnref2"&gt;2&lt;/sup&gt;. If CPU is fully saturated for only 20 to 30 seconds out of a 60-second window, the result can still look like only about 33% to 50% average utilization.&lt;/p&gt;

&lt;p&gt;That was exactly the trap: the node really was getting hammered, but only briefly, and the 1-minute metric smoothed it into something that looked unremarkable.&lt;/p&gt;

&lt;h2&gt;
  
  
  (Side note) Why the batch jobs were eating all the CPU
&lt;/h2&gt;

&lt;p&gt;The batch pod had no CPU limits configured, so there was no upper bound on how much CPU it could use.&lt;/p&gt;

&lt;p&gt;As a result, when multiple batch jobs ran at the same time, they were able to consume most of the CPU available on the node and interfere with other pods running there.&lt;/p&gt;

&lt;p&gt;After I added CPU limits to the batch pod, the API response time became stable.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I learned
&lt;/h2&gt;

&lt;p&gt;Metrics Explorer is a powerful tool, but you need to be aware of its sampling intervals. Short-lived CPU spikes can get averaged out and won't show up clearly on the graphs.&lt;/p&gt;

&lt;p&gt;In this case, I had already noticed the symptom from the application side: “the dev API feels slow sometimes.” But the infrastructure metrics alone did not reveal the cause. I only found the real problem after looking at the node directly with &lt;code&gt;htop&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I took two lessons from this.&lt;/p&gt;

&lt;p&gt;First, monitor application latency itself, especially p95 and p99. Infrastructure metrics do not always tell you that users are already feeling pain.&lt;/p&gt;

&lt;p&gt;Second, know the sampling resolution of the metrics you rely on. If a problem is short-lived enough, aggregated infrastructure graphs may hide it. In those cases, direct inspection of the machine can still be the fastest way to understand what is happening.&lt;/p&gt;

&lt;p&gt;If you need a good starting point for that kind of live investigation, &lt;a href="https://netflixtechblog.com/linux-performance-analysis-in-60-000-milliseconds-accc10403c55" rel="noopener noreferrer"&gt;Netflix’s Linux Performance Analysis in 60,000 Milliseconds&lt;/a&gt; is still a useful reference.&lt;/p&gt;




&lt;ol&gt;

&lt;li id="fn1"&gt;
&lt;p&gt;For how to run htop on a GKE Node, see &lt;a href="https://docs.cloud.google.com/container-optimized-os/docs/how-to/toolbox?hl=en" rel="noopener noreferrer"&gt;Debugging node issues using the toolbox&lt;/a&gt; ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn2"&gt;
&lt;p&gt;&lt;a href="https://docs.cloud.google.com/monitoring/api/metrics_gcp_c?hl=en#gcp-compute" rel="noopener noreferrer"&gt;https://docs.cloud.google.com/monitoring/api/metrics_gcp_c?hl=en#gcp-compute&lt;/a&gt; ↩&lt;/p&gt;
&lt;/li&gt;

&lt;/ol&gt;

</description>
      <category>monitoring</category>
      <category>googlecloud</category>
      <category>observability</category>
      <category>kubernetes</category>
    </item>
    <item>
      <title>How a Rails and K8s Newcomer Cut GKE Costs by 60% by Looking Across the Stack</title>
      <dc:creator>Katz Sakai</dc:creator>
      <pubDate>Tue, 31 Mar 2026 11:41:32 +0000</pubDate>
      <link>https://dev.to/katz/how-we-cut-rails-on-gke-costs-by-60-the-efficiency-first-roadmap-14pg</link>
      <guid>https://dev.to/katz/how-we-cut-rails-on-gke-costs-by-60-the-efficiency-first-roadmap-14pg</guid>
      <description>&lt;p&gt;&lt;strong&gt;tl;dr:&lt;/strong&gt; This is a journal of how an engineer with no prior Rails or Kubernetes experience cut Google Kubernetes Engine (GKE) costs by 60%. The following steps were taken to achieve this cost reduction:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Puma, which hosts the Rails app, was running with 1 worker and 33 threads. Because Ruby has the GVL, only one core was effectively being used. Changing to 4 workers and 8 threads let the Rails app take advantage of multi-core CPUs and process multiple requests more effectively per Pod.&lt;/li&gt;
&lt;li&gt;Every API request was running bcrypt for token authentication. This was clearly too much overhead for API token auth, so replacing it with a lighter scheme reduced per-request CPU load.&lt;/li&gt;
&lt;li&gt;The GKE Nodes running the Pods were on an older machine generation. Upgrading from n1 to n2d gave 56% better CPU performance, 23% more memory, and a 3% cost reduction, which improved Pod density per Node.&lt;/li&gt;
&lt;li&gt;There was no autoscaling for either Pods or Nodes, so capacity was always provisioned for peak traffic. By introducing KEDA for Pod autoscaling and GKE Cluster Autoscaler for Node autoscaling, capacity now scales with actual traffic, so we only pay for what we use.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As you can see, this cost reduction was achieved not only by changing the Kubernetes infrastructure settings, but also by tuning the Rails application running on top of it. It required a comprehensive understanding of the entire stack, from application to infrastructure, and a thorough review of the entire stack. For example, simply implementing Kubernetes autoscaling would only increase or decrease inefficient Pods and Nodes, resulting in limited cost savings.&lt;br&gt;
Furthermore, looking at each individual steps, they might seem like insignificant improvements. However, diligently accumulating these small improvements ultimately led to a 60% cost reduction.&lt;/p&gt;



&lt;p&gt;I work on a B2B SaaS platform that runs on Google Kubernetes Engine. The API server is a Rails application, and it was the biggest cost driver on our GKE cluster.&lt;/p&gt;

&lt;p&gt;When I joined this project, I had virtually no experience with Ruby on Rails or Kubernetes. However, this lack of knowledge actually became a hidden strength. In other words, it allowed me to look at every implementation and configuration with a fresh perspective and keep asking "why is it implemented/configured this way?" until I was satisfied.&lt;/p&gt;
&lt;h2&gt;
  
  
  The vicious cycle: why the cluster needed so many Pods
&lt;/h2&gt;

&lt;p&gt;The high cost was not caused by a single problem. It was a cycle:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Inefficiency:&lt;/strong&gt; Running Puma as a single process meant CPUs were not being fully used. On top of that, running bcrypt on every API request added unnecessary CPU load. The Rails API was simply slow.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Band-aid scaling:&lt;/strong&gt; Instead of tuning Rails, the response to traffic was to add more GKE Nodes and Pods.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Low cost-performance Nodes:&lt;/strong&gt; GKE Nodes were running on older generation instances, so the cost per unit of CPU and memory was poor.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Overprovisioning:&lt;/strong&gt; Without autoscaling, the Pod and Node count was fixed to match peak traffic and stayed that way around the clock, which wasted a lot of resources.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Several issues were driving costs up, and we had to tackle them one by one.&lt;/p&gt;
&lt;h2&gt;
  
  
  How the cost actually came down
&lt;/h2&gt;

&lt;p&gt;Here's what happened to costs. The chart below shows monthly GKE spending by SKU.&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.amazonaws.com%2Fuploads%2Farticles%2F1ii39oeaq56gb6qipe1c.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.amazonaws.com%2Fuploads%2Farticles%2F1ii39oeaq56gb6qipe1c.png" alt="Monthly cost" width="666" height="272"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This was not mainly due to lower traffic. API traffic stayed fairly stable during this period, and the savings came mainly from efficiency improvements.&lt;/p&gt;

&lt;p&gt;The cost came down in two steps.&lt;/p&gt;

&lt;p&gt;In the first step (early 2025 through June 2025), we simply reduced Pod and node counts that were clearly excessive. This brought costs down from the peak, but it was just trimming fat. The underlying inefficiencies remained.&lt;/p&gt;

&lt;p&gt;The second step (from July 2025 onward) is where most of the real savings came from. It breaks down into two parts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Part 1: Making each Pod and node more efficient&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Part 2: Scaling with demand&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
  
  
  Part 1: Making each Pod and node more efficient
&lt;/h2&gt;
&lt;h3&gt;
  
  
  1.1 GKE Node generation upgrade: n1-highmem-2 → n2d-highmem-2
&lt;/h3&gt;

&lt;p&gt;Our GKE nodes had been running on n1-highmem-2 since the early days of the service. The n1 family is Google Cloud's first generation of general-purpose instances, based on older Intel Skylake/Broadwell processors.&lt;/p&gt;

&lt;p&gt;We migrated to n2d-highmem-2, which uses AMD EPYC processors. That alone made the upgrade worth doing. According to &lt;a href="https://docs.cloud.google.com/compute/docs/coremark-scores-of-vm-instances?hl=ja" rel="noopener noreferrer"&gt;Google's official CoreMark benchmarks&lt;/a&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Instance type&lt;/th&gt;
&lt;th&gt;CoreMark score&lt;/th&gt;
&lt;th&gt;RAM&lt;/th&gt;
&lt;th&gt;Monthly cost (asia-northeast1)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;n1-highmem-2&lt;/td&gt;
&lt;td&gt;26,293&lt;/td&gt;
&lt;td&gt;13 GiB&lt;/td&gt;
&lt;td&gt;~$124&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;n2d-highmem-2&lt;/td&gt;
&lt;td&gt;41,073&lt;/td&gt;
&lt;td&gt;16 GiB&lt;/td&gt;
&lt;td&gt;~$120&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Just upgrading the Node type gives 56% better CPU performance, 23% more RAM, and a 3% lower cost. The migration had no surprises: update the Node Pool configuration, cordon and drain the old Nodes, switch to the new Node Pool. This was done in July 2025 and shows up as a SKU change in the cost graph above.&lt;/p&gt;

&lt;p&gt;For binary compatibility with existing Docker images, x86-based machines were used rather than Arm. Switching to Arm for further cost reduction is something being considered for the future.&lt;/p&gt;
&lt;h3&gt;
  
  
  1.2 Rails process model: from 33 threads to 4 workers
&lt;/h3&gt;

&lt;p&gt;This was where most of the savings came from.&lt;/p&gt;

&lt;p&gt;Our Rails application was running Puma with the following configuration:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;WEB_CONCURRENCY&lt;/code&gt; was not set (defaults to 1, meaning a single worker process)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;RAILS_MAX_THREADS=33&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is one process with 33 threads. At first, it looked like this should handle up to 33 concurrent requests, but that was not how it worked in practice. The reason is how Ruby works.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ruby's Global VM Lock (GVL)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://rubykaigi.org/2023/presentations/KnuX.html" rel="noopener noreferrer"&gt;Ruby has a Global VM Lock (GVL)&lt;/a&gt;. Within a single process, only one thread can execute Ruby code at a time. (Threads waiting on I/O such as DB queries, HTTP requests, or file reads do release the GVL, allowing other threads to run.)&lt;/p&gt;

&lt;p&gt;The API server was CPU-bound, so even with 33 threads in one process, effective parallelism was essentially 1. Having 33 threads did not mean 33 requests being processed simultaneously.&lt;/p&gt;

&lt;p&gt;The Nodes running the Pods had multi-core CPUs, but the GVL meant each Pod was effectively using only one core.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The bcrypt problem&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;On top of the GVL issue, every API request was running bcrypt for token authentication. bcrypt is a password hashing algorithm deliberately designed to be CPU-intensive in order to resist brute-force attacks. Running that expensive hash operation on every API request was using up CPU across all Pods. We replaced API token authentication with a lighter method for per-request token validation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The fix&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We changed the Puma configuration to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;WEB_CONCURRENCY=4&lt;/code&gt; (4 worker processes)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;RAILS_MAX_THREADS=8&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With this setup, Puma spawns 4 worker processes, each with its own GVL, so multi-core CPUs can be used properly. Thread count was reduced from 33 to 8. For reference, since &lt;a href="https://github.com/rails/rails/pull/50669" rel="noopener noreferrer"&gt;Rails 7.2, Puma's default thread count per worker was reduced from 5 to 3&lt;/a&gt;, so further reductions may make sense. For more on tuning workers and threads, see &lt;a href="https://github.com/puma/puma/blob/main/docs/deployment.md" rel="noopener noreferrer"&gt;Deployment engineering for Puma&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;A fair question is whether going from 1 to 4 workers would also mean using 4x more memory. In practice it did not. Copy-on-Write (CoW) lets worker processes share program memory.&lt;br&gt;
(Note that &lt;code&gt;preload_app!&lt;/code&gt; needs to be set in the Puma configuration for CoW to work.)&lt;/p&gt;

&lt;p&gt;Setting &lt;code&gt;MALLOC_ARENA_MAX=2&lt;/code&gt; in the Rails container environment also reduced per-Pod memory usage by about 20%. The details are in a separate article: &lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/katz/why-rails-app-memory-bloat-happens-causes-and-solutions-2025-edition-3g61" class="crayons-story__hidden-navigation-link"&gt;Why Rails App Memory Bloat Happens: Causes and Solutions (2025 Edition)&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/katz" class="crayons-avatar  crayons-avatar--l  "&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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F192880%2Fa0f93e5d-730c-476d-835f-e071842ccc96.jpg" alt="katz profile" class="crayons-avatar__image" width="500" height="500"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/katz" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Katz Sakai
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Katz Sakai
                
              
              &lt;div id="story-author-preview-content-3406459" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/katz" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F192880%2Fa0f93e5d-730c-476d-835f-e071842ccc96.jpg" class="crayons-avatar__image" alt="" width="500" height="500"&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Katz Sakai&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/katz/why-rails-app-memory-bloat-happens-causes-and-solutions-2025-edition-3g61" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Mar 26&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/katz/why-rails-app-memory-bloat-happens-causes-and-solutions-2025-edition-3g61" id="article-link-3406459"&gt;
          Why Rails App Memory Bloat Happens: Causes and Solutions (2025 Edition)
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/ruby"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;ruby&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/rails"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;rails&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/performance"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;performance&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/linux"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;linux&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/katz/why-rails-app-memory-bloat-happens-causes-and-solutions-2025-edition-3g61" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/multi-unicorn-b44d6f8c23cdd00964192bedc38af3e82463978aa611b4365bd33a0f1f4f3e97.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/raised-hands-74b2099fd66a39f2d7eed9305ee0f4553df0eb7b4f11b01b6b1b499973048fe5.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;25&lt;span class="hidden s:inline"&gt; reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/katz/why-rails-app-memory-bloat-happens-causes-and-solutions-2025-edition-3g61#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              Comments


              &lt;span class="hidden s:inline"&gt;Add Comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            5 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


&lt;p&gt;By combining CoW, MALLOC_ARENA_MAX, and reducing the number of threads, the amount of memory used by the Pod decreased from 4.2 GiB to 3.5 GiB, even though the number of worker processes was increased from 1 to 4.&lt;/p&gt;

&lt;p&gt;That said, the memory improvement was a side benefit. What mattered for cost was needing fewer Pods. With only one Puma worker per Pod and a CPU-bound API, each Pod could handle a limited number of concurrent requests. Moving to 4 workers per Pod meant the same traffic could be served with fewer Pods. Fewer Pods meant fewer Nodes, and that drove the cost down.&lt;/p&gt;




&lt;h2&gt;
  
  
  Part 2: Scaling with demand
&lt;/h2&gt;

&lt;p&gt;Now that each Pod was running efficiently, the next step was to reduce costs by stopping unnecessary Pods and Nodes during off-peak hours. However, before enabling autoscaling, it was necessary to configure the system so that Pods could be safely started and stopped at any time.&lt;/p&gt;

&lt;h3&gt;
  
  
  2.0 Prerequisites: making Pods safe to autoscale
&lt;/h3&gt;

&lt;p&gt;Autoscaling Pods means that Kubernetes creates and destroys Pods at any time. If your Pods are not set up for this, you trade cost savings for reliability problems.&lt;/p&gt;

&lt;p&gt;We configured the following before enabling any autoscaling:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Startup probe:&lt;/strong&gt; Our Rails applications can take time to start up (loading the framework, initializing gems, establishing DB connections, warming caches). Without a startup probe, Kubernetes may decide the Pod is not alive during initialization and kill it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Readiness probe:&lt;/strong&gt; This tells Kubernetes whether a Pod is ready to accept traffic. When a Pod temporarily cannot handle requests (during heavy processing or after a brief DB connection loss), the readiness probe fails and Kubernetes removes that Pod from the Service endpoints. Once the probe recovers, the Pod is added back and starts receiving traffic again.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Liveness probe:&lt;/strong&gt; This detects Pods that are running but stuck (a hung Rails process, for example). Kubernetes automatically restarts them. This is important for long-running Pods.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;terminationGracePeriodSeconds + &lt;code&gt;preStop&lt;/code&gt; hook:&lt;/strong&gt; The terminationGracePeriodSeconds is used to configure a graceful shutdown time for requests in progress after a termination signal &lt;code&gt;SIGTERM&lt;/code&gt; is sent to a Pod during Pod scale-in (Pod deletion). Using this setting allows you to specify the time Kubernetes waits before forcibly terminating a Pod, making it easier to prevent request errors during scaling.
However, this setting alone is not sufficient. Kubernetes processes sending the termination signal (SIGTERM) and detaching the Pod from the Service endpoint in parallel, so SIGTERM may arrive before the detachment is complete (after SIGTERM arrives, the Pod enters graceful shutdown and cannot accept new requests). Therefore, it is common to use a &lt;code&gt;preStop&lt;/code&gt; hook to sleep for about 10 seconds, delaying the SIGTERM transmission, to ensure that detachment from the Service endpoint occurs before &lt;code&gt;SIGTERM&lt;/code&gt;.
For more on this timing issue, see &lt;a href="https://engineering.rakuten.today/post/graceful-k8s-delpoyments/" rel="noopener noreferrer"&gt;Zero-Downtime Rolling Deployments in Kubernetes&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are the things we needed to put in place before autoscaling safely. Without them, autoscaling may look fine in theory, but it can cause problems in production. For details on configuring each probe type, see the &lt;a href="https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/" rel="noopener noreferrer"&gt;Kubernetes documentation on liveness, readiness, and startup probes&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  2.1 KEDA with Cron trigger
&lt;/h3&gt;

&lt;p&gt;As this is a B2B service, traffic is concentrated during weekday business hours (approximately 8:30 AM to 7:30 PM), and quiet at night and on weekends. This traffic pattern remained largely unchanged.&lt;/p&gt;

&lt;p&gt;However, the number of Pod replicas (the number of running Pods) was fixed to match the maximum load during peak hours and ran 24/7, 365 days a year. This is a significant waste of resources.&lt;/p&gt;

&lt;p&gt;Given this predictability, we chose &lt;a href="https://keda.sh/" rel="noopener noreferrer"&gt;KEDA&lt;/a&gt;'s Cron trigger. KEDA is an event-driven autoscaler for Kubernetes, and the Cron trigger is one of its simplest scaling options: it adjusts the replica count on a time-based schedule, rather than reacting to metrics like CPU or memory usage. If your traffic pattern is predictable, this is simpler and more reliable than reactive scaling. There is no lag waiting for metrics to cross a threshold, no risk of flapping, and the configuration is easy to understand.&lt;/p&gt;

&lt;p&gt;Our configuration:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Weekdays 08:00-20:00 JST: about 3–4x more replicas than the baseline off-hours level&lt;/li&gt;
&lt;li&gt;All other times: baseline replica count&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No metrics, no thresholds, no reactive logic. For our traffic pattern, that simplicity was a strength.&lt;/p&gt;

&lt;p&gt;Note that Cron-based scaling assumes a stable traffic pattern. If total request volume grows with business growth or the pattern itself changes, the current replica counts may not be enough. To catch that early, API response time and similar metrics are monitored externally on an ongoing basis. Degradation there is the signal to revisit the Cron scaling configuration.&lt;/p&gt;

&lt;p&gt;Cron scaling is not something you set once and never touch again. It just tends to need fewer updates over time.&lt;/p&gt;

&lt;h3&gt;
  
  
  2.2 GKE Node Autoscaling
&lt;/h3&gt;

&lt;p&gt;With KEDA changing Pod replica counts, GKE's Cluster Autoscaler was enabled next.&lt;/p&gt;

&lt;p&gt;The logic is simple: when KEDA scales Pods in, some nodes end up underutilized. The Cluster Autoscaler cordons those nodes and removes them. When KEDA scales Pods out, the autoscaler provisions new nodes to accommodate them. We configured a minimum node count for availability zone redundancy and a maximum to cap costs.&lt;/p&gt;

&lt;p&gt;With KEDA controlling Pod count and Cluster Autoscaler controlling Node count, the cluster now uses only the capacity it actually needs.&lt;/p&gt;




&lt;h2&gt;
  
  
  Results
&lt;/h2&gt;

&lt;p&gt;Comparing the H2 2024 average (before optimization) to the Q1 2026 average (after all changes were in place):&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;Before&lt;/th&gt;
&lt;th&gt;After&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Node type&lt;/td&gt;
&lt;td&gt;n1-highmem-2&lt;/td&gt;
&lt;td&gt;n2d-highmem-2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CoreMark score&lt;/td&gt;
&lt;td&gt;26,293&lt;/td&gt;
&lt;td&gt;41,073 (+56%)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Node RAM&lt;/td&gt;
&lt;td&gt;13 GiB&lt;/td&gt;
&lt;td&gt;16 GiB (+23%)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Node cost (asia-northeast1)&lt;/td&gt;
&lt;td&gt;~$124/month&lt;/td&gt;
&lt;td&gt;~$120/month (-3%)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Puma workers (WEB_CONCURRENCY)&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Threads per worker (RAILS_MAX_THREADS)&lt;/td&gt;
&lt;td&gt;33&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;API auth per request&lt;/td&gt;
&lt;td&gt;bcrypt&lt;/td&gt;
&lt;td&gt;Lighter method&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MALLOC_ARENA_MAX&lt;/td&gt;
&lt;td&gt;Not set&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pod memory request&lt;/td&gt;
&lt;td&gt;4.2Gi&lt;/td&gt;
&lt;td&gt;3.5Gi (-20%)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pod scaling&lt;/td&gt;
&lt;td&gt;Static (always at peak)&lt;/td&gt;
&lt;td&gt;KEDA Cron (higher during weekday daytime, minimum otherwise)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Node scaling&lt;/td&gt;
&lt;td&gt;Fixed count&lt;/td&gt;
&lt;td&gt;Cluster Autoscaler&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;GKE monthly cost&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Baseline&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;-60%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Costs have stabilized since the latter half of 2025, once the initial optimization is complete (see graph at the top of the page).&lt;/p&gt;




&lt;h2&gt;
  
  
  Lessons learned
&lt;/h2&gt;

&lt;p&gt;None of this was clever. Puma config, auth scheme, node type, a cron schedule. Each change looked minor in isolation. Together they cut costs by 60%.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ruby's GVL means adding threads to CPU-bound work does nothing.&lt;/strong&gt; More processes, not more threads. Thread count only matters once you know how I/O-heavy the workload actually is.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;bcrypt is for password hashing, not API tokens.&lt;/strong&gt; Running bcrypt on every API request was just a mistake. It's slow by design, that's the point for password hashing. Using it for per-request token verification was the wrong tool. We replaced it, and the CPU load dropped immediately.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Simple autoscaling is often enough.&lt;/strong&gt; KEDA has a lot of trigger types, metrics, queues, custom event sources. We used none of that. A Cron schedule matched our traffic pattern well enough, and it's been lower maintenance than anything reactive would have been.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Node generation matters more than it looks.&lt;/strong&gt; Just upgrading from n1 to n2d gave 56% better CPU, 23% more memory, and 3% lower cost. If you are still on n1 instances, moving to n2d or similar is worth doing soon.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Optimize the whole system, not just one layer.&lt;/strong&gt; The key to this improvement was not just looking at the infrastructure or the application in isolation, but reviewing both together. Revising the Puma worker configuration and authentication method was an application-side improvement, while updating node generations and autoscaling were infrastructure-side improvements. Rather than tuning just one part, aiming for overall optimization while understanding the connections of the entire stack led to significant results.&lt;/p&gt;

</description>
      <category>rails</category>
      <category>kubernetes</category>
      <category>devops</category>
      <category>performance</category>
    </item>
    <item>
      <title>Non-native English is my hidden strength as a tech blogger</title>
      <dc:creator>Katz Sakai</dc:creator>
      <pubDate>Mon, 30 Mar 2026 12:16:20 +0000</pubDate>
      <link>https://dev.to/katz/non-native-english-is-my-hidden-strength-as-a-tech-blogger-53ga</link>
      <guid>https://dev.to/katz/non-native-english-is-my-hidden-strength-as-a-tech-blogger-53ga</guid>
      <description>&lt;p&gt;Every article about "writing in English as a non-native speaker" seems to give the same advice. Use Grammarly. Read more books. Don't use a translator. Practice.&lt;/p&gt;

&lt;p&gt;That's fine, but it treats the problem as "you're bad at English, fix it." And that misses something.&lt;/p&gt;

&lt;p&gt;Being a non-native speaker is not just an obstacle to work around. Parts of it are actually useful, if you know how to use them.&lt;/p&gt;

&lt;p&gt;My native tongue is Japanese and I write tech posts in English. At some point, I realized my English writing had strengths I didn't expect. My non-native English was actually helping me, not holding me back.&lt;/p&gt;

&lt;p&gt;Here's what I mean.&lt;/p&gt;

&lt;h2&gt;
  
  
  A smaller vocabulary can be a good thing
&lt;/h2&gt;

&lt;p&gt;Writing simply is hard when you know too many words. Native English speakers often use longer words and more complex sentences, not because they're bad writers, but because their brain has more options and picks the familiar ones.&lt;/p&gt;

&lt;p&gt;I don't have that problem. My vocabulary is smaller, so I end up picking clear words. My sentences are shorter because I can't handle seven clauses at once. I avoid idioms because I'm not sure I'll use them right.&lt;/p&gt;

&lt;p&gt;This is not a weakness. This is what every writing guide tells people to do, and it's surprisingly hard when you have a big vocabulary to choose from.&lt;/p&gt;

&lt;p&gt;The audience for English tech blogs is global. A large portion of readers are non-native speakers. Your "limited" English is often easier for them to read than a native speaker's writing.&lt;/p&gt;

&lt;p&gt;Don't try to write like a native speaker. Try to write clearly. They're different things, and clarity wins.&lt;/p&gt;

&lt;h2&gt;
  
  
  Your unique content is the stuff that doesn't exist in English yet
&lt;/h2&gt;

&lt;p&gt;Here's the real advantage nobody talks about.&lt;/p&gt;

&lt;p&gt;If you work in a non-English-speaking tech community, you have access to knowledge, war stories, and ideas that the English-speaking internet has never seen. Conference talks, blog posts, and debugging stories that only exist in your language.&lt;/p&gt;

&lt;p&gt;I translate Japanese technical blog posts into English. Posts about code signing pipelines built around tools and services that English-language blogs don't cover. Every single one of these gets more attention than another "how to do X" tutorial, because the content doesn't already exist in English 50 times over.&lt;/p&gt;

&lt;p&gt;You don't have to translate word by word. Take a problem you solved at work that was discussed in your language, and write about it in English. Add your own context. The idea is the value, not the words.&lt;/p&gt;

&lt;p&gt;Nobody else can write that post. That's your unfair advantage.&lt;/p&gt;

&lt;h2&gt;
  
  
  The "translation test" catches bad writing
&lt;/h2&gt;

&lt;p&gt;Here's a trick I use that I've never seen anyone else mention.&lt;/p&gt;

&lt;p&gt;After I write a draft in English, I mentally translate it back to my native language. If a sentence sounds weird or unclear when translated, it's usually because the English version is also unclear. I just couldn't tell because I was too close to it.&lt;/p&gt;

&lt;p&gt;This works in reverse too. If I can't figure out how to say something in English, I write it in Japanese first. Then I don't translate the words. I translate the idea. The English version almost always comes out simpler and better than if I'd tried to write it directly.&lt;/p&gt;

&lt;p&gt;For me, having two languages means I can use each one to check the other. It's like having an extra pair of eyes.&lt;/p&gt;

&lt;h2&gt;
  
  
  The way you write is part of your voice
&lt;/h2&gt;

&lt;p&gt;Your writing is probably a bit different from a native speaker's. That's not a problem.&lt;/p&gt;

&lt;p&gt;There's a natural urge to smooth out every rough part until your writing looks the same as a native speaker's. But in tech blogs, being yourself matters more than being perfect. A post that reads like it was written by a real person in Tokyo is more interesting than one that could have been written by anyone, anywhere.&lt;/p&gt;

&lt;p&gt;Small things show that you're a real person:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Mentioning where you're based and what tech scene you're part of&lt;/li&gt;
&lt;li&gt;Mentioning tools, services, or ways of working that are common in your country but less known outside of it&lt;/li&gt;
&lt;li&gt;Adding local context sometimes ("In Japan, most companies still use X for this")&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These aren't flaws. They're detail. They make your post memorable.&lt;/p&gt;

&lt;h2&gt;
  
  
  What actually held me back (not language)
&lt;/h2&gt;

&lt;p&gt;These aren't non-native problems. They're writing problems. But I used to blame my English for them, so it's worth mentioning:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Not being specific enough.&lt;/strong&gt; "I improved performance" means nothing. "I reduced memory usage from 3GB to 2.4GB by setting &lt;code&gt;MALLOC_ARENA_MAX=2&lt;/code&gt;" is a post people will bookmark.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Burying the point.&lt;/strong&gt; In Japanese writing, it's common to build up context before reaching the conclusion. English readers want the point up front. I had to teach myself to put the most interesting thing first. "Our Rails app was eating 3GB of RAM. One environment variable cut it by 20%" as the first line, not the conclusion.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Overthinking the writing, underthinking the title.&lt;/strong&gt; I'd spend days on the article body and 30 seconds on the title. This is backwards. The title is what decides if anyone reads the rest. A specific, result-based title ("How we cut our CI build time from 40 minutes to 8") does better than a general one ("Optimizing CI/CD Pipelines") every time. This is true no matter what your native language is.&lt;/p&gt;

&lt;h2&gt;
  
  
  My actual process
&lt;/h2&gt;

&lt;p&gt;For what it's worth, here's the way I do it now:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Outline in my native language.&lt;/strong&gt; The structure is the hard part. Deciding what to say and the order is easier in my own language.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Write the draft in English.&lt;/strong&gt; Not translating from the outline, but writing new sentences in English. The outline is just a guide. This produces more natural English than sentence-by-sentence translation.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Run the translation test.&lt;/strong&gt; Mentally translate the whole draft to my language. If something sounds wrong, the English is probably unclear. If a sentence feels hard to follow, rewrite it shorter.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Write the title last.&lt;/strong&gt; After I know what the article actually says. It's always a bit different from what I planned. I write a title that shows the most interesting part.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Let it sit for a few hours.&lt;/strong&gt; Come back, read the first two paragraphs with fresh eyes. If they don't hook me, rewrite them. The opening matters more than everything else combined.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The point
&lt;/h2&gt;

&lt;p&gt;Most advice for non-native writers is about closing the language gap. And sure, getting better at English helps. But the gap gives you things too. You write simpler. You have content that doesn't exist in English yet. You can use your other language to check your writing. And the way you see things is different from everyone else's.&lt;/p&gt;

&lt;p&gt;Don't just work on your English. Work on what makes you different.&lt;/p&gt;

</description>
      <category>writing</category>
      <category>career</category>
      <category>devjournal</category>
      <category>learning</category>
    </item>
    <item>
      <title>Behind the Streams: Live at Netflix — 2025–10–22 Tokyo Video Tech #10 Session 2 Report</title>
      <dc:creator>Katz Sakai</dc:creator>
      <pubDate>Fri, 27 Mar 2026 05:38:37 +0000</pubDate>
      <link>https://dev.to/tokyo-video-tech/behind-the-streams-live-at-netflix-2025-10-22-tokyo-video-tech-10-session-2-report-5ed7</link>
      <guid>https://dev.to/tokyo-video-tech/behind-the-streams-live-at-netflix-2025-10-22-tokyo-video-tech-10-session-2-report-5ed7</guid>
      <description>&lt;p&gt;Most people know Netflix as the place you go to binge a series on a quiet evening. But since 2023, the company has been venturing into territory where there are no second takes — live streaming, at a scale that now reaches tens of millions of households simultaneously.&lt;/p&gt;

&lt;p&gt;At &lt;strong&gt;&lt;a href="https://luma.com/tokyo-video-tech?utm_source=tvt.devto" rel="noopener noreferrer"&gt;Tokyo Video Tech&lt;/a&gt; #10 “Continuous”&lt;/strong&gt;, held on October 22, 2025 at the Netflix Tokyo office, members of Netflix’s live streaming team gave their first talk in Tokyo. They walked the audience through what it actually takes to pull off a live broadcast at Netflix scale: the multi-path signal routing from venue to Broadcast Operations Center, the cloud encoding pipeline designed so that entire regions can fail without viewers noticing, and the culture of relentless rehearsal — including deliberate failure injection — that turns each one-shot event into something the team has already practiced dozens of times.&lt;/p&gt;

&lt;p&gt;Report by: &lt;a href="https://www.linkedin.com/in/ksakai/" rel="noopener noreferrer"&gt;Katz Sakai&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;Launched in 2023, Netflix’s live streaming service has grown — &lt;strong&gt;through continuous trial and refinement&lt;/strong&gt; — into a platform capable of reaching tens of millions of households simultaneously.&lt;br&gt;
By skillfully building on the technical foundation and operational expertise developed through its VOD business, Netflix has created an architecture that balances the immediacy and reliability required for live broadcasting.&lt;br&gt;
Above all, the session conveyed a clear message: success in live streaming depends on constant preparation and disciplined operational practice behind the scenes.&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.amazonaws.com%2Fuploads%2Farticles%2Fbcwf6oj72lybrmo9duvh.webp" 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.amazonaws.com%2Fuploads%2Farticles%2Fbcwf6oj72lybrmo9duvh.webp" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Live Streaming and Netflix
&lt;/h2&gt;

&lt;p&gt;When people think of Netflix, on-demand streaming is usually what comes to mind.&lt;br&gt;
However, the company has also been taking on the challenge of live streaming as a new form of entertainment.&lt;br&gt;
Its first step in this direction was &lt;a href="https://about.netflix.com/en/news/chris-rock-will-make-history-as-the-first-artist-to-perform-live-on-netflix" rel="noopener noreferrer"&gt;the &lt;em&gt;Chris Rock: Selective Outrage&lt;/em&gt; comedy special, streamed live in 2023 — the first live event in Netflix’s history&lt;/a&gt;, which drew significant attention worldwide. Since then, Netflix has continued to scale its live streaming efforts.&lt;br&gt;
For example, &lt;a href="https://about.netflix.com/en/news/jake-paul-vs-mike-tyson-over-108-million-live-global-viewers" rel="noopener noreferrer"&gt;the boxing event &lt;em&gt;Jake Paul vs. Mike Tyson&lt;/em&gt; was streamed live to &lt;strong&gt;more than 60 million households around the globe&lt;/strong&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  From the Venue to the BOC: Signal Aggregation
&lt;/h2&gt;

&lt;p&gt;One of the biggest challenges in live streaming is ensuring that the video signals from the event venue are reliably delivered to the Broadcast Operations Center (BOC).&lt;/p&gt;

&lt;p&gt;When Netflix first began its live streaming initiatives, all encoding was handled directly at the venue. However, due to various network and infrastructure constraints, achieving scalability proved difficult. Based on these lessons, Netflix transitioned to a model that &lt;strong&gt;minimizes on-site processing and consolidates control within the BOC&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Today, signals are transmitted from the venue to the BOC through multiple independent paths. Satellite links, dedicated lines, IP-based transmission (such as SRT), and bonded cellular connections — each with different technical characteristics — are combined to ensure that even if one path fails, the stream remains uninterrupted.&lt;br&gt;
Furthermore, Netflix conducts technical studies for each transmission method, compiling the resulting insights into recommended parameters and operational guidelines that are shared with vendor partners.&lt;/p&gt;

&lt;p&gt;At the BOC, the incoming signals are monitored and processed as needed.&lt;br&gt;
This may include inserting the Netflix watermark, adding slates such as “Starting Soon” before the event or “Thanks for Watching” after it, and in the event of a total signal loss, displaying an emergency slate to maintain a consistent viewing experience.&lt;/p&gt;

&lt;p&gt;For large-scale international events such as sports or entertainment broadcasts, the BOC also handles multilingual dubbing and audio mixing — producing simultaneous audio tracks in English, Japanese, German, and other languages to deliver regionally tailored streams to global audiences.&lt;/p&gt;
&lt;h2&gt;
  
  
  Converting BOC Signals into Delivery Formats in the Cloud
&lt;/h2&gt;

&lt;p&gt;When transmitting video signals from the Broadcast Operations Center (BOC) to the cloud, Netflix places particular emphasis on &lt;strong&gt;redundancy&lt;/strong&gt; and &lt;strong&gt;synchronization design&lt;/strong&gt; to ensure uninterrupted streaming.&lt;br&gt;
In live broadcasting, even a slight interruption can significantly impact the viewer experience, making it essential to balance both &lt;strong&gt;availability&lt;/strong&gt; and consistency across the system.&lt;/p&gt;

&lt;p&gt;For transmission from the BOC to the cloud, multiple independent paths — such as dedicated lines, IP-based links, and bonded cellular connections — are used in combination. This design ensures that if one route encounters a failure, others can immediately take over.&lt;br&gt;
Within the cloud, the same incoming signal is processed &lt;strong&gt;simultaneously across multiple regions&lt;/strong&gt;, preventing any single point of failure and ensuring high resilience.&lt;/p&gt;

&lt;p&gt;Once the signals reach the cloud, they are first processed by encoders and then passed to live packagers, which convert them into delivery-ready formats.&lt;br&gt;
These packagers leverage Netflix’s extensive knowledge accumulated through years of VOD delivery — specifically, its database of device-level capabilities and limitations. In practice, this means the system already “knows” which codecs, resolutions, and streaming formats are supported on each device, and applies that intelligence directly to live packaging.&lt;br&gt;
As a result, Netflix minimizes compatibility risks and ensures that live streams maintain consistent quality and playback success rates across the vast range of viewing devices.&lt;/p&gt;

&lt;p&gt;The packagers are also designed to handle the precise &lt;strong&gt;timing and segment synchronization&lt;/strong&gt; requirements unique to live streaming.&lt;br&gt;
All cloud encoders reference a shared epoch (a common timing source), ensuring that segments generated in different regions remain interchangeable. This enables instant failover between regions — if one region experiences an outage, another can seamlessly take over without disrupting the live stream.&lt;/p&gt;

&lt;p&gt;In this way, Netflix’s live streaming architecture — built on the three design pillars of &lt;strong&gt;redundancy&lt;/strong&gt;, &lt;strong&gt;time synchronization&lt;/strong&gt;, and &lt;strong&gt;device compatibility&lt;/strong&gt; — maintains high reliability while delivering a &lt;strong&gt;consistent playback experience across diverse devices worldwide&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.amazonaws.com%2Fuploads%2Farticles%2F3f92fdgje8ublztub2lz.jpg" 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.amazonaws.com%2Fuploads%2Farticles%2F3f92fdgje8ublztub2lz.jpg" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Principles of Delivery: Compatibility, Scale, and Quality
&lt;/h2&gt;

&lt;p&gt;The three key elements that Netflix prioritizes in live delivery are &lt;strong&gt;compatibility&lt;/strong&gt;, &lt;strong&gt;scale&lt;/strong&gt;, and &lt;strong&gt;quality&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Compatibility&lt;/strong&gt;&lt;br&gt;
The devices used by Netflix viewers are incredibly diverse — from smartphones released many years ago to the latest high-end devices — representing thousands of playback environments. As with VOD, Netflix’s live streaming platform is designed with the guiding principle of being playable on as many devices as possible.&lt;br&gt;
To achieve this, the delivery protocol is unified under an HTTPS-based approach, allowing existing Netflix players to stream live content without modification.&lt;br&gt;
Video is encoded in both the widely compatible &lt;strong&gt;AVC (H.264)&lt;/strong&gt; and the more efficient &lt;strong&gt;HEVC (H.265)&lt;/strong&gt; formats, while support for the next-generation &lt;strong&gt;AV1&lt;/strong&gt; codec continues to expand. This approach ensures that older devices can still play streams smoothly, while newer ones benefit from higher picture quality — achieving a balance between inclusivity and performance.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scale&lt;/strong&gt;&lt;br&gt;
Live streaming can attract tens of millions of concurrent viewers accessing the service simultaneously. To handle this immense load, Netflix leverages its proprietary CDN, &lt;strong&gt;Open Connect&lt;/strong&gt;. More than 18,000 servers deployed across 6,000 locations worldwide receive live segments from the origin and deliver them to viewers from the nearest node.&lt;br&gt;
The player references manifests built on a &lt;strong&gt;segment template model&lt;/strong&gt;, minimizing server queries and allowing efficient handling of massive request volumes.&lt;br&gt;
This scalable architecture enables stable delivery even during global events such as Jake Paul vs. Mike Tyson.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Quality&lt;/strong&gt;&lt;br&gt;
Just as in VOD, the Netflix player continuously monitors network conditions to select the optimal bitrate in real time. If throughput drops during playback, the system immediately switches to an alternative CDN node or adjusts the video bitrate to prevent buffering. Through these adaptive measures, Netflix ensures that viewers around the world can enjoy a &lt;strong&gt;smooth, uninterrupted live streaming experience&lt;/strong&gt; regardless of their network conditions.&lt;/p&gt;
&lt;h2&gt;
  
  
  Success in Live Streaming Starts with Preparation
&lt;/h2&gt;

&lt;p&gt;Another crucial factor in live streaming is &lt;strong&gt;preparation and operational discipline&lt;/strong&gt;. Unlike VOD, live streaming is a true one-shot performance — there are no retakes. For that reason, the team treats preparation like daily “training,” continuously running tests and rehearsals to stay ready for any scenario.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Building robust test environments&lt;/strong&gt;&lt;br&gt;
To simulate real-world live events, Netflix developed an internal system capable of generating virtual live streams. This system allows engineers to instantly launch test broadcasts and verify the entire pipeline — from encoding and packaging to CDN behavior — under production-like conditions. It enables safe experimentation with new features and architectural changes without risking live operations.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Practicing resilience through failure testing&lt;/strong&gt;&lt;br&gt;
Netflix routinely performs &lt;strong&gt;failure injection&lt;/strong&gt; tests, deliberately introducing issues such as network latency, packet loss, and simulated cloud region outages. This culture of anticipating the unexpected strengthens system robustness, ensuring that even during large-scale global events, live streams remain stable and uninterrupted.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Forecasting and real-time adaptability&lt;/strong&gt;&lt;br&gt;
Every live event begins with audience size predictions powered by machine learning models, allowing teams to pre-provision sufficient cloud and CDN resources. During and immediately before broadcasts, Netflix continuously analyzes real-time viewership trends. If the system detects audience growth exceeding expectations, capacity adjustments are made on the fly to prevent congestion. By integrating thorough &lt;strong&gt;pre-event simulations with agile real-time responses&lt;/strong&gt;, Netflix achieves stable and consistent live delivery under any conditions.&lt;/p&gt;
&lt;h2&gt;
  
  
  In Conclusion
&lt;/h2&gt;

&lt;p&gt;What stood out most in this session was how Netflix approaches live streaming — a relatively new domain for the company — without relying on individual know-how. Instead, the team has established clearly documented procedures and specifications to ensure operational consistency and reproducibility. Even the signal setup from the venue to the Broadcast Operations Center (BOC) has been systematized into operational guidelines rather than being treated as a “black magic”, enabling the same level of quality and reliability across different countries and production environments.&lt;/p&gt;

&lt;p&gt;Equally impressive was how Netflix has effectively leveraged its long-standing technical assets and operational expertise from VOD streaming. By applying its accumulated knowledge of device compatibility and CDN scaling directly to live workflows, the company delivers live events with the same &lt;em&gt;“Netflix-quality”&lt;/em&gt; scalability and reliability that define its on-demand service.&lt;/p&gt;

&lt;p&gt;Above all, the session conveyed a powerful message: &lt;strong&gt;live success is built on daily preparation&lt;/strong&gt;. Through continuous testing in virtual live environments, deliberate failure injections, and regular rehearsals, Netflix ensures that each one-time broadcast runs smoothly when it matters most.&lt;/p&gt;

&lt;p&gt;For more details on Netflix’s live streaming technology and engineering practices, you can refer to their official Netflix Tech Blog.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://netflixtechblog.com/behind-the-streams-live-at-netflix-part-1-d23f917c2f40" rel="noopener noreferrer"&gt;Behind the Streams: Three Years Of Live at Netflix. Part 1.&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://netflixtechblog.com/building-a-reliable-cloud-live-streaming-pipeline-for-netflix-8627c608c967" rel="noopener noreferrer"&gt;Behind the Streams: Building a Reliable Cloud Live Streaming Pipeline for Netflix. Part 2.&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://netflixtechblog.com/behind-the-streams-real-time-recommendations-for-live-events-e027cb313f8f" rel="noopener noreferrer"&gt;Behind the Streams: Real-Time Recommendations for Live Events Part 3.&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
  
  
  Join Us
&lt;/h2&gt;

&lt;p&gt;Tokyo Video Tech is an open community — whether you're a seasoned video engineer or just curious about the field, you're welcome to join.&lt;br&gt;
Visit our page on Luma (an event platform) and hit &lt;strong&gt;Subscribe&lt;/strong&gt; to get notified about upcoming events.&lt;br&gt;
&lt;a href="https://luma.com/tokyo-video-tech?utm_source=tvt.devto" class="crayons-btn crayons-btn--primary" rel="noopener noreferrer"&gt;Subscribe on Luma&lt;/a&gt;
&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>cloud</category>
      <category>devops</category>
      <category>techtalks</category>
    </item>
    <item>
      <title>Live Streaming at "Spring Festival in Tokyo" — 2025–10–22 Tokyo Video Tech #10 Session 1 Report</title>
      <dc:creator>Katz Sakai</dc:creator>
      <pubDate>Fri, 27 Mar 2026 05:36:03 +0000</pubDate>
      <link>https://dev.to/tokyo-video-tech/live-streaming-at-spring-festival-in-tokyo-2025-10-22-tokyo-video-tech-10-session-1-report-4gfg</link>
      <guid>https://dev.to/tokyo-video-tech/live-streaming-at-spring-festival-in-tokyo-2025-10-22-tokyo-video-tech-10-session-1-report-4gfg</guid>
      <description>&lt;p&gt;A classical music festival and an internet infrastructure company might seem like an unlikely pairing. But for the past several years, &lt;strong&gt;IIJ&lt;/strong&gt; (Internet Initiative Japan) — one of Japan's first commercial internet service providers, founded in 1992 and now a major force in cloud, networking, and enterprise IT — has been quietly pushing the boundaries of live streaming technology. Not for esports or pop concerts, but for opera, chamber music, and orchestral performances in the heart of Tokyo's Ueno Park.&lt;/p&gt;

&lt;p&gt;At &lt;strong&gt;&lt;a href="https://luma.com/tokyo-video-tech?utm_source=tvt.devto" rel="noopener noreferrer"&gt;Tokyo Video Tech&lt;/a&gt; #10 "Continuous"&lt;/strong&gt;, held on October 22, 2025 at the Netflix Tokyo office, Fumitaka Watanabe from IIJ pulled back the curtain on what it really takes to deliver these streams: the hundreds of meters of cables laid and removed for every show, the custom subtitle tools built because AI couldn't match the nuance of opera, and the creative networking solutions required when your venue is two floors underground in a World Heritage site.&lt;/p&gt;

&lt;p&gt;Report by: &lt;a href="https://www.linkedin.com/in/ksakai/" rel="noopener noreferrer"&gt;Katz Sakai&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  “Spring Festival in Tokyo” and IIJ
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;&lt;a href="https://www.tokyo-harusai.com/" rel="noopener noreferrer"&gt;The Spring Festival in Tokyo&lt;/a&gt;&lt;/em&gt; is a classical music festival founded in 2005 by maestro Seiji Ozawa and others. Each year, over a period of roughly 40 days from mid-March, around 70 performances are held across 12 to 15 venues throughout Ueno Park. In 2020, the festival was canceled due to the pandemic, but in 2021, IIJ embarked on an unprecedented challenge — &lt;strong&gt;live streaming every single concert&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.amazonaws.com%2Fuploads%2Farticles%2Fp7x8myfudjqtdus8iax7.jpg" 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.amazonaws.com%2Fuploads%2Farticles%2Fp7x8myfudjqtdus8iax7.jpg" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Journey So Far&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;2021: The First Year — “Handcrafted Streaming of Every Performance”
A conference room inside Tokyo Bunka Kaikan was converted into a temporary streaming base. Video feeds from each venue were aggregated via NDI and fiber connections. Performances were streamed in multiple formats including 4K, high-resolution audio, multi-angle views, and with subtitles.&lt;/li&gt;
&lt;li&gt;2022: Transition to Remote Production
A permanent base was established at IIJ’s Iidabashi office, where video from the venues was transmitted via SRT. Encoding and distribution were handled from the office’s stable network environment. IIJ also developed a custom web player that allowed viewers to switch camera angles and check program information in real time.&lt;/li&gt;
&lt;li&gt;2023: Launch of “IIJ Studio TOKYO”
In October 2022, &lt;a href="https://www.iij.ad.jp/iij-studio/" rel="noopener noreferrer"&gt;IIJ built a dedicated in-house streaming studio — IIJ Studio TOKYO&lt;/a&gt; — enabling integrated management of video, audio, and network operations. This dramatically improved the overall stability and quality of the live streams.&lt;/li&gt;
&lt;li&gt;2024: Returning to the Field with Agile Operations
As long-term studio occupation proved difficult, the team brought switching equipment back to Ueno venues, producing finished video signals on-site and transmitting them to IIJ Studio TOKYO via SRT, which continued to serve as the encoding hub. Operations were reoptimized for rapid setup and teardown at each performance.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Challenges Faced in On-Site Operations
&lt;/h2&gt;

&lt;p&gt;The &lt;em&gt;Spring Festival in Tokyo&lt;/em&gt; presents unique challenges due to its long duration and multi-venue nature, requiring IIJ to tackle numerous operational and technical issues.&lt;/p&gt;

&lt;p&gt;One of the most demanding tasks was cable management and setup. Connecting multiple venues scattered across Ueno Park required laying and removing hundreds of meters of cables for every performance. Preventing tripping hazards and cable damage demanded meticulous on-site expertise and coordination.&lt;/p&gt;

&lt;p&gt;Staff allocation was another major concern. At least two operators were assigned to each concert to ensure continuous coverage, but this structure also increased personnel costs and highlighted the difficulty of balancing efficiency with budget constraints.&lt;/p&gt;

&lt;p&gt;The diversity of venue environments posed additional hurdles. Some locations had unstable power supplies, causing equipment malfunctions that required off-site reinspection. Others lacked permanent network connections, making it necessary to arrange mobile or temporary lines months in advance.&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.amazonaws.com%2Fuploads%2Farticles%2Ftg1k28x0fusumz9o9gkr.jpg" 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.amazonaws.com%2Fuploads%2Farticles%2Ftg1k28x0fusumz9o9gkr.jpg" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Unstable networks also proved problematic — especially during the spring &lt;em&gt;hanami&lt;/em&gt; season, when mobile networks in Ueno Park became congested despite the use of bonded multi-carrier connections.&lt;/p&gt;

&lt;p&gt;A further challenge specific to classical music was the precision required for subtitles. In opera performances, the accuracy and timing of subtitles are integral to the artistic experience, and AI-generated captions proved inadequate. IIJ developed an in-house subtitle insertion tool, allowing human translators’ scripts to be synchronized precisely with the performance in real time.&lt;/p&gt;

&lt;p&gt;In summary, delivering these streams demanded not only technical reliability but also deep sensitivity to the cultural context of the event. The success of the project lay in IIJ’s ability to provide a &lt;strong&gt;“culturally informed live streaming experience”&lt;/strong&gt; — one that preserved the integrity and atmosphere of live classical performance.&lt;/p&gt;

&lt;h2&gt;
  
  
  New Challenges in 2025
&lt;/h2&gt;

&lt;p&gt;In 2025, IIJ took its previous initiatives a step further by introducing several new technical and operational challenges.&lt;/p&gt;

&lt;p&gt;First, the team leveraged &lt;strong&gt;Vipe&lt;/strong&gt;, a cloud-based playout system from BCNEXXT, enabling operators to switch between program segments, intermissions, and closing screens with a single click based on a preset timetable. This greatly reduced operational workload and improved on-site efficiency, though it also revealed a new challenge — rising costs associated with cloud services.&lt;/p&gt;

&lt;p&gt;Next, IIJ collaborated with &lt;strong&gt;KORG&lt;/strong&gt; and &lt;strong&gt;NHK Technologies&lt;/strong&gt; to conduct a live stream using &lt;strong&gt;Dolby Atmos&lt;/strong&gt;, delivering an immersive audio experience that traditional stereo streaming could not achieve. This marked a new milestone in IIJ’s efforts to “reproduce the live atmosphere as it is.”&lt;/p&gt;

&lt;p&gt;The team also took on the ambitious task of &lt;strong&gt;testing live streaming from the National Museum of Western Art&lt;/strong&gt;, a World Cultural Heritage site. Due to strict regulations, every piece of equipment required prior approval, along with detailed reports on power usage and personnel. The concert hall’s location — two floors underground — posed further difficulties, as neither wired nor mobile networks were available, and new line installations were prohibited. IIJ overcame this by connecting the museum to the nearby Tokyo Bunka Kaikan across the street via a &lt;strong&gt;60 GHz wireless network&lt;/strong&gt;, successfully transmitting both video and audio.&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.amazonaws.com%2Fuploads%2Farticles%2Fc4a99wid9mua98ud3127.jpg" 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.amazonaws.com%2Fuploads%2Farticles%2Fc4a99wid9mua98ud3127.jpg" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Another major constraint was time. Since the museum remained open to the public until 5 p.m., and the concert opened at 6:30 p.m. and began at 7 p.m., setup time was limited to just &lt;strong&gt;one and a half hours&lt;/strong&gt;, demanding exceptional efficiency and coordination.&lt;/p&gt;

&lt;p&gt;Finally, to address mobile network congestion during the cherry blossom season, IIJ experimented with &lt;strong&gt;local 5G&lt;/strong&gt; connectivity. The trial confirmed that stable 60 fps video transmission was possible, though it also revealed that operating local 5G in Japan involves complex licensing procedures, equipment preparation, and substantial costs and lead time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Network Design Philosophy
&lt;/h2&gt;

&lt;p&gt;For the venue network, IIJ implemented redundancy using multiple connections such as &lt;strong&gt;FLET’S Hikari Cross&lt;/strong&gt; and &lt;strong&gt;FLET’S Hikari Next&lt;/strong&gt;, securing two routing paths — &lt;strong&gt;IPoE&lt;/strong&gt; and &lt;strong&gt;PPPoE&lt;/strong&gt; — and managing them with IIJ’s proprietary router, &lt;strong&gt;&lt;a href="https://www.seil.jp/" rel="noopener noreferrer"&gt;SEIL&lt;/a&gt;&lt;/strong&gt;. In addition, multiple venues around Ueno Park were interconnected in a &lt;strong&gt;mesh topology&lt;/strong&gt;, enabling remote control of cameras and encoders from any location.&lt;/p&gt;

&lt;p&gt;The decision to deploy multiple lines stemmed from practical considerations: during the roughly 40-day festival period, scheduled maintenance by network providers could overlap with event days, and relying on a single line would risk operational downtime.&lt;/p&gt;

&lt;p&gt;At the same time, IIJ emphasized that redundancy is &lt;strong&gt;not simply “the more, the better.”&lt;/strong&gt; Excessive investment in multi-layered redundancy could harm overall profitability. Redundancy is a safeguard — a means of maintaining peace of mind and operational continuity. The real challenge lies in striking the right balance between &lt;strong&gt;cost efficiency and service reliability&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  International Collaboration — Partnership with Berlin Phil Media
&lt;/h2&gt;

&lt;p&gt;Since 2016, &lt;a href="https://www.iij.ad.jp/cd/berlin.html" rel="noopener noreferrer"&gt;IIJ has collaborated with &lt;strong&gt;Berlin Phil Media&lt;/strong&gt;, the streaming subsidiary of the &lt;strong&gt;Berlin Philharmonic Orchestra&lt;/strong&gt;&lt;/a&gt;, conducting joint experiments on &lt;strong&gt;uncompressed audio transmission&lt;/strong&gt; and &lt;strong&gt;high-quality international live relays&lt;/strong&gt;. Together, the two organizations have pursued the goal of achieving classical music streaming that surpasses traditional broadcast standards, continuing research and technical verification on advanced delivery systems and cross-border integration.&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.amazonaws.com%2Fuploads%2Farticles%2Fv845rdllyn2zlj811pg6.jpg" 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.amazonaws.com%2Fuploads%2Farticles%2Fv845rdllyn2zlj811pg6.jpg" alt="Silent robotic camera used by the Berlin Phil Media" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The greatest lesson IIJ has learned from working with Berlin Phil Media is their unwavering &lt;strong&gt;“orchestra-first” philosophy&lt;/strong&gt;. Every technical decision is made with the musicians in mind — for example, the robotic cameras installed in the concert hall operate completely silently so as not to disturb performers’ concentration. This meticulous attention to the artistic environment inspired IIJ to reaffirm the importance of &lt;strong&gt;harmonizing technology and artistry&lt;/strong&gt; in classical music streaming.&lt;/p&gt;

&lt;h2&gt;
  
  
  In Conclusion
&lt;/h2&gt;

&lt;p&gt;What left the deepest impression from this session was realizing just how much effort and ingenuity lie behind the seemingly effortless beauty of a concert live stream. From laying cables and designing network routes to fine-tuning subtitles, countless individual actions come together to create the seamless environment that allows us, the audience, to enjoy music online.&lt;/p&gt;

&lt;p&gt;It was also inspiring to see how the IIJ team never simply repeats the same routine each year, but continually embraces new technological frontiers — such as cloud-based operations, Dolby Atmos, and local 5G. Their pursuit of stability while still pushing the boundaries of innovation truly embodies IIJ’s mission: to evolve the Internet into a genuine social infrastructure.&lt;/p&gt;




&lt;h2&gt;
  
  
  Join Us
&lt;/h2&gt;

&lt;p&gt;Tokyo Video Tech is an open community — whether you're a seasoned video engineer or just curious about the field, you're welcome to join.&lt;br&gt;
Visit our page on Luma (an event platform) and hit &lt;strong&gt;Subscribe&lt;/strong&gt; to get notified about upcoming events.&lt;br&gt;
&lt;a href="https://luma.com/tokyo-video-tech?utm_source=tvt.devto" class="crayons-btn crayons-btn--primary" rel="noopener noreferrer"&gt;Subscribe on Luma&lt;/a&gt;
&lt;/p&gt;

</description>
      <category>techtalks</category>
      <category>video</category>
      <category>architecture</category>
      <category>cloud</category>
    </item>
    <item>
      <title>IIJ and Netflix Talk Live Streaming at Tokyo Video Tech #10 (Seminar Report)</title>
      <dc:creator>Katz Sakai</dc:creator>
      <pubDate>Fri, 27 Mar 2026 05:26:50 +0000</pubDate>
      <link>https://dev.to/tokyo-video-tech/2025-10-22-tokyo-video-tech-10-reporten-4de4</link>
      <guid>https://dev.to/tokyo-video-tech/2025-10-22-tokyo-video-tech-10-reporten-4de4</guid>
      <description>&lt;p&gt;On October 22, 2025, &lt;strong&gt;&lt;a href="https://luma.com/tokyo-video-tech?utm_source=tvt.devto" rel="noopener noreferrer"&gt;Tokyo Video Tech&lt;/a&gt; #10 “Continuous”&lt;/strong&gt; took place at the Netflix Tokyo office.&lt;/p&gt;

&lt;p&gt;This time, the theme was &lt;strong&gt;live streaming&lt;/strong&gt;, with engineers from IIJ and Netflix sharing what they’ve learned and the challenges they’ve faced in the field. It was also the &lt;strong&gt;first time Netflix’s live streaming team gave a talk in Tokyo&lt;/strong&gt;, which made for an especially lively Q&amp;amp;A session. Here’s a quick recap of the event.&lt;/p&gt;

&lt;p&gt;Report by: &lt;a href="https://www.linkedin.com/in/ksakai/" rel="noopener noreferrer"&gt;Katz Sakai&lt;/a&gt;&lt;br&gt;
(&lt;a href="https://medium.com/tokyo-video-tech/2025-10-22-tokyo-vide-tech-10-seminar-report-ja-c3201c09401a" rel="noopener noreferrer"&gt;本レポートの日本語版はこちらからご覧いただけます&lt;/a&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.amazonaws.com%2Fuploads%2Farticles%2Flfn3ugtmdzhm6yn5qk8i.jpg" 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.amazonaws.com%2Fuploads%2Farticles%2Flfn3ugtmdzhm6yn5qk8i.jpg" alt="Group Photo" width="800" height="288"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Executive Summary
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Session 1 — Live Streaming at “Spring Festival in Tokyo”
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Since 2021, IIJ has been responsible for live streaming all performances of the Spring Festival in Tokyo. Covering multiple venues across Ueno Park over an extended period, IIJ recreated the sense of being at a live classical concert through innovations such as network redundancy and a custom subtitle tool.&lt;/li&gt;
&lt;li&gt;They continue to &lt;strong&gt;balance stability and innovation each year&lt;/strong&gt;, taking on new technologies like &lt;strong&gt;cloud playout, Dolby Atmos, local 5G, and 60GHz wireless transmission&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Through international collaboration with &lt;strong&gt;Berlin Phil Media&lt;/strong&gt; and others, the team addresses the unique sensitivity of broadcasting cultural events, expanding the reach of live music through technology.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;▶️ &lt;a href="https://dev.to/tokyo-video-tech/live-streaming-at-spring-festival-in-tokyo-2025-10-22-tokyo-video-tech-10-session-1-report-4gfg"&gt;Read the full session report&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Session 2 — Behind the Streams: Live at Netflix
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Netflix launched its live streaming service in 2023, which has since grown to support &lt;strong&gt;simultaneous viewing by tens of millions of households worldwide&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;By connecting live venues to the cloud through multiple redundant paths with synchronized timing, Netflix ensures reliable signal transmission. &lt;strong&gt;Leveraging its long-standing VOD expertise&lt;/strong&gt;, the company supports playback across a wide variety of global devices. Through audience forecasting and real-time monitoring, Netflix dynamically manages capacity and bitrate to deliver a consistently high-quality viewing experience.&lt;/li&gt;
&lt;li&gt;Continuous testing and &lt;strong&gt;failure injection&lt;/strong&gt; have strengthened operational readiness, enabling Netflix to maintain stable live streaming performance even in one-shot live events.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;▶️ &lt;a href="https://dev.to/tokyo-video-tech/behind-the-streams-live-at-netflix-2025-10-22-tokyo-video-tech-10-session-2-report-5ed7"&gt;Read the full session report&lt;/a&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  Opening Remarks: Celebrating the 10th Edition
&lt;/h2&gt;

&lt;p&gt;The event kicked off with opening remarks from Hayashi-san, one of the organizers of Tokyo Video Tech.&lt;/p&gt;

&lt;p&gt;Tokyo Video Tech started back in October 2018, inspired by &lt;strong&gt;&lt;a href="https://www.demuxed.com/" rel="noopener noreferrer"&gt;Demuxed&lt;/a&gt;&lt;/strong&gt;, a global conference for video engineers held in San Francisco. The idea was simple but powerful — to create a place in Japan where video engineers could connect, share, and learn from each other. Since then, the community has grown beyond Japan, hosting guest sessions at &lt;strong&gt;&lt;a href="https://medium.com/tokyo-video-tech/report-taipei-video-technology-3-336a514cf3b8" rel="noopener noreferrer"&gt;Taiwan Multimedia Technology&lt;/a&gt;&lt;/strong&gt; and &lt;strong&gt;&lt;a href="https://medium.com/tokyo-video-tech/vdd2023-dublin-report-en-27e181ecc9d3" rel="noopener noreferrer"&gt;Dublin&lt;/a&gt;&lt;/strong&gt;, expanding its reach worldwide.&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.amazonaws.com%2Fuploads%2Farticles%2Fpvxl8ixvk0gzr1i1zapt.jpg" 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.amazonaws.com%2Fuploads%2Farticles%2Fpvxl8ixvk0gzr1i1zapt.jpg" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The group has also been deeply involved in the broader video tech community — for instance, Tokyo Video Tech supported &lt;strong&gt;&lt;a href="https://images.videolan.org/videolan/events/vdd19/" rel="noopener noreferrer"&gt;Video Dev Days 2019&lt;/a&gt;&lt;/strong&gt;, an event that brought together over a hundred developers of &lt;strong&gt;VLC, ffmpeg&lt;/strong&gt;, and the &lt;strong&gt;AV1 decoder dav1d&lt;/strong&gt; for two days of in-depth discussions in Tokyo.&lt;/p&gt;

&lt;p&gt;So, how did this edition end up being hosted at Netflix Tokyo?&lt;br&gt;
It all started with a message from &lt;strong&gt;&lt;a href="http://flv.io/" rel="noopener noreferrer"&gt;Flávio Ribeiro&lt;/a&gt;&lt;/strong&gt; at Netflix about four weeks ago:&lt;/p&gt;

&lt;p&gt;“We’re planning to be in Tokyo — how about hosting a meetup together?”&lt;br&gt;
A quick “Sure, we can do it!” set everything in motion.&lt;/p&gt;

&lt;p&gt;Thanks to the support of the &lt;strong&gt;Netflix Tokyo office, IIJ&lt;/strong&gt;, and the &lt;strong&gt;Tokyo Video Tech team&lt;/strong&gt;, the meetup came together in just four weeks and welcomed about &lt;strong&gt;35 participants&lt;/strong&gt; on the day of the event.&lt;/p&gt;

&lt;p&gt;Hayashi-san wrapped up his opening remarks by saying:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“This event’s theme, Continuous, represents our determination to keep moving forward — to continue learning and engaging in dialogue, even after the pandemic. I hope today’s meetup will be a fruitful and inspiring one for everyone here.”&lt;/p&gt;
&lt;/blockquote&gt;


&lt;h2&gt;
  
  
  The Sessions
&lt;/h2&gt;

&lt;p&gt;What followed were two sessions that showed just how different, and how fascinating, the world of live streaming can be depending on who's behind it and what's at stake.&lt;/p&gt;

&lt;p&gt;In the first session, IIJ’s Fumitaka Watanabe took us inside the unlikely intersection of internet infrastructure and classical music — where hundreds of meters of cable are laid and removed for every performance, and a World Heritage venue two floors underground becomes a live streaming challenge unlike any other.&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/tokyo-video-tech/live-streaming-at-spring-festival-in-tokyo-2025-10-22-tokyo-video-tech-10-session-1-report-4gfg" class="crayons-story__hidden-navigation-link"&gt;Live Streaming at "Spring Festival in Tokyo" — 2025–10–22 Tokyo Video Tech #10 Session 1 Report&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;
          &lt;a class="crayons-logo crayons-logo--l" href="/tokyo-video-tech"&gt;
            &lt;img alt="Tokyo Video Tech logo" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F12820%2Fd25ef94f-c7d6-40ec-a1df-749ecc98004a.png" class="crayons-logo__image" width="400" height="400"&gt;
          &lt;/a&gt;

          &lt;a href="/katz" class="crayons-avatar  crayons-avatar--s absolute -right-2 -bottom-2 border-solid border-2 border-base-inverted  "&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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F192880%2F8756053a-f9dd-4672-9c58-ce517bbd0b3c.png" alt="katz profile" class="crayons-avatar__image" width="256" height="256"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/katz" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Katz Sakai
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Katz Sakai
                
              
              &lt;div id="story-author-preview-content-3412065" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/katz" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F192880%2F8756053a-f9dd-4672-9c58-ce517bbd0b3c.png" class="crayons-avatar__image" alt="" width="256" height="256"&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Katz Sakai&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

            &lt;span&gt;
              &lt;span class="crayons-story__tertiary fw-normal"&gt; for &lt;/span&gt;&lt;a href="/tokyo-video-tech" class="crayons-story__secondary fw-medium"&gt;Tokyo Video Tech&lt;/a&gt;
            &lt;/span&gt;
          &lt;/div&gt;
          &lt;a href="https://dev.to/tokyo-video-tech/live-streaming-at-spring-festival-in-tokyo-2025-10-22-tokyo-video-tech-10-session-1-report-4gfg" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Mar 27&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/tokyo-video-tech/live-streaming-at-spring-festival-in-tokyo-2025-10-22-tokyo-video-tech-10-session-1-report-4gfg" id="article-link-3412065"&gt;
          Live Streaming at "Spring Festival in Tokyo" — 2025–10–22 Tokyo Video Tech #10 Session 1 Report
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag crayons-tag--filled  " href="/t/techtalks"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;techtalks&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/video"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;video&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/architecture"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;architecture&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/cloud"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;cloud&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/tokyo-video-tech/live-streaming-at-spring-festival-in-tokyo-2025-10-22-tokyo-video-tech-10-session-1-report-4gfg" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/fire-f60e7a582391810302117f987b22a8ef04a2fe0df7e3258a5f49332df1cec71e.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;1&lt;span class="hidden s:inline"&gt; reaction&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/tokyo-video-tech/live-streaming-at-spring-festival-in-tokyo-2025-10-22-tokyo-video-tech-10-session-1-report-4gfg#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              Comments


              &lt;span class="hidden s:inline"&gt;Add Comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            6 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


&lt;p&gt;In the second session, Netflix’s live streaming team — presenting in Tokyo for the first time — revealed the engineering behind broadcasts that reach tens of millions of households, from multi-path signal routing to a culture of deliberate failure injection that turns every one-shot event into something they’ve already rehearsed dozens of times.&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/tokyo-video-tech/behind-the-streams-live-at-netflix-2025-10-22-tokyo-video-tech-10-session-2-report-5ed7" class="crayons-story__hidden-navigation-link"&gt;Behind the Streams: Live at Netflix — 2025–10–22 Tokyo Video Tech #10 Session 2 Report&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;
          &lt;a class="crayons-logo crayons-logo--l" href="/tokyo-video-tech"&gt;
            &lt;img alt="Tokyo Video Tech logo" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F12820%2Fd25ef94f-c7d6-40ec-a1df-749ecc98004a.png" class="crayons-logo__image" width="400" height="400"&gt;
          &lt;/a&gt;

          &lt;a href="/katz" class="crayons-avatar  crayons-avatar--s absolute -right-2 -bottom-2 border-solid border-2 border-base-inverted  "&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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F192880%2F8756053a-f9dd-4672-9c58-ce517bbd0b3c.png" alt="katz profile" class="crayons-avatar__image" width="256" height="256"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/katz" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Katz Sakai
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Katz Sakai
                
              
              &lt;div id="story-author-preview-content-3412124" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/katz" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F192880%2F8756053a-f9dd-4672-9c58-ce517bbd0b3c.png" class="crayons-avatar__image" alt="" width="256" height="256"&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Katz Sakai&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

            &lt;span&gt;
              &lt;span class="crayons-story__tertiary fw-normal"&gt; for &lt;/span&gt;&lt;a href="/tokyo-video-tech" class="crayons-story__secondary fw-medium"&gt;Tokyo Video Tech&lt;/a&gt;
            &lt;/span&gt;
          &lt;/div&gt;
          &lt;a href="https://dev.to/tokyo-video-tech/behind-the-streams-live-at-netflix-2025-10-22-tokyo-video-tech-10-session-2-report-5ed7" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Mar 27&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/tokyo-video-tech/behind-the-streams-live-at-netflix-2025-10-22-tokyo-video-tech-10-session-2-report-5ed7" id="article-link-3412124"&gt;
          Behind the Streams: Live at Netflix — 2025–10–22 Tokyo Video Tech #10 Session 2 Report
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag crayons-tag--filled  " href="/t/techtalks"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;techtalks&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/architecture"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;architecture&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/cloud"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;cloud&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/devops"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;devops&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/tokyo-video-tech/behind-the-streams-live-at-netflix-2025-10-22-tokyo-video-tech-10-session-2-report-5ed7" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/raised-hands-74b2099fd66a39f2d7eed9305ee0f4553df0eb7b4f11b01b6b1b499973048fe5.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;1&lt;span class="hidden s:inline"&gt; reaction&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/tokyo-video-tech/behind-the-streams-live-at-netflix-2025-10-22-tokyo-video-tech-10-session-2-report-5ed7#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              Comments


              &lt;span class="hidden s:inline"&gt;Add Comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            7 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;





&lt;h2&gt;
  
  
  Join Us
&lt;/h2&gt;

&lt;p&gt;Tokyo Video Tech is an open community — whether you're a seasoned video engineer or just curious about the field, you're welcome to join.&lt;br&gt;
Visit our page on Luma (an event platform) and hit &lt;strong&gt;Subscribe&lt;/strong&gt; to get notified about upcoming events.&lt;br&gt;
&lt;a href="https://luma.com/tokyo-video-tech?utm_source=tvt.devto" class="crayons-btn crayons-btn--primary" rel="noopener noreferrer"&gt;Subscribe on Luma&lt;/a&gt;
&lt;/p&gt;

</description>
      <category>techtalks</category>
      <category>video</category>
      <category>webdev</category>
      <category>architecture</category>
    </item>
    <item>
      <title>OpenSSL Engine API Explained: Connecting Google Cloud KMS, YubiKey, and More</title>
      <dc:creator>Katz Sakai</dc:creator>
      <pubDate>Fri, 27 Mar 2026 00:22:50 +0000</pubDate>
      <link>https://dev.to/katz/what-is-the-openssl-engine-api-integrating-cloud-hsms-and-yubikeys-with-openssl-2db8</link>
      <guid>https://dev.to/katz/what-is-the-openssl-engine-api-integrating-cloud-hsms-and-yubikeys-with-openssl-2db8</guid>
      <description>&lt;p&gt;OpenSSL can do more than work with local key files. Through its Engine API, it can delegate signing and encryption to external hardware like cloud HSMs or YubiKeys, so your private key never has to leave secure storage. This matters more than ever: since June 2023, code signing certificates require that private keys be stored on FIPS 140-2 Level 2 compliant hardware. This post explains how the Engine API works, how it connects to Google Cloud KMS and YubiKey via PKCS#11, and why it has become essential for modern code signing workflows.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Is the OpenSSL Engine API?
&lt;/h2&gt;

&lt;p&gt;While OpenSSL implements fundamental cryptographic operations such as encryption and signing on its own, it also provides a plugin-like mechanism called the Engine API that allows these operations to be delegated to external hardware. By using the Engine API, cryptographic operations provided by cloud-based HSMs (Hardware Security Modules) or other external hardware can be called transparently through OpenSSL.&lt;/p&gt;

&lt;p&gt;By delegating cryptographic operations to secure hardware such as HSMs, it becomes possible to perform operations like signing while keeping the private key stored securely on the hardware — all while still using OpenSSL.&lt;/p&gt;

&lt;h2&gt;
  
  
  Examples of Engine API Usage
&lt;/h2&gt;

&lt;p&gt;One example of the Engine API in action is the &lt;a href="https://github.com/OpenSC/libp11" rel="noopener noreferrer"&gt;pkcs11 engine plugin&lt;/a&gt;. This plugin enables OpenSSL to access cryptographic devices that implement the PKCS#11 interface.&lt;/p&gt;

&lt;p&gt;Google has published the &lt;a href="https://github.com/GoogleCloudPlatform/kms-integrations" rel="noopener noreferrer"&gt;Google PKCS #11 Cloud KMS Library&lt;/a&gt;, which allows Google Cloud HSMs to be operated via PKCS#11. By using this library, encryption and signing operations can be executed on Google Cloud's HSMs.&lt;/p&gt;

&lt;p&gt;Similarly, Yubico has published &lt;a href="https://developers.yubico.com/yubico-piv-tool/YKCS11/Supported_applications/openssl_engine.html" rel="noopener noreferrer"&gt;YKCS11&lt;/a&gt;, which enables YubiKey hardware to be operated via PKCS#11. Through this, OpenSSL can invoke operations that use asymmetric private keys stored on the YubiKey hardware.&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.amazonaws.com%2Fuploads%2Farticles%2F0wjp9pcvjkat0wvutcxd.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.amazonaws.com%2Fuploads%2Farticles%2F0wjp9pcvjkat0wvutcxd.png" alt="Conceptual diagram of the Engine API" width="322" height="392"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Benefits of Externalizing Signing and Other Operations via the Engine API
&lt;/h2&gt;

&lt;p&gt;By using the OpenSSL Engine API, it becomes possible to perform cryptographic operations such as signing on FIPS 140-2 Level 3 compliant hardware like Google Cloud HSM. As a result, private keys never leave the HSM, significantly reducing the risk of key leakage.&lt;/p&gt;

&lt;p&gt;A real-world example of a serious security incident caused by key leakage is the &lt;a href="https://www.threatdown.com/blog/stolen-nvidia-certificates-used-to-sign-malware-heres-what-to-do/" rel="noopener noreferrer"&gt;discovery in 2022 that Nvidia's code signing certificates were being used to sign malware&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Because incidents involving the leak of code signing keys have occurred repeatedly, an industry rule was established requiring that, as of June 1, 2023, private keys used for code signing must be stored on FIPS 140-2 Level 2 compliant hardware&lt;sup id="fnref1"&gt;1&lt;/sup&gt;&lt;sup id="fnref2"&gt;2&lt;/sup&gt;. The Engine API has become an essential means of meeting these industry requirements.&lt;/p&gt;




&lt;ol&gt;

&lt;li id="fn1"&gt;
&lt;p&gt;&lt;a href="https://support.globalsign.com/code-signing/new-requirements-related-private-key-protection-codesigning-certificates" rel="noopener noreferrer"&gt;https://support.globalsign.com/code-signing/new-requirements-related-private-key-protection-codesigning-certificates&lt;/a&gt; ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn2"&gt;
&lt;p&gt;CA/Browser Forum document outlining code signing certificate requirements: &lt;a href="https://cabforum.org/working-groups/code-signing/requirements/" rel="noopener noreferrer"&gt;https://cabforum.org/working-groups/code-signing/requirements/&lt;/a&gt; ↩&lt;/p&gt;
&lt;/li&gt;

&lt;/ol&gt;

</description>
      <category>openssl</category>
      <category>security</category>
      <category>infosec</category>
    </item>
    <item>
      <title>Why Rails App Memory Bloat Happens: Causes and Solutions (2025 Edition)</title>
      <dc:creator>Katz Sakai</dc:creator>
      <pubDate>Thu, 26 Mar 2026 10:58:03 +0000</pubDate>
      <link>https://dev.to/katz/why-rails-app-memory-bloat-happens-causes-and-solutions-2025-edition-27c4</link>
      <guid>https://dev.to/katz/why-rails-app-memory-bloat-happens-causes-and-solutions-2025-edition-27c4</guid>
      <description>&lt;p&gt;After running a Rails application in production for a while, you may encounter a phenomenon where memory usage grows unexpectedly large. In July 2025, I investigated the causes of this behavior and explored countermeasures. Here is a summary of my findings.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The reason a Rails app "appears to keep consuming memory" is that glibc, which Ruby relies on, retains freed memory internally for future reuse rather than returning it to the OS. This is distinct from a typical memory leak.&lt;/li&gt;
&lt;li&gt;Starting with Ruby 3.3.0, you can optimize the heap by calling &lt;code&gt;Process.warmup&lt;/code&gt; after the Rails application has finished booting. However, since this mechanism is intended to be executed at the completion of the application boot sequence, it is not easily applicable to reducing memory in long-running Rails applications.&lt;/li&gt;
&lt;li&gt;As a countermeasure that requires zero changes to product code, setting the environment variable &lt;code&gt;MALLOC_ARENA_MAX=2&lt;/code&gt; remains effective. This prevents glibc from creating numerous arenas (memory pools) one after another, forcing memory reuse within existing pools and thereby preventing glibc from hoarding too much freed memory.&lt;/li&gt;
&lt;li&gt;Switching to the memory allocator jemalloc, which was previously recommended as an effective countermeasure, should now be avoided because jemalloc is no longer being maintained.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why Does Memory Usage in Rails Apps Appear to Keep Growing?
&lt;/h2&gt;

&lt;p&gt;Hongli Lai's article "What causes Ruby memory bloat?" covers this in detail.&lt;br&gt;
&lt;/p&gt;
&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
        &lt;div class="c-embed__cover"&gt;
          &lt;a href="https://www.joyfulbikeshedding.com/blog/2019-03-14-what-causes-ruby-memory-bloat.html" class="c-link align-middle" rel="noopener noreferrer"&gt;
            &lt;img alt="" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.joyfulbikeshedding.com%2Fimages%2F2019%2Fruby_memory_bloat_banner-f0bdf762.png" height="auto" class="m-0"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="c-embed__body"&gt;
        &lt;h2 class="fs-xl lh-tight"&gt;
          &lt;a href="https://www.joyfulbikeshedding.com/blog/2019-03-14-what-causes-ruby-memory-bloat.html" rel="noopener noreferrer" class="c-link"&gt;
            What causes Ruby memory bloat? – Joyful Bikeshedding
          &lt;/a&gt;
        &lt;/h2&gt;
          &lt;p class="truncate-at-3"&gt;
            Ruby apps can use a lot of memory. But why? Various people in the community attribute it to memory fragmentation, and provide two “hacky” solutions. Dissatisfied by the current explanations and provided solutions, I set out on a journey to discover the deeper truth and to find better solutions.

          &lt;/p&gt;
        &lt;div class="color-secondary fs-s flex items-center"&gt;
            &lt;img alt="favicon" class="c-embed__favicon m-0 mr-2 radius-0" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.joyfulbikeshedding.com%2Fimages%2Ffavicon-1b152eac.png"&gt;
          joyfulbikeshedding.com
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;According to that article, the reasons memory bloat "appears to occur" are as follows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The previously cited explanation of "heap page fragmentation on the Ruby side" was not, in fact, a major contributor to increased memory usage.&lt;/li&gt;
&lt;li&gt;The true cause was that "glibc's memory allocator, malloc, retains memory that Ruby has freed instead of returning it to the OS, holding onto it for future use." In particular, free pages that are not at the end of the heap are not returned to the OS, so unused memory continues to accumulate internally. From the OS's perspective, this makes it look like "Ruby keeps consuming memory."&lt;/li&gt;
&lt;li&gt;Calling glibc's &lt;code&gt;malloc_trim(0)&lt;/code&gt; ensures that memory freed by Ruby is returned to the OS, effectively reducing the process's memory usage (RSS) as seen by the OS.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;However, there is an extremely important caveat here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Memory that Ruby has allocated and freed during processing is usually fragmented. Calling &lt;code&gt;malloc_trim(0)&lt;/code&gt; does not resolve the fragmentation; it merely returns the fragmented regions to the OS as-is.&lt;/li&gt;
&lt;li&gt;Even though the memory is fragmented, it is still returned to the OS, so Ruby's memory usage (RSS) as seen by the OS does decrease. However, because other programs cannot allocate contiguous regions from fragmented free memory, an OOM (Out of Memory) error can occur even when there appears to be free memory available.&lt;/li&gt;
&lt;li&gt;Since returning fragmented memory to the OS does not make it easy to reuse effectively, malloc is designed to retain allocated but unused memory internally and reuse it, enabling stable allocation.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is one of the reasons "Ruby has freed the memory, but malloc does not readily return it to the OS."&lt;sup id="fnref1"&gt;1&lt;/sup&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What Is the Memory Bloat Countermeasure Code Introduced in Ruby 3.3.0?
&lt;/h2&gt;

&lt;p&gt;Ruby 3.3.0 introduced the &lt;code&gt;Process.warmup&lt;/code&gt; method. This method is intended to signal to the Ruby virtual machine from an application server or similar that "the application's startup sequence has completed, making this an optimal time to perform GC and memory optimization."&lt;sup id="fnref2"&gt;2&lt;/sup&gt;&lt;br&gt;
When &lt;code&gt;Process.warmup&lt;/code&gt; is called, the Ruby virtual machine performs the following optimization operations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Forces a major GC&lt;/li&gt;
&lt;li&gt;Compacts the heap&lt;/li&gt;
&lt;li&gt;Promotes all surviving objects to the old generation&lt;/li&gt;
&lt;li&gt;Pre-computes string coderanges (to speed up future string operations)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This cleans up objects and caches that were generated during application startup but are no longer needed, improving memory sharing efficiency in Copy-on-Write (CoW) environments.&lt;/p&gt;

&lt;p&gt;Furthermore, since unnecessary objects have been garbage collected and the heap has been compacted, there is a high likelihood that fragmentation in the heap allocated by malloc has been reduced. This makes it an ideal time to call &lt;code&gt;malloc_trim(0)&lt;/code&gt;, and a patch that calls &lt;code&gt;malloc_trim(0)&lt;/code&gt; internally within &lt;code&gt;Process.warmup&lt;/code&gt; has been merged.&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_github-liquid-tag"&gt;
  &lt;h1&gt;
    &lt;a href="https://github.com/ruby/ruby/pull/8451" rel="noopener noreferrer"&gt;
      &lt;img class="github-logo" alt="GitHub logo" src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg"&gt;
      &lt;span class="issue-title"&gt;
        Process.warmup: invoke `malloc_trim` if available
      &lt;/span&gt;
      &lt;span class="issue-number"&gt;#8451&lt;/span&gt;
    &lt;/a&gt;
  &lt;/h1&gt;
  &lt;div class="github-thread"&gt;
    &lt;div class="timeline-comment-header"&gt;
      &lt;a href="https://github.com/casperisfine" rel="noopener noreferrer"&gt;
        &lt;img class="github-liquid-tag-img" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Favatars.githubusercontent.com%2Fu%2F19192189%3Fv%3D4" alt="casperisfine avatar"&gt;
      &lt;/a&gt;
      &lt;div class="timeline-comment-header-text"&gt;
        &lt;strong&gt;
          &lt;a href="https://github.com/casperisfine" rel="noopener noreferrer"&gt;casperisfine&lt;/a&gt;
        &lt;/strong&gt; posted on &lt;a href="https://github.com/ruby/ruby/pull/8451" rel="noopener noreferrer"&gt;&lt;time&gt;Sep 15, 2023&lt;/time&gt;&lt;/a&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class="ltag-github-body"&gt;
      &lt;p&gt;Similar to releasing free GC pages, releasing free malloc pages reduce the amount of page faults post fork.&lt;/p&gt;
&lt;p&gt;NB: Some popular allocators such as &lt;code&gt;jemalloc&lt;/code&gt; don't implement it, so it's a noop for them.&lt;/p&gt;

    &lt;/div&gt;
    &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/ruby/ruby/pull/8451" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;An important point is that &lt;code&gt;Process.warmup&lt;/code&gt; is not automatically called behind the scenes like GC. It is the kind of method that should be explicitly called at an appropriate time on the application server side when a major GC would be acceptable (e.g., before forking, before worker startup). Therefore, there may not always be an appropriate time to call it in long-running Rails applications.&lt;/p&gt;

&lt;h2&gt;
  
  
  Countermeasures for Memory Bloat in Long-Running Rails Apps
&lt;/h2&gt;

&lt;p&gt;So how can you suppress memory bloat without using &lt;code&gt;Process.warmup&lt;/code&gt; or &lt;code&gt;malloc_trim(0)&lt;/code&gt;?&lt;/p&gt;

&lt;p&gt;Online resources have recommended using jemalloc, a smarter memory allocator. However, &lt;a href="https://github.com/jemalloc/jemalloc" rel="noopener noreferrer"&gt;jemalloc's repository was archived in June 2025&lt;/a&gt;, and ongoing maintenance cannot be expected. It is best to avoid adopting it for new projects.&lt;/p&gt;

&lt;p&gt;As an alternative, setting the environment variable &lt;code&gt;MALLOC_ARENA_MAX=2&lt;/code&gt; remains effective. Here's why:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It reduces the number of arenas (memory management regions) that glibc allocates. glibc's malloc allocates numerous arenas as needed to prevent contention when multiple threads request memory simultaneously (normally, on 64-bit systems, the upper limit is 8 times the number of vCPU cores on the machine).&lt;/li&gt;
&lt;li&gt;As described above, glibc's memory allocator is reluctant to return memory to the OS. Therefore, the more arenas there are, the more "unreturned free memory" accumulates internally.&lt;/li&gt;
&lt;li&gt;By limiting the number of arenas, you can reduce the total amount of memory that glibc does not return to the OS (at the cost of slightly increased contention for memory allocation among threads).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;According to the following articles, setting &lt;code&gt;MALLOC_ARENA_MAX=2&lt;/code&gt; significantly reduces memory usage in exchange for a slight degradation in response time of a few percent.&lt;br&gt;
&lt;a href="https://www.speedshop.co/2017/12/04/malloc-doubles-ruby-memory.html" rel="noopener noreferrer"&gt;https://www.speedshop.co/2017/12/04/malloc-doubles-ruby-memory.html&lt;/a&gt;&lt;br&gt;
&lt;a href="https://techracho.bpsinc.jp/hachi8833/2022_06_23/50109" rel="noopener noreferrer"&gt;https://techracho.bpsinc.jp/hachi8833/2022_06_23/50109&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Additionally, &lt;code&gt;MALLOC_ARENA_MAX=2&lt;/code&gt; is the default setting on Heroku, which suggests it is a relatively safe configuration.&lt;br&gt;
&lt;a href="https://devcenter.heroku.com/changelog-items/1683" rel="noopener noreferrer"&gt;https://devcenter.heroku.com/changelog-items/1683&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Furthermore, the following reasoning supports why a value of &lt;code&gt;2&lt;/code&gt; for &lt;code&gt;MALLOC_ARENA_MAX&lt;/code&gt; is sufficient:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Ruby has a GVL (Global VM Lock), which means only one thread can execute Ruby code at any given time. Therefore, even if the application spawns many threads, the number of threads actively running and allocating memory at any point is expected to be very small. Consequently, glibc does not need to maintain many arenas; a small number (around &lt;code&gt;2&lt;/code&gt;) sufficient to handle requests from active threads should be adequate.
For this reason, setting &lt;code&gt;MALLOC_ARENA_MAX&lt;/code&gt; to &lt;code&gt;2&lt;/code&gt; causes virtually no operational issues in practice, while effectively minimizing the total amount of free memory hoarded across multiple arenas.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you want to be thorough, you should measure memory usage and response time with each setting—unset, 2, 3, and 4—and determine the optimal value.&lt;/p&gt;




&lt;ol&gt;

&lt;li id="fn1"&gt;
&lt;p&gt;A proposal was made to "call &lt;code&gt;malloc_trim(0)&lt;/code&gt; when a full GC is performed in Ruby to return memory to the OS," but it was not implemented because returning fragmented memory to the OS provides little benefit since the OS cannot effectively utilize it. &lt;a href="https://bugs.ruby-lang.org/issues/15667#note-10" rel="noopener noreferrer"&gt;Feature #15667: Introduce malloc_trim(0) in full gc cycles - Ruby - Ruby Issue Tracking System&lt;/a&gt; ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn2"&gt;
&lt;p&gt;The background behind the introduction of &lt;code&gt;Process.warmup&lt;/code&gt; is explained in &lt;a href="https://bugs.ruby-lang.org/issues/18885" rel="noopener noreferrer"&gt;Feature #18885: End of boot advisory API for RubyVM - Ruby - Ruby Issue Tracking System&lt;/a&gt; ↩&lt;/p&gt;
&lt;/li&gt;

&lt;/ol&gt;

</description>
    </item>
    <item>
      <title>Building a Cost-Effective Windows Code Signing Pipeline with Sectigo, Google Cloud KMS, and GitHub Actions</title>
      <dc:creator>Katz Sakai</dc:creator>
      <pubDate>Thu, 26 Mar 2026 05:29:51 +0000</pubDate>
      <link>https://dev.to/katz/building-a-cost-effective-windows-code-signing-pipeline-sectigo-google-cloud-kms-on-github-2ghf</link>
      <guid>https://dev.to/katz/building-a-cost-effective-windows-code-signing-pipeline-sectigo-google-cloud-kms-on-github-2ghf</guid>
      <description>&lt;h2&gt;
  
  
  Overview
&lt;/h2&gt;

&lt;p&gt;Most code signing services charge per signature. That sounds fine until your CI pipeline signs staging and production builds on every push, and your monthly bill starts climbing. We replaced per-signature billing with a flat-cost setup using a &lt;a href="https://www.sectigostore.com/code-signing/sectigo-code-signing-certificate?aid=52915358" rel="noopener noreferrer"&gt;Sectigo certificate&lt;/a&gt; and Google Cloud KMS. Key storage costs about $2.50/month, signing is $0.15 per 10,000 operations, and the private key never leaves the HSM. This post walks through the full architecture and setup on GitHub Actions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Table Of Contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;System Architecture of the Code Signing Environment on GitHub Actions&lt;/li&gt;
&lt;li&gt;Why We Chose Sectigo's Code Signing Certificate&lt;/li&gt;
&lt;li&gt;
Setup Instructions

&lt;ul&gt;
&lt;li&gt;Generate a Signing Private Key on Google Cloud KMS HSM&lt;/li&gt;
&lt;li&gt;Create a CSR on Your Local Machine&lt;/li&gt;
&lt;li&gt;Purchase the Code Signing Certificate&lt;/li&gt;
&lt;li&gt;Integrate into GitHub Actions&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

Notes

&lt;ul&gt;
&lt;li&gt;Key Types and Bit Lengths Commonly Used in Code Signing&lt;/li&gt;
&lt;li&gt;Instant SmartScreen Bypass via EV Certificate Is No Longer Possible&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  System Architecture of the Code Signing Environment on GitHub Actions
&lt;/h2&gt;

&lt;p&gt;The signing environment built on the GitHub Actions Windows Runner is structured as shown below.&lt;br&gt;
The signing process with SignTool.exe is performed using the key stored on the HSM via the KMS CNG (Cryptography Next Generation) Provider supplied by Google. This allows the signing process to be executed securely without ever holding the private key on the GitHub Actions runner.&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.amazonaws.com%2Fuploads%2Farticles%2Fu04mwzlumbp0qb6ic67q.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.amazonaws.com%2Fuploads%2Farticles%2Fu04mwzlumbp0qb6ic67q.png" alt="System Architecture" width="691" height="602"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Why We Chose Sectigo's Code Signing Certificate
&lt;/h2&gt;

&lt;p&gt;The reason is that it allows us to build a code signing environment without incurring unnecessary costs.&lt;/p&gt;

&lt;p&gt;For example, the code signing certificates offered by competitor SSL.com are subject to usage-based billing per number of signatures. This usage-based billing can become surprisingly burdensome.&lt;br&gt;
The screenshot below shows SSL.com's code signing pricing. At first glance, 20 signatures per month for 20 USD/month might seem sufficient. However, in practice, production and staging environments are often separate, and if you sign staging binaries each time as well, 20 signatures are quickly exhausted. As a result, you end up needing a higher-tier plan with 100 or 300 signatures, paying a substantial monthly fee.&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.amazonaws.com%2Fuploads%2Farticles%2F1q0rwnfmn910w7ped1y7.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.amazonaws.com%2Fuploads%2Farticles%2F1q0rwnfmn910w7ped1y7.png" alt="SSL.com codesign pricing" width="783" height="360"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;While we used SSL.com as an example, most vendors that provide their own signing systems employ a similar usage-based billing model.&lt;/p&gt;

&lt;p&gt;In contrast, Sectigo code signing allows you to perform signing operations on your own Google Cloud KMS HSM. With this approach, key storage costs just 2.50 USD/month, and signing operations cost 0.15 USD per 10,000 signatures—an extremely cost-effective option.&lt;br&gt;
(All prices as of July 2025.)&lt;/p&gt;

&lt;p&gt;Because signing costs are essentially negligible, you can freely sign binaries for both production and staging environments. This guarantees the origin and integrity of binaries in both environments, enabling a more secure distribution pipeline. Furthermore, since signing costs are not a concern, you can flexibly handle re-signing and verification during incident response.&lt;/p&gt;
&lt;h2&gt;
  
  
  Setup Instructions
&lt;/h2&gt;

&lt;p&gt;Sectigo publishes the procedure as a PDF, and we followed it closely.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://certificategeneration.com/en/content/pdf/google-kms-code-signing.pdf" rel="noopener noreferrer"&gt;https://certificategeneration.com/en/content/pdf/google-kms-code-signing.pdf&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Generate a Signing Private Key on Google Cloud KMS HSM
&lt;/h3&gt;

&lt;p&gt;The private key used for code signing is generated on the Google Cloud KMS HSM (Hardware Security Module).&lt;br&gt;
This key does not need to be regenerated as long as its security is maintained, and the same key can be continued to be used when the certificate is renewed (you can skip this step at the next certificate renewal).&lt;/p&gt;
&lt;h4&gt;
  
  
  (1) Create a Key Ring
&lt;/h4&gt;

&lt;p&gt;Create a key ring to group your keys.&lt;br&gt;
We selected the multi-region &lt;code&gt;asia1&lt;/code&gt; for the region.&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.amazonaws.com%2Fuploads%2Farticles%2F2ruw1yh55969pfp5178x.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.amazonaws.com%2Fuploads%2Farticles%2F2ruw1yh55969pfp5178x.png" width="580" height="512"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h4&gt;
  
  
  (2) Create a Signing Key
&lt;/h4&gt;

&lt;p&gt;Next, create the key used for signing.&lt;br&gt;
On the screen where you specify the key name and protection level, you must select "HSM" as the protection level.&lt;br&gt;
This is because the code signing certificate issuance requirements mandate that "the private key must be generated within an HSM and must not be exportable from the HSM"&lt;sup id="fnref1"&gt;1&lt;/sup&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.amazonaws.com%2Fuploads%2Farticles%2Fug2ss0at2vhtn7vbwwxs.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.amazonaws.com%2Fuploads%2Farticles%2Fug2ss0at2vhtn7vbwwxs.png" width="597" height="563"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For the purpose and algorithm, select "Asymmetric sign" and "4096 bit RSA - PKCS#1 v1.5 padding - SHA256 digest" respectively.&lt;br&gt;
&lt;em&gt;We chose this key type because this combination is most commonly used in code signing for Windows binaries in practice (see the notes at the bottom of the page for details).&lt;/em&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.amazonaws.com%2Fuploads%2Farticles%2Fly6eyk1bxuqd7kveyx6i.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.amazonaws.com%2Fuploads%2Farticles%2Fly6eyk1bxuqd7kveyx6i.png" width="582" height="546"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once the settings are configured, execute the key generation.&lt;/p&gt;
&lt;h4&gt;
  
  
  (3) Obtain the Attestation for the Generated Key
&lt;/h4&gt;

&lt;p&gt;An attestation is data that certifies that "a key was securely generated and is securely stored on the HSM." Certificate vendors such as Sectigo need to verify the key's integrity using the attestation when issuing a code signing certificate, so you must download the attestation from Google Cloud KMS.&lt;/p&gt;

&lt;p&gt;To download the attestation, first select the generated key, then click "Verify attestation" from the three-dot menu.&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.amazonaws.com%2Fuploads%2Farticles%2F60u4s184iouz32oltzm1.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.amazonaws.com%2Fuploads%2Farticles%2F60u4s184iouz32oltzm1.png" width="800" height="549"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next, in the dialog that appears, click "Download attestation bundle" to download a zip file. Save this file and submit it when purchasing the certificate.&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.amazonaws.com%2Fuploads%2Farticles%2Frim9uhwurqa6i2zw4w9o.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.amazonaws.com%2Fuploads%2Farticles%2Frim9uhwurqa6i2zw4w9o.png" width="800" height="525"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Create a CSR on Your Local Machine
&lt;/h3&gt;

&lt;p&gt;To issue a certificate, you need to create a CSR (Certificate Signing Request) corresponding to the signing key.&lt;br&gt;
This procedure is easiest to perform in a Linux or WSL environment.&lt;/p&gt;

&lt;p&gt;The overall architecture for the CSR creation environment is as shown below.&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.amazonaws.com%2Fuploads%2Farticles%2Fbrvxmhlo8u51bhnndse3.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.amazonaws.com%2Fuploads%2Farticles%2Fbrvxmhlo8u51bhnndse3.png" alt="System setup for CSR" width="592" height="542"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A PKCS#11 plugin is integrated into OpenSSL's Engine API (plugin mechanism)&lt;sup id="fnref2"&gt;2&lt;/sup&gt;, and the PKCS#11 plugin accesses the Google Cloud HSM through Google's PKCS#11 Library to generate the CSR using the private key.&lt;/p&gt;
&lt;h4&gt;
  
  
  Install the Required Tools
&lt;/h4&gt;

&lt;p&gt;First, install the tools needed to access Google Cloud KMS from OpenSSL via the PKCS#11 interface.&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="nb"&gt;sudo &lt;/span&gt;apt-get &lt;span class="nb"&gt;install &lt;/span&gt;libengine-pkcs11-openssl opensc
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, download Google's PKCS#11 library from the GitHub releases page and extract it to a directory of your choice.&lt;br&gt;
&lt;a href="https://github.com/GoogleCloudPlatform/kms-integrations/releases?q=pkcs%2311&amp;amp;expanded=true" rel="noopener noreferrer"&gt;pkcs#11 · Releases · GoogleCloudPlatform/kms-integrations&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Set the path to &lt;code&gt;libkmsp11.so&lt;/code&gt; in the extracted directory as the environment variable &lt;code&gt;PKCS11_MODULE_PATH&lt;/code&gt;.&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="nv"&gt;PKCS11_MODULE_PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"/path/to/libkmsp11.so"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, create a YAML file at any location that specifies the path to the key ring containing the key used for CSR creation.&lt;br&gt;
The YAML content should look like this.&lt;br&gt;
Replace &lt;code&gt;YOUR_GCP_PROJECT_ID&lt;/code&gt; and &lt;code&gt;KEY_RING_NAME&lt;/code&gt; with your own Google Cloud project ID and key ring name.&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="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;tokens&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;key_ring&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;projects/YOUR_GCP_PROJECT_ID/locations/asia1/keyRings/KEY_RING_NAME"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Set the path of the created YAML file as the environment variable &lt;code&gt;KMS_PKCS11_CONFIG&lt;/code&gt;.&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="nv"&gt;KMS_PKCS11_CONFIG&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"/path/to/google_hsm_config.yaml"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, obtain the Application Default Credentials (ADC) using the Google Cloud CLI. The account used for login must have the necessary permissions to access the created key.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gcloud auth application-default login
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the ADC has been obtained and the plugin and YAML configuration described above are correctly set up, you should be able to retrieve the key information with the following command.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pkcs11-tool &lt;span class="nt"&gt;--module&lt;/span&gt; /path/to/libkmsp11.so &lt;span class="nt"&gt;--list-objects&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, generate the CSR using the openssl command. Below is an example command for CSR creation.&lt;br&gt;
Replace "EXAMPLE, Inc." with your company's name, and replace &lt;code&gt;test-authenticode-key&lt;/code&gt; with the name of the key you created.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;openssl req &lt;span class="nt"&gt;-new&lt;/span&gt; &lt;span class="nt"&gt;-subj&lt;/span&gt; &lt;span class="s1"&gt;'/CN=EXAMPLE, Inc./'&lt;/span&gt; &lt;span class="nt"&gt;-sha256&lt;/span&gt; &lt;span class="nt"&gt;-engine&lt;/span&gt; pkcs11 &lt;span class="nt"&gt;-keyform&lt;/span&gt; engine &lt;span class="nt"&gt;-key&lt;/span&gt; pkcs11:object&lt;span class="o"&gt;=&lt;/span&gt;test-authenticode-key
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Purchase the Code Signing Certificate
&lt;/h3&gt;

&lt;p&gt;Once the attestation and CSR are prepared, proceed to purchase the code signing certificate.&lt;br&gt;
When purchasing from &lt;a href="https://www.sectigostore.com/code-signing/sectigo-code-signing-certificate?aid=52915358" rel="noopener noreferrer"&gt;Sectigo Code Signing Certificates&lt;/a&gt;, select "Install on Existing HSM" as the Certificate Delivery Method.&lt;br&gt;
There is little reason to choose Extended Validation for the Validation Option (see the notes at the bottom of the page), so Standard Validation should suffice.&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.amazonaws.com%2Fuploads%2Farticles%2Fwxxup0ngt09eqnhxs1ir.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.amazonaws.com%2Fuploads%2Farticles%2Fwxxup0ngt09eqnhxs1ir.png" alt=" " width="800" height="732"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You will be prompted to submit the attestation and CSR during the purchase process.&lt;/p&gt;

&lt;p&gt;After the purchase, there is a review process for issuing the certificate.&lt;br&gt;
When issuing a certificate under a corporate entity, the following steps are required:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Applicant identity verification

&lt;ul&gt;
&lt;li&gt;Submit a photo of the passport itself&lt;/li&gt;
&lt;li&gt;Submit a selfie holding the passport next to your face&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Company existence verification

&lt;ul&gt;
&lt;li&gt;Provide your corporate number, and Sectigo will verify it by querying the government's corporate registry&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Company phone number verification

&lt;ul&gt;
&lt;li&gt;An automated voice call is placed to the company phone number set during purchase, providing a numeric code that you enter into a web form to complete the verification&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once this review process is complete, the certificate is issued.&lt;/p&gt;
&lt;h3&gt;
  
  
  Integrate into GitHub Actions
&lt;/h3&gt;

&lt;p&gt;Once the certificate has been issued, integrate the code signing process into the Windows Runner on GitHub Actions.&lt;br&gt;
The signing process uses signtool.exe on the Windows Runner. The signing key stored in Google Cloud KMS is accessed through the KMS CNG (Cryptography Next Generation) Provider supplied by Google. Additionally, a timestamp can be applied to the signature, allowing the signature to remain valid even after the certificate expires&lt;sup id="fnref3"&gt;3&lt;/sup&gt;.&lt;/p&gt;

&lt;p&gt;Below is an example GitHub Actions configuration for performing the signing process.&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="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;Install Google Cloud KMS CNG provider for code signing with Sectigo BYO HSM, and add SignTool.exe to PATH&lt;/span&gt;
      &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
        &lt;span class="s"&gt;curl -L https://github.com/GoogleCloudPlatform/kms-integrations/releases/download/cng-v1.2/kmscng-1.2-windows-amd64.zip -o kmscng.zip&lt;/span&gt;
        &lt;span class="s"&gt;unzip kmscng.zip&lt;/span&gt;
        &lt;span class="s"&gt;cd kmscng-1.2-windows-amd64&lt;/span&gt;
        &lt;span class="s"&gt;msiexec /i kmscng.msi /quiet /qn /norestart&lt;/span&gt;

        &lt;span class="s"&gt;# Create a working directory&lt;/span&gt;
        &lt;span class="s"&gt;New-Item -Path "C:\Windows\KMSCNG" -ItemType Directory -Force&lt;/span&gt;

        &lt;span class="s"&gt;# Add SignTool.exe to PATH&lt;/span&gt;
        &lt;span class="s"&gt;echo "C:\Program Files (x86)\Windows Kits\10\App Certification Kit" &amp;gt;&amp;gt; $env:GITHUB_PATH&lt;/span&gt;
      &lt;span class="na"&gt;shell&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;pwsh&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;Decode the BASE64-encoded Sectigo code signing certificate and save it to a file&lt;/span&gt;
      &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
        &lt;span class="s"&gt;[IO.File]::WriteAllBytes(&lt;/span&gt;
          &lt;span class="s"&gt;'C:\Windows\KMSCNG\cert.p12',&lt;/span&gt;
          &lt;span class="s"&gt;[Convert]::FromBase64String("${{ vars.sectigo_code_signing_cert_base64 }}")&lt;/span&gt;
        &lt;span class="s"&gt;)&lt;/span&gt;
      &lt;span class="na"&gt;shell&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;pwsh&lt;/span&gt;

    &lt;span class="c1"&gt;# The account used here must be granted the roles/cloudkms.signerVerifier role&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;Authenticate to Google Cloud for access to the Sectigo code signing private key on Google Cloud KMS&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;google-github-actions/auth@v2&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;project_id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;YOUR_PROJECT_ID&lt;/span&gt;
        &lt;span class="na"&gt;create_credentials_file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
        &lt;span class="na"&gt;workload_identity_provider&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;SAMPLE_PROVIDER&lt;/span&gt;
        &lt;span class="na"&gt;service_account&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;YOUR_SERVICE_ACCOUNT@PROJECT.iam.gserviceaccount.com&lt;/span&gt;
        &lt;span class="na"&gt;export_environment_variables&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
        &lt;span class="na"&gt;access_token_lifetime&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;600&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;Sign the code&lt;/span&gt;
      &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
        &lt;span class="s"&gt;signtool.exe sign /v /debug /as /fd sha256 /tr http://timestamp.sectigo.com /td SHA384 /f C:\Windows\KMSCNG\cert.p12 /csp "Google Cloud KMS Provider" /kc projects/YOUR_GCP_PROJECT_ID/locations/asia1/keyRings/KEY_RING_NAME/cryptoKeys/KEY_NAME/cryptoKeyVersions/1 C:\Temp\binary-to-be-signed.exe&lt;/span&gt;
      &lt;span class="na"&gt;shell&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cmd&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Notes
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Key Types and Bit Lengths Commonly Used in Code Signing
&lt;/h3&gt;

&lt;p&gt;We surveyed the key types and bit lengths used for code signing in real-world Windows applications. The results from examining 30 Windows application installers on hand as of May 2025 are as follows.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Key Type / Bit Length&lt;/th&gt;
&lt;th&gt;Count&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;RSA 4096-bit&lt;/td&gt;
&lt;td&gt;17&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RSA 3072-bit&lt;/td&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ECC 384-bit&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ECC 256-bit&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;These results show that choosing RSA 4096-bit is the safe bet.&lt;/p&gt;

&lt;h3&gt;
  
  
  Instant SmartScreen Bypass via EV Certificate Is No Longer Possible
&lt;/h3&gt;

&lt;p&gt;Previously, performing code signing with an EV (Extended Validation) code signing certificate could bypass the Microsoft SmartScreen warning screen.&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.amazonaws.com%2Fuploads%2Farticles%2Fadgo02hknmvrlhzelbdc.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.amazonaws.com%2Fuploads%2Farticles%2Fadgo02hknmvrlhzelbdc.png" alt=" " width="642" height="604"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;However, due to a change in Windows behavior in March 2024, even EV certificates can no longer instantly bypass SmartScreen. As of now, SmartScreen warnings may still appear until sufficient reputation has been built up for the certificate itself or the signed binary.&lt;/p&gt;

&lt;p&gt;This is also noted on &lt;a href="https://www.sectigostore.com/code-signing/sectigo-code-signing-certificate?aid=52915358" rel="noopener noreferrer"&gt;Sectigo's EV code signing certificate purchase page&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; In March 2024, Microsoft changed the way MS SmartScreen interacts with EV Code Signing certificates. EV Code Signing certificates remain the highest trust certificates available, but they no longer instantly remove SmartScreen warnings.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Therefore, there is no longer a reason to choose an EV certificate for the purpose of SmartScreen mitigation.&lt;/p&gt;




&lt;ol&gt;

&lt;li id="fn1"&gt;
&lt;p&gt;This became an industry requirement because there were numerous incidents where private keys were leaked and code signing certificates were abused, as exemplified by &lt;a href="https://www.malwarebytes.com/blog/news/2022/03/stolen-nvidia-certificates-used-to-sign-malware-heres-what-to-do" rel="noopener noreferrer"&gt;NVIDIA's code signing private key leak&lt;/a&gt;. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn2"&gt;
&lt;p&gt;For more on the OpenSSL Engine API, see the article &lt;a href="https://zenn.dev/inop/articles/c04c452ce645fc" rel="noopener noreferrer"&gt;What is the OpenSSL Engine API that enables integration with cloud HSMs and YubiKeys&lt;/a&gt;. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn3"&gt;
&lt;p&gt;When a trusted third-party timestamp is applied, the date and time of signing become provable, allowing a third party to verify that the certificate was indeed valid at the time of signing. Without a timestamp, the signing date and time are self-reported by the signer, making it impossible for a third party to verify whether the certificate was valid at the time of signing (since it would be possible to set the machine's clock to a past date and sign). Therefore, once the certificate expires, the code signature is also considered invalid. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;/ol&gt;

</description>
      <category>github</category>
      <category>security</category>
      <category>windows</category>
      <category>infosec</category>
    </item>
    <item>
      <title>The Missing Guide to Windows Code Signing in CI/CD (GitHub Actions Edition)</title>
      <dc:creator>Katz Sakai</dc:creator>
      <pubDate>Thu, 26 Mar 2026 04:36:15 +0000</pubDate>
      <link>https://dev.to/katz/how-to-set-up-code-signing-for-windows-apps-in-github-actions-cicd-pipelines-21ee</link>
      <guid>https://dev.to/katz/how-to-set-up-code-signing-for-windows-apps-in-github-actions-cicd-pipelines-21ee</guid>
      <description>&lt;h2&gt;
  
  
  Overview
&lt;/h2&gt;

&lt;p&gt;Code signing Windows binaries is straightforward in theory. In practice, doing it inside GitHub Actions introduces a real constraint: your private key must live on an HSM, and that HSM must be reachable from a CI runner. You can either use an HSM hosted by your certificate authority or bring your own cloud HSM. Each option has different trade-offs in cost, flexibility, and setup complexity.&lt;/p&gt;

&lt;p&gt;This post maps out the full architecture so you can choose the right approach before committing.&lt;/p&gt;

&lt;p&gt;As a more concrete follow-up to what is discussed in this article, I also published a separate article on how to set up a code signing environment on GitHub Actions using Google Cloud KMS and Sectigo:&lt;/p&gt;


&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/katz/building-a-cost-effective-windows-code-signing-pipeline-sectigo-google-cloud-kms-on-github-2ghf" class="crayons-story__hidden-navigation-link"&gt;Building a Cost-Effective Windows Code Signing Pipeline with Sectigo, Google Cloud KMS, and GitHub Actions&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/katz" class="crayons-avatar  crayons-avatar--l  "&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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F192880%2F8756053a-f9dd-4672-9c58-ce517bbd0b3c.png" alt="katz profile" class="crayons-avatar__image" width="256" height="256"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/katz" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Katz Sakai
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Katz Sakai
                
              
              &lt;div id="story-author-preview-content-3406640" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/katz" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F192880%2F8756053a-f9dd-4672-9c58-ce517bbd0b3c.png" class="crayons-avatar__image" alt="" width="256" height="256"&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Katz Sakai&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/katz/building-a-cost-effective-windows-code-signing-pipeline-sectigo-google-cloud-kms-on-github-2ghf" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Mar 26&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/katz/building-a-cost-effective-windows-code-signing-pipeline-sectigo-google-cloud-kms-on-github-2ghf" id="article-link-3406640"&gt;
          Building a Cost-Effective Windows Code Signing Pipeline with Sectigo, Google Cloud KMS, and GitHub Actions
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/github"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;github&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/security"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;security&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/windows"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;windows&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/infosec"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;infosec&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
            &lt;a href="https://dev.to/katz/building-a-cost-effective-windows-code-signing-pipeline-sectigo-google-cloud-kms-on-github-2ghf#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              Comments


              &lt;span class="hidden s:inline"&gt;Add Comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            9 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


&lt;h2&gt;
  
  
  The Big Picture
&lt;/h2&gt;

&lt;p&gt;The overall architecture of the system built on GitHub Actions looks roughly like the diagram below.&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.amazonaws.com%2Fuploads%2Farticles%2Fjlnf21kzhk1tlvoh6qdp.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.amazonaws.com%2Fuploads%2Farticles%2Fjlnf21kzhk1tlvoh6qdp.png" alt="System Diagram" width="731" height="602"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When integrating a code signing process into a CI/CD pipeline on GitHub Actions, the code signing private key must be stored in a cloud-based HSM (Hardware Security Module) that is accessible from the Windows machine running on GitHub Actions.&lt;sup id="fnref1"&gt;1&lt;/sup&gt;&lt;/p&gt;

&lt;p&gt;There are two types of cloud-based HSMs: those provided by a Certificate Authority (CA), and those running in your own cloud environment. With the former, you may be charged based on the annual number of code signing operations, and those costs can be quite high. So, if managing your own infrastructure is not a burden, using your own cloud HSM is the recommended approach.&lt;/p&gt;

&lt;p&gt;It is common practice to attach a timestamp when code signing. Without a timestamp, the date and time of signing is self-reported by the signer, meaning third parties cannot verify whether the certificate was valid at the time of signing (since one could set the machine's clock to a past date and sign). As a result, once the certificate expires, the code signature is also considered invalid.&lt;/p&gt;

&lt;p&gt;In contrast, if a trusted third-party timestamp is attached, the signing date and time become verifiable, allowing third parties to confirm that the certificate had not been revoked at the time of signing. This means the code signature remains valid even after the certificate itself expires.&lt;/p&gt;

&lt;p&gt;The code signing certificate issued by the CA does not contain sensitive data such as private keys. Therefore, it can be placed on the GitHub Actions Runner as a file.&lt;/p&gt;

&lt;h2&gt;
  
  
  General Steps for Obtaining a Code Signing Certificate
&lt;/h2&gt;

&lt;p&gt;First and foremost, you need to have a code signing certificate issued.&lt;br&gt;
Here are the key points for this process:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The code signing private key must be generated on a FIPS 140-2 Level 2 compliant HSM and stored within the HSM (the private key must not be exportable from the HSM).

&lt;ul&gt;
&lt;li&gt;This is an industry requirement for how private keys must be handled. If this requirement is not met, the CA will not issue a code signing certificate.&lt;sup id="fnref1"&gt;1&lt;/sup&gt;
&lt;/li&gt;
&lt;li&gt;While it is technically possible under the requirements to store the private key on a FIPS 140-2 Level 2 compliant USB security token, GitHub Actions Runners cannot access USB devices, so this option is not viable for this architecture.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;The CA issues the code signing certificate against the public key corresponding to the private key.

&lt;ul&gt;
&lt;li&gt;In other words, the CA is certifying that "this public key indeed belongs to organization XXX."&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Once a private key is generated, it does not need to be regenerated as long as its security is not compromised. This means you can continue using the same key when renewing the certificate without creating a new one.&lt;/li&gt;
&lt;li&gt;Certificates have an expiration date, so they must be reissued each time the expiration arrives.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you generate the private key on your own cloud HSM, the general steps are as follows:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Generate the code signing private key on a FIPS 140-2 Level 2 compliant HSM and store it within the HSM.

&lt;ul&gt;
&lt;li&gt;As long as the key's security has not been compromised, there is no need to regenerate it. This means that when reissuing the code signing certificate in subsequent years, you can start from step 2.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Obtain the Attestation for the private key.

&lt;ul&gt;
&lt;li&gt;A private key Attestation is data that allows a third party (in this case, the CA) to verify that the private key was generated in a trusted environment and has not been tampered with.&lt;/li&gt;
&lt;li&gt;The Attestation is downloaded from the HSM.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Create a CSR (Certificate Signing Request) using the private key.

&lt;ul&gt;
&lt;li&gt;The CSR contains the public key and a signature created with the private key, so the certificate issued by the CA is linked to the key pair through this CSR.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Submit both the CSR and the Attestation to the CA to have the code signing certificate issued.

&lt;ul&gt;
&lt;li&gt;The CA verifies the integrity of the private key based on the Attestation and then issues the code signing certificate based on the CSR.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Receive the certificate file from the CA and store it somewhere safe.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Step 3, creating the CSR, is typically done on a local Linux machine using an OpenSSL command like the following.&lt;br&gt;
For information on how to use OpenSSL with cloud HSMs, please refer to &lt;a href="https://zenn.dev/articles/c04c452ce645fc/edit" rel="noopener noreferrer"&gt;What is the OpenSSL Engine API that enables integration between OpenSSL and cloud HSMs or YubiKey&lt;/a&gt;.&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="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;PKCS11_MODULE_PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/tmp/libkmsp11-1.6-linux-amd64-fips/libkmsp11.so
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;KMS_PKCS11_CONFIG&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/tmp/pkcs11-config.yaml

openssl req &lt;span class="nt"&gt;-new&lt;/span&gt; &lt;span class="nt"&gt;-subj&lt;/span&gt; &lt;span class="s1"&gt;'/CN=example.com/'&lt;/span&gt; &lt;span class="nt"&gt;-sha256&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-key&lt;/span&gt; pub.pem &lt;span class="nt"&gt;-engine&lt;/span&gt; pkcs11 &lt;span class="nt"&gt;-keyform&lt;/span&gt; engine &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-key&lt;/span&gt; pkcs11:object&lt;span class="o"&gt;=&lt;/span&gt;sign_key_name &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; cert-request.csr
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If generating the private key and creating the CSR yourself seems too cumbersome, using an HSM provided by the CA may allow some of these steps to be semi-automated.&lt;/p&gt;

&lt;h2&gt;
  
  
  Code Signing Procedure
&lt;/h2&gt;

&lt;p&gt;Once the certificate has been issued, you need to set up the code signing environment on the Windows machine running on the GitHub Actions Runner.&lt;br&gt;
Here are the key points for setting up the environment:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Code signing is performed using SignTool.exe, which is included in the Windows SDK (it is pre-installed on GitHub-hosted Windows machines).&lt;sup id="fnref2"&gt;2&lt;/sup&gt;
&lt;/li&gt;
&lt;li&gt;You need to install a library on the GitHub Actions Windows Runner that allows SignTool.exe to delegate its signing operations to the cloud-based HSM.

&lt;ul&gt;
&lt;li&gt;To connect with Google Cloud's HSM, install the Google Cloud CNG Provider.&lt;/li&gt;
&lt;li&gt;To connect with a CA-provided HSM, install the library provided by the respective CA:

&lt;ul&gt;
&lt;li&gt;DigiCert: &lt;a href="https://docs.digicert.com/en/software-trust-manager/client-tools/cryptographic-libraries-and-frameworks/ksp-library.html" rel="noopener noreferrer"&gt;https://docs.digicert.com/en/software-trust-manager/client-tools/cryptographic-libraries-and-frameworks/ksp-library.html&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;SSL.com: &lt;a href="https://www.ssl.com/guide/code-signing-automation/" rel="noopener noreferrer"&gt;https://www.ssl.com/guide/code-signing-automation/&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Simply installing the library is not enough — you also need to configure which credentials to use for cloud access and which key to use, following the setup instructions for each library.

&lt;ul&gt;
&lt;li&gt;For example, with the Google Cloud CNG Provider, ADC (Application Default Credentials) is used for authenticating with Google Cloud, and the key to use is defined in a YAML configuration file.&lt;/li&gt;
&lt;li&gt;When connecting to proprietary HSM environments such as DigiCert or SSL.com, refer to the documentation published by each company.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once the environment is set up, all that remains is to call SignTool.exe to perform the code signing.&lt;br&gt;
The following is an example command for signing with a Sectigo certificate:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;signtool.exe sign /v /fd sha256 /t http://timestamp.sectigo.com /f path/to/mysigncscertificate.crt /csp "Google Cloud KMS Provider" /kc projects/PROJECT_ID/locations/LOCATION/keyRings/KEY_RING/cryptoKeys/KEY_NAME/cryptoKeyVersions/1 path/to/file-tobe-signed.exe
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;ol&gt;

&lt;li id="fn1"&gt;
&lt;p&gt;Prior to 2023, it was possible to store private keys in PEM files. However, due to ongoing private key leakage incidents — exemplified by the &lt;a href="https://www.malwarebytes.com/blog/news/2022/03/stolen-nvidia-certificates-used-to-sign-malware-heres-what-to-do" rel="noopener noreferrer"&gt;NVIDIA code signing private key leak&lt;/a&gt; — the industry now requires that private keys be stored on an HSM when issuing code signing certificates. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn2"&gt;
&lt;p&gt;You can view the list of software pre-installed on Runners at &lt;a href="https://github.com/actions/runner-images" rel="noopener noreferrer"&gt;https://github.com/actions/runner-images&lt;/a&gt;. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;/ol&gt;

</description>
      <category>github</category>
      <category>security</category>
      <category>windows</category>
      <category>infosec</category>
    </item>
    <item>
      <title>Why Rails App Memory Bloat Happens: Causes and Solutions (2025 Edition)</title>
      <dc:creator>Katz Sakai</dc:creator>
      <pubDate>Thu, 26 Mar 2026 04:15:01 +0000</pubDate>
      <link>https://dev.to/katz/why-rails-app-memory-bloat-happens-causes-and-solutions-2025-edition-3g61</link>
      <guid>https://dev.to/katz/why-rails-app-memory-bloat-happens-causes-and-solutions-2025-edition-3g61</guid>
      <description>&lt;p&gt;When running a Rails application in a production environment for a while, you may encounter a phenomenon where memory usage increases unexpectedly. In July 2025, I investigated the cause of this behavior and considered countermeasures, and this article summarizes my findings.&lt;/p&gt;

&lt;h2&gt;
  
  
  TL;DR: Key Takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;The reason a Rails application "appears to be constantly consuming memory" is due to the design of glibc, which Ruby uses, which holds onto free memory internally instead of returning it to the OS for future reuse. This is not a typical memory leak.&lt;/li&gt;
&lt;li&gt;Since Ruby 3.3.0, it's possible to optimize the heap by calling &lt;code&gt;Process.warmup&lt;/code&gt; when a Rails application has finished booting. However, this mechanism is intended to be executed when the application has finished booting, making it difficult to use for reducing memory usage in Rails applications that are running for extended periods.&lt;/li&gt;
&lt;li&gt;Setting the environment variable &lt;code&gt;MALLOC_ARENA_MAX=2&lt;/code&gt; remains an effective way to reduce memory usage without rewriting any product code. This setting prevents glibc from creating numerous arenas (memory pools) one after another, and instead reuses memory within existing memory pools, thus preventing glibc from accumulating too much free memory.&lt;/li&gt;
&lt;li&gt;
&lt;del&gt;Switching to jemalloc, which used to be a common recommendation, should now be avoided because jemalloc is no longer being maintained.&lt;/del&gt; Update Apr 3rd, 2026: Meta has recently announced a renewed commitment to jemalloc&lt;sup id="fnref1"&gt;1&lt;/sup&gt;. If that leads to active releases again, jemalloc may become an option again.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why Does Memory Usage in Rails Apps Appear to Keep Growing?
&lt;/h2&gt;

&lt;p&gt;Hongli Lai's article "What causes Ruby memory bloat?" covers this in detail.&lt;br&gt;
&lt;/p&gt;
&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
        &lt;div class="c-embed__cover"&gt;
          &lt;a href="https://www.joyfulbikeshedding.com/blog/2019-03-14-what-causes-ruby-memory-bloat.html" class="c-link align-middle" rel="noopener noreferrer"&gt;
            &lt;img alt="" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.joyfulbikeshedding.com%2Fimages%2F2019%2Fruby_memory_bloat_banner-f0bdf762.png" height="246" class="m-0" width="800"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="c-embed__body"&gt;
        &lt;h2 class="fs-xl lh-tight"&gt;
          &lt;a href="https://www.joyfulbikeshedding.com/blog/2019-03-14-what-causes-ruby-memory-bloat.html" rel="noopener noreferrer" class="c-link"&gt;
            What causes Ruby memory bloat? – Joyful Bikeshedding
          &lt;/a&gt;
        &lt;/h2&gt;
          &lt;p class="truncate-at-3"&gt;
            Ruby apps can use a lot of memory. But why? Various people in the community attribute it to memory fragmentation, and provide two “hacky” solutions. Dissatisfied by the current explanations and provided solutions, I set out on a journey to discover the deeper truth and to find better solutions.

          &lt;/p&gt;
        &lt;div class="color-secondary fs-s flex items-center"&gt;
            &lt;img alt="favicon" class="c-embed__favicon m-0 mr-2 radius-0" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.joyfulbikeshedding.com%2Fimages%2Ffavicon-1b152eac.png" width="24" height="24"&gt;
          joyfulbikeshedding.com
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;According to that article, the reasons memory bloat "appears to occur" are as follows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The previously held belief that "heap page fragmentation on the Ruby side" was the primary cause of increased memory usage was not actually the main factor.&lt;/li&gt;
&lt;li&gt;The true cause was that "glibc's memory allocator, malloc, retains memory that Ruby has freed instead of returning it to the OS, holding onto it for future use." In particular, free pages that are not at the end of the heap are not returned to the OS, so unused memory continues to accumulate internally. From the OS's perspective, this makes it look like "Ruby keeps consuming memory."&lt;/li&gt;
&lt;li&gt;Calling glibc's &lt;code&gt;malloc_trim(0)&lt;/code&gt; ensures that memory freed by Ruby is returned to the OS, effectively reducing the process's memory usage (RSS) as seen by the OS.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Though there is one important caveat:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Memory that Ruby has allocated and freed during processing is usually fragmented. Calling &lt;code&gt;malloc_trim(0)&lt;/code&gt; does not resolve the fragmentation; it merely returns the fragmented regions to the OS as-is.&lt;/li&gt;
&lt;li&gt;Even if the memory is fragmented, it is still returned to the OS, so Ruby's memory usage (RSS) goes down. However, because other programs cannot allocate contiguous regions from fragmented free memory, an OOM (Out of Memory) error can occur even when there appears to be free memory available.&lt;/li&gt;
&lt;li&gt;Since returning fragmented memory to the OS does not make it easy to reuse effectively, malloc is designed to retain allocated but unused memory internally and reuse it, enabling stable allocation.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is one of the reasons "Ruby has freed the memory, but malloc does not readily return it to the OS."&lt;sup id="fnref2"&gt;2&lt;/sup&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What Memory-Related Improvement Was Added in Ruby 3.3.0?
&lt;/h2&gt;

&lt;p&gt;Ruby 3.3.0 introduced the &lt;code&gt;Process.warmup&lt;/code&gt; method. This method is intended to signal to the Ruby virtual machine from an application server that "the application's startup sequence has completed, making this an optimal time to perform GC and memory optimization."&lt;sup id="fnref3"&gt;3&lt;/sup&gt;&lt;br&gt;
When &lt;code&gt;Process.warmup&lt;/code&gt; is called, the Ruby virtual machine performs the following optimizations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Forces a major GC&lt;/li&gt;
&lt;li&gt;Compacts the heap&lt;/li&gt;
&lt;li&gt;Promotes all surviving objects to the old generation&lt;/li&gt;
&lt;li&gt;Pre-computes string coderanges (to speed up future string operations)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This cleans up objects and caches that were generated during application startup but are no longer needed, improving memory sharing efficiency in Copy-on-Write (CoW) environments.&lt;/p&gt;

&lt;p&gt;Also, because unnecessary objects have already been collected and the heap has been compacted, malloc-side fragmentation is likely lower at this point. This makes it an ideal time to call &lt;code&gt;malloc_trim(0)&lt;/code&gt;, and a patch that calls &lt;code&gt;malloc_trim(0)&lt;/code&gt; internally within &lt;code&gt;Process.warmup&lt;/code&gt; has been merged.&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_github-liquid-tag"&gt;
  &lt;h1&gt;
    &lt;a href="https://github.com/ruby/ruby/pull/8451" rel="noopener noreferrer"&gt;
      &lt;img class="github-logo" alt="GitHub logo" src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg"&gt;
      &lt;span class="issue-title"&gt;
        Process.warmup: invoke `malloc_trim` if available
      &lt;/span&gt;
      &lt;span class="issue-number"&gt;#8451&lt;/span&gt;
    &lt;/a&gt;
  &lt;/h1&gt;
  &lt;div class="github-thread"&gt;
    &lt;div class="timeline-comment-header"&gt;
      &lt;a href="https://github.com/casperisfine" rel="noopener noreferrer"&gt;
        &lt;img class="github-liquid-tag-img" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Favatars.githubusercontent.com%2Fu%2F19192189%3Fv%3D4" alt="casperisfine avatar"&gt;
      &lt;/a&gt;
      &lt;div class="timeline-comment-header-text"&gt;
        &lt;strong&gt;
          &lt;a href="https://github.com/casperisfine" rel="noopener noreferrer"&gt;casperisfine&lt;/a&gt;
        &lt;/strong&gt; posted on &lt;a href="https://github.com/ruby/ruby/pull/8451" rel="noopener noreferrer"&gt;&lt;time&gt;Sep 15, 2023&lt;/time&gt;&lt;/a&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class="ltag-github-body"&gt;
      &lt;p&gt;Similar to releasing free GC pages, releasing free malloc pages reduce the amount of page faults post fork.&lt;/p&gt;
&lt;p&gt;NB: Some popular allocators such as &lt;code&gt;jemalloc&lt;/code&gt; don't implement it, so it's a noop for them.&lt;/p&gt;

    &lt;/div&gt;
    &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/ruby/ruby/pull/8451" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;An important point is that &lt;code&gt;Process.warmup&lt;/code&gt; is not automatically called behind the scenes like GC. It is the kind of method that should be explicitly called at an appropriate time on the application server side when a major GC would be acceptable (e.g., before forking, before worker startup). Therefore, there may not always be an appropriate time to call it in long-running Rails applications.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reducing Memory Bloat in Long-Running Rails Apps
&lt;/h2&gt;

&lt;p&gt;So how can you prevent memory bloat without using &lt;code&gt;Process.warmup&lt;/code&gt; or &lt;code&gt;malloc_trim(0)&lt;/code&gt;?&lt;/p&gt;

&lt;p&gt;&lt;del&gt;Online resources have recommended using jemalloc, a smarter memory allocator. However, &lt;a href="https://github.com/jemalloc/jemalloc" rel="noopener noreferrer"&gt;jemalloc's repository was archived in June 2025&lt;/a&gt;, and it does not appear to be actively maintained. It is best to avoid adopting it for new projects.&lt;/del&gt; Update Apr 3rd, 2026: Meta has recently announced a renewed commitment to jemalloc. If that leads to active releases again, jemalloc may become an option again.&lt;/p&gt;

&lt;p&gt;As an alternative, setting the environment variable &lt;code&gt;MALLOC_ARENA_MAX=2&lt;/code&gt; remains effective. Because:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It reduces the number of arenas (memory management regions) that glibc allocates. glibc's malloc allocates numerous arenas as needed to prevent contention when multiple threads request memory simultaneously (normally, on 64-bit systems, the upper limit is 8 times the number of vCPU cores on the machine).&lt;/li&gt;
&lt;li&gt;As described above, glibc's memory allocator tends to hold on to memory instead of returning it to the OS. Therefore, the more arenas there are, the more "unreturned free memory" accumulates internally.&lt;/li&gt;
&lt;li&gt;Limiting the number of arenas can reduce the amount of memory glibc keeps, though it may slightly increase contention between threads during memory allocation.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The articles below suggest that &lt;code&gt;MALLOC_ARENA_MAX=2&lt;/code&gt; can cut memory usage noticeably, while increasing response time by only a few percent.&lt;br&gt;
&lt;a href="https://www.speedshop.co/2017/12/04/malloc-doubles-ruby-memory.html" rel="noopener noreferrer"&gt;https://www.speedshop.co/2017/12/04/malloc-doubles-ruby-memory.html&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Additionally, &lt;code&gt;MALLOC_ARENA_MAX=2&lt;/code&gt; is the default setting on Heroku, which suggests it is a relatively safe configuration.&lt;br&gt;
&lt;a href="https://devcenter.heroku.com/changelog-items/1683" rel="noopener noreferrer"&gt;https://devcenter.heroku.com/changelog-items/1683&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So why is &lt;code&gt;MALLOC_ARENA_MAX=2&lt;/code&gt; enough?:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Ruby has a GVL (Global VM Lock), which means only one thread can execute Ruby code at any given time. So even if the application has many threads, only a small number of them are likely to be running and allocating memory at the same time. Consequently, glibc does not need to maintain many arenas; a small number (around &lt;code&gt;2&lt;/code&gt;) sufficient to handle requests from active threads should be adequate.
For this reason, setting &lt;code&gt;MALLOC_ARENA_MAX=2&lt;/code&gt; usually does not cause problems, while helping reduce the amount of freed memory glibc keeps across multiple arenas.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you want to test it more carefully, compare memory usage and response time with the value unset, then with 2, 3, and 4, and see which works best for your app.&lt;/p&gt;

&lt;p&gt;We compared the memory usage per Pod before and after setting MALLOC_ARENA_MAX=2. The solid line represents the usage after the setting was applied, and the dashed line represents the usage before. You can see the clear difference.&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.amazonaws.com%2Fuploads%2Farticles%2Fvm479lu8mxlbjfqh4zt6.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.amazonaws.com%2Fuploads%2Farticles%2Fvm479lu8mxlbjfqh4zt6.png" width="800" height="457"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;ol&gt;

&lt;li id="fn1"&gt;
&lt;p&gt;&lt;a href="https://engineering.fb.com/2026/03/02/data-infrastructure/investing-in-infrastructure-metas-renewed-commitment-to-jemalloc/" rel="noopener noreferrer"&gt;Investing in Infrastructure: Meta’s Renewed Commitment to jemalloc&lt;/a&gt; ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn2"&gt;
&lt;p&gt;A proposal was made to "call &lt;code&gt;malloc_trim(0)&lt;/code&gt; when a full GC is performed in Ruby to return memory to the OS," but it was not implemented because returning fragmented memory to the OS provides little benefit since the OS cannot effectively utilize it. &lt;a href="https://bugs.ruby-lang.org/issues/15667#note-10" rel="noopener noreferrer"&gt;Feature #15667: Introduce malloc_trim(0) in full gc cycles - Ruby - Ruby Issue Tracking System&lt;/a&gt; ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn3"&gt;
&lt;p&gt;The background behind the introduction of &lt;code&gt;Process.warmup&lt;/code&gt; is explained in &lt;a href="https://bugs.ruby-lang.org/issues/18885" rel="noopener noreferrer"&gt;Feature #18885: End of boot advisory API for RubyVM - Ruby - Ruby Issue Tracking System&lt;/a&gt; ↩&lt;/p&gt;
&lt;/li&gt;

&lt;/ol&gt;

</description>
      <category>ruby</category>
      <category>rails</category>
      <category>performance</category>
      <category>linux</category>
    </item>
  </channel>
</rss>
