{"id":5362,"date":"2026-09-23T17:38:34","date_gmt":"2026-09-23T09:38:34","guid":{"rendered":"https:\/\/arc.dev\/employer-blog\/?p=5362"},"modified":"2026-09-23T17:39:45","modified_gmt":"2026-09-23T09:39:45","slug":"ruby-on-rails-scalability","status":"publish","type":"post","link":"https:\/\/arc.dev\/employer-blog\/ruby-on-rails-scalability\/","title":{"rendered":"Ruby on Rails scalability: how Rails handles growth from MVP to millions of users"},"content":{"rendered":"\n<p>Written for Ruby on Rails 8.1.3.1 (July 29, 2026).<\/p>\n\n\n\n<p>&#8220;Rails doesn&#8217;t scale.&#8221; &#8220;Rails is fine for an MVP, but you&#8217;ll rewrite it once you grow.&#8221; &#8220;Twitter left Rails because it couldn&#8217;t handle scale.&#8221; If you&#8217;re deciding whether to build on Rails or keep investing in the Rails app you already have, you&#8217;ve heard all three.<\/p>\n\n\n\n<p>Those claims deserve a real answer, not a defensive one. <strong>Rails scales through a well-known sequence of fixes applied in order: eager loading, background job offloading, caching, horizontal web scaling, read replicas, and eventually sharding, <\/strong>and the companies running Rails at the largest scale today use exactly those mechanisms rather than a secret framework.&nbsp;<\/p>\n\n\n\n<p>During BFCM 2025, Shopify&#8217;s app servers peaked at more than 117 million requests per minute, with 489 million per minute at the edge and 14.8 trillion database queries across the weekend (<a href=\"https:\/\/www.shopify.com\/investors\/press-releases\/shopify-merchants-achieve-record-breaking-146-billion-black\">Shopify<\/a>). A year earlier, the peaks were 80 million on app servers and 284 million at the edge (<a href=\"https:\/\/shopify.engineering\/bfcm-readiness-2025\">Shopify Engineering<\/a>). That&#8217;s about 46% more app-server traffic and 72% more edge traffic year over year, and Shopify got there by sharding Rails&#8217; database layer rather than leaving Rails.<\/p>\n\n\n\n<p>What follows walks through the growth curve: which bottleneck hits first, which fix belongs at which traffic stage, where Ruby&#8217;s per-process throughput becomes a real cost, and when a rewrite is the wrong response to a caching problem.<\/p>\n\n\n\n<p><strong>In this guide:<\/strong><\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Does Rails scale for high-traffic products?<\/li>\n\n\n\n<li>When do Rails applications need a new scaling strategy?<\/li>\n\n\n\n<li>How to scale the request and job layers<\/li>\n\n\n\n<li>How to keep the database from becoming the bottleneck<\/li>\n\n\n\n<li>What senior Rails expertise changes during growth<\/li>\n\n\n\n<li>Frequently asked questions<\/li>\n\n\n\n<li>Building a Rails system that grows deliberately<\/li>\n<\/ol>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Does Rails scale for high-traffic products?<\/strong><\/h2>\n\n\n\n<p>Rails scales to millions of users, and the framework rarely breaks. The database, the job queue, and missing caching break first, in roughly that order.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>The short answer: what Rails can and cannot solve<\/strong><\/h3>\n\n\n\n<p>Rails solves the parts of scaling that are about structure: connection pooling through Active Record, background work through Active Job, fragment and low-level caching through the cache store, read\/write splitting through multiple database configurations, and horizontal sharding through Active Record&#8217;s connects_to API.<\/p>\n\n\n\n<p>Rails cannot solve raw per-process throughput. Ruby executes fewer requests per second per CPU core than Go or the JVM for the same CPU-bound work. That gap is real, and you pay for it in server count.<\/p>\n\n\n\n<p>The tradeoff is straightforward. You spend more on compute and less on engineering time. For a team of 10 to 100 people shipping product features, that trade favors Rails. For a service parsing binary protocols at single-digit millisecond latency budgets, it does not.&nbsp;<\/p>\n\n\n\n<p>As teams grow past a single squad, the tradeoff shifts again; <strong>our guide to <\/strong><a href=\"https:\/\/arc.dev\/employer-blog\/ruby-on-rails-a-product-team-guide-to-agile-delivery\/\"><strong>Rails and agile delivery at scale<\/strong><\/a><strong> covers the modular-monolith patterns<\/strong> (engines, service objects, enforced module boundaries) that keep multiple teams shipping to the same Rails app without stepping on each other.<\/p>\n\n\n\n<p>YJIT, Ruby&#8217;s just-in-time compiler, narrows the gap. Shopify&#8217;s Ruby &amp; Rails infrastructure team built it, and it shipped upstream in Ruby 3.1. There&#8217;s nothing to configure: <a href=\"https:\/\/guides.rubyonrails.org\/7_2_release_notes.html\">Rails 7.2 and later, including Rails 8, turn YJIT on by default<\/a> when your app runs on Ruby 3.3 or newer.<\/p>\n\n\n\n<p><a href=\"https:\/\/www.ruby-lang.org\/en\/news\/2025\/12\/25\/ruby-4-0-0-released\/\">Ruby 4.0<\/a>, released December 25, 2025, adds a second JIT called <a href=\"https:\/\/railsatscale.com\/2025-12-24-launch-zjit\/\">ZJIT<\/a>, built by the same Shopify compiler team behind YJIT. ZJIT is experimental. The Ruby core team says it&#8217;s faster than the interpreter but not yet as fast as YJIT, and advises holding off on production use.\u00a0<\/p>\n\n\n\n<p>Their goal is to make it faster than YJIT and production-ready in Ruby 4.1. Until then, keep YJIT on in production and wait for 4.1 before testing ZJIT on real traffic.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Claim vs. reality: why scaling problems are often architectural<\/strong><\/h3>\n\n\n\n<p>Most &#8220;Rails is slow&#8221; incidents trace back to code, not the framework.<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><tbody><tr><td><strong>Common claim<\/strong><\/td><td><strong>What&#8217;s usually happening<\/strong><\/td><td><strong>The named fix<\/strong><\/td><\/tr><tr><td>&#8220;Rails can&#8217;t handle our traffic&#8221;<\/td><td>N+1 queries multiplying under concurrency<\/td><td>includes \/ preload eager loading, caught by the Bullet gem<\/td><\/tr><tr><td>&#8220;The database keeps falling over&#8221;<\/td><td>Every request hits the primary; no cache layer<\/td><td>Solid Cache or Redis for hot paths, plus a read replica<\/td><\/tr><tr><td>&#8220;Response times spike at peak&#8221;<\/td><td>Slow third-party API calls inside the request cycle<\/td><td>Move to Active Job with Solid Queue or Sidekiq<\/td><\/tr><tr><td>&#8220;We&#8217;ve maxed out our server&#8221;<\/td><td>Single Puma instance, no horizontal layer<\/td><td>Multiple stateless app containers behind a load balancer<\/td><\/tr><tr><td>&#8220;We need microservices&#8221;<\/td><td>One fat model and no query indexes<\/td><td>Add indexes, extract service objects, keep the monolith<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p>Rails scales when the architecture underneath it scales. Every &#8220;Rails can&#8217;t handle it&#8221; claim in the table above traces back to a fixable pattern, not a hard ceiling in the framework itself.<\/p>\n\n\n\n<p>Scale is one part of the case for Rails. For adoption data, download numbers, and the companies still running it in production, see our look at <a href=\"https:\/\/arc.dev\/employer-blog\/is-ruby-on-rails-still-relevant\/\">whether Ruby on Rails is still relevant in 2026<\/a>.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>What &#8220;millions of users&#8221; actually means for throughput, data, and concurrency<\/strong><\/h3>\n\n\n\n<p>&#8220;Millions of users&#8221; is a vanity number. Three figures decide your architecture:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>Peak requests per second.<\/strong> A million monthly users with a weekly-check-in usage pattern might peak at 200 requests per second. A million users on a real-time chat product might peak at 20,000. The second app needs a fundamentally different topology.<\/li>\n\n\n\n<li><strong>Working data set size versus RAM.<\/strong> Postgres stays fast as long as hot rows fit in memory. Once your active table exceeds available RAM, query times jump, and you need partitioning, archival, or a bigger instance.<\/li>\n\n\n\n<li><strong>Concurrent open connections.<\/strong> A request\/response app releases its connection in 80 milliseconds. A WebSocket app holds thousands of connections open at once, which is where Action Cable&#8217;s adapter choice matters.<\/li>\n<\/ol>\n\n\n\n<p>Measure all three before you buy infrastructure.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>When do Rails applications need a new scaling strategy?<\/strong><\/h2>\n\n\n\n<p>You need a new strategy when the current bottleneck no longer responds to the current fix. Adding a bigger server to an N+1 problem buys you a week; adding eager loading fixes it permanently.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Scaling stages: from MVP traffic to sustained growth<\/strong><\/h3>\n\n\n\n<p>Bottlenecks arrive in a predictable order because fixing one unmasks the next.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" width=\"1024\" height=\"862\" src=\"https:\/\/arc.dev\/employer-blog\/wp-content\/uploads\/2026\/09\/image-2-1024x862.png\" alt=\"\" class=\"wp-image-5364\" srcset=\"https:\/\/arc.dev\/employer-blog\/wp-content\/uploads\/2026\/09\/image-2-1024x862.png 1024w, https:\/\/arc.dev\/employer-blog\/wp-content\/uploads\/2026\/09\/image-2-300x253.png 300w, https:\/\/arc.dev\/employer-blog\/wp-content\/uploads\/2026\/09\/image-2-768x647.png 768w, https:\/\/arc.dev\/employer-blog\/wp-content\/uploads\/2026\/09\/image-2-1536x1293.png 1536w, https:\/\/arc.dev\/employer-blog\/wp-content\/uploads\/2026\/09\/image-2.png 2048w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p><strong>First: N+1 queries surface under load.<\/strong> In development with 20 seed records, a loop firing 21 queries feels fine. At 500 concurrent users, those queries saturate the connection pool and requests start queuing on the database.<\/p>\n\n\n\n<p><strong>Second: the connection pool runs out.<\/strong> Puma with 4 workers and 5 threads each wants 20 connections. Add background job workers, and you&#8217;ll quickly exceed the default managed Postgres connection limit. You&#8217;ll see ActiveRecord::ConnectionTimeoutError in the logs before you see slow queries.<\/p>\n\n\n\n<p><strong>Third: the background job queue backs up.<\/strong> Emails, webhook deliveries, and PDF generation pile up. Jobs that ran in 2 seconds now sit in the queue for 40 minutes, and users notice because their receipt never arrives.<\/p>\n\n\n\n<p><strong>Fourth: the single server hits its memory or CPU ceiling.<\/strong> Ruby processes are memory-hungry. Four Puma workers on a 2 GB instance will start getting OOM-killed, and you cannot thread your way out of it.<\/p>\n\n\n\n<p><strong>Fifth: cache and session invalidation get messy.<\/strong> Once you&#8217;re running multiple app servers, in-process caching and cookie-based session assumptions break. Stale fragments and logged-out users become the new bug class.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>How monitoring reveals the real bottleneck<\/strong><\/h3>\n\n\n\n<p>You cannot fix what you haven&#8217;t measured, and Rails gives you the instrumentation for free through Active Support notifications.<\/p>\n\n\n\n<p>Wire up an APM that reads those events. AppSignal, Datadog, New Relic, and Skylight all break request time into database, view rendering, and external HTTP segments. That breakdown alone is usually enough to point to the bottleneck, often within the same day you set up the alert.<\/p>\n\n\n\n<p>For production visibility across services, <a href=\"https:\/\/opentelemetry.io\/docs\/languages\/ruby\/\">instrumenting requests with OpenTelemetry&#8217;s Ruby SDK<\/a> alongside Puma worker and thread tuning gives you the trace data needed to hold a latency SLO under load.<\/p>\n\n\n\n<p>Add rack-mini-profiler in staging. It shows per-request SQL inline, which makes N+1 patterns obvious without reading logs.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Latency, Error Rate, Queue Depth, and Database Saturation Signals<\/strong><\/h3>\n\n\n\n<p>Four numbers tell you which fix to reach for.<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>p95 request latency climbing while p50 stays flat.<\/strong> A subset of requests is doing heavy work. Look for uncached expensive queries or a slow external API call on one endpoint.<\/li>\n\n\n\n<li>ActiveRecord::ConnectionTimeoutError<strong> in your error tracker.<\/strong> Your pool is exhausted. Resize the pool or reduce concurrency per process before you scale out.<\/li>\n\n\n\n<li><strong>Job queue depth trending up instead of oscillating.<\/strong> Your workers cannot keep pace with the enqueue rate. Add dedicated worker processes per queue, and split latency-sensitive queues from bulk ones.<\/li>\n\n\n\n<li><strong>Database CPU above 70% sustained, or replica lag over a few seconds.<\/strong> You&#8217;re saturating the primary. Move read-heavy endpoints to a replica, or cache them.<\/li>\n<\/ol>\n\n\n\n<p>Set alerts on all four. Discovering queue depth at 400,000 jobs during a launch is avoidable.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>How to scale the request and job layers<\/strong><\/h2>\n\n\n\n<p>Scaling the request layer means making every web process stateless so you can run many of them, and scaling the job layer means moving anything slow out of the request cycle entirely.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Horizontal scaling, load balancing, and web-process concurrency<\/strong><\/h3>\n\n\n\n<p>Rails apps scale horizontally when web processes hold no state. Sessions go to a shared store (encrypted cookies, Redis, or the database), uploaded files go to object storage through Active Storage, and cache goes to Solid Cache or Redis. Then any request can hit any process.<\/p>\n\n\n\n<p>Inside each process, Puma handles concurrency with workers and threads. Workers are separate OS processes with their own memory. Threads share memory inside a worker. A common starting point is workers equal to CPU cores, with 3 to 5 threads per worker, then tuned against real latency data.<\/p>\n\n\n\n<p>Above that, three deployment patterns dominate real Rails production today:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Kamal 2<\/strong>, the default deploy tool in Rails 8, deploys Docker containers to servers you own and ships with kamal-proxy for zero-downtime rollouts and traffic routing across multiple app hosts.<\/li>\n\n\n\n<li><strong>Kubernetes with the Horizontal Pod Autoscaler<\/strong>, which scales Rails pods on CPU or on custom metrics like queue depth. This is the pattern large Rails shops use.<\/li>\n\n\n\n<li><strong>Managed platforms<\/strong> (Heroku, Render, Fly.io, AWS ECS\/Fargate), where you set a process count and the platform handles the load balancer.<\/li>\n<\/ul>\n\n\n\n<p>Every one of these needs a health check endpoint. Rails 8 ships \/up by default through Rails::HealthController, so your load balancer has something to poll on day one.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Moving slow work to background job queues<\/strong><\/h3>\n\n\n\n<p>Offloading work to background jobs is the highest-leverage scaling move available to a Rails app, because it removes wait time from the request without touching your database or server count.<\/p>\n\n\n\n<p>Rails 8 ships <strong>Solid Queue<\/strong> as the default Active Job backend. It stores jobs in your database using FOR UPDATE SKIP LOCKED, so you get durable queues with no Redis dependency. Basecamp runs it in production at HEY&#8217;s mail volume.<\/p>\n\n\n\n<p><strong>Sidekiq<\/strong> remains the choice at high throughput, because Redis-backed enqueue and dequeue is faster than a database round trip and Sidekiq&#8217;s thread-per-job model packs more concurrency into less memory. At high enough volume, Redis-backed queues still win on raw throughput, which is why Sidekiq remains common at companies running large-scale background processing.<\/p>\n\n\n\n<p>What moves out of the request cycle, roughly in order of payoff:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>Transactional email.<\/strong> deliver_later instead of deliver_now. Typically saves low hundreds of milliseconds per signup, with the exact amount depending on your mail provider&#8217;s API latency.<\/li>\n\n\n\n<li><strong>Third-party API calls.<\/strong> Payment webhooks, CRM syncs, Slack notifications, and LLM calls. Any of these can hang for 30 seconds and take your request with it. If you&#8217;re adding AI features, our guide to <a href=\"https:\/\/arc.dev\/employer-blog\/integrating-ai-with-ruby-on-rails\/\">integrating AI with Ruby on Rails<\/a> covers queuing model calls and streaming results back to the user.<\/li>\n\n\n\n<li><strong>File and image processing.<\/strong> Active Storage variants, CSV imports, PDF generation.<\/li>\n\n\n\n<li><strong>Report generation and aggregate counts.<\/strong> Compute nightly, serve from a cached table.<\/li>\n\n\n\n<li><strong>Search index updates.<\/strong> Enqueue on model save; never block the write.<\/li>\n<\/ol>\n\n\n\n<p>Run separate worker processes per queue class. Use one pool for latency-sensitive jobs like email and another for bulk imports, so a 100,000-row CSV never delays a password reset.<\/p>\n\n\n\n<p>For long-running jobs, Rails 8.1 adds<a href=\"https:\/\/rubyonrails.org\/2025\/10\/22\/rails-8-1\"> Active Job continuations<\/a>. You include ActiveJob::Continuable, split the job into steps, and advance a cursor as you go. When a deploy or restart interrupts the job, it resumes from the last completed step instead of starting over.\u00a0<\/p>\n\n\n\n<p>That matters with Kamal, which gives job containers 30 seconds to shut down by default. Stopping cleanly mid-job depends on your queue adapter: Rails&#8217; Sidekiq adapter supports it, and Solid Queue added support in <a href=\"https:\/\/github.com\/rails\/solid_queue\/releases\">version 1.2.0<\/a>.<\/p>\n\n\n\n<p>An AI coding assistant helps here in a specific way: point it at your controllers and ask it to flag every synchronous external HTTP call and mailer deliver_now. On a codebase with 200 controller actions, that audit takes minutes instead of a day, and you review the list yourself before changing anything.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Caching hot paths without creating cache-invalidation failures<\/strong><\/h3>\n\n\n\n<p>Caching is where Rails gives you the most speed for the least code, and where you create the most subtle bugs if you cache without an invalidation plan.<\/p>\n\n\n\n<p>Rails 8 defaults to <strong>Solid Cache<\/strong>, a database-backed store that uses disk instead of RAM. That trade lets you keep a much larger cache than a memory-bound store, at higher read latency than Redis. For very hot, small keys, Redis or Memcached still wins on latency.<\/p>\n\n\n\n<p>Use Rails&#8217; built-in invalidation rather than writing your own. Russian doll caching with cache @post generates a key from the record&#8217;s updated_at, so touching the record automatically expires the fragment. Add touch: true on child associations so updating a comment expires the parent post&#8217;s fragment.<\/p>\n\n\n\n<p>Three rules keep cache bugs rare:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Never cache authorization decisions.<\/strong> Cache the rendered content; check permissions on every request.<\/li>\n\n\n\n<li><strong>Include everything that changes output in the cache key.<\/strong> Current user role, locale, feature flag state.<\/li>\n\n\n\n<li><strong>Prefer key-based expiration over manual deletes.<\/strong> Manual Rails.cache.delete calls scattered across 40 files are how stale data survives a deploy.<\/li>\n<\/ul>\n\n\n\n<p>Put a CDN in front of static assets and public pages. Serving a marketing page from an edge node costs your Rails processes nothing.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Handling high-concurrency websockets and real-time features<\/strong><\/h3>\n\n\n\n<p>Action Cable holds one connection per subscriber, so WebSocket scaling is a connection-count problem rather than a request-throughput problem.<\/p>\n\n\n\n<p>Rails 8 defaults to <strong>Solid Cable<\/strong>, which stores pubsub messages in the database with fast polling. It removes the Redis requirement for small and mid-size real-time features and works well when you&#8217;re broadcasting to hundreds or a few thousand subscribers.<\/p>\n\n\n\n<p>At high connection counts, the Redis adapter is the documented path, because Redis pubsub fans out messages without database polling overhead. Beyond that, teams put a dedicated process pool behind a load balancer with sticky-session-free WebSocket routing, and run Action Cable servers separately from web servers so a broadcast storm cannot starve HTTP requests.<\/p>\n\n\n\n<p>If your product&#8217;s core value is holding 100,000 simultaneous connections with sub-50-millisecond fanout, run that specific service in Go or Elixir and keep the rest in Rails. Splitting one service is cheaper than rewriting an application.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>How to keep the database from becoming the bottleneck<\/strong><\/h2>\n\n\n\n<p>The database is where Rails apps die, and query-level fixes beat infrastructure spending in nearly every case you&#8217;ll encounter before millions of users.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Preventing N+1 queries and improving query performance<\/strong><\/h3>\n\n\n\n<p>An N+1 query loads a collection, then fires one query per record for an association. Twenty posts, each loading their author, becomes 21 queries. The fix is eager loading.<\/p>\n\n\n\n<p>Use includes when you need the association loaded, preload to force separate queries, and eager_load to force a single LEFT JOIN. When you&#8217;re filtering on the joined table in a where, use eager_load or joins, since preload cannot see the joined columns.<\/p>\n\n\n\n<p>Install the <strong>Bullet gem<\/strong> in development and test. It raises or logs whenever it detects a missing eager load or an unused one. Running it in your test suite catches N+1s in CI before they reach production. The same discipline applies whether you&#8217;re serving a web frontend or a mobile client; <strong>our guide to <\/strong><a href=\"https:\/\/arc.dev\/employer-blog\/ruby-on-rails-for-mobile-apps-can-rails-be-used-for-mobile-app-development\/\"><strong>Rails as a mobile app backend<\/strong><\/a><strong> covers catching N+1s in an API-only Rails app before they show up as slow mobile screens<\/strong>.<\/p>\n\n\n\n<p>Select fewer columns. User.select(:id, :email) avoids pulling a large bio text column across 10,000 rows. Use pluck when you need raw values and no model objects, since <a href=\"https:\/\/www.monterail.com\/blog\/ruby-on-rails-database-optimization\">selective data retrieval and eager loading<\/a> cut both query time and Ruby object allocation.<\/p>\n\n\n\n<p>Replace per-record counts with counter_cache or a grouped count query. Calling .comments.count inside a loop of 50 posts is 50 round trips.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Indexes, query plans, and pagination for growing datasets<\/strong><\/h3>\n\n\n\n<p>Every foreign key and every column you filter or sort on needs an index. Rails does not add them automatically for belongs_to unless you use add_reference &#8230; index: true.<\/p>\n\n\n\n<p>Read the actual plan before you guess. Run EXPLAIN ANALYZE on your slowest queries in Postgres. A Seq Scan on a table with 2 million rows means you&#8217;re missing an index. A high rows removed by filter count means your index is on the wrong column order.<\/p>\n\n\n\n<p>Composite index order matters: put the equality-filtered column first, the range or sort column second. An index on (account_id, created_at) serves WHERE account_id = ? ORDER BY created_at DESC well; the reverse order does not.<\/p>\n\n\n\n<p>Offset pagination degrades badly. LIMIT 20 OFFSET 100000 forces Postgres to walk 100,020 rows. Switch to keyset (cursor) pagination on large tables: WHERE created_at &lt; ? ORDER BY created_at DESC LIMIT 20. Response time stays flat regardless of page depth.<\/p>\n\n\n\n<p>Add EXPLAIN ANALYZE output to your slow query review, and check it again after adding the index. Postgres sometimes ignores an index it considers unhelpful.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Connection Pooling Across Web Workers and Background Jobs<\/strong><\/h3>\n\n\n\n<p>Active Record&#8217;s pool is per process, not per application. Every Puma worker, every Sidekiq process, and every Rails console session opens its own pool. Teams miss this and exhaust the database.<\/p>\n\n\n\n<p>The arithmetic is simple. Set pool in database.yml to be at least equal to the thread count in that process. Then:<\/p>\n\n\n\n<p><strong>Total connections = (Puma workers \u00d7 pool) + (job processes \u00d7 pool) + headroom for consoles, migrations, and cron tasks.<\/strong><\/p>\n\n\n\n<p>With 3 Puma workers at pool 5 and 2 Sidekiq processes at concurrency 10, you need 15 + 20 = 35 connections, plus headroom. Multiply that by however many app servers you run horizontally, and a 100-connection Postgres limit disappears at 3 servers.<\/p>\n\n\n\n<p>When you outgrow the limit, put <strong>PgBouncer<\/strong> in transaction pooling mode in front of Postgres. It multiplexes hundreds of app connections onto a few dozen database connections. Managed equivalents exist (RDS Proxy, Supabase&#8217;s pooler). Note that transaction pooling breaks prepared statements, so set prepared_statements: false in database.yml.<\/p>\n\n\n\n<p>Raising the pool number does not add database capacity. It only changes how long a thread waits before timing out.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>When Read Replicas, Partitioning, and Sharding Make Sense<\/strong><\/h3>\n\n\n\n<p>These three fixes solve different problems, and applying them out of order wastes months.<\/p>\n\n\n\n<p><strong>Read replicas<\/strong> come first, once a single instance serves both reads and writes under sustained load and read queries dominate. Rails supports this natively: declare writing and reading roles in database.yml, then use connects_to and ActiveRecord::Base.connected_to(role: :reading). Rails&#8217; automatic role switching sends GETs to the replica and everything else to the primary. Budget for replication lag; a user who just posted must read from the primary or their post appears to vanish.<\/p>\n\n\n\n<p><strong>Partitioning<\/strong> comes next, when one table (events, logs, audit records) grows past what fits comfortably in memory. Postgres declarative partitioning by date range keeps queries scanning one month instead of five years, and lets you drop old partitions instantly.<\/p>\n\n\n\n<p><strong>Sharding<\/strong> comes last. It splits data across separate databases, and it changes your application code, your migrations, and every cross-tenant query. Rails 8 supports horizontal sharding through connects_to shards: and connected_to(shard: :shard_one). It&#8217;s the right call when a single primary cannot absorb your write volume, and premature when your problem is a missing index or an uncached dashboard query.<\/p>\n\n\n\n<p>Shopify shards by shop, which works because a shop&#8217;s data rarely joins to another shop&#8217;s data. If your data model has no clean partition key, sharding costs far more than it returns.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>What senior Rails expertise changes during growth<\/strong><\/h2>\n\n\n\n<p>Senior Rails engineers change scaling outcomes by picking the cheapest fix that resolves the bottleneck, and by shipping it without an outage. Both are judgment calls that come only from having done it before.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Choosing the right fix before adding infrastructure<\/strong><\/h3>\n\n\n\n<p>The expensive mistake is scaling infrastructure to paper over a code problem. Doubling your server count to survive an N+1 query triples your bill and leaves the bug in place, ready to break again at the next traffic step.<\/p>\n\n\n\n<p>Fixes ordered by cost, cheapest first:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>Query fixes.<\/strong> Add an index, eager load, replace a loop with a join. Hours of work, permanent gain.<\/li>\n\n\n\n<li><strong>Caching.<\/strong> Fragment caching a dashboard can cut its response time by an order of magnitude. Days of work.<\/li>\n\n\n\n<li><strong>Background job offloading.<\/strong> Move blocking calls out of the request cycle. Days of work, no infrastructure change if you&#8217;re on Solid Queue.<\/li>\n\n\n\n<li><strong>Horizontal web scaling.<\/strong> Add app processes behind the load balancer. Fast to do, ongoing cost.<\/li>\n\n\n\n<li><strong>Read replicas and connection pooling.<\/strong> Real infrastructure work plus lag-handling code.<\/li>\n\n\n\n<li><strong>Sharding.<\/strong> Months, and it touches everything.<\/li>\n<\/ol>\n\n\n\n<p>An engineer who reaches for step 6 when step 1 would do has misdiagnosed the problem. Profile first, then pick the step.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Planning safe performance tests, rollouts, and rollbacks<\/strong><\/h3>\n\n\n\n<p>Every scaling change needs a way to prove it worked and a way to undo it.<\/p>\n\n\n\n<p>Load test against production-like data volume. A query that&#8217;s fast on 1,000 rows tells you nothing about 5 million. Use k6, Vegeta, or wrk against a staging environment restored from an anonymized production snapshot.<\/p>\n\n\n\n<p>Ship behind feature flags. Flipper or a database-backed flag lets you route 5% of traffic to the new query path, watch p95 latency, and roll back with a toggle instead of a deploy.<\/p>\n\n\n\n<p>Migrate schema safely. Adding an index on a large table locks writes unless you use algorithm: :concurrently with disable_ddl_transaction!. Use strong_migrations to catch dangerous migrations in CI before they take production down.<\/p>\n\n\n\n<p>Watch the right metric after deploy. p95 latency for the changed endpoint, queue depth for job changes, and database CPU for query changes. If the number didn&#8217;t move, the fix wasn&#8217;t the bottleneck.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>When to bring in a senior Ruby on Rails developer<\/strong><\/h3>\n\n\n\n<p>Bring in senior Rails help when you can name the symptom but not the cause, or when the fix touches data you cannot afford to lose.<\/p>\n\n\n\n<p>Concrete triggers:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>p95 latency has doubled over a quarter, and your APM breakdown does not obviously blame one endpoint<\/li>\n\n\n\n<li>You&#8217;re considering a read replica, PgBouncer, or sharding and nobody on the team has run one in production<\/li>\n\n\n\n<li>Job queue depth grows during every peak and adding workers stopped helping<\/li>\n\n\n\n<li>Someone has proposed rewriting the app in another language to fix performance<\/li>\n\n\n\n<li>You need a large table partitioned or a zero-downtime migration on 50 million rows<\/li>\n<\/ul>\n\n\n\n<p>Finding that person is the hard part. Rails developers who have shipped through a sharding project or a Postgres partition rollout are a small slice of the Rails hiring pool, and they&#8217;re rarely browsing job boards.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Frequently asked questions<\/strong><\/h2>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Does Ruby on Rails scale?<\/strong><\/h3>\n\n\n\n<p>Yes. Rails scales through a well-understood sequence of fixes: eager loading, background job offloading, caching, horizontal web scaling, read replicas, and sharding. The framework handles the structural parts of scaling (connection pooling, background jobs, caching, database sharding); what it can&#8217;t solve is Ruby&#8217;s lower per-process throughput compared to Go or Java, which shows up as higher server costs rather than a hard ceiling.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Can Ruby on Rails handle millions of users?<\/strong><\/h3>\n\n\n\n<p>Yes, and companies are running it at far larger scale than that today. During BFCM 2025, Shopify&#8217;s app servers peaked at more than 117 million requests per minute (489 million at the edge), on a Rails monolith, by sharding its database layer rather than moving off Rails. Whether &#8220;millions of users&#8221; is actually demanding depends more on peak requests per second and concurrency patterns than the raw user count.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Why did Twitter move off Ruby on Rails?<\/strong><\/h3>\n\n\n\n<p>Twitter&#8217;s early scaling struggles happened over a decade ago, when the company was growing faster than almost any web application in history, and much of what broke was a general monolith-at-hyperscale problem rather than something specific to Rails. Companies running Rails at comparable or greater scale today, including Shopify and GitHub, haven&#8217;t hit the same wall, largely because Rails scaling tooling (sharding support, background job infrastructure, caching defaults) has matured significantly since then.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>What is Ruby on Rails&#8217; biggest scalability limitation?<\/strong><\/h3>\n\n\n\n<p>Ruby&#8217;s per-process throughput. Ruby executes fewer requests per second per CPU core than Go or the JVM for the same CPU-bound work, so scaling a Rails app costs more in server count than an equivalent app in a faster language. For most product-focused teams, that tradeoff favors Rails anyway, since it buys back engineering time; it stops favoring Rails for workloads with single-digit-millisecond latency budgets or heavy CPU-bound computation.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>How does Shopify scale Ruby on Rails?<\/strong><\/h3>\n\n\n\n<p>Shopify shards its Rails database by shop, which works because one shop&#8217;s data rarely needs to join against another shop&#8217;s data. On top of that, Shopify&#8217;s engineering team built YJIT, a just-in-time compiler for Ruby, specifically to close the per-process throughput gap, and it&#8217;s now a standard part of Ruby itself.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>When should you shard a Rails database?<\/strong><\/h3>\n\n\n\n<p>Only after read replicas and query optimization stop being enough, and only when a single primary database can&#8217;t absorb your write volume. Sharding is the most expensive and disruptive scaling step, since it touches application code, migrations, and cross-tenant queries, so applying it before you&#8217;ve fixed N+1 queries, added caching, or added a read replica usually means solving the wrong problem at far higher cost.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Is Ruby on Rails good for high-traffic websites?<\/strong><\/h3>\n\n\n\n<p>Yes, for most high-traffic use cases: content sites, marketplaces, SaaS products, and database-backed applications with heavy read\/write patterns. It&#8217;s a weaker fit specifically when the core product requirement is extreme low-latency responses, six-figure concurrent WebSocket connections, or CPU-bound computation, in which case teams typically extract just that piece into a separate service rather than rewriting the whole application.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Building a Rails system that grows deliberately<\/strong><\/h2>\n\n\n\n<p>Rails scales in a sequence, and you can know it in advance. N+1 queries surface first, then the connection pool, then the job queue, then the single-server ceiling, then cache and session state once you&#8217;re multi-server.&nbsp;<\/p>\n\n\n\n<p>Each stage has a named fix: eager loading with Bullet catching regressions in CI, pool sizing against Puma and Sidekiq concurrency, Solid Queue or Sidekiq with dedicated worker pools, stateless processes behind a load balancer, and Solid Cache or Redis for hot paths.<\/p>\n\n\n\n<p>Ruby&#8217;s per-process throughput is lower than Go&#8217;s or Java&#8217;s, and that costs you servers. Shopify&#8217;s app servers peaking at more than 117 million requests per minute during BFCM 2025, on a sharded Rails monolith with YJIT enabled, shows what the framework absorbs when the architecture underneath it is right.<\/p>\n\n\n\n<p>The honest limits are narrow: single-digit-millisecond latency budgets, six-figure concurrent WebSocket counts, and CPU-bound number crunching all belong in a separate service written in Go, Rust, or Elixir. Extracting one service takes two weeks, while rewriting an application takes two years, and it usually reveals that the original problem was a missing index.<\/p>\n\n\n\n<p>Before your next scaling decision, do three things: check p95 latency by endpoint in your APM, run EXPLAIN ANALYZE on your five slowest queries, and add up your total connection count across every Puma worker and job process. Those three numbers name your bottleneck more reliably than any architecture debate.<\/p>\n\n\n\n<p>Scaling Rails means having engineers who&#8217;ve already hit these bottlenecks and know which fix applies at each stage. <a href=\"https:\/\/arc.dev\/\"><strong>Arc<\/strong><\/a> pre-vets Rails developers for technical depth and English fluency before you see a profile. HireAI matches your requirements against a pool of vetted candidates and returns a shortlist in minutes, not weeks, from a network of 450,000 professionals across 190 countries.&nbsp;<\/p>\n\n\n\n<p><a href=\"https:\/\/arc.dev\/hire-developers\/ruby-on-rails\"><strong>Hire vetted Ruby on Rails developers with Arc \u2192<\/strong><\/a><\/p>\n\n\n\n<script type=\"application\/ld+json\">\n{\n  \"@context\": \"https:\/\/schema.org\",\n  \"@graph\": [\n    {\n      \"@type\": \"Article\",\n      \"headline\": \"Ruby on Rails Scalability: How Rails Handles Growth from MVP to Millions of Users\",\n      \"description\": \"How Ruby on Rails scales from MVP to millions of users: the real bottleneck sequence, named fixes at each stage, and how companies like Shopify scale Rails in production.\",\n      \"image\": [\n        {\n          \"@type\": \"ImageObject\",\n          \"url\": \"https:\/\/cdn-employer-wp.arc.dev\/wp-content\/uploads\/2026\/09\/ruby-on-rails-scalability.png\"\n        },\n        {\n          \"@type\": \"ImageObject\",\n          \"url\": \"https:\/\/cdn-employer-wp.arc.dev\/wp-content\/uploads\/2026\/09\/image-2.png\",\n          \"caption\": \"Five-stage Rails scaling bottleneck sequence: N+1 queries, connection pool exhaustion, job queue backlog, single-server ceiling, and cache and session state, each with its warning signal and fix.\"\n        }\n      ],\n      \"author\": {\n        \"@type\": \"Organization\",\n        \"name\": \"Arc\",\n        \"url\": \"https:\/\/arc.dev\"\n      },\n      \"publisher\": {\n        \"@type\": \"Organization\",\n        \"name\": \"Arc\",\n        \"logo\": {\n          \"@type\": \"ImageObject\",\n          \"url\": \"https:\/\/cdn.arc.dev\/arc-next-landing\/images\/arc\/share-logo.png\"\n        }\n      },\n      \"datePublished\": \"2026-08-31\",\n      \"dateModified\": \"2026-09-22\",\n      \"mainEntityOfPage\": {\n        \"@type\": \"WebPage\",\n        \"@id\": \"https:\/\/arc.dev\/employer-blog\/ruby-on-rails-scalability\/\"\n      }\n    },\n    {\n      \"@type\": \"FAQPage\",\n      \"mainEntity\": [\n        {\n          \"@type\": \"Question\",\n          \"name\": \"Does Ruby on Rails scale?\",\n          \"acceptedAnswer\": {\n            \"@type\": \"Answer\",\n            \"text\": \"Yes. Rails scales through a well-understood sequence of fixes: eager loading, background job offloading, caching, horizontal web scaling, read replicas, and sharding. The framework handles the structural parts of scaling: connection pooling, background jobs, caching, database sharding. What it can't solve is Ruby's lower per-process throughput compared to Go or Java, which shows up as higher server costs rather than a hard ceiling.\"\n          }\n        },\n        {\n          \"@type\": \"Question\",\n          \"name\": \"Can Ruby on Rails handle millions of users?\",\n          \"acceptedAnswer\": {\n            \"@type\": \"Answer\",\n            \"text\": \"Yes, and companies are running it at far higher scale than that today. During BFCM 2025, Shopify's app servers peaked at more than 117 million requests per minute (489 million at the edge), on a Rails monolith, by sharding its database layer rather than moving off Rails. Whether millions of users is actually demanding depends more on peak requests per second and concurrency patterns than the raw user count.\"\n          }\n        },\n        {\n          \"@type\": \"Question\",\n          \"name\": \"Why did Twitter move off Ruby on Rails?\",\n          \"acceptedAnswer\": {\n            \"@type\": \"Answer\",\n            \"text\": \"Twitter's early scaling struggles happened over a decade ago, at a time when the company was growing faster than almost any web application in history, and much of what broke was a general monolith-at-hyperscale problem rather than something specific to Rails. Companies running Rails at comparable or greater scale today, including Shopify and GitHub, haven't hit the same wall, largely because the tooling for scaling Rails, sharding support, background job infrastructure, and caching defaults has matured significantly since then.\"\n          }\n        },\n        {\n          \"@type\": \"Question\",\n          \"name\": \"What is Ruby on Rails' biggest scalability limitation?\",\n          \"acceptedAnswer\": {\n            \"@type\": \"Answer\",\n            \"text\": \"Ruby's per-process throughput. Ruby executes fewer requests per second per CPU core than Go or the JVM for the same CPU-bound work, so scaling a Rails app costs more in server count than an equivalent app in a faster language. For most product-focused teams that tradeoff favors Rails anyway, since it buys back engineering time. It stops favoring Rails for workloads with single-digit-millisecond latency budgets or heavy CPU-bound computation.\"\n          }\n        },\n        {\n          \"@type\": \"Question\",\n          \"name\": \"How does Shopify scale Ruby on Rails?\",\n          \"acceptedAnswer\": {\n            \"@type\": \"Answer\",\n            \"text\": \"Shopify shards its Rails database by shop, which works because one shop's data rarely needs to join against another shop's data. On top of that, Shopify's engineering team built YJIT, a just-in-time compiler for Ruby, specifically to close the per-process throughput gap, and it's now a standard part of Ruby itself.\"\n          }\n        },\n        {\n          \"@type\": \"Question\",\n          \"name\": \"When should you shard a Rails database?\",\n          \"acceptedAnswer\": {\n            \"@type\": \"Answer\",\n            \"text\": \"Only after read replicas and query optimization stop being enough, and only when a single primary database can't absorb your write volume. Sharding is the most expensive and disruptive scaling step, since it touches application code, migrations, and cross-tenant queries, so applying it before you've fixed N plus 1 queries, added caching, or added a read replica usually means solving the wrong problem at far higher cost.\"\n          }\n        },\n        {\n          \"@type\": \"Question\",\n          \"name\": \"Is Ruby on Rails good for high-traffic websites?\",\n          \"acceptedAnswer\": {\n            \"@type\": \"Answer\",\n            \"text\": \"Yes, for most high-traffic use cases: content sites, marketplaces, SaaS products, and database-backed applications with heavy read and write patterns. It's a weaker fit specifically when the core product requirement is extreme low-latency responses, six-figure concurrent WebSocket connections, or CPU-bound computation, in which case teams typically extract just that piece into a separate service rather than rewriting the whole application.\"\n          }\n        }\n      ]\n    }\n  ]\n}\n\n<\/script>\n","protected":false},"excerpt":{"rendered":"<p>Written for Ruby on Rails 8.1.3.1 (July 29, 2026). &#8220;Rails doesn&#8217;t scale.&#8221; &#8220;Rails is fine for an MVP, but you&#8217;ll rewrite it once you grow.&#8221; &#8220;Twitter left Rails because it couldn&#8217;t handle scale.&#8221; If you&#8217;re deciding whether to build on Rails or keep investing in the Rails app you already have, you&#8217;ve heard all three. [&hellip;]<\/p>\n","protected":false},"author":15,"featured_media":5363,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[93],"tags":[],"class_list":["post-5362","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-development"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v28.5 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>Ruby on Rails Scalability: MVP to Millions of Users - Arc Employer Blog<\/title>\n<meta name=\"description\" content=\"See how Ruby on Rails scalability holds up in production: the real bottleneck sequence, the named fix at each stage, and how Shopify scales it.\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/arc.dev\/employer-blog\/ruby-on-rails-scalability\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Ruby on Rails Scalability: MVP to Millions of Users - Arc Employer Blog\" \/>\n<meta property=\"og:description\" content=\"See how Ruby on Rails scalability holds up in production: the real bottleneck sequence, the named fix at each stage, and how Shopify scales it.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/arc.dev\/employer-blog\/ruby-on-rails-scalability\/\" \/>\n<meta property=\"og:site_name\" content=\"Arc Employer Blog\" \/>\n<meta property=\"article:publisher\" content=\"https:\/\/www.facebook.com\/arcdotdev\" \/>\n<meta property=\"article:author\" content=\"https:\/\/www.facebook.com\/arcdotdev\" \/>\n<meta property=\"article:published_time\" content=\"2026-09-23T09:38:34+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2026-09-23T09:39:45+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/arc.dev\/employer-blog\/wp-content\/uploads\/2026\/09\/ruby-on-rails-scalability.png\" \/>\n\t<meta property=\"og:image:width\" content=\"1672\" \/>\n\t<meta property=\"og:image:height\" content=\"941\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/png\" \/>\n<meta name=\"author\" content=\"The Arc Team\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:creator\" content=\"@arcdotdev\" \/>\n<meta name=\"twitter:site\" content=\"@arcdotdev\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"The Arc Team\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"23 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\\\/\\\/arc.dev\\\/employer-blog\\\/ruby-on-rails-scalability\\\/#article\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/arc.dev\\\/employer-blog\\\/ruby-on-rails-scalability\\\/\"},\"author\":{\"name\":\"The Arc Team\",\"@id\":\"https:\\\/\\\/arc.dev\\\/employer-blog\\\/#\\\/schema\\\/person\\\/08dd4743f5c0f965590e77094c5579bc\"},\"headline\":\"Ruby on Rails scalability: how Rails handles growth from MVP to millions of users\",\"datePublished\":\"2026-09-23T09:38:34+00:00\",\"dateModified\":\"2026-09-23T09:39:45+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\\\/\\\/arc.dev\\\/employer-blog\\\/ruby-on-rails-scalability\\\/\"},\"wordCount\":4608,\"publisher\":{\"@id\":\"https:\\\/\\\/arc.dev\\\/employer-blog\\\/#organization\"},\"image\":{\"@id\":\"https:\\\/\\\/arc.dev\\\/employer-blog\\\/ruby-on-rails-scalability\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/arc.dev\\\/employer-blog\\\/wp-content\\\/uploads\\\/2026\\\/09\\\/ruby-on-rails-scalability.png\",\"articleSection\":[\"Development\"],\"inLanguage\":\"en-US\"},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/arc.dev\\\/employer-blog\\\/ruby-on-rails-scalability\\\/\",\"url\":\"https:\\\/\\\/arc.dev\\\/employer-blog\\\/ruby-on-rails-scalability\\\/\",\"name\":\"Ruby on Rails Scalability: MVP to Millions of Users - Arc Employer Blog\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/arc.dev\\\/employer-blog\\\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\\\/\\\/arc.dev\\\/employer-blog\\\/ruby-on-rails-scalability\\\/#primaryimage\"},\"image\":{\"@id\":\"https:\\\/\\\/arc.dev\\\/employer-blog\\\/ruby-on-rails-scalability\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/arc.dev\\\/employer-blog\\\/wp-content\\\/uploads\\\/2026\\\/09\\\/ruby-on-rails-scalability.png\",\"datePublished\":\"2026-09-23T09:38:34+00:00\",\"dateModified\":\"2026-09-23T09:39:45+00:00\",\"description\":\"See how Ruby on Rails scalability holds up in production: the real bottleneck sequence, the named fix at each stage, and how Shopify scales it.\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/arc.dev\\\/employer-blog\\\/ruby-on-rails-scalability\\\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/arc.dev\\\/employer-blog\\\/ruby-on-rails-scalability\\\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/arc.dev\\\/employer-blog\\\/ruby-on-rails-scalability\\\/#primaryimage\",\"url\":\"https:\\\/\\\/arc.dev\\\/employer-blog\\\/wp-content\\\/uploads\\\/2026\\\/09\\\/ruby-on-rails-scalability.png\",\"contentUrl\":\"https:\\\/\\\/arc.dev\\\/employer-blog\\\/wp-content\\\/uploads\\\/2026\\\/09\\\/ruby-on-rails-scalability.png\",\"width\":1672,\"height\":941,\"caption\":\"Ruby on Rails scalability: how Rails handles growth from MVP to millions of users\"},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/arc.dev\\\/employer-blog\\\/ruby-on-rails-scalability\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/arc.dev\\\/employer-blog\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Ruby on Rails scalability: how Rails handles growth from MVP to millions of users\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/arc.dev\\\/employer-blog\\\/#website\",\"url\":\"https:\\\/\\\/arc.dev\\\/employer-blog\\\/\",\"name\":\"Arc Employer Blog\",\"description\":\"Insights on hiring and remote work\",\"publisher\":{\"@id\":\"https:\\\/\\\/arc.dev\\\/employer-blog\\\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/arc.dev\\\/employer-blog\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\\\/\\\/arc.dev\\\/employer-blog\\\/#organization\",\"name\":\"Arc.dev\",\"url\":\"https:\\\/\\\/arc.dev\\\/employer-blog\\\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/arc.dev\\\/employer-blog\\\/#\\\/schema\\\/logo\\\/image\\\/\",\"url\":\"https:\\\/\\\/arc.dev\\\/employer-blog\\\/wp-content\\\/uploads\\\/2022\\\/02\\\/Arc-alternate-logo.png\",\"contentUrl\":\"https:\\\/\\\/arc.dev\\\/employer-blog\\\/wp-content\\\/uploads\\\/2022\\\/02\\\/Arc-alternate-logo.png\",\"width\":512,\"height\":512,\"caption\":\"Arc.dev\"},\"image\":{\"@id\":\"https:\\\/\\\/arc.dev\\\/employer-blog\\\/#\\\/schema\\\/logo\\\/image\\\/\"},\"sameAs\":[\"https:\\\/\\\/www.facebook.com\\\/arcdotdev\",\"https:\\\/\\\/x.com\\\/arcdotdev\",\"https:\\\/\\\/www.instagram.com\\\/arcdotdev\\\/\",\"https:\\\/\\\/www.linkedin.com\\\/company\\\/arcdotdev\",\"https:\\\/\\\/www.youtube.com\\\/c\\\/Arcdotdev\"]},{\"@type\":\"Person\",\"@id\":\"https:\\\/\\\/arc.dev\\\/employer-blog\\\/#\\\/schema\\\/person\\\/08dd4743f5c0f965590e77094c5579bc\",\"name\":\"The Arc Team\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/c1380473325c827343a6d47c7b5d6916c147171af99760766d2acb56da62ed02?s=96&d=mm&r=pg\",\"url\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/c1380473325c827343a6d47c7b5d6916c147171af99760766d2acb56da62ed02?s=96&d=mm&r=pg\",\"contentUrl\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/c1380473325c827343a6d47c7b5d6916c147171af99760766d2acb56da62ed02?s=96&d=mm&r=pg\",\"caption\":\"The Arc Team\"},\"description\":\"The Arc team provides articles and expert advice on tech careers and remote work. From helping beginners land their first junior role to supporting remote workers facing challenges at home or guiding mid-level professionals toward leadership, Arc covers it all!\",\"sameAs\":[\"https:\\\/\\\/arc.dev\\\/developer-blog\\\/\",\"https:\\\/\\\/www.facebook.com\\\/arcdotdev\",\"https:\\\/\\\/www.instagram.com\\\/arcdotdev\\\/\",\"https:\\\/\\\/www.linkedin.com\\\/company\\\/arcdotdev\",\"https:\\\/\\\/x.com\\\/arcdotdev\",\"https:\\\/\\\/www.youtube.com\\\/c\\\/Arcdotdev\"],\"url\":\"https:\\\/\\\/arc.dev\\\/employer-blog\\\/author\\\/thearcteam\\\/\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Ruby on Rails Scalability: MVP to Millions of Users - Arc Employer Blog","description":"See how Ruby on Rails scalability holds up in production: the real bottleneck sequence, the named fix at each stage, and how Shopify scales it.","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/arc.dev\/employer-blog\/ruby-on-rails-scalability\/","og_locale":"en_US","og_type":"article","og_title":"Ruby on Rails Scalability: MVP to Millions of Users - Arc Employer Blog","og_description":"See how Ruby on Rails scalability holds up in production: the real bottleneck sequence, the named fix at each stage, and how Shopify scales it.","og_url":"https:\/\/arc.dev\/employer-blog\/ruby-on-rails-scalability\/","og_site_name":"Arc Employer Blog","article_publisher":"https:\/\/www.facebook.com\/arcdotdev","article_author":"https:\/\/www.facebook.com\/arcdotdev","article_published_time":"2026-09-23T09:38:34+00:00","article_modified_time":"2026-09-23T09:39:45+00:00","og_image":[{"width":1672,"height":941,"url":"https:\/\/arc.dev\/employer-blog\/wp-content\/uploads\/2026\/09\/ruby-on-rails-scalability.png","type":"image\/png"}],"author":"The Arc Team","twitter_card":"summary_large_image","twitter_creator":"@arcdotdev","twitter_site":"@arcdotdev","twitter_misc":{"Written by":"The Arc Team","Est. reading time":"23 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/arc.dev\/employer-blog\/ruby-on-rails-scalability\/#article","isPartOf":{"@id":"https:\/\/arc.dev\/employer-blog\/ruby-on-rails-scalability\/"},"author":{"name":"The Arc Team","@id":"https:\/\/arc.dev\/employer-blog\/#\/schema\/person\/08dd4743f5c0f965590e77094c5579bc"},"headline":"Ruby on Rails scalability: how Rails handles growth from MVP to millions of users","datePublished":"2026-09-23T09:38:34+00:00","dateModified":"2026-09-23T09:39:45+00:00","mainEntityOfPage":{"@id":"https:\/\/arc.dev\/employer-blog\/ruby-on-rails-scalability\/"},"wordCount":4608,"publisher":{"@id":"https:\/\/arc.dev\/employer-blog\/#organization"},"image":{"@id":"https:\/\/arc.dev\/employer-blog\/ruby-on-rails-scalability\/#primaryimage"},"thumbnailUrl":"https:\/\/arc.dev\/employer-blog\/wp-content\/uploads\/2026\/09\/ruby-on-rails-scalability.png","articleSection":["Development"],"inLanguage":"en-US"},{"@type":"WebPage","@id":"https:\/\/arc.dev\/employer-blog\/ruby-on-rails-scalability\/","url":"https:\/\/arc.dev\/employer-blog\/ruby-on-rails-scalability\/","name":"Ruby on Rails Scalability: MVP to Millions of Users - Arc Employer Blog","isPartOf":{"@id":"https:\/\/arc.dev\/employer-blog\/#website"},"primaryImageOfPage":{"@id":"https:\/\/arc.dev\/employer-blog\/ruby-on-rails-scalability\/#primaryimage"},"image":{"@id":"https:\/\/arc.dev\/employer-blog\/ruby-on-rails-scalability\/#primaryimage"},"thumbnailUrl":"https:\/\/arc.dev\/employer-blog\/wp-content\/uploads\/2026\/09\/ruby-on-rails-scalability.png","datePublished":"2026-09-23T09:38:34+00:00","dateModified":"2026-09-23T09:39:45+00:00","description":"See how Ruby on Rails scalability holds up in production: the real bottleneck sequence, the named fix at each stage, and how Shopify scales it.","breadcrumb":{"@id":"https:\/\/arc.dev\/employer-blog\/ruby-on-rails-scalability\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/arc.dev\/employer-blog\/ruby-on-rails-scalability\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/arc.dev\/employer-blog\/ruby-on-rails-scalability\/#primaryimage","url":"https:\/\/arc.dev\/employer-blog\/wp-content\/uploads\/2026\/09\/ruby-on-rails-scalability.png","contentUrl":"https:\/\/arc.dev\/employer-blog\/wp-content\/uploads\/2026\/09\/ruby-on-rails-scalability.png","width":1672,"height":941,"caption":"Ruby on Rails scalability: how Rails handles growth from MVP to millions of users"},{"@type":"BreadcrumbList","@id":"https:\/\/arc.dev\/employer-blog\/ruby-on-rails-scalability\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/arc.dev\/employer-blog\/"},{"@type":"ListItem","position":2,"name":"Ruby on Rails scalability: how Rails handles growth from MVP to millions of users"}]},{"@type":"WebSite","@id":"https:\/\/arc.dev\/employer-blog\/#website","url":"https:\/\/arc.dev\/employer-blog\/","name":"Arc Employer Blog","description":"Insights on hiring and remote work","publisher":{"@id":"https:\/\/arc.dev\/employer-blog\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/arc.dev\/employer-blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/arc.dev\/employer-blog\/#organization","name":"Arc.dev","url":"https:\/\/arc.dev\/employer-blog\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/arc.dev\/employer-blog\/#\/schema\/logo\/image\/","url":"https:\/\/arc.dev\/employer-blog\/wp-content\/uploads\/2022\/02\/Arc-alternate-logo.png","contentUrl":"https:\/\/arc.dev\/employer-blog\/wp-content\/uploads\/2022\/02\/Arc-alternate-logo.png","width":512,"height":512,"caption":"Arc.dev"},"image":{"@id":"https:\/\/arc.dev\/employer-blog\/#\/schema\/logo\/image\/"},"sameAs":["https:\/\/www.facebook.com\/arcdotdev","https:\/\/x.com\/arcdotdev","https:\/\/www.instagram.com\/arcdotdev\/","https:\/\/www.linkedin.com\/company\/arcdotdev","https:\/\/www.youtube.com\/c\/Arcdotdev"]},{"@type":"Person","@id":"https:\/\/arc.dev\/employer-blog\/#\/schema\/person\/08dd4743f5c0f965590e77094c5579bc","name":"The Arc Team","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/secure.gravatar.com\/avatar\/c1380473325c827343a6d47c7b5d6916c147171af99760766d2acb56da62ed02?s=96&d=mm&r=pg","url":"https:\/\/secure.gravatar.com\/avatar\/c1380473325c827343a6d47c7b5d6916c147171af99760766d2acb56da62ed02?s=96&d=mm&r=pg","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/c1380473325c827343a6d47c7b5d6916c147171af99760766d2acb56da62ed02?s=96&d=mm&r=pg","caption":"The Arc Team"},"description":"The Arc team provides articles and expert advice on tech careers and remote work. From helping beginners land their first junior role to supporting remote workers facing challenges at home or guiding mid-level professionals toward leadership, Arc covers it all!","sameAs":["https:\/\/arc.dev\/developer-blog\/","https:\/\/www.facebook.com\/arcdotdev","https:\/\/www.instagram.com\/arcdotdev\/","https:\/\/www.linkedin.com\/company\/arcdotdev","https:\/\/x.com\/arcdotdev","https:\/\/www.youtube.com\/c\/Arcdotdev"],"url":"https:\/\/arc.dev\/employer-blog\/author\/thearcteam\/"}]}},"_links":{"self":[{"href":"https:\/\/arc.dev\/employer-blog\/wp-json\/wp\/v2\/posts\/5362","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/arc.dev\/employer-blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/arc.dev\/employer-blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/arc.dev\/employer-blog\/wp-json\/wp\/v2\/users\/15"}],"replies":[{"embeddable":true,"href":"https:\/\/arc.dev\/employer-blog\/wp-json\/wp\/v2\/comments?post=5362"}],"version-history":[{"count":0,"href":"https:\/\/arc.dev\/employer-blog\/wp-json\/wp\/v2\/posts\/5362\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/arc.dev\/employer-blog\/wp-json\/wp\/v2\/media\/5363"}],"wp:attachment":[{"href":"https:\/\/arc.dev\/employer-blog\/wp-json\/wp\/v2\/media?parent=5362"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/arc.dev\/employer-blog\/wp-json\/wp\/v2\/categories?post=5362"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/arc.dev\/employer-blog\/wp-json\/wp\/v2\/tags?post=5362"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}