Skip to content
Paso a paso Dmitrii Fomichev Notes
  • Notes
  • What I do
  • Problems
  • Stack
  • Domains
  • Contact
Back to Engineering Notes

Engineering Notes

Reducing Amazon RDS Storage from 200 GiB to 40 GiB with Blue/Green

Published 14 July 2026

  • AWS
  • PostgreSQL
  • RDS
  • Production

A production PostgreSQL database had 200 GiB of allocated Amazon RDS storage but was using only around 20 GiB.

The database had grown into this configuration over time, but the allocated capacity was now far beyond its real needs. Keeping it was not causing an immediate technical failure, but it meant paying for unused storage and carrying an unnecessarily oversized configuration indefinitely.

The goal looked simple:

Reduce the allocated storage from 200 GiB to 40 GiB without putting production data at unnecessary risk.

The implementation was less simple. This was a live database used by several services, so rebuilding it casually or accepting a long maintenance window was not reasonable.

The constraint

The existing RDS instance could not simply be edited from 200 GiB down to 40 GiB through the normal storage modification flow.

Increasing storage is straightforward. Reducing it requires creating a replacement environment with the desired storage configuration and then moving production to it.

RDS Blue/Green provided that migration path: it created a separately configurable replacement database, kept it synchronized with production, and provided a managed switchover once the new environment was ready.

The main options were:

  1. Export and restore the database into a new instance.
  2. Use logical replication between two independently created databases.
  3. Create an RDS Blue/Green deployment and perform a controlled switchover.

A manual export and restore would have been easy to understand, but it would also have introduced a longer period during which writes needed to be stopped or synchronized separately.

Building our own logical replication process would have provided more control, but also more moving parts to configure, test and later remove.

For this database, the Blue/Green approach offered the best balance between control and operational simplicity.

The migration shape

The process looked approximately like this:

Existing production database
PostgreSQL, 200 GiB
          |
          | continuous replication
          v
Green environment
PostgreSQL, 40 GiB
Autoscaling enabled up to 150 GiB
          |
          | validation and controlled switchover
          v
New production database
PostgreSQL, 40 GiB

The original database remained the production system while the green environment was created and synchronized.

That separation was important. It allowed us to prepare and inspect the new environment without changing the database currently serving the application.

Choosing the target size

The database was using approximately 20 GiB, but I did not set the replacement storage to exactly that amount.

A database needs room for:

  • future data growth;
  • temporary operations;
  • indexes;
  • transaction logs;
  • maintenance activity;
  • unexpected increases in usage.

We selected 40 GiB as the new initial allocation. This removed most of the unused capacity while leaving reasonable headroom.

Storage autoscaling was also enabled with a maximum of 150 GiB.

This meant the database could grow automatically if needed, but it would not immediately return to the previous oversized allocation.

The objective was not to find the smallest possible number. It was to create a more sensible starting point with a controlled growth path.

Creating the green environment

I created an RDS Blue/Green deployment from the production database and configured the green database with the new storage settings.

During this stage, the original database continued receiving production traffic. The green environment remained isolated from application writes and received changes through replication.

Creating the environment was only the beginning. A replacement database being marked as available does not mean it is ready to become production.

The important part was validating its state.

Watching replication

Replication lag was not immediately zero. At one point it was approximately 58 seconds.

That was not itself a failure. It meant that the green environment was still catching up with production.

The important questions were:

  • Was replication still progressing?
  • Was the lag stable, increasing or decreasing?
  • Were there any replication errors?
  • Was the green database receiving recent changes?
  • Would switching at this point lose or delay expected data?

We waited rather than treating the existence of the environment as proof that it was ready.

This is a small operational detail, but it is also where many risky migrations go wrong. Infrastructure creation is visible and satisfying. Waiting for synchronization and checking boring details feels less productive, but it is usually the more important work.

Validating the new database

Before switchover, I checked the new environment from several directions.

The checks included:

  • confirming the expected PostgreSQL version and configuration;
  • checking the allocated storage and autoscaling limit;
  • verifying that replication was healthy;
  • comparing row counts in important tables;
  • checking known production records;
  • confirming that recent changes appeared in the green database;
  • checking application connectivity requirements;
  • reviewing monitoring and security configuration.

One of the largest tables was a receipt-items datamart containing a very large number of rows. Comparing every row individually would have been slow and difficult to interpret, while one hash for the entire table would have hidden where a mismatch occurred.

Instead, the data was validated in stable partitions, such as by business date and restaurant. For each partition, I compared the row count, the latest source timestamp and a deterministic checksum generated from selected business columns in a stable order.

Conceptually, the result looked like this:

Partition             Blue rows   Green rows   Blue checksum   Green checksum   Result
--------------------------------------------------------------------------------------
2026-06-01 / 013       184,291     184,291      8f2a...          8f2a...          OK
2026-06-02 / 013       176,804     176,804      31bd...          31bd...          OK
2026-06-03 / 013       191,442     191,442      b782...          b782...          OK

Partitioning made failures easier to locate and avoided treating one enormous checksum as sufficient evidence. The checksum also needed deterministic serialization and ordering; otherwise two equivalent datasets could produce different results simply because rows were read in a different order.

These checks did not prove that every possible application path was correct, but they gave strong evidence that the large analytical dataset had been copied completely and consistently.

The validation list was written down before the switchover. That reduced the chance of forgetting a check while concentrating on the live migration.

Switchover

Once replication was sufficiently caught up and the validation checks passed, I initiated the Blue/Green switchover.

We treated this as a real production change, not as a harmless infrastructure action.

The application and database were monitored during and after the switch. The immediate checks included:

  • database availability;
  • application health;
  • authentication and user access;
  • recent writes;
  • background processing;
  • error logs;
  • database connections;
  • storage configuration.

After the switchover, the production database retained its expected identity from the application’s perspective, but it was now backed by the new 40 GiB environment.

The previous production instance became the old blue environment.

Cleanup without removing the safety net too early

A successful switchover did not mean the migration was finished.

The new production environment was observed and validated again before the old instance was removed. The old database was then deleted, but a final snapshot was retained.

This provided a recovery option without continuing to pay for a complete unused database instance.

The remaining Blue/Green deployment metadata was also cleaned up after confirming that it was no longer needed.

The final state was:

Production storage:       40 GiB
Storage autoscaling:      enabled
Maximum storage:          150 GiB
Old database instance:    removed
Final snapshot:           retained

The process stayed safe for three main reasons:

  • production remained authoritative until the controlled switchover;
  • readiness was checked using both replication health and application-level data validation;
  • the old environment was retained until the new production state had been confirmed.

What I would do differently

The migration succeeded, but I would automate more of the validation next time.

A reusable script could run the same checks against both environments, calculate partitioned checksums for the largest tables, and save a concise comparison report as part of the change record.

For example:

Check                              Blue        Green       Result
-----------------------------------------------------------------
Active users                       53          53          OK
Latest receipt timestamp           19:42:11    19:42:11    OK
Receipt partitions checked         30          30          OK
Mismatched partition checksums      0           0          OK
Orphan reference check              0           0          OK

This would make the decision to switch more repeatable and reduce manual error.

I would also prepare a written rollback decision before starting. Not only “we can roll back,” but:

  • which symptoms require rollback;
  • who makes the decision;
  • how long we observe before deciding;
  • which data written after switchover must be considered.

Finally, I would add a periodic check comparing allocated storage with actual usage. The original problem developed gradually. A simple capacity review could have identified it earlier.

The main lesson

The interesting part of this change was not reducing a number from 200 to 40.

It was replacing a production database while preserving confidence in the result.

Managed cloud services remove a large amount of operational work, but they do not remove the need for an operational process. The migration still required understanding the platform constraint, observing synchronization, validating business data, planning the switchover and retaining a recovery path.

The final database was smaller and less expensive, but the more important result was that the change was controlled and explainable.

© 2026 Dmitrii Fomichev · Autónomo in Spain

  • Legal Notice
  • Privacy Policy
  • Cookie Policy