Skip to content

Latest commit

 

History

10 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Automatic replication after tagging for Amazon S3

Automates Amazon S3 replication for objects tagged after creation. S3 Replication evaluates tag-scoped rules only at object-creation time, so adding a tag to an object that already exists does not trigger it. This Solution runs on a recurring schedule, reads new tagging operations from the S3 Metadata journal, and submits S3 Batch Operations replication jobs for matched objects.

The Solution operates entirely on the source side. It never accesses the destination account or Region.

Use Cases covers two patterns: gating replication on a post-upload check such as a GuardDuty malware scan, and copying a selected dataset to another Region. Either can fan out to several destination buckets based on the tag's value. To apply a matching tag across a large set of objects that already exist, see Tagging Objects at Scale.

This is sample code. It is provided for demonstration and educational purposes, and is not intended for production use without your own testing, security review, and validation against your requirements and environment. Review Required AWS Permissions, the Hardening Options that are off by default, and the S3 Batch Operations cost implications of your check interval before deploying it into production.

Documentation

This README is the operator reference. Detail lives alongside it:

Document Covers
Deployment Guide Deployment walkthrough, the full parameter reference, IAM and KMS setup, multiple accounts and Regions
Use Cases The patterns this Solution suits, and how tag values fan out to several destinations
Tagging Objects at Scale Applying a matching tag across objects that already exist
Backfilling After a Replication-Rule Change The Athena query and Batch Operations recipe for a manual catch-up
Monitoring Reference Audit actions, metric semantics and alarm recipes, task-failure diagnosis
Completion Reporting Tracking mechanics, outcomes, and report group fields
Cost Detail A worked example, and what is excluded from it
Required AWS Permissions What each grant is for, the Batch Operations job role, Lake Formation
Hardening Options The three protections that are off by default
Customer-Managed KMS Keys All three KMS parameters, key-policy grants, verification
Testing The end-to-end test against real AWS resources

How It Works

One Lambda function, on a schedule, doing five things per source bucket:

  1. Read the bucket's replication configuration and keep the tag-scoped rules.
  2. Query the S3 Metadata journal through Athena for tagging operations since the last checkpoint.
  3. Match those objects against the tag filters, and drop versions that have been permanently deleted.
  4. Build one manifest in memory and submit one S3 Batch Operations replication job for the bucket.
  5. Advance the checkpoint, but only once the job is submitted.

S3 performs the replication itself, using the bucket's own rules and destinations. This Solution decides which object versions to hand it.

Prerequisites

Per source account and Region. Everything here is a condition on your account or your buckets; the IAM permissions you need to run the deployment itself are in deploy/README.md.

  • Versioning enabled on every source and destination bucket, as S3 replication requires regardless of this Solution.
  • An S3 replication configuration on each source bucket with at least one tag-scoped rule. Rules with no tag filter are ignored, since S3 already applies those at upload.
  • A replication role attached to each of those configurations, carrying the permissions S3 replication itself needs. The Solution requires nothing added to it: the stack creates its own role for the Batch Operations jobs it submits.
  • The S3 Metadata journal table enabled on each source bucket.
  • The S3 Tables analytics-services integration enabled in the Region, which creates the s3tablescatalog Glue catalog that Athena reads the journal through. One-time per account and Region, via S3 console → Table buckets → Enable integration. deploy/README.md has the aws glue create-catalog equivalent.
  • An active CloudTrail trail capturing management events in the Region, required only if you set AlarmEmail. Batch Operations job-status events reach EventBridge through CloudTrail, so without a trail those alerts never fire. Replication itself is unaffected.
  • Customer-managed KMS keys, optional. See Customer-Managed KMS Keys.
  • VPC connectivity, only when deploying the Lambda into a VPC. The subnets must route to S3, Athena, and CloudWatch (via gateway or interface VPC endpoints, or a NAT gateway). Leave the VPC parameters empty to deploy outside a VPC, which needs no additional networking setup.

Getting Started

Complete the Prerequisites, then per source account and Region:

  1. Download template.yaml, package-<version>.zip, and package-<version>.zip.sha256 from the project's Releases page.
  2. Verify the zip against its checksum: shasum -a 256 -c package-<version>.zip.sha256 on macOS, or sha256sum -c package-<version>.zip.sha256 on Linux.
  3. Upload the zip to an S3 bucket in the same Region as the stack, keeping the version in the key. Its S3 URI, for example s3://<your-code-bucket>/package-<version>.zip, is the CodeLocation parameter.
  4. Create the CloudFormation stack from template.yaml, set the parameters, and acknowledge CAPABILITY_IAM. Use the console, or the AWS CLI with the template staged in S3 (see deploy/README.md).

One stack covers one account and one Region, and every bucket in SourceBucketNames must be co-regional with it. Multiple Accounts and Regions covers what to repeat for each stack, how to aggregate metrics across them, and what to watch for when two stacks share an account and Region.

Parameters

Required (no default, you must provide a value):

CloudFormation parameter Description
CodeLocation S3 URI of the Lambda zip, e.g. s3://<your-code-bucket>/package-<version>.zip (bucket co-regional with the stack). Must change on upgrade (see Upgrading)
SourceBucketNames Comma-separated source bucket names to monitor. At least one required

The role that each S3 Batch Operations job runs as is not a parameter. The stack creates it, grants it s3:InitiateReplication on the buckets in SourceBucketNames plus manifest and completion-report access on the State Bucket, and publishes its ARN as the BatchOperationsRoleArn output. See Required AWS Permissions.

The decisions worth making before you deploy. Every other parameter has a default that works; the full reference covers scale tuning, encryption, VPC placement, and Lake Formation.

CloudFormation parameter Default Description
CheckFrequencyMinutes 15 How often the Solution runs, in minutes (15–1440). Because S3 Batch Operations charges per job, smaller values can raise cost, most of all when tagging activity is spread over time rather than arriving in a single batch
CompletionNotificationEmail (empty) Email address for completion email reports and report-missing alerts. The S3 Batch Operations completion report CSV is always written to the State Bucket; this parameter enables the SNS email and report-missing alert (see Completion Reporting)
AlarmEmail (empty) Email address for run-failure, Batch Operations job-failure, and bucket-disabled alerts. Leave empty to disable alerting; no SNS topic is provisioned (see Monitoring)
MetricsNamespace (empty) CloudWatch namespace to publish metrics under, any name you choose, e.g. S3ReplicateOnTag. CloudWatch creates it on first publish; names starting with AWS/ are reserved. Leave empty to disable metrics
KmsKeyArn (empty) Customer-managed KMS key ARN; leave empty for SSE-S3. See Customer-Managed KMS Keys
JournalReadRowCap 500,000 Max tagging operations processed in one interval, and the single scale knob bounding in-memory manifest size. Raise it only alongside LambdaMemoryMB (see Configuration)

The threshold parameters can also be changed on the Lambda function's environment variables without a stack update, which is useful for tuning against a live workload. Tuning without a stack update lists the variables and the one case where this needs care.

Configuration

The Solution is configured entirely through CloudFormation stack parameters. There is no file to author before deploying. On deploy, the stack writes an internal config object (solution-config.json) to the State Bucket that the Lambda reads at startup. Every value in it is derived from stack parameters and is overwritten on the next deploy, so there is no supported manual edit to it. Per-bucket runtime state, including a bucket's disabled flag, lives in that bucket's state object instead (see Monitoring).

Monitored buckets. The bucket list comes from the SourceBucketNames parameter. To add or remove a bucket, update SourceBucketNames and deploy a stack update. This is the only supported method. The execution role's per-bucket permissions, and in Lake Formation accounts the journal grants, are scoped to this parameter at deploy time, so adding a bucket by hand-editing the config object would leave it without journal or bucket access.

Replication rules. Tag filters, key prefixes, and destinations are read from each bucket's existing replication configuration, not set on this Solution. The Solution acts only on tag-scoped rules; rules without a tag filter, whether prefix-only or unfiltered, are ignored. A bucket may have multiple tag-scoped rules. All of a bucket's tag-scoped rules are evaluated together, and every matched object across every rule goes into one Batch Operations job per bucket per interval.

Journal read cap. JournalReadRowCap (default 500,000) caps how many journal rows one run reads, and is the Solution's single scale knob. The cap governs the whole read: both the new operations above the Solution's position in the journal and the JournalLookbackSeconds window below it that each run re-scans for late-arriving journal records. At least 20% of the cap is always reserved for new operations, so a run always makes progress and a burst larger than the cap drains across a bounded number of runs rather than stalling. No tagging operation is dropped, only delayed. A capped run reinvokes itself immediately rather than waiting for the next scheduled interval, so a temporary burst clears faster than the schedule alone allows.

A backlog large enough that the re-scan window alone would exceed 80% of the cap shortens the window this run: the Solution raises the lower bound of its read so the most recent part of the window is still re-scanned and the oldest part is skipped. This reduces tolerance for late-arriving journal records for as long as the backlog lasts. It emits an error log entry naming the row counts involved and publishes the JournalTailShortened metric, so it is visible rather than silent. See Monitoring.

The default suits most workloads. Its ceiling for each LambdaMemoryMB, what happens when a value exceeds that ceiling, and the sustained tagging rate the cap supports are in the deployment parameter reference.

Completion Reporting

Answers the question "did the S3 Batch Replication tasks succeed?" The Solution resolves each tracked object from the S3 Batch Operations completion report. S3 documents that a Batch Replication task's status depends on the object's replication status and its annotation replication status (S3 Batch Replication considerations).

Set CompletionNotificationEmail to receive one report per source bucket covering the outcomes resolved since the prior email:

my-bucket: 150 objects replicated successfully. No action needed. No replication jobs remain outstanding.

The email groups counts, outcomes, and time ranges by rule and destination. Object keys and version IDs are not included. Read the Batch Operations completion report CSV in the State Bucket under completion-reports/ for per-object results and failure details.

One completion-report row is the aggregate result for its task. For an object bound for multiple destinations, a failed row does not identify which destination failed. Use the S3 replication failure event or inspect the destination to diagnose a destination-specific failure. A successful row also does not show whether a replica remains present later: per AWS's S3 Batch Replication considerations, a destination version deleted by specifying its version ID is not re-replicated.

Leave CompletionNotificationEmail empty to disable completion email reports and report-missing alerts. The Batch Operations completion report CSV is written to the State Bucket either way, and the Solution still reads it to diagnose permission-shaped failures (InitiateReplicationNotPermitted, AccessDenied) in CloudWatch Logs. Per-object outcomes are only tracked when the email is set, so with it empty the report CSV in the State Bucket is the record of what replicated.

Completion Reporting has the outcomes an object can reach and the fields each report group carries.

Deleted-Version Filtering

Before writing any manifest, the Solution removes object versions that have been permanently deleted, since enough failed tasks will cause S3 Batch Operations to cancel the whole job.

  • Permanently deleted (DELETE with is_delete_marker = false, or lifecycle expiration): excluded from the manifest.
  • Delete marker placed (is_delete_marker = true): the tagged version still exists as a noncurrent version, so it is kept in the manifest.

A delete marker is never itself replicated. Two independent reasons, so this is not something a configuration change can alter.

A delete marker carries no tags. The journal records a null tag set for a DELETE record, so a delete marker cannot satisfy a tag filter and never becomes a candidate for a manifest. See S3 Metadata journal tables schema for the column behavior, and Working with delete markers for what a delete marker is.

S3 also does not support delete marker replication for tag-based rules at all. A rule whose Filter includes a Tag must set DeleteMarkerReplication to Disabled, and S3 rejects the configuration otherwise, so the tag-scoped rules this Solution reads could not replicate a delete marker even if one could be selected. See DeleteMarkerReplication.

The practical consequence: deleting an object in a source bucket is not propagated to any destination, by this Solution or by the rules it reads. The delete marker stays on the source side, and the replica that was already delivered remains. Propagating deletes needs a prefix-scoped or bucket-wide replication rule with delete marker replication enabled, which is outside what this Solution drives.

Objects That Are Not Replicated

S3 does not replicate objects in an archived storage class. They must be restored and copied to another storage class first. This is a property of S3 Replication, not of this Solution: see What isn't replicated with replication configurations?.

Tagging such an object matches a rule as normal, so the Solution handles the object rather than ignoring it.

Storage class or tier Replicated Behavior
GLACIER (S3 Glacier Flexible Retrieval) No Excluded before the manifest is written. No Batch Operations task is submitted and nothing is billed for it
DEEP_ARCHIVE (S3 Glacier Deep Archive) No Excluded before the manifest is written, as above
S3 Intelligent-Tiering Archive Access or Deep Archive Access tier No Submitted, then the task fails at S3 with SrcObjectNotEligible. The journal reports the storage class as INTELLIGENT_TIERING whatever tier the object occupies, so the Solution cannot tell this object apart from a replicable one
GLACIER_IR (S3 Glacier Instant Retrieval) Yes Replicated normally. Despite the name this is not an asynchronous class and needs no restore
Every other storage class Yes Replicated normally

An excluded object raises an archived_objects_excluded log entry and the ArchivedObjectsExcluded metric.

To replicate a restored object, copy it to a non-archived storage class and tag it again. Restoring alone does not cause another attempt, because the checkpoint has already advanced past the original tagging event.

A rejected task does not interfere with the object's lifecycle rules: S3 never initiates replication, so the object never acquires x-amz-replication-status and is never left PENDING or FAILED (either of which would block lifecycle transitions).

Repeat Tagging

Tagging an object again is a new tagging operation, whether or not the object has already been replicated.

Condition Behavior
An already-replicated object is tagged again and its new tags match a rule S3 replicates the tag change itself, with no involvement from this Solution: x-amz-replication-status on the source object returns to PENDING on the tagging call, and reaches COMPLETED again once the new tag set is on the replica. The version separately enters the next Batch Operations job and is billed for one manifest entry. If the destination already holds that version, S3 Replication treats it as delivered: no transfer and no destination-side charge
Two matching tag states on one object version inside one interval One manifest entry. The job evaluates the object's live tags, so a destination selected only by the superseded tag state is not reached: not by this run, and not by any later run, because the tag that selected it no longer exists
Two or more versions of the same key tagged inside one interval One version per run, oldest tagging event first, until all are replicated. Each of those runs submits a job and is billed for its manifest entries. Versions still waiting must stay within JournalLookbackSeconds of the checkpoint, which they do unless newer tagging activity advances the watermark past them
An object version permanently deleted at the destination Not restored, by this or any other job (see Completion Reporting)

Bounded Concurrent Jobs per Bucket

MaxConcurrentJobsPerBucket (default 3, minimum 1, maximum 10) caps how many Batch Operations jobs may be outstanding at once for one source bucket. A job is outstanding until it reaches a terminal status: Complete, Failed, or Cancelled. At the limit, the bucket is skipped for that run.

At most one job is submitted per bucket per run, whatever the limit is set to. The limit bounds how many jobs may be outstanding, not how fast they are submitted.

Nothing is lost by a skip. The run returns before querying the journal, so no Athena charge is incurred, and because nothing is submitted the checkpoint does not advance and no operation enters the deduplication window. Every tagging event that was waiting stays waiting and is picked up whole once a job finishes.

The limit exists because a job's duration is set by replication throughput, not by how many objects it covers. A bucket of large objects is bandwidth-bound, so its job can run for far longer than CheckFrequencyMinutes. For scale, 50 GiB across 5,400 objects took just over five minutes to replicate cross-Region, which puts a multi-terabyte job into hours and a multi-hundred-terabyte one into days. Without a cap, such a bucket would accumulate jobs against an account-level Batch Operations quota, each carrying its own per-job charge.

Condition Behavior
Fewer outstanding jobs than the limit Normal run: the journal is read and a job is submitted if anything matched, alongside any jobs still running
Outstanding jobs at or above the limit Bucket skipped. An audit entry submission_deferred_job_in_flight records the outstanding count, the limit, and the oldest outstanding job's ID, status, and age. The SubmissionDeferred metric is published for the bucket
A job whose status cannot be read Counted as outstanding, because assuming it finished could admit a job that should have waited
A job whose status has been unreadable for 14 days No longer counted, so the bucket can submit again. An error names the job and the likely causes. Its record is kept, so the report-missing check still covers it
No outstanding jobs Normal run

A skip is per bucket. Other buckets in the same run are unaffected.

If a skip is held up entirely by jobs whose status could not be read, the run logs an error rather than only the audit entry. Nothing is working as intended in that case: the usual cause is the execution role losing s3:DescribeJob, or a job ID in the state object that this account does not own. It clears itself once the calls succeed, and at 14 days regardless, but you should not have to infer it from a run of SubmissionDeferred datapoints.

Setting the limit to 1 serializes the bucket: one job at a time, each new job waiting for the previous one. That is a legitimate choice, and it is why the minimum is 1 rather than 2. It costs throughput, because one job already covers every matched object across all of the bucket's tag-scoped rules and finishes only when all its tasks do, so serializing extends that wait across batches.

A few consecutive skips mean a job simply outlasted an interval. A long unbroken run of them means the bucket's replication throughput is what limits how quickly tagging is acted on, not this Solution. Alarm on SubmissionDeferred if you want to be told; treat missing data as not breaching, because the metric is published only when a skip happens.

Raising the limit raises the ceiling on concurrent per-job charges for that bucket. See Cost.

Journal Start Point

The Solution begins processing journal records from the time the stack is deployed, not from the beginning of the journal. On stack creation, the custom resource writes an initial checkpoint per source bucket with the watermark set to the deployment timestamp. The first Lambda invocation reads from deployment_timestamp - JournalLookbackSeconds forward.

That start point reaches slightly into the past, which cuts both ways:

Tagged Replicated by the scheduled runs
After deployment Yes
Within JournalLookbackSeconds before deployment (2 hours by default) Yes, by the first run. If you tagged objects while preparing to deploy, expect the first run to replicate them
Earlier than that No, and nothing retries or alerts. Use the manual catch-up recipe in Backfilling After a Replication-Rule Change

The first run therefore submits a Batch Operations job as soon as it finds matching tagging activity in that period, and you are billed for that job and its manifest entries. Deploy into a quiet period if you want the first run to be a no-op.

When a source bucket is added via stack update, it receives its own checkpoint at the update timestamp, and the same two-hour reach-back applies to it. Existing buckets are not affected.

Backfilling After a Replication-Rule Change

Adding or widening a tag-scoped rule does not replicate objects tagged before the change. Backfilling After a Replication-Rule Change has the Athena query and Batch Operations recipe for a manual catch-up.

Monitoring

Tells you whether the Solution is running, keeping up, and still covering every bucket you gave it. Each run emits a structured JSON interval_summary, plus an entry for anything that needed a decision or went wrong. Object keys are never logged; error messages reference a SHA-256 fingerprint instead. A per-bucket error is logged and the run continues for the remaining buckets, so one bad bucket does not stop the others.

Log entries lists every event type and the fields it carries, and Audit actions does the same for each audit action.

Auto-disable. When consecutive Batch Operations job failures for a bucket reach MaxBatchJobFailures (default 4), the Solution sets that bucket's disabled flag to true in its state object and clears its stored failure history, in a single write. The other buckets keep running. This circuit breaker prevents runaway per-job costs from a bucket whose job keeps failing. When AlarmEmail is set, an email names the disabled bucket and the recovery step.

The flag is held in the bucket's own state object rather than in solution-config.json because a stack update rewrites that config object wholesale from the template parameters. A disable recorded there would be cleared by any later deploy, resuming billable job submission for a bucket nobody had decided to re-enable. Nothing a deploy does touches a state object that already exists.

The same threshold applies to permanent submission failures, where create_job is rejected by botocore's own parameter validation before the request is sent. Such a request fails identically on every retry, unlike a terminal-job failure, which may be transient, so it needs a change deployed rather than another interval. Service-side errors such as throttling and permission issues do not count toward the threshold.

To re-enable a bucket, address the cause of the job failures, then set "disabled": false in its state object on the State Bucket (s3://<state-bucket>/state/<bucket-name>.json) and wait for the next scheduled run. Removing the disabled key has the same effect. No redeploy is needed, and no other edit: the same write that set the flag also cleared the job history that tripped the breaker, so the first run after re-enabling starts from a clean counter. A bucket disabled by a rejected request needs a code fix deployed first, or the failure reproduces.

Leave the rest of the state object alone while making that edit. Checkpoint and Recovery covers what else it holds and why deleting it is not a way to reset a bucket.

CloudWatch metrics

Set MetricsNamespace and the Solution publishes eight per-bucket and run-level counters after each run: TaggingOperationsRead, MatchedObjects, BatchJobsSubmitted, ArchivedObjectsExcluded, SubmissionDeferred, JournalTailShortened, BucketErrors, and DisabledBuckets. Leave it empty to disable this entirely; no CloudWatch permission is then required.

Some are withheld for an idle bucket rather than published as a zero, to avoid paying for a flat series (see CloudWatch metric charges), so a missing data point carries meaning. Monitoring Reference has each metric's dimension and publish condition, followed by the alarm recipes that depend on them, including how to catch a bucket that silently stopped being processed.

Run failure alerts

A ReplicationLambdaErrorAlarm alarm covers the run itself failing, as opposed to a job failing after a run submitted it. It watches the function's CloudWatch Errors metric, so it catches an unhandled exception, a timeout, and an init failure such as a code package with the wrong layout. While it is in alarm, no tagging activity is being processed at all.

The alarm is always created, and emails the AlarmEmail address when one is set, including when the runs recover. It reads the native Lambda metric rather than the Solution's own log entries, because an init failure produces none: the module never imports. Its ARN is the ReplicationLambdaErrorAlarmArn output.

Batch job failure alerts

Set AlarmEmail to be notified when an S3 Batch Operations job fails or is cancelled. The stack creates an SNS topic, an email subscription, and an EventBridge rule that sends one readable email per failed or cancelled job, carrying the job ID, its status, and a console link. This requires an active CloudTrail trail capturing management events in the stack's Region (see Verifying the CloudTrail trail). Leave AlarmEmail empty to disable alerting; no SNS topic is provisioned. A CloudWatch alarm on the same event exists for console and dashboard visibility and does not send its own email.

Four other alerts go to the same address. The run failure alarm above is one. The bucket-disabled notification names the bucket, the cause, and the exact recovery step. The submission-failure alert names the bucket, the operation (CreateJob), and the validation error; it fires once per episode and is suppressed while the same failure persists, and a successful submission clears the suppression so a recurrence after a fix is reported again. The journal-unavailable alert names the bucket and the remedy when its S3 Metadata journal does not exist (see A bucket replicates nothing and raises no alarm); it repeats at most once a day while the journal is still missing. All are always written to the BatchJobFailureLogGroup CloudWatch log group even when AlarmEmail is not set.

Diagnosing task failures

A Batch Operations job can reach Complete while individual tasks in it failed, and the job's status does not reveal that. Every task failure in a job's completion report raises an error log entry naming the error code, how many tasks carried it, and the message S3 reported for it.

Diagnosing task failures covers the two permission-shaped failures, why the reported message matters more than the error code, and the two log signals that catch a job whose tasks all failed at either job size.

A bucket replicates nothing and raises no alarm

A bucket the Solution cannot read the journal for is skipped, and the run still succeeds for the remaining buckets. Because the run succeeds, the Lambda Errors metric stays at zero and ReplicationLambdaErrorAlarm does not fire. The most common cause is the S3 Metadata journal not being enabled on that bucket, or the Region's S3 Tables integration never having been registered, both listed under Prerequisites.

The Solution reads the journal and does not create it, so this condition persists until you act on it.

Signal Where Frequency
journal_unavailable audit entry The function's log group Every run
BucketErrors at 1 for that bucket CloudWatch, when MetricsNamespace is set Every run
journal_unavailable entry naming the remedy BatchJobFailureLogGroup At most once a day per bucket
Email naming the bucket and the remedy AlarmEmail, when set At most once a day per bucket

The audit entry is written every run, so the condition is always queryable. The notification is limited to once a day so an unmet prerequisite stays visible without arriving every CheckFrequencyMinutes, and it stops as soon as the journal is readable.

To resolve, enable the journal on the bucket (S3 console: select the bucket, Metadata configuration, Create metadata configuration), then confirm the s3tablescatalog Glue catalog exists in the Region. The bucket resumes on the next scheduled run. It is not disabled and its checkpoint has not moved, so no state edit is part of the recovery, and no tagging operation is lost.

A journal read that fails for any other reason, including throttling and a missing Lake Formation grant, raises an error entry instead. Those are not reported as a missing journal, because enabling one that already exists would not fix them.

Checkpoint and Recovery

A run that fails part way through does not skip the objects it was working on. Each bucket's progress advances only once its Batch Operations job has been submitted, so a run that dies before that point leaves the checkpoint where it was and the next run covers the same tagging operations again. The checkpoint lives in the State Bucket at state/<bucket-name>.json.

Do not delete the state object to replay history. It holds the lease, the processed-operation window, submission records, the bucket's disabled flag, and all completion tracking alongside the watermark, so deleting it resets the bucket to the beginning of the journal and every object in that history enters the next manifest. Batch Operations bills every manifest entry, so on a bucket with a long journal that is an unbounded charge. It also discards completion tracking for objects still in flight, disables failed-job recovery, and silently re-enables the bucket if it was disabled. To replicate objects tagged before the deployment, use Backfilling After a Replication-Rule Change instead.

Two fields are edited by hand, one field at a time, leaving everything else in the object untouched:

Field When to edit it
disabled Set to false to re-enable a bucket after an auto-disable (see Monitoring)
last_processed_watermark Only to repair the case below

If last_processed_watermark is set to a value more than 24 hours in the future, the Solution rejects it as implausible and skips that bucket every run, logging Failed to read checkpoint. No run can repair it, because every write happens after the read that fails. Edit that one field to a plausible value. The disabled flag is still read in that state, so a corrupted watermark cannot quietly re-enable a disabled bucket. A stale lease needs no intervention: the next run takes it over, and a lease carrying an implausible watermark is discarded automatically with a lease_discarded audit entry.

Cost

This Solution uses only pay-per-use services. There are no fixed or idle charges. Cost scales with how many objects are tagged, how often the Solution runs, and how many source buckets are monitored.

Component Driver
S3 Batch Operations Per-job plus per-object charge. One job per source bucket per run that has matches. Usually the dominant cost.
Amazon Athena Per-TB scanned, with a 10 MB per-query minimum. The journal is an Apache Iceberg table, so the record_timestamp > <checkpoint> predicate lets Athena skip data files that fall entirely below the checkpoint, keeping scanned volume roughly proportional to new activity rather than total journal size.
AWS Lambda Per invocation and GB-second. One invocation per run, scaled by LambdaMemoryMB.
Amazon S3 (State Bucket) Storage for manifests, Athena results, and completion reports, expired after LifecycleExpirationDays, plus request charges. Completion outcomes are resolved by reading those reports from this bucket.
Amazon CloudWatch (optional) Only when MetricsNamespace is set: per custom metric per month, billed per unique metric-and-dimension combination. Log ingestion for the structured JSON logs applies regardless.
Amazon SNS (optional) Per-notification charges when CompletionNotificationEmail or AlarmEmail is set.
AWS KMS (optional) Per-request charges when KmsKeyArn or JournalKmsKeyArn is set.

The biggest lever is CheckFrequencyMinutes. A job is submitted only on runs that find matches, so shorter intervals raise cost most when tagging activity is spread over time, and least when it arrives in a single batch. Choose the largest interval that meets your replication-latency needs.

Rule count does not affect cost. There is one job and one journal query per bucket per run, covering every matched object across all of that bucket's tag-scoped rules.

For one source bucket tagging 10,000 objects a day, the worked example in Cost Detail comes to roughly $13 a month checking hourly and $14 checking every 15 minutes. That page also lists what is excluded, chiefly S3 Replication and journal-table charges.

Hardening Options

State Bucket access logging, State Bucket versioning, and SNS topic encryption are off by default to keep deployment simple. Hardening Options covers what to change for each. Customer-Managed KMS Keys covers all three KMS parameters, key-policy grants, and the verification procedure.

Required AWS Permissions

The execution role holds source-side actions only. deploy/iam-policy.json is the full least-privilege policy.

The stack also creates the role its S3 Batch Operations jobs run as, separate from the execution role and from your buckets' replication roles.

Required AWS Permissions explains what each grant is for, the Batch Operations job role, and the optional customer-managed KMS grants.

Upgrading

Upgrade by repeating the Getting Started steps with the new release's assets and updating the stack.

Check CHANGELOG.md for the release you are moving to first. A release that removes a stack parameter requires dropping that parameter from your update command, because CloudFormation rejects a parameter the template does not declare.

CodeLocation must change for the Lambda code to be replaced. Give each release its own S3 key, which the versioned asset filename does for you if you keep the version in the key.

Overwriting one fixed key leaves CodeLocation unchanged. CloudFormation replaces the function code only when that parameter changes, so the stack applies the new template while the Lambda keeps running the previous release's code, and reports UPDATE_COMPLETE either way. The result is a new template against old code, with no error to indicate it.

Development

pip install -e ".[dev]" installs the package with its test dependencies; pytest runs the unit suite.

Testing covers the end-to-end test against real AWS resources, its environment variables, and the manual verification procedures.

Author

Ed Gummett, Storage Specialist Solutions Architect, AWS — Connect on LinkedIn

About

A source-side Solution that reads tagging operations from the Amazon S3 Metadata journal and replicates newly tagged objects using S3 Batch Replication

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

1 star

Watchers

1 watching

Forks

Releases

Used by

Contributors

Languages