Serverless data archival moving cold data out of RDS
Two tables were taking in about 750K records every 6 hours, and the data stopped mattering after three days. A scheduled, per-client pipeline on Lambda, SQS and Fargate now moves everything older than 14 days to S3 as Parquet. Primary RDS storage went from 400 GB to 3 GB.
The problem
Two issuance tables did most of the damage. One received about 500K records every 6 hours, the other about 250K. The data is only relevant for about three days, but nothing ever removed it.
The database had grown to about 97 GB when I wrote the design, and primary storage reached 400 GB before archival took over. Queries were getting slower and RDS was running out of room.
How it works
EventBridge triggers a Lambda every 14 days. The Lambda reads each client's archival configuration from DynamoDB: which tables to archive, how many days to keep, and a flag to suspend a table. It then sends one SQS message per client, so clients are processed independently and in parallel.
Each message starts a Fargate task. The task extracts the rows that are past the cutoff, converts them to Parquet, compresses and encrypts the file, and uploads it to S3 under a path for that client, table and date. Only then does it delete those rows from RDS.
The cutoff is 14 days by default and can be set per table. The data stops being useful after three days, but we keep two weeks in the database anyway.
An S3 lifecycle policy moves archives to Glacier after three to six months. Nobody has to run anything by hand.
Choosing where the job runs
I compared three options for a job that runs for about 45 minutes per client, on 1 vCPU and 2 GB of memory, across 10 clients.
| Fargate | Fargate Spot | EKS with EC2 | |
|---|---|---|---|
| Price per hour | ~$0.05 | ~$0.02 | ~$0.04 |
| Monthly cost | $0.74 | $0.26 | $0.31 |
| Retry logic needed | No | Yes | No |
| DevOps effort | None | None | High |
| Scaling | Automatic | Automatic | Needs setup |
I went with Fargate. Spot was about a third of the price, but its tasks can be interrupted, so the job would have needed retry logic. EKS meant ongoing DevOps work. At well under a dollar a month, neither saving was worth what it cost.
Why Parquet
Parquet is columnar, so it compresses far better than CSV or JSON and is fast to read when you only need some columns. It works with Athena, Spark and Redshift, and it handles schema changes without rewriting old files.
The costs are real but small here. The files are not human-readable, and writing them takes more CPU than writing a plain format.
Why Glacier
Restores are rare, so storage price mattered more than retrieval speed. These were the per-GB prices when I wrote the design.
| Storage class | Per GB / month | Retrieval |
|---|---|---|
| S3 Standard | $0.023 | Instant |
| S3 Infrequent Access | $0.0125 | Instant |
| S3 Glacier | $0.004 | 1 minute to 12 hours |
| S3 Glacier Deep Archive | $0.00099 | 12 to 48 hours |
Glacier is about 83% cheaper than Standard. The price is paid at restore time: a retrieval has to be requested, it can take hours, and a fast one costs extra. Deep Archive stays open as a later step if the bill ever matters more than the wait.
Getting data back
Archived records stay reachable through DynamoDB and S3, with low-latency retrieval for historical lookups.
For a full restore, the Parquet file is retrieved, decrypted and reinserted into the database.
Result
Primary RDS storage went from 400 GB to 3 GB. Queries got faster and the infrastructure bill went down.
- When
- 2023 – 2025
- RDS storage
- 400 GB → 3 GB
- Inflow
- ~750K records / 6 hours
- Kept in RDS
- 14 days
- Runs
- Every 14 days, one task per client
- Estimated compute
- $0.74 / month at 10 clients