Rails framework and application performance concepts explained in depth, one at a time.
A migration that runs in 40ms in dev can hold an ACCESS EXCLUSIVE lock on a production table for minutes — this concept covers exactly which DDL locks what, how strong_migrations catches it before it ships, and the multi-deploy pattern for changes that are unsafe no matter how you write them.
A read replica offloads query load but does nothing for write throughput or storage on the primary — sharding is the much bigger, much harder step for when the primary itself is the bottleneck. Rails 6+ gives you connects_to, connected_to, and automatic DatabaseSelector/ShardSelector middleware for both, but the 2-second staleness window and cross-shard joins each carry real, easy-to-miss failure modes.
Which indexes a Rails schema actually needs, why composite index column order decides everything, and how to confirm from a Rails console that Postgres is really using the index you added.
includes isn't one strategy, it's a choice between preload and eager_load — and nested eager loading can multiply instantiated objects until it's slower than the N+1 it replaced. Plus: aggregate in SQL, and activerecord-import for bulk writes.
The processes × threads × hosts arithmetic your database pool has to fit under, why deploy-time overlap is the real risk, and how pgbouncer's pool size means something different.
The most common source of hidden N+1s isn't a missing includes — it's an ActiveRecord query method quietly living inside an instance method.
Rails 8 defaults to database-backed job, cache, and pubsub adapters instead of Redis — 37signals cut Basecamp's cache cost 80% and its P95 latency in half doing this, but polling isn't push, and the honest ceiling for each adapter is documented, not assumed.
Rails costs ~28ms per request over bare Rack — a rounding error against a 700ms page load, but a real bill on a high-traffic API; here are the levers, and why a stripped Rails ended up 25% faster than stock Sinatra.
instances_needed = arrival_rate x average_response_time — the one equation behind fleet sizing, why 100% utilization is an outage waiting for a burst, and why queue time (not response time) is the only honest signal to scale up.
Every Ruby app server picks a trade-off between defending against a slow client and defending against a slow app — knowing which one decides your deploy topology.
Zeitwerk's file-path-mirrors-namespace rule is a runtime contract, not a style guide — violate it and you get a Zeitwerk::NameError, often on the first eager load in CI or production rather than in local development. This concept covers the contract itself, the acronym and STI-naming collisions that break it at scale, and the two real tools for drawing module boundaries — Rails Engines and Packwerk — with the concrete trade-offs between them.
Row-based, schema-based, and database-based tenancy buy wildly different isolation guarantees at wildly different operational cost, and the cheapest one — a shared schema with a tenant_id column — enforces isolation entirely in application code, where a single forgotten scope is a full cross-tenant data leak.
rack-attack's defaults look finished the moment they boot, but a per-process memory cache store on a multi-worker Puma deployment silently under-counts requests and makes a throttle limit meaningless. This concept covers the DSL, the shared-cache-store requirement that actually makes throttling work, the fixed-window algorithm's boundary burst, and why per-IP blocking is a false-positive risk behind shared NAT.
Pundit vs CanCanCan is an architecture choice, `permit!` is a mass-assignment hole with a one-character name, and `cookies_serializer = :marshal` turns a leaked secret_key_base into remote code execution instead of just session forgery.