Scaling the database

One connection per request adds up fast

Every Rails request checks a database connection out of a pool, uses it, and checks it back in. That pool is capped (database.yml's max_connections, from module 09) β€” fine at low traffic, but at real scale, hundreds of Puma threads across multiple servers can each want a connection at once, and Postgres itself has a hard ceiling on how many connections it will accept. PgBouncer sits in front of Postgres as a lightweight connection pooler: your app connects to PgBouncer, which multiplexes many app-side connections onto a much smaller number of real Postgres connections. The database sees far fewer connections; your app doesn't have to change a line of Ruby to get that.

The other lever is read replicas β€” one or more read-only copies of the primary database, kept in sync automatically. A read-heavy API (and PawWalk's GET /walkers, GET /bookings far outnumber writes) can send SELECTs to a replica, leaving the primary free to handle writes and anything that needs the absolute latest data.

Rails' own multi-database support makes this a config change, not a rewrite: connects_to database: { writing: :primary, reading: :replica } tells a model (or ApplicationRecord itself) which named connection in database.yml to use for writes vs. reads. Pair it with ActiveRecord::Base.connected_to(role: :reading) { ... } around a block, or Rails' automatic role-switching middleware, and GET requests transparently route to the replica while POST/PATCH/DELETE stay on the primary β€” no controller code has to know which database it's talking to.

This builds directly on modules 16-17: N+1 fixes and caching reduce HOW MANY queries you run; replicas and pooling change WHERE those queries land once you can't reduce the count any further.