Tiering Archived Binlogs with S3 Lifecycle Rules
The lifecycle rule that saves the most money is also the one most likely to delete your recovery capability, and the two failure modes are subtle: a Transition that pushes the recovery window into Glacier adds hours to RTO, and an Expiration that fires inside the recovery window silently amputates the chain. This page writes S3 lifecycle rules that tier archived binlogs down for cost while keeping every segment the recovery window needs both present and fast, using precise prefix scoping, a guarded expiration that stays behind the base-backup overlap, and explicit noncurrent-version handling so a versioned bucket does not quietly retain a second copy of everything. It is the concrete implementation of the policy in Object Storage Lifecycle & Cost Management for Archived Binlogs.
Context & Prerequisites
This implements the guarded tiering from Object Storage Lifecycle & Cost Management; read that for the recovery-horizon inequality this must not violate. You need an S3 bucket holding the archive laid out per AWS S3 & GCS Sync Pipelines, Python 3.10+ with boto3, and IAM permission for s3:PutLifecycleConfiguration. The expiry days must exceed your base-backup overlap window from Base Backup Integration for PITR.
Step-by-Step Implementation
1. Scope the rule to the segment prefix only
Filter the rule to the segments/ prefix so it never touches manifests or metadata.
{
"Rules": [{
"ID": "binlog-segments-tiering",
"Filter": { "Prefix": "segments/" },
"Status": "Enabled",
"Transitions": [
{ "Days": 30, "StorageClass": "STANDARD_IA" },
{ "Days": 90, "StorageClass": "GLACIER" }
],
"Expiration": { "Days": 400 }
}]
}PITR relevance: scoping to segments/ keeps the manifest — the index a recovery reads first — permanently in fast storage, so resolving a recovery plan is never delayed by a cold retrieval.
2. Set expiry behind the base-backup overlap, with margin
Choose Expiration.Days strictly greater than the oldest base backup you honor plus a safety margin, so no expiry can outrun the chain.
# Python 3.10+
def safe_expiry_days(oldest_base_backup_age_days: int, margin_days: int = 30) -> int:
# Expiry must never fire before the oldest recoverable backup + margin.
return oldest_base_backup_age_days + margin_daysPITR relevance: deriving expiry from the actual backup horizon — not a round number — is what guarantees the archive always overlaps every recoverable base backup.
3. Keep the recovery window in a fast tier
Ensure the Days before the first Transition exceeds your RTO-scoped recovery window, so no segment you might replay under RTO lives in Glacier.
# Python 3.10+
def first_transition_days(recovery_window_days: int, rto_buffer_days: int = 3) -> int:
return recovery_window_days + rto_buffer_daysPITR relevance: a recovery that must fetch a Glacier segment waits minutes to hours; keeping the whole RTO window in Standard/IA keeps retrieval off the critical path.
4. Handle noncurrent versions on versioned buckets
If the bucket is versioned, add a NoncurrentVersionExpiration so superseded objects (e.g. a re-archived truncated segment) do not accumulate cost forever.
{
"ID": "binlog-noncurrent-cleanup",
"Filter": { "Prefix": "segments/" },
"Status": "Enabled",
"NoncurrentVersionExpiration": { "NoncurrentDays": 30 }
}PITR relevance: the current version is always the authoritative segment; noncurrent versions are superseded copies safe to expire after a short retention, and leaving them unmanaged doubles storage silently.
Configuration Snippet & Reference Table
# Python 3.10+ — apply the lifecycle configuration
import boto3, json
def put_lifecycle(bucket: str, rules: dict) -> None:
boto3.client("s3").put_bucket_lifecycle_configuration(
Bucket=bucket, LifecycleConfiguration=rules)| Rule element | Value | Why it matters for PITR |
|---|---|---|
Filter.Prefix | segments/ | Keeps manifests in fast storage. |
first Transition.Days | > RTO window | Recovery window stays fast. |
GLACIER transition | compliance tail | Cheap retention beyond RTO scope. |
Expiration.Days | > base-backup overlap + margin | Never amputates the chain. |
NoncurrentVersionExpiration | short (e.g. 30d) | Stops superseded copies accruing cost. |
Verification Checklist
Gotchas & Version-Specific Caveats
Expiration is asynchronous and silent. S3 may take up to 48 hours after the expiry threshold to delete, and it emits no failure — so an over-aggressive rule removes objects with no error. Only manifest reconciliation catches the resulting gap.
Glacier retrieval is not instant. A GLACIER (or Deep Archive) object requires a restore request and wait before it can be read; never place the RTO-scoped window there.
Lifecycle applies to current versions by default. On a versioned bucket, Expiration and Transition act on current versions; noncurrent copies need their own NoncurrentVersion* actions or they linger.
Object Lock can block expiry. If segments are under a retention lock for compliance, expiry cannot remove them until the lock elapses — intended, but budget for the storage until then.
Frequently Asked Questions
How do I pick the expiry days without risking the chain?
Derive it, do not guess it. Take the age of the oldest base backup you intend to recover from, add a safety margin (a week or a month), and set Expiration.Days to that. Because the number tracks your actual backup retention, the archive always overlaps every recoverable backup. Re-derive it whenever backup retention changes, and let manifest reconciliation catch any drift.
Should the manifest be tiered too?
No. Keep the manifest in Standard storage permanently. It is tiny relative to the segments and it is the first thing a recovery reads to resolve which objects to fetch — putting it in a cold tier would add retrieval latency to every recovery for negligible savings. Scope lifecycle rules to the segments/ prefix so the manifest is untouched.
Related
- Object Storage Lifecycle & Cost Management — the policy this configuration implements.
- AWS S3 & GCS Sync Pipelines — the bucket layout the prefix scoping relies on.
- S3 vs GCS Sync Pipeline Trade-offs for Binlog Archiving — how the same tiering maps onto GCS lifecycle management.