Cloud Agentics Talk to an engineer

Work / Database selection

Database selection, June 2025

The right AWS database for each workload, with no rewrite

A client ran Aurora Serverless v1 at maximum capacity 24 hours a day and paid more than Aurora Provisioned would cost. The application code depended on Aurora-specific features, so the client could not switch. We built a connection layer that makes the database type a configuration setting.

0code changesto switch between RDS and Aurora
5 %pinned connectionson RDS Proxy, from 95 %
50 msLambda cold startfrom 800 ms
3,200USD per month savedAurora Serverless v1 to Provisioned

The situation

The client's Aurora Serverless v1 cluster ran at its maximum capacity all day. A provisioned cluster of the same size costs less, but the application was written against Aurora-specific features and a change of database type meant a rewrite. We have found the same pattern in four forms.

Serverless for a steady load

The team chose Aurora Serverless for a quick start in development. The traffic is predictable, and the cluster now costs 3 times what a provisioned cluster would.

Over-provisioned RDS

The team bought a db.r6g.2xlarge instance as a reserve. Utilization is 15 %, and a smaller instance cannot be tested without risk.

RDS Proxy without its benefit

The team added RDS Proxy for Lambda, but the application sets session variables in every connection. Each connection is pinned to one database connection, so the proxy pools nothing.

The wrong engine

The team chose MySQL because they knew it. The workload is read-heavy and needs Aurora read replicas, which the team cannot add without changes.

The connection layer

For every client we add a connection layer between the application and the database. The layer owns pooling, retries, failover and read and write routing. The application holds no session state in a connection. A change from RDS to Aurora, or from provisioned to serverless, is then a configuration change and not a code change.

API service Background jobs Lambda functions
RDS Proxy connection pool, no session state
Connection pool configuration Retry and failover logic Read and write splitting Endpoints from the environment
RDS MySQL steady load Aurora Provisioned high availability Aurora Serverless variable traffic

A database change is a configuration change

We document the trade-offs of each database option and prepare the migration path to each one. When the requirements change, the client switches without a rewrite.

Five rules we apply

We set up the same five rules for every client, so the database can be changed from the first day.

RuleWhat we doEffect
No session state in connectionsWe remove SET statements, temporary tables and user-defined variables from the code.RDS Proxy reuses connections without pinning.
Endpoints from the environmentDatabase endpoints come from environment variables with one naming standard.A change from RDS to Aurora is a deployment configuration change, with no code change.
Read replica readyWe structure queries so that read operations can be routed to a replica, even before a replica exists.Reads scale without a refactor.
Pool size per environmentDevelopment, staging and production each have their own pool size and can each use a different database type.Aurora Serverless in development, Aurora Provisioned in production.
Performance baseline before a migrationWe record query performance metrics before any migration.The new database is shown to perform equally or better before cutover is final.

RDS Proxy and connection pinning

RDS Proxy pools connections between an application and the database. When a client sets session state on a connection, the proxy must pin that client to one database connection, and the pool gives no benefit. The cause is in the application code, so we trace each pinned connection back to the statement that caused it.

SET @variable = value

Replace with

a query parameter
SET SESSION timezone

Replace with

UTC in storage and queries, conversion in the application
CREATE TEMPORARY TABLE

Replace with

a common table expression or a subquery
PREPARE statement

Replace with

the prepared statements of the ORM

Before: pinned connections

95 % of connections were pinned. A Lambda cold start took 800 ms. The connection limit was reached at peak traffic, for years.

After: pooled connections

5 % of connections are pinned. A Lambda cold start takes 50 ms, 16 times shorter. There is no downtime at peak traffic.

95 % to 5 %pinned connectionson RDS Proxy
16×shorter Lambda cold startfrom 800 ms to 50 ms
0downtime at peak trafficconnection limit no longer reached

Three migrations and their figures

With the connection layer in place, each migration below was a configuration change and a cutover window.

FromToReasonSaving, USD per monthMigration time, h
Aurora Serverless v1Aurora ProvisionedThe traffic was predictable. Serverless cost 2.5 times more.3,2002
RDS db.r6g.2xlargeRDS db.r6g.large and one read replica80 % of the load was reads. Routing reads to the replica reduced the load on the primary.1,8004
Aurora ProvisionedAurora Serverless v2The development and staging environments were idle 70 % of the time.2,4001

Protection of production data

A DELETE statement without a WHERE clause removes a table's data in seconds. AWS offers more than one way to recover, and each has a cost. We select the level of protection for each environment, so that critical production data has the most options and development data has the least cost.

Aurora recovery optionWhat it doesWindowEnginesDowntime
BacktrackRewinds the cluster in place to a point in time, without a restore from backup. Recovery takes seconds.72 hAurora MySQLNone
Point-in-time recoveryRestores a new cluster from any point within the backup retention period. The original cluster is not changed.up to 35 daysAurora MySQL and Aurora PostgreSQLNone, new cluster

Protection level by environment

BacktrackPoint-in-time recoveryCross-region backup

Most options, with recovery in seconds.

Point-in-time recovery, 7 days of retention

A balance of cost and recovery options.

Point-in-time recovery, 1 day of retention

Basic protection. The data can be recreated from production.

Minimal, or snapshots only

Lowest cost. The data can be reproduced.

What the client received

  • A connection layer with pooling, retries, failover and read and write routing, and application code with no session state.
  • Database endpoints and pool sizes set per environment, so each environment can run a different database type.
  • A performance baseline recorded before each migration and compared after it.
  • RDS Proxy with 5 % pinned connections, and Lambda cold starts of 50 ms.
  • A documented migration path to RDS, Aurora Provisioned and Aurora Serverless, and a recovery level for each environment.