<?xml version="1.0" encoding="UTF-8" standalone="no"?><rss xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/" xmlns:sy="http://purl.org/rss/1.0/modules/syndication/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" version="2.0">

<channel>
	<title>AWS Big Data Blog</title>
	<atom:link href="https://aws.amazon.com/blogs/big-data/feed/" rel="self" type="application/rss+xml"/>
	<link>https://aws.amazon.com/blogs/big-data/</link>
	<description>Official Big Data Blog of Amazon Web Services</description>
	<lastBuildDate>Wed, 14 Oct 2026 12:55:33 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	
	<item>
		<title>Resolving concurrent write conflicts for Apache Iceberg tables in Amazon Redshift</title>
		<link>https://aws.amazon.com/blogs/big-data/resolving-concurrent-write-conflicts-for-apache-iceberg-tables-in-amazon-redshift/</link>
		
		<dc:creator><![CDATA[Sanjay Devireddy]]></dc:creator>
		<pubDate>Tue, 13 Oct 2026 20:16:48 +0000</pubDate>
				<category><![CDATA[Amazon Redshift]]></category>
		<category><![CDATA[Announcements]]></category>
		<category><![CDATA[Foundational (100)]]></category>
		<guid isPermaLink="false">75eba7619d8e8a1359b5d09312cf87421f725f68</guid>

					<description>Amazon Redshift now automatically resolves many concurrent writes to Apache Iceberg tables that don't actually overlap, committing changes that previously failed and had to retry. Learn why these writes conflict, how the engine rebases non-overlapping ones across operation, partition, and file checks, and the throughput gains it delivers.</description>
										<content:encoded>&lt;p&gt;In &lt;a href="https://aws.amazon.com/blogs/big-data/getting-started-with-apache-iceberg-write-support-in-amazon-redshift-part-1/" target="_blank" rel="noopener"&gt;Getting started with Apache Iceberg write support in Amazon Redshift – Part 1&lt;/a&gt;, you learned how to create Apache Iceberg tables and write to them with &lt;code&gt;INSERT&lt;/code&gt;. In &lt;a href="https://aws.amazon.com/blogs/big-data/getting-started-with-apache-iceberg-write-support-in-amazon-redshift-part-2/" target="_blank" rel="noopener"&gt;Part 2&lt;/a&gt;, you modified data at the row level with &lt;code&gt;DELETE&lt;/code&gt;, &lt;code&gt;UPDATE&lt;/code&gt;, and &lt;code&gt;MERGE&lt;/code&gt;. Both posts assumed your write had the table to itself. In production, that is rarely true: streaming jobs, scheduled pipelines, and one-time transformations frequently write to the same table at the same time. Amazon Redshift now resolves many of these concurrent writes automatically, committing changes that previously failed and were forced to retry. In this post, we explain why concurrent writes to Iceberg tables conflict, how Amazon Redshift resolves the conflicts that don’t actually overlap, how the engine escalates its checks from operations to partitions to individual files, and the throughput improvement it provides.&lt;/p&gt;
&lt;h2 id="the-challenge-of-concurrent-writes"&gt;The challenge of concurrent writes&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://iceberg.apache.org/docs/1.6.0/reliability/#concurrent-write-operations" target="_blank" rel="noopener"&gt;Apache Iceberg uses optimistic concurrency control (OCC)&lt;/a&gt; to keep a table consistent when more than one writer commits at the same time. Each writer prepares its changes against the table’s current version and commits by atomically advancing the table version in the catalog, on the condition that no other writer advanced it first. If another writer commits in the meantime, the condition fails and the entire write is discarded. This strictness maintains snapshot isolation semantics: if the later commit were applied anyway, it would replace the table version without the earlier writer’s changes, silently losing a committed write.&lt;/p&gt;
&lt;p&gt;Readers don’t encounter this problem. An Iceberg scan always reads a committed snapshot, so a read is never invalidated by a concurrent write. That leaves write-write conflicts as the only case the engine must resolve, and resolving them correctly is what preserves snapshot isolation semantics for the table.&lt;/p&gt;
&lt;p&gt;The problem is the bluntness of this check. The condition is evaluated against the table version, not against the rows or partitions each writer touched. At that grain, its answer is correct in that the version really did change. However, it says nothing about whether the two writes overlapped in the data underneath. Two writers that modified entirely separate partitions, or that independently inserted new data, conflict exactly as hard as two that rewrote the same rows. This happens whenever their query-start-to-commit windows overlap.&lt;/p&gt;
&lt;p&gt;Consider a retail analytics platform with a &lt;code&gt;customer_orders&lt;/code&gt; Iceberg table. A streaming pipeline might land new orders continuously with hundreds of inserts a minute. A nightly compliance job deletes records older than seven years, and takes about an hour to run. The two writes never touch the same data: the stream only adds recent orders, and the compliance job only removes old ones. Under basic optimistic concurrency, that doesn’t matter. If a single streaming insert commits at any point during that hour, the compliance job loses the race, its hour of work is discarded, and the next attempt re-scans terabytes of data only to re-enter the same race. The table version conflict was real even though the data conflict was not.&lt;/p&gt;
&lt;p&gt;The common ways to avoid this are all manual, and each carries a cost. You can serialize your writers so that only one commits at a time, which gives up concurrency entirely. You can split a single logical table into several physical tables so that no two writers ever share one. This trades a clean data model and the ability to query across them for the ability to write in parallel. Or you can wrap every write in an application-side retry loop and absorb the cost of re-running statements that never actually conflicted. In each case, the effort goes toward working around the commit protocol rather than toward your data.&lt;/p&gt;
&lt;h2 id="how-amazon-redshift-resolves-write-conflicts"&gt;How Amazon Redshift resolves write conflicts&lt;/h2&gt;
&lt;p&gt;When a commit loses the race, Amazon Redshift now asks a more precise question before giving up: Did the writers that committed ahead of it actually change the same partitions or files or rows it changed? When the answer is no, there’s no reason to discard the work. Instead of aborting, Amazon Redshift rebases the change (similar to &lt;a href="https://git-scm.com/docs/git-rebase" target="_blank" rel="noopener"&gt;git rebase&lt;/a&gt;), replaying the already-prepared write onto the latest version of the table and committing that result. Both writes land, with no re-scan, no re-execution, and no retry loop in your application.&lt;/p&gt;
&lt;p&gt;In the retail example, the compliance delete previously failed the moment a streaming insert committed inside its execution window. Now Amazon Redshift examines both writes, sees that the inserts only added data files while the delete only removed others, and confirms they never touched the same data. Because the changes are provably independent, Amazon Redshift rebases the delete onto the updated table version and commits it. Both writes succeed, the hour of work is preserved, and downstream consumers see both changes.&lt;/p&gt;
&lt;p&gt;This behavior is automatic for eligible writes, with no clustering keys to choose, no repartitioning, and no setting to enable. When two writes genuinely conflict, the behavior is unchanged from today: the conflict surfaces and the later writer fails and must retry.&lt;/p&gt;
&lt;p&gt;Two principles govern how the resolution works. The first is that a conflict is a matter of granularity. A failed race isn’t automatically a conflict. It’s a question that can be asked at different resolutions. The engine can ask whether any writer removed data at all or whether they only added it, and if data was removed, whether it came from the same partition, the same file, or the same rows that the current write touched. The finer the granularity examined, the more apparent conflicts turn out to be independent changes that can safely coexist. But finer checks also cost more: taken to the limit, the engine could intersect two change sets row by row, and that is expensive and usually unnecessary. So Amazon Redshift evaluates the least expensive level first and examines a finer level only when it must.&lt;/p&gt;
&lt;p&gt;The second principle is that correctness takes precedence over concurrency. Conflict resolution works entirely from table metadata and never re-scans data or re-plans a query. When the engine can’t prove that two writes are independent, it doesn’t guess. It declines to resolve the conflict and the write retries, exactly as it would today. An unnecessary retry costs a small amount of time, whereas an incorrect resolution risks losing data, so the system is deliberately biased toward the safe outcome. Resolution operates under snapshot isolation, the same model Amazon Redshift already uses for Iceberg reads. Engines such as &lt;a href="https://iceberg.apache.org/docs/latest/spark-writes/" target="_blank" rel="noopener"&gt;Apache Spark&lt;/a&gt;, &lt;a href="https://docs.aws.amazon.com/emr/latest/ReleaseGuide/emr-iceberg.html" target="_blank" rel="noopener"&gt;Amazon EMR&lt;/a&gt;, and &lt;a href="https://docs.aws.amazon.com/athena/latest/ug/querying-iceberg.html" target="_blank" rel="noopener"&gt;Amazon Athena&lt;/a&gt; honor it through the table’s isolation-level property, so a table shared across engines retains consistent semantics. For tables whose write isolation property is set to serializable, Amazon Redshift preserves the stricter contract and surfaces every conflict rather than resolving it. This is the Iceberg table property &lt;a href="https://iceberg.apache.org/docs/latest/configuration/#write-properties" target="_blank" rel="noopener"&gt;write.*.isolation-level&lt;/a&gt;. It’s unrelated to the Amazon Redshift database isolation level set with &lt;a href="https://docs.aws.amazon.com/redshift/latest/dg/r_ALTER_DATABASE.html" target="_blank" rel="noopener"&gt;ALTER DATABASE … ISOLATION LEVEL&lt;/a&gt;, which governs Amazon Redshift transactions and has no effect on Iceberg commit conflict resolution.&lt;/p&gt;
&lt;h2 id="resolving-at-the-right-granularity"&gt;Resolving at the right granularity&lt;/h2&gt;
&lt;p&gt;When a commit loses the race, the engine escalates through a sequence of checks, from the cheapest to the most precise. Each level is a complete resolution on its own and if a check proves the writes independent, the rebase commits and no finer check runs. The following diagram shows this escalation sequence: an operation-level check first, then partition-level, then file-level. Each level either proves the writes independent or escalates to the next, and a genuine conflict surfaces only after the last.&lt;/p&gt;
&lt;div style="width: 810px" class="wp-caption alignnone"&gt;
 &lt;a href="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/10/02/BDB-6258-1-1.jpg" target="_blank" rel="noopener"&gt;&lt;img src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/10/02/BDB-6258-1-1.jpg" alt="Flowchart of the conflict-resolution escalation: an operation check, then a partition check, then a file check, ending in a conflict" width="800"&gt;&lt;/a&gt;
 &lt;p class="wp-caption-text"&gt;Figure 1: The conflict-resolution escalation from operation-level to partition-level to file-level checks&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;The first check is by operation. A write that only added data, for example an &lt;code&gt;INSERT&lt;/code&gt; consisting of new data landing from a streaming job, can’t have removed anything another writer relied on, so any write can be rebased against it. This single rule resolves every combination in which at least one of the two writes only appended. A &lt;code&gt;DELETE&lt;/code&gt;, &lt;code&gt;UPDATE&lt;/code&gt;, or &lt;code&gt;MERGE&lt;/code&gt; commits cleanly against a stream of concurrent inserts, and an &lt;code&gt;INSERT&lt;/code&gt; commits cleanly over any other concurrent change, including a compaction.&lt;/p&gt;
&lt;p&gt;Next, when both writes removed data, the engine compares the partitions they each touched. Writes that modified disjoint partitions, for example batch jobs updating different dates or per-Region pipelines maintaining their own slices of one table, are independent by construction and both can commit cleanly.&lt;/p&gt;
&lt;p&gt;Finally, when both writes touch the same partition, the engine compares the individual files that each one changed. Two jobs deleting different rows of the same date partition usually land in different files, and when they do, both can commit cleanly. Only when two writes modified the same file does the conflict surface and the statement re-run. The protocol additionally has headroom for one further level: distinguishing changes to different rows within a single file.&lt;/p&gt;
&lt;p&gt;The following table summarizes the outcomes by operation pair. Read down for the concurrent write that committed first, and across for your write.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;table border="1px" width="100%" cellpadding="10px"&gt;
 &lt;tbody&gt;
  &lt;tr&gt;
   &lt;td&gt;&lt;/td&gt;
   &lt;td&gt;&lt;strong&gt;Your write ↓ / Concurrent write →&lt;/strong&gt;&lt;/td&gt;
   &lt;td&gt;&lt;strong&gt;&lt;code&gt;INSERT&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
   &lt;td&gt;&lt;strong&gt;&lt;code&gt;DELETE&lt;/code&gt; / &lt;code&gt;UPDATE&lt;/code&gt; / &lt;code&gt;MERGE&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
   &lt;td&gt;&lt;strong&gt;Compaction&lt;/strong&gt;&lt;/td&gt;
   &lt;td&gt;&lt;strong&gt;&lt;code&gt;ALTER&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;1&lt;/td&gt;
   &lt;td&gt;&lt;code&gt;INSERT&lt;/code&gt;&lt;/td&gt;
   &lt;td&gt;Resolves&lt;/td&gt;
   &lt;td&gt;Resolves&lt;/td&gt;
   &lt;td&gt;Resolves&lt;/td&gt;
   &lt;td&gt;Re-run required&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;2&lt;/td&gt;
   &lt;td&gt;&lt;code&gt;DELETE&lt;/code&gt; / &lt;code&gt;UPDATE&lt;/code&gt; / &lt;code&gt;MERGE&lt;/code&gt;&lt;/td&gt;
   &lt;td&gt;Resolves&lt;/td&gt;
   &lt;td&gt;Resolves based on partitions or files touched. A same-file change surfaces as a conflict&lt;/td&gt;
   &lt;td&gt;Resolves based on partitions or files touched. A same-file change surfaces as a conflict&lt;/td&gt;
   &lt;td&gt;Re-run required&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;3&lt;/td&gt;
   &lt;td&gt;&lt;code&gt;ALTER&lt;/code&gt;&lt;/td&gt;
   &lt;td&gt;Re-run required&lt;/td&gt;
   &lt;td&gt;Re-run required&lt;/td&gt;
   &lt;td&gt;Re-run required&lt;/td&gt;
   &lt;td&gt;Re-run required&lt;/td&gt;
  &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id="performance"&gt;Performance&lt;/h2&gt;
&lt;p&gt;To measure the effect, we ran an increasing number of concurrent writers committing to a single Iceberg table over a fixed window. We compared basic optimistic concurrency mode, where a losing commit is aborted and re-runs, against automatic conflict resolution mode being enabled. Every writer issued the same single-statement &lt;code&gt;INSERT&lt;/code&gt;, so the only variable across the various runs was contention rather than workload. We scaled 1–16 concurrent writers and recorded, for each configuration, how many commits succeeded and how many were rejected as conflicts.&lt;/p&gt;
&lt;p&gt;Setup: one unpartitioned Iceberg table registered in the AWS Glue Data Catalog in the US East (N. Virginia) AWS Region (us-east-1). Each writer is an independent session issuing autocommit &lt;code&gt;INSERT&lt;/code&gt; statements in a loop over a 60-second period. We report the commits completed and the attempts rejected as conflicts for each configuration.&lt;/p&gt;
&lt;p&gt;The first figure shows write throughput as writers are added, normalized to the single-writer case so the comparison doesn’t depend on instance type or table size.&lt;/p&gt;
&lt;div style="width: 810px" class="wp-caption alignnone"&gt;
 &lt;a href="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/10/02/BDB-6258-2-1.jpg" target="_blank" rel="noopener"&gt;&lt;img src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/10/02/BDB-6258-2-1.jpg" alt="Write throughput versus number of concurrent writers for basic optimistic concurrency compared to automatic conflict resolution" width="800"&gt;&lt;/a&gt;
 &lt;p class="wp-caption-text"&gt;Figure 2: Write throughput as concurrent writers increase, with and without automatic conflict resolution&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;Under basic optimistic concurrency, adding writers makes writing to the table slower. At 16 writers, throughput falls to 0.17× of a single writer, because nearly every writer’s work is discarded and redone rather than committed. With automatic conflict resolution the same workload scales to roughly 2×, and no commit is rejected in any measured configuration. At 16 concurrent writers, 12× more commits complete in the same window. The second figure shows why.&lt;/p&gt;
&lt;div style="width: 810px" class="wp-caption alignnone"&gt;
 &lt;a href="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/10/02/BDB-6258-3-1.jpg" target="_blank" rel="noopener"&gt;&lt;img src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/10/02/BDB-6258-3-1.jpg" alt="Commit failure rate versus concurrent writers, near (N−1)/N for the baseline and zero with automatic conflict resolution" width="800"&gt;&lt;/a&gt;
 &lt;p class="wp-caption-text"&gt;Figure 3: Commit failure rate as concurrent writers increase, with and without automatic conflict resolution&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;The baseline’s failure rate is almost exactly (N−1)/N, for example 50 percent at two writers, 75 percent at four writers, and 94 percent at 16 writers. This illustrates the table-level check that occurred previously: only one writer wins each commit round and every other writer in flight is rejected. Additional writers translate almost entirely into repeated work rather than progress. With conflict resolution enabled, the same runs record no conflicts at all, because every write in this workload is an append, and an append never removes data another writer relied on.&lt;/p&gt;
&lt;p&gt;This benchmark isolates the most basic append case deliberately: a single unpartitioned table and insert-only writers, which is the pattern behind continuous ingestion. Workloads that remove data exercise the partition and file checks described earlier, where the outcome depends on whether the two writes touched the same partition and the same files.&lt;/p&gt;
&lt;h2&gt;Fleet Results&lt;/h2&gt;
&lt;p&gt;Before conflict resolution, concurrent-write conflicts were the single largest cause of failed Iceberg writes in Amazon Redshift, accounting for roughly one in five failed Iceberg write queries each week. We rolled out this feature in stages, beginning with the operation-level check that covers concurrent appends. As that stage reached full fleet deployment in early September 2026, conflict errors from concurrent writes dropped to nearly zero, even as Iceberg write volume on the fleet more than doubled. One residual spike shown below came from a workload issuing concurrent &lt;code&gt;UPDATE&lt;/code&gt; statements, a conflict class that subsequent partition and file level checks will resolve as they deploy across the fleet.&lt;/p&gt;
&lt;div id="attachment_94879" style="width: 1530px" class="wp-caption alignnone"&gt;
 &lt;img aria-describedby="caption-attachment-94879" loading="lazy" class="wp-image-94879 size-full" src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/10/12/bdb-6258-figure3.png" alt="Figure 3: Conflict errors as a share of failed Iceberg writes" width="1520" height="880"&gt;
 &lt;p id="caption-attachment-94879" class="wp-caption-text"&gt;Figure 3: Conflict errors as a share of failed Iceberg writes&lt;/p&gt;
&lt;/div&gt;
&lt;h2 id="observing-conflict-resolution"&gt;Observing conflict resolution&lt;/h2&gt;
&lt;p&gt;Every Iceberg commit attempt is recorded in the &lt;code&gt;stl_iceberg_commit&lt;/code&gt; system table, including the outcome, the number of attempts, the concurrent operations the write rebased over, and the time spent rebasing. To see how often resolution is engaging on an example table:&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-sql"&gt;SELECT outcome, count(*), avg(attempt_number)
FROM stl_iceberg_commit
WHERE trim(table_name) = 'customer_orders'
GROUP BY outcome;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Concurrent writes are a fact of production data lakes, and until now, they forced you to serialize writers, partition around the problem, or retry. With automatic conflict resolution, Amazon Redshift commits concurrent writes that don’t actually overlap. These include any mix of writes alongside concurrent appends, and row-level writes that changed different partitions or different files of the same table. To get started, write to your Iceberg tables from multiple jobs on &lt;a href="https://docs.aws.amazon.com/redshift/latest/mgmt/cluster-versions.html#cluster-version-203" target="_blank" rel="noopener"&gt;patch 203 or later&lt;/a&gt;. Non-conflicting commits start to resolve on their own. To learn more about Iceberg write support in Amazon Redshift, see &lt;a href="https://aws.amazon.com/blogs/big-data/getting-started-with-apache-iceberg-write-support-in-amazon-redshift-part-1/" target="_blank" rel="noopener"&gt;Part 1&lt;/a&gt; and &lt;a href="https://aws.amazon.com/blogs/big-data/getting-started-with-apache-iceberg-write-support-in-amazon-redshift-part-2/" target="_blank" rel="noopener"&gt;Part 2&lt;/a&gt;.&lt;/p&gt;
&lt;p style="clear: both"&gt;&lt;/p&gt;
&lt;hr style="width: 100%"&gt;
&lt;h2&gt;About the authors&lt;/h2&gt;
&lt;footer&gt;
 &lt;div class="blog-author-box" style="padding-top: 2.0em"&gt;
  &lt;div class="blog-author-image" style="margin-right: 1.0em"&gt;
   &lt;img loading="lazy" class="alignnone size-full" src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/10/02/BDB-6258-4-1.jpg" alt="Sanjay Devireddy" width="100" height="133"&gt;
  &lt;/div&gt;
  &lt;h3 class="lb-h4"&gt;Sanjay Devireddy&lt;/h3&gt;
  &lt;p style="overflow: hidden"&gt;Sanjay is a senior engineer in Amazon Redshift working in various areas such as Iceberg transactions, concurrency scaling, and data sharing mesh architectures.&lt;/p&gt;
 &lt;/div&gt;
 &lt;div class="blog-author-box" style="padding-top: 2.0em"&gt;
  &lt;div class="blog-author-image" style="margin-right: 1.0em"&gt;
   &lt;img loading="lazy" class="alignnone size-full" src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/10/02/BDB-6258-5-1.jpg" alt="Xiening Dai" width="100" height="133"&gt;
  &lt;/div&gt;
  &lt;h3 class="lb-h4"&gt;Xiening Dai&lt;/h3&gt;
  &lt;p style="overflow: hidden"&gt;Xiening is a principal software engineer leading various efforts in Amazon Redshift’s query execution and data lake processing.&lt;/p&gt;
 &lt;/div&gt;
 &lt;div class="blog-author-box" style="padding-top: 2.0em"&gt;
  &lt;div class="blog-author-image" style="margin-right: 1.0em"&gt;
   &lt;img loading="lazy" class="alignnone size-full" src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/10/02/BDB-6258-6-1.jpg" alt="Madhukiran Vaddi" width="100" height="133"&gt;
  &lt;/div&gt;
  &lt;h3 class="lb-h4"&gt;Madhukiran Vaddi&lt;/h3&gt;
  &lt;p style="overflow: hidden"&gt;Madhukiran is an engineering lead with the Amazon Redshift team. Leads Iceberg transaction support, including conflict resolution and multi-table transactions, as well as Redshift’s concurrency scaling, backups, and durability. Passionate about building reliable data infrastructure at cloud scale.&lt;/p&gt;
 &lt;/div&gt;
 &lt;div class="blog-author-box" style="padding-top: 2.0em"&gt;
  &lt;div class="blog-author-image" style="margin-right: 1.0em"&gt;
   &lt;img loading="lazy" class="alignnone size-full" src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/10/02/BDB-6258-7-1.jpg" alt="Srishti Mittal" width="100" height="133"&gt;
  &lt;/div&gt;
  &lt;h3 class="lb-h4"&gt;Srishti Mittal&lt;/h3&gt;
  &lt;p style="overflow: hidden"&gt;Srishti is a Product Manager at AWS, with a focus on making open data lakes performant and interoperable across analytics engines. She leads product strategy for open table formats such as Apache Iceberg, partnering with customers and field teams to turn real-world data lake challenges into product capabilities.&lt;/p&gt;
 &lt;/div&gt;
&lt;/footer&gt;</content:encoded>
					
		
		
			</item>
		<item>
		<title>Bring data quality to your Amazon EMR workloads with Amazon EMR Data Quality built on AWS Glue Data Quality</title>
		<link>https://aws.amazon.com/blogs/big-data/bring-data-quality-to-your-amazon-emr-workloads-with-amazon-emr-data-quality-built-on-aws-glue-data-quality/</link>
		
		<dc:creator><![CDATA[Mert Hocanin]]></dc:creator>
		<pubDate>Mon, 12 Oct 2026 23:40:26 +0000</pubDate>
				<category><![CDATA[Advanced (300)]]></category>
		<category><![CDATA[Amazon EMR]]></category>
		<category><![CDATA[Announcements]]></category>
		<category><![CDATA[AWS Glue]]></category>
		<guid isPermaLink="false">677d09e986a568c56e0894ab3e8e815ae2cd3e13</guid>

					<description>AWS Glue Data Quality is now available natively on Amazon EMR, bundled in AWS runtime for Apache Spark (emr-spark-8.1.0). Write the same DQDL rulesets you use in AWS Glue directly in your Amazon EMR Spark jobs. This post walks through two patterns: lightweight inline validation and an Apache Iceberg Write-Audit-Publish workflow with managed rulesets.</description>
										<content:encoded>&lt;p&gt;Bad data rarely announces itself. It slips into an Amazon EMR pipeline quietly. A null where there should be a value. A timestamp in the wrong format. A referential key that no longer exists. It travels downstream until it surfaces as a wrong number in a dashboard, a failed downstream job, or a business decision made on data nobody verified.&lt;/p&gt;
&lt;p&gt;For teams running large-scale data processing on Amazon EMR, choosing a data quality strategy has meant picking between two trade-offs. You can build your own quality checks in Apache Spark using an open source library like &lt;a href="https://github.com/awslabs/deequ" target="_blank" rel="noopener"&gt;Deequ&lt;/a&gt;. With this approach, you can customize checks to your exact pipeline, but it adds custom code your team must maintain, with no centralized ruleset management and no consistent quality catalog across pipelines. Or you can route your data through AWS Glue to access its managed data quality capabilities. This approach minimizes the code you need to maintain but turns data quality validation into a separate step and adds another engine to your pipeline.&lt;/p&gt;
&lt;p&gt;Today, there is a third option.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://docs.aws.amazon.com/emr/latest/ManagementGuide/emr-data-quality.html" target="_blank" rel="noopener"&gt;Amazon EMR Data Quality&lt;/a&gt; built on AWS Glue is now available natively on Amazon EMR, bundled in &lt;a href="https://docs.aws.amazon.com/emr/latest/ReleaseGuide/emr-spark810-release.html" target="_blank" rel="noopener"&gt;AWS runtime for Apache Spark (emr-spark-8.1.0)&lt;/a&gt; and later versions. You can write data quality rules using the same Data Quality Definition Language (DQDL) used in AWS Glue. Run them directly inside your Amazon EMR Spark jobs, across each of the three Amazon EMR deployment modes: Amazon EMR on EC2, Amazon EMR Serverless, and Amazon EMR on EKS.&lt;/p&gt;
&lt;p&gt;This post walks through two patterns for using AWS Glue Data Quality, a capability of AWS Glue, on Amazon EMR. The first shows a lightweight ad hoc validation workflow, useful for exploratory processing and pipeline-local checks. The second builds on the Apache Iceberg Write-Audit-Publish (WAP) pattern. An extract, transform, and load (ETL) job writes data to an Iceberg branch, retrieves a managed ruleset from the AWS Glue Data Catalog, and validates the branch. Only if the data passes your quality bar does it merge to the main table. By the end of this post, you will have working examples of both patterns ready to run on any mode of Amazon EMR.&lt;/p&gt;
&lt;h2 id="what-is-amazon-emr-data-quality"&gt;What is Amazon EMR Data Quality?&lt;/h2&gt;
&lt;p&gt;With Amazon EMR Data Quality, you can define and run data quality rules against your datasets. At its core is the &lt;strong&gt;Data Quality Definition Language (DQDL)&lt;/strong&gt;, a declarative rule syntax. With DQDL, you can express constraints like completeness, uniqueness, value ranges, referential integrity, and statistical distributions in plain, readable statements.&lt;/p&gt;
&lt;p&gt;DQDL also includes machine learning (ML)-based anomaly detection. Rather than setting explicit thresholds, &lt;code&gt;DetectAnomalies&lt;/code&gt; rules learn the statistical baseline of your data across runs and flag deviations automatically. This is useful for metrics that shift over time, like daily record counts or revenue totals, where a fixed range rule would require constant maintenance.&lt;/p&gt;
&lt;p&gt;You can define rules inline within a job or store them as managed rulesets in the &lt;a href="https://docs.aws.amazon.com/glue/latest/dg/catalog-and-crawler.html" target="_blank" rel="noopener"&gt;AWS Glue Data Catalog&lt;/a&gt;, a centralized metadata repository for table definitions and data quality rulesets. After the rules are stored, you can version these rulesets, share them across teams, and reuse them across multiple pipelines. The evaluator returns per-rule pass or fail results and an overall quality score. Your job can inspect the score to take action: fail the pipeline, write results to Amazon Simple Storage Service (Amazon S3), trigger an alert, or conditionally merge data.&lt;/p&gt;
&lt;p&gt;You can now run this rule engine directly inside your Spark jobs on Amazon EMR. The same DQDL rulesets you author for AWS Glue work without modification.&lt;/p&gt;
&lt;p&gt;For a full reference on DQDL syntax and rule types, see the &lt;a href="https://docs.aws.amazon.com/glue/latest/dg/data-quality.html" target="_blank" rel="noopener"&gt;AWS Glue Data Quality&lt;/a&gt; documentation.&lt;/p&gt;
&lt;h2 id="prerequisites-and-setup"&gt;Prerequisites and setup&lt;/h2&gt;
&lt;p&gt;Amazon EMR is a managed service for running Apache Spark and other open source big data frameworks on AWS. It offers three deployment modes: Amazon EMR on EC2, Amazon EMR Serverless, and Amazon EMR on EKS.&lt;/p&gt;
&lt;p&gt;Before running the examples in this post, make sure you have the following in place.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Amazon EMR release:&lt;/strong&gt; Amazon EMR Data Quality is bundled in AWS runtime for Apache Spark (emr-spark-8.1.0) and later versions across Amazon EMR on EC2, Amazon EMR on EKS, and Amazon EMR Serverless. No bootstrap action or additional installation is required. The library is available as part of the release.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;AWS Identity and Access Management (IAM) permissions:&lt;/strong&gt; The execution role for your Amazon EMR job needs the following permissions:&lt;/p&gt;
&lt;ul&gt;
 &lt;li&gt;&lt;code&gt;glue:AccessDataQualityRuntimeConfiguration&lt;/code&gt; to publish data quality results to the AWS Glue Data Catalog.&lt;/li&gt;
 &lt;li&gt;&lt;code&gt;glue:CreateDataQualityRuleset&lt;/code&gt; to create managed rulesets in the AWS Glue Data Catalog.&lt;/li&gt;
 &lt;li&gt;&lt;code&gt;glue:GetDataQualityRuleset&lt;/code&gt; to retrieve managed rulesets from the AWS Glue Data Catalog.&lt;/li&gt;
 &lt;li&gt;&lt;code&gt;glue:GetDataQualityResult&lt;/code&gt; to read prior quality results.&lt;/li&gt;
 &lt;li&gt;&lt;code&gt;glue:PublishDataQuality&lt;/code&gt; to submit data quality results to the AWS Glue Data Catalog.&lt;/li&gt;
 &lt;li&gt;&lt;code&gt;glue:GetDatabase&lt;/code&gt; and &lt;code&gt;glue:GetTable&lt;/code&gt; for catalog access.&lt;/li&gt;
 &lt;li&gt;Standard Amazon S3 read/write permissions for your data and output locations.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="pattern-1-ad-hoc-data-quality-validation"&gt;Pattern 1: Ad hoc data quality validation&lt;/h2&gt;
&lt;p&gt;This pattern defines data quality rules directly inside your Amazon EMR Spark job, with no externally managed ruleset to maintain.&lt;/p&gt;
&lt;h3 id="the-use-case"&gt;The use case&lt;/h3&gt;
&lt;p&gt;Your team ingests a raw CSV file from a third-party vendor and needs to validate it before loading it into your data lake. The rules are straightforward: check that the record ID is never null, that revenue values are non-negative, and that country codes conform to an allowed list. This is a one-off check that lives entirely within the job. There is no centralized ruleset to manage, and the goal is to validate data quality before the load.&lt;/p&gt;
&lt;p&gt;This pattern works well for:&lt;/p&gt;
&lt;ul&gt;
 &lt;li&gt;Exploratory or one time processing pipelines.&lt;/li&gt;
 &lt;li&gt;Early-stage pipelines where rules are still evolving.&lt;/li&gt;
 &lt;li&gt;Lightweight validation that does not require centralized governance.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="how-it-works"&gt;How it works&lt;/h3&gt;
&lt;p&gt;You define your DQDL rules inline as a string, pass your &lt;code&gt;DataFrame&lt;/code&gt; and the rules to &lt;code&gt;EvaluateDataQuality&lt;/code&gt;, and inspect the results to decide how the job should proceed.&lt;/p&gt;
&lt;h3 id="to-validate-data-quality-inline-using-pyspark"&gt;To validate data quality inline using PySpark&lt;/h3&gt;
&lt;p&gt;To implement this pattern using PySpark, see the following code sample, which can be run on all three Amazon EMR deployment modes:&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-python"&gt;import os
from pyspark.sql import SparkSession
from awsdataquality.transforms import EvaluateDataQuality

spark = SparkSession.builder \
    .appName("dq-ruleset-catalog") \
    .enableHiveSupport() \
    .getOrCreate()

# Read new incoming data
input_df = spark.read.parquet("s3://amzn-s3-demo-sales-data/sales_data/incoming_data/")

# Contains our DQDL rules and analyzers. Analyzers submit statistics to the AWS Glue service for inspection. This can help create new rules.
ruleset = """
Rules = [
    RowCount &amp;gt; 0,
    IsPrimaryKey "order_id",
    Completeness "customer_email" &amp;gt;= 0.95,
    ColumnValues "order_status" in ["PENDING","SHIPPED","DELIVERED","RETURNED"],
    ColumnValues "net_amount" between 0.0 and 25000.0
]
Analyzers = [
    AllStatistics "net_amount",
    DistinctValuesCount "country",
    RowCount
]
"""

# Publishing options for the results of the data quality evaluation
publishing_opts = {
    "dataQualityEvaluationContext": "blog-post-dq-evaluation-context",
    "enableDataQualityResultsPublishing": "true"
}

# AWS Glue Data Quality uses the additional_options parameter to configure advanced evaluation behaviors, output formats, anomaly detection and performance tuning for data quality checks.
additional_opts = {"compositeRuleEvaluation.method": "ROW"}

# This set tells the Data Quality Evaluator if there are other data sources that
# we need to pull in for checks such as cross table reference checking.
additional_sources = {}

# Evaluate the ruleset against the data frame created by reading the parquet files and save
# the results in dq_results as a DataFrame.
dq_results = EvaluateDataQuality.apply(input_df, ruleset, publishing_opts, additional_opts,
    additional_sources)

print(f"{dq_results.count()} rule result row(s)")  # forces evaluation

# Iterate through the DQ results for any failed rules and if there are any failed
# rules, throw an exception to stop the job.
failures = []
for row in dq_results.collect():
    if row["Outcome"] == "Failed":
        failures.append(f"Rule: {row['Rule']} | Reason: {row['FailureReason']}")

if failures:
    failure_summary = "\n".join(failures)
    raise RuntimeError(f"Data quality checks failed:\n{failure_summary}")

# Write out data into the Iceberg table sales_data table
input_df.writeTo("company_sales.sales_data").append()
print(f"all rules passed; appending {input_df.count()} row(s) to company_sales.sales_data")&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;The preceding script passes the incoming &lt;code&gt;DataFrame&lt;/code&gt;, an inline ruleset, and publishing options to &lt;code&gt;EvaluateDataQuality&lt;/code&gt;, which runs the ruleset and returns a results &lt;code&gt;DataFrame&lt;/code&gt;. The results &lt;code&gt;DataFrame&lt;/code&gt; lists each rule with a &lt;code&gt;Passed&lt;/code&gt; or &lt;code&gt;Failed&lt;/code&gt; outcome and, for failed rules, the reason. The &lt;a href="https://docs.aws.amazon.com/glue/latest/dg/glue-etl-scala-apis-glue-dq-EvaluateDataQuality.html" target="_blank" rel="noopener"&gt;publishing options&lt;/a&gt; control if and where the results get published. The script raises an error and stops if any rule fails. If every rule passes, it appends the incoming data to the &lt;code&gt;company_sales.sales_data&lt;/code&gt; table.&lt;/p&gt;
&lt;h3 id="what-the-data-quality-results-look-like"&gt;What the data quality results look like&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;EvaluateDataQuality.apply()&lt;/code&gt; function returns the results as a &lt;code&gt;DataFrame&lt;/code&gt; containing each rule, whether it passed or failed, and the evaluated rule. Your job can read this &lt;code&gt;DataFrame&lt;/code&gt; and branch its logic accordingly: writing clean records to Amazon S3, routing failed records to a quarantine path, or halting the pipeline entirely. Following is sample output of the code earlier:&lt;/p&gt;
&lt;table border="1px" width="100%" cellpadding="10px"&gt;
 &lt;tbody&gt;
  &lt;tr&gt;
   &lt;td&gt;&lt;strong&gt;Rule&lt;/strong&gt;&lt;/td&gt;
   &lt;td&gt;&lt;strong&gt;Outcome&lt;/strong&gt;&lt;/td&gt;
   &lt;td&gt;&lt;strong&gt;FailureReason&lt;/strong&gt;&lt;/td&gt;
   &lt;td&gt;&lt;strong&gt;EvaluatedMetrics&lt;/strong&gt;&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;RowCount &amp;gt; 0&lt;/td&gt;
   &lt;td&gt;Passed&lt;/td&gt;
   &lt;td&gt;NULL&lt;/td&gt;
   &lt;td&gt;{RowCount -&amp;gt; 50000.0}&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;IsPrimaryKey “order_id”&lt;/td&gt;
   &lt;td&gt;Passed&lt;/td&gt;
   &lt;td&gt;NULL&lt;/td&gt;
   &lt;td&gt;{Uniqueness -&amp;gt; 1.0, Completeness -&amp;gt; 1.0}&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;Completeness “customer_email” &amp;gt;= 0.95&lt;/td&gt;
   &lt;td&gt;Passed&lt;/td&gt;
   &lt;td&gt;NULL&lt;/td&gt;
   &lt;td&gt;{Completeness -&amp;gt; 0.97924}&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;ColumnValues “order_status” in […]&lt;/td&gt;
   &lt;td&gt;Failed&lt;/td&gt;
   &lt;td&gt;Value: 0.99598 does not meet …&lt;/td&gt;
   &lt;td&gt;{ColumnValues.Compliance -&amp;gt; 0.99598}&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;ColumnValues “net_amount” between 0.0 and 25000.0&lt;/td&gt;
   &lt;td&gt;Failed&lt;/td&gt;
   &lt;td&gt;Value: -2228.87 does not meet …&lt;/td&gt;
   &lt;td&gt;{Minimum -&amp;gt; -2228.87, Maximum -&amp;gt; 3394.51}&lt;/td&gt;
  &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-text"&gt;RuntimeError: Data quality checks failed:
Rule: ColumnValues "order_status" in ["PENDING","SHIPPED","DELIVERED","RETURNED"] | Reason: Value: 0.99598 ...
Rule: ColumnValues "net_amount" between 0.0 and 25000.0 | Reason: Value: -2228.87 ...&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h2 id="pattern-2-iceberg-write-audit-publish-with-managed-rulesets"&gt;Pattern 2: Iceberg Write-Audit-Publish with managed rulesets&lt;/h2&gt;
&lt;p&gt;This pattern gates data promotion on a centrally managed ruleset, merging new data into your main table only after it passes your quality checks.&lt;/p&gt;
&lt;h3 id="the-use-case-1"&gt;The use case&lt;/h3&gt;
&lt;p&gt;Your team runs a nightly ETL job that transforms raw transactional data and writes it to an &lt;a href="https://iceberg.apache.org/" target="_blank" rel="noopener"&gt;Apache Iceberg&lt;/a&gt; table. Apache Iceberg is an open table format for large analytic datasets that supports schema evolution, time travel, and branching on Amazon S3. Rather than writing directly to the production table and discovering quality issues after the fact, you want a safety gate. Write to a staging branch, validate it against your organization’s data quality standards, and only promote the data to the main table if it passes.&lt;/p&gt;
&lt;p&gt;This is the &lt;a href="https://iceberg.apache.org/docs/latest/branching/" target="_blank" rel="noopener"&gt;Write-Audit-Publish (WAP)&lt;/a&gt; pattern, a well-established practice in Iceberg-based architectures. For a detailed walkthrough of this pattern using AWS Glue, see &lt;a href="https://aws.amazon.com/blogs/big-data/build-write-audit-publish-pattern-with-apache-iceberg-branching-and-aws-glue-data-quality/" target="_blank" rel="noopener"&gt;Build Write-Audit-Publish pattern with Apache Iceberg branching and AWS Glue Data Quality&lt;/a&gt;. WAP gives you atomic promotion: either all the new data meets your quality bar and gets merged, or none of it does. Readers of the main table should not see partial or failed writes.&lt;/p&gt;
&lt;p&gt;When combining AWS Glue Data Quality with WAP, you define your quality rules once in the AWS Glue Data Catalog and have your data governance team own them. You enforce them automatically at promotion time, without touching the pipeline code. Combined with managed rulesets in the AWS Glue Data Catalog, you get quality enforcement that is consistent and auditable. The data quality rules are also decoupled from the job code and changing them does not require touching the pipeline.&lt;/p&gt;
&lt;p&gt;This pattern works well for:&lt;/p&gt;
&lt;ul&gt;
 &lt;li&gt;Production ETL pipelines where bad data reaching the main table has downstream consequences.&lt;/li&gt;
 &lt;li&gt;Organizations with centralized data governance teams that own and version quality standards.&lt;/li&gt;
 &lt;li&gt;Apache Iceberg-based lakehouses where the WAP pattern is already in use.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="how-it-works-1"&gt;How it works&lt;/h3&gt;
&lt;p&gt;The flow has four steps:&lt;/p&gt;
&lt;ul&gt;
 &lt;li&gt;&lt;strong&gt;Retrieve the ruleset –&lt;/strong&gt;&amp;nbsp;Call the AWS Glue Data Catalog to fetch the managed ruleset for this table. The ruleset is owned by your governance team and versioned in the catalog.&lt;/li&gt;
 &lt;li&gt;&lt;strong&gt;Write to a branch –&lt;/strong&gt;&amp;nbsp;Before writing the ETL output, create an Apache Iceberg branch on the target table. Write all transformed records to this branch rather than to main.&lt;/li&gt;
 &lt;li&gt;&lt;strong&gt;Evaluate –&lt;/strong&gt;&amp;nbsp;Run &lt;code&gt;EvaluateDataQuality&lt;/code&gt; against the branch data using the retrieved ruleset.&lt;/li&gt;
 &lt;li&gt;&lt;strong&gt;Merge or discard –&lt;/strong&gt;&amp;nbsp;If the quality score meets your threshold, merge the branch into main. If not, discard the branch and trigger your alerting workflow.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Creating a managed ruleset:&lt;/strong&gt; To create a ruleset in the AWS Glue Data Catalog that targets the same database and table, you can create one using the AWS Command Line Interface (AWS CLI). For example:&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-bash"&gt;aws glue create-data-quality-ruleset \
    --name "sales-orders-ruleset" \
    --ruleset "Rules = [ RowCount &amp;gt; 0, IsPrimaryKey \"order_id\", Completeness \"customer_email\" &amp;gt;= 0.95, ColumnValues \"order_status\" in [\"PENDING\",\"SHIPPED\",\"DELIVERED\",\"RETURNED\"], ColumnValues \"net_amount\" between 0.0 and 25000.0 ]" \
    --target-table '{"TableName": "sales_data", "DatabaseName": "company_sales"}'&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;The &lt;code&gt;--target-table&lt;/code&gt; values must match the &lt;code&gt;ICEBERG_DATABASE&lt;/code&gt; and &lt;code&gt;ICEBERG_TABLE&lt;/code&gt; environment variables used in the Pattern 2 job. The job discovers the ruleset at runtime using &lt;code&gt;list_data_quality_rulesets&lt;/code&gt; filtered by that target table.&lt;/p&gt;
&lt;h3 id="to-validate-data-quality-inline-using-pyspark-1"&gt;To validate data quality inline using PySpark&lt;/h3&gt;
&lt;p&gt;To implement this pattern, see the following code sample which can be run on all three Amazon EMR deployment modes:&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-python"&gt;import os
import boto3
from pyspark.sql import SparkSession
from awsdataquality.transforms import EvaluateDataQuality

spark = (SparkSession.builder.appName("blog-wap-dq")
    .config("spark.sql.catalog.iceberg", "org.apache.iceberg.spark.SparkCatalog")
    .config("spark.sql.catalog.iceberg.warehouse", "s3://amzn-s3-demo-bucket/warehouse/")
    .config("spark.sql.catalog.iceberg.catalog-impl", "org.apache.iceberg.aws.glue.GlueCatalog")
    .enableHiveSupport().getOrCreate())

database = os.environ["ICEBERG_DATABASE"]
table = os.environ["ICEBERG_TABLE"]
input_path = os.environ["WAP_INPUT_PATH"]
threshold = float(os.environ.get("WAP_SCORE_THRESHOLD", "1.0"))
region = os.environ.get("AWS_REGION", "us-east-1")
ruleset_name = os.environ.get("GLUE_DQ_RULESET_NAME")

# Branch name carries the Spark application id so concurrent nightly runs, and reruns
# of a failed one, never collide on the same branch.
branch = os.environ.get("WAP_BRANCH") or f"audit_{spark.sparkContext.applicationId}"
branch = branch.replace("-", "_")
fqtn = f"iceberg.`{database}`.`{table}`"
branch_fqtn = f"iceberg.`{database}`.`{table}`.branch_{branch}"

glue = boto3.client("glue", region_name=region)

# ---------------------------------------------------------- 1. DQ RULESET
# We will then get the data quality rules.
ruleset = glue.get_data_quality_ruleset(Name=ruleset_name)["Ruleset"]
print(f"auditing against catalog ruleset '{ruleset_name}':\n{ruleset}")

# ---------------------------------------------------------- 2. WRITE
main_before = spark.table(fqtn).count()
print(f"main has {main_before} row(s) before this batch")

# CREATE BRANCH (not CREATE OR REPLACE): if the branch somehow already exists, that is
# a concurrent or half-finished run and silently replacing it would discard its state.
spark.sql(f"ALTER TABLE {fqtn} CREATE BRANCH {branch}")
print(f"created branch {branch}")

# Write out our batch to our new branch
batch = spark.read.parquet(input_path)
batch.writeTo(branch_fqtn).append()
print(f"wrote {batch.count()} row(s) to branch {branch}")

# Reread the branch and create a data frame to perform our data quality checks.
branch_df = spark.read.option("branch", branch).table(f"iceberg.`{database}`.`{table}`")

# ---------------------------------------------------------- 3. AUDIT
# Will now evaluate the ruleset against the branch
publishing_opts = {
    "dataQualityEvaluationContext": ruleset_name,
    "publishToDataCatalogTarget": {
        "DatabaseName": database,
        "TableName": table
    },
    "enableDataQualityResultsPublishing": "true"
}

# See the AWS Glue Data Quality additional_options reference for advanced configuration:
# https://docs.aws.amazon.com/glue/latest/dg/data-quality-additional-options.html
additional_opts = {}
additional_sources = {}

# Run EvaluateDataQuality.
dq_results = EvaluateDataQuality.apply(branch_df, ruleset, publishing_opts,
    additional_opts, additional_sources)

# ---------------------------------------------------------- 4. PUBLISH or discard
# Iterate through the DQ results for any failed rules and if there are any failed
# rules, throw an exception to stop the job.
failures = []
for row in dq_results.collect():
    if row["Outcome"] == "Failed":
        failures.append(f"Rule: {row['Rule']} | Reason: {row['FailureReason']}")

# If there are any failures of any rule, we will drop the branch, and throw an error
if failures:
    # Drop first, then raise: an exception alone would leave the rejected branch (and
    # its data files) behind on the table for every failed night.
    spark.sql(f"ALTER TABLE {fqtn} DROP BRANCH {branch}")
    main_after = spark.table(fqtn).count()
    print(f"REJECTED: branch {branch} dropped; main unchanged at {main_after} row(s)")
    failure_summary = "\n".join(r for r in failures)
    raise RuntimeError(f"Data quality checks failed:\n{failure_summary}")

# fast_forward moves main's pointer to the branch's snapshot. It requires main to
# be an ancestor of the branch, which holds here because the branch was cut from
# main and only appended to --- no rewrite of the promoted data, no second copy.
spark.sql(f"CALL iceberg.system.fast_forward("
    f"table =&amp;gt; '{database}.{table}', branch =&amp;gt; 'main', to =&amp;gt; '{branch}')")
spark.sql(f"ALTER TABLE {fqtn} DROP BRANCH {branch}")
main_after = spark.table(fqtn).count()
print(f"PUBLISHED: main fast-forwarded to {branch} "
    f"({main_before} -&amp;gt; {main_after} row(s)); branch dropped")

spark.stop()&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;This code demonstrates a sample WAP approach using Apache Iceberg. We start by retrieving the ruleset from the AWS Glue service that will run against the table. We then create a branch within the Apache Iceberg table and apply new data against it. We then run our data quality checks using &lt;code&gt;EvaluateDataQuality&lt;/code&gt; with the ruleset that we earlier obtained. If any of the data quality rules fail, a branch that holds the new version of the table gets dropped. Otherwise, it will be committed as the new main branch and made available for all consumers.&lt;/p&gt;
&lt;p&gt;In this example, you will notice that the &lt;code&gt;publishing_opts&lt;/code&gt; (publishing options) look different. For the &lt;code&gt;dataQualityEvaluationContext&lt;/code&gt;, we set the ruleset name. We also fill in the &lt;code&gt;publishToDataCatalogTarget&lt;/code&gt; with the database and table name within the AWS Glue Data Catalog. This tags the data quality run and its results to that table for easier lookups if you want to view the history of data quality runs on the table.&lt;/p&gt;
&lt;h2 id="clean-up"&gt;Clean up&lt;/h2&gt;
&lt;p&gt;To avoid ongoing charges, remove the resources that you no longer need:&lt;/p&gt;
&lt;ol type="1"&gt;
 &lt;li&gt;Shut down Amazon EMR on EC2 clusters, Amazon EMR Serverless applications, or Amazon EMR on EKS.&lt;/li&gt;
 &lt;li&gt;Delete the tables that you created. The table mentioned in this blog post is &lt;code&gt;company_sales.sales_data&lt;/code&gt;.&lt;/li&gt;
 &lt;li&gt;Delete the data quality ruleset created in Pattern 2.&lt;/li&gt;
 &lt;li&gt;Delete any Amazon S3 data that was generated for the purposes of this blog post.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Data quality is a concern for most production data pipelines. Until now, teams running on Amazon EMR had to build that quality layer themselves or bring in AWS Glue as a separate processing engine to get managed quality capabilities.&lt;/p&gt;
&lt;p&gt;With AWS Glue Data Quality now bundled in AWS runtime for Apache Spark (emr-spark-8.1.0) and later versions, that gap is closed. You write your DQDL rules once and store them in the AWS Glue Data Catalog. From there, run them across each of the three Amazon EMR deployment modes.&lt;/p&gt;
&lt;p&gt;The patterns in this post represent two points on that spectrum: a lightweight inline check for ad hoc workloads, and a full WAP workflow for production pipelines that need atomic, auditable data promotion.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;To learn more:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
 &lt;li&gt;&lt;a href="https://docs.aws.amazon.com/glue/latest/dg/data-quality.html" target="_blank" rel="noopener"&gt;AWS Glue Data Quality documentation&lt;/a&gt; – Full DQDL rule syntax and rule type reference.&lt;/li&gt;
 &lt;li&gt;&lt;a href="https://docs.aws.amazon.com/emr/latest/EMR-Serverless-UserGuide/emr-serverless.html" target="_blank" rel="noopener"&gt;Amazon EMR Serverless documentation&lt;/a&gt; – Getting started with Amazon EMR Serverless.&lt;/li&gt;
 &lt;li&gt;&lt;a href="https://iceberg.apache.org/docs/latest/branching/" target="_blank" rel="noopener"&gt;Apache Iceberg branching and Write-Audit-Publish&lt;/a&gt; – Full reference for the WAP pattern used in Pattern 2.&lt;/li&gt;
 &lt;li&gt;&lt;a href="https://docs.aws.amazon.com/emr/latest/ReleaseGuide/emr-spark810-release.html" target="_blank" rel="noopener"&gt;Amazon EMR release 8.1 notes&lt;/a&gt; – Bundled libraries and changes in release 8.1.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Have questions or feedback on this post? Leave a comment below or reach out on the &lt;a href="https://repost.aws/" target="_blank" rel="noopener"&gt;AWS re:Post community&lt;/a&gt;.&lt;/p&gt;
&lt;p style="clear: both"&gt;&lt;/p&gt;
&lt;hr style="width: 100%"&gt;
&lt;h2&gt;About the authors&lt;/h2&gt;
&lt;footer&gt;
 &lt;div class="blog-author-box" style="padding-top: 2.0em"&gt;
  &lt;div class="blog-author-image" style="margin-right: 1.0em"&gt;
   &lt;img loading="lazy" class="alignnone size-full" src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/10/09/BDB-6303-1.jpg" alt="Mert Hocanin" width="100" height="133"&gt;
  &lt;/div&gt;
  &lt;h3 class="lb-h4"&gt;Mert Hocanin&lt;/h3&gt;
  &lt;p style="overflow: hidden"&gt;&lt;a href="https://aws.amazon.com/blogs/big-data/author/hocanint/" target="_blank" rel="noopener"&gt;Mert&lt;/a&gt; is a Specialist Solutions Architect at AWS focused on data governance and analytics. He works with customers building data lakes and large-scale processing pipelines using AWS Lake Formation, AWS Glue, Amazon EMR, Amazon Athena, and Amazon SageMaker Unified Studio, helping engineering and data engineering teams adopt cloud-native data quality and governance practices at scale.&lt;/p&gt;
 &lt;/div&gt;
 &lt;div class="blog-author-box" style="padding-top: 2.0em"&gt;
  &lt;div class="blog-author-image" style="margin-right: 1.0em"&gt;
   &lt;img loading="lazy" class="alignnone size-full" src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/10/09/BDB-6303-2.jpg" alt="Shrey Malpani" width="100" height="133"&gt;
  &lt;/div&gt;
  &lt;h3 class="lb-h4"&gt;Shrey Malpani&lt;/h3&gt;
  &lt;p style="overflow: hidden"&gt;&lt;a href="https://www.linkedin.com/in/shreymalpani/" target="_blank" rel="noopener"&gt;Shrey&lt;/a&gt; is a Senior Product Manager Technical at Amazon Web Services (AWS), where he works at the intersection of distributed data processing and data integration. He is focused on building and scaling data integration and data management capabilities across services like AWS Glue, Amazon EMR, and Amazon Redshift that help customers build AI-ready data foundations for their analytics and machine learning workflows.&lt;/p&gt;
 &lt;/div&gt;
 &lt;div class="blog-author-box" style="padding-top: 2.0em"&gt;
  &lt;div class="blog-author-image" style="margin-right: 1.0em"&gt;
   &lt;img loading="lazy" class="alignnone size-full" src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/10/09/BDB-6303-3.jpg" alt="Linan Zheng" width="100" height="133"&gt;
  &lt;/div&gt;
  &lt;h3 class="lb-h4"&gt;Linan Zheng&lt;/h3&gt;
  &lt;p style="overflow: hidden"&gt;&lt;a href="https://www.linkedin.com/in/linan-z-3a4147a6" target="_blank" rel="noopener"&gt;Linan&lt;/a&gt; is a Software Development Engineer on the AWS Glue Data Quality team, based in New York. Linan previously worked on AWS Glue streaming ETL and zero-ETL integrations, and now works on the AWS Glue Data Quality engine and its integration with Amazon EMR, including cross-platform validation across AWS Glue and all three Amazon EMR deployment modes. Linan focuses on making data quality checks reliable and consistent wherever customers run their Spark workloads.&lt;/p&gt;
 &lt;/div&gt;
 &lt;div class="blog-author-box" style="padding-top: 2.0em"&gt;
  &lt;div class="blog-author-image" style="margin-right: 1.0em"&gt;
   &lt;img loading="lazy" class="alignnone size-full" src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/10/09/BDB-6303-4.jpg" alt="Edison Xie" width="100" height="133"&gt;
  &lt;/div&gt;
  &lt;h3 class="lb-h4"&gt;Edison Xie&lt;/h3&gt;
  &lt;p style="overflow: hidden"&gt;&lt;a href="https://www.linkedin.com/in/edisonx" target="_blank" rel="noopener"&gt;Edison&lt;/a&gt; is a Software Development Engineer on the AWS Glue Data Quality team, based in New York. Edison works on the data quality engine that powers rule evaluation, results publishing, and anomaly detection, and helped bring it to Amazon EMR on EC2, Amazon EMR Serverless, and Amazon EMR on EKS. Edison’s work spans the library, its integration into Amazon EMR releases, and the testing that keeps it reliable across each deployment mode.&lt;/p&gt;
 &lt;/div&gt;
 &lt;div class="blog-author-box" style="padding-top: 2.0em"&gt;
  &lt;div class="blog-author-image" style="margin-right: 1.0em"&gt;
   &lt;img loading="lazy" class="alignnone size-full" src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/10/09/BDB-6303-5.jpg" alt="Aditya Vallabhani" width="100" height="133"&gt;
  &lt;/div&gt;
  &lt;h3 class="lb-h4"&gt;Aditya Vallabhani&lt;/h3&gt;
  &lt;p style="overflow: hidden"&gt;&lt;a href="https://www.linkedin.com/in/avallabhani/" target="_blank" rel="noopener"&gt;Aditya&lt;/a&gt; is a Software Development Engineer on the AWS Glue Data Quality team, where he builds core capabilities that extend managed data quality across AWS Glue, Amazon EMR, and the broader Spark ecosystem, with a focus on making rule evaluation feel like a native part of the runtime rather than a separate service call. Before joining AWS Glue Data Quality, he worked on AWS Zero-ETL integrations, building direct pathways between operational data stores and analytics engines so customers could analyze fresh data without maintaining custom pipeline code.&lt;/p&gt;
 &lt;/div&gt;
&lt;/footer&gt;</content:encoded>
					
		
		
			</item>
		<item>
		<title>Build cross-account streaming pipelines with the Amazon Kinesis Producer Library and Kinesis Client Library</title>
		<link>https://aws.amazon.com/blogs/big-data/build-cross-account-streaming-pipelines-with-the-amazon-kinesis-producer-library-and-kinesis-client-library/</link>
		
		<dc:creator><![CDATA[Charishma Makineni]]></dc:creator>
		<pubDate>Mon, 12 Oct 2026 15:48:07 +0000</pubDate>
				<category><![CDATA[Expert (400)]]></category>
		<category><![CDATA[Kinesis Data Streams]]></category>
		<category><![CDATA[Technical How-to]]></category>
		<guid isPermaLink="false">0fedc9438b728a4c5c13a75ef4dd66570420fa11</guid>

					<description>Learn how to build an enterprise-scale cross-account streaming pipeline with Amazon Kinesis Data Streams using the Kinesis Producer Library (KPL) and Kinesis Client Library (KCL) across three separate AWS accounts, with resource-based policies, cross-account IAM, and a shared customer managed AWS KMS key for maximum security isolation.</description>
										<content:encoded>&lt;p&gt;Organizations use real-time streaming to act on data the moment it arrives. They detect fraud, personalize customer experiences, and respond to infrastructure events, all within milliseconds. Amazon Kinesis Data Streams ingests and processes millions of records per second with sub-second latency. With multi-account deployment, you can enforce strict security boundaries, satisfy regulatory requirements, and contain scope using &lt;a href="https://aws.amazon.com/kinesis/data-streams/" target="_blank" rel="noopener"&gt;Amazon Kinesis Data Streams&lt;/a&gt;. This approach still delivers the high-throughput performance that streaming pipelines require. Realizing these benefits, however, requires configuring producers and consumers to operate across account boundaries. This involves coordinating AWS Identity and Access Management (IAM) policies, encryption keys, and monitoring across accounts, which can extend delivery timelines.&lt;/p&gt;
&lt;p&gt;This post addresses that complexity directly. We first examine the common architectural patterns for producing and consuming data from Amazon Kinesis Data Streams across multiple AWS accounts. This post has a detailed walkthrough of the most isolated configuration, in which the producer, the stream, and the consumer each reside in a separate AWS account. This illustrates the best practices for enterprise-scale streaming with maximum security isolation.&lt;/p&gt;
&lt;p&gt;Amazon Kinesis Data Streams integrates directly with services such as AWS Lambda, Amazon Data Firehose, and Amazon Managed Service for Apache Flink. Granting cross-account access to other service is usually done through Kinesis resource policies. However, many enterprise workloads run on custom applications. Two open source libraries let these applications produce and consume streams:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://docs.aws.amazon.com/streams/latest/dev/developing-producers-with-kpl.html" target="_blank" rel="noopener"&gt;Amazon Kinesis Producer Library (KPL)&lt;/a&gt; – Writes records to a Kinesis data stream at high throughput. KPL aggregates multiple user records into a single Kinesis record to maximize shard utilization. It retries failed records with exponential backoff. It also publishes per-instance Amazon CloudWatch metrics for operational visibility.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://docs.aws.amazon.com/streams/latest/dev/kcl.html" target="_blank" rel="noopener"&gt;Kinesis Client Library (KCL)&lt;/a&gt; – Consumes and processes records from a Kinesis data stream. KCL 3.5 discovers shards automatically and coordinates leases through Amazon DynamoDB. It checkpoints progress so that a failed worker resumes from the last saved position. It performs graceful lease handover during resharding. It also balances shard assignments across workers to distribute load evenly.&lt;/p&gt;
&lt;p&gt;Both libraries support Java. KCL also supports other languages through the MultiLangDaemon. This walkthrough uses both libraries.&lt;/p&gt;
&lt;h2 id="challenges-enterprises-face-with-multi-account-streaming"&gt;Challenges enterprises face with multi-account streaming&lt;/h2&gt;
&lt;p&gt;Large enterprises commonly encounter the following challenges when building cross-account streaming architectures:&lt;/p&gt;
&lt;ul&gt;
 &lt;li&gt;&lt;strong&gt;Account boundary enforcement&lt;/strong&gt;: Producers (app teams) and consumers (analytics teams) sit in separate accounts for isolation and cost attribution. Access is denied by default, so each path is granted explicitly. The producer needs write access to a stream it does not own, the consumer needs read access to that foreign stream, and encrypted streams add AWS Key Management Service (AWS KMS) key access on both sides.&lt;/li&gt;
 &lt;li&gt;Cross-account access has policy complexity and requires coordinating three policies across accounts: a resource-based policy on the stream, identity-based policies on producers and consumers, and AWS KMS key sharing for encrypted streams.&lt;/li&gt;
 &lt;li&gt;&lt;strong&gt;Encryption across boundaries&lt;/strong&gt;: Streams encrypted with AWS managed keys cannot be shared cross-account. Customer managed &lt;a href="https://aws.amazon.com/kms/" target="_blank" rel="noopener"&gt;AWS KMS&lt;/a&gt; keys are required.&lt;/li&gt;
 &lt;li&gt;&lt;strong&gt;Monitoring across accounts&lt;/strong&gt;: &lt;a href="https://aws.amazon.com/cloudwatch/" target="_blank" rel="noopener"&gt;Amazon CloudWatch&lt;/a&gt; metrics for producer throughput, stream utilization, and consumer lag live in different accounts, requiring cross-account observability.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="cross-account-deployment-patterns"&gt;Cross-account deployment patterns&lt;/h2&gt;
&lt;p&gt;The following table illustrates common permutations of KPL, Kinesis data stream, and KCL placement across accounts:&lt;/p&gt;
&lt;table border="1px" width="100%" cellpadding="10px"&gt;
 &lt;tbody&gt;
  &lt;tr&gt;
   &lt;td&gt;&lt;strong&gt;Pattern&lt;/strong&gt;&lt;/td&gt;
   &lt;td&gt;&lt;strong&gt;KPL (Producer)&lt;/strong&gt;&lt;/td&gt;
   &lt;td&gt;&lt;strong&gt;Kinesis data stream&lt;/strong&gt;&lt;/td&gt;
   &lt;td&gt;&lt;strong&gt;KCL (Consumer)&lt;/strong&gt;&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;&lt;strong&gt;All-in-one account&lt;/strong&gt;&lt;/td&gt;
   &lt;td&gt;Account A&lt;/td&gt;
   &lt;td&gt;Account A&lt;/td&gt;
   &lt;td&gt;Account A&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;&lt;strong&gt;Cross-account Consumer&lt;/strong&gt;&lt;/td&gt;
   &lt;td&gt;Account A&lt;/td&gt;
   &lt;td&gt;Account A&lt;/td&gt;
   &lt;td&gt;Account B&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;&lt;strong&gt;Cross-account Producer&lt;/strong&gt;&lt;/td&gt;
   &lt;td&gt;Account A&lt;/td&gt;
   &lt;td&gt;Account B&lt;/td&gt;
   &lt;td&gt;Account B&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;&lt;strong&gt;Full separation&lt;/strong&gt;&lt;/td&gt;
   &lt;td&gt;Account A&lt;/td&gt;
   &lt;td&gt;Account B&lt;/td&gt;
   &lt;td&gt;Account C&lt;/td&gt;
  &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Each pattern requires specific IAM configurations. The full-separation pattern (Account A to Account B to Account C) demonstrates the most complex scenario and is what we walk through in the following sections.&lt;/p&gt;
&lt;h2 id="solution-architecture"&gt;Solution architecture&lt;/h2&gt;
&lt;p&gt;Our reference implementation deploys three AWS accounts:&lt;/p&gt;
&lt;ul&gt;
 &lt;li&gt;&lt;strong&gt;Account A (Producer)&lt;/strong&gt;: Runs the KPL application on &lt;a href="https://aws.amazon.com/ec2/" target="_blank" rel="noopener"&gt;Amazon Elastic Compute Cloud (Amazon EC2)&lt;/a&gt;. Assumes a cross-account IAM role in Account B to write records.&lt;/li&gt;
 &lt;li&gt;&lt;strong&gt;Account B (Stream)&lt;/strong&gt;: Owns the Kinesis data stream. Hosts a cross-account IAM role for the producer (Account A) and a resource-based policy granting read access to the consumer (Account C). Also pre-registers an Enhanced Fan-Out consumer shared with Account C through a consumer resource policy. The stream is encrypted with a customer managed AWS KMS key shared with both accounts.&lt;/li&gt;
 &lt;li&gt;&lt;strong&gt;Account C (Consumer)&lt;/strong&gt;: Runs the KCL 3.5 application with its own DynamoDB lease table and CloudWatch metrics. It reads the cross-account stream directly through a resource-based policy (no role assumption). The policy authorizes &lt;code&gt;GetRecords&lt;/code&gt;, &lt;code&gt;GetShardIterator&lt;/code&gt;, &lt;code&gt;DescribeStream&lt;/code&gt;, and &lt;code&gt;ListShards&lt;/code&gt;. Enhanced Fan-Out (EFO) is supported for dedicated 2 MB/s per-shard throughput and ~70 ms latency. It uses &lt;code&gt;SubscribeToShard&lt;/code&gt;, which must be authorized separately, and Account B must pre-register the EFO consumer and share the consumer ARN.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="how-the-cross-account-access-works"&gt;How the cross-account access works&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Producer path (Account A to B):&lt;/strong&gt;&lt;/p&gt;
&lt;ol type="1"&gt;
 &lt;li&gt;The KPL app runs with &lt;code&gt;KPLProducerRole&lt;/code&gt; (Account A EC2 instance profile).&lt;/li&gt;
 &lt;li&gt;At startup it calls &lt;code&gt;sts:AssumeRole&lt;/code&gt; on &lt;code&gt;KinesisProducerCrossAccountRole&lt;/code&gt; in Account B (with external ID).&lt;/li&gt;
 &lt;li&gt;The assumed temporary credentials are used for Kinesis &lt;code&gt;PutRecords&lt;/code&gt; calls.&lt;/li&gt;
 &lt;li&gt;CloudWatch metrics are published to Account A using local instance profile credentials.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Consumer path (Account C to B):&lt;/strong&gt;&lt;/p&gt;
&lt;ol type="1"&gt;
 &lt;li&gt;The KCL app runs with &lt;code&gt;KCLConsumerRole&lt;/code&gt; (Account C EC2 instance profile).&lt;/li&gt;
 &lt;li&gt;It calls Kinesis &lt;code&gt;GetRecords&lt;/code&gt; using its local credentials (&lt;code&gt;KCLConsumerRole&lt;/code&gt;).&lt;/li&gt;
 &lt;li&gt;The stream’s resource-based policy in Account B authorizes the request.&lt;/li&gt;
 &lt;li&gt;DynamoDB lease tables and CloudWatch metrics use the local &lt;code&gt;KCLConsumerRole&lt;/code&gt; credentials (Account C).&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The following figure shows how cross-account access works.&lt;/p&gt;
&lt;div style="width: 810px" class="wp-caption alignnone"&gt;
 &lt;a href="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/10/01/BDB-6149-1.png" target="_blank" rel="noopener"&gt;&lt;img src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/10/01/BDB-6149-1.png" alt="Architecture diagram showing the producer in Account A assuming a role into the Kinesis data stream in Account B, and the consumer in Account C reading through a resource-based policy, with a shared customer managed AWS KMS key" width="800"&gt;&lt;/a&gt;
 &lt;p class="wp-caption-text"&gt;Figure 1: Cross-account streaming architecture across the producer, stream, and consumer accounts&lt;/p&gt;
&lt;/div&gt;
&lt;h2 id="deployment-walkthrough"&gt;Deployment walkthrough&lt;/h2&gt;
&lt;p&gt;The solution is deployed using &lt;a href="https://aws.amazon.com/cloudformation/" target="_blank" rel="noopener"&gt;AWS CloudFormation&lt;/a&gt; templates across three accounts. Clone the repository from &lt;a href="https://github.com/aws-samples/sample-kinesis-cross-account-kpl-kcl" target="_blank" rel="noopener"&gt;GitHub&lt;/a&gt; and follow these steps:&lt;/p&gt;
&lt;h3 id="prerequisites"&gt;Prerequisites&lt;/h3&gt;
&lt;ol type="1"&gt;
 &lt;li&gt;Three AWS accounts (Producer, Stream, Consumer) with AWS Command Line Interface (AWS CLI) configured.&lt;/li&gt;
 &lt;li&gt;AWS CLI v2 installed with named profiles for each account.&lt;/li&gt;
 &lt;li&gt;Java 11+ and Maven installed (for building KPL and KCL applications).&lt;/li&gt;
 &lt;li&gt;Clone the repository: &lt;code&gt;git clone https://github.com/aws-samples/sample-kinesis-cross-account-kpl-kcl.git&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="step-1-deploy-the-stream-stack-account-b-stream-account"&gt;Step 1: Deploy the stream stack (Account B: Stream Account)&lt;/h3&gt;
&lt;p&gt;Deploy the CloudFormation template that creates the Kinesis data stream, customer managed AWS KMS key, cross-account producer role, resource-based consumer policy, and pre-registered EFO consumer. Navigate to the &lt;code&gt;cloudformation/&lt;/code&gt; directory.&lt;/p&gt;
&lt;ol type="1"&gt;
 &lt;li&gt;Deploy:
  &lt;div class="hide-language"&gt;
   &lt;pre&gt;&lt;code class="language-bash"&gt;aws cloudformation deploy \
  --template-file cloudformation/stream-account-stack.yaml \
  --stack-name kinesis-cross-account-stream \
  --parameter-overrides \
  ProducerAccountId=&amp;lt;ACCOUNT_A_ID&amp;gt; \
  ConsumerAccountId=&amp;lt;ACCOUNT_C_ID&amp;gt; \
  --capabilities CAPABILITY_NAMED_IAM \
  --profile &amp;lt;account-b-profile&amp;gt;&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;/li&gt;
 &lt;li&gt;Note the stack outputs: &lt;code&gt;StreamArn&lt;/code&gt;, &lt;code&gt;KMSKeyArn&lt;/code&gt;, &lt;code&gt;ProducerCrossAccountRoleArn&lt;/code&gt;, and &lt;code&gt;EFOConsumerArn&lt;/code&gt;. These are needed for the next steps.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The following screenshot shows the CloudFormation output for the deployed stream stack.&lt;/p&gt;
&lt;div style="width: 810px" class="wp-caption alignnone"&gt;
 &lt;a href="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/10/01/BDB-6149-2.png" target="_blank" rel="noopener"&gt;&lt;img src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/10/01/BDB-6149-2.png" alt="CloudFormation console showing the stream stack outputs, including StreamArn, KMSKeyArn, ProducerCrossAccountRoleArn, and EFOConsumerArn" width="800"&gt;&lt;/a&gt;
 &lt;p class="wp-caption-text"&gt;Figure 2: CloudFormation outputs for the stream stack&lt;/p&gt;
&lt;/div&gt;
&lt;h3 id="step-2-deploy-the-producer-stack-account-a-producer-account"&gt;Step 2: Deploy the producer stack (Account A: Producer Account)&lt;/h3&gt;
&lt;p&gt;The KPL handles the following for your producer application:&lt;/p&gt;
&lt;ul&gt;
 &lt;li&gt;&lt;strong&gt;Aggregation&lt;/strong&gt; – Packs multiple user records into one Kinesis record (up to 1 MB) to maximize shard utilization.&lt;/li&gt;
 &lt;li&gt;&lt;strong&gt;Retries&lt;/strong&gt; – Retries failed puts with exponential backoff to absorb transient throttling.&lt;/li&gt;
 &lt;li&gt;&lt;strong&gt;Rate limiting&lt;/strong&gt; – Tracks shard limits and adjusts send rates to prevent &lt;code&gt;ProvisionedThroughputExceededException&lt;/code&gt;.&lt;/li&gt;
 &lt;li&gt;&lt;strong&gt;Metrics&lt;/strong&gt; – Publishes per-instance CloudWatch metrics (buffering time, record age, errors).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Deploy the CloudFormation template that creates the KPL application infrastructure and cross-account IAM role:&lt;/p&gt;
&lt;ol type="1"&gt;
 &lt;li&gt;Deploy:
  &lt;div class="hide-language"&gt;
   &lt;pre&gt;&lt;code class="language-bash"&gt;aws cloudformation deploy \
  --template-file cloudformation/producer-account-stack.yaml \
  --stack-name kinesis-cross-account-producer \
  --parameter-overrides \
  ProducerCrossAccountRoleArn=&amp;lt;ProducerCrossAccountRoleArn from Step 1&amp;gt; \
  StreamArn=&amp;lt;StreamArn from Step 1&amp;gt; \
  KMSKeyArn=&amp;lt;KMSKeyArn from Step 1&amp;gt; \
  --capabilities CAPABILITY_NAMED_IAM \
  --profile &amp;lt;account-a-profile&amp;gt;&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;/li&gt;
 &lt;li&gt;Connect to the EC2 instance through AWS Systems Manager, clone the repository, then build and run.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The following screenshot shows the output for the producer CloudFormation stack.&lt;/p&gt;
&lt;div style="width: 810px" class="wp-caption alignnone"&gt;
 &lt;a href="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/10/01/BDB-6149-3.png" target="_blank" rel="noopener"&gt;&lt;img src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/10/01/BDB-6149-3.png" alt="CloudFormation console showing the producer stack deployment outputs" width="800"&gt;&lt;/a&gt;
 &lt;p class="wp-caption-text"&gt;Figure 3: CloudFormation outputs for the producer stack&lt;/p&gt;
&lt;/div&gt;
&lt;h3 id="step-3-deploy-the-consumer-stack-account-c-consumer-account"&gt;Step 3: Deploy the consumer stack (Account C: Consumer Account)&lt;/h3&gt;
&lt;p&gt;KCL 3.5 handles the following for your consumer application:&lt;/p&gt;
&lt;ul&gt;
 &lt;li&gt;&lt;strong&gt;Graceful lease handover&lt;/strong&gt; – Transfers shard leases during resharding without duplicate processing.&lt;/li&gt;
 &lt;li&gt;&lt;strong&gt;Load balancing&lt;/strong&gt; – Distributes shards evenly across workers and rebalances when workers join or leave.&lt;/li&gt;
 &lt;li&gt;&lt;strong&gt;Checkpointing&lt;/strong&gt; – Persists progress in DynamoDB so a failed worker resumes from its last checkpoint.&lt;/li&gt;
 &lt;li&gt;&lt;strong&gt;Enhanced Fan-Out&lt;/strong&gt; – Supports &lt;code&gt;SubscribeToShard&lt;/code&gt; for dedicated per-shard throughput when you pass an EFO consumer ARN.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Deploy the CloudFormation template that creates the KCL application infrastructure, DynamoDB lease table permissions, and Kinesis read permissions:&lt;/p&gt;
&lt;ol type="1"&gt;
 &lt;li&gt;Deploy:
  &lt;div class="hide-language"&gt;
   &lt;pre&gt;&lt;code class="language-bash"&gt;aws cloudformation deploy \
  --template-file cloudformation/consumer-account-stack.yaml \
  --stack-name kinesis-cross-account-consumer \
  --parameter-overrides \
  StreamArn=&amp;lt;StreamArn from Step 1&amp;gt; \
  EFOConsumerArn=&amp;lt;EFOConsumerArn from Step 1&amp;gt; \
  KMSKeyArn=&amp;lt;KMSKeyArn from Step 1&amp;gt; \
  --capabilities CAPABILITY_NAMED_IAM \
  --profile &amp;lt;account-c-profile&amp;gt;&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;/li&gt;
 &lt;li&gt;Connect to the EC2 instance through Systems Manager, clone the repository, then build and run.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The following screenshot shows the output for the consumer CloudFormation stack.&lt;/p&gt;
&lt;div style="width: 810px" class="wp-caption alignnone"&gt;
 &lt;a href="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/10/01/BDB-6149-4.png" target="_blank" rel="noopener"&gt;&lt;img src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/10/01/BDB-6149-4.png" alt="CloudFormation console showing the consumer stack deployment outputs" width="800"&gt;&lt;/a&gt;
 &lt;p class="wp-caption-text"&gt;Figure 4: CloudFormation outputs for the consumer stack&lt;/p&gt;
&lt;/div&gt;
&lt;h2 id="testing-end-to-end-data-flow"&gt;Testing end-to-end data flow&lt;/h2&gt;
&lt;p&gt;After all three stacks are deployed, start the applications to verify end-to-end cross-account streaming:&lt;/p&gt;
&lt;h3 id="start-the-kpl-producer-account-a"&gt;Start the KPL producer (Account A)&lt;/h3&gt;
&lt;p&gt;Connect to the producer EC2 instance through Session Manager, then run &lt;code&gt;CrossAccountKPLProducer&lt;/code&gt;. The application assumes the cross-account role, connects to the Kinesis data stream in Account B, and begins publishing aggregated records:&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-bash"&gt;aws ssm start-session --target &amp;lt;ProducerInstanceId&amp;gt; --profile &amp;lt;account-a-profile&amp;gt;

# On the instance:
git clone https://github.com/aws-samples/sample-kinesis-cross-account-kpl-kcl.git
cd sample-kinesis-cross-account-kpl-kcl/producer
mvn clean package -q
java -jar target/cross-account-kpl-producer-1.0.jar &amp;lt;StreamArn&amp;gt; &amp;lt;region&amp;gt; &amp;lt;ProducerCrossAccountRoleArn&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div style="width: 810px" class="wp-caption alignnone"&gt;
 &lt;a href="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/10/01/BDB-6149-5.png" target="_blank" rel="noopener"&gt;&lt;img src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/10/01/BDB-6149-5.png" alt="Producer application logs showing aggregated records being published to the Kinesis data stream in Account B" width="800"&gt;&lt;/a&gt;
 &lt;p class="wp-caption-text"&gt;Figure 5: KPL producer publishing aggregated records to the cross-account stream&lt;/p&gt;
&lt;/div&gt;
&lt;h3 id="start-the-kcl-consumer-account-c"&gt;Start the KCL consumer (Account C)&lt;/h3&gt;
&lt;p&gt;Connect to the consumer EC2 instance through Session Manager, then run &lt;code&gt;CrossAccountKCLConsumer&lt;/code&gt;. The KCL application uses its local credentials, and the stream’s resource-based policy authorizes read access without role assumption:&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-bash"&gt;aws ssm start-session --target &amp;lt;ConsumerInstanceId&amp;gt; --profile &amp;lt;account-c-profile&amp;gt;

# On the instance:
git clone https://github.com/aws-samples/sample-kinesis-cross-account-kpl-kcl.git
cd sample-kinesis-cross-account-kpl-kcl/consumer
mvn clean package -q
java -jar target/cross-account-kcl-consumer-1.0.jar &amp;lt;StreamArn&amp;gt; &amp;lt;region&amp;gt; StockTradeConsumer&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;To use Enhanced Fan-Out (dedicated 2 MB/s per consumer, approximately 70 ms latency), pass &lt;code&gt;--efo&lt;/code&gt; with the pre-registered consumer ARN:&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-bash"&gt;java -jar target/cross-account-kcl-consumer-1.0.jar &amp;lt;StreamArn&amp;gt; &amp;lt;region&amp;gt; StockTradeConsumerEFO --efo &amp;lt;EFOConsumerArn&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; With resource-based policies, KCL cannot auto-register stream consumers cross-account. The stream owner (Account B) pre-registers the consumer through CloudFormation, and you pass the resulting &lt;code&gt;EFOConsumerArn&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The following screenshot shows the KCL consumer logs after the consumer has started.&lt;/p&gt;
&lt;div style="width: 810px" class="wp-caption alignnone"&gt;
 &lt;a href="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/10/01/BDB-6149-6.png" target="_blank" rel="noopener"&gt;&lt;img src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/10/01/BDB-6149-6.png" alt="KCL consumer logs showing the consumer has started and is reading records from the cross-account stream" width="800"&gt;&lt;/a&gt;
 &lt;p class="wp-caption-text"&gt;Figure 6: KCL consumer startup logs&lt;/p&gt;
&lt;/div&gt;
&lt;h3 id="verify-in-cloudwatch-account-b-stream-metrics"&gt;Verify in CloudWatch (Account B: Stream metrics)&lt;/h3&gt;
&lt;p&gt;In the stream-owning account, verify the &lt;code&gt;IncomingRecords&lt;/code&gt; and &lt;code&gt;GetRecords.Success&lt;/code&gt; metrics in Amazon CloudWatch to confirm records are flowing end to end across all three accounts.&lt;/p&gt;
&lt;p&gt;The following screenshot shows the CloudWatch metrics for the stream.&lt;/p&gt;
&lt;div style="width: 810px" class="wp-caption alignnone"&gt;
 &lt;a href="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/10/01/BDB-6149-7.png" target="_blank" rel="noopener"&gt;&lt;img src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/10/01/BDB-6149-7.png" alt="Amazon CloudWatch showing IncomingRecords and GetRecords.Success metrics confirming end-to-end data flow" width="800"&gt;&lt;/a&gt;
 &lt;p class="wp-caption-text"&gt;Figure 7: CloudWatch metrics confirming records flow end to end across all three accounts&lt;/p&gt;
&lt;/div&gt;
&lt;h2 id="key-considerations"&gt;Key considerations&lt;/h2&gt;
&lt;ul&gt;
 &lt;li&gt;&lt;strong&gt;Use stream ARN, not stream name&lt;/strong&gt;: Cross-account KCL configuration must reference the full stream ARN. Stream names resolve only within the owning account.&lt;/li&gt;
 &lt;li&gt;&lt;strong&gt;AWS KMS key sharing is mandatory for encrypted streams&lt;/strong&gt;: AWS managed keys can’t be shared. Create a customer managed key and add cross-account key policies.&lt;/li&gt;
 &lt;li&gt;&lt;strong&gt;Minimum library versions&lt;/strong&gt;: KCL 2.5.3+ or KCL 1.15.0+ is required for resource-based policy support. For best performance and cost, use KCL 3.5.&lt;/li&gt;
 &lt;li&gt;&lt;strong&gt;Enhanced Fan-Out works cross-account&lt;/strong&gt;: The stream owner must pre-register the consumer and attach a resource policy to the consumer ARN (&lt;code&gt;SubscribeToShard&lt;/code&gt;, &lt;code&gt;DescribeStreamConsumer&lt;/code&gt;). Pass the consumer ARN to KCL with &lt;code&gt;--efo&lt;/code&gt;. Auto-registration does not work cross-account with resource policies.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="clean-up"&gt;Clean up&lt;/h2&gt;
&lt;p&gt;To avoid ongoing charges, delete the CloudFormation stacks in reverse deployment order:&lt;/p&gt;
&lt;ol type="1"&gt;
 &lt;li&gt;&lt;strong&gt;Consumer Account (Account C)&lt;/strong&gt;: Delete the &lt;code&gt;kinesis-cross-account-consumer&lt;/code&gt; stack.&lt;/li&gt;
 &lt;li&gt;&lt;strong&gt;Producer Account (Account A)&lt;/strong&gt;: Delete the &lt;code&gt;kinesis-cross-account-producer&lt;/code&gt; stack.&lt;/li&gt;
 &lt;li&gt;&lt;strong&gt;Stream Account (Account B)&lt;/strong&gt;: Delete the &lt;code&gt;kinesis-cross-account-stream&lt;/code&gt; stack.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This removes all CloudFormation-managed resources across the three accounts, including the Kinesis data stream, customer managed AWS KMS key, IAM roles, and EC2 instances. Stack deletion typically completes within 5–10 minutes per account.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; The DynamoDB tables created by KCL at runtime (&lt;code&gt;StockTradeConsumer&lt;/code&gt;, &lt;code&gt;StockTradeConsumer-WorkerMetricStats&lt;/code&gt;, &lt;code&gt;StockTradeConsumer-CoordinatorState&lt;/code&gt;, and the corresponding &lt;code&gt;StockTradeConsumerEFO-*&lt;/code&gt; tables if you ran the EFO consumer) are not managed by CloudFormation and must be deleted manually from Account C.&lt;/p&gt;
&lt;h2 id="common-errors-in-cross-account"&gt;Common errors in cross-account&lt;/h2&gt;
&lt;ul&gt;
 &lt;li&gt;&lt;code&gt;AccessDenied&lt;/code&gt; on &lt;code&gt;AssumeRole&lt;/code&gt; (producer): Account B’s role trust policy is missing Account A’s principal or the external ID doesn’t match.&lt;/li&gt;
 &lt;li&gt;&lt;code&gt;AccessDenied&lt;/code&gt; on reads despite the resource policy (consumer): Granted on the stream but not on the consumer’s own identity policy. Cross-account needs both.&lt;/li&gt;
 &lt;li&gt;AWS KMS &lt;code&gt;AccessDenied&lt;/code&gt;: The key policy in the stream account does not grant the foreign producer or consumer &lt;code&gt;GenerateDataKey&lt;/code&gt;/&lt;code&gt;Decrypt&lt;/code&gt;.&lt;/li&gt;
 &lt;li&gt;EFO failures: Resource policy missing &lt;code&gt;SubscribeToShard&lt;/code&gt;, or the EFO consumer was not pre-registered or shared.&lt;/li&gt;
 &lt;li&gt;Expired temporary credentials (producer): Long-running KPL must refresh assumed-role credentials, not assume once at startup.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="whats-new-in-kpl-and-kcl"&gt;What’s new in KPL and KCL&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Kinesis Client Library (KCL) 3.5:&lt;/strong&gt; The newest minor version of KCL introduces a &lt;strong&gt;streamlined single-table format and leader-driven migration paths&lt;/strong&gt; for high-scale stream processing applications. KCL 3.5 consolidates all internal tracking (including worker leases, coordinator states, and resource utilization metrics) into a single unified Amazon DynamoDB table, rather than spreading data across multiple tables. This translates directly into lower baseline AWS database costs, reduced operational complexity, and simpler IAM cross-account permission models for enterprise stream consumer fleets.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Kinesis Client Library (KCL) 3.0&lt;/strong&gt;: The latest major version of KCL introduces &lt;a href="https://aws.amazon.com/blogs/big-data/reduce-your-compute-costs-for-stream-processing-applications-with-kinesis-client-library-3-0" target="_blank" rel="noopener"&gt;graceful load balancing and reduced compute costs&lt;/a&gt; for stream processing applications. KCL 3.0 dynamically assigns shards based on actual CPU and memory utilization rather than distributing them evenly across workers. As a result, you can process the same streaming volume with fewer compute instances. This translates directly into lower infrastructure spend for cross-account architectures where consumer fleets must be right-sized.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Kinesis Client Library (KCL) 2.5.3+&lt;/strong&gt;: Added native support for cross-account access through &lt;a href="https://docs.aws.amazon.com/streams/latest/dev/resource-based-policy-examples.html" target="_blank" rel="noopener"&gt;resource-based policies&lt;/a&gt;, so KCL consumers can read from streams in other accounts without complex IAM role chaining. For KCL 1.x users, version 1.15.0 or higher is required for cross-account support.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Kinesis Producer Library (KPL) 1.x&lt;/strong&gt;: KPL continues to provide high-throughput, batched record aggregation with automatic retries. When used cross-account, KPL assumes an IAM role in the stream-owning account to publish records, using &lt;a href="https://docs.aws.amazon.com/STS/latest/APIReference/Welcome.html" target="_blank" rel="noopener"&gt;AWS Security Token Service (AWS STS)&lt;/a&gt; for temporary credential exchange.&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;In this post, we demonstrated how to build a cross-account streaming pipeline using Amazon Kinesis Data Streams with KPL and KCL deployed across three separate AWS accounts. With this pattern, you can maintain security isolation, cost attribution, and operational independence while processing real-time data at scale. The resource-based policy model introduced for Kinesis Data Streams simplifies cross-account access without complex IAM role chaining, and KCL’s intelligent load balancing helps you right-size consumer compute regardless of deployment topology.&lt;/p&gt;
&lt;p&gt;The solution code is available in the &lt;a href="https://github.com/aws-samples/sample-kinesis-cross-account-kpl-kcl" target="_blank" rel="noopener"&gt;sample-kinesis-cross-account-kpl-kcl repository on GitHub&lt;/a&gt;. Clone it, customize it for your account structure, and deploy a cross-account streaming pipeline with maximum security isolation.&lt;/p&gt;
&lt;p style="clear: both"&gt;&lt;/p&gt;
&lt;hr style="width: 100%"&gt;
&lt;h2&gt;About the authors&lt;/h2&gt;
&lt;footer&gt;
 &lt;div class="blog-author-box" style="padding-top: 2.0em"&gt;
  &lt;div class="blog-author-image" style="margin-right: 1.0em"&gt;
   &lt;img loading="lazy" class="alignnone size-full" src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/10/01/BDB-6149-8.jpg" alt="Charishma Makineni" width="100" height="133"&gt;
  &lt;/div&gt;
  &lt;h3 class="lb-h4"&gt;Charishma Makineni&lt;/h3&gt;
  &lt;p style="overflow: hidden"&gt;Charishma is a Sr.&amp;nbsp;Specialist Solutions Architect for streaming at AWS. She helps customers build real-time streaming architectures using services like Amazon Kinesis Data Streams and Amazon Managed Streaming for Apache Kafka (Amazon MSK). Outside of work, Charishma enjoys being outdoors, gardening, and reading.&lt;/p&gt;
 &lt;/div&gt;
 &lt;div class="blog-author-box" style="padding-top: 2.0em"&gt;
  &lt;div class="blog-author-image" style="margin-right: 1.0em"&gt;
   &lt;img loading="lazy" class="alignnone size-full" src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/10/01/BDB-6149-9.jpg" alt="Chinmayi Narasimhadevara" width="100" height="133"&gt;
  &lt;/div&gt;
  &lt;h3 class="lb-h4"&gt;Chinmayi Narasimhadevara&lt;/h3&gt;
  &lt;p style="overflow: hidden"&gt;Chinmayi is a Solutions Architect for Data and Analytics, focused on Generative AI, at Amazon Web Services. She has over 20 years of experience in information technology and helps AWS customers build advanced, highly scalable, and performant data foundations for their GenAI and analytics workloads.&lt;/p&gt;
 &lt;/div&gt;
 &lt;div class="blog-author-box" style="padding-top: 2.0em"&gt;
  &lt;div class="blog-author-image" style="margin-right: 1.0em"&gt;
   &lt;img loading="lazy" class="alignnone size-full" src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/10/01/BDB-6149-10.jpg" alt="Pratik Patel" width="100" height="133"&gt;
  &lt;/div&gt;
  &lt;h3 class="lb-h4"&gt;Pratik Patel&lt;/h3&gt;
  &lt;p style="overflow: hidden"&gt;&lt;a href="https://www.linkedin.com/in/pratikpatel-aws/" target="_blank" rel="noopener"&gt;Pratik&lt;/a&gt; is a Sr.&amp;nbsp;Technical Account Manager and streaming analytics specialist. He works with AWS customers and provides ongoing support and technical guidance to help plan and build solutions using best practices and proactively keep customers’ AWS environments operationally healthy.&lt;/p&gt;
 &lt;/div&gt;
 &lt;div class="blog-author-box" style="padding-top: 2.0em"&gt;
  &lt;div class="blog-author-image" style="margin-right: 1.0em"&gt;
   &lt;img loading="lazy" class="alignnone size-full" src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/10/01/BDB-6149-11.jpg" alt="Priyanka Chaudhary" width="100" height="133"&gt;
  &lt;/div&gt;
  &lt;h3 class="lb-h4"&gt;Priyanka Chaudhary&lt;/h3&gt;
  &lt;p style="overflow: hidden"&gt;Priyanka is a Senior Solutions Architect at AWS. She is specialized in data lake and analytics services and helps many customers in this area. As a Solutions Architect, she plays a crucial role in guiding strategic customers through their cloud journey by designing scalable and secure cloud solutions. Outside of work, she loves spending time with friends and family, watching movies, and traveling.&lt;/p&gt;
 &lt;/div&gt;
&lt;/footer&gt;</content:encoded>
					
		
		
			</item>
		<item>
		<title>Monitoring MWAA-orchestrated ETL pipelines with Amazon OpenSearch Service</title>
		<link>https://aws.amazon.com/blogs/big-data/monitoring-mwaa-orchestrated-etl-pipelines-with-amazon-opensearch-service/</link>
		
		<dc:creator><![CDATA[Anupa Bhattacharyya]]></dc:creator>
		<pubDate>Tue, 06 Oct 2026 16:17:46 +0000</pubDate>
				<category><![CDATA[Advanced (300)]]></category>
		<category><![CDATA[Amazon Managed Workflows for Apache Airflow (Amazon MWAA)]]></category>
		<category><![CDATA[Amazon OpenSearch Service]]></category>
		<category><![CDATA[Technical How-to]]></category>
		<guid isPermaLink="false">8195c580cb9263f06a4fd4baa9b4ac1104f35ef2</guid>

					<description>Troubleshooting a multi-service, MWAA-orchestrated ETL pipeline often means hunting across scattered Amazon CloudWatch log groups. This post shows how to centralize your ETL logs in Amazon OpenSearch Service and query them in plain language through an MCP server on Amazon Bedrock AgentCore, so you find root causes faster.</description>
										<content:encoded>&lt;p&gt;When your MWAA-orchestrated extract, transform, and load (ETL) pipeline spans multiple AWS services, troubleshooting a failure becomes a scavenger hunt. &lt;a href="https://aws.amazon.com/glue/" target="_blank" rel="noopener"&gt;AWS Glue&lt;/a&gt; jobs transform data, custom scripts run on &lt;a href="https://aws.amazon.com/pm/ec2/" target="_blank" rel="noopener"&gt;Amazon Elastic Compute Cloud (Amazon EC2)&lt;/a&gt;, and directed acyclic graphs (DAGs) in &lt;a href="https://aws.amazon.com/managed-workflows-for-apache-airflow/" target="_blank" rel="noopener"&gt;Amazon Managed Workflows for Apache Airflow&lt;/a&gt; (Amazon MWAA) coordinate the workflow, but each service writes logs to its own &lt;a href="https://aws.amazon.com/cloudwatch/" target="_blank" rel="noopener"&gt;Amazon CloudWatch&lt;/a&gt; log group. When something breaks at 2 AM, your team spends valuable time locating the right log stream before they can even begin diagnosing the root cause.&lt;/p&gt;
&lt;p&gt;These observability challenges aren’t unique to any one team. DevOps engineers routinely juggle multiple tools and must analyze numerous logs to identify and resolve an issue. Every pivot between tools or logs costs minutes during an outage and directly inflates mean time to resolution. Interpreting logs is another challenge. Even when engineers locate the right log stream, parsing the output requires deep familiarity with each service’s logging conventions. A single failed task can scatter relevant context across dozens of verbose, interleaved log entries that obscure the root cause rather than reveal it.&lt;/p&gt;
&lt;p&gt;This solution helps you remediate errors in an analytics pipeline by using an AI agent to speed up root cause identification, interpret relevant logs, and recommend how to resolve the issue. In this post, you learn how to implement this analytics observability solution. The target audience is data engineers, DevOps engineers, and cloud engineers.&lt;/p&gt;
&lt;p&gt;You deploy a set of provided &lt;a href="https://aws.amazon.com/cloudformation/" target="_blank" rel="noopener"&gt;AWS CloudFormation&lt;/a&gt; templates and an &lt;a href="https://docs.aws.amazon.com/sagemaker/latest/dg/notebooks.html" target="_blank" rel="noopener"&gt;Amazon SageMaker AI notebook&lt;/a&gt; to implement a sample architecture and create a set of demo ETL jobs orchestrated by Amazon MWAA. The CloudFormation templates deploy the architectural components, and the SageMaker AI notebook contains code to configure the components and interact with the MCP server.&lt;/p&gt;
&lt;h2 id="solution-overview"&gt;Solution overview&lt;/h2&gt;
&lt;p&gt;This solution uses CloudWatch real-time streaming to centralize the logs in &lt;a href="https://aws.amazon.com/opensearch-service/" target="_blank" rel="noopener"&gt;Amazon OpenSearch Service&lt;/a&gt;. With an OpenSearch MCP server running on &lt;a href="https://aws.amazon.com/bedrock/agentcore/" target="_blank" rel="noopener"&gt;Amazon Bedrock AgentCore&lt;/a&gt;, engineers can identify issues and receive recommendations through the ETL analysis agent.&lt;/p&gt;
&lt;div style="width: 563px" class="wp-caption alignnone"&gt;
 &lt;a href="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/10/01/BDB-5911-1.png" target="_blank" rel="noopener"&gt;&lt;img src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/10/01/BDB-5911-1.png" alt="Architecture diagram showing ETL logs streaming through CloudWatch into Amazon OpenSearch Service, queried by an MCP server on Amazon Bedrock AgentCore" width="553"&gt;&lt;/a&gt;
 &lt;p class="wp-caption-text"&gt;Figure 1: Solution architecture that streams ETL logs into Amazon OpenSearch Service and queries them through an MCP server on Amazon Bedrock AgentCore&lt;/p&gt;
&lt;/div&gt;
&lt;h2 id="prerequisites"&gt;Prerequisites&lt;/h2&gt;
&lt;p&gt;Before deploying this solution, make sure you have the following in place:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;AWS account and AWS Region&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;An active AWS account with access to the US East (N. Virginia) us-east-1 Region. CloudFormation stacks must be deployed in us-east-1.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;IAM permissions&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;An AWS Identity and Access Management (IAM) user or role with permissions to create and manage the following AWS resources:&lt;/p&gt;
&lt;ul&gt;
 &lt;li&gt;Amazon OpenSearch Service (domain creation, fine-grained access control).&lt;/li&gt;
 &lt;li&gt;Amazon MWAA (environment creation, DAG execution).&lt;/li&gt;
 &lt;li&gt;Amazon MWAA Serverless (workflow creation and execution, a versioned S3 bucket for the workflow definition, and a workflow execution role that requires &lt;code&gt;iam:PassRole&lt;/code&gt;).&lt;/li&gt;
 &lt;li&gt;AWS Glue (job creation and execution).&lt;/li&gt;
 &lt;li&gt;Amazon EC2 (instance launch, security groups).&lt;/li&gt;
 &lt;li&gt;Amazon Simple Storage Service (Amazon S3) (bucket creation, object management).&lt;/li&gt;
 &lt;li&gt;AWS Lambda (function creation and execution).&lt;/li&gt;
 &lt;li&gt;Amazon CloudWatch Logs (log group creation, subscription filters).&lt;/li&gt;
 &lt;li&gt;Amazon SageMaker AI (notebook instance creation).&lt;/li&gt;
 &lt;li&gt;Amazon Bedrock (model access, and the AgentCore runtime, a capability of Amazon Bedrock AgentCore).&lt;/li&gt;
 &lt;li&gt;Amazon Cognito (user pool creation).&lt;/li&gt;
 &lt;li&gt;Amazon Elastic Container Registry (Amazon ECR) (repository creation).&lt;/li&gt;
 &lt;li&gt;AWS CodeBuild (project creation).&lt;/li&gt;
 &lt;li&gt;AWS CloudFormation (stack creation with IAM resources).&lt;/li&gt;
 &lt;li&gt;AWS Secrets Manager (secret creation).&lt;/li&gt;
 &lt;li&gt;IAM (role and policy creation).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Amazon Bedrock model access&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Enable access to the Anthropic Claude Sonnet model in the Amazon Bedrock console. Navigate to &lt;strong&gt;Model access&lt;/strong&gt; in the Amazon Bedrock console and request access if it isn’t already enabled.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;CloudFormation templates&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Download the three CloudFormation template files (&lt;code&gt;opensearch_cfn.yaml&lt;/code&gt;, &lt;code&gt;etl.yaml&lt;/code&gt;, and &lt;code&gt;agentcore-mcp-server.yaml&lt;/code&gt;) from the provided &lt;a href="https://github.com/aws-samples/sample-etl-troubleshooting-using-genai" target="_blank" rel="noopener"&gt;GitHub repository&lt;/a&gt; before beginning deployment.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Networking&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The ETL stack creates a new virtual private cloud (VPC) (CIDR 10.192.0.0/16 by default). Check that this CIDR range doesn’t conflict with existing VPCs in your account if you plan to set up VPC peering or connectivity.&lt;/p&gt;
&lt;h2 id="architecture"&gt;Architecture&lt;/h2&gt;
&lt;p&gt;The architecture uses Amazon MWAA (provisioned and serverless) as the orchestration layer. As a managed Apache Airflow service, Amazon MWAA lets teams author complex, dependency-aware pipelines as code and schedule, retry, and monitor them without provisioning or operating any Airflow infrastructure. An Airflow DAG defines the pipeline workflow, triggering AWS Glue ETL jobs and Python scripts running on Amazon EC2 instances. Each of these components generates logs that flow into Amazon CloudWatch Logs: Amazon MWAA through its native integration, AWS Glue through its default log group configuration, and Amazon EC2 through the &lt;a href="https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/install-CloudWatch-Agent-on-EC2-Instance.html" target="_blank" rel="noopener"&gt;CloudWatch agent&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;CloudWatch subscription filters provide the bridge between log storage and analysis. When configured, these filters immediately start streaming real-time log data from selected log groups to Amazon OpenSearch Service. This approach means that data doesn’t need to be copied or duplicated. The subscription filter creates a real-time streaming pipeline that indexes logs as they arrive.&lt;/p&gt;
&lt;p&gt;Within OpenSearch, the ML Connector framework integrates with Amazon Bedrock to provide large language model (LLM)-based inference over the log indices. The OpenSearch MCP (Model Context Protocol) server then exposes these capabilities to AI assistants, so users can query their pipeline logs using natural language to identify errors, understand failure patterns, and receive contextual remediation suggestions.&lt;/p&gt;
&lt;h3 id="orchestration-and-log-generation"&gt;Orchestration and log generation&lt;/h3&gt;
&lt;p&gt;The architecture begins with Amazon MWAA as the orchestration layer. An Airflow DAG defines the pipeline workflow, triggering AWS Glue ETL jobs and Python scripts running on Amazon EC2 instances. Each of these components generates logs that flow into Amazon CloudWatch Logs.&lt;/p&gt;
&lt;h3 id="workflow"&gt;Workflow&lt;/h3&gt;
&lt;p&gt;The Amazon MWAA DAG triggers the ETL workflow on a scheduled or event-driven basis. AWS Glue jobs run Spark-based transformations, and logs flow automatically to the &lt;code&gt;/aws-glue/&lt;/code&gt; CloudWatch log group. In parallel, Amazon EC2 Python scripts run custom processing logic and send their logs to CloudWatch. Amazon MWAA task logs automatically land in &lt;code&gt;/airflow/{env}/&lt;/code&gt; log groups. Finally, the CloudWatch unified agent ships logs to the designated log group, where they can be queried through the OpenSearch MCP server.&lt;/p&gt;
&lt;h3 id="log-integration-methods"&gt;Log integration methods&lt;/h3&gt;
&lt;table border="1px" width="100%" cellpadding="10px"&gt;
 &lt;tbody&gt;
  &lt;tr&gt;
   &lt;td&gt;&lt;strong&gt;Component&lt;/strong&gt;&lt;/td&gt;
   &lt;td&gt;&lt;strong&gt;Integration&lt;/strong&gt;&lt;/td&gt;
   &lt;td&gt;&lt;strong&gt;Log group&lt;/strong&gt;&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;Amazon MWAA&lt;/td&gt;
   &lt;td&gt;Native integration&lt;/td&gt;
   &lt;td&gt;/airflow/{env}/task&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;AWS Glue&lt;/td&gt;
   &lt;td&gt;Default log configuration&lt;/td&gt;
   &lt;td&gt;/aws-glue/jobs/output&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;Amazon EC2 scripts&lt;/td&gt;
   &lt;td&gt;CloudWatch unified agent&lt;/td&gt;
   &lt;td&gt;/ec2/etl-scripts&lt;/td&gt;
  &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;div style="width: 810px" class="wp-caption alignnone"&gt;
 &lt;a href="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/10/01/BDB-5911-2.jpg" target="_blank" rel="noopener"&gt;&lt;img src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/10/01/BDB-5911-2.jpg" alt="Diagram showing Amazon MWAA, AWS Glue, and Amazon EC2 components sending logs to separate Amazon CloudWatch log groups" width="800"&gt;&lt;/a&gt;
 &lt;p class="wp-caption-text"&gt;Figure 2: Log generation and integration across Amazon MWAA, AWS Glue, and Amazon EC2 components&lt;/p&gt;
&lt;/div&gt;
&lt;h3 id="real-time-log-streaming"&gt;Real-time log streaming&lt;/h3&gt;
&lt;p&gt;CloudWatch subscription filters provide the bridge between log storage and analysis. When configured, these filters immediately start streaming real-time log data from selected log groups to Amazon OpenSearch Service. This approach means that data doesn’t need to be copied or duplicated. The subscription filter creates a real-time streaming pipeline that indexes logs as they arrive.&lt;/p&gt;
&lt;h4 id="process"&gt;Process&lt;/h4&gt;
&lt;ol type="1"&gt;
 &lt;li&gt;Subscription filters are configured on each CloudWatch log group to match incoming log events.&lt;/li&gt;
 &lt;li&gt;An AWS Lambda function decompresses gzip-encoded log data, parses and enriches records, and formats them for the OpenSearch Bulk API.&lt;/li&gt;
 &lt;li&gt;Transformed data is bulk-indexed into Amazon OpenSearch Service domain indices (&lt;code&gt;airflow-logs-*&lt;/code&gt;, &lt;code&gt;glue-logs-*&lt;/code&gt;, &lt;code&gt;ec2-logs-*&lt;/code&gt;, &lt;code&gt;unified-etl-*&lt;/code&gt;).&lt;/li&gt;
&lt;/ol&gt;
&lt;div style="width: 810px" class="wp-caption alignnone"&gt;
 &lt;a href="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/10/01/BDB-5911-3.png" target="_blank" rel="noopener"&gt;&lt;img src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/10/01/BDB-5911-3.png" alt="Diagram showing CloudWatch subscription filters streaming log data through an AWS Lambda function into Amazon OpenSearch Service indices" width="800"&gt;&lt;/a&gt;
 &lt;p class="wp-caption-text"&gt;Figure 3: Real-time log streaming from CloudWatch through AWS Lambda into Amazon OpenSearch Service indices&lt;/p&gt;
&lt;/div&gt;
&lt;h3 id="ai-powered-analysis-and-mcp-interface"&gt;AI-powered analysis and MCP interface&lt;/h3&gt;
&lt;p&gt;Within OpenSearch, the ML Connector framework integrates with Amazon Bedrock to provide LLM-based inference over the log indices. The OpenSearch MCP (Model Context Protocol) server then exposes these capabilities to AI assistants, so users can query their pipeline logs using natural language to identify errors, understand failure patterns, and receive contextual remediation suggestions.&lt;/p&gt;
&lt;h4 id="process-1"&gt;Process&lt;/h4&gt;
&lt;ol type="1"&gt;
 &lt;li&gt;A user submits a natural language query through the AI assistant (for example, “Why did the Glue job fail at 3 AM?”).&lt;/li&gt;
 &lt;li&gt;The MCP server translates the query into OpenSearch DSL with ML-enhanced ranking and semantic search.&lt;/li&gt;
 &lt;li&gt;The ML Connector invokes Amazon Bedrock (Claude) for semantic understanding, log summarization, and pattern detection.&lt;/li&gt;
 &lt;li&gt;The AI assistant returns root cause analysis with specific remediation steps to the user.&lt;/li&gt;
&lt;/ol&gt;
&lt;div style="width: 810px" class="wp-caption alignnone"&gt;
 &lt;a href="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/10/01/BDB-5911-4.jpg" target="_blank" rel="noopener"&gt;&lt;img src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/10/01/BDB-5911-4.jpg" alt="Diagram showing a natural language query flowing through the MCP server and ML Connector to Amazon Bedrock and returning analysis" width="800"&gt;&lt;/a&gt;
 &lt;p class="wp-caption-text"&gt;Figure 4: AI-powered log analysis flow from a natural language query to root cause and remediation guidance&lt;/p&gt;
&lt;/div&gt;
&lt;h3 id="key-architectural-benefits"&gt;Key architectural benefits&lt;/h3&gt;
&lt;ul&gt;
 &lt;li&gt;Zero data duplication: Subscription filters stream data directly without batch exports, S3 staging, or data copying.&lt;/li&gt;
 &lt;li&gt;Near real-time: Logs are indexed in OpenSearch shortly after generation in source systems.&lt;/li&gt;
 &lt;li&gt;Natural language: Users query logs conversationally through MCP, with no need to write OpenSearch DSL manually.&lt;/li&gt;
 &lt;li&gt;Reduced mean time to resolution: Root cause and remediation recommendations are delivered in a single query, reducing mean time to resolution.&lt;/li&gt;
 &lt;li&gt;Unified view: ETL components are observable through a single search interface.&lt;/li&gt;
 &lt;li&gt;Managed services: There’s no infrastructure to provision, patch, or maintain.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="cloudformation-stacks"&gt;CloudFormation stacks&lt;/h2&gt;
&lt;p&gt;The solution is split into three &lt;a href="https://github.com/aws-samples/sample-etl-troubleshooting-using-genai/tree/main/assets/cfn" target="_blank" rel="noopener"&gt;CloudFormation stacks&lt;/a&gt;. Each template provides a distinct layer of the pipeline.&lt;/p&gt;
&lt;table border="1px" width="100%" cellpadding="10px"&gt;
 &lt;tbody&gt;
  &lt;tr&gt;
   &lt;td&gt;&lt;strong&gt;Stack&lt;/strong&gt;&lt;/td&gt;
   &lt;td&gt;&lt;strong&gt;Template file&lt;/strong&gt;&lt;/td&gt;
   &lt;td&gt;&lt;strong&gt;Deploy time&lt;/strong&gt;&lt;/td&gt;
   &lt;td&gt;&lt;strong&gt;Purpose&lt;/strong&gt;&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;opensearch-cfn&lt;/td&gt;
   &lt;td&gt;&lt;code&gt;opensearch_cfn.yaml&lt;/code&gt;&lt;/td&gt;
   &lt;td&gt;~15–20 min&lt;/td&gt;
   &lt;td&gt;OpenSearch domain, SageMaker AI notebook, IAM roles&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;etl&lt;/td&gt;
   &lt;td&gt;&lt;code&gt;etl.yaml&lt;/code&gt;&lt;/td&gt;
   &lt;td&gt;~25–30 min&lt;/td&gt;
   &lt;td&gt;VPC, Amazon MWAA, AWS Glue, Amazon EC2, log streaming pipeline&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;agentcore-mcp-server&lt;/td&gt;
   &lt;td&gt;&lt;code&gt;agentcore-mcp-server.yaml&lt;/code&gt;&lt;/td&gt;
   &lt;td&gt;~8–12 min&lt;/td&gt;
   &lt;td&gt;Amazon Bedrock AgentCore MCP Server with Amazon Cognito authentication&lt;/td&gt;
  &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id="stack-1-opensearch-cfn-opensearch_cfn.yaml"&gt;Stack 1: opensearch-cfn (opensearch_cfn.yaml)&lt;/h3&gt;
&lt;p&gt;This stack provisions the foundational OpenSearch domain along with a classic SageMaker AI notebook instance for interactive exploration. This stack creates:&lt;/p&gt;
&lt;ul&gt;
 &lt;li&gt;An Amazon OpenSearch Service domain (OpenSearch 3.5) with fine-grained access control.&lt;/li&gt;
 &lt;li&gt;A SageMaker AI notebook instance preloaded with workshop lab notebooks.&lt;/li&gt;
 &lt;li&gt;S3 buckets for ETL data, DAGs, and more.&lt;/li&gt;
 &lt;li&gt;IAM roles for notebook and Amazon Bedrock access.&lt;/li&gt;
 &lt;li&gt;A Secrets Manager secret for OpenSearch credentials.&lt;/li&gt;
&lt;/ul&gt;
&lt;table border="1px" width="100%" cellpadding="10px"&gt;
 &lt;tbody&gt;
  &lt;tr&gt;
   &lt;td&gt;&lt;strong&gt;Parameter&lt;/strong&gt;&lt;/td&gt;
   &lt;td&gt;&lt;strong&gt;Default&lt;/strong&gt;&lt;/td&gt;
   &lt;td&gt;&lt;strong&gt;Description&lt;/strong&gt;&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;OpenSearchUsername&lt;/td&gt;
   &lt;td&gt;admin&lt;/td&gt;
   &lt;td&gt;Admin username for the OpenSearch cluster&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;OpenSearchPassword&lt;/td&gt;
   &lt;td&gt;(secure)&lt;/td&gt;
   &lt;td&gt;Admin password (8–32 chars, letters + numbers + symbols)&lt;/td&gt;
  &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id="stack-2-etl-etl.yaml"&gt;Stack 2: etl (etl.yaml)&lt;/h3&gt;
&lt;p&gt;This stack provisions the ETL resources: the networking, orchestration, compute, and log streaming pipeline that feeds OpenSearch. This stack creates:&lt;/p&gt;
&lt;ul&gt;
 &lt;li&gt;A VPC with private subnets and a NAT gateway for Amazon MWAA networking.&lt;/li&gt;
 &lt;li&gt;An Amazon MWAA environment running an Airflow DAG.&lt;/li&gt;
 &lt;li&gt;An AWS Glue ETL job (reads XLSX, drops a column, writes JSON).&lt;/li&gt;
 &lt;li&gt;An Amazon EC2 instance running a parallel Python ETL script.&lt;/li&gt;
 &lt;li&gt;An Amazon MWAA Serverless workflow for aggregation.&lt;/li&gt;
 &lt;li&gt;S3 buckets for DAG storage and ETL data.&lt;/li&gt;
&lt;/ul&gt;
&lt;table border="1px" width="100%" cellpadding="10px"&gt;
 &lt;tbody&gt;
  &lt;tr&gt;
   &lt;td&gt;&lt;strong&gt;Parameter&lt;/strong&gt;&lt;/td&gt;
   &lt;td&gt;&lt;strong&gt;Default&lt;/strong&gt;&lt;/td&gt;
   &lt;td&gt;&lt;strong&gt;Description&lt;/strong&gt;&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;EC2InstanceType&lt;/td&gt;
   &lt;td&gt;t3.micro&lt;/td&gt;
   &lt;td&gt;EC2 instance type for the ETL script&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;VpcCIDR&lt;/td&gt;
   &lt;td&gt;10.192.0.0/16&lt;/td&gt;
   &lt;td&gt;CIDR block for the Amazon MWAA VPC&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;OpenSearchStackName&lt;/td&gt;
   &lt;td&gt;opensearch-cfn&lt;/td&gt;
   &lt;td&gt;Name of the OpenSearch stack (for cross-stack imports)&lt;/td&gt;
  &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id="stack-3-agentcore-mcp-server-agentcore-mcp-server.yaml"&gt;Stack 3: agentcore-mcp-server (agentcore-mcp-server.yaml)&lt;/h3&gt;
&lt;p&gt;This stack deploys an Amazon Bedrock AgentCore MCP Server that exposes OpenSearch tools (ListIndexTool, IndexMappingTool, SearchIndexTool) for natural language log queries. It includes Amazon Cognito authentication and a containerized MCP server built through CodeBuild. This stack creates:&lt;/p&gt;
&lt;ul&gt;
 &lt;li&gt;An Amazon Bedrock AgentCore runtime hosting the OpenSearch MCP server.&lt;/li&gt;
 &lt;li&gt;An Amazon Cognito user pool for OAuth authentication.&lt;/li&gt;
 &lt;li&gt;An Amazon ECR repository for the MCP server container.&lt;/li&gt;
 &lt;li&gt;A CodeBuild project to build and deploy the container.&lt;/li&gt;
&lt;/ul&gt;
&lt;table border="1px" width="100%" cellpadding="10px"&gt;
 &lt;tbody&gt;
  &lt;tr&gt;
   &lt;td&gt;&lt;strong&gt;Parameter&lt;/strong&gt;&lt;/td&gt;
   &lt;td&gt;&lt;strong&gt;Default&lt;/strong&gt;&lt;/td&gt;
   &lt;td&gt;&lt;strong&gt;Description&lt;/strong&gt;&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;MultimodalStackName&lt;/td&gt;
   &lt;td&gt;opensearch-cfn&lt;/td&gt;
   &lt;td&gt;OpenSearch stack name (for importing domain endpoint)&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;AgentCoreMCPServerName&lt;/td&gt;
   &lt;td&gt;opensearch_mcp_server&lt;/td&gt;
   &lt;td&gt;MCP server name (max 35 chars, appended with unique ID)&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;AmazonOpenSearchEndpoint&lt;/td&gt;
   &lt;td&gt;(auto-import)&lt;/td&gt;
   &lt;td&gt;Leave blank to auto-import from opensearch-cfn stack&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;ExecutionRole&lt;/td&gt;
   &lt;td&gt;(auto-create)&lt;/td&gt;
   &lt;td&gt;Leave blank to create a new role&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;ECRRepository&lt;/td&gt;
   &lt;td&gt;(auto-create)&lt;/td&gt;
   &lt;td&gt;Leave blank to create a new ECR repo&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;OAuthDiscoveryURL&lt;/td&gt;
   &lt;td&gt;(auto-create Cognito)&lt;/td&gt;
   &lt;td&gt;Leave blank to create a new Amazon Cognito user pool&lt;/td&gt;
  &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;Deployment order:&lt;/strong&gt; opensearch-cfn, followed by etl and agentcore-mcp-server. The etl and agentcore-mcp-server CloudFormation templates use outputs from opensearch-cfn.&lt;/p&gt;
&lt;h2 id="implementation"&gt;Implementation&lt;/h2&gt;
&lt;h3 id="deploy-each-cloudformation-stack-in-sequence"&gt;Deploy each CloudFormation stack in sequence&lt;/h3&gt;
&lt;ol type="1"&gt;
 &lt;li&gt;Deploy the OpenSearch infrastructure for opensearch-cfn.&lt;/li&gt;
 &lt;li&gt;Go to the AWS CloudFormation console, confirm you are in us-east-1, and confirm that Amazon Bedrock is available.
  &lt;p&gt;&lt;/p&gt;
  &lt;div style="width: 810px" class="wp-caption alignnone"&gt;
   &lt;a href="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/10/01/BDB-5911-5.png" target="_blank" rel="noopener"&gt;&lt;img src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/10/01/BDB-5911-5.png" alt="AWS CloudFormation console with the Region set to US East (N. Virginia)" width="800"&gt;&lt;/a&gt;
   &lt;p class="wp-caption-text"&gt;Figure 5: Confirming the us-east-1 Region in the AWS CloudFormation console&lt;/p&gt;
  &lt;/div&gt;&lt;/li&gt;
 &lt;li&gt;Create the stack. Choose &lt;strong&gt;Create stack&lt;/strong&gt; (with new resources), and then choose an existing template. Select &lt;strong&gt;Upload a template file&lt;/strong&gt;, choose the opensearch-cfn YAML file, and choose &lt;strong&gt;Next&lt;/strong&gt;.
  &lt;p&gt;&lt;/p&gt;
  &lt;div style="width: 810px" class="wp-caption alignnone"&gt;
   &lt;a href="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/10/01/BDB-5911-6.png" target="_blank" rel="noopener"&gt;&lt;img src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/10/01/BDB-5911-6.png" alt="Create stack page in AWS CloudFormation with Upload a template file selected" width="800"&gt;&lt;/a&gt;
   &lt;p class="wp-caption-text"&gt;Figure 6: Uploading the opensearch-cfn template on the Create stack page&lt;/p&gt;
  &lt;/div&gt;&lt;/li&gt;
 &lt;li&gt;Configure stack options. For stack name, enter &lt;code&gt;opensearch-cfn&lt;/code&gt;, leave the parameters as their defaults, and choose &lt;strong&gt;Next&lt;/strong&gt;.
  &lt;p&gt;&lt;/p&gt;
  &lt;div style="width: 810px" class="wp-caption alignnone"&gt;
   &lt;a href="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/10/01/BDB-5911-7.png" target="_blank" rel="noopener"&gt;&lt;img src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/10/01/BDB-5911-7.png" alt="Configure stack options page showing the stack name opensearch-cfn" width="800"&gt;&lt;/a&gt;
   &lt;p class="wp-caption-text"&gt;Figure 7: Entering the stack name on the Configure stack options page&lt;/p&gt;
  &lt;/div&gt;&lt;/li&gt;
 &lt;li&gt;Review and deploy. Review the parameter summary, then scroll to the bottom and select &lt;strong&gt;I acknowledge that AWS CloudFormation might create IAM resources with custom names&lt;/strong&gt;. Choose &lt;strong&gt;Next&lt;/strong&gt;, and then choose &lt;strong&gt;Submit&lt;/strong&gt;.
  &lt;div style="width: 810px" class="wp-caption alignnone"&gt;
   &lt;a href="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/10/01/BDB-5911-8.png" target="_blank" rel="noopener"&gt;&lt;img src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/10/01/BDB-5911-8.png" alt="CloudFormation review page with the IAM capabilities acknowledgment checkbox selected" width="800"&gt;&lt;/a&gt;
   &lt;p class="wp-caption-text"&gt;Figure 8: Acknowledging IAM resource creation on the review page&lt;/p&gt;
  &lt;/div&gt;
  &lt;div style="width: 810px" class="wp-caption alignnone"&gt;
   &lt;a href="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/10/01/BDB-5911-9.png" target="_blank" rel="noopener"&gt;&lt;img src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/10/01/BDB-5911-9.png" alt="CloudFormation review page ready to submit the stack" width="800"&gt;&lt;/a&gt;
   &lt;p class="wp-caption-text"&gt;Figure 9: Reviewing and submitting the stack&lt;/p&gt;
  &lt;/div&gt;
  &lt;p&gt;Wait for the stack to complete until its status changes to &lt;strong&gt;CREATE_COMPLETE&lt;/strong&gt;.&lt;/p&gt;&lt;/li&gt;
 &lt;li&gt;Deploy the ETL stack the same way. Set the stack name to &lt;code&gt;etl&lt;/code&gt;, leave the parameters as their defaults, and wait for the status to change to &lt;strong&gt;CREATE_COMPLETE&lt;/strong&gt;.&lt;/li&gt;
 &lt;li&gt;Deploy the agentcore-mcp-server stack the same way. Set the stack name to &lt;code&gt;agentcore-mcp-server&lt;/code&gt;, leave the parameters as their defaults, and wait for the status to change to &lt;strong&gt;CREATE_COMPLETE&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="work-with-the-sagemaker-ai-notebook"&gt;Work with the SageMaker AI notebook&lt;/h3&gt;
&lt;ol type="1"&gt;
 &lt;li&gt;Open the SageMaker AI notebook. Go to AWS CloudFormation on the AWS Management Console and select the &lt;strong&gt;opensearch-cfn&lt;/strong&gt; stack.&lt;/li&gt;
 &lt;li&gt;Select the &lt;strong&gt;Outputs&lt;/strong&gt; tab, scroll down, and open the URL for the SageMaker AI notebook.&lt;/li&gt;
 &lt;li&gt;After SageMaker AI has loaded, select &lt;strong&gt;Lab-OpenSearch-Observability.ipnyb&lt;/strong&gt; from the left side menu to open the notebook.&lt;/li&gt;
 &lt;li&gt;Run each cell in order. To do this, select the cell and then choose the play button (the right-facing triangle).&lt;/li&gt;
 &lt;li&gt;Complete the prerequisites. Section 1 of the notebook loads the required Python modules to run the code in this notebook. It also retrieves resource metadata for the resources created by the CloudFormation stacks. These cells need to run before you move to section 2.
  &lt;div style="width: 810px" class="wp-caption alignnone"&gt;
   &lt;a href="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/10/01/BDB-5911-10.png" target="_blank" rel="noopener"&gt;&lt;img src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/10/01/BDB-5911-10.png" alt="Notebook cell that loads the required libraries and imports the Python modules" width="800"&gt;&lt;/a&gt;
   &lt;p class="wp-caption-text"&gt;Figure 10: Load the required libraries and import the Python modules&lt;/p&gt;
  &lt;/div&gt;
  &lt;p&gt;&lt;/p&gt;
  &lt;div style="width: 810px" class="wp-caption alignnone"&gt;
   &lt;a href="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/10/01/BDB-5911-11.png" target="_blank" rel="noopener"&gt;&lt;img src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/10/01/BDB-5911-11.png" alt="Notebook cell that loads the CloudFormation stack outputs" width="800"&gt;&lt;/a&gt;
   &lt;p class="wp-caption-text"&gt;Figure 11: Load the CloudFormation stack outputs&lt;/p&gt;
  &lt;/div&gt;&lt;/li&gt;
 &lt;li&gt;&lt;strong&gt;Section 2: Connect to OpenSearch.&lt;/strong&gt;
  &lt;div style="width: 810px" class="wp-caption alignnone"&gt;
   &lt;a href="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/10/01/BDB-5911-12.png" target="_blank" rel="noopener"&gt;&lt;img src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/10/01/BDB-5911-12.png" alt="Notebook cell that retrieves the OpenSearch admin credentials from Secrets Manager" width="800"&gt;&lt;/a&gt;
   &lt;p class="wp-caption-text"&gt;Figure 12: Authenticate by retrieving the OpenSearch admin credentials from Secrets Manager&lt;/p&gt;
  &lt;/div&gt;
  &lt;div style="width: 810px" class="wp-caption alignnone"&gt;
   &lt;a href="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/10/01/BDB-5911-13.png" target="_blank" rel="noopener"&gt;&lt;img src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/10/01/BDB-5911-13.png" alt="Notebook cell that grants OpenSearch access to the notebook role and the AgentCore execution role" width="800"&gt;&lt;/a&gt;
   &lt;p class="wp-caption-text"&gt;Figure 13: Grant access to the notebook role and the AgentCore execution role&lt;/p&gt;
  &lt;/div&gt;
  &lt;div style="width: 810px" class="wp-caption alignnone"&gt;
   &lt;a href="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/10/01/BDB-5911-14.png" target="_blank" rel="noopener"&gt;&lt;img src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/10/01/BDB-5911-14.png" alt="Notebook cell that switches OpenSearch to IAM-based authentication" width="800"&gt;&lt;/a&gt;
   &lt;p class="wp-caption-text"&gt;Figure 14: Switch to IAM-based authentication&lt;/p&gt;
  &lt;/div&gt;
  &lt;p&gt;&lt;/p&gt;
  &lt;div style="width: 810px" class="wp-caption alignnone"&gt;
   &lt;a href="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/10/01/BDB-5911-15.png" target="_blank" rel="noopener"&gt;&lt;img src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/10/01/BDB-5911-15.png" alt="Notebook cell that persists the OpenSearch connection variables" width="800"&gt;&lt;/a&gt;
   &lt;p class="wp-caption-text"&gt;Figure 15: Persist connection variables for use in subsequent cells and notebooks&lt;/p&gt;
  &lt;/div&gt;&lt;/li&gt;
 &lt;li&gt;&lt;strong&gt;Section 3: Stream CloudWatch Logs into OpenSearch.&lt;/strong&gt;
  &lt;div style="width: 810px" class="wp-caption alignnone"&gt;
   &lt;a href="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/10/01/BDB-5911-16.png" target="_blank" rel="noopener"&gt;&lt;img src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/10/01/BDB-5911-16.png" alt="Notebook cell that creates a Lambda function and CloudWatch subscription filters" width="800"&gt;&lt;/a&gt;
   &lt;p class="wp-caption-text"&gt;Figure 16: Create the Lambda function and CloudWatch subscription filters that stream ETL-related log groups into OpenSearch indices&lt;/p&gt;
  &lt;/div&gt;
  &lt;div style="width: 810px" class="wp-caption alignnone"&gt;
   &lt;a href="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/10/01/BDB-5911-17.png" target="_blank" rel="noopener"&gt;&lt;img src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/10/01/BDB-5911-17.png" alt="Notebook cell that creates an IAM role for the Lambda function" width="800"&gt;&lt;/a&gt;
   &lt;p class="wp-caption-text"&gt;Figure 17: Create the IAM role for the Lambda function&lt;/p&gt;
  &lt;/div&gt;
  &lt;div style="width: 810px" class="wp-caption alignnone"&gt;
   &lt;a href="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/10/01/BDB-5911-18.png" target="_blank" rel="noopener"&gt;&lt;img src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/10/01/BDB-5911-18.png" alt="Notebook cell that creates the Lambda function" width="800"&gt;&lt;/a&gt;
   &lt;p class="wp-caption-text"&gt;Figure 18: Create the Lambda function&lt;/p&gt;
  &lt;/div&gt;
  &lt;div style="width: 810px" class="wp-caption alignnone"&gt;
   &lt;a href="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/10/01/BDB-5911-19.png" target="_blank" rel="noopener"&gt;&lt;img src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/10/01/BDB-5911-19.png" alt="Notebook cell that grants CloudWatch Logs permission to invoke the Lambda function" width="800"&gt;&lt;/a&gt;
   &lt;p class="wp-caption-text"&gt;Figure 19: Grant CloudWatch Logs permission to invoke the Lambda function&lt;/p&gt;
  &lt;/div&gt;
  &lt;p&gt;&lt;/p&gt;
  &lt;div style="width: 810px" class="wp-caption alignnone"&gt;
   &lt;a href="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/10/01/BDB-5911-20.png" target="_blank" rel="noopener"&gt;&lt;img src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/10/01/BDB-5911-20.png" alt="Notebook cell that maps the Lambda execution role to the OpenSearch all_access role" width="800"&gt;&lt;/a&gt;
   &lt;p class="wp-caption-text"&gt;Figure 20: Map the Lambda execution role to the OpenSearch all_access role&lt;/p&gt;
  &lt;/div&gt;&lt;/li&gt;
 &lt;li&gt;&lt;strong&gt;Section 4: Trigger the ETL DAG.&lt;/strong&gt;
  &lt;p&gt;Trigger the ETL DAG and generate logs through the &lt;code&gt;USE_SERVERLESS&lt;/code&gt; flag to select your preferred runtime environment.&lt;/p&gt;
  &lt;p&gt;When set to False (the default), the &lt;code&gt;observability_etl_dag&lt;/code&gt; DAG runs on provisioned Amazon MWAA, running AWS Glue and Amazon EC2 tasks in parallel. When set to True, the &lt;code&gt;observability_blog_aggregation&lt;/code&gt; DAG runs on Amazon MWAA Serverless, running an AWS Glue aggregation job.&lt;/p&gt;
  &lt;div style="width: 810px" class="wp-caption alignnone"&gt;
   &lt;a href="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/10/01/BDB-5911-21.png" target="_blank" rel="noopener"&gt;&lt;img src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/10/01/BDB-5911-21.png" alt="Notebook cell that triggers the ETL DAG" width="800"&gt;&lt;/a&gt;
   &lt;p class="wp-caption-text"&gt;Figure 21: Trigger the ETL DAG&lt;/p&gt;
  &lt;/div&gt;
  &lt;div style="width: 810px" class="wp-caption alignnone"&gt;
   &lt;a href="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/10/01/BDB-5911-22.png" target="_blank" rel="noopener"&gt;&lt;img src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/10/01/BDB-5911-22.png" alt="Notebook output that verifies the DAG has stopped running" width="800"&gt;&lt;/a&gt;
   &lt;p class="wp-caption-text"&gt;Figure 22: Verify that the DAG has stopped running&lt;/p&gt;
  &lt;/div&gt;
  &lt;p&gt;&lt;/p&gt;
  &lt;div style="width: 810px" class="wp-caption alignnone"&gt;
   &lt;a href="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/10/01/BDB-5911-23.png" target="_blank" rel="noopener"&gt;&lt;img src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/10/01/BDB-5911-23.png" alt="Notebook output that verifies log ingestion into OpenSearch" width="800"&gt;&lt;/a&gt;
   &lt;p class="wp-caption-text"&gt;Figure 23: Verify log ingestion&lt;/p&gt;
  &lt;/div&gt;&lt;/li&gt;
 &lt;li&gt;&lt;strong&gt;Section 5: Register the Claude LLM connector in OpenSearch.&lt;/strong&gt;
  &lt;ol type="a"&gt;
   &lt;li&gt;Create an ML connector in OpenSearch that calls the Anthropic Claude model on Amazon Bedrock (&lt;code&gt;us.anthropic.claude-sonnet-4-20250514-v1:0&lt;/code&gt;) through the Converse API, using SigV4 authentication and an assumed IAM role.&lt;/li&gt;
   &lt;li&gt;Register and deploy the model so that OpenSearch can use it for ML-powered features such as Retrieval Augmented Generation (RAG) and conversational search.&lt;/li&gt;
  &lt;/ol&gt;
  &lt;div style="width: 810px" class="wp-caption alignnone"&gt;
   &lt;a href="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/10/01/BDB-5911-24.png" target="_blank" rel="noopener"&gt;&lt;img src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/10/01/BDB-5911-24.png" alt="Notebook cell that creates the ML connector to the Claude model on Amazon Bedrock" width="800"&gt;&lt;/a&gt;
   &lt;p class="wp-caption-text"&gt;Figure 24: Create the machine learning connector to the Claude model&lt;/p&gt;
  &lt;/div&gt;
  &lt;p&gt;&lt;/p&gt;
  &lt;div style="width: 810px" class="wp-caption alignnone"&gt;
   &lt;a href="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/10/01/BDB-5911-25.png" target="_blank" rel="noopener"&gt;&lt;img src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/10/01/BDB-5911-25.png" alt="Notebook cell that registers and deploys the model in OpenSearch" width="800"&gt;&lt;/a&gt;
   &lt;p class="wp-caption-text"&gt;Figure 25: Register and deploy the model in OpenSearch&lt;/p&gt;
  &lt;/div&gt;&lt;/li&gt;
 &lt;li&gt;&lt;strong&gt;Section 6: Register the AI agent with the OpenSearch MCP server.&lt;/strong&gt;
  &lt;ol type="a"&gt;
   &lt;li&gt;Install the agent libraries (&lt;code&gt;mcp&lt;/code&gt;, &lt;code&gt;strands-agents&lt;/code&gt;, &lt;code&gt;uv&lt;/code&gt;).&lt;/li&gt;
   &lt;li&gt;Load the Amazon Cognito credentials for authenticating with the AgentCore MCP Server.&lt;/li&gt;
   &lt;li&gt;Choose a deployment mode. Option A (local) runs the OpenSearch MCP server as a subprocess through &lt;code&gt;uvx&lt;/code&gt; for development.&lt;/li&gt;
  &lt;/ol&gt;
  &lt;div style="width: 810px" class="wp-caption alignnone"&gt;
   &lt;a href="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/10/01/BDB-5911-26.png" target="_blank" rel="noopener"&gt;&lt;img src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/10/01/BDB-5911-26.png" alt="Notebook cell that runs the OpenSearch MCP server locally through uvx" width="800"&gt;&lt;/a&gt;
   &lt;p class="wp-caption-text"&gt;Figure 26: Run the OpenSearch MCP server locally as a subprocess&lt;/p&gt;
  &lt;/div&gt;
  &lt;p&gt;Option B (AgentCore) connects to a production MCP server on Amazon Bedrock AgentCore using OAuth 2.0 client credentials.&lt;/p&gt;
  &lt;div style="width: 810px" class="wp-caption alignnone"&gt;
   &lt;a href="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/10/01/BDB-5911-27.png" target="_blank" rel="noopener"&gt;&lt;img src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/10/01/BDB-5911-27.png" alt="Notebook cell that connects to the production MCP server on Amazon Bedrock AgentCore" width="800"&gt;&lt;/a&gt;
   &lt;p class="wp-caption-text"&gt;Figure 27: Connect to the production MCP server on Amazon Bedrock AgentCore&lt;/p&gt;
  &lt;/div&gt;
  &lt;p&gt;d.&amp;nbsp;Create the ETL analysis agent.&lt;/p&gt;
  &lt;p&gt;&lt;/p&gt;
  &lt;div style="width: 810px" class="wp-caption alignnone"&gt;
   &lt;a href="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/10/01/BDB-5911-28.png" target="_blank" rel="noopener"&gt;&lt;img src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/10/01/BDB-5911-28.png" alt="Notebook cell that creates the ETL analysis agent" width="800"&gt;&lt;/a&gt;
   &lt;p class="wp-caption-text"&gt;Figure 28: Create the ETL analysis agent&lt;/p&gt;
  &lt;/div&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="results"&gt;Results&lt;/h2&gt;
&lt;p&gt;In section 7 of the notebook, you can ask questions about your ETL pipeline. A sample question is included in the notebook: “What errors do you see in the logs”. Try asking additional questions about the ETL pipeline and related services. The agent autonomously searches indices, correlates events, and returns an analysis of the error with remediation guidance.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Example natural language queries&lt;/strong&gt;&lt;/p&gt;
&lt;table border="1px" width="100%" cellpadding="10px"&gt;
 &lt;tbody&gt;
  &lt;tr&gt;
   &lt;td&gt;&lt;strong&gt;What you want to find&lt;/strong&gt;&lt;/td&gt;
   &lt;td&gt;&lt;strong&gt;Example prompt&lt;/strong&gt;&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;Errors across the sources&lt;/td&gt;
   &lt;td&gt;Show me the ERROR level logs&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;AWS Glue job success logs&lt;/td&gt;
   &lt;td&gt;Find successful AWS Glue ETL job completions&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;Amazon EC2 script failures&lt;/td&gt;
   &lt;td&gt;Show me Amazon EC2 ETL script errors with stack traces&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;Amazon MWAA task failures&lt;/td&gt;
   &lt;td&gt;Find failed Amazon MWAA DAG tasks&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;Amazon MWAA Serverless workflow logs&lt;/td&gt;
   &lt;td&gt;Show me logs from the Amazon MWAA Serverless aggregation workflow&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;Recent activity&lt;/td&gt;
   &lt;td&gt;Show me the last 20 log entries from any source&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;Specific time range&lt;/td&gt;
   &lt;td&gt;Show me logs from the last 30 minutes&lt;/td&gt;
  &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The following screenshot shows a natural language query being sent to the &lt;code&gt;search_agent&lt;/code&gt; through the MCP client, with the agent using multiple tools (ListIndexTool, IndexMappingTool, SearchIndexTool) to discover indices, understand intent, and return structured findings from the &lt;code&gt;pipeline-logs&lt;/code&gt; index.&lt;/p&gt;
&lt;div style="width: 810px" class="wp-caption alignnone"&gt;
 &lt;a href="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/10/01/BDB-5911-29.png" target="_blank" rel="noopener"&gt;&lt;img src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/10/01/BDB-5911-29.png" alt="MCP client showing a natural language query and the ETL analysis agent’s structured findings from the pipeline-logs index" width="800"&gt;&lt;/a&gt;
 &lt;p class="wp-caption-text"&gt;Figure 29: Example natural language query and the agent’s structured findings&lt;/p&gt;
&lt;/div&gt;
&lt;h2 id="outcome"&gt;Outcome&lt;/h2&gt;
&lt;p&gt;After a single CloudFormation deployment and five steps, you have a pipeline that processes tabular data through parallel ETL paths, aggregates the results, and consolidates operational logs into one searchable index. When something breaks, you open one dashboard, type what you are looking for, and get your answer. There is no tab-hopping, no timestamp-matching, and no guessing which service threw the error.&lt;/p&gt;
&lt;p&gt;The combination of Amazon MWAA for orchestration, OpenSearch for log aggregation, and Amazon Bedrock for natural language access gives you an observability layer that your team actually uses, because it is faster than the alternative.&lt;/p&gt;
&lt;h2 id="clean-up"&gt;Clean up&lt;/h2&gt;
&lt;p&gt;To remove the services used in this solution, delete the three stacks using the AWS CloudFormation console, or run the following command in the AWS CLI:&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-bash"&gt;aws cloudformation delete-stack --stack-name &amp;lt;stack name&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;This removes the provisioned resources, including the OpenSearch domain, Amazon MWAA environment, AWS Glue jobs, Amazon EC2 instance, and associated IAM roles.&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Observability for a multi-service ETL pipeline doesn’t need to mean stitching together multiple CloudWatch log groups by hand. By streaming every component’s logs into a single OpenSearch index and putting an Amazon Bedrock model in front of it, you turn “I need to find the right log group and write a filter expression” into “show me errors from AWS Glue in the last hour”.&lt;/p&gt;
&lt;p&gt;Where to go from here:&lt;/p&gt;
&lt;ul&gt;
 &lt;li&gt;Add alerting: configure OpenSearch alerting rules to notify your team through Amazon Simple Notification Service (Amazon SNS) when ERROR-level logs exceed a threshold.&lt;/li&gt;
 &lt;li&gt;Expand the index: add logs from other components (AWS Step Functions, AWS Lambda, and additional AWS Glue jobs) by creating new CloudWatch subscription filters.&lt;/li&gt;
 &lt;li&gt;Build dashboards: use the OpenSearch UI for visualizations, such as error rate over time, log volume by source, and latency between DAG trigger and job completion.&lt;/li&gt;
 &lt;li&gt;Fine-tune the Amazon Bedrock model: adjust the prompt template in the ML connector to include your index mapping, which improves query accuracy for domain-specific questions.&lt;/li&gt;
 &lt;li&gt;Use OpenSearch Ask AI directly from the &lt;a href="https://aws.amazon.com/blogs/big-data/agentic-ai-for-observability-and-troubleshooting-with-amazon-opensearch-service/" target="_blank" rel="noopener"&gt;OpenSearch UI&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The goal is straightforward: when your pipeline fails, you should spend your time fixing the problem, not finding it. Try the solution in your own environment and tell us what you think in the comments.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style="clear: both"&gt;&lt;/p&gt;
&lt;hr style="width: 100%"&gt;
&lt;h2&gt;About the authors&lt;/h2&gt;
&lt;footer&gt;
 &lt;div class="blog-author-box" style="padding-top: 2.0em"&gt;
  &lt;div class="blog-author-image" style="margin-right: 1.0em"&gt;
   &lt;img loading="lazy" class="alignnone size-full" src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/10/01/BDB-5911-30.jpg" alt="Anupa Bhattacharyya" width="100" height="133"&gt;
  &lt;/div&gt;
  &lt;h3 class="lb-h4"&gt;Anupa Bhattacharyya&lt;/h3&gt;
  &lt;p style="overflow: hidden"&gt;Anupa is an Enterprise Support Lead in CIENG at Amazon Web Services, where she guides Enterprise customers through their cloud journey. With over 15 years of experience in data and analytics, she excels in defining strategic initiatives for enterprise customers. Outside of work, she enjoys painting, traveling, family time, and savoring new cuisines.&lt;/p&gt;
 &lt;/div&gt;
 &lt;div class="blog-author-box" style="padding-top: 2.0em"&gt;
  &lt;div class="blog-author-image" style="margin-right: 1.0em"&gt;
   &lt;img loading="lazy" class="alignnone size-full" src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/10/01/BDB-5911-31.jpg" alt="Sean Bjurstrom" width="100" height="133"&gt;
  &lt;/div&gt;
  &lt;h3 class="lb-h4"&gt;Sean Bjurstrom&lt;/h3&gt;
  &lt;p style="overflow: hidden"&gt;Sean is a Technical Account Manager in ISV accounts at Amazon Web Services, where he specializes in analytics technologies and draws on his background in consulting to support customers on their analytics and cloud journeys. Sean is passionate about helping businesses harness the power of data to drive innovation and growth. Outside of work, he enjoys running and has participated in several marathons.&lt;/p&gt;
 &lt;/div&gt;
 &lt;div class="blog-author-box" style="padding-top: 2.0em"&gt;
  &lt;div class="blog-author-image" style="margin-right: 1.0em"&gt;
   &lt;img loading="lazy" class="alignnone size-full" src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/10/01/BDB-5911-32.jpg" alt="Manikandan Mylsamy" width="100" height="133"&gt;
  &lt;/div&gt;
  &lt;h3 class="lb-h4"&gt;Manikandan Mylsamy&lt;/h3&gt;
  &lt;p style="overflow: hidden"&gt;Manikandan is a Technical Account Manager, EC2 SME specializing in Microsoft technologies at AWS ISV accounts. He helps enterprises accelerate cloud adoption and optimize infrastructure for operational excellence. Outside work, he enjoys cricket, swimming, and long drives.&lt;/p&gt;
 &lt;/div&gt;
 &lt;div class="blog-author-box" style="padding-top: 2.0em"&gt;
  &lt;div class="blog-author-image" style="margin-right: 1.0em"&gt;
   &lt;img loading="lazy" class="alignnone size-full" src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/10/01/BDB-5911-33.jpg" alt="Karthik Seshadri" width="100" height="133"&gt;
  &lt;/div&gt;
  &lt;h3 class="lb-h4"&gt;Karthik Seshadri&lt;/h3&gt;
  &lt;p style="overflow: hidden"&gt;Karthik is a Sr.&amp;nbsp;Software Development Engineer in AWS, where he specializes in orchestration of big data technologies. He is enthusiastic about serverless technologies, data engineering and building great services. Outside of work, he enjoys traveling and playing various sports.&lt;/p&gt;
 &lt;/div&gt;
&lt;/footer&gt;</content:encoded>
					
		
		
			</item>
		<item>
		<title>Cost-effective ETL with DuckDB and Amazon S3 Tables on AWS Glue</title>
		<link>https://aws.amazon.com/blogs/big-data/cost-effective-etl-with-duckdb-and-amazon-s3-tables-on-aws-glue/</link>
		
		<dc:creator><![CDATA[Bezuayehu Wate]]></dc:creator>
		<pubDate>Tue, 06 Oct 2026 16:13:02 +0000</pubDate>
				<category><![CDATA[Advanced (300)]]></category>
		<category><![CDATA[Amazon S3 Tables]]></category>
		<category><![CDATA[AWS Glue]]></category>
		<category><![CDATA[Technical How-to]]></category>
		<guid isPermaLink="false">9d134a66f17859a5d14c48a168f65cfa4b8840c8</guid>

					<description>Learn how to pair DuckDB with AWS Glue 6.0 to run SQL-centric ETL on a single worker, reading Parquet from Amazon S3 and writing Apache Iceberg tables to Amazon S3 Tables. This post walks through a complete, runnable example and compares measured cost and runtime against an equivalent Apache Spark job on the same Glue runtime.</description>
										<content:encoded>&lt;p&gt;Many data integration jobs are SQL-centric: they filter, join, and aggregate data on a schedule, and they run frequently enough that fast startup matters. For this shape of work, teams want to match the engine to the job and run it quickly and cost-effectively, without standing up and tuning separate infrastructure.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://aws.amazon.com/glue/" target="_blank" rel="noopener"&gt;AWS Glue&lt;/a&gt; is the serverless data integration service that customers use to run extract, transform, and load (ETL) jobs at any scale, without managing infrastructure. With AWS Glue, you can run &lt;a href="https://duckdb.org/" target="_blank" rel="noopener"&gt;DuckDB&lt;/a&gt;, an embedded, in-process, vectorized SQL engine, inside a standard AWS Glue job. DuckDB reads Parquet files from Amazon Simple Storage Service (Amazon S3) and writes Apache Iceberg tables directly to &lt;a href="https://aws.amazon.com/s3/features/tables/" target="_blank" rel="noopener"&gt;Amazon S3 Tables&lt;/a&gt;, a capability of Amazon S3. DuckDB is an open source, in-process, vectorized analytical SQL engine that runs embedded in your application, with no separate server or cluster to manage. It reads and writes cloud data formats such as Parquet and Apache Iceberg natively. &lt;a href="https://aws.amazon.com/blogs/aws/aws-glue-6-0-now-available-with-30-lower-price-and-full-apache-iceberg-v3-support/" target="_blank" rel="noopener"&gt;AWS Glue 6.0&lt;/a&gt; is the latest version, running on a modernized runtime with a &lt;a href="https://aws.amazon.com/glue/pricing/" target="_blank" rel="noopener"&gt;30 percent price reduction&lt;/a&gt; over previous versions. DuckDB reads Amazon S3 Parquet through its &lt;code&gt;httpfs&lt;/code&gt; extension and commits Iceberg snapshots to Amazon S3 Tables through the Iceberg REST endpoint, so no separate catalog synchronization is required. Running DuckDB in AWS Glue is well suited to SQL-centric transformations such as filters, joins, and aggregations. It also fits frequent, scheduled jobs such as hourly or daily aggregations, incremental loads, and rollups that benefit from fast startup. This pattern complements Apache Spark on AWS Glue rather than replacing it: when a workload needs distributed processing, the same job type runs PySpark with no change to your infrastructure, IAM, or triggers.&lt;/p&gt;
&lt;p&gt;This post walks through the pattern with a concrete ETL use case and provides complete, runnable code. It also compares measured cost and runtime against a Spark job performing the same work on the same AWS Glue 6.0 runtime.&lt;/p&gt;
&lt;h2 id="when-to-use-this-pattern"&gt;When to use this pattern&lt;/h2&gt;
&lt;p&gt;This pattern is a complement to Spark on AWS Glue, not a replacement. The following table summarizes when each approach yields the best results.&lt;/p&gt;
&lt;table border="1px" width="100%" cellpadding="10px"&gt;
 &lt;tbody&gt;
  &lt;tr&gt;
   &lt;td&gt;
    &lt;h3 id="signal"&gt;Signal&lt;/h3&gt;
   &lt;/td&gt;
   &lt;td&gt;
    &lt;h3 id="duckdb-on-aws-glue-6.0"&gt;DuckDB on AWS Glue 6.0&lt;/h3&gt;
   &lt;/td&gt;
   &lt;td&gt;
    &lt;h3 id="apache-spark-on-aws-glue-6.0"&gt;Apache Spark on AWS Glue 6.0&lt;/h3&gt;
   &lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;Dataset size per run&lt;/td&gt;
   &lt;td&gt;Scales with worker size&lt;/td&gt;
   &lt;td&gt;Scales horizontally across multiple nodes for datasets of any size&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;Parallelism requirement&lt;/td&gt;
   &lt;td&gt;Single-node, in-process execution&lt;/td&gt;
   &lt;td&gt;Distributed processing across a managed cluster&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;SQL complexity&lt;/td&gt;
   &lt;td&gt;Aggregations, joins, window functions&lt;/td&gt;
   &lt;td&gt;Complex graph operations, custom UDFs, ML pipelines&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;Cost priority&lt;/td&gt;
   &lt;td&gt;Minimize per-run cost and duration&lt;/td&gt;
   &lt;td&gt;Maximize throughput at scale&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;Iceberg writes&lt;/td&gt;
   &lt;td&gt;DuckDB iceberg extension to S3 Tables&lt;/td&gt;
   &lt;td&gt;Native Spark Iceberg integration&lt;/td&gt;
  &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;For workloads that need distributed processing, the same &lt;code&gt;glueetl&lt;/code&gt; job type runs PySpark with no change to your infrastructure, AWS Identity and Access Management (IAM) configuration, or triggers. You choose the engine that fits each workload.&lt;/p&gt;
&lt;h2 id="how-duckdb-runs-on-aws-glue-6.0"&gt;How DuckDB runs on AWS Glue 6.0&lt;/h2&gt;
&lt;p&gt;Running DuckDB in an AWS Glue job comes down to two things working together: a runtime modern enough to load DuckDB and its native extensions, and the capabilities DuckDB brings to ETL once it does.&lt;/p&gt;
&lt;h3 id="what-the-aws-glue-6.0-runtime-provides"&gt;What the AWS Glue 6.0 runtime provides&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Modern runtime compatibility.&lt;/strong&gt; AWS Glue 6.0 runs on Amazon Linux 2023 with glibc 2.34 and Python 3.13. DuckDB 1.5.x and its native C++ extension binaries (&lt;code&gt;httpfs&lt;/code&gt;, &lt;code&gt;aws&lt;/code&gt;, &lt;code&gt;iceberg&lt;/code&gt;) install through pip and load without workarounds. The DuckDB extension binaries require a modern glibc (2.28 or later), which the AWS Glue 6.0 runtime provides.&lt;/p&gt;
&lt;p&gt;AWS Glue 6.0 resolves this compatibility requirement. You can add DuckDB 1.5.x to an AWS Glue 6.0 job in two ways. The first is the &lt;code&gt;--additional-python-modules&lt;/code&gt; job parameter (&lt;code&gt;duckdb==1.5.1&lt;/code&gt;), which pip-installs the package at job startup and loads all extensions without additional steps. Alternatively, you can package the dependencies as a Python virtual environment, upload it to Amazon S3, and reference it using the &lt;code&gt;--python-virtual-env&lt;/code&gt; parameter. On AWS Glue 6.0, you can also add &lt;code&gt;--python-virtual-env-storage-prefix&lt;/code&gt; to have AWS Glue build and cache the virtual environment automatically. For more information, see &lt;a href="https://docs.aws.amazon.com/glue/latest/dg/aws-glue-programming-python-virtual-environments.html" target="_blank" rel="noopener"&gt;Using Python virtual environments with AWS Glue&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id="what-duckdb-provides"&gt;What DuckDB provides&lt;/h3&gt;
&lt;p&gt;DuckDB is an open source, in-process analytical SQL engine. It runs inside an AWS Glue job as a single process, with no separate cluster or coordinator. The following capabilities make it a practical fit for ETL on the AWS Glue 6.0 runtime.&lt;/p&gt;
&lt;ul&gt;
 &lt;li&gt;&lt;strong&gt;Single-node vectorized execution.&lt;/strong&gt; DuckDB runs inside a single AWS Glue job. For a couple of gigabytes, there is no shuffle, no executor scheduling, and no inter-node network I/O. The work happens in a single vectorized pass over columnar memory.&lt;/li&gt;
 &lt;li&gt;&lt;strong&gt;Native Amazon S3 and Parquet access.&lt;/strong&gt; The &lt;code&gt;httpfs&lt;/code&gt; extension reads and writes Amazon S3 objects directly, using the IAM role of the AWS Glue job automatically through &lt;code&gt;CREDENTIAL_CHAIN&lt;/code&gt;.&lt;/li&gt;
 &lt;li&gt;&lt;strong&gt;Native Amazon S3 Tables writes.&lt;/strong&gt; The &lt;code&gt;iceberg&lt;/code&gt; extension connects to the Amazon S3 Tables Iceberg REST endpoint (&lt;code&gt;ENDPOINT_TYPE s3_tables&lt;/code&gt;) and commits standard Iceberg snapshots. With AWS Glue 6.0, you can use two capabilities that matured independently: Amazon S3 Tables and DuckDB Iceberg writes.&lt;/li&gt;
 &lt;li&gt;&lt;strong&gt;Larger-than-memory operators.&lt;/strong&gt; Sort, join, and aggregate spill to &lt;code&gt;/tmp&lt;/code&gt;, so datasets larger than available RAM still process without code changes.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The output is a standard &lt;a href="https://iceberg.apache.org/" target="_blank" rel="noopener"&gt;Apache Iceberg table&lt;/a&gt; in &lt;a href="https://aws.amazon.com/s3/features/tables/" target="_blank" rel="noopener"&gt;Amazon S3 Tables&lt;/a&gt;. It is queryable by &lt;a href="https://aws.amazon.com/athena/" target="_blank" rel="noopener"&gt;Amazon Athena&lt;/a&gt;, &lt;a href="https://aws.amazon.com/redshift/" target="_blank" rel="noopener"&gt;Amazon Redshift&lt;/a&gt;, and &lt;a href="https://aws.amazon.com/emr/" target="_blank" rel="noopener"&gt;Amazon EMR&lt;/a&gt;, and other Iceberg-compatible engines that support the &lt;a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/s3-tables-integrating-open-source.html" target="_blank" rel="noopener"&gt;Iceberg REST Catalog API&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Sizing guidance.&lt;/strong&gt; DuckDB runs within a single AWS Glue worker, so its available memory and disk scale with the worker type. This walkthrough uses the minimum &lt;code&gt;glueetl&lt;/code&gt; configuration of 2 workers (2 data processing units, or DPUs) with worker type G.1X: each G.1X worker provides 4 vCPUs and 16 GB of memory. DuckDB runs on the driver and processes data in memory, spilling to local disk when a dataset or intermediate result exceeds available RAM. For larger inputs, choose a bigger worker: G.2X provides 8 vCPUs and 32 GB of memory, and the G.4X and G.8X types scale higher. Size the worker to your input volume and the memory footprint of your aggregations and joins. For current specifications, see &lt;a href="https://docs.aws.amazon.com/glue/latest/dg/worker-types.html" target="_blank" rel="noopener"&gt;AWS Glue worker types&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="architecture"&gt;Architecture&lt;/h2&gt;
&lt;p&gt;The following image shows the architecture described in this post.&lt;/p&gt;
&lt;p&gt;&lt;img loading="lazy" class="alignnone size-full wp-image-94900" src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/10/14/BDB-6254-1_no_lakeformation_002.png" alt="" width="1451" height="848"&gt;&lt;/p&gt;
&lt;p&gt;The pipeline consists of the following managed components:&lt;/p&gt;
&lt;table style="height: 324px" border="1px" width="1351" cellpadding="10px"&gt;
 &lt;tbody&gt;
  &lt;tr&gt;
   &lt;td&gt;
    &lt;h3 id="layer"&gt;Layer&lt;/h3&gt;
   &lt;/td&gt;
   &lt;td&gt;
    &lt;h3 id="role"&gt;Role&lt;/h3&gt;
   &lt;/td&gt;
   &lt;td&gt;
    &lt;h3 id="aws-service"&gt;AWS Service&lt;/h3&gt;
   &lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;&lt;strong&gt;Source&lt;/strong&gt;&lt;/td&gt;
   &lt;td&gt;Raw Parquet files, partitioned by date&lt;/td&gt;
   &lt;td&gt;Amazon S3&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;&lt;strong&gt;Compute&lt;/strong&gt;&lt;/td&gt;
   &lt;td&gt;DuckDB SQL engine running on the AWS Glue 6.0 runtime&lt;/td&gt;
   &lt;td&gt;AWS Glue 6.0 (&lt;code&gt;glueetl&lt;/code&gt;)&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;&lt;strong&gt;Destination&lt;/strong&gt;&lt;/td&gt;
   &lt;td&gt;Iceberg analytical tables, queryable by any engine&lt;/td&gt;
   &lt;td&gt;Amazon S3 Tables&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;&lt;strong&gt;Query&lt;/strong&gt;&lt;/td&gt;
   &lt;td&gt;Analytics and business intelligence (BI) on the output tables&lt;/td&gt;
   &lt;td&gt;Amazon Athena, Amazon QuickSight&lt;/td&gt;
  &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Raw Parquet files land in &lt;a href="https://aws.amazon.com/s3/" target="_blank" rel="noopener"&gt;Amazon S3&lt;/a&gt; on a schedule. An AWS Glue 6.0 job runs DuckDB. DuckDB reads the files, applies SQL transformations in memory, and writes the aggregated result as an Iceberg table to Amazon S3 Tables through the Iceberg REST catalog. &lt;a href="https://aws.amazon.com/athena/" target="_blank" rel="noopener"&gt;Amazon Athena&lt;/a&gt; and &lt;a href="https://aws.amazon.com/quicksight/" target="_blank" rel="noopener"&gt;Amazon QuickSight&lt;/a&gt; can query the output immediately. No separate catalog synchronization is required.&lt;/p&gt;
&lt;p&gt;You can trigger the job several ways:&lt;/p&gt;
&lt;ul&gt;
 &lt;li&gt;With a native &lt;a href="https://docs.aws.amazon.com/glue/latest/dg/about-triggers.html" target="_blank" rel="noopener"&gt;AWS Glue trigger&lt;/a&gt;, which can be scheduled, on-demand, or conditional on another job or crawler completing.&lt;/li&gt;
 &lt;li&gt;With &lt;a href="https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-what-is.html" target="_blank" rel="noopener"&gt;Amazon EventBridge Scheduler&lt;/a&gt; calling &lt;code&gt;StartJobRun&lt;/code&gt; on a fixed schedule.&lt;/li&gt;
 &lt;li&gt;In response to new data through an Amazon S3 event notification that invokes an &lt;a href="https://aws.amazon.com/lambda/" target="_blank" rel="noopener"&gt;AWS Lambda&lt;/a&gt; function calling &lt;code&gt;StartJobRun&lt;/code&gt;.&lt;/li&gt;
 &lt;li&gt;As part of an &lt;a href="https://docs.aws.amazon.com/glue/latest/dg/workflows_overview.html" target="_blank" rel="noopener"&gt;AWS Glue Workflow&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="walkthrough-ecommerce-daily-order-summary"&gt;Walkthrough: eCommerce daily order summary&lt;/h2&gt;
&lt;p&gt;This section walks through a daily ETL pipeline for an eCommerce application. The pipeline reads raw transaction files from Amazon S3, cleanses and aggregates them, and writes a query-ready summary to Amazon S3 Tables.&lt;/p&gt;
&lt;table style="height: 310px" border="1px" width="1389" cellpadding="10px"&gt;
 &lt;tbody&gt;
  &lt;tr&gt;
   &lt;td&gt;
    &lt;h3 id="step"&gt;Step&lt;/h3&gt;
   &lt;/td&gt;
   &lt;td&gt;
    &lt;h3 id="operation"&gt;Operation&lt;/h3&gt;
   &lt;/td&gt;
   &lt;td&gt;
    &lt;h3 id="detail"&gt;Detail&lt;/h3&gt;
   &lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;&lt;strong&gt;1. Source&lt;/strong&gt;&lt;/td&gt;
   &lt;td&gt;Read raw Parquet from S3&lt;/td&gt;
   &lt;td&gt;&lt;code&gt;s3://&amp;lt;amzn-s3-demo-source-bucket&amp;gt;/orders/year=2026/month=08/*.parquet&lt;/code&gt;&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;&lt;strong&gt;2. Filter&lt;/strong&gt;&lt;/td&gt;
   &lt;td&gt;&lt;code&gt;status IN (‘completed’,‘processing’)&lt;/code&gt;&lt;/td&gt;
   &lt;td&gt;Drop canceled and test orders&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;&lt;strong&gt;3. Enrich&lt;/strong&gt;&lt;/td&gt;
   &lt;td&gt;&lt;code&gt;net_revenue&lt;/code&gt;, &lt;code&gt;avg_order_value&lt;/code&gt;&lt;/td&gt;
   &lt;td&gt;Derived columns via SQL expressions&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;&lt;strong&gt;4. Aggregate&lt;/strong&gt;&lt;/td&gt;
   &lt;td&gt;&lt;code&gt;GROUP BY order_day, region, category&lt;/code&gt;&lt;/td&gt;
   &lt;td&gt;Daily revenue, order count, unique customers&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;&lt;strong&gt;5. Write&lt;/strong&gt;&lt;/td&gt;
   &lt;td&gt;&lt;code&gt;INSERT&lt;/code&gt; into an Amazon S3 Tables Iceberg table&lt;/td&gt;
   &lt;td&gt;Idempotent per-day reload&lt;/td&gt;
  &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id="prerequisites"&gt;Prerequisites&lt;/h3&gt;
&lt;ul&gt;
 &lt;li&gt;An AWS account with permissions for AWS Glue, Amazon S3, Amazon S3 Tables, and AWS Identity and Access Management (IAM).&lt;/li&gt;
 &lt;li&gt;An Amazon S3 bucket containing raw Parquet files (referred to as &lt;code&gt;&amp;lt;amzn-s3-demo-source-bucket&amp;gt;&lt;/code&gt; in this post).&lt;/li&gt;
 &lt;li&gt;An Amazon S3 Tables table bucket (referred to as &lt;code&gt;&amp;lt;amzn-s3-demo-table-bucket&amp;gt;&lt;/code&gt; in this post). See the &lt;strong&gt;Create the S3 Tables table bucket&lt;/strong&gt; section.&lt;/li&gt;
 &lt;li&gt;An IAM role for the AWS Glue job with:
  &lt;ul&gt;
   &lt;li&gt;Amazon S3 read access on &lt;code&gt;&amp;lt;amzn-s3-demo-source-bucket&amp;gt;&lt;/code&gt;.&lt;/li&gt;
   &lt;li&gt;Amazon S3 Tables permissions on the table bucket ARN: &lt;code&gt;s3tables:GetTableBucket&lt;/code&gt;, &lt;code&gt;s3tables:ListNamespaces&lt;/code&gt;, &lt;code&gt;s3tables:GetNamespace&lt;/code&gt;, &lt;code&gt;s3tables:CreateNamespace&lt;/code&gt;, &lt;code&gt;s3tables:ListTables&lt;/code&gt;, &lt;code&gt;s3tables:GetTable&lt;/code&gt;, &lt;code&gt;s3tables:CreateTable&lt;/code&gt;, &lt;code&gt;s3tables:GetTableMetadataLocation&lt;/code&gt;, &lt;code&gt;s3tables:UpdateTableMetadataLocation&lt;/code&gt;, &lt;code&gt;s3tables:GetTableData&lt;/code&gt;, and &lt;code&gt;s3tables:PutTableData&lt;/code&gt;&lt;/li&gt;
   &lt;li&gt;AWS Glue job execution permissions.&lt;/li&gt;
  &lt;/ul&gt;&lt;/li&gt;
 &lt;li&gt;An AWS Glue 6.0 job (&lt;code&gt;glueetl&lt;/code&gt;) with:
  &lt;ul&gt;
   &lt;li&gt;&lt;code&gt;--additional-python-modules&lt;/code&gt;: &lt;code&gt;duckdb==1.5.1&lt;/code&gt;.&lt;/li&gt;
   &lt;li&gt;Minimum worker configuration: 2 workers, type G.1X.&lt;/li&gt;
  &lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Note on DuckDB versions.&lt;/strong&gt; &lt;a href="https://duckdb.org/docs/current/core_extensions/iceberg/writing" target="_blank" rel="noopener"&gt;DuckDB support for writing Apache Iceberg&lt;/a&gt; tables through a REST catalog, including Amazon S3 Tables, requires version 1.4.0 or later. This walkthrough uses &lt;code&gt;duckdb==1.5.1&lt;/code&gt;. On the AWS Glue 6.0 runtime (Amazon Linux 2023), it installs and all native extensions load without additional configuration.&lt;/p&gt;
&lt;h3 id="create-the-s3-tables-table-bucket"&gt;Create the S3 Tables table bucket&lt;/h3&gt;
&lt;p&gt;If you don’t already have an Amazon S3 Tables table bucket, create one using the AWS Command Line Interface (AWS CLI):&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-bash"&gt;aws s3tables create-table-bucket \
    --name &amp;lt;amzn-s3-demo-table-bucket&amp;gt; \
    --region &amp;lt;YOUR-REGION&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Note the table bucket Amazon Resource Name (ARN) from the output. It follows the format:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;arn:aws:s3tables:&amp;lt;YOUR-REGION&amp;gt;:&amp;lt;YOUR-ACCOUNT-ID&amp;gt;:bucket/&amp;lt;amzn-s3-demo-table-bucket&amp;gt;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Turn on integration with AWS analytics services so the table is discoverable by Amazon Athena, Amazon Redshift, and Amazon EMR. Complete the integration by creating the &lt;code&gt;s3tablescatalog&lt;/code&gt; catalog in the AWS Glue Data Catalog using the AWS CLI. For the steps, see &lt;a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/s3-tables-integrating-aws.html" target="_blank" rel="noopener"&gt;Integrating Amazon S3 Tables with AWS analytics services&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Generate sample data&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This walkthrough uses a synthetic eCommerce dataset. Run the following Python script locally or in &lt;a href="https://aws.amazon.com/cloudshell/" target="_blank" rel="noopener"&gt;AWS CloudShell&lt;/a&gt; to generate Parquet files that match the schema used in the transform. It produces roughly 8.4 million rows across 12 files (about 94 MB on disk as Parquet, roughly 1.2 GB uncompressed in memory).&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-python"&gt;import pandas as pd
import numpy as np
import pyarrow as pa
import pyarrow.parquet as pq
import os

np.random.seed(42)

N = 8_400_000  # ~8.4M rows
NUM_FILES = 12  # split across 12 files to mimic a partitioned landing zone

df = pd.DataFrame({
    "order_date": pd.date_range("2026-08-01", periods=N, freq="s"),
    "region": np.random.choice(["US", "EU", "APAC"], N),
    "category": np.random.choice(["electronics", "books", "home", "clothing"], N),
    "status": np.random.choice(
        ["completed", "processing", "cancelled"], N, p=[0.6, 0.3, 0.1]
    ),
    "quantity": np.random.randint(1, 10, N),
    "unit_price": np.round(np.random.uniform(5.0, 200.0, N), 2),
    "customer_id": np.random.randint(1000, 9999, N),
})

os.makedirs("sample_orders", exist_ok=True)

for i, chunk in enumerate(np.array_split(df, NUM_FILES)):
    path = f"sample_orders/orders_part_{i}.parquet"
    pq.write_table(pa.Table.from_pandas(chunk), path)
    print(f"Wrote {path} ({os.path.getsize(path):,} bytes)")&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Upload the generated files to your source bucket:&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-bash"&gt;aws s3 cp sample_orders/ \
    s3://&amp;lt;amzn-s3-demo-source-bucket&amp;gt;/orders/ \
    --recursive&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Note.&lt;/strong&gt; The CLI commands and code examples in this walkthrough use angle-bracket placeholders such as &lt;code&gt;&amp;lt;amzn-s3-demo-source-bucket&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;amzn-s3-demo-table-bucket&amp;gt;&lt;/code&gt;. Replace these with your own values before running.&lt;/p&gt;
&lt;h3 id="step-1-configure-duckdb-in-the-aws-glue-6.0-job"&gt;Step 1: Configure DuckDB in the AWS Glue 6.0 job&lt;/h3&gt;
&lt;p&gt;The AWS Glue filesystem is read-only except for &lt;code&gt;/tmp&lt;/code&gt;, so DuckDB uses &lt;code&gt;/tmp&lt;/code&gt; as a writable home directory for its extension cache and spill files. The job loads DuckDB extensions: &lt;code&gt;httpfs&lt;/code&gt; reads and writes Amazon S3 objects directly, &lt;a href="https://duckdb.org/docs/current/core_extensions/aws" target="_blank" rel="noopener"&gt;aws&lt;/a&gt; handles AWS credential resolution, refresh, and AWS Region detection, and &lt;code&gt;iceberg&lt;/code&gt; connects to the Amazon S3 Tables REST catalog. The &lt;code&gt;CREDENTIAL_CHAIN&lt;/code&gt; provider (from the &lt;a href="https://duckdb.org/docs/current/core_extensions/aws" target="_blank" rel="noopener"&gt;aws extension&lt;/a&gt;) tells DuckDB to use the standard AWS credential provider chain, which automatically picks up the IAM role attached to the AWS Glue job. No access keys or secrets appear in the code.&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-python"&gt;import os
import duckdb

os.makedirs('/tmp/.duckdb/extensions', exist_ok=True)

con = duckdb.connect(':memory:')
con.execute("SET home_directory='/tmp';")
con.execute("SET extension_directory='/tmp/.duckdb/extensions';")

con.execute("INSTALL httpfs; LOAD httpfs;")
con.execute("INSTALL aws; LOAD aws;")
con.execute("INSTALL iceberg; LOAD iceberg;")

con.execute("CREATE SECRET (TYPE s3, PROVIDER credential_chain);")&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;The &lt;code&gt;home_directory&lt;/code&gt; setting must be applied before loading any extensions. Without it, DuckDB attempts to write to &lt;code&gt;/.duckdb/&lt;/code&gt; and fails with &lt;code&gt;IOError: Permission denied&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id="step-2-read-and-transform-with-duckdb-sql"&gt;Step 2: Read and transform with DuckDB SQL&lt;/h3&gt;
&lt;p&gt;DuckDB reads Amazon S3 Parquet files directly through the &lt;a href="https://duckdb.org/docs/current/core_extensions/httpfs/s3api" target="_blank" rel="noopener"&gt;httpfs extension&lt;/a&gt;. No local download is required. The &lt;code&gt;read_parquet()&lt;/code&gt; function accepts Amazon S3 glob patterns, reading multiple files as a single relation.&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-python"&gt;import sys
from awsglue.utils import getResolvedOptions

args = getResolvedOptions(sys.argv, ['s3_input_path'])
S3_INPUT = args['s3_input_path']

con.execute(f"""
CREATE OR REPLACE TEMP TABLE _batch AS
SELECT date_trunc('day', order_date) AS order_day,
region, category,
COUNT(*) AS total_orders,
SUM(quantity * unit_price) AS gross_revenue,
SUM(CASE WHEN status = 'completed'
THEN quantity * unit_price ELSE 0 END) AS net_revenue,
COUNT(DISTINCT customer_id) AS unique_customers,
ROUND(AVG(quantity * unit_price), 2) AS avg_order_value,
SUM(CASE WHEN quantity * unit_price &amp;gt; 500
THEN 1 ELSE 0 END) AS high_value_orders
FROM read_parquet('{S3_INPUT}')
WHERE status IN ('completed', 'processing')
GROUP BY ALL
ORDER BY order_day DESC, gross_revenue DESC
""")&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;&lt;code&gt;GROUP BY ALL&lt;/code&gt; is a DuckDB SQL extension that groups by every non-aggregate column in the &lt;code&gt;SELECT&lt;/code&gt; list. It’s a convenience feature rather than standard SQL, and support varies across query engines. If you adapt this query for another engine, check whether it supports &lt;code&gt;GROUP BY ALL&lt;/code&gt; or list the grouping columns explicitly (&lt;code&gt;GROUP BY order_day, region, category&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;WHERE&lt;/code&gt; clause retains both &lt;code&gt;completed&lt;/code&gt; and &lt;code&gt;processing&lt;/code&gt; orders. The &lt;code&gt;gross_revenue&lt;/code&gt; column reflects all in-flight revenue, while &lt;code&gt;net_revenue&lt;/code&gt; counts only completed orders. A partition containing only processing orders shows &lt;code&gt;net_revenue = 0&lt;/code&gt;. This is by design: the two columns serve different reporting purposes.&lt;/p&gt;
&lt;h3 id="step-3-write-to-amazon-s3-tables"&gt;Step 3: Write to Amazon S3 Tables&lt;/h3&gt;
&lt;p&gt;DuckDB attaches the S3 Tables bucket as an Iceberg REST catalog using the &lt;a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/s3-tables-integrating-open-source.html" target="_blank" rel="noopener"&gt;ENDPOINT_TYPE s3_tables&lt;/a&gt; option. DuckDB commits each write as a new Iceberg snapshot through the catalog.&lt;/p&gt;
&lt;p&gt;The write uses an idempotent per-day reload pattern: create the table if it does not exist, delete any existing rows for the batch’s date range, then insert. This way, re-runs don’t produce duplicate rows.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; The &lt;code&gt;DELETE&lt;/code&gt; and &lt;code&gt;INSERT&lt;/code&gt; are not committed atomically. If the job fails between them, the affected partition is left empty. For mitigations, see &lt;a href="#error-handling-for-production"&gt;Error handling for production&lt;/a&gt;.&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-python"&gt;import sys
from awsglue.utils import getResolvedOptions

args = getResolvedOptions(sys.argv, ['s3t_arn'])
S3T_ARN = args['s3t_arn']

con.execute(f"ATTACH '{S3T_ARN}' AS s3t (TYPE iceberg, ENDPOINT_TYPE s3_tables);")
con.execute("CREATE SCHEMA IF NOT EXISTS s3t.analytics;")
con.execute("""
CREATE TABLE IF NOT EXISTS s3t.analytics.daily_order_summary (
order_day DATE,
region VARCHAR,
category VARCHAR,
total_orders BIGINT,
gross_revenue DOUBLE,
net_revenue DOUBLE,
unique_customers BIGINT,
avg_order_value DOUBLE,
high_value_orders BIGINT
);
""")

con.execute("""
DELETE FROM s3t.analytics.daily_order_summary
WHERE order_day IN (SELECT DISTINCT order_day FROM _batch);
""")
con.execute("""
INSERT INTO s3t.analytics.daily_order_summary BY NAME
SELECT * FROM _batch;
""")

count = con.execute(
"SELECT COUNT(*) FROM s3t.analytics.daily_order_summary"
).fetchone()[0]
print(f"S3 Tables now holds {count} rows in analytics.daily_order_summary")&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;The resulting Iceberg table is immediately readable by Amazon Athena, Amazon Redshift, and Amazon EMR through the S3 Tables REST catalog. Amazon S3 Tables handles compaction, snapshot expiration, and orphan-file cleanup automatically.&lt;/p&gt;
&lt;h3 id="complete-aws-glue-6.0-job-script"&gt;Complete AWS Glue 6.0 job script&lt;/h3&gt;
&lt;p&gt;The following script combines all three steps with structured logging, error handling, and AWS Glue job parameter parsing. It can be used directly as the script for an AWS Glue 6.0 &lt;code&gt;glueetl&lt;/code&gt; job.&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-python"&gt;import os, sys, logging, duckdb
from awsglue.utils import getResolvedOptions

logging.basicConfig(level=logging.INFO,
                    format='%(asctime)s %(levelname)s %(message)s')
logger = logging.getLogger(__name__)

TARGET = 's3t.analytics.daily_order_summary'

DDL = """
CREATE TABLE IF NOT EXISTS s3t.analytics.daily_order_summary (
order_day DATE, region VARCHAR, category VARCHAR,
total_orders BIGINT, gross_revenue DOUBLE, net_revenue DOUBLE,
unique_customers BIGINT, avg_order_value DOUBLE, high_value_orders BIGINT
);
"""

TRANSFORM = """
SELECT date_trunc('day', order_date) AS order_day,
region, category,
COUNT(*) AS total_orders,
SUM(quantity * unit_price) AS gross_revenue,
SUM(CASE WHEN status = 'completed' THEN quantity * unit_price ELSE 0 END) AS net_revenue,
COUNT(DISTINCT customer_id) AS unique_customers,
ROUND(AVG(quantity * unit_price), 2) AS avg_order_value,
SUM(CASE WHEN quantity * unit_price &amp;gt; 500 THEN 1 ELSE 0 END) AS high_value_orders
FROM read_parquet('{s3_input}')
WHERE status IN ('completed', 'processing')
GROUP BY ALL
ORDER BY order_day DESC, gross_revenue DESC
"""

def setup_duckdb():
    os.makedirs('/tmp/.duckdb/extensions', exist_ok=True)
    con = duckdb.connect(':memory:')
    con.execute("SET home_directory='/tmp';")
    con.execute("SET extension_directory='/tmp/.duckdb/extensions';")
    con.execute("INSTALL httpfs; LOAD httpfs;")
    con.execute("INSTALL aws; LOAD aws;")
    con.execute("INSTALL iceberg; LOAD iceberg;")
    con.execute("CREATE SECRET (TYPE s3, PROVIDER credential_chain);")
    logger.info("DuckDB %s initialized with httpfs, aws, and iceberg extensions",
                duckdb.__version__)
    return con

def transform_orders(con, s3_path):
    logger.info("Reading source data: %s", s3_path)
    con.execute("CREATE OR REPLACE TEMP TABLE _batch AS " +
                TRANSFORM.format(s3_input=s3_path))
    return con.execute("SELECT COUNT(*) FROM _batch").fetchone()[0]

def write_to_s3_tables(con, s3t_arn):
    con.execute(f"ATTACH '{s3t_arn}' AS s3t (TYPE iceberg, ENDPOINT_TYPE s3_tables);")
    con.execute("CREATE SCHEMA IF NOT EXISTS s3t.analytics;")
    con.execute(DDL)
    con.execute(f"""
DELETE FROM {TARGET}
WHERE order_day IN (SELECT DISTINCT order_day FROM _batch);
""")
    con.execute(f"INSERT INTO {TARGET} BY NAME SELECT * FROM _batch;")
    count = con.execute(f"SELECT COUNT(*) FROM {TARGET}").fetchone()[0]
    logger.info("Write complete. %s now holds %s rows.", TARGET, count)
    return count

def main():
    args = getResolvedOptions(sys.argv, ['s3_input_path', 's3t_arn'])
    con = setup_duckdb()
    n = transform_orders(con, args['s3_input_path'])
    logger.info("Transformed %s summary rows", n)
    total = write_to_s3_tables(con, args['s3t_arn'])
    logger.info("ETL complete. Table holds %s total rows.", total)

if __name__ == '__main__':
    main()&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Create the job using the AWS CLI:&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-bash"&gt;aws glue create-job \
    --name duckdb-order-summary \
    --role &amp;lt;YOUR-AWS-GLUE-ROLE&amp;gt; \
    --glue-version "6.0" \
    --number-of-workers 2 --worker-type G.1X \
    --command '{"Name":"glueetl","ScriptLocation":"s3://&amp;lt;amzn-s3-demo-source-bucket&amp;gt;/scripts/duckdb_job.py","PythonVersion":"3"}' \
    --default-arguments '{
    "--additional-python-modules": "duckdb==1.5.1",
    "--s3_input_path": "s3://&amp;lt;YOUR-SOURCE-BUCKET&amp;gt;/orders/year=2026/month=08/*.parquet",
    "--s3t_arn": "arn:aws:s3tables:&amp;lt;YOUR-REGION&amp;gt;:&amp;lt;YOUR-ACCOUNT-ID&amp;gt;:bucket/&amp;lt;amzn-s3-demo-table-bucket&amp;gt;"
}'&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Note.&lt;/strong&gt; Replace the angle-bracket placeholders (&lt;code&gt;&amp;lt;amzn-s3-demo-source-bucket&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;amzn-s3-demo-table-bucket&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;YOUR-REGION&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;YOUR-ACCOUNT-ID&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;YOUR-AWS-GLUE-ROLE&amp;gt;&lt;/code&gt;) with your own values before running.&lt;/p&gt;
&lt;h2 id="error-handling-for-production"&gt;Error handling for production&lt;/h2&gt;
&lt;p&gt;For production use, plan for three failure modes:&lt;/p&gt;
&lt;ul&gt;
 &lt;li&gt;&lt;strong&gt;Catalog access.&lt;/strong&gt; If &lt;code&gt;ATTACH&lt;/code&gt; to Amazon S3 Tables returns an access-denied error, verify that the job’s IAM role has the &lt;code&gt;s3tables&lt;/code&gt; actions listed in the prerequisites on the table bucket ARN.&lt;/li&gt;
 &lt;li&gt;&lt;strong&gt;Partial writes.&lt;/strong&gt; The &lt;code&gt;DELETE&lt;/code&gt; and &lt;code&gt;INSERT&lt;/code&gt; are not committed atomically, so a failure between them can leave a partition empty. Set &lt;code&gt;MaxRetries&lt;/code&gt; to 1 so the idempotent reload re-runs automatically, or write to a staging table and swap on success.&lt;/li&gt;
 &lt;li&gt;&lt;strong&gt;Timeouts.&lt;/strong&gt; Set the job &lt;code&gt;Timeout&lt;/code&gt; higher than the expected run time to stop hung runs.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="monitoring"&gt;Monitoring&lt;/h2&gt;
&lt;p&gt;DuckDB runs inside a standard AWS Glue job, so you can track each run from the AWS Glue console or the &lt;code&gt;GetJobRun&lt;/code&gt; API, including run state, duration, and DPU usage.&lt;/p&gt;
&lt;p&gt;To observe DuckDB itself, use structured logging. The production script uses &lt;code&gt;&lt;span&gt;logger.info&lt;/span&gt;&lt;/code&gt; to record the rows transformed and rows written, and those lines appear in the job’s Amazon CloudWatch Logs stream. For finer-grained timing, add &lt;code&gt;&lt;span&gt;logger.info&lt;/span&gt;&lt;/code&gt; statements around each stage. To track values over time, such as rows written per run, publish them as custom metrics with the CloudWatch &lt;code&gt;PutMetricData&lt;/code&gt; API.&lt;/p&gt;
&lt;h2 id="measured-results"&gt;Measured results&lt;/h2&gt;
&lt;p&gt;The measurements in this section were collected on AWS Glue 6.0 with DuckDB 1.5.1 writing to Amazon S3 Tables in the US East (N. Virginia) Region (&lt;code&gt;us-east-1&lt;/code&gt;). Output tables were verified by querying them in Amazon Athena. Both jobs produced identical output: 1,176 summary rows.&lt;/p&gt;
&lt;p&gt;The dataset consisted of 8.4 million rows across 12 Parquet files (approximately 94 MB compressed on disk, approximately 1.2 GB uncompressed). One job ran DuckDB on the AWS Glue 6.0 runtime. The other ran Apache Spark on AWS Glue 6.0 with the equivalent transform and a native Iceberg write.&lt;/p&gt;
&lt;table border="1px" width="100%" cellpadding="10px"&gt;
 &lt;tbody&gt;
  &lt;tr&gt;
   &lt;td&gt;
    &lt;h3 id="metric"&gt;Metric&lt;/h3&gt;
   &lt;/td&gt;
   &lt;td&gt;
    &lt;h3 id="duckdb-on-aws-glue-6.0-1"&gt;DuckDB on AWS Glue 6.0&lt;/h3&gt;
   &lt;/td&gt;
   &lt;td&gt;
    &lt;h3 id="spark-on-aws-glue-6.0"&gt;Spark on AWS Glue 6.0&lt;/h3&gt;
   &lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;Compute configuration&lt;/td&gt;
   &lt;td&gt;2 DPU (2x G.1X)&lt;/td&gt;
   &lt;td&gt;2 DPU (2x G.1X)&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;Job Duration&lt;/td&gt;
   &lt;td&gt;~56 seconds&lt;/td&gt;
   &lt;td&gt;~117 seconds&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;Billed duration&lt;/td&gt;
   &lt;td&gt;1 minute (minimum)&lt;/td&gt;
   &lt;td&gt;2 minutes&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;Cost per run&lt;/td&gt;
   &lt;td&gt;$0.0103&lt;/td&gt;
   &lt;td&gt;$0.0205&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;Output rows (Athena-verified)&lt;/td&gt;
   &lt;td&gt;1,176&lt;/td&gt;
   &lt;td&gt;1,176&lt;/td&gt;
  &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;On the same AWS Glue 6.0 runtime and the same 2 DPU configuration, DuckDB completed in approximately half the time at approximately half the cost of Spark for this workload.&lt;/p&gt;
&lt;p&gt;Cost is calculated at $0.308 per DPU-hour (AWS Glue 6.0 rate). AWS Glue bills in 1-second increments with a 1-minute minimum per run. Verify against the current &lt;a href="https://aws.amazon.com/glue/pricing/" target="_blank" rel="noopener"&gt;AWS Glue pricing page&lt;/a&gt; for your Region. Results scale with dataset size, query complexity, and Region.&lt;/p&gt;
&lt;p&gt;At 20 runs per day, this job costs approximately $75 per year with DuckDB, compared to $150 per year with Spark. Beyond the cost savings, this pattern keeps SQL-centric work quick to iterate on: you express the transformation in SQL, and DuckDB runs it in-process on the AWS Glue worker.&lt;/p&gt;
&lt;h2 id="clean-up"&gt;Clean up&lt;/h2&gt;
&lt;p&gt;To avoid ongoing charges, delete the resources created during this walkthrough:&lt;/p&gt;
&lt;ol type="1"&gt;
 &lt;li&gt;Delete the AWS Glue job (&lt;code&gt;duckdb-order-summary&lt;/code&gt;).&lt;/li&gt;
 &lt;li&gt;Remove the sample data from your Amazon S3 bucket (&lt;code&gt;s3://&amp;lt;amzn-s3-demo-source-bucket&amp;gt;/orders/&lt;/code&gt;).&lt;/li&gt;
 &lt;li&gt;Drop the Iceberg table in Amazon Athena: &lt;code&gt;DROP TABLE analytics.daily_order_summary;&lt;/code&gt;&lt;/li&gt;
 &lt;li&gt;Delete the Amazon S3 Tables table bucket if it was created for this walkthrough.&lt;/li&gt;
 &lt;li&gt;Remove the IAM role if no longer needed.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;In this post, we demonstrated how to run DuckDB inside an AWS Glue 6.0 job to read Amazon S3 Parquet, transform it with SQL, and write Apache Iceberg tables directly to Amazon S3 Tables. AWS Glue 6.0 modernized the runtime environment to Amazon Linux 2023, Python 3.13, and &lt;a href="https://docs.aws.amazon.com/glue/latest/dg/release-notes.html" target="_blank" rel="noopener"&gt;Apache Spark 4.1&lt;/a&gt;. With that modernization, you can run embedded SQL in the AWS Glue job and write Iceberg tables directly to Amazon S3 Tables. For ETL jobs where the data fits in memory on a single worker, this pattern completed the same work in approximately half the time and half the cost of Spark. The Measured results section describes these measurements. The job uses the same &lt;code&gt;glueetl&lt;/code&gt; job type, IAM configuration, and triggering mechanisms as any Spark job on AWS Glue. When a workload outgrows single-worker processing, switching the script back to PySpark requires no infrastructure changes. The result is the ability to match the engine to each job: a scheduled SQL transformation and a large distributed workload can run on one platform, and you pick the engine per job without managing separate systems.&lt;/p&gt;
&lt;p&gt;To get started, create an AWS Glue 6.0 job, add &lt;code&gt;duckdb==1.5.1&lt;/code&gt; through the &lt;code&gt;--additional-python-modules&lt;/code&gt; parameter, and point it at your Amazon S3 source data and an Amazon S3 Tables bucket. The complete script in this post is a working starting point you can adapt to your own datasets and schedules. For more information, see the &lt;a href="https://docs.aws.amazon.com/glue/latest/dg/" target="_blank" rel="noopener"&gt;AWS Glue Developer Guide&lt;/a&gt; and the &lt;a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/s3-tables.html" target="_blank" rel="noopener"&gt;Amazon S3 Tables user guide&lt;/a&gt;. For a complementary pattern that uses DuckDB to read and query data in Amazon S3 Tables, see &lt;a href="https://aws.amazon.com/blogs/storage/streamlining-access-to-tabular-datasets-stored-in-amazon-s3-tables-with-duckdb/" target="_blank" rel="noopener"&gt;Streamlining access to tabular datasets stored in Amazon S3 Tables with DuckDB&lt;/a&gt;.&lt;/p&gt;
&lt;hr style="width: 100%"&gt;
&lt;h2&gt;About the authors&lt;/h2&gt;
&lt;footer&gt;
 &lt;div class="blog-author-box" style="padding-top: 2.0em"&gt;
  &lt;div class="blog-author-image" style="margin-right: 1.0em"&gt;
   &lt;img loading="lazy" class="alignnone size-full" src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/10/05/BDB-6254-2.jpg" alt="Bezuayehu Wate" width="100" height="133"&gt;
  &lt;/div&gt;
  &lt;h3 class="lb-h4"&gt;Bezuayehu Wate&lt;/h3&gt;
  &lt;p style="overflow: hidden"&gt;&lt;a href="https://www.linkedin.com/in/bezuayehutwate" target="_blank" rel="noopener"&gt;Bezuayehu&lt;/a&gt; is a Specialist Solutions Architect at AWS, specializing in big data analytics and AI. She works closely with customers to modernize their analytics platforms with AWS data and AI services, and is passionate about emerging technologies and designing cloud solutions that deliver measurable impact for customers.&lt;/p&gt;
 &lt;/div&gt;
 &lt;div class="blog-author-box" style="padding-top: 2.0em"&gt;
  &lt;div class="blog-author-image" style="margin-right: 1.0em"&gt;
   &lt;img loading="lazy" class="alignnone size-full" src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/10/05/BDB-6254-3.jpg" alt="Manjeet Chayel" width="100" height="133"&gt;
  &lt;/div&gt;
  &lt;h3 class="lb-h4"&gt;Manjeet Chayel&lt;/h3&gt;
  &lt;p style="overflow: hidden"&gt;Manjeet Chayel serves as Big Data Manager, Worldwide Specialist Solutions Architects at AWS, where he leads a global team of specialist architects driving customer-facing engagements across Amazon EMR, AWS Glue, and the broader Big Data Analytics portfolio. With over 15 years at Amazon, he brings deep expertise in big data processing and building experiences that operate reliably at massive scale combining work with customers architecting their analytics platforms with a focus on scaling and developing the next generation of technical leaders across AWS.&lt;/p&gt;
 &lt;/div&gt;
&lt;/footer&gt;</content:encoded>
					
		
		
			</item>
		<item>
		<title>Query across accounts and table formats with multi-catalog in Amazon EMR</title>
		<link>https://aws.amazon.com/blogs/big-data/query-across-accounts-and-table-formats-with-multi-catalog-in-amazon-emr/</link>
		
		<dc:creator><![CDATA[Suthan Phillips]]></dc:creator>
		<pubDate>Tue, 06 Oct 2026 16:05:27 +0000</pubDate>
				<category><![CDATA[Advanced (300)]]></category>
		<category><![CDATA[Amazon EMR]]></category>
		<category><![CDATA[Announcements]]></category>
		<guid isPermaLink="false">41ada7c7c2baf6f4c9588a36bbdd496105958848</guid>

					<description>Amazon EMR 8.1.0 adds multi-catalog support through the RedirectingSessionCatalog, so you can query Iceberg, Delta Lake, Hudi, and Hive tables through a single catalog and join data across AWS accounts without copying it. This post shows how to put these capabilities into practice on Amazon EMR Serverless.</description>
										<content:encoded>&lt;p&gt;Analytics teams on AWS often store data in more than one open table format, and that data frequently lives in more than one AWS account. Two problems follow: querying across table formats without catalog-level complexity, and joining data across accounts without copying it. This is common in a data mesh, where domain teams own data in separate AWS accounts while analytics workloads run centrally. Multi-catalog support in Amazon EMR 8.1.0 addresses both problems.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://aws.amazon.com/emr/" target="_blank" rel="noopener"&gt;Amazon EMR&lt;/a&gt; release 8.1.0 addresses both challenges with multi-catalog support. The &lt;code&gt;RedirectingSessionCatalog&lt;/code&gt; (RSC) is an opt-in catalog that you set as the Spark default. It automatically detects each table’s format from AWS Glue metadata, routes queries to the correct format handler, and supports multiple AWS Glue Data Catalogs across AWS accounts. With multi-catalog support, you can query Iceberg, Hudi, Delta Lake, and Hive tables through a single unified catalog. You can also join tables across AWS accounts without copying data and discover remote catalogs dynamically at query time.&lt;/p&gt;
&lt;p&gt;In this post, we show how to put these capabilities into practice using &lt;a href="https://aws.amazon.com/emr/serverless/" target="_blank" rel="noopener"&gt;Amazon EMR Serverless&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="the-spark-single-catalog-constraint"&gt;The Spark single-catalog constraint&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Many formats.&lt;/strong&gt; The Spark default catalog (spark_catalog) accepts only one CatalogExtension at a time: SparkSessionCatalog for Iceberg, DeltaCatalog for Delta Lake, or HoodieCatalog for Hudi. You configure one, and queries against tables in other formats fail unless those tables are registered in a separate, format-specific catalog.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Many accounts.&lt;/strong&gt; The Spark V1 metastore (ExternalCatalog) is a singleton bound to one AWS Glue Data Catalog in one account. The Spark V2 catalog API supports named catalogs, but only Iceberg uses it. Delta Lake, Hudi, and Hive tables still rely on V1. As a result, cross-account access for those formats required complex multi-step workarounds. These included AWS Lake Formation grants, AWS Resource Access Manager (AWS RAM) shares, resource links, and per-table permissions.&lt;/p&gt;
&lt;p&gt;Amazon EMR 8.1.0 addresses both constraints with the &lt;code&gt;RedirectingSessionCatalog&lt;/code&gt;, described in the following section.&lt;/p&gt;
&lt;h2 id="the-redirectingsessioncatalog"&gt;The RedirectingSessionCatalog&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;RedirectingSessionCatalog&lt;/code&gt; provides three opt-in, backward-compatible capabilities. Set RSC as the default catalog to run multi-format queries without format-specific prefixes. Declare a named RSC catalog to join across accounts without data copies. Turn on the AWS Glue Data Catalog resolver to discover and register remote catalogs at query time, with no upfront &lt;code&gt;spark.sql.catalog.*&lt;/code&gt; configuration. The following sections cover each capability.&lt;/p&gt;
&lt;h3 id="multi-format-support"&gt;Multi-format support&lt;/h3&gt;
&lt;p&gt;In Amazon EMR 8.1.0, you set the default catalog to the &lt;code&gt;RedirectingSessionCatalog&lt;/code&gt; (RSC). On table resolution, RSC calls the AWS Glue Data Catalog, reads the table’s format from its metadata, caches the result, and delegates to the matching format-specific catalog (Iceberg, Delta Lake, Hudi, or Hive).&lt;/p&gt;
&lt;p&gt;Without this feature, you had to register a separate catalog for each format and prefix every table reference:&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-properties"&gt;# One catalog per format, all pointing at the same Glue metastore
spark.sql.catalog.spark_catalog = org.apache.iceberg.spark.SparkSessionCatalog
spark.sql.catalog.delta_catalog = org.apache.spark.sql.delta.catalog.DeltaCatalog
spark.sql.catalog.hudi_catalog = org.apache.spark.sql.hudi.catalog.HoodieCatalog

# Queries must use format-specific catalog prefixes
SELECT * FROM spark_catalog.db.iceberg_table;
SELECT * FROM delta_catalog.db.delta_table;
SELECT * FROM hudi_catalog.db.hudi_table;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;With multi-format support, this reduces to a single catalog property:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What you set:&lt;/strong&gt;&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-properties"&gt;# Replace the default catalog with RedirectingSessionCatalog.
# RSC auto-detects each table's format from Glue metadata.
spark.sql.catalog.spark_catalog = org.apache.spark.sql.connector.catalog.\
redirecting.RedirectingSessionCatalog&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;How you query:&lt;/strong&gt;&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-sql"&gt;-- No format prefixes needed. RSC resolves the format at query time.
SELECT * FROM db.iceberg_table;
SELECT * FROM db.delta_table;
SELECT * FROM db.hudi_table;

-- Cross-format joins work in a single statement.
SELECT i.id, d.val, h.val
FROM db.iceberg_table i
JOIN db.delta_table d ON i.id = d.id
JOIN db.hudi_table h ON i.id = h.id;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;On table resolution, RSC:&lt;/p&gt;
&lt;ul&gt;
 &lt;li&gt;Calls the AWS Glue Data Catalog to read table metadata.&lt;/li&gt;
 &lt;li&gt;Inspects the table’s Parameters map to determine the format (Iceberg, Delta Lake, Hudi, or Hive/Parquet).&lt;/li&gt;
 &lt;li&gt;Delegates the operation to the appropriate format-specific catalog implementation.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The following diagram shows how a single query flows through the &lt;code&gt;RedirectingSessionCatalog&lt;/code&gt; to the correct format handler.&lt;/p&gt;
&lt;div style="width: 810px" class="wp-caption alignnone"&gt;
 &lt;a href="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/29/BDB-6221-1-1.png" target="_blank" rel="noopener"&gt;&lt;img src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/29/BDB-6221-1-1.png" alt="RedirectingSessionCatalog routing a query to Iceberg, Delta Lake, Hudi, and Hive handlers through AWS Glue" width="800"&gt;&lt;/a&gt;
 &lt;p class="wp-caption-text"&gt;Figure 1: Multi-format routing. A single query enters the RedirectingSessionCatalog, which calls the AWS Glue Data Catalog to read each table’s format, then routes the table to the matching format-specific handler (Iceberg, Delta Lake, Hudi, or Hive) so results return through one catalog&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;As the diagram illustrates, the query enters through spark_catalog (the RSC). The RSC reads each table’s format from the AWS Glue Data Catalog and routes the operation to the matching engine: Iceberg, Delta Lake, Hudi, or Hive/Parquet. The four format handlers read the underlying data files from Amazon Simple Storage Service (Amazon S3). The caller issues one query with no format-specific catalog prefixes.&lt;/p&gt;
&lt;h3 id="multi-catalog-cross-account-access"&gt;Multi-catalog: Cross-account access&lt;/h3&gt;
&lt;p&gt;When production data lives in a separate AWS account from your analytics compute, you can declare a named RSC catalog that points at that account’s AWS Glue Data Catalog. RSC resolves tables in the remote account the same way it resolves local tables, so a single query can join across accounts without copying data.&lt;/p&gt;
&lt;p&gt;Previously, cross-account table resolution worked only for Iceberg (V2 catalog). Hive, Delta Lake, and Hudi tables in another account required manual Lake Formation and AWS RAM configuration rather than catalog-level resolution. With Amazon EMR 8.1.0, you declare a named RSC catalog for the remote account:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Configuration:&lt;/strong&gt;&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-properties"&gt;# Declare a named catalog pointing at the remote account's Glue catalog.
# "prod" is any name you choose for this catalog.
spark.sql.catalog.prod = org.apache.spark.sql.connector.catalog.\
redirecting.RedirectingSessionCatalog

# Tell it to use Glue as the metastore backend.
spark.sql.catalog.prod.metastore.type = glue

# Point it at the remote account's Glue catalog ID.
spark.sql.catalog.prod.metastore.hadoop.hive.metastore.glue.catalogid = 111122223333&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Query:&lt;/strong&gt;&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-sql"&gt;-- Join local and remote tables directly. No data copy.
SELECT o.order_id, f.status
FROM spark_catalog.analytics.orders o -- local Iceberg table
JOIN prod.salesdb.fulfillment f -- remote Hudi table (account 111122223333)
ON o.order_id = f.order_id;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Each named RSC instance creates its own V1 metastore delegate and registers it with the global SessionCatalog, removing the singleton limitation.&lt;/p&gt;
&lt;p&gt;The following diagram shows how a single query joins tables across two AWS accounts through named catalogs.&lt;/p&gt;
&lt;div style="width: 810px" class="wp-caption alignnone"&gt;
 &lt;a href="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/29/BDB-6221-2-1.png" target="_blank" rel="noopener"&gt;&lt;img src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/29/BDB-6221-2-1.png" alt="A query joining a local table and a remote table across two AWS accounts through named catalogs" width="800"&gt;&lt;/a&gt;
 &lt;p class="wp-caption-text"&gt;Figure 2: Cross-account access. A query in the local account references a named RSC catalog that points at a second account’s AWS Glue Data Catalog, so the local and remote tables join in one query without copying data between accounts&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;As the diagram illustrates, the analytics account uses spark_catalog (the RSC) for its local Glue Data Catalog, while a named catalog (prod) points at the production account’s Glue Data Catalog. The query joins a local table to a remote table in a single statement, shown by the JOIN between the two accounts. Each account keeps its own Glue Data Catalog, and no data is copied between them.&lt;/p&gt;
&lt;h3 id="auto-wiring"&gt;Auto-wiring&lt;/h3&gt;
&lt;p&gt;The preceding multi-catalog setup requires you to pre-declare each remote catalog in &lt;code&gt;spark.sql.catalog.*&lt;/code&gt; properties. Auto-wiring in Amazon EMR 8.1.0 removes this requirement at two levels.&lt;/p&gt;
&lt;p&gt;Before Amazon EMR 8.1.0, you declared the resolver, a handler per format, and each handler’s delegate class explicitly:&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-properties"&gt;# Declare the resolver, a per-format handler, and each handler's delegate catalog
spark.sql.catalog.spark_catalog.table-format-resolver = \
com.amazonaws.glue.catalog.redirecting.GlueTableFormatResolver
spark.sql.catalog.spark_catalog.handler.iceberg = \
org.apache.spark.sql.connector.catalog.redirecting.DefaultTableFormatCatalogHandler
spark.sql.catalog.spark_catalog.handler.delta = \
org.apache.spark.sql.connector.catalog.redirecting.DefaultTableFormatCatalogHandler
spark.sql.catalog.spark_catalog.handler.hudi = \
org.apache.spark.sql.connector.catalog.redirecting.DefaultTableFormatCatalogHandler
spark.sql.catalog.spark_catalog.iceberg.delegate-class = org.apache.iceberg.spark.SparkCatalog
spark.sql.catalog.spark_catalog.iceberg.catalog-impl = org.apache.iceberg.aws.glue.GlueCatalog
spark.sql.catalog.spark_catalog.delta.delegate-class = \
org.apache.spark.sql.delta.catalog.DeltaCatalog
spark.sql.catalog.spark_catalog.hudi.delegate-class = \
org.apache.spark.sql.hudi.catalog.HoodieCatalog

# Manually declare every remote catalog you might query
spark.sql.catalog.prod = ...RedirectingSessionCatalog
spark.sql.catalog.prod.metastore.type = glue
spark.sql.catalog.prod.metastore.hadoop.hive.metastore.glue.catalogid = 111122223333
spark.sql.catalog.staging = ...RedirectingSessionCatalog
spark.sql.catalog.staging.metastore.type = glue
spark.sql.catalog.staging.metastore.hadoop.hive.metastore.glue.catalogid = 444455556666&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Auto-wiring reduces this to:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Configuration:&lt;/strong&gt;&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-properties"&gt;# Set the default catalog. EMR auto-registers Iceberg, Delta, Hudi handlers.
# No handler.* properties needed.
spark.sql.catalog.spark_catalog = org.apache.spark.sql.connector.catalog.\
redirecting.RedirectingSessionCatalog

# Enable catalog discovery. The resolver inspects the connection type and
# registers the catalog at query time, with no upfront spark.sql.catalog.* config.
spark.sql.catalogResolver = com.amazonaws.glue.catalog.\
redirecting.GlueCatalogResolver

# Optional. Falls back to the SDK default region chain. Set this to query Glue catalogs in a specific region.
spark.sql.catalogResolver.region = us-east-1&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Query:&lt;/strong&gt;&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-sql"&gt;-- Reference a remote catalog by its account ID. No prior declaration exists.
-- EMR calls Glue GetCatalog, determines the type, registers it on the spot.
SELECT o.order_id, f.status
FROM spark_catalog.analytics.orders o
JOIN `111122223333`.sales.fulfillment f
ON o.order_id = f.order_id;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;The AWS Glue Data Catalog resolver is opt-in. When enabled, Amazon EMR issues an AWS Glue GetCatalog API call each time a query references a catalog that hasn’t been registered. This isn’t enabled by default to avoid unintended API calls for catalog names that don’t exist.&lt;/p&gt;
&lt;p&gt;At the handler level, setting the default catalog to &lt;code&gt;RedirectingSessionCatalog&lt;/code&gt; is enough. Amazon EMR fills in the Iceberg, Delta, and Hudi handlers automatically, so you don’t need to write handler.* properties.&lt;/p&gt;
&lt;p&gt;At the catalog level, when you enable the Glue catalog resolver, Amazon EMR discovers new catalogs on demand. The first time a query references an undeclared catalog, Amazon EMR calls the AWS Glue GetCatalog API, inspects the connection type, and registers the catalog at query time.&lt;/p&gt;
&lt;p&gt;Dynamic discovery is functional in Amazon EMR 8.1.0 for three catalog types. For standard cross-account AWS Glue catalogs, the resolver registers a redirecting catalog and multi-format routing applies. For Amazon S3 Tables, a capability of Amazon S3, the resolver reads the federated AWS Glue metadata and routes through Iceberg for both reads and writes. It also supports Amazon Redshift Managed Storage.&lt;/p&gt;
&lt;p&gt;The following diagram shows how the &lt;code&gt;GlueCatalogResolver&lt;/code&gt; discovers and registers a catalog the first time a query references it.&lt;/p&gt;
&lt;div style="width: 810px" class="wp-caption alignnone"&gt;
 &lt;a href="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/29/BDB-6221-3-1.png" target="_blank" rel="noopener"&gt;&lt;img src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/29/BDB-6221-3-1.png" alt="GlueCatalogResolver registering an undeclared catalog at query time through the AWS Glue GetCatalog API" width="800"&gt;&lt;/a&gt;
 &lt;p class="wp-caption-text"&gt;Figure 3: Auto-wiring. When a query references an undeclared catalog, the Glue catalog resolver calls the AWS Glue GetCatalog API, inspects the connection type, and registers the catalog at query time, so no upfront catalog configuration is required&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;As the diagram illustrates, a query references a catalog that has not been declared, which raises a catalog-not-found condition. The &lt;code&gt;GlueCatalogResolver&lt;/code&gt; intercepts it and calls the AWS Glue Data Catalog through the GetCatalog API. Based on the connection type, the resolver registers the appropriate catalog: a redirecting catalog for a cross-account AWS Glue Data Catalog, a push-down catalog for Redshift Managed Storage, or a Spark catalog for Amazon S3 Tables. Registration happens at query time, with no upfront configuration.&lt;/p&gt;
&lt;h2 id="quick-start"&gt;Quick start&lt;/h2&gt;
&lt;p&gt;Follow these steps to enable multi-catalog support on an existing Amazon EMR 8.1.0 application:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1.&lt;/strong&gt; Set spark_catalog to the &lt;code&gt;RedirectingSessionCatalog&lt;/code&gt;:&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-properties"&gt;spark.sql.catalog.spark_catalog = org.apache.spark.sql.connector.catalog.\
redirecting.RedirectingSessionCatalog&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; If using Delta Lake or Hudi, also add open table format (OTF) session extensions. Hudi additionally requires KryoSerializer.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; To query a different AWS account, add a named catalog pointing to that account’s AWS Glue Data Catalog.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. (Optional) Enable the Glue catalog resolver:&lt;/strong&gt;&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-properties"&gt;spark.sql.catalogResolver = com.amazonaws.glue.catalog.\
redirecting.GlueCatalogResolver&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Step 1 is all you need for Iceberg-only multi-format queries. The notes call out additional configuration for Delta Lake, Hudi, or cross-account scenarios. Step 2 removes the need to pre-declare catalogs by resolving them at query time.&lt;/p&gt;
&lt;h2 id="try-it-yourself"&gt;Try it yourself&lt;/h2&gt;
&lt;p&gt;The following walkthrough creates four tables (one per format), runs a cross-format join, and extends to a cross-account query. The accompanying sample scripts handle resource creation, job submission, and cleanup. The accompanying code is in the &lt;a href="https://github.com/aws-samples/aws-emr-utilities" target="_blank" rel="noopener"&gt;aws-emr-utilities repository&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id="prerequisites"&gt;Prerequisites&lt;/h3&gt;
&lt;ul&gt;
 &lt;li&gt;An AWS account with permissions for Amazon EMR Serverless, AWS Glue Data Catalog, and Amazon S3.&lt;/li&gt;
 &lt;li&gt;An Amazon EMR Serverless application running release emr-spark-8.1.0 (Spark). The multi-catalog features also work on Amazon EMR on EC2 and Amazon EMR on EKS.&lt;/li&gt;
 &lt;li&gt;An Amazon EMR Serverless job execution role scoped to the specific AWS Glue databases and S3 prefixes.&lt;/li&gt;
 &lt;li&gt;An S3 bucket for scripts and output (this post uses &lt;code&gt;s3://amzn-s3-demo-bucket/multicatalog/&lt;/code&gt;).&lt;/li&gt;
 &lt;li&gt;(For cross-account) A producer account with Lake Formation grants, AWS Glue resource policy, Amazon S3 bucket policy, and AWS Key Management Service (AWS KMS) key policy configured.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="step-by-step-implementation"&gt;Step-by-step implementation&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Step 1:&lt;/strong&gt; Clone the repository and configure&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-bash"&gt;git clone https://github.com/aws-samples/aws-emr-utilities.git
cd aws-emr-utilities/examples/emr-multi-catalog
cp env.template .env
# Edit .env with your application ID, role ARN, bucket, and region&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;The repository contains two phases: single-account multi-format and cross-account. The .env file stores resource identifiers referenced by all scripts.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Step 2:&lt;/strong&gt; Bootstrap the environment&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-bash"&gt;./scripts/bootstrap.sh&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;This script creates the AWS Glue database, uploads PySpark scripts to S3, and verifies that your Amazon EMR Serverless application is in CREATED state. Note the application ID from the output if you have not set it in .env.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Step 3:&lt;/strong&gt; Create tables across four formats&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-bash"&gt;./scripts/run_demo.sh --phase setup&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;The setup phase submits a PySpark job that creates one table per format (Iceberg, Delta Lake, Hudi, Hive/Parquet) in a single AWS Glue database with a shared id/val schema. Each &lt;code&gt;CREATE TABLE&lt;/code&gt; uses a different &lt;code&gt;USING&lt;/code&gt; clause but all go through the same spark_catalog. RSC routes each to the correct engine.&lt;/p&gt;
&lt;p&gt;The job configuration includes the &lt;code&gt;RedirectingSessionCatalog&lt;/code&gt;, OTF session extensions for Delta and Hudi, and KryoSerializer for Hudi. If your workload is Iceberg-only, you can omit the extensions and serializer.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Step 4:&lt;/strong&gt; Run the cross-format join&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-bash"&gt;./scripts/run_demo.sh --phase query&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;This submits a query that references four tables by database.table only, with no format prefix. The query joins all four through a single catalog with no format-specific configuration:&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-sql"&gt;SELECT i.id, i.val AS iceberg, d.val AS delta, h.val AS hudi, p.val AS hive
FROM salesdb.orders_iceberg i
JOIN salesdb.returns_delta d ON i.id = d.id
JOIN salesdb.shipments_hudi h ON i.id = h.id
JOIN salesdb.products_hive p ON i.id = p.id;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Expected output:&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-text"&gt;+---+---------+-------+-------+------+
| id| iceberg | delta | hudi | hive |
+---+---------+-------+-------+------+
| 1| ice-1 | dl-1 | hu-1 | hv-1 |
| 2| ice-2 | dl-2 | hu-2 | hv-2 |
| 3| ice-3 | dl-3 | hu-3 | hv-3 |
+---+---------+-------+-------+------+&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Each column came from a different table format, joined in one query with no format-specific catalog configuration.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Step 5:&lt;/strong&gt; (Optional) Extend to cross-account&lt;/p&gt;
&lt;p&gt;Cross-account access requires grants from the account that owns the data. Run one bootstrap in each account:&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-bash"&gt;# In the consumer account (where EMR runs):
./scripts/bootstrap_consumer.sh --region us-east-1 \
--producer-account 111122223333

# In the producer account (which owns the data):
./scripts/bootstrap_producer.sh \
--consumer-role &amp;lt;ROLE_ARN from previous step&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Then, back in the consumer account, run the cross-account phase:&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-bash"&gt;./scripts/run_demo.sh --phase xacct-autowire \
--producer-account 111122223333 --producer-table fulfillment&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;The result joins the producer account’s table to a local Iceberg table in a single query, with no data copy.&lt;/p&gt;
&lt;p&gt;For the full cross-account policy setup (Lake Formation grants, AWS Glue resource policy, S3 bucket policy, and AWS KMS key policy), see the Cross-account setup section.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; Add –dry-run to either bootstrap script to preview every action it would take (buckets, roles, policies, applications) without creating anything.&lt;/p&gt;
&lt;h3 id="clean-up"&gt;Clean up&lt;/h3&gt;
&lt;p&gt;To avoid ongoing charges, run the clean-up script or follow the steps in the repository README:&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-bash"&gt;./scripts/cleanup.sh&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;This removes S3 data, AWS Glue databases and tables, Lake Formation permissions, and the Amazon EMR Serverless application.&lt;/p&gt;
&lt;h2 id="cross-account-setup"&gt;Cross-account setup&lt;/h2&gt;
&lt;p&gt;Cross-account access requires configuration across four services. The following example shows the AWS Glue resource policy. The accompanying &lt;code&gt;bootstrap_producer.sh&lt;/code&gt; script configures all four, so you don’t need to author each policy by hand.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1. AWS Lake Formation:&lt;/strong&gt; Grant permissions on the database and tables to the consumer principal.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. AWS Glue resource policy:&lt;/strong&gt; Allow cross-account access to catalog metadata.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3. Amazon S3 bucket policy:&lt;/strong&gt; Allow access to the underlying data files.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;4. AWS KMS key policy:&lt;/strong&gt; Use a customer managed key (the aws/glue managed key doesn’t support cross-account grants).&lt;/p&gt;
&lt;h3 id="example-aws-glue-resource-policy"&gt;Example: AWS Glue resource policy&lt;/h3&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-json"&gt;{ "Version": "2012-10-17", "Statement": [{
"Effect": "Allow",
"Principal": {"AWS": "arn:aws:iam::444455556666:role/EMRExecutionRole"},
"Action": ["glue:GetCatalog","glue:GetDatabase","glue:GetDatabases",
"glue:GetTable","glue:GetTables","glue:GetPartition","glue:GetPartitions"],
"Resource": ["arn:aws:glue:us-east-1:111122223333:catalog",
"arn:aws:glue:us-east-1:111122223333:database/*",
"arn:aws:glue:us-east-1:111122223333:table/*/*"]
}]}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h2 id="choosing-the-right-configuration"&gt;Choosing the right configuration&lt;/h2&gt;
&lt;table border="1px" width="100%" cellpadding="10px"&gt;
 &lt;tbody&gt;
  &lt;tr&gt;
   &lt;td&gt;&lt;strong&gt;Your situation&lt;/strong&gt;&lt;/td&gt;
   &lt;td&gt;&lt;strong&gt;What to set&lt;/strong&gt;&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;Lake has Iceberg, Delta, and Hudi tables&lt;/td&gt;
   &lt;td&gt;&lt;code&gt;spark.sql.catalog.spark_catalog&lt;/code&gt; = RedirectingSessionCatalog (Delta and Hudi require additional &lt;code&gt;spark.sql.extensions&lt;/code&gt;. See Quick start)&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;Analytics in one account, data in another&lt;/td&gt;
   &lt;td&gt;&lt;code&gt;spark.sql.catalog.&amp;lt;name&amp;gt;&lt;/code&gt; pointing at the remote Glue account (see Multi-catalog section)&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;Many accounts, want zero upfront config&lt;/td&gt;
   &lt;td&gt;&lt;code&gt;spark.sql.catalogResolver&lt;/code&gt; = GlueCatalogResolver&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;All of the above&lt;/td&gt;
   &lt;td&gt;All three. They compose.&lt;/td&gt;
  &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Multi-catalog is query-time resolution. It doesn’t copy data between accounts, replicate tables, or grant access. Cross-account reads still require Lake Formation grants, AWS Glue resource policies, Amazon S3 bucket policies, and (if encrypted) AWS KMS key policies.&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;In this post, we configured the &lt;code&gt;RedirectingSessionCatalog&lt;/code&gt; as the default Spark catalog on Amazon EMR 8.1.0. With a single configuration property, the RSC resolved Iceberg, Delta Lake, Hudi, and Hive tables through one catalog without format-specific prefixes. We then declared a named catalog to join tables across two AWS accounts, and enabled the &lt;code&gt;GlueCatalogResolver&lt;/code&gt; to discover remote catalogs at query time without pre-declared &lt;code&gt;spark.sql.catalog.*&lt;/code&gt; properties.&lt;/p&gt;
&lt;p&gt;Multi-catalog support is available on Amazon EMR 8.1.0 across all deployment models: Amazon EMR Serverless, Amazon EMR on EC2, and Amazon EMR on EKS. To reproduce the walkthrough, clone the aws-samples/aws-emr-utilities repository and follow the steps in the Quick start section.&lt;/p&gt;
&lt;p&gt;To learn more and get started, explore the following resources:&lt;/p&gt;
&lt;ul&gt;
 &lt;li&gt;&lt;a href="https://aws.amazon.com/emr/" target="_blank" rel="noopener"&gt;Amazon EMR&lt;/a&gt;&lt;/li&gt;
 &lt;li&gt;&lt;a href="https://docs.aws.amazon.com/emr/" target="_blank" rel="noopener"&gt;Amazon EMR documentation&lt;/a&gt;&lt;/li&gt;
 &lt;li&gt;&lt;a href="https://github.com/aws-samples/aws-emr-utilities" target="_blank" rel="noopener"&gt;aws-emr-utilities repository&lt;/a&gt; with the accompanying walkthrough code&lt;/li&gt;
 &lt;li&gt;&lt;a href="https://aws.amazon.com/blogs/big-data/get-a-quick-start-with-apache-hudi-apache-iceberg-and-delta-lake-with-amazon-emr-on-eks/" target="_blank" rel="noopener"&gt;Get a quick start with Apache Hudi, Apache Iceberg, and Delta Lake with Amazon EMR on EKS&lt;/a&gt;&lt;/li&gt;
 &lt;li&gt;&lt;a href="https://aws.amazon.com/blogs/big-data/enforce-fine-grained-access-control-on-open-table-formats-via-amazon-emr-integrated-with-aws-lake-formation/" target="_blank" rel="noopener"&gt;Enforce fine-grained access control on open table formats via Amazon EMR integrated with AWS Lake Formation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style="clear: both"&gt;&lt;/p&gt;
&lt;hr style="width: 100%"&gt;
&lt;h2&gt;About the authors&lt;/h2&gt;
&lt;footer&gt;
 &lt;div class="blog-author-box" style="padding-top: 2.0em"&gt;
  &lt;div class="blog-author-image" style="margin-right: 1.0em"&gt;
   &lt;img loading="lazy" class="alignnone size-full" src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/29/BDB-6221-4-1.jpg" alt="Suthan Phillips" width="100" height="133"&gt;
  &lt;/div&gt;
  &lt;h3 class="lb-h4"&gt;Suthan Phillips&lt;/h3&gt;
  &lt;p style="overflow: hidden"&gt;Suthan is a Senior Specialist Solutions Architect at AWS, helping customers design and optimize scalable, high-performance data platforms that turn data into business insights. He brings expertise in system architecture, performance tuning, and security best practices across the full data stack, from ingestion and processing to analytics and visualization. Outside of work, Suthan enjoys swimming, hiking, and exploring the Pacific Northwest.&lt;/p&gt;
 &lt;/div&gt;
 &lt;div class="blog-author-box" style="padding-top: 2.0em"&gt;
  &lt;div class="blog-author-image" style="margin-right: 1.0em"&gt;
   &lt;img loading="lazy" class="alignnone size-full" src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/29/BDB-6221-5-1.jpg" alt="Manjeet Chayel" width="100" height="133"&gt;
  &lt;/div&gt;
  &lt;h3 class="lb-h4"&gt;Manjeet Chayel&lt;/h3&gt;
  &lt;p style="overflow: hidden"&gt;Manjeet serves as Big Data Manager, Worldwide Specialist Solutions Architects at AWS, where he leads a global team of specialist architects driving customer-facing engagements across Amazon EMR, AWS Glue, and the broader Big Data Analytics portfolio. With over 15 years at Amazon, he brings deep expertise in big data processing and building experiences that operate reliably at massive scale. He combines work with customers architecting their analytics platforms with a focus on scaling and developing the next generation of technical leaders across AWS.&lt;/p&gt;
 &lt;/div&gt;
&lt;/footer&gt;</content:encoded>
					
		
		
			</item>
		<item>
		<title>Materialize once, query anywhere: Introducing Iceberg materialized views in Amazon Redshift</title>
		<link>https://aws.amazon.com/blogs/big-data/materialize-once-query-anywhere-introducing-iceberg-materialized-views-in-amazon-redshift/</link>
		
		<dc:creator><![CDATA[Sudipta Bagchi]]></dc:creator>
		<pubDate>Mon, 05 Oct 2026 21:41:49 +0000</pubDate>
				<category><![CDATA[Advanced (300)]]></category>
		<category><![CDATA[Amazon Redshift]]></category>
		<category><![CDATA[Announcements]]></category>
		<guid isPermaLink="false">dc8b3605b003f896ba49f73bc68d25ec14dbf492</guid>

					<description>Amazon Redshift now supports Iceberg materialized views. Compute an aggregation once in Amazon Redshift and store the result as a standard Apache Iceberg table in Amazon S3, queryable by Amazon Athena, Apache Spark, Amazon SageMaker, and AWS Glue. This post covers use cases, incremental refresh, and a step-by-step getting-started guide.</description>
										<content:encoded>&lt;p&gt;Amazon Redshift has progressively deepened its integration with Apache Iceberg. Earlier this year we launched Amazon Redshift RG, powered by AWS Graviton, with a purpose-built, integrated vectorized query engine designed from the ground up for data lakes. Instead of sending scans to a separate fleet, RG runs them natively on the cluster using vectorized Parquet scans, a smart-prefetch I/O subsystem, partition- and file-level pruning, improved bloom filters, and automatic Iceberg statistics collection through JIT Analyze for better query plans. Together, these deliver &lt;a href="https://aws.amazon.com/blogs/big-data/amazon-redshift-rg-faster-and-lower-cost-graviton-powered/" target="_blank" rel="noopener"&gt;up to 2.4x faster Apache Iceberg queries than RA3&lt;/a&gt;, at 30 percent lower cost per vCPU and with no per-terabyte scan charges on data lake queries. On top of that performance foundation, you can write directly to Iceberg tables with full ACID alignment using &lt;code&gt;INSERT&lt;/code&gt;, &lt;code&gt;CTAS&lt;/code&gt;, &lt;code&gt;UPDATE&lt;/code&gt;, &lt;code&gt;DELETE&lt;/code&gt;, and &lt;code&gt;MERGE&lt;/code&gt;. You can govern access with AWS Identity and Access Management (IAM) permissions through the external schema’s IAM role or with AWS Lake Formation for fine-grained, cross-engine control.&lt;/p&gt;
&lt;p&gt;Amazon Redshift now also supports creating and refreshing Iceberg materialized views. A materialized view (MV) pre-computes expensive joins and aggregations once and stores the result as a standard Apache Iceberg table in Amazon Simple Storage Service (Amazon S3) or Amazon S3 Table Buckets, registered in the AWS Glue Data Catalog. You create one using familiar SQL (&lt;code&gt;CREATE MATERIALIZED VIEW ... USING ICEBERG&lt;/code&gt;), and the result is instantly queryable by Iceberg-compatible engines, including Amazon Athena, Apache Spark on Amazon EMR, and AWS Glue. Amazon Redshift keeps it current with incremental refresh, and because the result is an ordinary Iceberg table in the AWS Glue Data Catalog, it is governed and discovered like any other catalog table.&lt;/p&gt;
&lt;p&gt;Consider a team that runs its analytics on Amazon Redshift. Their transformations are already written in Amazon Redshift SQL, their staff know Amazon Redshift, and they’ve invested in its query engine. What they don’t have is a way to share their most expensive pre-computed results with the other engines in their organization, such as a data science group on Spark or an ad-hoc reporting team on Athena, without exporting copies or standing up a second transformation stack. The gap for this team is that they want interoperability and acceleration from the engine they already run.&lt;/p&gt;
&lt;p&gt;Now they can create this materialized view in Amazon Redshift, in the SQL they already write, and Amazon Redshift stores the pre-computed result as an open Iceberg table. The Spark and Athena teams read that same result directly, without maintaining copies or separate pipelines. As new data lands, incremental refresh recomputes only what changed. The team gets a single, consistent source of truth for its most expensive queries that every engine shares. The Amazon Redshift team can run an end-to-end transformation pipeline in one engine, using materialized views as the building block between raw, cleaned, and serving layers without stitching multiple engines together stage by stage.&lt;/p&gt;
&lt;p&gt;And you don’t need to choose between open and fast: for your most latency-sensitive dashboards, you can still load these Iceberg materialized views into Amazon Redshift Managed Storage (RMS) as native RMS materialized views.&lt;/p&gt;
&lt;h2 id="when-to-use-iceberg-mvs-compared-to-amazon-redshift-rms-materialized-views"&gt;When to use Iceberg MVs compared to Amazon Redshift (RMS) materialized views&lt;/h2&gt;
&lt;p&gt;Iceberg materialized views don’t replace the standard materialized views of Amazon Redshift. They serve a different need. Amazon Redshift materialized views store their results in Amazon Redshift Managed Storage (RMS), which is highly optimized for fast reads from Amazon Redshift. Iceberg materialized views store their results as open Iceberg tables in your Amazon S3, readable by your choice of engine. Choose based on where and how the result is consumed:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Use Amazon Redshift (RMS) materialized views when:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
 &lt;li&gt;You query only from Amazon Redshift.&lt;/li&gt;
 &lt;li&gt;You need the lowest read latency. For interactive dashboards and sub-second lookups, reading from RMS is significantly faster than reading an Iceberg table from Amazon S3.&lt;/li&gt;
 &lt;li&gt;You want the most straightforward option for an Amazon Redshift-only workload.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Use Iceberg materialized views when:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
 &lt;li&gt;You want the pre-computed result readable by engines beyond Amazon Redshift (Athena, Spark, Amazon SageMaker AI, third-party engines) without copying data.&lt;/li&gt;
 &lt;li&gt;You’re standardizing on Apache Iceberg for interoperability and don’t want acceleration tied to an Amazon Redshift-only storage format.&lt;/li&gt;
 &lt;li&gt;You want to run an end-to-end pipeline in a single engine and have every downstream consumer share the same open result.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;They’re complementary.&lt;/strong&gt; A common pattern is to build and transform data as Iceberg materialized views for openness and cross-engine access, then load the most performance-sensitive results into an RMS materialized view for your hottest interactive dashboards. This keeps your data open by default and fast where it counts.&lt;/p&gt;
&lt;p&gt;In this post, you will:&lt;/p&gt;
&lt;ol type="1"&gt;
 &lt;li&gt;Understand why Iceberg materialized views matter and their key use cases.&lt;/li&gt;
 &lt;li&gt;Learn how incremental refresh and cross-engine access work.&lt;/li&gt;
 &lt;li&gt;Set up prerequisites (IAM, Amazon S3, AWS Glue).&lt;/li&gt;
 &lt;li&gt;Create your first Iceberg materialized view.&lt;/li&gt;
 &lt;li&gt;Verify cross-engine access from Amazon Athena and PyIceberg.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This solution uses the following AWS services:&lt;/p&gt;
&lt;ul&gt;
 &lt;li&gt;Amazon Redshift (Serverless or RG provisioned).&lt;/li&gt;
 &lt;li&gt;AWS Glue Data Catalog.&lt;/li&gt;
 &lt;li&gt;Amazon S3 (general purpose buckets or Amazon S3 Tables).&lt;/li&gt;
 &lt;li&gt;AWS Identity and Access Management (IAM).&lt;/li&gt;
 &lt;li&gt;AWS Lake Formation (optional, for governed access).&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="solution-overview"&gt;Solution overview&lt;/h2&gt;
&lt;p&gt;With Iceberg materialized views, you can compute aggregations once in Amazon Redshift and store the results as standard Apache Iceberg tables in Amazon S3 or Amazon S3 Table buckets. Iceberg-compatible engines can then query these pre-computed results directly.&lt;/p&gt;
&lt;div style="width: 810px" class="wp-caption alignnone"&gt;
 &lt;a href="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/10/05/BDB-6188-2.png" target="_blank" rel="noopener"&gt;&lt;img src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/10/05/BDB-6188-2.png" alt="Iceberg-compatible engines reading the pre-computed materialized view directly from Amazon S3" width="800"&gt;&lt;/a&gt;
 &lt;p class="wp-caption-text"&gt;Figure 1: Iceberg-compatible engines query the pre-computed materialized view directly from Amazon S3&lt;/p&gt;
&lt;/div&gt;
&lt;h2 id="powered-by-amazon-redshift-serverless-and-amazon-redshift-rg"&gt;Powered by Amazon Redshift Serverless and Amazon Redshift RG&lt;/h2&gt;
&lt;p&gt;Iceberg materialized views are supported on:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Amazon Redshift Serverless&lt;/strong&gt; – Fully managed, auto scaling compute. Recommended for variable workloads where MV refreshes run alongside one-time queries without capacity planning.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Amazon Redshift RG (provisioned instances powered by AWS Graviton)&lt;/strong&gt; – Provisioned clusters running on AWS Graviton processors with a custom-built integrated vectorized query engine. &lt;a href="https://aws.amazon.com/blogs/big-data/meet-amazon-redshift-rg-aws-graviton-based-instances-with-an-integrated-data-lake-query-engine-delivering-up-to-2-4x-better-performance-at-30-lower-price-than-ra3/" target="_blank" rel="noopener"&gt;Up to 2.4x better performance for data lake workloads at 30% lower price per vCPU compared to RA3 instances&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Amazon Redshift RA3 and DC2 instance types don’t support Iceberg materialized views.&lt;/p&gt;
&lt;p&gt;Amazon Redshift does the heavy computation once on Serverless or Provisioned RG instances. Every Iceberg-compatible engine (Athena, Spark, SageMaker, and AWS Glue) consumes the pre-computed Iceberg MV from Amazon S3 or Amazon S3 Tables at standard Amazon S3 read cost. No additional compute charges on the consumer side.&lt;/p&gt;
&lt;h2 id="use-cases"&gt;Use cases&lt;/h2&gt;
&lt;p&gt;Iceberg materialized views support several patterns across analytics, cost optimization, and AI workloads.&lt;/p&gt;
&lt;h3 id="medallion-architecture-with-shared-optimization"&gt;1. Medallion architecture with shared optimization&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;The problem:&lt;/strong&gt; In Bronze→Silver→Gold architectures, optimizations at silver/gold layers benefit only the engine that computed them.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;With Iceberg MVs:&lt;/strong&gt; Amazon Redshift RG computes silver and gold layers as Iceberg MVs with incremental refresh. Output is standard Iceberg on Amazon S3, so every consumer benefits without additional compute.&lt;/p&gt;
&lt;h3 id="empowering-agentic-ai-feature-stores-and-generative-ai-workloads"&gt;2. Empowering agentic AI, feature stores, and generative AI workloads&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;The problem:&lt;/strong&gt; AI agents, machine learning (ML) pipelines, and generative AI applications need pre-computed features, such as rolling averages, customer lifetime value, and engagement scores, in a format frameworks can consume without direct warehouse connectivity.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;With Iceberg MVs:&lt;/strong&gt; The heavy computation (complex joins, window functions, statistical aggregations) runs once on Amazon Redshift Serverless or RG. Materialized views that use window functions or aggregations beyond COUNT and SUM are fully recomputed on each refresh rather than incrementally updated. Once the materialized view is computed and stored in Amazon S3 as a standard Iceberg table, it can be accessed by different consumers natively:&lt;/p&gt;
&lt;ul&gt;
 &lt;li&gt;&lt;strong&gt;Amazon SageMaker notebooks and training jobs&lt;/strong&gt; read features directly from Amazon S3 through PyIceberg, with no JDBC driver needed.&lt;/li&gt;
 &lt;li&gt;&lt;strong&gt;Amazon Bedrock agents&lt;/strong&gt; access pre-computed analytics as structured data for Retrieval Augmented Generation (RAG).&lt;/li&gt;
 &lt;li&gt;&lt;strong&gt;Apache Spark on Amazon EMR&lt;/strong&gt; consumes features through &lt;code&gt;spark.table()&lt;/code&gt; for large-scale ML training pipelines.&lt;/li&gt;
 &lt;li&gt;&lt;strong&gt;Amazon Athena&lt;/strong&gt; provides serverless SQL access to materialized features for ad-hoc analysis and dashboarding.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Incremental refresh keeps features fresh. For incremental refresh eligibility, see &lt;a href="https://docs.aws.amazon.com/redshift/latest/dg/materialized-view-iceberg.html" target="_blank" rel="noopener"&gt;Materialized views stored as Apache Iceberg tables&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id="cost-optimization-through-compute-consolidation"&gt;3. Cost optimization through compute consolidation&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;The problem:&lt;/strong&gt; When the same aggregation is re-executed independently across multiple engines (Amazon Redshift, Athena, Spark, third-party tools), organizations pay for redundant compute on each engine, which multiplies cost linearly with the number of consumers.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;With Iceberg MVs:&lt;/strong&gt; One Amazon Redshift Serverless or RG refresh computes the aggregation once. Consumers read the pre-computed result directly from Amazon S3 at standard storage read cost, alleviating redundant compute across engines. The cost reduction can scale with the number of consuming engines you consolidate.&lt;/p&gt;
&lt;h3 id="governed-data-sharing-without-data-movement"&gt;4. Governed data sharing without data movement&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;The problem:&lt;/strong&gt; Sharing analytics across teams requires data copying or engine-specific sharing mechanisms.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;With Iceberg MVs:&lt;/strong&gt; Output is governed by AWS Lake Formation. Grant access with a single permission model. Consumers bring their preferred engine.&lt;/p&gt;
&lt;h3 id="single-source-of-truth-across-analytics-engines"&gt;5. Single source of truth across analytics engines&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;The problem:&lt;/strong&gt; Multiple teams recompute the same metrics independently across Spark, Amazon Redshift, Athena, and custom tools, producing inconsistent numbers.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;With Iceberg MVs:&lt;/strong&gt; One &lt;code&gt;CREATE MATERIALIZED VIEW ... USING ICEBERG&lt;/code&gt; computes the metric once on Amazon Redshift Serverless or RG. Every engine reads the same Iceberg table from Amazon S3, with the same numbers, the same snapshot, and zero reconciliation.&lt;/p&gt;
&lt;h2 id="how-it-works"&gt;How it works&lt;/h2&gt;
&lt;p&gt;Iceberg MVs extend the native materialized view capability of Amazon Redshift with the &lt;code&gt;USING ICEBERG&lt;/code&gt; clause:&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-sql"&gt;CREATE MATERIALIZED VIEW awsdatacatalog.analytics.daily_revenue
USING ICEBERG
LOCATION 's3://amzn-s3-demo-analytics/daily_revenue/'
PARTITIONED BY (day(order_date))
AS
SELECT order_date, region,
       SUM(amount) AS total_revenue, COUNT(*) AS transaction_count
FROM awsdatacatalog.source.transactions
GROUP BY 1, 2;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;The MV can also be stored in &lt;a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/s3-tables-buckets.html" target="_blank" rel="noopener"&gt;Amazon S3 Table Buckets&lt;/a&gt;. If you omit the &lt;code&gt;LOCATION&lt;/code&gt; clause, Amazon S3 Tables manages storage automatically.&lt;/p&gt;
&lt;h3 id="incremental-refresh"&gt;Incremental refresh&lt;/h3&gt;
&lt;p&gt;Amazon Redshift tracks Iceberg snapshot IDs across refreshes. On &lt;code&gt;REFRESH MATERIALIZED VIEW&lt;/code&gt;, it identifies changed source partitions and recomputes only the delta.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Patterns supporting incremental refresh:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
 &lt;li&gt;SUM and COUNT aggregates with GROUP BY.&lt;/li&gt;
 &lt;li&gt;Non-aggregated queries (row-level delta tracking).&lt;/li&gt;
 &lt;li&gt;Inner JOINs between Iceberg tables.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Constructs that use full refresh (still supported):&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
 &lt;li&gt;DISTINCT, outer JOINs, window functions, subqueries.&lt;/li&gt;
 &lt;li&gt;Set operations (UNION ALL, UNION, INTERSECT, EXCEPT).&lt;/li&gt;
 &lt;li&gt;MIN, MAX, AVG, COUNT(DISTINCT), SUM(DISTINCT).&lt;/li&gt;
 &lt;li&gt;GROUPING SETS, ROLLUP, CUBE.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="cross-cluster-refresh"&gt;Cross-cluster refresh&lt;/h3&gt;
&lt;p&gt;The MV isn’t tied to the creating cluster. Amazon Redshift clusters or Serverless workgroups with the appropriate IAM role can refresh it. When multiple clusters attempt to refresh the same MV concurrently, Amazon Redshift coordinates through the AWS Glue Data Catalog to make sure that only one refresh succeeds at a time, helping prevent conflicts automatically. For more details on concurrency handling, see the &lt;a href="https://docs.aws.amazon.com/redshift/latest/dg/querying-iceberg.html" target="_blank" rel="noopener"&gt;Amazon Redshift Iceberg materialized views&lt;/a&gt; documentation.&lt;/p&gt;
&lt;h3 id="cross-engine-access"&gt;Cross-engine access&lt;/h3&gt;
&lt;p&gt;The result is a standard Iceberg table that needs no special drivers. This materialized view can be read from different engines, as shown in the following examples:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Amazon Athena:&lt;/strong&gt;&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-sql"&gt;SELECT * FROM analytics.daily_revenue WHERE region = 'us-east';&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Apache Spark on Amazon EMR:&lt;/strong&gt;&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-python"&gt;spark.table("analytics.daily_revenue").filter(col("region") == "us-east")&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Amazon SageMaker / PyIceberg:&lt;/strong&gt;&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-python"&gt;from pyiceberg.catalog import load_catalog
catalog = load_catalog("glue", **{"type": "glue"})
df = catalog.load_table("analytics.daily_revenue").scan().to_pandas()&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h2 id="prerequisites"&gt;Prerequisites&lt;/h2&gt;
&lt;p&gt;Setting up Iceberg MVs requires IAM, Amazon S3, and AWS Glue configuration. Follow these steps to prepare your environment.&lt;/p&gt;
&lt;p&gt;For a complete walkthrough with console screenshots, see &lt;a href="https://docs.aws.amazon.com/redshift/latest/dg/materialized-view-overview.html" target="_blank" rel="noopener"&gt;Getting started with Iceberg materialized views&lt;/a&gt; in the Amazon Redshift documentation.&lt;/p&gt;
&lt;p&gt;The following table summarizes the resources you will configure:&lt;/p&gt;
&lt;table border="1px" width="100%" cellpadding="10px"&gt;
 &lt;tbody&gt;
  &lt;tr&gt;
   &lt;td&gt;&lt;strong&gt;Resource&lt;/strong&gt;&lt;/td&gt;
   &lt;td&gt;&lt;strong&gt;Purpose&lt;/strong&gt;&lt;/td&gt;
   &lt;td&gt;&lt;strong&gt;Created in Step&lt;/strong&gt;&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;IAM Role (IcebergMvDefiner)&lt;/td&gt;
   &lt;td&gt;Definer role for MV operations (2-service trust policy)&lt;/td&gt;
   &lt;td&gt;Steps 1–2&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;S3 Bucket&lt;/td&gt;
   &lt;td&gt;Stores Iceberg MV data (Parquet files)&lt;/td&gt;
   &lt;td&gt;Step 3&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;AWS Glue database&lt;/td&gt;
   &lt;td&gt;Catalogs MV metadata in AWS Glue Data Catalog&lt;/td&gt;
   &lt;td&gt;Step 4&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;Cluster Role Association&lt;/td&gt;
   &lt;td&gt;Grants the Amazon Redshift cluster permission to assume the definer role&lt;/td&gt;
   &lt;td&gt;Step 5&lt;/td&gt;
  &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id="step-1-create-the-iam-role"&gt;Step 1: Create the IAM role&lt;/h3&gt;
&lt;p&gt;Create an IAM role named IcebergMvDefiner with the following trust policy. Note that two service principals are required:&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-json"&gt;{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "Service": [
                    "redshift.amazonaws.com",
                    "glue.amazonaws.com"
                ]
            },
            "Action": "sts:AssumeRole"
        }
    ]
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Why are these two principals?&lt;/strong&gt; Amazon Redshift needs to assume the role to perform materialized view operations. AWS Glue needs to check base table permissions on behalf of the materialized view definer role.&lt;/p&gt;
&lt;h3 id="step-2-attach-iam-policies"&gt;Step 2: Attach IAM policies&lt;/h3&gt;
&lt;p&gt;Attach the following scoped inline policies to the IcebergMvDefiner role. These provide the minimum permissions required for Iceberg materialized view operations.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;S3 access (scoped to your bucket):&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Create an inline policy named s3-mv-access:&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-json"&gt;{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "s3:GetObject",
                "s3:PutObject",
                "s3:DeleteObject",
                "s3:ListBucket",
                "s3:GetBucketLocation"
            ],
            "Resource": [
                "arn:aws:s3:::&amp;lt;&amp;lt;your-bucket&amp;gt;&amp;gt;",
                "arn:aws:s3:::&amp;lt;&amp;lt;your-bucket&amp;gt;&amp;gt;/*"
            ]
        }
    ]
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;AWS Glue Data Catalog access policy (scoped to your database):&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Create an inline policy named glue-mv-access:&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-json"&gt;{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "glue:GetDatabase",
                "glue:GetDatabases",
                "glue:GetTable",
                "glue:GetTables",
                "glue:CreateTable",
                "glue:UpdateTable",
                "glue:DeleteTable",
                "glue:GetPartitions",
                "glue:BatchGetPartition"
            ],
            "Resource": [
                "arn:aws:glue:&amp;lt;&amp;lt;your-region&amp;gt;&amp;gt;:&amp;lt;&amp;lt;your-account-id&amp;gt;&amp;gt;:catalog",
                "arn:aws:glue:&amp;lt;&amp;lt;your-region&amp;gt;&amp;gt;:&amp;lt;&amp;lt;your-account-id&amp;gt;&amp;gt;:database/&amp;lt;&amp;lt;your-glue-db&amp;gt;&amp;gt;",
                "arn:aws:glue:&amp;lt;&amp;lt;your-region&amp;gt;&amp;gt;:&amp;lt;&amp;lt;your-account-id&amp;gt;&amp;gt;:table/&amp;lt;&amp;lt;your-glue-db&amp;gt;&amp;gt;/*"
            ]
        }
    ]
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;IAM PassRole policy (scoped to the definer role):&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Create an inline policy named mv-access:&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-json"&gt;{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": "iam:PassRole",
            "Resource": "arn:aws:iam::&amp;lt;&amp;lt;your-account-id&amp;gt;&amp;gt;:role/IcebergMvDefiner"
        }
    ]
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h3 id="step-3-create-s3-bucket"&gt;Step 3: Create S3 bucket&lt;/h3&gt;
&lt;p&gt;Create an S3 bucket for MV storage. We recommend the naming convention iceberg-mv-. Enable default encryption (SSE-S3) and block all public access.&lt;/p&gt;
&lt;h3 id="step-4-create-aws-glue-database"&gt;Step 4: Create AWS Glue database&lt;/h3&gt;
&lt;p&gt;Create a database named iceberg_mv in the AWS Glue Data Catalog. Use a plain &lt;code&gt;create-database&lt;/code&gt; command. The database inherits &lt;code&gt;IAM_ALLOWED_PRINCIPALS&lt;/code&gt; by default, which allows cross-engine access from Amazon Athena and other engines.&lt;/p&gt;
&lt;h3 id="step-5-associate-role-with-redshift"&gt;Step 5: Associate role with Redshift&lt;/h3&gt;
&lt;p&gt;Associate the IcebergMvDefiner role with your Amazon Redshift cluster or Serverless namespace:&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-bash"&gt;aws redshift modify-cluster-iam-roles --cluster-identifier --add-iam-roles arn:aws:iam:::role/IcebergMvDefiner&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h3 id="step-6-set-case-sensitivity"&gt;Step 6: Set case sensitivity&lt;/h3&gt;
&lt;p&gt;Connect to your Amazon Redshift cluster and run:&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-sql"&gt;SET enable_case_sensitive_identifier TO FALSE;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h2 id="walkthrough"&gt;Creating Your First Iceberg MV&lt;/h2&gt;
&lt;p&gt;With prerequisites in place, you can now create an external schema, a base Iceberg table, and your first materialized view.&lt;/p&gt;
&lt;h3 id="step-7-create-external-schema"&gt;Step 7: Create external schema&lt;/h3&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-sql"&gt;CREATE EXTERNAL SCHEMA iceberg_schema FROM DATA CATALOG DATABASE 'iceberg_mv' REGION '&amp;lt;&amp;lt;your-region&amp;gt;&amp;gt;' IAM_ROLE 'arn:aws:iam:::role/IcebergMvDefiner';&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h3 id="step-8-create-iceberg-base-table-with-sample-data"&gt;Step 8: Create Iceberg base table with sample data&lt;/h3&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-sql"&gt;CREATE TABLE iceberg_schema.orders USING ICEBERG LOCATION 's3://&amp;lt;&amp;lt;your-bucket&amp;gt;&amp;gt;/iceberg_mv_blog/orders' AS SELECT 1 AS id, 'us' AS region, 100 AS amount UNION ALL SELECT 2, 'eu', 200 UNION ALL SELECT 3, 'jp', 150;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h3 id="step-9-create-the-iceberg-materialized-view"&gt;Step 9: Create the Iceberg materialized view&lt;/h3&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-sql"&gt;CREATE MATERIALIZED VIEW iceberg_schema.sales_by_region USING ICEBERG LOCATION 's3://&amp;lt;&amp;lt;your-bucket&amp;gt;&amp;gt;/iceberg_mv_blog/sales_by_region' AS SELECT region, SUM(amount) AS total, COUNT(*) AS num_orders FROM iceberg_schema.orders GROUP BY region;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h3 id="step-10-verify-mv-contents"&gt;Step 10: Verify MV contents&lt;/h3&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-sql"&gt;SELECT * FROM iceberg_schema.sales_by_region;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div style="width: 810px" class="wp-caption alignnone"&gt;
 &lt;a href="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/10/05/BDB-6188-3.png" target="_blank" rel="noopener"&gt;&lt;img src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/10/05/BDB-6188-3.png" alt="Query results from the sales_by_region materialized view, showing total and order count per region" width="800"&gt;&lt;/a&gt;
 &lt;p class="wp-caption-text"&gt;Figure 2: Initial materialized view query results aggregated by region&lt;/p&gt;
&lt;/div&gt;
&lt;h3 id="step-11-test-incremental-refresh"&gt;Step 11: Test incremental refresh&lt;/h3&gt;
&lt;p&gt;Insert new rows into the base table and refresh the MV:&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-sql"&gt;INSERT INTO iceberg_schema.orders VALUES (4, 'us', 300), (5, 'eu', 50);
REFRESH MATERIALIZED VIEW iceberg_schema.sales_by_region;

SELECT * FROM iceberg_schema.sales_by_region;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div id="attachment_94859" style="width: 1152px" class="wp-caption alignnone"&gt;
 &lt;img aria-describedby="caption-attachment-94859" loading="lazy" class="size-full wp-image-94859" src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/10/06/image-60.png" alt="" width="1142" height="202"&gt;
 &lt;p id="caption-attachment-94859" class="wp-caption-text"&gt;Figure 3: Materialized view query results after inserting new rows and refreshing&lt;/p&gt;
&lt;/div&gt;
&lt;h3 id="cross-engine-verification"&gt;Cross-engine verification&lt;/h3&gt;
&lt;p&gt;The materialized view is now a standard Iceberg table in the AWS Glue Data Catalog, accessible from compatible engines without an Amazon Redshift connection.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Amazon Athena:&lt;/strong&gt;&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-sql"&gt;SELECT * FROM iceberg_mv.sales_by_region WHERE region = 'us';&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div id="attachment_94860" style="width: 2218px" class="wp-caption alignnone"&gt;
 &lt;img aria-describedby="caption-attachment-94860" loading="lazy" class="size-full wp-image-94860" src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/10/06/image-61.png" alt="" width="2208" height="1452"&gt;
 &lt;p id="caption-attachment-94860" class="wp-caption-text"&gt;Figure 4: Querying the materialized view from Amazon Athena&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Amazon SageMaker / PyIceberg:&lt;/strong&gt;&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-python"&gt;from pyiceberg.catalog import load_catalog
catalog = load_catalog("glue", **{"type": "glue"})
df = catalog.load_table("iceberg_mv.sales_by_region").scan().to_pandas()
print(df)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Apache Spark on Amazon EMR:&lt;/strong&gt;&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-python"&gt;spark.table("iceberg_mv.sales_by_region").filter(col("region") == "us").show()&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h2 id="the-business-case"&gt;The business case&lt;/h2&gt;
&lt;p&gt;The following table illustrates a representative scenario where a common aggregation is computed across multiple engines:&lt;/p&gt;
&lt;table border="1px" width="100%" cellpadding="10px"&gt;
 &lt;tbody&gt;
  &lt;tr&gt;
   &lt;td&gt;&lt;strong&gt;Dimension&lt;/strong&gt;&lt;/td&gt;
   &lt;td&gt;&lt;strong&gt;Traditional (siloed)&lt;/strong&gt;&lt;/td&gt;
   &lt;td&gt;&lt;strong&gt;Iceberg MVs on Serverless/RG&lt;/strong&gt;&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;Compute cost&lt;/td&gt;
   &lt;td&gt;~$7,500/month (4 engines)&lt;/td&gt;
   &lt;td&gt;~$1,500/month (1 refresh)&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;Metric consistency&lt;/td&gt;
   &lt;td&gt;3–4 versions&lt;/td&gt;
   &lt;td&gt;1 version&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;Time to new metric&lt;/td&gt;
   &lt;td&gt;Days (per engine)&lt;/td&gt;
   &lt;td&gt;Hours (one definition)&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;Governance&lt;/td&gt;
   &lt;td&gt;Per-engine ACLs&lt;/td&gt;
   &lt;td&gt;IAM + optional Lake Formation&lt;/td&gt;
  &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Cost estimate assumes a mid-size aggregation (1 TB input, 100 GB output) running daily across Athena ($5/TB scan), Spark on Amazon EMR ($0.096/hr × 4 nodes), Amazon Redshift Serverless (8 RPU), and a third-party engine. Actual savings vary by workload.&lt;/p&gt;
&lt;h2 id="current-limitations"&gt;Current limitations&lt;/h2&gt;
&lt;p&gt;For the current list of supported SQL constructs, incremental refresh eligibility, and known limitations, see &lt;a href="https://docs.aws.amazon.com/redshift/latest/dg/materialized-view-iceberg.html" target="_blank" rel="noopener"&gt;Materialized views stored as Apache Iceberg tables&lt;/a&gt; in the Amazon Redshift documentation.&lt;/p&gt;
&lt;h2 id="optional-add-lake-formation-governance"&gt;(Optional) Add Lake Formation governance&lt;/h2&gt;
&lt;p&gt;If your organization requires centralized access control across engines, you can layer AWS Lake Formation governance on top of the IAM-only setup. Note that Lake Formation permissions for Iceberg MVs are coarse-grained (database and table level). Fine-grained access control (row filters, column filters) isn’t supported on Iceberg materialized views. The following additional steps were validated in the same environment used in this walkthrough:&lt;/p&gt;
&lt;ol type="1"&gt;
 &lt;li&gt;Add lakeformation.amazonaws.com to the IAM role trust policy (in addition to redshift.amazonaws.com and glue.amazonaws.com).&lt;/li&gt;
 &lt;li&gt;Add lakeformation:GetDataAccess to the role’s inline policy.&lt;/li&gt;
 &lt;li&gt;Register the S3 bucket as a Lake Formation data location:
  &lt;div class="hide-language"&gt;
   &lt;pre&gt;&lt;code class="language-bash"&gt;aws lakeformation register-resource --resource-arn arn:aws:s3:::&amp;lt;your-bucket&amp;gt; --role-arn arn:aws:iam::&amp;lt;account-id&amp;gt;:role/IcebergMvDefiner --region &amp;lt;region&amp;gt;&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;/li&gt;
 &lt;li&gt;Recreate the AWS Glue database with empty CreateTableDefaultPermissions (this makes Lake Formation authoritative for table-level access):
  &lt;div class="hide-language"&gt;
   &lt;pre&gt;&lt;code class="language-bash"&gt;aws glue delete-database --name iceberg_mv --region &amp;lt;region&amp;gt;
aws glue create-database --region &amp;lt;region&amp;gt; --database-input '{"Name":"iceberg_mv","CreateTableDefaultPermissions":[]}'&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;/li&gt;
 &lt;li&gt;Grant Lake Formation permissions to the definer role: DATA_LOCATION_ACCESS on the S3 bucket, CREATE_TABLE/DESCRIBE/ALTER/DROP on the database, and ALL on tables (with grant option).&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;For a complete Lake Formation walkthrough, see &lt;a href="https://aws.amazon.com/blogs/big-data/how-to-use-streamlined-permissions-for-amazon-s3-tables-and-iceberg-materialized-views/" target="_blank" rel="noopener"&gt;How to use streamlined permissions for Amazon S3 Tables and Iceberg materialized views&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="clean-up"&gt;Clean up&lt;/h2&gt;
&lt;p&gt;To avoid incurring ongoing charges, remove the resources created in this walkthrough:&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-sql"&gt;DROP MATERIALIZED VIEW iceberg_schema.sales_by_region;
DROP TABLE iceberg_schema.orders;
DROP SCHEMA iceberg_schema;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; &lt;code&gt;DROP MATERIALIZED VIEW&lt;/code&gt; removes the AWS Glue catalog entry but does not delete the underlying data in Amazon S3. To remove the data, delete the Amazon S3 prefix manually:&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-bash"&gt;aws s3 rm s3://&amp;lt;&amp;lt;your-bucket&amp;gt;&amp;gt;/iceberg_mv_blog/ --recursive&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Iceberg materialized views take the open lakehouse promise further: &lt;strong&gt;optimization itself becomes portable&lt;/strong&gt;. Amazon Redshift, whether running as Serverless or on RG instances powered by AWS Graviton, does the heavy computation once. Every other engine and ML pipeline benefits without additional compute. Start with one MV. Watch the numbers match across engines for the first time. Then scale from there.&lt;/p&gt;
&lt;h2 id="resources"&gt;Resources&lt;/h2&gt;
&lt;p&gt;Getting started with Iceberg materialized views (Amazon Redshift documentation)&lt;/p&gt;
&lt;ul&gt;
 &lt;li&gt;&lt;a href="https://aws.amazon.com/blogs/big-data/getting-started-with-apache-iceberg-write-support-in-amazon-redshift/" target="_blank" rel="noopener"&gt;Getting started with Apache Iceberg write support in Amazon Redshift (AWS Blog, Nov 2025)&lt;/a&gt;&lt;/li&gt;
 &lt;li&gt;&lt;a href="https://aws.amazon.com/blogs/big-data/achieve-2x-faster-data-lake-query-performance-with-apache-iceberg-on-amazon-redshift/" target="_blank" rel="noopener"&gt;Achieve 2x faster data lake query performance with Apache Iceberg on Amazon Redshift&lt;/a&gt;&lt;/li&gt;
 &lt;li&gt;&lt;a href="https://aws.amazon.com/blogs/big-data/how-to-use-streamlined-permissions-for-amazon-s3-tables-and-iceberg-materialized-views/" target="_blank" rel="noopener"&gt;How to use streamlined permissions for Amazon S3 Tables and Iceberg materialized views&lt;/a&gt;&lt;/li&gt;
 &lt;li&gt;&lt;a href="https://aws.amazon.com/blogs/big-data/meet-amazon-redshift-rg-aws-graviton-based-instances-with-an-integrated-data-lake-query-engine-delivering-up-to-2-4x-better-performance-at-30-lower-price-than-ra3/" target="_blank" rel="noopener"&gt;Meet Amazon Redshift RG&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p style="clear: both"&gt;&lt;/p&gt;
&lt;hr style="width: 100%"&gt;
&lt;h2&gt;About the authors&lt;/h2&gt;
&lt;footer&gt;
 &lt;div class="blog-author-box" style="padding-top: 2.0em"&gt;
  &lt;div class="blog-author-image" style="margin-right: 1.0em"&gt;
   &lt;img loading="lazy" class="alignnone size-full" src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/10/05/BDB-6188-6.jpg" alt="Sudipta Bagchi" width="100" height="133"&gt;
  &lt;/div&gt;
  &lt;h3 class="lb-h4"&gt;Sudipta Bagchi&lt;/h3&gt;
  &lt;p style="overflow: hidden"&gt;Sudipta is a Senior Specialist Solutions Architect for SQL Analytics at AWS, helping customers design high-performance analytical architectures with Amazon Redshift and open lakehouse patterns.&lt;/p&gt;
 &lt;/div&gt;
 &lt;div class="blog-author-box" style="padding-top: 2.0em"&gt;
  &lt;div class="blog-author-image" style="margin-right: 1.0em"&gt;
   &lt;img loading="lazy" class="alignnone size-full" src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/10/05/BDB-6188-7.jpg" alt="Dhaval Shah" width="100" height="133"&gt;
  &lt;/div&gt;
  &lt;h3 class="lb-h4"&gt;Dhaval Shah&lt;/h3&gt;
  &lt;p style="overflow: hidden"&gt;Dhaval is a Senior Specialist Solutions Architect for SQL Analytics at AWS, helping customers build the data foundations that fuel AI and analytics at scale.&lt;/p&gt;
 &lt;/div&gt;
 &lt;div class="blog-author-box" style="padding-top: 2.0em"&gt;
  &lt;div class="blog-author-image" style="margin-right: 1.0em"&gt;
   &lt;img loading="lazy" class="alignnone size-full" src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/10/05/BDB-6188-8.jpg" alt="Srishti Mittal" width="100" height="133"&gt;
  &lt;/div&gt;
  &lt;h3 class="lb-h4"&gt;Srishti Mittal&lt;/h3&gt;
  &lt;p style="overflow: hidden"&gt;Srishti is a Product Manager at AWS, with a focus on making open data lakes performant and interoperable across analytics engines. She leads product strategy for open table formats such as Apache Iceberg, partnering with customers and field teams to turn real-world data lake challenges into product capabilities.&lt;/p&gt;
 &lt;/div&gt;
 &lt;div class="blog-author-box" style="padding-top: 2.0em"&gt;
  &lt;div class="blog-author-image" style="margin-right: 1.0em"&gt;
   &lt;img loading="lazy" class="alignnone size-full" src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/10/05/BDB-6188-9.jpg" alt="Gaurav Saxena" width="100" height="133"&gt;
  &lt;/div&gt;
  &lt;h3 class="lb-h4"&gt;Gaurav Saxena&lt;/h3&gt;
  &lt;p style="overflow: hidden"&gt;Gaurav is a Principal Engineer in the Database Services (DBS) Amazon Redshift team at AWS.&lt;/p&gt;
 &lt;/div&gt;
 &lt;div class="blog-author-box" style="padding-top: 2.0em"&gt;
  &lt;div class="blog-author-image" style="margin-right: 1.0em"&gt;
   &lt;img loading="lazy" class="alignnone size-full" src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/10/05/BDB-6188-10.jpg" alt="Andre Hernich" width="100" height="133"&gt;
  &lt;/div&gt;
  &lt;h3 class="lb-h4"&gt;Andre Hernich&lt;/h3&gt;
  &lt;p style="overflow: hidden"&gt;Andre is a Principal Software Engineer in the Database Services (DBS) Amazon Redshift team at AWS.&lt;/p&gt;
 &lt;/div&gt;
&lt;/footer&gt;</content:encoded>
					
		
		
			</item>
		<item>
		<title>Run an automated operational review with the Amazon Redshift MCP server</title>
		<link>https://aws.amazon.com/blogs/big-data/run-an-automated-operational-review-with-the-amazon-redshift-mcp-server/</link>
		
		<dc:creator><![CDATA[Sidhanth Muralidhar]]></dc:creator>
		<pubDate>Mon, 05 Oct 2026 15:44:45 +0000</pubDate>
				<category><![CDATA[Advanced (300)]]></category>
		<category><![CDATA[Amazon Redshift]]></category>
		<category><![CDATA[Technical How-to]]></category>
		<guid isPermaLink="false">b4b21ced0273f13125ee1e1c34be5b9db1bd562b</guid>

					<description>Amazon Redshift automates much of its own tuning, but periodic operational reviews still pay off. Learn how the review_cluster tool in the open-source Amazon Redshift MCP server runs a full cluster diagnostic from one natural-language request, evaluating 12 diagnostic areas and returning prioritized, documentation-linked recommendations in minutes.</description>
										<content:encoded>&lt;p&gt;It’s the end of a strong quarter, and your &lt;a href="https://aws.amazon.com/redshift/" target="_blank" rel="noopener"&gt;Amazon Redshift&lt;/a&gt; workloads have grown with the business. Data volumes are up, new pipelines have shipped, and more teams are querying than when you first sized the cluster. Nothing is broken, but this is exactly when a periodic operational review pays off. It confirms the cluster is still tuned for how it’s used today, and surfaces ways to optimize cost and performance as you scale.&lt;/p&gt;
&lt;p&gt;Amazon Redshift already automates significant operational complexity. Autonomics features such as automatic table optimization, automatic workload management, and automatic vacuum handle much of the complex work, so you can focus on writing queries rather than managing infrastructure. Amazon Redshift Serverless goes further, using AI-driven scaling that adapts compute to workload demand.&lt;/p&gt;
&lt;p&gt;Even so, some decisions still benefit from human judgment. For example, table design choices may not have accounted for common join patterns, or query patterns may have shifted since the tables were built. Both are worth revisiting. Likewise, as workloads increase, it’s worth deciding whether the current deployment model is still correctly sized.&lt;/p&gt;
&lt;p&gt;Many organizations have turned this into a recurring business process: monitoring dashboards and key performance indicators (KPIs), narrowing down long-running queries, collaborating across teams to resolve them, and coaching users on efficient query patterns. This isn’t unique to Amazon Redshift. It’s a best practice for any production data system.&lt;/p&gt;
&lt;p&gt;AWS Enterprise Support helps customers with these reviews. But even with specialist assistance, the process typically consumes 4–8 hours of focused effort per cluster: assembling diagnostic queries, interpreting results, cross-referencing documentation, and compiling findings into a prioritized report. Most teams recognize the value, but consistently deprioritize it in favor of feature delivery and day-to-day operations.&lt;/p&gt;
&lt;p&gt;In a &lt;a href="https://aws.amazon.com/blogs/big-data/query-amazon-redshift-using-natural-language-with-kiro/" target="_blank" rel="noopener"&gt;previous post&lt;/a&gt;, we demonstrated how to query Amazon Redshift using natural language with Kiro and the Amazon Redshift Model Context Protocol (MCP) server. That approach replaced manual schema navigation and hand-written SQL with conversational analytics. This post takes the next step: from &lt;em&gt;asking questions about your data&lt;/em&gt; to &lt;em&gt;asking questions about your cluster’s operational health&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;The &lt;strong&gt;review_cluster&lt;/strong&gt; tool in the &lt;a href="https://github.com/awslabs/mcp/tree/main/src/redshift-mcp-server" target="_blank" rel="noopener"&gt;Amazon Redshift MCP&lt;/a&gt; server makes the entire diagnostic process available from a single natural-language request. It evaluates 12 diagnostic areas, identifies potential issues, and returns prioritized recommendations linked directly to AWS documentation, covering both provisioned clusters and serverless workgroups in one invocation. What previously required hours of specialist effort now completes in a few minutes. This makes it practical to review your cluster weekly, after significant schema changes, or before peak traffic events.&lt;/p&gt;
&lt;p&gt;In this post, you learn how to:&lt;/p&gt;
&lt;ol type="1"&gt;
 &lt;li&gt;Run an automated operational review of your Amazon Redshift cluster using natural language.&lt;/li&gt;
 &lt;li&gt;Interpret the structured findings and recommendations.&lt;/li&gt;
 &lt;li&gt;Act on specific findings conversationally, turning diagnostics into remediation without leaving Kiro, Claude, or any MCP-compatible client.&lt;/li&gt;
 &lt;li&gt;Incorporate periodic reviews into your operational workflow.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="what-is-the-review_cluster-tool"&gt;What is the review_cluster tool?&lt;/h2&gt;
&lt;p&gt;A single natural-language request triggers a comprehensive diagnostic assessment. It complements the server’s discovery and query tools: those give you conversational access to your data, while &lt;strong&gt;review_cluster&lt;/strong&gt; gives you the same for your cluster’s health. The tool executes a curated set of queries against Amazon Redshift system views, evaluates the results against known best-practice thresholds, and returns structured findings with prioritized recommendations. Every recommendation includes direct links to the relevant AWS documentation, so you can move from identification to remediation without searching. The entire process is read-only. No data is modified, no configuration is changed, and no resources are created. The tool observes and reports. Remediation decisions remain with you.&lt;/p&gt;
&lt;p&gt;The tool returns a structured result containing:&lt;/p&gt;
&lt;ul&gt;
 &lt;li&gt;Signals evaluated: The total number of diagnostic checks executed.&lt;/li&gt;
 &lt;li&gt;Findings: A list of triggered conditions, each with a name, the number of affected objects (tables, queries, nodes), and linked recommendation IDs.&lt;/li&gt;
 &lt;li&gt;Recommendations: A deduplicated list of corrective actions, ordered by effort, with documentation links.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="what-it-inspects"&gt;What it inspects&lt;/h3&gt;
&lt;p&gt;The review currently evaluates your cluster across 12 diagnostic areas:&lt;/p&gt;
&lt;ol type="1"&gt;
 &lt;li&gt;Automatic Table Optimization (ATO): Whether the automated tuning actions of Amazon Redshift (encoding, sort keys, distribution styles) are completing successfully or not.&lt;/li&gt;
 &lt;li&gt;Table design recommendations: Amazon Redshift Advisor recommendations for encoding, sort keys, and distribution that have not yet been applied.&lt;/li&gt;
 &lt;li&gt;&lt;code&gt;COPY&lt;/code&gt; and data ingestion performance: File sizing, parallelism relative to slice count, and ingestion throughput patterns.&lt;/li&gt;
 &lt;li&gt;External query (Spectrum) performance: Partition pruning effectiveness, file sizes, and scan efficiency for queries against external tables.&lt;/li&gt;
 &lt;li&gt;Materialized view health: Staleness, auto-refresh status, and maintenance overhead of materialized views.&lt;/li&gt;
 &lt;li&gt;Node and storage utilization: Disk usage, node type currency, and whether the cluster would benefit from migration to the latest instance generation.&lt;/li&gt;
 &lt;li&gt;Table-level statistics: Vacuum status, stale statistics, sort key effectiveness, distribution skew, and compression encoding coverage.&lt;/li&gt;
 &lt;li&gt;Query performance: The longest-running queries, nested loop joins, and disk spill patterns.&lt;/li&gt;
 &lt;li&gt;Workload usage patterns: How intensively the cluster is used throughout the day and whether the workload suits the current deployment model.&lt;/li&gt;
 &lt;li&gt;Workload Management (WLM) configuration: Queue setup, concurrency scaling, short query acceleration, priority settings, and query monitoring rules.&lt;/li&gt;
 &lt;li&gt;Workload evaluation: Whether the cluster’s utilization pattern suggests it could benefit from a different deployment model such as serverless.&lt;/li&gt;
 &lt;li&gt;Serverless scaling: Whether a serverless workgroup’s observed compute range falls within the AI-driven scaling window.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Not every area applies to every deployment, because some checks target configuration that only exists in one model. Four checks are provisioned-only: &lt;code&gt;COPY&lt;/code&gt; parallelism relative to slice count, node and storage utilization, WLM configuration, and workload evaluation. These tune constructs that Amazon Redshift Serverless manages for you. Serverless has no nodes or slices to size, and it always uses automatic WLM rather than user-configured queues, so there is nothing for those checks to act on. The serverless-scaling check is the reverse: it evaluates AI-driven Redshift Processing Unit (RPU) scaling, which is specific to serverless and has no equivalent on a provisioned cluster. As a result, a provisioned cluster evaluates 11 of the areas and a serverless workgroup evaluates 8, and the tool reports the number actually run as signals evaluated.&lt;/p&gt;
&lt;h2 id="running-your-first-review"&gt;Running your first review&lt;/h2&gt;
&lt;p&gt;The following steps cover what you need in place and how to start a review.&lt;/p&gt;
&lt;h3 id="prerequisites"&gt;Prerequisites&lt;/h3&gt;
&lt;p&gt;Before running a review, make sure that you have:&lt;/p&gt;
&lt;ul&gt;
 &lt;li&gt;An MCP-compatible client configured with the Amazon Redshift MCP server (for example, Kiro). See the &lt;a href="https://github.com/awslabs/mcp/tree/main/src/redshift-mcp-server" target="_blank" rel="noopener"&gt;Amazon Redshift MCP&lt;/a&gt; server README for installation and configuration steps.&lt;/li&gt;
 &lt;li&gt;Valid AWS credentials with the AWS Identity and Access Management (IAM) permissions the server requires.&lt;/li&gt;
 &lt;li&gt;The &lt;code&gt;sys:monitor&lt;/code&gt; role granted to the connecting database user, the one additional database permission review_cluster needs.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To grant this access, a database administrator with superuser privileges runs:&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-sql"&gt;GRANT ROLE sys:monitor TO "IAMR:YourRoleName";&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;The database user name for IAM identities follows the format &lt;code&gt;IAMR:RoleName&lt;/code&gt; or &lt;code&gt;IAM:UserName&lt;/code&gt;. Confirm yours with &lt;code&gt;SELECT current_user&lt;/code&gt; in the Amazon Redshift Query Editor. The double quotes in the &lt;code&gt;GRANT&lt;/code&gt; statement are required for IAM identity names.&lt;/p&gt;
&lt;p&gt;With prerequisites in place, the review is a single prompt:&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-plaintext"&gt;Run a review of analytics-cluster&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;The agent identifies the target cluster, connects to the database, and executes the diagnostic assessment.&lt;/p&gt;
&lt;p&gt;The following example shows the output from a review of a serverless workgroup. Because four provisioned-only areas don’t apply, the tool evaluated 8 of the 12 diagnostic areas, identified 8 findings, and mapped them to 6 distinct recommendations:&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-text"&gt;Cluster Review: analytics-cluster (Serverless, eu-west-2 / London)
Ran the diagnostic --- result: 8 signals evaluated, 8 findings, 6 recommendations.
Findings
- Missing statistics --- 16 queries hit tables that have never been analyzed
- Dist/Broadcast alerts --- 10 queries reshuffle data across nodes
- Nested loop joins / cross-joins --- 5 long-running queries
- Small tables without ALL distribution --- 4 tables
- Small tables with a manual sort key --- 4 tables
- Large table with data skew --- 1 table (slice imbalance ≥4x)
- Large table distributed by date/datetime --- 1 table
- Variable serverless workload in the AI-scaling range --- 1 workgroup
Recommendations
1. REC_003 --- Analyze tables (fix missing stats). Effort: Low. Run or schedule ANALYZE on the 16 affected tables. Lowest-effort, highest-impact item; also improves the poor distribution/plan decisions the optimizer is making. Docs: https://docs.aws.amazon.com/redshift/latest/dg/t_Analyzing_tables.html
2. REC_007 --- Right-size sort keys. Effort: Low. The 4 small tables carry manual sort keys that add overhead for little benefit --- switch them to ALTER TABLE [table] ALTER SORTKEY AUTO. Docs: https://docs.aws.amazon.com/redshift/latest/dg/c_best-practices-sort-key.html
3. REC_008 --- Choose the right distribution style. Effort: Low--Medium. Small tables → DISTSTYLE AUTO/ALL; the skewed large table → pick a better DISTKEY (skew often from NULLs in the key); the date-distributed large table → reconsider the key or move to DISTSTYLE AUTO. This also resolves the 10 Dist/Broadcast query alerts. Docs: https://docs.aws.amazon.com/redshift/latest/dg/c_best-practices-best-dist-key.html
4. REC_030 --- Tune serverless AI-driven scaling. Effort: Low. Review your price-performance target (Optimizes for cost / Balanced / Optimizes for performance) and pair it with Max capacity + Max RPU-hours caps to keep cost predictable. Adapts over 1--3 days. Docs: https://docs.aws.amazon.com/redshift/latest/mgmt/serverless-capacity.html
5. REC_009 --- Remove nested loop joins (cross-joins). Effort: Medium. Add join conditions to the 5 cross-join queries. Docs: https://docs.aws.amazon.com/redshift/latest/dg/query-performance-improvement-opportunities.html#nested-loop
6. REC_019 --- Add QMR rules. Effort: Medium. Add query monitoring rules to catch nested loops and long runtimes going forward (log/hop/abort actions). Docs: https://docs.aws.amazon.com/redshift/latest/dg/cm-c-wlm-query-monitoring-rules.html
Suggested starting point
Start with REC_003 (ANALYZE) --- cheapest, highest-impact, and likely a root cause behind several slow-query findings. Then apply the AUTO table-optimization changes in REC_007 and REC_008, which are mostly one-line ALTER TABLE statements.
Want me to pull the specific table names behind each finding and draft the exact ANALYZE / ALTER TABLE remediation statements?&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Each finding represents an independent diagnostic condition that was triggered. The &lt;code&gt;affected_row_count&lt;/code&gt; indicates how many objects match that specific condition. These counts describe different dimensions of the cluster and are not additive across findings.&lt;/p&gt;
&lt;p&gt;When the tool returns zero findings, the cluster is operating within best-practice thresholds across the evaluated areas.&lt;/p&gt;
&lt;h2 id="from-findings-to-fixes"&gt;From findings to fixes&lt;/h2&gt;
&lt;p&gt;The review output isn’t a static report. It’s a starting point for an interactive conversation. Each finding identifies a specific condition, and each recommendation provides a clear remediation path with documentation links. From here, you can continue working within the same Kiro session to plan and prepare your next steps.&lt;/p&gt;
&lt;p&gt;Recommendations fall into four broad categories:&lt;/p&gt;
&lt;ul&gt;
 &lt;li&gt;Quick configuration changes: Actions such as enabling concurrency scaling or short query acceleration require a single parameter change in the Amazon Redshift console or a brief API call. These are low-risk, high-impact adjustments that can often be applied immediately.&lt;/li&gt;
 &lt;li&gt;Batch table operations: Findings related to table design (distribution style, sort keys, compression encoding) typically affect multiple tables. You can ask Kiro to list the specific tables involved and generate the corresponding &lt;code&gt;ALTER TABLE&lt;/code&gt; statements. Review the generated SQL, then apply it through the Amazon Redshift Query Editor or your preferred SQL client.&lt;/li&gt;
 &lt;li&gt;Data ingestion optimization: Findings related to &lt;code&gt;COPY&lt;/code&gt; performance identify inefficiencies in how data is loaded. For example, source files might be too small, or file counts might not align with the cluster’s slice count. Addressing these requires changes upstream in your extract, transform, and load (ETL) pipeline or Amazon Simple Storage Service (Amazon S3) staging process rather than within Amazon Redshift itself.&lt;/li&gt;
 &lt;li&gt;Architectural decisions: Recommendations such as migrating to Amazon Redshift Serverless or resizing to RG instances require broader evaluation. These are not single-command fixes. They involve capacity planning, workload testing, and potentially migration steps. The recommendation text and linked documentation provide the context needed to begin that planning.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;After presenting findings, Kiro proposes the next best step to act upon, typically starting with the lowest-effort, highest-impact recommendation. Alternatively, you can direct the conversation yourself. For example:&lt;/p&gt;
&lt;ul&gt;
 &lt;li&gt;“Which tables are affected by the distribution style finding?”&lt;/li&gt;
 &lt;li&gt;“What would the ALTER TABLE statements look like for those tables?”&lt;/li&gt;
 &lt;li&gt;“Explain what concurrency scaling does and how to enable it.”&lt;/li&gt;
 &lt;li&gt;“What are the trade-offs of migrating this workload to serverless?”&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Kiro retrieves the relevant details, generates SQL where applicable, and references the documentation. The tool diagnoses and recommends, but does not modify your cluster. The decision to apply changes remains with you.&lt;/p&gt;
&lt;h2 id="best-practices"&gt;Best practices&lt;/h2&gt;
&lt;p&gt;Tips for getting the most out of &lt;strong&gt;review_cluster.&lt;/strong&gt;&lt;/p&gt;
&lt;ol type="1"&gt;
 &lt;li&gt;Embed reviews in your DataOps practice. Regular table and cluster maintenance is a foundational best practice for any production data warehouse, and its value comes from consistency. Operational reviews are one component of a broader DataOps discipline: the practice of maintaining data systems that are clean, reliable, governed, and always available. Define KPIs for your cluster, such as query latency percentiles, disk spill frequency, and WLM queue wait times. Then use periodic review_cluster runs to track your query and workload optimization progress against them. Over time, this creates a feedback loop: findings inform remediation, KPIs measure impact, and the next review validates improvement.&lt;/li&gt;
 &lt;li&gt;Run reviews on a regular cadence. Treat operational reviews like testing: the more routinely you run them, the sooner you catch drift before it affects users. Consider running a review weekly, after significant schema changes, after major data loads, or before anticipated peak traffic events.&lt;/li&gt;
 &lt;li&gt;Follow the documentation links. Every recommendation includes direct links to the relevant AWS documentation. These pages provide detailed guidance, edge cases, and configuration examples that go beyond what the recommendation text can cover. Use them as your primary reference when planning remediation.&lt;/li&gt;
 &lt;li&gt;Start with low-effort wins. The recommendations are ordered by effort. Begin with quick configuration changes (concurrency scaling, short query acceleration, query monitoring rules) before moving to structural changes that require broader planning. Early wins build confidence and often improve cluster performance enough to create headroom for larger changes.&lt;/li&gt;
 &lt;li&gt;Use the review as a baseline. Run a review before and after significant changes to measure their effect. For example, after applying table design recommendations, a follow-up review should show fewer table-related findings. This before-and-after pattern helps validate that your changes had the intended impact.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;In this post, you learned how to run an automated operational review of your Amazon Redshift cluster using natural language with Kiro. The &lt;strong&gt;review_cluster&lt;/strong&gt; tool evaluates 12 diagnostic areas, from table design and workload management to node utilization and data ingestion performance. It returns prioritized, actionable recommendations linked directly to AWS documentation.&lt;/p&gt;
&lt;p&gt;What previously required a specialist to assemble diagnostic scripts, interpret system view outputs, and compile findings over several hours now completes in a single request. This shift makes it practical to incorporate operational reviews into your regular workflow rather than treating them as an infrequent, resource-intensive exercise.&lt;/p&gt;
&lt;p&gt;To get started:&lt;/p&gt;
&lt;ol type="1"&gt;
 &lt;li&gt;Make sure the Amazon Redshift MCP server is configured in Kiro (see the &lt;a href="https://aws.amazon.com/blogs/big-data/query-amazon-redshift-using-natural-language-with-kiro/" target="_blank" rel="noopener"&gt;setup post&lt;/a&gt;).&lt;/li&gt;
 &lt;li&gt;Grant the &lt;code&gt;sys:monitor&lt;/code&gt; role to your database user.&lt;/li&gt;
 &lt;li&gt;Ask Kiro to review your cluster.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;To go further:&lt;/p&gt;
&lt;ul&gt;
 &lt;li&gt;Visit the &lt;a href="https://awslabs.github.io/mcp/servers/redshift-mcp-server" target="_blank" rel="noopener"&gt;Amazon Redshift MCP server&lt;/a&gt; documentation for the full tool reference.&lt;/li&gt;
 &lt;li&gt;Read the &lt;a href="https://awslabs.github.io/mcp/" target="_blank" rel="noopener"&gt;MCP&lt;/a&gt; protocol to understand how AI agents integrate with external tools.&lt;/li&gt;
 &lt;li&gt;Explore &lt;a href="https://kiro.dev/" target="_blank" rel="noopener"&gt;Kiro&lt;/a&gt; for additional capabilities including steering files, hooks, and agent automation.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr style="width: 100%"&gt;
&lt;h2&gt;About the authors&lt;/h2&gt;
&lt;footer&gt;
 &lt;div class="blog-author-box" style="padding-top: 2.0em"&gt;
  &lt;div class="blog-author-image" style="margin-right: 1.0em"&gt;
   &lt;p&gt;&lt;img loading="lazy" class="alignnone size-full" src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/22/BDB-6088-1.jpg" alt="Sidhanth Muralidhar" width="100" height="133"&gt;&lt;/p&gt;
  &lt;/div&gt;
  &lt;h3 class="lb-h4"&gt;Sidhanth Muralidhar&lt;/h3&gt;
  &lt;p&gt;Sidhanth is a Principal Technical Account Manager at AWS, where he partners with enterprise customers to design, scale, and optimize cloud-focused systems. He specializes in guiding organizations through complex architectural decisions across cost efficiency, reliability, performance, and operational excellence. His work increasingly sits at the intersection of data systems and AI as well, helping customers operationalize modern data architectures and build intelligent, production-ready systems.&lt;/p&gt;
 &lt;/div&gt;
 &lt;div class="blog-author-box" style="padding-top: 2.0em"&gt;
  &lt;div class="blog-author-image" style="margin-right: 1.0em"&gt;
   &lt;p&gt;&lt;img loading="lazy" class="alignnone size-full" src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/22/BDB-6088-2.jpg" alt="Sergey Konoplev" width="100" height="133"&gt;&lt;/p&gt;
  &lt;/div&gt;
  &lt;h3 class="lb-h4"&gt;Sergey Konoplev&lt;/h3&gt;
  &lt;p&gt;Sergey is a Senior Database Engineer on the Amazon Redshift team at AWS. For more than a decade he has focused on the automation and improvement of database and data operations, and he currently drives a range of initiatives spanning operations, observability, and AI tooling.&lt;/p&gt;
 &lt;/div&gt;
 &lt;div class="blog-author-box" style="padding-top: 2.0em"&gt;
  &lt;div class="blog-author-image" style="margin-right: 1.0em"&gt;
   &lt;p&gt;&lt;img loading="lazy" class="alignnone size-full" src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/22/BDB-6088-3.jpg" alt="Vlad Siniavin" width="100" height="133"&gt;&lt;/p&gt;
  &lt;/div&gt;
  &lt;h3 class="lb-h4"&gt;Vlad Siniavin&lt;/h3&gt;
  &lt;p&gt;Vlad is a Sr.&amp;nbsp;Technical Account Manager at AWS with over 15 years of experience in building innovative solutions, products and services. He is driven by delivering measurable outcomes for his customers – whether that’s reducing operational risk, optimising costs, or accelerating cloud adoption. He believes the best technical guidance starts with deeply understanding what matters most to the customer and acting in their best interest.&lt;/p&gt;
 &lt;/div&gt;
&lt;/footer&gt;</content:encoded>
					
		
		
			</item>
		<item>
		<title>Optimize consumer rebalancing on Amazon MSK with next generation protocol</title>
		<link>https://aws.amazon.com/blogs/big-data/optimize-consumer-rebalancing-on-amazon-msk-with-next-generation-protocol/</link>
		
		<dc:creator><![CDATA[Yashika Jain]]></dc:creator>
		<pubDate>Thu, 01 Oct 2026 16:43:54 +0000</pubDate>
				<category><![CDATA[Advanced (300)]]></category>
		<category><![CDATA[Amazon Managed Streaming for Apache Kafka (Amazon MSK)]]></category>
		<category><![CDATA[Technical How-to]]></category>
		<guid isPermaLink="false">69d92e52c42412b163a55af48bd32f4e5b258a73</guid>

					<description>The KIP-848 consumer protocol in Apache Kafka 4.0 redesigns consumer group rebalancing to eliminate stop-the-world pauses. This post explains how the consumer protocol works on Amazon MSK, how to enable it on MSK Standard and Express brokers, and how to diagnose and resolve slow rebalancing issues.</description>
										<content:encoded>&lt;p&gt;If you run large consumer groups on Apache Kafka and &lt;a href="https://aws.amazon.com/msk/" target="_blank" rel="noopener"&gt;Amazon Managed Streaming for Apache Kafka&lt;/a&gt; (Amazon MSK), you’ve likely experienced the pain of slow rebalances: processing stalls across all consumers, “rebalance storms” triggered by routine scaling events, and prolonged recovery times that impact downstream applications. With the classic rebalance protocol, even a single consumer joining or leaving the group forces a global synchronization barrier, pausing every consumer regardless of whether its partition assignments changed.&lt;/p&gt;
&lt;p&gt;The &lt;a href="https://cwiki.apache.org/confluence/spaces/KAFKA/pages/217387038/KIP-848+The+Next+Generation+of+the+Consumer+Rebalance+Protocol" target="_blank" rel="noopener"&gt;KIP-848&lt;/a&gt; consumer protocol, introduced in &lt;a href="https://kafka.apache.org/40/getting-started/introduction/" target="_blank" rel="noopener"&gt;Apache Kafka 4.0&lt;/a&gt;, fundamentally redesigns how consumer group rebalancing works. Also referred to as “the Next Generation Consumer Rebalance Protocol”, KIP-848 shifts coordination logic from the client to the broker-side group coordinator. This supports fully incremental, server-driven rebalancing that significantly improves performance for large consumer groups. You can use the consumer protocol on Amazon MSK on all 4.x Apache Kafka versions on both MSK Standard and Express brokers.&lt;/p&gt;
&lt;p&gt;In this post, we explain how the consumer protocol works, how to enable it on Amazon MSK, and how to diagnose and resolve slow rebalancing issues to help improve performance.&lt;/p&gt;
&lt;h2 id="the-classic-protocol-compared-to-the-consumer-protocol"&gt;The classic protocol compared to the consumer protocol&lt;/h2&gt;
&lt;p&gt;The classic protocol relied on client-side rebalance logic with a global synchronization barrier. Every rebalance caused all consumers in the group to pause processing simultaneously regardless of whether their partition assignments were changing. This led to “rebalance storms” in large consumer groups where cascading rebalances could take minutes to resolve. The &lt;a href="https://kafka.apache.org/25/javadoc/org/apache/kafka/clients/consumer/CooperativeStickyAssignor.html" target="_blank" rel="noopener"&gt;CooperativeStickyAssignor&lt;/a&gt; is a client-side partition assignment strategy that supports incremental, cooperative rebalancing. It significantly improves rebalance performance and minimizes disruption to groups during rebalance events. However, it still suffers from bottlenecks as consumer group size and partition count increase. For large workloads, client-side rebalancing behavior can result in longer rebalancing times and require significant client tuning and monitoring during rebalances.&lt;/p&gt;
&lt;p&gt;The consumer protocol addresses these limitations by moving all rebalancing logic to the server. The broker handles coordination using a continuous heartbeat mechanism and server-driven reconciliation process. Only affected partitions move during a rebalance, and consumers with unchanged assignments continue processing uninterrupted. This results in faster recovery compared to the classic protocol, and improved scalability as workloads grow.&lt;/p&gt;
&lt;p&gt;The following table compares the classic and next generation protocols across key dimensions.&lt;/p&gt;
&lt;table border="1px" width="100%" cellpadding="10px"&gt;
 &lt;tbody&gt;
  &lt;tr&gt;
   &lt;td&gt;&lt;strong&gt;Aspect&lt;/strong&gt;&lt;/td&gt;
   &lt;td&gt;&lt;strong&gt;Classic Protocol&lt;/strong&gt;&lt;/td&gt;
   &lt;td&gt;&lt;strong&gt;Next Generation Protocol (KIP-848)&lt;/strong&gt;&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;Rebalance logic&lt;/td&gt;
   &lt;td&gt;Client-side&lt;/td&gt;
   &lt;td&gt;Fully server-driven&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;Consumer impact&lt;/td&gt;
   &lt;td&gt;Depends on assignor, all consumers pause, or rebalance is limited by group size&lt;/td&gt;
   &lt;td&gt;Only affected consumers impacted, scales effectively as groups grow&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;Mechanism&lt;/td&gt;
   &lt;td&gt;Client-side algorithm and cross-group coordination&lt;/td&gt;
   &lt;td&gt;Incremental, async reconciliation&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;Commit processing&lt;/td&gt;
   &lt;td&gt;Paused during rebalance&lt;/td&gt;
   &lt;td&gt;Able to progress during rebalance&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;Scalability&lt;/td&gt;
   &lt;td&gt;Complex, fragile at scale&lt;/td&gt;
   &lt;td&gt;Resilient, broker-driven&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;Rebalance storms&lt;/td&gt;
   &lt;td&gt;Common in large groups&lt;/td&gt;
   &lt;td&gt;Eliminated&lt;/td&gt;
  &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id="server-side-configuration"&gt;Server-side configuration&lt;/h2&gt;
&lt;p&gt;With the consumer protocol, key parameters are now configured on the server rather than the client:&lt;/p&gt;
&lt;ul&gt;
 &lt;li&gt;&lt;code&gt;group.consumer.heartbeat.interval.ms&lt;/code&gt; – Controls the consumer heartbeat interval (server-side).&lt;/li&gt;
 &lt;li&gt;&lt;code&gt;group.consumer.session.timeout.ms&lt;/code&gt; – Controls the session timeout (server-side).&lt;/li&gt;
 &lt;li&gt;&lt;code&gt;group.consumer.assignors&lt;/code&gt; – Specifies available assignors (uniform and range by default).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In Amazon MSK Express brokers, these configurations are read-only and cannot be modified. In Amazon MSK Standard brokers, these configurations are managed with broker configurations. To update these configurations in Amazon MSK Standard brokers, refer to &lt;a href="https://docs.aws.amazon.com/msk/latest/developerguide/msk-update-cluster-config.html" target="_blank" rel="noopener"&gt;Update the configuration of an Amazon MSK cluster&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="when-to-use-the-consumer-protocol"&gt;When to use the consumer protocol&lt;/h2&gt;
&lt;p&gt;The consumer protocol provides the most benefit to workloads with the following requirements:&lt;/p&gt;
&lt;ul&gt;
 &lt;li&gt;&lt;strong&gt;Large consumer groups:&lt;/strong&gt; Groups with many consumers and partitions see the most significant improvements because of the elimination of global synchronization barriers.&lt;/li&gt;
 &lt;li&gt;&lt;strong&gt;High-availability applications:&lt;/strong&gt; Applications that cannot afford processing interruptions benefit from continuous message processing during rebalances. Financial services, real-time analytics, and fraud detection systems are ideal candidates.&lt;/li&gt;
 &lt;li&gt;&lt;strong&gt;Frequently rebalancing environments:&lt;/strong&gt; Automatic scaling deployments, Kubernetes with frequent pod restarts, or continuous integration and continuous delivery (CI/CD) environments experience significantly less disruption.&lt;/li&gt;
 &lt;li&gt;&lt;strong&gt;Dynamic partition scaling:&lt;/strong&gt; Workloads that regularly add partitions or topics benefit from the incremental, server-driven approach.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="prerequisites"&gt;Prerequisites&lt;/h2&gt;
&lt;p&gt;Before you begin, make sure that you have the following:&lt;/p&gt;
&lt;ul&gt;
 &lt;li&gt;An Amazon MSK cluster running Apache Kafka version 4.0 or later (both MSK Standard and Express brokers are supported).&lt;/li&gt;
 &lt;li&gt;A Kafka client library that supports the KIP-848 consumer protocol (see Step 4 for supported versions).&lt;/li&gt;
 &lt;li&gt;Basic familiarity with Apache Kafka consumer groups and partition assignment.&lt;/li&gt;
 &lt;li&gt;An AWS account with appropriate permissions to manage your MSK cluster.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="enabling-the-consumer-protocol-on-amazon-msk"&gt;Enabling the consumer protocol on Amazon MSK&lt;/h2&gt;
&lt;p&gt;The following steps walk you through verifying your cluster version, configuring your consumer client, removing deprecated configurations, and confirming client library support.&lt;/p&gt;
&lt;h3 id="step-1-verify-cluster-version"&gt;Step 1: Verify cluster version&lt;/h3&gt;
&lt;p&gt;The consumer protocol requires Apache Kafka 4.0 or later. To use the consumer protocol on Amazon MSK, verify that your cluster is running Apache Kafka version 4.0.x or later. You can verify your cluster’s Apache Kafka version using the AWS Management Console, AWS Command Line Interface (AWS CLI), or AWS SDKs:&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-bash"&gt;aws kafka describe-cluster-v2 --cluster-arn &amp;lt;your-cluster-arn&amp;gt; \
    --query "ClusterInfo.Provisioned.CurrentBrokerSoftwareInfo.KafkaVersion"&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;If your cluster is running Apache Kafka 4.0.x or later, the consumer protocol is automatically enabled on the server and ready to use. No additional server-side feature flag verification is needed.&lt;/p&gt;
&lt;h3 id="step-2-configure-consumer-client"&gt;Step 2: Configure consumer client&lt;/h3&gt;
&lt;p&gt;Set &lt;code&gt;group.protocol=consumer&lt;/code&gt; in your consumer configuration. The protocol is not enabled by default:&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-python"&gt;# confluent-kafka-python example
config = {
    'bootstrap.servers': bootstrap_servers,
    'group.id': group_id,
    'group.protocol': 'consumer',  # Required — defaults to 'classic' if omitted
    'auto.offset.reset': 'earliest'
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;The consumer protocol can be changed in-place for existing consumer groups. When you update the &lt;code&gt;group.protocol&lt;/code&gt;, perform a rolling restart of your consumers. The broker-side group coordinator automatically handles the upgrade to the consumer protocol and handles classic protocol requests from old clients alongside the upgraded clients.&lt;/p&gt;
&lt;h3 id="step-3-remove-deprecated-client-configurations"&gt;Step 3: Remove deprecated client configurations&lt;/h3&gt;
&lt;p&gt;When the consumer protocol is enabled, the following client-side configurations are no longer supported because they are controlled by the brokers:&lt;/p&gt;
&lt;ul&gt;
 &lt;li&gt;&lt;code&gt;heartbeat.interval.ms&lt;/code&gt;.&lt;/li&gt;
 &lt;li&gt;&lt;code&gt;session.timeout.ms&lt;/code&gt;.&lt;/li&gt;
 &lt;li&gt;&lt;code&gt;partition.assignment.strategy&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="step-4-verify-client-library-support"&gt;Step 4: Verify client library support&lt;/h3&gt;
&lt;p&gt;Verify that your Kafka client version supports the consumer protocol:&lt;/p&gt;
&lt;ul&gt;
 &lt;li&gt;&lt;strong&gt;Java clients:&lt;/strong&gt; Generally available (GA) in Apache Kafka 4.0+.&lt;/li&gt;
 &lt;li&gt;&lt;strong&gt;confluent-kafka-python:&lt;/strong&gt; Version 2.12.0+ (GA support for KIP-848). See the &lt;a href="https://github.com/confluentinc/confluent-kafka-python/releases" target="_blank" rel="noopener"&gt;release notes&lt;/a&gt;.&lt;/li&gt;
 &lt;li&gt;&lt;strong&gt;librdkafka-based clients (Go, .NET, C/C++):&lt;/strong&gt; Based on librdkafka 2.12.0+.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; For other Kafka client libraries, verify your client library’s documentation for &lt;code&gt;group.protocol=consumer&lt;/code&gt; support before enabling the next generation protocol. If your client doesn’t support &lt;a href="https://cwiki.apache.org/confluence/spaces/KAFKA/pages/217387038/KIP-848+The+Next+Generation+of+the+Consumer+Rebalance+Protocol" target="_blank" rel="noopener"&gt;KIP-848&lt;/a&gt;, it will continue to use the classic protocol.&lt;/p&gt;
&lt;h2 id="diagnosing-slow-consumer-group-rebalancing-with-the-consumer-protocol"&gt;Diagnosing slow consumer group rebalancing with the consumer protocol&lt;/h2&gt;
&lt;p&gt;Even after enabling the consumer protocol, you may encounter situations where consumer group rebalancing takes longer than expected. The following sections help you diagnose and resolve these issues.&lt;/p&gt;
&lt;h3 id="common-symptoms"&gt;Common symptoms&lt;/h3&gt;
&lt;ul&gt;
 &lt;li&gt;Consumer group rebalancing takes longer than expected despite setting &lt;code&gt;group.protocol=consumer&lt;/code&gt;.&lt;/li&gt;
 &lt;li&gt;Consumers pause processing during rebalances.&lt;/li&gt;
 &lt;li&gt;Broker logs show “member session expired” or “fenced” messages.&lt;/li&gt;
 &lt;li&gt;Frequent rebalances triggered during rolling deployments or pod restarts.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="step-1-confirm-the-consumer-protocol-is-active-using-broker-logs"&gt;Step 1: Confirm the consumer protocol is active using broker logs&lt;/h3&gt;
&lt;p&gt;Before troubleshooting performance, verify which protocol your consumers are actually using. Check &lt;a href="https://docs.aws.amazon.com/msk/latest/developerguide/msk-logging.html#broker-logs" target="_blank" rel="noopener"&gt;broker logs&lt;/a&gt; in &lt;a href="https://aws.amazon.com/cloudwatch/" target="_blank" rel="noopener"&gt;Amazon CloudWatch&lt;/a&gt; Logs Insights. The log patterns differ significantly between protocols.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Consumer protocol expected logs:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Key indicators: “consumer protocol”, “epoch” terminology, “target assignment” with server-side assignor, “fenced” for member removal.&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-text"&gt;[GroupCoordinator id=X] [GroupId &amp;lt;group-id&amp;gt;] Member &amp;lt;member-id&amp;gt; joins the consumer group using the consumer protocol.
[GroupCoordinator id=X] [GroupId &amp;lt;group-id&amp;gt;] Bumped group epoch to 309 with metadata hash 4064309670987706693.
[GroupCoordinator id=X] [GroupId &amp;lt;group-id&amp;gt;] Computed a new target assignment for epoch 309 with 'uniform' assignor in 0ms.
[GroupCoordinator id=X] [GroupId &amp;lt;group-id&amp;gt;] Member &amp;lt;member-id&amp;gt; fenced from the group because the member session expired.&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Classic protocol expected logs:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Key indicators: “PreparingRebalance” state, “old generation” terminology, “Assignment received from leader”.&lt;/p&gt;
&lt;p&gt;If you see classic protocol logs, the consumer protocol is not active. Proceed to Step 2 to troubleshoot why.&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-text"&gt;[GroupCoordinator id=X] Preparing to rebalance group &amp;lt;group-id&amp;gt; in state PreparingRebalance with old generation X
[GroupCoordinator id=X] Stabilized group &amp;lt;group-id&amp;gt; with X members
[GroupCoordinator id=X] Assignment received from leader for group &amp;lt;group-id&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h3 id="step-2-troubleshoot-why-the-consumer-protocol-is-not-active"&gt;Step 2: Troubleshoot why the consumer protocol is not active&lt;/h3&gt;
&lt;p&gt;Verify that your client configuration, client library versions, and cluster versions support the consumer protocol, as described in the preceding Step 1 through Step 4.&lt;/p&gt;
&lt;h3 id="step-3-resolve-slow-rebalancing-when-kip-848-is-active"&gt;Step 3: Resolve slow rebalancing when KIP-848 is active&lt;/h3&gt;
&lt;p&gt;After you verify the consumer protocol is active but rebalancing is still slow, investigate the following causes:&lt;/p&gt;
&lt;h4 id="a.-consumer-session-timeout-causing-premature-member-removal"&gt;A. Consumer session timeout causing premature member removal&lt;/h4&gt;
&lt;p&gt;With the consumer protocol, session timeout is server-controlled through &lt;code&gt;group.consumer.session.timeout.ms&lt;/code&gt; (default: 45 seconds). The diagnostic path depends on whether you are using static group membership. The following table outlines the diagnostic path and recommended actions for each scenario.&lt;/p&gt;
&lt;table border="1px" width="100%" cellpadding="10px"&gt;
 &lt;tbody&gt;
  &lt;tr&gt;
   &lt;td&gt;&lt;strong&gt;Scenario&lt;/strong&gt;&lt;/td&gt;
   &lt;td&gt;&lt;strong&gt;Symptom&lt;/strong&gt;&lt;/td&gt;
   &lt;td&gt;&lt;strong&gt;Root cause&lt;/strong&gt;&lt;/td&gt;
   &lt;td&gt;&lt;strong&gt;Recommended action&lt;/strong&gt;&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;&lt;strong&gt;With static group membership&lt;/strong&gt; (&lt;code&gt;group.instance.id&lt;/code&gt; configured)&lt;/td&gt;
   &lt;td&gt;Slow rebalancing when a static member terminates without calling &lt;code&gt;consumer.close()&lt;/code&gt;&lt;/td&gt;
   &lt;td&gt;The coordinator waits for the full session timeout before reassigning partitions. This is the most common cause of slow rebalancing in containerized environments.&lt;/td&gt;
   &lt;td&gt;&lt;strong&gt;MSK Standard:&lt;/strong&gt; Implement graceful shutdown to trigger an immediate leave-group request, or increase the session timeout: &lt;code&gt;group.consumer.session.timeout.ms=60000&lt;/code&gt; (default is &lt;code&gt;45000&lt;/code&gt;). &lt;strong&gt;MSK Express:&lt;/strong&gt; This configuration is not editable in Amazon MSK Express clusters. For Amazon MSK Express, optimize your client’s cold starts to allow members to restart within the 45 second consumer session timeout.&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;&lt;strong&gt;Without static group membership&lt;/strong&gt;&lt;/td&gt;
   &lt;td&gt;Session timeouts expiring during normal operations&lt;/td&gt;
   &lt;td&gt;Your consumer is freezing or becoming unresponsive, which prevents heartbeats from reaching the coordinator.&lt;/td&gt;
   &lt;td&gt;
    &lt;p&gt;Investigate long-running message processing, garbage collection pauses, network connectivity issues, or resource exhaustion on the consumer host. Look for this in broker logs:&lt;/p&gt;
    &lt;p&gt;[GroupCoordinator id=X] [GroupId &amp;lt;group-id&amp;gt;] Member &amp;lt;member-id&amp;gt; has timed out&lt;/p&gt;
   &lt;/td&gt;
  &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;h4 id="b.-missing-graceful-shutdown-handling"&gt;B. Missing graceful shutdown handling&lt;/h4&gt;
&lt;p&gt;When consumers terminate without calling &lt;code&gt;consumer.close()&lt;/code&gt;, the coordinator waits for the full session timeout before removing the member. This is the most common cause of slow rebalancing in containerized environments.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Resolution&lt;/strong&gt;: Implement proper &lt;code&gt;SIGTERM&lt;/code&gt; handling to trigger an immediate leave-group:&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-python"&gt;import signal
import sys
from confluent_kafka import Consumer

class GracefulKafkaConsumer:
    def __init__(self, config):
        self.running = True
        self.consumer = Consumer(config)
        signal.signal(signal.SIGTERM, self.shutdown_handler)
        signal.signal(signal.SIGINT, self.shutdown_handler)

    def shutdown_handler(self, signum, frame):
        print(f"Received signal {signum}, initiating graceful shutdown...")
        self.running = False

    def consume_loop(self):
        self.consumer.subscribe(['your-topic'])
        while self.running:
            msg = self.consumer.poll(timeout=1.0)
            if msg is None:
                continue
            # Process message

        print("Closing consumer gracefully...")
        self.consumer.close()  # Sends LeaveGroup — triggers immediate rebalance
        sys.exit(0)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;For Kubernetes, verify that &lt;code&gt;terminationGracePeriodSeconds&lt;/code&gt; allows time for &lt;code&gt;consumer.close()&lt;/code&gt; to complete:&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-yaml"&gt;spec:
  terminationGracePeriodSeconds: 60
  containers:
    - name: kafka-consumer&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h4 id="c.-frequent-rebalances-from-unstable-consumers"&gt;C. Frequent rebalances from unstable consumers&lt;/h4&gt;
&lt;p&gt;If consumers repeatedly join and leave (out-of-memory (OOM) kills, &lt;code&gt;CrashLoopBackOff&lt;/code&gt;, or short-lived tasks), each event triggers a new rebalance epoch.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Resolution&lt;/strong&gt;: Use static membership by assigning a unique &lt;code&gt;group.instance.id&lt;/code&gt;:&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-python"&gt;config = {
    'bootstrap.servers': bootstrap_servers,
    'group.id': 'my-group',
    'group.protocol': 'consumer',
    'group.instance.id': f'consumer-{unique_identifier}'  # Unique per consumer
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;With static membership:&lt;/p&gt;
&lt;ul&gt;
 &lt;li&gt;Short restarts within the session timeout don’t trigger rebalances.&lt;/li&gt;
 &lt;li&gt;The consumer rejoins with the same partition assignment.&lt;/li&gt;
 &lt;li&gt;Scaling up (adding new consumers) still works. New &lt;code&gt;group.instance.id&lt;/code&gt; values trigger assignment of unassigned partitions only.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="monitoring-and-validation"&gt;Monitoring and validation&lt;/h2&gt;
&lt;p&gt;After applying changes, confirm the improvement:&lt;/p&gt;
&lt;ul&gt;
 &lt;li&gt;&lt;strong&gt;Check broker logs:&lt;/strong&gt; Confirm that “member session expired” messages no longer appear during normal operations or deployments.&lt;/li&gt;
 &lt;li&gt;&lt;strong&gt;Monitor consumer lag:&lt;/strong&gt; Use the &lt;code&gt;SumOffsetLag&lt;/code&gt; and &lt;code&gt;EstimatedMaxTimeLag&lt;/code&gt; &lt;a href="https://docs.aws.amazon.com/msk/latest/developerguide/metrics-details.html" target="_blank" rel="noopener"&gt;Amazon CloudWatch metrics&lt;/a&gt; to verify that lag returns to zero quickly after a rebalance.&lt;/li&gt;
 &lt;li&gt;&lt;strong&gt;Describe consumer group:&lt;/strong&gt; Use &lt;code&gt;kafka-consumer-groups.sh --describe&lt;/code&gt; to verify that all members are active and stable.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;After implementing the consumer protocol, you should observe the following behavior for consumer group rebalances:&lt;/p&gt;
&lt;ul&gt;
 &lt;li&gt;Consistently faster rebalance times compared to the classic protocol.&lt;/li&gt;
 &lt;li&gt;Fewer session timeout-related rebalances.&lt;/li&gt;
 &lt;li&gt;More stable consumer group membership.&lt;/li&gt;
 &lt;li&gt;Smooth scaling operations without disrupting existing consumers.&lt;/li&gt;
 &lt;li&gt;Fewer unnecessary rebalances during consumer restarts when using static membership.&lt;/li&gt;
 &lt;li&gt;Clean consumer departures without waiting for timeout expiration when using graceful shutdown.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To get started, try the consumer protocol in your non-production workloads and observe the rebalance improvements as you scale your workload up and down.&lt;/p&gt;
&lt;p&gt;To learn more about Amazon MSK and the consumer rebalance protocol, see the following resources:&lt;/p&gt;
&lt;ul&gt;
 &lt;li&gt;&lt;a href="https://aws.amazon.com/msk/" target="_blank" rel="noopener"&gt;Amazon Managed Streaming for Apache Kafka (Amazon MSK)&lt;/a&gt;&lt;/li&gt;
 &lt;li&gt;&lt;a href="https://docs.aws.amazon.com/msk/latest/developerguide/" target="_blank" rel="noopener"&gt;Amazon MSK Developer Guide&lt;/a&gt;&lt;/li&gt;
 &lt;li&gt;&lt;a href="https://kafka.apache.org/40/operations/consumer-rebalance-protocol/" target="_blank" rel="noopener"&gt;Consumer Rebalance Protocol – Apache Kafka documentation&lt;/a&gt;&lt;/li&gt;
 &lt;li&gt;&lt;a href="https://repost.aws/knowledge-center/msk-consumer-group-rebalance" target="_blank" rel="noopener"&gt;Troubleshoot continuous rebalancing of your Amazon MSK consumer group&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style="clear: both"&gt;&lt;/p&gt;
&lt;hr style="width: 100%"&gt;
&lt;h2&gt;About the authors&lt;/h2&gt;
&lt;footer&gt;
 &lt;div class="blog-author-box" style="padding-top: 2.0em"&gt;
  &lt;div class="blog-author-image" style="margin-right: 1.0em"&gt;
   &lt;img loading="lazy" class="alignnone size-full" src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/28/BDB-6038-1.jpg" alt="Yashika Jain" width="100" height="133"&gt;
  &lt;/div&gt;
  &lt;h3 class="lb-h4"&gt;Yashika Jain&lt;/h3&gt;
  &lt;p style="overflow: hidden"&gt;&lt;a href="https://www.linkedin.com/in/jain-yashika/" target="_blank" rel="noopener"&gt;Yashika&lt;/a&gt; is a Senior Cloud Analytics Engineer at AWS, specializing in real-time analytics and event-driven architectures. She is committed to helping customers by providing deep technical guidance, driving best practices across real-time data platforms and solving complex issues related to their streaming data architectures.&lt;/p&gt;
 &lt;/div&gt;
 &lt;div class="blog-author-box" style="padding-top: 2.0em"&gt;
  &lt;div class="blog-author-image" style="margin-right: 1.0em"&gt;
   &lt;img loading="lazy" class="alignnone size-full" src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/28/BDB-6038-2.jpg" alt="Vinayaka Gangadhar" width="100" height="133"&gt;
  &lt;/div&gt;
  &lt;h3 class="lb-h4"&gt;Vinayaka Gangadhar&lt;/h3&gt;
  &lt;p style="overflow: hidden"&gt;&lt;a href="https://www.linkedin.com/in/vinayaka-gangadhar/" target="_blank" rel="noopener"&gt;Vinayaka&lt;/a&gt; is an Analytics Specialist at Amazon Web Services (AWS), where he helps customers build and troubleshoot scalable data platforms and derive meaningful insights through AWS analytics services, with deep expertise in Amazon Redshift and Amazon OpenSearch. When not solving complex analytics challenges, he enjoys exploring new technologies and spending quality time with his family.&lt;/p&gt;
 &lt;/div&gt;
 &lt;div class="blog-author-box" style="padding-top: 2.0em"&gt;
  &lt;div class="blog-author-image" style="margin-right: 1.0em"&gt;
   &lt;img loading="lazy" class="alignnone size-full" src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/28/BDB-6038-3.jpg" alt="Kalyan Janaki" width="100" height="133"&gt;
  &lt;/div&gt;
  &lt;h3 class="lb-h4"&gt;Kalyan Janaki&lt;/h3&gt;
  &lt;p style="overflow: hidden"&gt;Kalyan is Senior Big Data &amp;amp; Analytics Specialist with Amazon Web Services. He helps customers architect and build highly scalable, performant, and secure cloud-based solutions on AWS.&lt;/p&gt;
 &lt;/div&gt;
&lt;/footer&gt;</content:encoded>
					
		
		
			</item>
		<item>
		<title>Building an LLM-powered DAG failure analysis plugin for Amazon MWAA</title>
		<link>https://aws.amazon.com/blogs/big-data/building-an-llm-powered-dag-failure-analysis-plugin-for-amazon-mwaa/</link>
		
		<dc:creator><![CDATA[Sushant Samantaray]]></dc:creator>
		<pubDate>Tue, 29 Sep 2026 16:17:09 +0000</pubDate>
				<category><![CDATA[Advanced (300)]]></category>
		<category><![CDATA[Amazon Managed Workflows for Apache Airflow (Amazon MWAA)]]></category>
		<category><![CDATA[Technical How-to]]></category>
		<guid isPermaLink="false">e839c1e6429cf30419f8f54a2acfd6f1029eb0d5</guid>

					<description>Debugging Apache Airflow DAG failures across services like AWS Glue, Amazon EMR, and Amazon Athena is slow and manual. In this post, we show you how to build a custom Airflow plugin that integrates with Amazon Bedrock to automatically analyze DAG task failures and deliver on-demand root cause analysis on Amazon MWAA.</description>
										<content:encoded>&lt;p&gt;&lt;a href="https://airflow.apache.org/" target="_blank" rel="noopener"&gt;Apache Airflow&lt;/a&gt; has become the orchestration backbone for data pipelines across industries. But as those pipelines grow to hundreds of directed acyclic graphs (DAGs) spanning services like &lt;a href="https://aws.amazon.com/glue/" target="_blank" rel="noopener"&gt;AWS Glue&lt;/a&gt;, &lt;a href="https://aws.amazon.com/emr/" target="_blank" rel="noopener"&gt;Amazon EMR&lt;/a&gt;, &lt;a href="https://aws.amazon.com/athena/" target="_blank" rel="noopener"&gt;Amazon Athena&lt;/a&gt;, and &lt;a href="https://aws.amazon.com/redshift/" target="_blank" rel="noopener"&gt;Amazon Redshift&lt;/a&gt;, debugging a single task failure turns into a significant operational challenge. When a task fails, data engineers sift through logs, cross-reference &lt;a href="https://docs.aws.amazon.com/mwaa/latest/userguide/working-dags.html" target="_blank" rel="noopener"&gt;DAG&lt;/a&gt; configurations, and analyze error messages to find the root cause, delaying pipeline service level agreements (SLAs) and impacting team productivity.&lt;/p&gt;
&lt;p&gt;In this post, we show you how to build a custom Apache Airflow plugin that integrates with &lt;a href="https://aws.amazon.com/bedrock/" target="_blank" rel="noopener"&gt;Amazon Bedrock&lt;/a&gt; to automatically analyze DAG task failures and provide actionable diagnostic insights. The plugin deploys to &lt;a href="https://aws.amazon.com/managed-workflows-for-apache-airflow/" target="_blank" rel="noopener"&gt;Amazon Managed Workflows for Apache Airflow&lt;/a&gt; (Amazon MWAA) and provides AI-powered root cause analysis on demand.&lt;/p&gt;
&lt;p&gt;The complete source code for this solution is available in the &lt;a href="https://github.com/aws-samples/sample-aws-mwaa-llm-powered-plugin" target="_blank" rel="noopener"&gt;sample-aws-mwaa-llm-powered-plugin&lt;/a&gt; GitHub repository. Clone the repository and follow along as we explain the design decisions throughout this post.&lt;/p&gt;
&lt;h2 id="solution-overview"&gt;Solution overview&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://airflow.apache.org/" target="_blank" rel="noopener"&gt;Apache Airflow&lt;/a&gt; is a widely adopted open source platform for programmatically authoring, scheduling, and monitoring complex data pipelines. Teams use Airflow to orchestrate extract, transform, and load (ETL) processes, machine learning workflows, and data lake management across industries.&lt;/p&gt;
&lt;p&gt;Amazon MWAA is a managed service that makes it straightforward to run Apache Airflow on AWS without the operational burden of managing the underlying infrastructure. With Amazon MWAA, you can focus on authoring workflows and business logic while AWS handles provisioning, patching, scaling, and securing your Airflow environments.&lt;/p&gt;
&lt;p&gt;The solution uses the following AWS services:&lt;/p&gt;
&lt;ul&gt;
 &lt;li&gt;&lt;a href="https://aws.amazon.com/managed-workflows-for-apache-airflow/" target="_blank" rel="noopener"&gt;Amazon Managed Workflows for Apache Airflow (Amazon MWAA)&lt;/a&gt; – Hosts the Airflow environment and plugin.&lt;/li&gt;
 &lt;li&gt;&lt;a href="https://aws.amazon.com/bedrock/" target="_blank" rel="noopener"&gt;Amazon Bedrock&lt;/a&gt; – Provides foundation model (FM) inference (Anthropic Claude) for failure analysis.&lt;/li&gt;
 &lt;li&gt;&lt;a href="https://aws.amazon.com/s3/" target="_blank" rel="noopener"&gt;Amazon Simple Storage Service (Amazon S3)&lt;/a&gt; – Stores plugin artifacts, DAG files, and operator scripts.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The plugin adds an analysis view directly into your Airflow UI. At a high level, when a task fails and you trigger an analysis, the plugin automatically does the following:&lt;/p&gt;
&lt;ol type="1"&gt;
 &lt;li&gt;Retrieves the failed task instance metadata from the Airflow metadata database.&lt;/li&gt;
 &lt;li&gt;Collects comprehensive context including task logs, DAG source code, and operator-specific scripts.&lt;/li&gt;
 &lt;li&gt;Sends the enriched context to Amazon Bedrock for analysis.&lt;/li&gt;
 &lt;li&gt;Returns a structured diagnostic report with root cause identification, step-by-step resolution, and prevention recommendations.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="how-it-works"&gt;How it works&lt;/h3&gt;
&lt;p&gt;The preceding four steps happen behind a single Analyze Task action. The following diagram and pipeline show the high-level architecture and how the plugin carries them out.&lt;/p&gt;
&lt;div style="width: 810px" class="wp-caption alignnone"&gt;
 &lt;a href="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/22/BDB-5613-1.png" target="_blank" rel="noopener"&gt;&lt;img src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/22/BDB-5613-1.png" alt="Architecture of the task analyzer plugin connecting the Airflow UI on Amazon MWAA to Amazon Bedrock and Amazon S3" width="800"&gt;&lt;/a&gt;
 &lt;p class="wp-caption-text"&gt;Figure 1: High-level architecture of the LLM-powered task analyzer plugin on Amazon MWAA&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;The plugin follows a multi-step analysis pipeline:&lt;/p&gt;
&lt;ol type="1"&gt;
 &lt;li&gt;&lt;strong&gt;User triggers analysis&lt;/strong&gt; – From the Airflow UI, you select a failed task and choose &lt;strong&gt;Analyze Task&lt;/strong&gt;.&lt;/li&gt;
 &lt;li&gt;&lt;strong&gt;Context collection&lt;/strong&gt; – The plugin retrieves task metadata, execution logs, and DAG source code from the Airflow metadata database and Amazon S3.&lt;/li&gt;
 &lt;li&gt;&lt;strong&gt;Operator-aware enrichment&lt;/strong&gt; – Based on the operator type, the plugin fetches the actual code or query that failed (for example, a PySpark script from AWS Glue or a SQL query from Amazon Athena).&lt;/li&gt;
 &lt;li&gt;&lt;strong&gt;Foundation model analysis&lt;/strong&gt; – The enriched context is sent to Amazon Bedrock, which returns a structured diagnostic report.&lt;/li&gt;
 &lt;li&gt;&lt;strong&gt;Results presentation&lt;/strong&gt; – The analysis displays in the Airflow UI with actionable recommendations.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;All AWS API calls (Amazon Bedrock, Amazon S3, and AWS Glue) are authenticated through the &lt;code&gt;aws_default&lt;/code&gt; Airflow connection. By default on Amazon MWAA, this connection has no static credentials, so &lt;code&gt;boto3&lt;/code&gt; falls back to the environment’s execution role. This means there are no keys to manage or rotate. If you need to call Amazon Bedrock or fetch scripts using a different identity, you can supply those credentials in the &lt;code&gt;aws_default&lt;/code&gt; connection. This can be a dedicated IAM role or a cross-account principal, used instead of the execution role.&lt;/p&gt;
&lt;h3 id="operator-aware-context-collection"&gt;Operator-aware context collection&lt;/h3&gt;
&lt;p&gt;A key differentiator of this solution is its ability to understand different Airflow operator types and automatically fetch the associated code or queries. Unlike generic log analyzers, the plugin retrieves the actual code that failed, not just the error message.&lt;/p&gt;
&lt;p&gt;The following table summarizes what the plugin fetches for each operator type:&lt;/p&gt;
&lt;table border="1px" width="100%" cellpadding="10px"&gt;
 &lt;tbody&gt;
  &lt;tr&gt;
   &lt;td&gt;&lt;strong&gt;Operator type&lt;/strong&gt;&lt;/td&gt;
   &lt;td&gt;&lt;strong&gt;What the plugin fetches&lt;/strong&gt;&lt;/td&gt;
   &lt;td&gt;&lt;strong&gt;Source&lt;/strong&gt;&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;&lt;strong&gt;GlueJobOperator&lt;/strong&gt;&lt;/td&gt;
   &lt;td&gt;PySpark or Python script&lt;/td&gt;
   &lt;td&gt;Amazon S3 (from the AWS Glue job definition)&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;&lt;strong&gt;EmrAddStepsOperator&lt;/strong&gt;&lt;/td&gt;
   &lt;td&gt;Spark or Python script&lt;/td&gt;
   &lt;td&gt;Amazon S3 (from step arguments)&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;&lt;strong&gt;EmrServerlessStartJobOperator&lt;/strong&gt;&lt;/td&gt;
   &lt;td&gt;Spark script&lt;/td&gt;
   &lt;td&gt;Amazon S3 (from job driver)&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;&lt;strong&gt;AthenaOperator&lt;/strong&gt;&lt;/td&gt;
   &lt;td&gt;SQL query&lt;/td&gt;
   &lt;td&gt;Inline (from operator parameters)&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;&lt;strong&gt;RedshiftDataOperator&lt;/strong&gt;&lt;/td&gt;
   &lt;td&gt;SQL query&lt;/td&gt;
   &lt;td&gt;Inline (from operator parameters)&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;&lt;strong&gt;BashOperator&lt;/strong&gt;&lt;/td&gt;
   &lt;td&gt;Bash command&lt;/td&gt;
   &lt;td&gt;Inline (from operator parameters)&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;&lt;strong&gt;PythonOperator&lt;/strong&gt;&lt;/td&gt;
   &lt;td&gt;Python function&lt;/td&gt;
   &lt;td&gt;DAG source code&lt;/td&gt;
  &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;This approach means the foundation model can analyze the actual logic that failed, correlating error messages with specific lines in your code for precise root cause identification.&lt;/p&gt;
&lt;h2 id="prerequisites"&gt;Prerequisites&lt;/h2&gt;
&lt;p&gt;Before you begin, make sure that you have the following:&lt;/p&gt;
&lt;ul&gt;
 &lt;li&gt;An Amazon MWAA environment running &lt;strong&gt;Apache Airflow 3.x&lt;/strong&gt; (this walkthrough uses Airflow 3.2). The plugin registers its UI through the &lt;a href="https://airflow.apache.org/docs/apache-airflow/stable/administration-and-deployment/plugins.html" target="_blank" rel="noopener"&gt;FastAPI-based plugin&lt;/a&gt; interface (&lt;code&gt;fastapi_apps&lt;/code&gt;) introduced in Airflow 3.x. For setup instructions, see &lt;a href="https://docs.aws.amazon.com/mwaa/latest/userguide/get-started.html" target="_blank" rel="noopener"&gt;Get started with Amazon MWAA&lt;/a&gt;.&lt;/li&gt;
 &lt;li&gt;Access to Amazon Bedrock with the Anthropic Claude model family enabled in your AWS Region. This walkthrough uses Anthropic Claude, but you can adapt the plugin to work with Amazon Nova or other foundation models by modifying the prompt payload format in &lt;code&gt;prompts.py&lt;/code&gt;. See &lt;a href="https://docs.aws.amazon.com/bedrock/latest/userguide/model-access.html" target="_blank" rel="noopener"&gt;Model access&lt;/a&gt;.&lt;/li&gt;
 &lt;li&gt;An &lt;a href="https://aws.amazon.com/iam/" target="_blank" rel="noopener"&gt;AWS Identity and Access Management&lt;/a&gt; (IAM) execution role for Amazon MWAA with &lt;code&gt;bedrock:InvokeModel&lt;/code&gt; and &lt;code&gt;s3:GetObject&lt;/code&gt; permissions.&lt;/li&gt;
 &lt;li&gt;An Amazon S3 bucket backing your Amazon MWAA environment with &lt;strong&gt;bucket versioning enabled&lt;/strong&gt;. See &lt;a href="https://docs.aws.amazon.com/mwaa/latest/userguide/mwaa-s3-bucket.html" target="_blank" rel="noopener"&gt;Create an Amazon S3 bucket for Amazon MWAA&lt;/a&gt;.&lt;/li&gt;
 &lt;li&gt;Python 3.10 or later installed locally.&lt;/li&gt;
 &lt;li&gt;The &lt;a href="https://aws.amazon.com/cli/" target="_blank" rel="noopener"&gt;AWS Command Line Interface&lt;/a&gt; (AWS CLI) configured with appropriate permissions.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; In most Regions, you invoke Claude through an inference profile ID (for example, us.anthropic.claude-sonnet-4-5-20250929-v1:0) rather than a bare on-demand model ID. Run &lt;code&gt;aws bedrock list-inference-profiles&lt;/code&gt; to confirm a model is ACTIVE before configuring it.&lt;/p&gt;
&lt;h2 id="plugin-design"&gt;Plugin design&lt;/h2&gt;
&lt;p&gt;In this section, we explain the plugin design and its key components. The next section walks through deploying it to your Amazon MWAA environment.&lt;/p&gt;
&lt;h3 id="plugin-structure"&gt;Plugin structure&lt;/h3&gt;
&lt;p&gt;The plugin follows the standard &lt;a href="https://airflow.apache.org/docs/apache-airflow/stable/authoring-and-scheduling/plugins.html" target="_blank" rel="noopener"&gt;Apache Airflow plugin architecture&lt;/a&gt;. The &lt;a href="https://github.com/aws-samples/sample-aws-mwaa-llm-powered-plugin" target="_blank" rel="noopener"&gt;repository&lt;/a&gt; is organized as follows:&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-text"&gt;plugins/
├── task_analyzer_plugin.py    # Main plugin: FastAPI app, endpoints, registration
└── task_analyzer/
    ├── __init__.py
    ├── prompts.py             # Bedrock model configuration and prompt templates
    ├── script_utils.py        # Operator-specific script fetching logic
    ├── templates/
    │   └── index.html
    └── static/
        ├── css/
        │   └── styles.css
        └── js/
            ├── app.jsx
            ├── components.jsx
            ├── config.js
            ├── template.jsx
            └── utils.jsx&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;The repository also includes &lt;a href="https://github.com/aws-samples/sample-aws-mwaa-llm-powered-plugin/tree/main/dags" target="_blank" rel="noopener"&gt;example DAGs&lt;/a&gt; that simulate various failure scenarios across different operator types.&lt;/p&gt;
&lt;h3 id="plugin-registration"&gt;Plugin registration&lt;/h3&gt;
&lt;p&gt;In Apache Airflow 3.x, the web component of a plugin is registered as a &lt;a href="https://fastapi.tiangolo.com/" target="_blank" rel="noopener"&gt;FastAPI&lt;/a&gt; application through the &lt;code&gt;fastapi_apps&lt;/code&gt; attribute. In &lt;a href="https://github.com/aws-samples/sample-aws-mwaa-llm-powered-plugin/blob/main/plugins/task_analyzer_plugin.py" target="_blank" rel="noopener"&gt;task_analyzer_plugin.py&lt;/a&gt;, the &lt;code&gt;TaskAnalyzerPlugin&lt;/code&gt; class registers the FastAPI app under &lt;code&gt;/task-analyzer&lt;/code&gt; and adds a view to the task instance page:&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-python"&gt;class TaskAnalyzerPlugin(AirflowPlugin):
    name = "task_analyzer_plugin"

    fastapi_apps = [
        {
            "app": app,
            "url_prefix": "/task-analyzer",
            "name": "Task Analyzer",
        }
    ]

    external_views = [
        {
            "name": "Analyze Task",
            "href": "/task-analyzer/",
            "url_route": "task_analyzer_view",
            "destination": "task_instance",
        }
    ]&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Airflow automatically discovers any &lt;code&gt;AirflowPlugin&lt;/code&gt; subclass in the plugins folder. No registration call or configuration change is needed. On Amazon MWAA, the file is delivered inside &lt;code&gt;plugins.zip&lt;/code&gt; and extracted to &lt;code&gt;/usr/local/airflow/plugins/&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id="analysis-engine"&gt;Analysis engine&lt;/h3&gt;
&lt;p&gt;The analysis engine is the &lt;code&gt;POST /api/analyze-task&lt;/code&gt; endpoint in &lt;a href="https://github.com/aws-samples/sample-aws-mwaa-llm-powered-plugin/blob/main/plugins/task_analyzer_plugin.py" target="_blank" rel="noopener"&gt;task_analyzer_plugin.py&lt;/a&gt;. When you trigger an analysis, the endpoint performs the following steps:&lt;/p&gt;
&lt;ol type="1"&gt;
 &lt;li&gt;Retrieves AWS credentials from the &lt;code&gt;aws_default&lt;/code&gt; Airflow connection. To override, edit the &lt;code&gt;aws_default&lt;/code&gt; connection in the Airflow UI (&lt;strong&gt;Admin &amp;gt; Connections&lt;/strong&gt;).&lt;/li&gt;
 &lt;li&gt;Assembles a context dictionary from the request (task metadata, logs, DAG source).&lt;/li&gt;
 &lt;li&gt;Enriches the context with an operator-specific script through &lt;code&gt;fetch_and_add_operator_script&lt;/code&gt;.&lt;/li&gt;
 &lt;li&gt;Builds the prompt using the template in &lt;a href="https://github.com/aws-samples/sample-aws-mwaa-llm-powered-plugin/blob/main/plugins/task_analyzer/prompts.py" target="_blank" rel="noopener"&gt;prompts.py&lt;/a&gt;.&lt;/li&gt;
 &lt;li&gt;Invokes Amazon Bedrock and returns the structured analysis.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="operator-script-fetching"&gt;Operator script fetching&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;process_operator_script&lt;/code&gt; function in &lt;a href="https://github.com/aws-samples/sample-aws-mwaa-llm-powered-plugin/blob/main/plugins/task_analyzer/script_utils.py" target="_blank" rel="noopener"&gt;script_utils.py&lt;/a&gt; routes script retrieval based on operator type:&lt;/p&gt;
&lt;ul&gt;
 &lt;li&gt;&lt;strong&gt;External scripts (AWS Glue, Amazon EMR)&lt;/strong&gt; – The plugin calls the &lt;a href="https://docs.aws.amazon.com/glue/latest/dg/aws-glue-api.html" target="_blank" rel="noopener"&gt;AWS Glue API&lt;/a&gt; to look up the job definition, then reads the PySpark script from Amazon S3. Amazon EMR handlers follow the same pattern, extracting the script path from the step configuration or job driver.&lt;/li&gt;
 &lt;li&gt;&lt;strong&gt;Inline scripts (Amazon Athena, Amazon Redshift, BashOperator, PythonOperator, DBTOperator)&lt;/strong&gt; – The plugin reads the query or command directly from the task’s rendered template fields with no external API call.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The plugin implements &lt;strong&gt;smart fetching&lt;/strong&gt;: for external scripts, it only makes the Amazon S3 API call when the error message contains code-relevant patterns (such as SyntaxError, TypeError, or data type mismatch). Infrastructure errors like timeouts skip the script fetch entirely, minimizing unnecessary API calls.&lt;/p&gt;
&lt;h3 id="prompt-engineering"&gt;Prompt engineering&lt;/h3&gt;
&lt;p&gt;The prompt template in &lt;a href="https://github.com/aws-samples/sample-aws-mwaa-llm-powered-plugin/blob/main/plugins/task_analyzer/prompts.py" target="_blank" rel="noopener"&gt;prompts.py&lt;/a&gt; provides the foundation model with:&lt;/p&gt;
&lt;ul&gt;
 &lt;li&gt;Task metadata (DAG ID, task ID, run ID, state).&lt;/li&gt;
 &lt;li&gt;Error message and execution logs.&lt;/li&gt;
 &lt;li&gt;DAG source code.&lt;/li&gt;
 &lt;li&gt;Operator-specific script (when available).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The model produces a structured diagnostic report with root cause identification, step-by-step resolution, and prevention recommendations. Model IDs are configurable through Airflow Variables, so you can switch between Claude Sonnet and Claude Opus without redeploying the plugin.&lt;/p&gt;
&lt;h3 id="security-measures"&gt;Security measures&lt;/h3&gt;
&lt;p&gt;Before sending content to Amazon Bedrock, the plugin applies the following safeguards:&lt;/p&gt;
&lt;ul&gt;
 &lt;li&gt;&lt;strong&gt;Credential redaction&lt;/strong&gt; – The &lt;code&gt;sanitize_script&lt;/code&gt; function removes sensitive patterns (passwords, tokens, access keys) from scripts and logs.&lt;/li&gt;
 &lt;li&gt;&lt;strong&gt;Content truncation&lt;/strong&gt; – The &lt;code&gt;truncate_script&lt;/code&gt; function caps content size to stay within model context windows.&lt;/li&gt;
 &lt;li&gt;&lt;strong&gt;Path traversal prevention&lt;/strong&gt; – The &lt;code&gt;read_allowlisted_file&lt;/code&gt; function resolves canonical paths and verifies they reside within allowed base directories before reading any file.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For the full implementation, see &lt;a href="https://github.com/aws-samples/sample-aws-mwaa-llm-powered-plugin/blob/main/plugins/task_analyzer/script_utils.py" target="_blank" rel="noopener"&gt;script_utils.py&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Optional: PII detection and redaction.&lt;/strong&gt; The built-in &lt;code&gt;sanitize_script&lt;/code&gt; function targets credential patterns. If your logs or scripts might contain personally identifiable information (PII), consider adding a detection pass with &lt;a href="https://aws.amazon.com/comprehend/" target="_blank" rel="noopener"&gt;Amazon Comprehend&lt;/a&gt; before invoking Amazon Bedrock. The &lt;a href="https://docs.aws.amazon.com/comprehend/latest/dg/API_DetectPiiEntities.html" target="_blank" rel="noopener"&gt;DetectPiiEntities&lt;/a&gt; API returns the entity types (such as names, email addresses, or account numbers) and their character offsets. You can use these offsets to mask or obfuscate the spans before the context leaves your environment. This adds one API call and cost per analysis, so add it where your compliance requirements call for it. For guidance, see &lt;a href="https://docs.aws.amazon.com/comprehend/latest/dg/how-pii.html" target="_blank" rel="noopener"&gt;Detecting PII entities&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="deploy-the-plugin"&gt;Deploy the plugin&lt;/h2&gt;
&lt;p&gt;Follow these steps to deploy the plugin to your Amazon MWAA environment.&lt;/p&gt;
&lt;h3 id="step-1-clone-the-repository"&gt;Step 1: Clone the repository&lt;/h3&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-bash"&gt;git clone https://github.com/aws-samples/sample-aws-mwaa-llm-powered-plugin.git
cd sample-aws-mwaa-llm-powered-plugin&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h3 id="step-2-package-and-upload-to-amazon-s3"&gt;Step 2: Package and upload to Amazon S3&lt;/h3&gt;
&lt;p&gt;Create the &lt;code&gt;plugins.zip&lt;/code&gt; archive from the &lt;code&gt;plugins/&lt;/code&gt; directory and upload it to your Amazon MWAA S3 bucket:&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-bash"&gt;cd plugins
zip -r ../plugins.zip .
cd ..

aws s3 cp plugins.zip s3://&amp;lt;amzn-s3-demo-bucket&amp;gt;/plugins.zip

aws s3api head-object \
  --bucket &amp;lt;amzn-s3-demo-bucket&amp;gt; \
  --key plugins.zip \
  --query VersionId --output text&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Note the VersionId returned. You need it in the next step.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; This plugin requires only &lt;code&gt;fastapi&lt;/code&gt; and Boto3, both pre-installed on Amazon MWAA for Airflow 3.x. You don’t need a &lt;code&gt;requirements.txt&lt;/code&gt; file. Skipping the requirements file avoids package resolution conflicts that are a common cause of failed Amazon MWAA environment updates.&lt;/p&gt;
&lt;h3 id="step-3-update-the-amazon-mwaa-environment"&gt;Step 3: Update the Amazon MWAA environment&lt;/h3&gt;
&lt;p&gt;Update your environment to use the new plugin archive:&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-bash"&gt;aws mwaa update-environment \
  --name &amp;lt;your-environment-name&amp;gt; \
  --plugins-s3-path plugins.zip \
  --plugins-s3-object-version &amp;lt;version-id-from-step-2&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;The environment restarts automatically. This process typically takes 10–30 minutes. Monitor the status with:&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-bash"&gt;aws mwaa get-environment \
  --name &amp;lt;your-environment-name&amp;gt; \
  --query "Environment.{Status:Status,Plugins:PluginsS3Path}" --output json&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h3 id="step-4-configure-the-amazon-bedrock-connection"&gt;Step 4: Configure the Amazon Bedrock connection&lt;/h3&gt;
&lt;p&gt;On Amazon MWAA, the &lt;code&gt;aws_default&lt;/code&gt; connection exists by default and resolves to your environment’s execution role. In most cases, &lt;strong&gt;no action is needed&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;To override the Region, edit the &lt;code&gt;aws_default&lt;/code&gt; connection in the Airflow UI (&lt;strong&gt;Admin &amp;gt; Connections&lt;/strong&gt;) and set the &lt;strong&gt;Extra&lt;/strong&gt; field to:&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-json"&gt;{"region_name": "us-east-1"}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Leave login and password empty so the execution role is used.&lt;/p&gt;
&lt;h3 id="step-5-verify-the-deployment"&gt;Step 5: Verify the deployment&lt;/h3&gt;
&lt;p&gt;After the environment finishes updating, navigate to &lt;strong&gt;Admin &amp;gt; Plugins&lt;/strong&gt; in the Airflow UI. Verify that &lt;code&gt;task_analyzer_plugin&lt;/code&gt; appears in the list. The &lt;strong&gt;Analyze Task&lt;/strong&gt; entry is now available from any task instance view.&lt;/p&gt;
&lt;h2 id="test-the-solution"&gt;Test the solution&lt;/h2&gt;
&lt;p&gt;The repository includes &lt;a href="https://github.com/aws-samples/sample-aws-mwaa-llm-powered-plugin/tree/main/dags" target="_blank" rel="noopener"&gt;example DAGs&lt;/a&gt; that simulate failure scenarios across different operator types. To validate the deployment:&lt;/p&gt;
&lt;ol type="1"&gt;
 &lt;li&gt;Copy the dags/ directory contents to your Amazon MWAA S3 bucket’s DAGs folder:
  &lt;div class="hide-language"&gt;
   &lt;pre&gt;&lt;code class="language-bash"&gt;aws s3 cp dags/ s3://&amp;lt;amzn-s3-demo-bucket&amp;gt;/dags/ --recursive&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;/li&gt;
 &lt;li&gt;Wait for Amazon MWAA to sync the DAGs (typically 1–2 minutes).&lt;/li&gt;
 &lt;li&gt;In the Airflow UI, trigger one of the test DAGs (for example, &lt;code&gt;test_aws_sql_operators&lt;/code&gt;) and let the intentional failure occur.&lt;/li&gt;
 &lt;li&gt;Navigate to the failed task instance.&lt;/li&gt;
 &lt;li&gt;Choose &lt;strong&gt;Analyze Task&lt;/strong&gt; in the task instance view.&lt;/li&gt;
 &lt;li&gt;Review the generated analysis, which includes:
  &lt;ul&gt;
   &lt;li&gt;Root cause identification with file and line references.&lt;/li&gt;
   &lt;li&gt;Step-by-step resolution with code examples.&lt;/li&gt;
   &lt;li&gt;Prevention recommendations and monitoring suggestions.&lt;/li&gt;
  &lt;/ul&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The analysis typically completes within 5–10 seconds.&lt;/p&gt;
&lt;h2 id="cost-considerations"&gt;Cost considerations&lt;/h2&gt;
&lt;p&gt;The primary cost driver for this solution is Amazon Bedrock inference, which is billed by the number of input and output tokens each analysis consumes. Input tokens come from the task logs, DAG source, and operator script sent to the model. Output tokens come from the diagnostic report the model returns. Larger logs and scripts increase input tokens, and the model you select affects the per-token rate. For current per-model rates, see &lt;a href="https://aws.amazon.com/bedrock/pricing/" target="_blank" rel="noopener"&gt;Amazon Bedrock pricing&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;To help control cost, the plugin includes a caching mechanism that stores results keyed by a hash of the error context. Repeated analyses of the same failure pattern return cached results without invoking Amazon Bedrock again.&lt;/p&gt;
&lt;h2 id="best-practices"&gt;Best practices&lt;/h2&gt;
&lt;p&gt;When you deploy this solution in production, consider the following:&lt;/p&gt;
&lt;ul&gt;
 &lt;li&gt;&lt;strong&gt;IAM least privilege&lt;/strong&gt; – Grant only &lt;code&gt;bedrock:InvokeModel&lt;/code&gt; for your chosen model IDs and scope &lt;code&gt;s3:GetObject&lt;/code&gt; to specific bucket paths where your operator scripts reside. For guidance, see &lt;a href="https://docs.aws.amazon.com/mwaa/latest/userguide/mwaa-create-role.html" target="_blank" rel="noopener"&gt;Amazon MWAA execution role&lt;/a&gt;.&lt;/li&gt;
 &lt;li&gt;&lt;strong&gt;Data sanitization&lt;/strong&gt; – The plugin redacts credentials and truncates content before sending data to Amazon Bedrock. Store configuration values in &lt;a href="https://aws.amazon.com/secrets-manager/" target="_blank" rel="noopener"&gt;AWS Secrets Manager&lt;/a&gt; rather than hardcoding them in DAG source files.&lt;/li&gt;
 &lt;li&gt;&lt;strong&gt;Access control&lt;/strong&gt; – The plugin’s endpoints are protected by Airflow’s built-in authentication. For DAG-level access management at scale, see &lt;a href="https://aws.amazon.com/blogs/big-data/automated-tag-based-dag-permission-management-in-amazon-mwaa/" target="_blank" rel="noopener"&gt;Automated tag-based DAG permission management in Amazon MWAA&lt;/a&gt;.&lt;/li&gt;
 &lt;li&gt;&lt;strong&gt;Operational resilience&lt;/strong&gt; – Add retry logic and circuit breaker patterns around the Amazon Bedrock API call. Use &lt;a href="https://aws.amazon.com/cloudwatch/" target="_blank" rel="noopener"&gt;Amazon CloudWatch&lt;/a&gt; to monitor plugin performance and set alarms on failure rates.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="extending-the-solution"&gt;Extending the solution&lt;/h2&gt;
&lt;p&gt;You can extend this solution in the following ways:&lt;/p&gt;
&lt;ul&gt;
 &lt;li&gt;&lt;strong&gt;Proactive notifications&lt;/strong&gt; – Integrate with &lt;a href="https://aws.amazon.com/sns/" target="_blank" rel="noopener"&gt;Amazon Simple Notification Service&lt;/a&gt; (Amazon SNS) or Slack to deliver analyses automatically when failures occur.&lt;/li&gt;
 &lt;li&gt;&lt;strong&gt;Knowledge base integration&lt;/strong&gt; – Build a knowledge base of past analyses using &lt;a href="https://docs.aws.amazon.com/bedrock/latest/userguide/knowledge-base.html" target="_blank" rel="noopener"&gt;Amazon Bedrock Knowledge Bases&lt;/a&gt; for Retrieval Augmented Generation (RAG) powered recommendations that learn from your organization’s historical failures.&lt;/li&gt;
 &lt;li&gt;&lt;strong&gt;Additional operator support&lt;/strong&gt; – Add handlers for custom operators specific to your organization, such as proprietary data connectors or internal platform integrations.&lt;/li&gt;
 &lt;li&gt;&lt;strong&gt;Automated remediation&lt;/strong&gt; – For well-understood failure patterns, trigger automated fixes such as restarting tasks with adjusted resource configurations.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="clean-up"&gt;Clean up&lt;/h2&gt;
&lt;p&gt;To remove the plugin from your environment:&lt;/p&gt;
&lt;ol type="1"&gt;
 &lt;li&gt;Delete the plugin archive from Amazon S3:
  &lt;div class="hide-language"&gt;
   &lt;pre&gt;&lt;code class="language-bash"&gt;aws s3 rm s3://&amp;lt;amzn-s3-demo-bucket&amp;gt;/plugins.zip&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;/li&gt;
 &lt;li&gt;Update your Amazon MWAA environment to remove the plugin reference, then wait for the environment to restart.&lt;/li&gt;
 &lt;li&gt;Optionally, remove the Amazon Bedrock permissions from your execution role if they are no longer needed.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;In this post, we showed you how to deploy an LLM-powered DAG failure analysis plugin for Amazon MWAA using Amazon Bedrock. The operator-aware context collection differentiates this approach from generic log analyzers. By fetching the actual code from AWS Glue, Amazon EMR, and other services, the foundation model provides precise, actionable recommendations with specific line references.&lt;/p&gt;
&lt;p&gt;To get started, clone the &lt;a href="https://github.com/aws-samples/sample-aws-mwaa-llm-powered-plugin" target="_blank" rel="noopener"&gt;sample-aws-mwaa-llm-powered-plugin&lt;/a&gt; repository, deploy it to a development Amazon MWAA environment, and test with the included example DAGs. As your team builds confidence in the analysis quality, roll it out to production environments where it serves as the first line of investigation for any pipeline failure.&lt;/p&gt;
&lt;p style="clear: both"&gt;&lt;/p&gt;
&lt;hr style="width: 100%"&gt;
&lt;h2&gt;About the authors&lt;/h2&gt;
&lt;footer&gt;
 &lt;div class="blog-author-box" style="padding-top: 2.0em"&gt;
  &lt;div class="blog-author-image" style="margin-right: 1.0em"&gt;
   &lt;img loading="lazy" class="alignnone size-full" src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/22/BDB-5613-2.jpg" alt="Sushant Samantaray" width="100" height="133"&gt;
  &lt;/div&gt;
  &lt;h3 class="lb-h4"&gt;Sushant Samantaray&lt;/h3&gt;
  &lt;p style="overflow: hidden"&gt;&lt;a href="https://www.linkedin.com/in/sushantasamantaray/" target="_blank" rel="noopener"&gt;Sushant&lt;/a&gt; is a Sr.&amp;nbsp;Delivery Consultant at AWS, bringing 19 years of industry experience with a focus on Data Analytics and Generative AI/Agentic AI solutions. He works closely with enterprise customers to design and deliver innovative solutions across Big Data, Generative AI, and Agentic AI, leveraging AWS native services, partner offerings, and open-source technologies. A passionate technologist and problem solver at heart, he balances his professional life with watching and playing sports and spending quality time with family.&lt;/p&gt;
 &lt;/div&gt;
 &lt;div class="blog-author-box" style="padding-top: 2.0em"&gt;
  &lt;div class="blog-author-image" style="margin-right: 1.0em"&gt;
   &lt;img loading="lazy" class="alignnone size-full" src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/22/BDB-5613-3.jpg" alt="Parameswara Reddy Gajjela" width="100" height="133"&gt;
  &lt;/div&gt;
  &lt;h3 class="lb-h4"&gt;Parameswara Reddy Gajjela&lt;/h3&gt;
  &lt;p style="overflow: hidden"&gt;&lt;a href="https://www.linkedin.com/in/parameswara/" target="_blank" rel="noopener"&gt;Parameswara&lt;/a&gt; is a Delivery Consultant at AWS with 11+ years of experience in Data Analytics. He works closely with enterprise customers to architect innovative, end-to-end Big Data and Agentic AI solutions powered by AWS native services, partner ecosystems, and open-source technologies. His areas of expertise include modern DataLake and Data Warehouse migration and implementations on AWS.&lt;/p&gt;
 &lt;/div&gt;
 &lt;div class="blog-author-box" style="padding-top: 2.0em"&gt;
  &lt;div class="blog-author-image" style="margin-right: 1.0em"&gt;
   &lt;img loading="lazy" class="alignnone size-full" src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/22/BDB-5613-4.jpg" alt="Kamen Sharlandjiev" width="100" height="133"&gt;
  &lt;/div&gt;
  &lt;h3 class="lb-h4"&gt;Kamen Sharlandjiev&lt;/h3&gt;
  &lt;p style="overflow: hidden"&gt;Kamen is a Pr. Big Data and ETL Solutions Architect, MWAA and AWS Glue ETL expert. He’s on a mission to make life easier for customers who are facing complex data integration and orchestration challenges. His secret weapon? Fully managed AWS services that can get the job done with minimal effort. Follow Kamen on &lt;a href="https://www.linkedin.com/comm/mynetwork/discovery-see-all?usecase=PEOPLE_FOLLOWS&amp;amp;followMember=ksharlandjiev" target="_blank" rel="noopener"&gt;LinkedIn&lt;/a&gt; to keep up to date with the latest MWAA and AWS Glue features and news!&lt;/p&gt;
 &lt;/div&gt;
&lt;/footer&gt;</content:encoded>
					
		
		
			</item>
		<item>
		<title>Announcing Spark Connect on Amazon EMR on EKS: Interactive PySpark development, anywhere</title>
		<link>https://aws.amazon.com/blogs/big-data/announcing-spark-connect-on-amazon-emr-on-eks/</link>
		
		<dc:creator><![CDATA[Amit Maindola]]></dc:creator>
		<pubDate>Tue, 29 Sep 2026 16:12:17 +0000</pubDate>
				<category><![CDATA[Advanced (300)]]></category>
		<category><![CDATA[Amazon EMR on EKS]]></category>
		<category><![CDATA[Announcements]]></category>
		<guid isPermaLink="false">af075814e422073577508ef31418b4a34b391335</guid>

					<description>Announcing Spark Connect on Amazon EMR on EKS: build, test, and debug Spark applications from VS Code, PyCharm, Jupyter notebooks, Amazon SageMaker Unified Studio, or dbt, while running full-scale Spark operations on your existing Amazon EKS clusters.</description>
										<content:encoded>&lt;p&gt;Today, we’re announcing support for Spark Connect on &lt;a href="https://docs.aws.amazon.com/emr/latest/EMR-on-EKS-DevelopmentGuide/emr-eks.html" target="_blank" rel="noopener"&gt;Amazon EMR on EKS&lt;/a&gt;, starting from EMR release 7.14 (Apache Spark 3.5.8) and emr-spark-8.1 (Apache Spark 4.1.1). You can now build, test, and debug Spark applications from your preferred tools, such as &lt;a href="https://code.visualstudio.com/" target="_blank" rel="noopener"&gt;VS Code&lt;/a&gt;, &lt;a href="https://www.jetbrains.com/pycharm/" target="_blank" rel="noopener"&gt;PyCharm&lt;/a&gt;, Jupyter notebooks, &lt;a href="https://docs.aws.amazon.com/sagemaker-unified-studio/latest/userguide/what-is-sagemaker-unified-studio.html" target="_blank" rel="noopener"&gt;Amazon SageMaker Unified Studio&lt;/a&gt;. At the same time, your full-scale Spark operations run on &lt;a href="https://docs.aws.amazon.com/eks/latest/userguide/what-is-eks.html" target="_blank" rel="noopener"&gt;Amazon Elastic Kubernetes Service (Amazon EKS)&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Deploying Spark applications from a local development environment to a remote Amazon EKS cluster often means dealing with environment differences, dependency conflicts, and performance gaps at scale. Spark Connect removes this friction. It separates your application client from the Spark server, so you develop and debug locally while Spark Connect routes your operations to a scalable Spark cluster running on Amazon EKS.&lt;/p&gt;
&lt;p&gt;This client-server architecture supports a range of use cases, including interactive development from notebooks and IDEs, embedded Spark in web services, and continuous integration and continuous delivery (CI/CD) data-quality tests. All of these run on your existing EKS infrastructure. Each Spark Connect session uses its own &lt;a href="https://docs.aws.amazon.com/emr/latest/EMR-on-EKS-DevelopmentGuide/creating-job-execution-role.html" target="_blank" rel="noopener"&gt;AWS Identity and Access Management (IAM) execution role&lt;/a&gt;, custom tags, and cost tracking. For more information, see the &lt;a href="https://docs.aws.amazon.com/emr/latest/EMR-on-EKS-DevelopmentGuide/emr-eks-spark-connect.html" target="_blank" rel="noopener"&gt;Amazon EMR on EKS documentation&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Here are two demonstrations of using Spark Connect in Amazon SageMaker Unified Studio Notebooks and in a VS Code local IDE:&lt;/p&gt;
&lt;p&gt;Amazon SageMaker Unified Studio&amp;nbsp;Notebooks demo:&lt;/p&gt;
&lt;div style="width: 640px;" class="wp-video"&gt;
 &lt;video class="wp-video-shortcode" id="video-94546-1" width="640" height="360" preload="metadata" controls="controls"&gt;&lt;source type="video/mp4" src="https://d2908q01vomqb2.cloudfront.net/artifacts/DBSBlogs/smus-connect-recording.mp4?_=1"&gt;&lt;/video&gt;
&lt;/div&gt;
&lt;figure&gt;
 &lt;figcaption aria-hidden="true"&gt;Demo 1: Spark Connect running in an Amazon SageMaker Unified Studio notebook&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Local IDE demo:&lt;/p&gt;
&lt;div style="width: 640px;" class="wp-video"&gt;
 &lt;video class="wp-video-shortcode" id="video-94546-2" width="640" height="360" preload="metadata" controls="controls"&gt;&lt;source type="video/mp4" src="https://d2908q01vomqb2.cloudfront.net/artifacts/DBSBlogs/ide_spark_connect.mp4?_=2"&gt;&lt;/video&gt;
&lt;/div&gt;
&lt;figure&gt;
 &lt;figcaption aria-hidden="true"&gt;Demo 2: Spark Connect running in a local VS Code IDE session&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;For a runnable end-to-end example in an IDE, try the &lt;a href="https://github.com/aws-samples/aws-emr-utilities/blob/main/utilities/emr-spark-connect-client/examples/simple_demo.ipynb" target="_blank" rel="noopener"&gt;Spark Connect sample notebook&lt;/a&gt; in the &lt;a href="https://github.com/aws-samples/aws-emr-utilities/tree/main/utilities" target="_blank" rel="noopener"&gt;aws-emr-utilities&lt;/a&gt; repository. It includes a client wrapper solution, built by AWS architects, for simplified connectivity:&lt;/p&gt;
&lt;div style="width: 640px;" class="wp-video"&gt;
 &lt;video class="wp-video-shortcode" id="video-94546-3" width="640" height="360" preload="metadata" controls="controls"&gt;&lt;source type="video/mp4" src="https://d2908q01vomqb2.cloudfront.net/artifacts/DBSBlogs/wrapper-emr-eks-spark-connect-demo.mp4?_=3"&gt;&lt;/video&gt;
&lt;/div&gt;
&lt;figure&gt;
 &lt;figcaption aria-hidden="true"&gt;Demo 3: Spark Connect client wrapper connecting to Amazon EMR on EKS from a local IDE&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id="how-spark-connect-works-on-amazon-emr-on-eks"&gt;How Spark Connect works on Amazon EMR on EKS&lt;/h2&gt;
&lt;p&gt;Spark Connect uses a client-server architecture that separates application code from the Spark engine:&lt;/p&gt;
&lt;ol type="1"&gt;
 &lt;li&gt;&lt;strong&gt;Client&lt;/strong&gt; – A lightweight PySpark library running in your environment (such as an IDE or notebook). It doesn’t need Spark installed, direct access to data, or resources sized for the workload.&lt;/li&gt;
 &lt;li&gt;&lt;strong&gt;Connection&lt;/strong&gt; (EMR managed endpoint) – The client sends Spark operations over a secure gRPC/TLS channel to the Spark Connect server.&lt;/li&gt;
 &lt;li&gt;&lt;strong&gt;Server&lt;/strong&gt; – Runs Spark pods in your Amazon EMR on EKS namespace, starting from a minimum of two executors (adjustable) with autoscaling. The server performs Spark operations using the EKS compute resources and accesses data stores, such as an Amazon Simple Storage Service (Amazon S3) bucket, through job execution roles.&lt;/li&gt;
 &lt;li&gt;&lt;strong&gt;Results&lt;/strong&gt; – The server streams query results back to the client through gRPC as Apache Arrow-encoded row batches.&lt;/li&gt;
&lt;/ol&gt;
&lt;div style="width: 810px" class="wp-caption alignnone"&gt;
 &lt;a href="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/23/BDB-6122-4.png" target="_blank" rel="noopener"&gt;&lt;img src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/23/BDB-6122-4.png" alt="Client-server data flow from a local PySpark client through a gRPC channel to Spark pods on Amazon EMR on EKS" width="800"&gt;&lt;/a&gt;
 &lt;p class="wp-caption-text"&gt;Figure 1: Spark Connect’s client-server architecture&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;On endpoint creation, Amazon EMR on EKS launches the Spark Connect server as pods on EKS and returns an &lt;a href="https://aws.amazon.com/elasticloadbalancing/" target="_blank" rel="noopener"&gt;Elastic Load Balancing (ELB)&lt;/a&gt;-backed endpoint and a short-lived token. You don’t need to provision any server or networking manually. Because the Spark Connect server runs on the EKS cluster you already operate, it inherits the node types, container images, and Spark configurations. What you see while developing Spark applications on the client side is what runs in the EKS environment at scale.&lt;/p&gt;
&lt;p&gt;To provide a secure, simplified experience, Amazon EMR on EKS provisions two additional components on first use of Spark Connect on the EKS cluster:&lt;/p&gt;
&lt;div style="width: 810px" class="wp-caption alignnone"&gt;
 &lt;a href="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/23/BDB-6122-5.png" target="_blank" rel="noopener"&gt;&lt;img src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/23/BDB-6122-5.png" alt="Shared Envoy authentication-proxy router and Secret Agent service on the EKS cluster" width="800"&gt;&lt;/a&gt;
 &lt;p class="wp-caption-text"&gt;Figure 2: Shared Envoy router and Secret Agent service on the EKS cluster&lt;/p&gt;
&lt;/div&gt;
&lt;ul&gt;
 &lt;li&gt;&lt;strong&gt;Managed authentication-proxy router&lt;/strong&gt; – a shared Envoy router with three replicas by default (adjustable), fronted by a &lt;a href="https://docs.aws.amazon.com/elasticloadbalancing/latest/network/introduction.html" target="_blank" rel="noopener"&gt;Network Load Balancer&lt;/a&gt; (NLB). It routes client traffic to the correct server pods, terminates TLS, and validates the session token. One router serves Spark Connect endpoints on the EKS cluster.&lt;/li&gt;
 &lt;li&gt;&lt;strong&gt;Secret Agent service&lt;/strong&gt; – a lightweight, long-running pod that manages the short-lived credentials for session authentication. One service per EMR security configuration.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These components are long-running and shared across endpoints. Amazon EMR on EKS creates them automatically with the first endpoint on the cluster. Because the router is cluster-scoped and Secret Agent is namespace-scoped, deleting a managed endpoint doesn’t remove them. They keep running so that new endpoints can start within a minute. The router’s replica count is tunable. Scale down for non-production environments to reduce cost or scale up for higher throughput.&lt;/p&gt;
&lt;p&gt;To fully remove these components:&lt;/p&gt;
&lt;ul&gt;
 &lt;li&gt;Terminate all active managed endpoints and their virtual cluster that reference the Secret Agent’s security configuration, then delete the security configuration.&lt;/li&gt;
 &lt;li&gt;Once the last session-enabled virtual cluster is deleted, the authentication-proxy router and its underly resources, including the NLB and VPC endpoint, are removed automatically.&lt;/li&gt;
 &lt;li&gt;Alternatively, delete the EKS cluster to remove all in-cluster components at once.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="why-use-spark-connect-on-amazon-emr-on-eks"&gt;Why use Spark Connect on Amazon EMR on EKS&lt;/h2&gt;
&lt;p&gt;With Amazon EMR on EKS, teams can run Spark alongside other applications on shared Kubernetes clusters with existing infrastructure, operational tooling, and system expertise. Spark Connect extends that value to interactive, embedded, and self-service Spark workloads. Your client stays lightweight while Spark code runs in governed, scalable server pods on EKS.&lt;/p&gt;
&lt;h3 id="interactive-development-on-shared-kubernetes-clusters"&gt;Interactive development on shared Kubernetes clusters&lt;/h3&gt;
&lt;p&gt;Data engineers and scientists iterate on Spark code cell-by-cell in notebooks or local IDEs. The Spark engine runs remotely on EKS, so validation runs on the same engine as your batch workloads. After validation on the Spark Connect client, the same Spark code deploys as a batch StartJobRun with no changes.&lt;/p&gt;
&lt;p&gt;Spark Connect sessions run as pods on your existing cluster. They reuse your &lt;a href="https://docs.aws.amazon.com/eks/latest/userguide/default-roles-users.html" target="_blank" rel="noopener"&gt;EKS RBAC&lt;/a&gt;, network policies, node autoscaling, and observability stack (Prometheus, Grafana, Amazon CloudWatch Container Insights). There are no separate compute and monitoring layers to operate.&lt;/p&gt;
&lt;h3 id="embedded-spark-in-applications-and-services"&gt;Embedded Spark in applications and services&lt;/h3&gt;
&lt;p&gt;The Spark Connect client is a compact PySpark library. Teams can embed Spark operations directly into Python applications such as web services, dashboards, automation scripts, or backend APIs. The heavy processing runs on EKS while the application stays lightweight.&lt;/p&gt;
&lt;p&gt;Teams can also expose Spark Connect as a self-service capability on their internal application. Business users submit Spark SQL scripts from a web UI. The compute runs on Spark Connect server on EKS, so the team manages capacity, security, and upgrades centrally.&lt;/p&gt;
&lt;h3 id="multi-tenant-data-exploration-with-governance"&gt;Multi-tenant data exploration with governance&lt;/h3&gt;
&lt;p&gt;Each Spark Connect session uses the data user’s IAM permissions that you configure, limiting their access to authorized AWS services, data lake tables, and S3 paths. Every session carries tags with user, project, endpoint and virtual cluster IDs, feeding directly into billing and compliance reports. Meanwhile, data producers maintain guardrails on source data without blocking self-service exploration.&lt;/p&gt;
&lt;p&gt;To manage resource consumption across teams, Amazon EMR on EKS virtual clusters provide namespace-level isolation. Each tenant binds their Spark Connect endpoints to a virtual cluster (a namespace) with independent IAM roles. Using &lt;a href="https://kubernetes.io/docs/concepts/policy/resource-quotas/" target="_blank" rel="noopener"&gt;resource quotas&lt;/a&gt; and &lt;a href="https://kubernetes.io/docs/concepts/policy/limit-range/" target="_blank" rel="noopener"&gt;limit ranges&lt;/a&gt; on EKS, you can protect each virtual cluster by controlling the compute resources that Spark Connect sessions can consume. Importantly, activating &lt;a href="https://docs.aws.amazon.com/guidance/latest/cloud-intelligence-dashboards/scad-containers-dashboard-data-on-eks.html" target="_blank" rel="noopener"&gt;EKS split-cost allocation&lt;/a&gt; tags helps with chargeback reporting in a multi-tenant environment.&lt;/p&gt;
&lt;h3 id="reusable-container-images-and-scalable-deployment"&gt;Reusable container images and scalable deployment&lt;/h3&gt;
&lt;p&gt;Teams often maintain custom container images with proprietary libraries, including internal feature stores, compliance toolkits, UDFs, or machine learning (ML) frameworks. With Spark Connect on Amazon EMR on EKS, teams reuse those same images as the Spark runtime for interactive sessions. No separate dependency lists needed. The same image works for both batch jobs and Spark Connect sessions.&lt;/p&gt;
&lt;p&gt;Beyond the image itself, you can control Spark pod scheduling in Amazon EMR on EKS through pod templates and &lt;a href="https://docs.aws.amazon.com/emr-on-eks/latest/APIReference/API_CreateManagedEndpoint.html" target="_blank" rel="noopener"&gt;managed endpoint API&lt;/a&gt;s, scaling across your environment. For example, you can:&lt;/p&gt;
&lt;ul&gt;
 &lt;li&gt;Pin server pods to specific node types through pod templates. For example, Spot for cost savings.&lt;/li&gt;
 &lt;li&gt;Apply Spark Dynamic Resource allocation (DRA) to right-size each interactive session.&lt;/li&gt;
 &lt;li&gt;Use GPU node pools for accelerated Spark RAPIDS or ML.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-bash"&gt;cat &amp;gt; /tmp/spark-connect-endpoint.json &amp;lt;&amp;lt; EOF
{
  "name": "spark-connect-custom-config",
  "virtualClusterId": "$VC_ID",
  "type": "SPARK_CONNECT",
  "releaseLabel": "emr-7.14.0-latest",
  "executionRoleArn": "$ROLE_ARN",
  "configurationOverrides": {
    "applicationConfiguration": [{
      "classification": "spark-defaults",
      "properties": {
        "spark.kubernetes.container.image": "${CUSTOM_IMAGE_URI}",
        "spark.kubernetes.executor.podTemplateFile": "s3://$S3BUCKET/exec-pod-template.yaml",
        "spark.kubernetes.node.selector.karpenter.sh/nodepool": "gpu-pool",
        "spark.dynamicAllocation.enabled": "true",
        "spark.dynamicAllocation.minExecutors": "0"
      }
    }]
  }
}
EOF

aws emr-containers create-managed-endpoint \
--cli-input-json file:///tmp/spark-connect-endpoint.json&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h3 id="multi-cluster-multi-region-and-hybrid-architectures"&gt;Multi-cluster, multi-Region, and hybrid architectures&lt;/h3&gt;
&lt;p&gt;Enterprises running EKS clusters across multiple AWS accounts, AWS Regions, or hybrid environments with on-premises Kubernetes can use Spark Connect to query data wherever it’s processed. The lightweight client only needs to reach the Spark Connect endpoint, not the underlying S3 buckets or &lt;a href="https://docs.aws.amazon.com/prescriptive-guidance/latest/serverless-etl-aws-glue/aws-glue-data-catalog.html" target="_blank" rel="noopener"&gt;AWS Glue data catalogs&lt;/a&gt;. This means no VPC peering or direct network paths to every data store.&lt;/p&gt;
&lt;p&gt;The client-server split is the core architectural advantage of Spark Connect on Amazon EMR on EKS. A developer on a laptop behind a VPN, a CI/CD deployment pipeline in a centralized service account, or an Airflow DAG orchestrating across Regions can all connect to a remote Spark server on EKS. This works regardless of where the client itself runs. This decoupling simplifies cross-Region or cross-account analytics without duplicating data or requiring direct access to each data store.&lt;/p&gt;
&lt;h2 id="getting-started"&gt;Getting started&lt;/h2&gt;
&lt;p&gt;To create a Spark Connect endpoint on Amazon EMR on EKS, complete the following steps:&lt;/p&gt;
&lt;ol&gt;
 &lt;li&gt;Create EMR namespaces on EKS.&lt;/li&gt;
 &lt;li&gt;Create an EMR security configuration.&lt;/li&gt;
 &lt;li&gt;Create a virtual cluster with the security configuration.&lt;/li&gt;
 &lt;li&gt;Create a Spark Connect managed endpoint.&lt;/li&gt;
 &lt;li&gt;Obtain a session token.&lt;/li&gt;
 &lt;li&gt;Connect from your application.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="prerequisites"&gt;Prerequisites&lt;/h3&gt;
&lt;p&gt;To proceed with this post, make sure you have the following:&lt;/p&gt;
&lt;ul&gt;
 &lt;li&gt;An active AWS account &lt;a href="https://docs.aws.amazon.com/emr/latest/EMR-on-EKS-DevelopmentGuide/emr-eks-spark-connect.html#emr-eks-spark-connect-permissions"&gt;with permissions to create Amazon EMR on EKS resources&lt;/a&gt;.&lt;/li&gt;
 &lt;li&gt;An Amazon EKS cluster&lt;/li&gt;
 &lt;li&gt;&lt;a href="https://docs.aws.amazon.com/eks/latest/userguide/lbc-helm.html" target="_blank" rel="noopener"&gt;An AWS Load Balancer Controller&lt;/a&gt; installed on your EKS cluster.&lt;/li&gt;
 &lt;li&gt;AWS Command Line Interface (AWS CLI) 2.x &amp;gt;=2.35.23, boto3 &amp;gt;=1.43.48.&lt;/li&gt;
 &lt;li&gt;pyspark[connect]==3.5.8 in Python 3.8+ environment (client library for EMR 7.14).&lt;/li&gt;
 &lt;li&gt;Or pyspark[connect]==4.1.1 in Python 3.10+ environment (client library for emr-spark-8.1).&lt;/li&gt;
 &lt;li&gt;&lt;a href="https://docs.aws.amazon.com/emr/latest/EMR-on-EKS-DevelopmentGuide/creating-job-execution-role.html" target="_blank" rel="noopener"&gt;A job execution IAM role&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="step-1-create-emr-namespaces"&gt;Step 1: Create EMR namespaces&lt;/h3&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-bash"&gt;# set environment variables
export EKS_CLUSTER_NAME=my-eks-cluster
export USER_NAMESPACE=spark-connect-1
export SYS_NAMESPACE=spark-connect-1-system
export AWS_REGION=us-west-2
# connect to your EKS cluster
aws eks update-kubeconfig --name $EKS_CLUSTER_NAME --region $AWS_REGION
kubectl create namespace $USER_NAMESPACE
kubectl create namespace $SYS_NAMESPACE&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h3 id="step-2-create-a-security-configuration"&gt;Step 2: Create a security configuration&lt;/h3&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-bash"&gt;cat &amp;gt; /tmp/sec-config.json &amp;lt;&amp;lt; EOF
{
  "name": "spark-connect-1-sc",
  "securityConfigurationData": {
    "authenticationConfiguration": {
      "identityCenterConfiguration": { "enableIdentityCenter": false }
    }
  },
  "containerProvider": {
    "type": "EKS",
    "id": "$EKS_CLUSTER_NAME",
    "info": { "eksInfo": { "namespace": "$SYS_NAMESPACE" } }
  }
}
EOF

SEC_CONFIG_ID=$(aws emr-containers create-security-configuration \
--region $AWS_REGION \
--cli-input-json file:///tmp/sec-config.json \
--query id \
--output text)
echo "Security Configuration ID: $SEC_CONFIG_ID"&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h3 id="step-3-create-a-virtual-cluster-with-the-security-configuration"&gt;Step 3: Create a virtual cluster with the security configuration&lt;/h3&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-bash"&gt;cat &amp;gt; /tmp/vc.json &amp;lt;&amp;lt; EOF
{
  "name": "spark-connect-demo",
  "containerProvider": {
    "id": "$EKS_CLUSTER_NAME",
    "type": "EKS",
    "info": {"eksInfo": {"namespace": "$USER_NAMESPACE"}}
  },
  "securityConfigurationId": "$SEC_CONFIG_ID",
  "sessionEnabled": true
}
EOF

VC_ID=$(aws emr-containers create-virtual-cluster \
--region $AWS_REGION \
--cli-input-json file:///tmp/vc.json \
--query 'id' \
--output text)
# validate the virtual cluster
echo "Virtual Cluster ID: $VC_ID"
aws emr-containers describe-virtual-cluster --region $AWS_REGION --id $VC_ID&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h3 id="step-4-create-a-spark-connect-managed-endpoint"&gt;Step 4: Create a Spark Connect managed endpoint&lt;/h3&gt;
&lt;p&gt;Start an interactive session on your virtual cluster. Provide a job execution role that grants the session access to your data sources.&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-bash"&gt;# reuse an existing execution role
ROLE_ARN="arn:aws:iam::YOUR_ACCOUNT_ID:role/EMRonEKSExecutionRole"
cat &amp;gt; /tmp/spark-connect-endpoint.json &amp;lt;&amp;lt; EOF
{
  "name": "spark-connect-demo",
  "virtualClusterId": "$VC_ID",
  "type": "SPARK_CONNECT",
  "releaseLabel": "emr-7.14.0-latest",
  "executionRoleArn": "$ROLE_ARN",
  "sessionIdleTimeoutInMinutes": 1440,
  "configurationOverrides": {
    "applicationConfiguration": [{
      "classification": "spark-defaults",
      "properties": {
        "spark.dynamicAllocation.enabled": "true",
        "spark.dynamicAllocation.minExecutors": "0",
        "spark.dynamicAllocation.maxExecutors": "2"
      }
    }]
  }
}
EOF

EP_ID=$(aws emr-containers create-managed-endpoint \
--region $AWS_REGION \
--cli-input-json file:///tmp/spark-connect-endpoint.json \
--query 'id' \
--output text)
# Validate
echo "Endpoint ID: $EP_ID"
export EP_URL=$(aws emr-containers describe-managed-endpoint \
--region $AWS_REGION \
--virtual-cluster-id $VC_ID \
--id $EP_ID \
--query 'endpoint.authProxyUrl' \
--output text)
echo "Endpoint URL: $EP_URL"&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div style="width: 810px" class="wp-caption alignnone"&gt;
 &lt;a href="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/23/BDB-6122-6.png" target="_blank" rel="noopener"&gt;&lt;img src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/23/BDB-6122-6.png" alt="Managed endpoint creation output showing the endpoint ID and endpoint URL" width="800"&gt;&lt;/a&gt;
 &lt;p class="wp-caption-text"&gt;Figure 3: Managed endpoint creation output with the endpoint ID and URL&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;You can optionally pass some custom configuration overrides and tags:&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-bash"&gt;aws emr-containers create-managed-endpoint \
--type SPARK_CONNECT \
--virtual-cluster-id $VC_ID \
--name more-endpoint \
--execution-role-arn $ROLE_ARN \
--release-label emr-7.14.0-latest \
--configuration-overrides '{
"applicationConfiguration": [{
"classification": "spark-defaults",
"properties": {
"spark.executor.instances": "1",
"spark.executor.memory": "4g",
"spark.executor.cores": "1",
"spark.sql.extensions": "org.apache.iceberg.spark.extensions.IcebergSparkSessionExtensions"
}
}]
}' \
--tags '{
"team": "data-engineering",
"project": "customer-analytics"
}'&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h3 id="step-5-obtain-a-session-token"&gt;Step 5: Obtain a session token&lt;/h3&gt;
&lt;p&gt;Request a session token after the managed endpoint is active:&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-bash"&gt;# get a session token with a 12-hour expiry (adjustable)
export TOKEN=$(aws emr-containers get-managed-endpoint-session-credentials \
--region $AWS_REGION \
--virtual-cluster-identifier $VC_ID \
--endpoint-identifier $EP_ID \
--execution-role-arn $ROLE_ARN \
--credential-type TOKEN \
--duration-in-seconds 43200 \
--query 'credentials.token' \
--output text)
echo "Session Token: $TOKEN"&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Security note&lt;/strong&gt;: Communication between your environment and the Spark Connect server is encrypted using TLS. The authentication token is time-limited (15 minutes by default). For long-running sessions, refresh the token periodically by calling &lt;code&gt;get-managed-endpoint-session-credentials&lt;/code&gt; again. Consider using AWS Secrets Manager to store and retrieve tokens programmatically.&lt;/p&gt;
&lt;h3 id="step-6-connect-from-your-application"&gt;Step 6: Connect from your application&lt;/h3&gt;
&lt;p&gt;Use the returned endpoint URL and token to connect from a PySpark-compatible environment. The following Python code shows how to establish a Spark Connect session:&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-python"&gt;import os
from pyspark.sql import SparkSession
session_endpoint = os.environ["EP_URL"]
auth_token = os.environ["TOKEN"]
spark_conn_url = (f"{session_endpoint};use_ssl=true;x-aws-proxy-auth={auth_token}")
spark = SparkSession.builder
.remote(spark_conn_url)
.getOrCreate()
# verify the connection
print(f"Connected remotely! Spark version: {spark.version}")
# query data through the AWS Glue Data Catalog
df = spark.sql("SELECT * FROM my_catalog.my_database.my_table LIMIT 10")
df.show()&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;After you’re connected, you can:&lt;/p&gt;
&lt;ul&gt;
 &lt;li&gt;&lt;strong&gt;Debug interactively&lt;/strong&gt; – Set breakpoints, inspect DataFrames, and step through Spark code in your IDE or notebook while the operations run remotely on EKS.&lt;/li&gt;
 &lt;li&gt;&lt;strong&gt;Combine local and remote processing&lt;/strong&gt; – Pull query results back to the client as a pandas or PyArrow DataFrame for local analysis, visualization, or ML (scikit-learn, notebook widgets), then push further Spark operations back to the server in the same session. Heavy processing stays on Amazon EMR on EKS. Only the results you request cross the wire.&lt;/li&gt;
 &lt;li&gt;&lt;strong&gt;Reconnect without losing state&lt;/strong&gt; – A managed endpoint runs independently of single clients for a configurable idle timeout (default: 60 minutes). Your Spark session, cached data, and temporary views are preserved on the server between connections. When a session token expires (default: 15 minutes, configurable up to 12 hours), request a new token and reconnect to the same endpoint to resume where you left off.&lt;/li&gt;
 &lt;li&gt;&lt;strong&gt;Reuse across workload types&lt;/strong&gt; – The same client connection pattern works everywhere Python runs: notebooks, IDEs, batch scripts, Airflow operators, or web services. One endpoint, one connection pattern, many workload types.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="validation"&gt;Validation&lt;/h3&gt;
&lt;p&gt;After you create the endpoint, verify that the Spark Connect server is running and reachable through Amazon EMR on EKS API and standard Kubernetes tooling:&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-bash"&gt;# get endpoint status
aws emr-containers describe-managed-endpoint --virtual-cluster-id $VC_ID --id $EP_ID
# inspect the server pods (driver + executors) in your namespace
kubectl get pods -n $USER_NAMESPACE -l "emr-containers.amazonaws.com/managed-endpoint-id=$EP_ID"
# View driver logs
kubectl logs -n $USER_NAMESPACE &amp;lt;driver-pod-name&amp;gt; -c spark-kubernetes-driver&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div style="width: 810px" class="wp-caption alignnone"&gt;
 &lt;a href="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/23/BDB-6122-7.png" target="_blank" rel="noopener"&gt;&lt;img src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/23/BDB-6122-7.png" alt="Terminal output showing endpoint status and the running driver and executor pods" width="800"&gt;&lt;/a&gt;
 &lt;p class="wp-caption-text"&gt;Figure 4: Endpoint status and the running driver and executor pods&lt;/p&gt;
&lt;/div&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-bash"&gt;# to view the live Spark UI, port-forward your driver pod:
DRIVER_POD=$(kubectl get pods -n $USER_NAMESPACE \
-l "emr-containers.amazonaws.com/managed-endpoint-id=$EP_ID,emr-containers.amazonaws.com/component=driver" \
-o name)
kubectl port-forward -n $USER_NAMESPACE "$DRIVER_POD" 4040:4040
# Open http://localhost:4040 in your browser&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div style="width: 810px" class="wp-caption alignnone"&gt;
 &lt;a href="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/23/BDB-6122-8.png" target="_blank" rel="noopener"&gt;&lt;img src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/23/BDB-6122-8.png" alt="Live Spark UI for the Spark Connect session viewed in a browser through port forwarding" width="800"&gt;&lt;/a&gt;
 &lt;p class="wp-caption-text"&gt;Figure 5: Live Spark UI for the Spark Connect session&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;Spark Connect endpoints run as pods on your EKS cluster. The existing Kubernetes observability stack, such as CloudWatch Container Insights, Prometheus, and Grafana, captures Spark Connect endpoint metrics alongside other cluster workloads.&lt;/p&gt;
&lt;h3 id="clean-up-resources"&gt;Clean up resources&lt;/h3&gt;
&lt;p&gt;Terminate your session when you’re done to avoid ongoing costs:&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;div class="hide-language"&gt;
  &lt;pre&gt;&lt;code class="lang-bash"&gt;# (OPTIONAL) Endpoints are auto-deleted after the idle timeout (default: 60 minutes).
aws emr-containers delete-managed-endpoint \
--virtual-cluster-id $VC_ID \
--id $EP_ID
# Delete the virtual cluster only when no active endpoints remain
aws emr-containers delete-virtual-cluster --id $VC_ID
# Delete Security Configuration
aws emr-containers delete-security-configuration --id $SEC_CONFIG_ID
# remove the remaining EKS namespaces
kubectl delete namespace $USER_NAMESPACE $SYS_NAMESPACE spark-connect-router&lt;/code&gt;&lt;/pre&gt;
 &lt;/div&gt;
 &lt;p&gt;Deleting or timing out a managed endpoint automatically removes its corresponding driver and executor pods. The Envoy router and Secret Agent service are shared across endpoints on the EKS cluster and remain running when individual endpoints are terminated. To fully remove these shared components, delete the virtual cluster to remove its corresponding Secret Agent service. Before doing so, ensure that no managed endpoints in the virtual cluster are active. Terminating the last session-enabled virtual cluster automatically removes the Envoy router from the EKS cluster.&lt;/p&gt;
&lt;/div&gt;
&lt;h2 id="availability-and-pricing"&gt;Availability and pricing&lt;/h2&gt;
&lt;p&gt;Spark Connect on Amazon EMR on EKS is available with EMR release 7.14 (Apache Spark 3.5) and emr-spark-8.1 (Apache Spark 4.1), in all AWS Regions where Amazon EMR on EKS is available, except the AWS GovCloud (US) Regions and the China Regions. The Amazon SageMaker Unified Studio experience is available in &lt;a href="https://docs.aws.amazon.com/sagemaker-unified-studio/latest/adminguide/supported-regions.html" target="_blank" rel="noopener"&gt;supported Regions&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;There is no additional charge for Spark Connect managed endpoints beyond the standard Amazon EMR on EKS pricing. You pay for underlying Amazon EKS resources such as EC2 and ELB. For timed-out or terminated managed endpoints, EMR automatically removes their Spark pods from the EKS cluster.&lt;/p&gt;
&lt;p&gt;Recommendations for cost efficiency:&lt;/p&gt;
&lt;ul&gt;
 &lt;li&gt;Use Karpenter (or Cluster Autoscaler) to right-size cluster capacity to session workload demand. This provisions nodes when endpoints need them and removes them when idle, which keeps cost aligned to actual usage.&lt;/li&gt;
 &lt;li&gt;Schedule interactive session pods on &lt;strong&gt;On-Demand instances&lt;/strong&gt; for persistent compute.&lt;/li&gt;
 &lt;li&gt;Use &lt;a href="https://aws.amazon.com/ec2/graviton/" target="_blank" rel="noopener"&gt;&lt;strong&gt;AWS Graviton processors&lt;/strong&gt;&lt;/a&gt;&amp;nbsp;for &lt;a href="https://aws.amazon.com/blogs/big-data/amazon-emr-on-eks-gets-up-to-19-performance-boost-running-on-aws-graviton3-processors-vs-graviton2/" target="_blank" rel="noopener"&gt;better performance on Spark workloads&lt;/a&gt;.&lt;/li&gt;
 &lt;li&gt;Activate &lt;a href="https://docs.aws.amazon.com/guidance/latest/cloud-intelligence-dashboards/scad-containers-dashboard-data-on-eks.html" target="_blank" rel="noopener"&gt;Amazon EMR on EKS Cost Allocation&lt;/a&gt; tags to track per-team and per-project spending at granular level.&lt;/li&gt;
 &lt;li&gt;Keep a single, shared Envoy router and NLB serving all Spark Connect endpoints (the default) on the cluster. Right-size the router replica count (three by default) for your availability requirements.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Considerations and limitations&lt;/h2&gt;
&lt;p&gt;Before you build on Spark Connect for Amazon EMR on EKS, review the &lt;a href="https://docs.aws.amazon.com/emr/latest/EMR-on-EKS-DevelopmentGuide/emr-eks-spark-connect.html#emr-eks-spark-connect-considerations"&gt;Considerations and limitations in the Amazon EMR on EKS documentation&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;In this post, we showed how, with Spark Connect on Amazon EMR on EKS, you can build, test, and debug Spark applications from the tools you already use: IDEs, notebooks, Amazon SageMaker Unified Studio or Airflow. Your workloads run at scale on your existing Kubernetes clusters, with no application code changes.&lt;/p&gt;
&lt;p&gt;For teams already running Amazon EMR on EKS, Spark Connect extends your virtual clusters to interactive and embedded workloads. The same virtual cluster that runs your batch &lt;em&gt;StartJobRun&lt;/em&gt; jobs now also serves Spark Connect sessions. Each session runs as pods on your EKS cluster, inheriting your node groups, container images, and Spark configurations. Each session also carries its own IAM execution role and cost tags. This extends the security, multi-tenancy, and observability of your Amazon EMR on EKS investment to a broader set of users and use cases.&lt;/p&gt;
&lt;p&gt;To get started, visit the &lt;a href="https://docs.aws.amazon.com/emr/latest/EMR-on-EKS-DevelopmentGuide/emr-eks-spark-connect.html" target="_blank" rel="noopener"&gt;Spark Connect on Amazon EMR on EKS documentation&lt;/a&gt;, try the &lt;a href="https://docs.aws.amazon.com/sagemaker-unified-studio/latest/userguide/notebooks-spark-connect.html#spark-connect-emr-eks" target="_blank" rel="noopener"&gt;Amazon SageMaker Unified Studio Getting Started guide&lt;/a&gt;, and review the &lt;a href="https://docs.aws.amazon.com/emr/latest/EMR-on-EKS-DevelopmentGuide/emr-eks-7.14.0.html" target="_blank" rel="noopener"&gt;Amazon EMR on EKS release notes for EMR 7.14&lt;/a&gt;.&lt;/p&gt;
&lt;p style="clear: both"&gt;&lt;/p&gt;
&lt;hr style="width: 100%"&gt;
&lt;h2&gt;About the authors&lt;/h2&gt;
&lt;footer&gt;
 &lt;div class="blog-author-box" style="padding-top: 2.0em"&gt;
  &lt;div class="blog-author-image" style="margin-right: 1.0em"&gt;
   &lt;img loading="lazy" class="alignnone size-full" src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/23/BDB-6122-9.jpg" alt="Amit Maindola" width="100" height="133"&gt;
  &lt;/div&gt;
  &lt;h3 class="lb-h4"&gt;Amit Maindola&lt;/h3&gt;
  &lt;p style="overflow: hidden"&gt;Amit is a Senior Data &amp;amp; AI Architect with AWS ProServe team focused on data engineering, analytics, and AI/ML at Amazon Web Services. He helps customers in their digital transformation journey and enables them to build highly scalable, robust, and secure cloud-based analytical solutions on AWS to gain timely insights and make critical business decisions.&lt;/p&gt;
 &lt;/div&gt;
 &lt;div class="blog-author-box" style="padding-top: 2.0em"&gt;
  &lt;div class="blog-author-image" style="margin-right: 1.0em"&gt;
   &lt;img loading="lazy" class="alignnone size-full" src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/23/BDB-6122-10.jpg" alt="Melody Yang" width="100" height="133"&gt;
  &lt;/div&gt;
  &lt;h3 class="lb-h4"&gt;Melody Yang&lt;/h3&gt;
  &lt;p style="overflow: hidden"&gt;Melody Yang is a Principal Analytics Specialist Solution Architect at AWS with expertise in Big Data technologies. She is an experienced analytics leader working with AWS customers to provide best practice guidance and technical advice in order to assist their success in data transformation. Her areas of interests are open-source frameworks and automation, data engineering and DataOps.&lt;/p&gt;
 &lt;/div&gt;
 &lt;div class="blog-author-box" style="padding-top: 2.0em"&gt;
  &lt;div class="blog-author-image" style="margin-right: 1.0em"&gt;
   &lt;img loading="lazy" class="alignnone size-full" src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/23/BDB-6122-11.jpg" alt="Al MS" width="100" height="133"&gt;
  &lt;/div&gt;
  &lt;h3 class="lb-h4"&gt;Al MS&lt;/h3&gt;
  &lt;p style="overflow: hidden"&gt;Al is a product manager for Amazon EMR at AWS.&lt;/p&gt;
 &lt;/div&gt;
&lt;/footer&gt;</content:encoded>
					
		
		<enclosure length="6630455" type="video/mp4" url="https://d2908q01vomqb2.cloudfront.net/artifacts/DBSBlogs/smus-connect-recording.mp4"/>
<enclosure length="4142320" type="video/mp4" url="https://d2908q01vomqb2.cloudfront.net/artifacts/DBSBlogs/ide_spark_connect.mp4"/>
<enclosure length="23437553" type="video/mp4" url="https://d2908q01vomqb2.cloudfront.net/artifacts/DBSBlogs/wrapper-emr-eks-spark-connect-demo.mp4"/>

			</item>
		<item>
		<title>Aurora PostgreSQL zero-ETL integration with Amazon SageMaker</title>
		<link>https://aws.amazon.com/blogs/big-data/aurora-postgresql-zero-etl-integration-with-amazon-sagemaker/</link>
		
		<dc:creator><![CDATA[Apurwa Pawar]]></dc:creator>
		<pubDate>Tue, 29 Sep 2026 16:10:14 +0000</pubDate>
				<category><![CDATA[Advanced (300)]]></category>
		<category><![CDATA[Amazon Aurora]]></category>
		<category><![CDATA[Amazon SageMaker]]></category>
		<category><![CDATA[Technical How-to]]></category>
		<guid isPermaLink="false">471dbcc009dfc367efa0b960c9f356453f713b3d</guid>

					<description>Amazon Aurora PostgreSQL zero-ETL integration with Amazon SageMaker replicates your operational data to a lakehouse in near real time, without building custom ETL pipelines. Learn the architecture and change data capture mechanics, then set up the integration and query your data in Amazon SageMaker.</description>
										<content:encoded>&lt;p&gt;When you need quick insights from your Amazon Aurora PostgreSQL operational data, traditional analytics approaches force you to build complex extract, transform, and load (ETL) pipelines. These pipelines introduce latency, operational overhead, and data silos, which slow down decision making and increase cost. AWS introduced the support for &lt;a href="https://aws.amazon.com/about-aws/whats-new/2025/10/amazon-aurora-postgresql-zero-etl-integration-sagemaker/" target="_blank" rel="noopener"&gt;Amazon Aurora PostgreSQL zero-ETL integration with Amazon SageMaker&lt;/a&gt;, providing near real-time data availability for analytics workloads.&lt;/p&gt;
&lt;p&gt;The zero-ETL integration automatically replicates the data from your &lt;a href="https://aws.amazon.com/rds/aurora/" target="_blank" rel="noopener"&gt;Amazon Aurora PostgreSQL&lt;/a&gt; database into a target AWS Glue managed catalog, where it’s available as Apache Iceberg tables. You can then analyze this data through &lt;a href="https://aws.amazon.com/sagemaker/" target="_blank" rel="noopener"&gt;Amazon SageMaker&lt;/a&gt; alongside data from other sources using your preferred analytics and machine learning (ML) tools. The data is compatible with Apache Iceberg open standards, so you can use SQL, Apache Spark, business intelligence, and artificial intelligence and machine learning (AI/ML) tools.&lt;/p&gt;
&lt;p&gt;In this post, you explore the benefits of this integration, the architectural concepts, and the underlying change data capture (CDC) mechanics. You also go through the setup process and learn how to query your Aurora PostgreSQL data in Amazon SageMaker AI.&lt;/p&gt;
&lt;h2 id="zero-etl-in-the-lakehouse-architecture"&gt;Zero-ETL in the lakehouse architecture&lt;/h2&gt;
&lt;p&gt;The &lt;a href="https://aws.amazon.com/sagemaker/lakehouse/" target="_blank" rel="noopener"&gt;lakehouse architecture&lt;/a&gt; of Amazon SageMaker AI brings together data across Amazon Simple Storage Service (Amazon S3) data lakes and Amazon Redshift data warehouses. Because it’s built on open standards, you can build analytics and AI/ML applications on a single copy of data, without moving it between systems.&lt;/p&gt;
&lt;p&gt;Amazon SageMaker AI uses &lt;a href="https://docs.aws.amazon.com/prescriptive-guidance/latest/serverless-etl-aws-glue/aws-glue-data-catalog.html" target="_blank" rel="noopener"&gt;AWS Glue Data Catalog&lt;/a&gt; and &lt;a href="https://aws.amazon.com/lake-formation/" target="_blank" rel="noopener"&gt;AWS Lake Formation&lt;/a&gt; to provide integrated access controls across S3 data lakes and Amazon Redshift data warehouses from a single governance plane.&lt;/p&gt;
&lt;h2 id="understanding-change-data-capture-mechanics"&gt;Understanding change data capture mechanics&lt;/h2&gt;
&lt;p&gt;At its core, Aurora PostgreSQL zero-ETL integration is powered by CDC. CDC continuously monitors the database transaction log and streams every insert, update, and delete to a downstream target in near real time.&lt;/p&gt;
&lt;p&gt;Aurora PostgreSQL uses &lt;a href="https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/AuroraPostgreSQL.Replication.Logical.html" target="_blank" rel="noopener"&gt;enhanced logical replication&lt;/a&gt; as its CDC engine. Standard PostgreSQL logical replication publishes row-level changes from the write-ahead log (WAL). The enhanced logical replication in Aurora offers added capabilities that make it well-suited for zero-ETL integrations, including automatic DDL propagation and continuous streaming of transactional changes.&lt;/p&gt;
&lt;h2 id="solution-overview"&gt;Solution overview&lt;/h2&gt;
&lt;p&gt;With Amazon Aurora PostgreSQL zero-ETL integration with Amazon SageMaker AI, you can:&lt;/p&gt;
&lt;ul&gt;
 &lt;li&gt;&lt;strong&gt;Remove ETL complexity&lt;/strong&gt; – Automatically replicate data without building custom ETL pipelines.&lt;/li&gt;
 &lt;li&gt;&lt;strong&gt;Near real-time analytics&lt;/strong&gt; – Access operational data in Amazon SageMaker AI within seconds of changes in Aurora PostgreSQL.&lt;/li&gt;
 &lt;li&gt;&lt;strong&gt;Unify data analysis&lt;/strong&gt; – Combine Aurora PostgreSQL data with data from other sources in a single lakehouse architecture.&lt;/li&gt;
 &lt;li&gt;&lt;strong&gt;Reduce costs&lt;/strong&gt; – Minimize operational overhead and infrastructure costs associated with maintaining ETL pipelines.&lt;/li&gt;
 &lt;li&gt;&lt;strong&gt;Accelerate insights&lt;/strong&gt; – Query data using familiar SQL tools and integrate with ML workflows in Amazon SageMaker AI.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The following diagram illustrates the architecture of this solution:&lt;/p&gt;
&lt;div style="width: 808px" class="wp-caption alignnone"&gt;
 &lt;a href="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/24/BDB-5743-1.png" target="_blank" rel="noopener"&gt;&lt;img src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/24/BDB-5743-1.png" alt="Aurora PostgreSQL zero-ETL integration replicating data into an AWS Glue managed catalog queried through Amazon SageMaker" width="798"&gt;&lt;/a&gt;
 &lt;p class="wp-caption-text"&gt;Figure 1: Architecture of the Aurora PostgreSQL zero-ETL integration with Amazon SageMaker&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;The workflow includes the following steps:&lt;/p&gt;
&lt;ol type="1"&gt;
 &lt;li&gt;Your application writes data to an Amazon Aurora PostgreSQL database cluster.&lt;/li&gt;
 &lt;li&gt;The zero-ETL integration automatically captures changes from the Aurora PostgreSQL database.&lt;/li&gt;
 &lt;li&gt;Data is replicated to the target AWS Glue managed catalog in near real time.&lt;/li&gt;
 &lt;li&gt;You can query and analyze the data using Amazon Athena, Amazon Redshift, or other analytics tools integrated with Amazon SageMaker AI.&lt;/li&gt;
 &lt;li&gt;Data scientists can build and train ML models using Amazon SageMaker AI with direct access to the Apache Iceberg tables in the target AWS Glue managed catalog.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="prerequisites"&gt;Prerequisites&lt;/h2&gt;
&lt;p&gt;Before setting up the zero-ETL integration, verify that you have the following:&lt;/p&gt;
&lt;ul&gt;
 &lt;li&gt;An &lt;a href="https://aws.amazon.com/vpc/" target="_blank" rel="noopener"&gt;Amazon Virtual Private Cloud&lt;/a&gt; (Amazon VPC) setup with the proper networking configurations for database connectivity.&lt;/li&gt;
 &lt;li&gt;An &lt;a href="https://aws.amazon.com/ec2/" target="_blank" rel="noopener"&gt;Amazon Elastic Compute Cloud&lt;/a&gt; (Amazon EC2) &lt;a href="https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/Overview.RDSSecurityGroups.html" target="_blank" rel="noopener"&gt;security group&lt;/a&gt; set up and an allowed DB instance port connection to the source and target DB instances.&lt;/li&gt;
 &lt;li&gt;&lt;a href="https://aws.amazon.com/cli/" target="_blank" rel="noopener"&gt;AWS Command Line Interface&lt;/a&gt; (AWS CLI v2) &lt;a href="https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-getting-started.html" target="_blank" rel="noopener"&gt;installed and configured&lt;/a&gt; with the appropriate &lt;a href="https://aws.amazon.com/iam/" target="_blank" rel="noopener"&gt;AWS Identity and Access Management&lt;/a&gt; (IAM) credentials and permissions to interact with &lt;a href="https://aws.amazon.com/rds/aurora/" target="_blank" rel="noopener"&gt;Amazon Aurora&lt;/a&gt; and SageMaker AI.&lt;/li&gt;
 &lt;li&gt;Sufficient AWS service quotas for Aurora PostgreSQL resources.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="configure-the-source-postgresql-database-for-zero-etl-integration"&gt;Configure the source PostgreSQL database for zero-ETL integration&lt;/h2&gt;
&lt;p&gt;When you have all the prerequisites in place, you can configure the source PostgreSQL database for zero-ETL integration.&lt;/p&gt;
&lt;h3 id="create-a-custom-aurora-postgresql-cluster-parameter-group"&gt;Create a custom Aurora PostgreSQL cluster parameter group&lt;/h3&gt;
&lt;p&gt;Your Aurora PostgreSQL database needs to have parameters configured for real-time replication. In this section, you will create the &lt;a href="https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/zero-etl.setting-up.html#zero-etl.parameters" target="_blank" rel="noopener"&gt;DB cluster parameter group&lt;/a&gt; and configure parameters. For more information, see &lt;a href="https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/zero-etl.setting-up.html#zero-etl.parameters" target="_blank" rel="noopener"&gt;Getting started with Aurora zero-ETL integrations&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Use the following AWS CLI command to create an Aurora PostgreSQL cluster parameter group:&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-bash"&gt;aws rds create-db-cluster-parameter-group \
    --db-cluster-parameter-group-name aurora-pgsql-zetl-cluster-pg \
    --db-parameter-group-family aurora-postgresql16 \
    --description "Aurora PostgreSQL with enhanced logical replication" \
    --region us-east-1 --output json&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Now set the parameters by modifying the parameter group:&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-bash"&gt;aws rds modify-db-cluster-parameter-group --db-cluster-parameter-group-name &amp;lt;aurora-pgsql-zetl-cluster-pg&amp;gt; \
    --parameters \
    ParameterName=rds.logical_replication,ParameterValue=1,ApplyMethod=pending-reboot \
    ParameterName=aurora.enhanced_logical_replication,ParameterValue=1,ApplyMethod=pending-reboot \
    ParameterName=aurora.logical_replication_backup,ParameterValue=0,ApplyMethod=pending-reboot \
    ParameterName=aurora.logical_replication_globaldb,ParameterValue=0,ApplyMethod=pending-reboot \
    --region us-east-1 --output json&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;The parameter group is now fully configured and ready to be applied to your Aurora PostgreSQL cluster.&lt;/p&gt;
&lt;h3 id="select-or-create-a-source-aurora-postgresql-cluster"&gt;Select or create a source Aurora PostgreSQL cluster&lt;/h3&gt;
&lt;p&gt;If you already have an Aurora PostgreSQL cluster, you can use it, or you can create a new Aurora PostgreSQL cluster.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Your source DB cluster must be running a supported version of Aurora PostgreSQL. For a list of supported versions, see &lt;a href="https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/Concepts.Aurora_Fea_Regions_DB-eng.Feature.Zero-ETL.html" target="_blank" rel="noopener"&gt;Regions and database engines supported for Aurora zero-ETL integrations&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;While creating an Aurora PostgreSQL cluster, use the parameter group (aurora-pgsql-zetl-cluster-pg) you created earlier:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Throughout this post, make sure to replace the with your own information.&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-bash"&gt;aws rds create-db-cluster \
    --db-cluster-identifier &amp;lt;aurora-pgsql-zetl&amp;gt; \
    --engine aurora-postgresql \
    --engine-version 16 \
    --master-username &amp;lt;admin&amp;gt; \
    --master-user-password &amp;lt;password&amp;gt; \
    --database-name &amp;lt;my_db&amp;gt; \
    --db-cluster-parameter-group-name &amp;lt;aurora-pgsql-zetl-cluster-pg&amp;gt; \
    --storage-encrypted \
    --kms-key-id alias/aws/rds \
    --backup-retention-period 7 \
    --db-subnet-group-name &amp;lt;dbsubnet&amp;gt; \
    --vpc-security-group-ids &amp;lt;sg-c14219ba&amp;gt; \
    --region &amp;lt;us-east-1&amp;gt; \
    --output json&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-bash"&gt;aws rds create-db-instance \
    --db-instance-identifier &amp;lt;aurora-pgsql-zetl-instance-1&amp;gt; \
    --db-instance-class db.r5.large \
    --engine aurora-postgresql \
    --db-cluster-identifier &amp;lt;aurora-pgsql-zetl&amp;gt; \
    --region us-east-1 \
    --output json&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;If you’re creating a new Aurora PostgreSQL cluster, wait for your DB instance(s) to be in an “Available” status. You can verify DB instance status by using the describe-db-instances API call:&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-bash"&gt;aws rds describe-db-instances --filters 'Name=db-cluster-id,Values=&amp;lt;aurora-pgsql-zetl&amp;gt;' --output json | grep -o '"DBInstanceStatus": "[^"]*"'&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-plaintext"&gt;"DBInstanceStatus": "available"&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h3 id="reboot-the-cluster-to-apply-parameter-changes"&gt;Reboot the cluster to apply parameter changes&lt;/h3&gt;
&lt;p&gt;A cluster reboot is needed before zero-ETL integration can function correctly:&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-bash"&gt;aws rds reboot-db-instance \
    --db-instance-identifier &amp;lt;aurora-pgsql-zetl-instance-1&amp;gt; \
    --region &amp;lt;us-east-1&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Wait until the cluster and the primary instance are back in Available status. For more information, see &lt;a href="https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/USER_RebootCluster.html" target="_blank" rel="noopener"&gt;reboot-db-instance&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="create-a-target-aws-glue-managed-catalog"&gt;Create a target AWS Glue managed catalog&lt;/h2&gt;
&lt;p&gt;With your source PostgreSQL database configured for enhanced logical replication, the next step is setting up your target Amazon SageMaker AI. Zero-ETL integration uses AWS Glue Data Catalog backed by Amazon Redshift managed storage as its target. To have this functionality, you need to create a managed catalog, configure IAM permissions for Amazon SageMaker AI to access and query the managed catalog, and set up authorization for incoming integration requests from your source database.&lt;/p&gt;
&lt;h3 id="create-an-aws-glue-managed-catalog"&gt;Create an AWS Glue managed catalog&lt;/h3&gt;
&lt;p&gt;You must create a new catalog (if it doesn’t exist already) managed by AWS Glue to store table metadata and serve as the landing zone for your replicated datasets. Zero-ETL integration streams the data into Amazon Redshift managed storage, and AWS Glue keeps track of table definitions so that tools such as SageMaker AI, Athena, and &lt;a href="https://docs.aws.amazon.com/redshift/latest/dg/c-using-spectrum.html" target="_blank" rel="noopener"&gt;Amazon Redshift Spectrum&lt;/a&gt; can query the data.&lt;/p&gt;
&lt;h3 id="create-an-iam-role-for-aws-glue-and-amazon-redshift-to-access-the-aws-glue-managed-catalog"&gt;Create an IAM role for AWS Glue and Amazon Redshift to access the AWS Glue managed catalog&lt;/h3&gt;
&lt;p&gt;Now, use the following command to create an IAM role so that AWS Glue and Amazon Redshift can interact with the catalog. This role serves two key functions: It allows AWS Glue and Amazon Redshift to perform catalog operations, and it authorizes incoming integration requests from your source database.&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-bash"&gt;aws iam create-role \
    --role-name &amp;lt;GlueDataCatalogDataTransferRole&amp;gt; \
    --assume-role-policy-document '{
        "Version": "2012-10-17",
        "Statement": [
            {
                "Effect": "Allow",
                "Principal": {
                    "Service": [
                        "glue.amazonaws.com",
                        "redshift.amazonaws.com"
                    ]
                },
                "Action": "sts:AssumeRole"
            }
        ]
    }'&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Next, attach a policy to this IAM role that provides the minimum required permissions for AWS Glue and Amazon Redshift. This policy should also include the necessary permissions for encryption key actions to help maintain secure data handling throughout the integration process:&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-bash"&gt;aws iam put-role-policy \
    --role-name &amp;lt;GlueDataCatalogDataTransferRole&amp;gt; \
    --policy-name &amp;lt;GlueDataTransferPolicy&amp;gt; \
    --policy-document '{
        "Version": "2012-10-17",
        "Statement": [
            {
                "Sid": "DataTransferRolePolicy",
                "Effect": "Allow",
                "Action": [
                    "kms:GenerateDataKey",
                    "kms:Decrypt",
                    "glue:GetDatabase",
                    "glue:GetCatalog"
                ],
                "Resource": ["*"]
            }
        ]
    }'&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h2 id="set-up-aws-lake-formation-access"&gt;Set up AWS Lake Formation access&lt;/h2&gt;
&lt;p&gt;Before using the managed catalog for zero-ETL integration, you must configure data lake administrators in &lt;a href="https://aws.amazon.com/lake-formation/" target="_blank" rel="noopener"&gt;AWS Lake Formation&lt;/a&gt; who have administrative or read-only permissions on the managed resources. Additionally, you need to grant &lt;code&gt;ReadOnlyAdmin&lt;/code&gt; permissions to the Amazon Redshift service-linked role, &lt;code&gt;AWSServiceRoleForRedshift&lt;/code&gt;, in your account. If this role doesn’t exist in your account or you need to verify its permissions, see &lt;a href="https://docs.aws.amazon.com/redshift/latest/mgmt/using-service-linked-roles.html" target="_blank" rel="noopener"&gt;Using service-linked roles for Amazon Redshift.&lt;/a&gt;&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-bash"&gt;aws lakeformation put-data-lake-settings \
    --region &amp;lt;us-east-1&amp;gt; \
    --cli-input-json '{
        "DataLakeSettings": {
            "DataLakeAdmins": [
                {
                    "DataLakePrincipalIdentifier": "&amp;lt;arn:aws:iam::111122223333:role/Admin&amp;gt;"
                }
            ],
            "ReadOnlyAdmins": [
                {
                    "DataLakePrincipalIdentifier": "&amp;lt;arn:aws:iam::111122223333:role/aws-service-role/redshift.amazonaws.com/AWSServiceRoleForRedshift&amp;gt;"
                }
            ],
            "CreateDatabaseDefaultPermissions": [],
            "CreateTableDefaultPermissions": [],
            "Parameters": {
                "CROSS_ACCOUNT_VERSION": "4",
                "SET_CONTEXT": "TRUE"
            },
            "AllowExternalDataFiltering": false,
            "ExternalDataFilteringAllowList": []
        }
    }'&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h3 id="create-the-aws-glue-managed-catalog-backed-by-amazon-redshift-managed-storage"&gt;Create the AWS Glue managed catalog backed by Amazon Redshift managed storage&lt;/h3&gt;
&lt;p&gt;Because you have configured IAM permissions and Lake Formation settings, you can now create the AWS Glue managed catalog.&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-bash"&gt;aws glue create-catalog \
    --region &amp;lt;us-east-1&amp;gt; \
    --cli-input-json '{
        "Name": "&amp;lt;zetl-catalog&amp;gt;",
        "CatalogInput": {
            "Description": "A Glue Data Catalog backed by Redshift Managed Storage",
            "CreateDatabaseDefaultPermissions": [],
            "CreateTableDefaultPermissions": [],
            "CatalogProperties": {
                "DataLakeAccessProperties": {
                    "DataLakeAccess": true,
                    "DataTransferRole": "&amp;lt;arn:aws:iam::111122223333:role/GlueDataCatalogDataTransferRole&amp;gt;",
                    "CatalogType": "aws:redshift"
                }
            }
        }
    }'&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h3 id="register-the-catalog-as-a-zero-etl-integration-target"&gt;Register the catalog as a zero-ETL integration target&lt;/h3&gt;
&lt;p&gt;To prepare your target AWS Glue managed catalog for zero-ETL integration, use the create-integration-resource-property command with these required parameters:&lt;/p&gt;
&lt;ul&gt;
 &lt;li&gt;The –resource-arn parameter specifies the &lt;a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/reference-arns.html" target="_blank" rel="noopener"&gt;Amazon Resource Name&lt;/a&gt; (ARN) of your AWS Glue managed catalog that will serve as the integration target.&lt;/li&gt;
 &lt;li&gt;The –target-processing-properties parameter requires the ARN of an IAM role that has describe permissions on the target AWS Glue managed catalog.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You can use the GlueDataCatalogDataTransferRole created in the earlier step because it already includes the minimal &lt;a href="https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/zero-etl.creating-smlh.html#:~:text=The%20target%20IAM%20role%20needs%20describe%20permissions%20on%20the%20target%20catalog%20and%20must%20have%20the%20following%20permissions%3A" target="_blank" rel="noopener"&gt;describe permissions&lt;/a&gt; needed for this integration. Alternatively, you can create a new IAM role specifically for this purpose and attach the necessary minimal permissions to meet your company’s security requirements.&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-bash"&gt;aws glue create-integration-resource-property \
    --region &amp;lt;us-east-1&amp;gt; \
    &amp;lt;arn:aws:glue:us-east-1:111122223333:catalog/zetl-catalog&amp;gt; \
    '{"RoleArn": "&amp;lt;arn:aws:iam::111122223333:role/GlueDataCatalogDataTransferRole&amp;gt;"}'&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Example output:&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-json"&gt;{
    "ResourceArn": "arn:aws:glue:us-east-1:111122223333:catalog/zetl-catalog",
    "TargetProcessingProperties": {
        "RoleArn": "arn:aws:iam::111122223333:role/GlueDataCatalogDataTransferRole"
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h3 id="configure-authorization-for-inbound-integration-requests"&gt;Configure authorization for inbound integration requests&lt;/h3&gt;
&lt;p&gt;The last step in creating a target managed catalog is to define a resource-based access policy that authorizes zero-ETL integration to push data into your catalog. This policy grants AWS Glue the necessary permissions to create and authorize incoming integration requests from your source database. Apply this resource policy by using the AWS Glue put-resource-policy API call to complete the catalog configuration for your zero-ETL integration:&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-bash"&gt;aws glue put-resource-policy \
    --region &amp;lt;us-east-1&amp;gt; \
    --policy-in-json '{
        "Version": "2012-10-17",
        "Statement": [
            {
                "Principal": {
                    "AWS": [
                        "111122223333"
                    ]
                },
                "Effect": "Allow",
                "Action": [
                    "glue:CreateInboundIntegration"
                ],
                "Resource": [
                    "arn:aws:glue:us-east-1:111122223333:catalog/zetl-catalog"
                ],
                "Condition": {
                    "StringEquals": {
                        "aws:SourceArn": "arn:aws:rds:us-east-1:111122223333:cluster:aurora-pgsql-zetl"
                    }
                }
            },
            {
                "Principal": {
                    "Service": [
                        "glue.amazonaws.com"
                    ]
                },
                "Effect": "Allow",
                "Action": [
                    "glue:AuthorizeInboundIntegration"
                ],
                "Resource": [
                    "arn:aws:glue:us-east-1:111122223333:catalog/zetl-catalog"
                ],
                "Condition": {
                    "StringEquals": {
                        "aws:SourceArn": "arn:aws:rds:us-east-1:111122223333:cluster:aurora-pgsql-zetl"
                    }
                }
            }
        ]
    }'&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Your AWS Glue managed catalog is now ready to receive data from the zero-ETL integration.&lt;/p&gt;
&lt;h2 id="load-data-in-the-source-aurora-postgresql-database"&gt;Load data in the source Aurora PostgreSQL database&lt;/h2&gt;
&lt;p&gt;Now that your Aurora PostgreSQL database is configured and ready, you must populate it with sample data that serves as the historical baseline for your zero-ETL integration. This first dataset provides the foundation for testing and demonstrating the integration capabilities. After you set up the zero-ETL integration, subsequent database changes stream automatically in near real time to your target AWS Glue managed catalog.&lt;/p&gt;
&lt;h3 id="connect-to-the-source-aurora-postgresql-cluster"&gt;Connect to the source Aurora PostgreSQL cluster&lt;/h3&gt;
&lt;p&gt;Use the following commands to create a &lt;a href="https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/Aurora.Connecting.html" target="_blank" rel="noopener"&gt;connection&lt;/a&gt; to your source Aurora PostgreSQL cluster:&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-bash"&gt;psql --host aurora-pgsql-zetl-xxxxx.us-east-1.rds.amazonaws.com --username admin --port 5432 --dbname my_db --password&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h3 id="create-a-database-and-table"&gt;Create a database and table&lt;/h3&gt;
&lt;p&gt;Create a table named products to store product information:&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-sql"&gt;CREATE TABLE products (product_id SERIAL PRIMARY KEY,product_name VARCHAR(100) NOT NULL, description TEXT,category VARCHAR(50),price NUMERIC(10,2) NOT NULL,stock_quantity INTEGER DEFAULT 0,is_active BOOLEAN DEFAULT TRUE,created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP );&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h3 id="insert-historical-data"&gt;Insert historical data&lt;/h3&gt;
&lt;p&gt;Use the following code to insert a row:&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-sql"&gt;INSERT INTO products (product_name, description, category, price, stock_quantity) VALUES ('Laptop', 'High-performance laptop with 16GB RAM and 512GB SSD', 'Electronics', 1299.99, 50);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;This table serves as a representative dataset to demonstrate the data capture and streaming capabilities of the zero-ETL integration. After your zero-ETL integration is active, all database changes, including inserts, updates, and deletes, are automatically captured and streamed to your AWS Glue managed catalog. This creates a data pipeline from your Aurora PostgreSQL database to your Amazon SageMaker for real-time analytics on your operational data.&lt;/p&gt;
&lt;h2 id="create-a-zero-etl-integration"&gt;Create a zero-ETL integration&lt;/h2&gt;
&lt;p&gt;Because your Aurora PostgreSQL database is now populated with historical data, you can set up the zero-ETL integration that continuously streams database changes to your AWS Glue managed catalog backed by Amazon Redshift managed storage.&lt;/p&gt;
&lt;h3 id="create-the-integration"&gt;Create the integration&lt;/h3&gt;
&lt;p&gt;Create the integration between your source PostgreSQL database and target AWS Glue catalog by using the aws rds create-integration AWS CLI command. You can customize the integration by specifying added configurations, such as data filters, to control which data gets replicated to your target environment:&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-bash"&gt;aws rds create-integration \
    --source-arn &amp;lt;arn:aws:rds:us-east-1:111122223333:cluster:aurora-pgsql-zetl&amp;gt; \
    --target-arn &amp;lt;arn:aws:glue:us-east-1:111122223333:catalog/zetl-catalog&amp;gt; \
    --integration-name &amp;lt;zetl-test-integration&amp;gt; \
    --data-filter "include: *.*" \
    --region us-east-1&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;When you run the command, the zero-ETL integration begins provisioning and enters a &lt;strong&gt;‘creating’&lt;/strong&gt; state. The AWS CLI response provides key details about the integration configuration.&lt;/p&gt;
&lt;p&gt;Example CLI output:&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-json"&gt;{
    "SourceArn": "&amp;lt;arn:aws:rds:us-east-1:111122223333:cluster:aurora-pgsql-zetl&amp;gt;",
    "TargetArn": "&amp;lt;arn:aws:glue:us-east-1:111122223333:catalog/zetl-catalog&amp;gt;",
    "IntegrationName": "&amp;lt;zetl-test-integration&amp;gt;",
    "IntegrationArn": "&amp;lt;arn:aws:rds:us-east-1:111122223333:integration:4c4d81b9-xxxx&amp;gt;",
    "KMSKeyId": "&amp;lt;arn:aws:kms:us-east-1:111122223333:key/b9130ae1-exxxx&amp;gt;",
    "Status": "creating",
    "Tags": [],
    "CreateTime": "2025-07-04T05:42:56.841000+00:00",
    "DataFilter": "include: *.*"
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;When the integration status changes to “active”, your zero-ETL integration pipeline is fully operational.&lt;/p&gt;
&lt;h2 id="monitor-the-integration"&gt;Monitor the integration&lt;/h2&gt;
&lt;p&gt;Before generating new live data, verify that the integration has reached an “active” state by running the describe-integrations AWS CLI command. This monitoring step is important to confirm that changes from your source Aurora cluster are successfully streaming to the AWS Glue managed catalog without errors:&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-bash"&gt;aws rds describe-integrations&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-json"&gt;{
    "Integrations": [
        {
            "SourceArn": "&amp;lt;arn:aws:rds:us-east-1:111122223333:cluster:aurora-pgsql-zetl&amp;gt;",
            "TargetArn": "&amp;lt;arn:aws:glue:us-east-1:111122223333:catalog/zetl-catalog&amp;gt;",
            "IntegrationName": "&amp;lt;zetl-test-integration&amp;gt;",
            "IntegrationArn": "&amp;lt;arn:aws:rds:us-east-1:111122223333:integration:4c4d81b9-xxxx&amp;gt;",
            "KMSKeyId": "&amp;lt;arn:aws:kms:us-east-1:111122223333:key/b9130ae1-xxxx&amp;gt;",
            "Status": "active",
            "Tags": [],
            "CreateTime": "2025-07-04T05:42:56.841000+00:00",
            "DataFilter": "include: *.*"
        }
    ]
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h2 id="verify-the-zero-etl-integration"&gt;Verify the zero-ETL integration&lt;/h2&gt;
&lt;p&gt;Now that your historical data is loaded and the zero-ETL integration is “active”, you must confirm that the data has been successfully replicated.&lt;/p&gt;
&lt;h2 id="grant-lake-formation-permissions"&gt;Grant Lake Formation permissions&lt;/h2&gt;
&lt;p&gt;Before you can query the AWS Glue managed catalog by using the &lt;a href="https://docs.aws.amazon.com/redshift-data/latest/APIReference/Welcome.html" target="_blank" rel="noopener"&gt;Amazon Redshift Data API&lt;/a&gt;, you must make sure the IAM user or role has the right permissions to create and manage tables within the catalog. Use the Lake Formation &lt;a href="https://docs.aws.amazon.com/cli/latest/reference/lakeformation/grant-permissions.html" target="_blank" rel="noopener"&gt;grant-permissions&lt;/a&gt; API to provide these necessary permissions so that Amazon Redshift can access your AWS Glue managed catalog for the zero-ETL integration. For more information, see &lt;a href="https://docs.aws.amazon.com/lake-formation/latest/dg/create-rms-catalog.html" target="_blank" rel="noopener"&gt;Creating an Amazon Redshift managed catalog in the AWS Glue Data Catalog&lt;/a&gt;.&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-bash"&gt;aws lakeformation grant-permissions \
    --region &amp;lt;us-east-1&amp;gt; \
    --cli-input-json '{
        "Principal": {
            "DataLakePrincipalIdentifier": "&amp;lt;arn:aws:iam::111122223333:role/Admin&amp;gt;"
        },
        "Resource": {
            "Table": {
                "DatabaseName": "my_db",
                "CatalogId": "&amp;lt;111122223333:zetl-catalog/zetl_0dff6d97-xxxx&amp;gt;",
            },
            "Permissions": [
                "CREATE_CATALOG",
                "DESCRIBE",
                "CREATE_DATABASE",
                "DROP",
                "ALTER"
            ],
            "PermissionsWithGrantOption": [
                "CREATE_CATALOG",
                "DESCRIBE",
                "CREATE_DATABASE",
                "DROP",
                "ALTER"
            ]
        }'&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;These permissions allow for query execution and metadata inspection on the managed catalog.&lt;/p&gt;
&lt;h2 id="query-historical-data-by-using-the-amazon-redshift-data-api"&gt;Query historical data by using the Amazon Redshift Data API&lt;/h2&gt;
&lt;p&gt;With the necessary permissions in place, you can now verify your historical data by querying the AWS Glue managed catalog through the Amazon Redshift execute-statement Data API. Begin this verification process by running a &lt;code&gt;SELECT&lt;/code&gt; statement against the catalog:&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-bash"&gt;aws redshift-data execute-statement --sql 'SELECT * FROM "zetl_0dff6d97-xxxx@zetl-catalog"."my_db"."products" LIMIT 10;' --database "&amp;lt;arn:aws:glue:us-east-1:111122223333:catalog/pg-zetl-catalog&amp;gt;"&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;The following command returns a unique query ID that you can use to monitor the execution status and retrieve results from your query:&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-json"&gt;{
    "CreatedAt": "2025-07-15T00:31:48.778000+00:00",
    "Database": "arn:aws:glue:us-east-1:111122223333:catalog/zetl-catalog",
    "DbUser": "IAMR:Admin",
    "Id": "ce1ff0el-xxxx",
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Monitor your query’s progress by using the describe-statement API with the query ID. Continue checking until the status shows that your query has completed successfully:&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-bash"&gt;//use the Id to make the describe-statement API call to verify execution status is Started
aws redshift-data describe-statement --id &amp;lt;ce1ff0el-xxxx&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-json"&gt;{
    "CreatedAt": "2025-07-15T00:31:48.778000+00:00",
    "Database": "arn:aws:glue:us-east-1:111122223333:catalog/zetl-catalog",
    "DbUser": "IAMR:Admin",
    "Duration": 6238051060,
    "HasResultSet": true,
    "Id": "2ca8fedf-xxxx",
    "QueryString": "SELECT * FROM \"zetl_0dff6d97-xxxx_zeroetl@pg-zetl-catalog\".\"zetl_default\".\"products\" LIMIT 10;",
    "RedshiftPid": 1073791309,
    "RedshiftQueryId": 1018598,
    "ResultFormat": "json",
    "ResultRows": 1,
    "ResultSize": 149,
    "Status": "FINISHED",
    "UpdatedAt": "2025-07-15T00:31:55.491000+00:00"
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;To complete the verification process and view your historical data now available in Amazon SageMaker AI, retrieve the query results by using the get-statement-result API call:&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-bash"&gt;aws redshift-data get-statement-result --id &amp;lt;ce1ff0el-xxxx&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-json"&gt;{
    "Records": [
        [
            {
                "longValue": 1
            },
            {
                "stringValue": "Laptop"
            },
            {
                "stringValue": "High-performance laptop with 16GB RAM and 512GB SSD"
            },
            {
                "stringValue": "Electronics"
            },
            {
                "stringValue": "1299.99"
            },
            {
                "longValue": 50
            },
            {
                "booleanValue": true
            },
            {
                "stringValue": "2026-02-27 13:32:02.697117"
            },
            {
                "stringValue": "2026-02-27 13:32:02.697117"
            }
        ]
    ],
    "ColumnMetadata": [
        ....
        //Skipping metadata
    ],
    "TotalNumRows": 1
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;With your zero-ETL integration now active, you can demonstrate real-time data streaming by adding new data to your source Aurora PostgreSQL instance. Run the following INSERT query to add a new row, which shows how changes are automatically replicated in near real time:&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-sql"&gt;INSERT INTO products (product_name, description, category, price, stock_quantity)
VALUES ('Wireless Mouse', 'Ergonomic wireless mouse with USB receiver and long battery life', 'Electronics', 29.99, 150);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;You can verify that the recent changes from your source database have been replicated to the target environment within seconds. Use the same Amazon Redshift Data API workflow you used earlier to confirm the real-time replication:&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-bash"&gt;aws redshift-data execute-statement --sql 'SELECT * FROM "zetl_0dff6d97-xxxx@zetl-catalog"."my_db"."products" LIMIT 10;' --database "&amp;lt;arn:aws:glue:us-east-1:111122223333:catalog/pg-zetl-catalog&amp;gt;"&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-json"&gt;{
    "CreatedAt": "2025-07-15T00:31:48.778000+00:00",
    "Database": "arn:aws:glue:us-east-1:111122223333:catalog/zetl-catalog",
    "DbUser": "IAMR:Admin",
    "Id": "2ca8fedf-a604-4c87-a183-3a553d62354c",
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Use the describe-statement API call to monitor the query execution and confirm that the status shows ‘FINISHED’ before proceeding to retrieve the results:&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-bash"&gt;aws redshift-data describe-statement --id &amp;lt;ce1ff0ef-xxxx&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-json"&gt;{
    "CreatedAt": "2025-07-15T00:31:48.778000+00:00",
    "Database": "arn:aws:glue:us-east-1:111122223333:catalog/zetl-catalog",
    "DbUser": "IAMR:Admin",
    "Duration": 6238051060,
    "HasResultSet": true,
    "Id": "2ca8fedf-xxxx",
    "QueryString": "SELECT * FROM \"zetl_0dff6d97-xxxx_zeroetl@pg-zetl-catalog\".\"zetl_default\".\"products\" LIMIT 10;",
    "RedshiftPid": 1073791309,
    "RedshiftQueryId": 1018598,
    "ResultFormat": "json",
    "ResultRows": 2,
    "ResultSize": 317,
    "Status": "FINISHED",
    "UpdatedAt": "2025-07-15T00:31:55.491000+00:00"
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Finally, retrieve the query results by using the get-statement-result API call:&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-bash"&gt;aws redshift-data get-statement-result --id &amp;lt;ce1ff0ef-xxxx&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-json"&gt;{
    "Records": [
        [
            {
                "longValue": 1
            },
            {
                "stringValue": "Laptop"
            },
            {
                "stringValue": "High-performance laptop with 16GB RAM and 512GB SSD"
            },
            {
                "stringValue": "Electronics"
            },
            {
                "stringValue": "1299.99"
            },
            {
                "longValue": 50
            },
            {
                "booleanValue": true
            },
            {
                "stringValue": "2026-02-27 13:32:02.697117"
            },
            {
                "stringValue": "2026-02-27 13:32:02.697117"
            }
        ],
        [
            {
                "longValue": 2
            },
            {
                "stringValue": "Wireless Mouse"
            },
            {
                "stringValue": "Ergonomic wireless mouse with USB receiver and long battery life"
            },
            {
                "stringValue": "Electronics"
            },
            {
                "stringValue": "29.99"
            },
            {
                "longValue": 150
            },
            {
                "booleanValue": true
            },
            {
                "stringValue": "2026-02-27 15:41:00.206273"
            },
            {
                "stringValue": "2026-02-27 15:41:00.206273"
            }
        ]
    ],
    "ColumnMetadata": [
        ....
        //Skipping metadata
    ],
    "TotalNumRows": 2
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;This verification process confirms that your zero-ETL integration from Aurora PostgreSQL to Amazon SageMaker AI is working and continuously replicating both historical and real-time data. Although zero-ETL integration significantly simplifies data replication, it’s important to understand certain limitations on supported data types, schema change handling, and data filtering capabilities. For more details about these considerations and best practices, see &lt;a href="https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/zero-etl.html" target="_blank" rel="noopener"&gt;Aurora zero-ETL integrations&lt;/a&gt; and &lt;a href="https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/zero-etl.html" target="_blank" rel="noopener"&gt;Amazon RDS zero-ETL integrations&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="clean-up"&gt;Clean up&lt;/h2&gt;
&lt;p&gt;This section guides you through the cleanup process to remove the resources and components you created during this walkthrough. When you delete a zero-ETL integration, Amazon Aurora removes it from the source Aurora DB cluster. Your transactional data isn’t removed from Amazon Aurora or the analytics destination, but Aurora doesn’t send new data to Amazon SageMaker AI.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Delete the zero-ETL integration&lt;/strong&gt;: Begin the cleanup process by removing the integration between your source Amazon Relational Database Service (Amazon RDS) database and the AWS Glue managed catalog. Run the following command to delete the integration:&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-bash"&gt;aws rds delete-integration --integration-identifier &amp;lt;arn:aws:rds:us-east-1:111122223333:integration:4c4d81b9-af2a-4b09-b922-007636ba7f66&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Delete the AWS Glue managed catalog&lt;/strong&gt;: After you successfully delete the integration, delete the AWS Glue managed catalog that served as your zero-ETL target destination. Use the following command to remove the catalog:&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-bash"&gt;aws glue delete-catalog --catalog-id &amp;lt;111122223333:zetl-catalog&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;This permanently removes all associated table metadata and Amazon Redshift managed storage references.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Delete the Aurora DB cluster&lt;/strong&gt;: If you created the source Aurora DB cluster for this demonstration and you no longer need it, you can complete the cleanup by deleting the entire DB cluster. By skipping the final snapshot option, you avoid retaining any test data and confirm complete resource removal:&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-bash"&gt;aws rds delete-db-instance --db-instance-identifier &amp;lt;aurora-pgsql-zetl-instance-1&amp;gt; --skip-final-snapshot --region &amp;lt;us-east-1&amp;gt;

aws rds delete-db-cluster --db-cluster-identifier &amp;lt;aurora-pgsql-zetl&amp;gt; --skip-final-snapshot --region &amp;lt;us-east-1&amp;gt;

aws rds delete-db-cluster-parameter-group --db-cluster-parameter-group-name &amp;lt;aurora-zetl-cluster-pg&amp;gt; --region &amp;lt;us-east-1&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;In this post, you learned how to configure zero-ETL integration between Aurora PostgreSQL and your Amazon SageMaker AI using AWS CLI. This integration automatically replicates your PostgreSQL data to a lakehouse in near real time, removing the need for custom ETL pipelines.&lt;/p&gt;
&lt;p&gt;As you move forward, consider expanding this zero-ETL approach to more supported data sources, such as Amazon RDS for MySQL and Amazon DynamoDB. This creates a centralized data access strategy across your company. You can also explore advanced analytics scenarios by combining zero-ETL integrations with Amazon Redshift capabilities. These include large-scale SQL analytics, Amazon Redshift ML for in-database ML, and federated queries that span multiple data lakes and warehouses. These integrations provide the foundation for building a near real-time data platform that scales with your business needs.&lt;/p&gt;
&lt;p&gt;To get started, see the &lt;a href="https://aws.amazon.com/what-is/zero-etl/" target="_blank" rel="noopener"&gt;AWS zero-ETL documentation&lt;/a&gt; for setup guidance, supported configurations, troubleshooting integrations, and architectural best practices.&lt;/p&gt;
&lt;p&gt;Related posts and references:&lt;/p&gt;
&lt;ul&gt;
 &lt;li&gt;&lt;a href="https://aws.amazon.com/rds/aurora/" target="_blank" rel="noopener"&gt;Amazon Aurora&lt;/a&gt;&lt;/li&gt;
 &lt;li&gt;&lt;a href="https://console.aws.amazon.com/rds/" target="_blank" rel="noopener"&gt;AWS Management Console&lt;/a&gt;&lt;/li&gt;
 &lt;li&gt;&lt;a href="https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/CHAP_Tutorials.html" target="_blank" rel="noopener"&gt;Amazon Aurora tutorials and sample code&lt;/a&gt;&lt;/li&gt;
 &lt;li&gt;&lt;a href="https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/zero-etl.html" target="_blank" rel="noopener"&gt;Amazon Aurora zero-ETL integrations&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p style="clear: both"&gt;&lt;/p&gt;
&lt;hr style="width: 100%"&gt;
&lt;h2&gt;About the authors&lt;/h2&gt;
&lt;footer&gt;
 &lt;div class="blog-author-box" style="padding-top: 2.0em"&gt;
  &lt;div class="blog-author-image" style="margin-right: 1.0em"&gt;
   &lt;img loading="lazy" class="alignnone size-full" src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/24/BDB-5743-2.jpg" alt="Apurwa Pawar" width="100" height="133"&gt;
  &lt;/div&gt;
  &lt;h3 class="lb-h4"&gt;Apurwa Pawar&lt;/h3&gt;
  &lt;p style="overflow: hidden"&gt;Apurwa is a Solutions Architect at AWS and a Data Analytics and AI enthusiast. She helps customers build their modern data strategy and cloud-native innovative solutions on AWS. She works with enterprise organizations across industries including healthcare, life sciences, financial services, and hospitality, partnering with engineering and business leadership to turn data into insight and action.&lt;/p&gt;
 &lt;/div&gt;
 &lt;div class="blog-author-box" style="padding-top: 2.0em"&gt;
  &lt;div class="blog-author-image" style="margin-right: 1.0em"&gt;
   &lt;img loading="lazy" class="alignnone size-full" src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/24/BDB-5743-3.jpg" alt="Sarika Subramaniam" width="100" height="133"&gt;
  &lt;/div&gt;
  &lt;h3 class="lb-h4"&gt;Sarika Subramaniam&lt;/h3&gt;
  &lt;p style="overflow: hidden"&gt;Sarika is a Solutions Architect at AWS, specializing in analytics and data platforms. She helps customers design scalable, secure, and cloud-based modern data architectures on AWS. She works with enterprise customers across industries, partnering with engineering teams to build innovative data solutions and drive business outcomes.&lt;/p&gt;
 &lt;/div&gt;
&lt;/footer&gt;</content:encoded>
					
		
		
			</item>
		<item>
		<title>Getting started with Apache Iceberg write support in Amazon Redshift – Part 3</title>
		<link>https://aws.amazon.com/blogs/big-data/getting-started-with-apache-iceberg-write-support-in-amazon-redshift-part-3/</link>
		
		<dc:creator><![CDATA[Raghu Kuppala]]></dc:creator>
		<pubDate>Mon, 28 Sep 2026 15:48:35 +0000</pubDate>
				<category><![CDATA[Advanced (300)]]></category>
		<category><![CDATA[Amazon Redshift]]></category>
		<category><![CDATA[Technical How-to]]></category>
		<guid isPermaLink="false">86614ebec853787349c302007d0e2dc80a14367f</guid>

					<description>Amazon Redshift now supports evolving Apache Iceberg table schemas and partition layouts through ALTER statements, with no data rewrites or pipeline rebuilds. In this final post of the series, you rename, add, drop, and widen columns, evolve partitions, and create AWS Lake Formation resource links for governed cross-engine access to Amazon S3 Tables.</description>
										<content:encoded>&lt;p&gt;Production data is always evolving. Tables gain and lose columns, outgrow their data types, and get re-partitioned as query patterns shift. Multiple engines often need to read the same data. These changes used to mean expensive data rewrites or rebuilt pipelines. &lt;a href="https://iceberg.apache.org/" target="_blank" rel="noopener"&gt;Apache Iceberg&lt;/a&gt; makes them metadata-only operations, and &lt;a href="https://aws.amazon.com/redshift/" target="_blank" rel="noopener"&gt;Amazon Redshift&lt;/a&gt; now supports evolving schemas and partitioning layouts through ALTER statements, with no data rewrites and no pipeline rebuilds. You can also create &lt;a href="https://aws.amazon.com/lake-formation/" target="_blank" rel="noopener"&gt;AWS Lake Formation&lt;/a&gt; resource links in the catalog of &lt;a href="https://aws.amazon.com/s3/features/tables/" target="_blank" rel="noopener"&gt;Amazon S3 Tables&lt;/a&gt;, a capability of Amazon Simple Storage Service (Amazon S3), for centralized cross-engine governance.&lt;/p&gt;
&lt;p&gt;In &lt;a href="https://aws.amazon.com/blogs/big-data/getting-started-with-apache-iceberg-write-support-in-amazon-redshift-part-1/" target="_blank" rel="noopener"&gt;Part 1&lt;/a&gt;, you created Apache Iceberg tables and wrote data directly from Amazon Redshift to your data lake, setting up external schemas, creating tables in both &lt;a href="https://aws.amazon.com/s3/" target="_blank" rel="noopener"&gt;Amazon Simple Storage Service (Amazon S3)&lt;/a&gt; and Amazon S3 Tables, and performing INSERT operations with full ACID (Atomicity, Consistency, Isolation, Durability) compliance. In &lt;a href="https://aws.amazon.com/blogs/big-data/getting-started-with-apache-iceberg-write-support-in-amazon-redshift-part-2/" target="_blank" rel="noopener"&gt;Part 2&lt;/a&gt;, you performed DELETE, UPDATE, and MERGE operations to modify data at the row level and synchronize staging and production tables.&lt;/p&gt;
&lt;p&gt;In this post, you use the &lt;code&gt;customer&lt;/code&gt; and &lt;code&gt;orders&lt;/code&gt; datasets from the previous posts to evolve Iceberg table schemas and partitioning with ALTER operations. You also create an AWS Lake Formation resource link in the S3 Tables catalog to share tables with other analytics engines under a single, centralized permission model.&lt;/p&gt;
&lt;h2 id="solution-overview"&gt;Solution overview&lt;/h2&gt;
&lt;p&gt;This solution demonstrates ALTER operations for Apache Iceberg tables in Amazon Redshift and Lake Formation resource link creation for the S3 Tables catalog. The walkthrough includes the following key operations:&lt;/p&gt;
&lt;ul&gt;
 &lt;li&gt;&lt;strong&gt;ALTER TABLE RENAME COLUMN&lt;/strong&gt; – Rename existing columns without changing data types or partition specs.&lt;/li&gt;
 &lt;li&gt;&lt;strong&gt;ALTER TABLE ADD/DROP COLUMN&lt;/strong&gt; – Add new columns or remove existing columns as metadata-only operations.&lt;/li&gt;
 &lt;li&gt;&lt;strong&gt;ALTER TABLE ALTER COLUMN&lt;/strong&gt; – Widen column data types (for example, INT to BIGINT) without rewriting data.&lt;/li&gt;
 &lt;li&gt;&lt;strong&gt;ALTER TABLE SET TABLE PROPERTIES&lt;/strong&gt; – Change compression type for future writes.&lt;/li&gt;
 &lt;li&gt;&lt;strong&gt;ALTER TABLE ADD/DROP/REPLACE PARTITION FIELD&lt;/strong&gt; – Evolve partition specs without re-partitioning existing data.&lt;/li&gt;
 &lt;li&gt;&lt;strong&gt;Lake Formation resource link&lt;/strong&gt; – Create a resource link in the S3 Tables catalog for centralized access governance.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The following diagram shows the end-to-end architecture:&lt;/p&gt;
&lt;div style="width: 810px" class="wp-caption alignnone"&gt;
 &lt;a href="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/24/BDB-5994-1.png" target="_blank" rel="noopener"&gt;&lt;img src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/24/BDB-5994-1.png" alt="Architecture diagram of Amazon Redshift running ALTER operations on Iceberg tables in S3 Tables, with Lake Formation resource links providing access from Amazon Athena and other engines" width="800"&gt;&lt;/a&gt;
 &lt;p class="wp-caption-text"&gt;Figure 1: Architecture showing Amazon Redshift performing ALTER operations on Iceberg tables in S3 Tables, with Lake Formation resource links providing access from Amazon Athena and other engines&lt;/p&gt;
&lt;/div&gt;
&lt;h2 id="prerequisites"&gt;Prerequisites&lt;/h2&gt;
&lt;p&gt;Complete the setup from &lt;a href="https://aws.amazon.com/blogs/big-data/getting-started-with-apache-iceberg-write-support-in-amazon-redshift-part-1/" target="_blank" rel="noopener"&gt;Part 1&lt;/a&gt; and &lt;a href="https://aws.amazon.com/blogs/big-data/getting-started-with-apache-iceberg-write-support-in-amazon-redshift-part-2/" target="_blank" rel="noopener"&gt;Part 2&lt;/a&gt;, including:&lt;/p&gt;
&lt;ul&gt;
 &lt;li&gt;An Amazon Redshift data warehouse (provisioned or Serverless) on &lt;a href="https://docs.aws.amazon.com/redshift/latest/mgmt/cluster-versions.html#cluster-version-201" target="_blank" rel="noopener"&gt;patch 201&lt;/a&gt; or higher.&lt;/li&gt;
 &lt;li&gt;The AWS Identity and Access Management (IAM) role (&lt;code&gt;RedshifticebergRole&lt;/code&gt;) with permissions for Amazon S3, &lt;a href="https://aws.amazon.com/glue/" target="_blank" rel="noopener"&gt;AWS Glue Data Catalog&lt;/a&gt;, and Lake Formation.&lt;/li&gt;
 &lt;li&gt;The &lt;code&gt;customer&lt;/code&gt; table in a standard Amazon S3 bucket (AWS Glue catalog: &lt;code&gt;customer_db&lt;/code&gt;).&lt;/li&gt;
 &lt;li&gt;The &lt;code&gt;orders&lt;/code&gt; table in an Amazon S3 table bucket (&lt;code&gt;iceberg-write-blog@s3tablescatalog&lt;/code&gt;).&lt;/li&gt;
 &lt;li&gt;Access to an IAM role that is a Lake Formation data lake administrator.&lt;/li&gt;
 &lt;li&gt;AWS Glue Data Catalog integrated with S3 Tables (&lt;code&gt;s3tablescatalog&lt;/code&gt; exists).&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="schema-evolution-with-alter-table"&gt;Schema evolution with ALTER TABLE&lt;/h2&gt;
&lt;p&gt;With ALTER TABLE, you can change Iceberg table definitions, including schema, partition specs, and properties, without rewriting stored data. Each operation updates only metadata. The table structure changes instantly while existing data files remain untouched. This helps make schema evolution, partition adjustments, and property updates safe to run on production tables.&lt;/p&gt;
&lt;h3 id="add-a-column"&gt;Add a column&lt;/h3&gt;
&lt;p&gt;You can add a new column to an Iceberg table using ALTER TABLE. Each new column is added with a unique field ID that Iceberg uses for column tracking across schema evolution. Existing rows return NULL for the newly added column.&lt;/p&gt;
&lt;p&gt;Verify the current schema:&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-sql"&gt;SHOW TABLE dev.demo_iceberg.customer;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div style="width: 606px" class="wp-caption alignnone"&gt;
 &lt;a href="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/24/BDB-5994-2.png" target="_blank" rel="noopener"&gt;&lt;img src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/24/BDB-5994-2.png" alt="SHOW TABLE output listing the current columns of the customer table" width="596"&gt;&lt;/a&gt;
 &lt;p class="wp-caption-text"&gt;Figure 2: SHOW TABLE output showing the current customer table schema&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;Add the column:&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-sql"&gt;-- Add a loyalty_tier column to the customer table
ALTER TABLE dev.demo_iceberg.customer
ADD COLUMN loyalty_tier VARCHAR;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Verify the schema change:&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-sql"&gt;SHOW TABLE dev.demo_iceberg.customer;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div style="width: 598px" class="wp-caption alignnone"&gt;
 &lt;a href="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/24/BDB-5994-3.png" target="_blank" rel="noopener"&gt;&lt;img src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/24/BDB-5994-3.png" alt="SHOW TABLE output showing the new loyalty_tier column added to the customer schema" width="588"&gt;&lt;/a&gt;
 &lt;p class="wp-caption-text"&gt;Figure 3: SHOW TABLE output showing the loyalty_tier column added to the schema&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;The following output shows the new loyalty_tier column as NULL for existing rows:&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-sql"&gt;SELECT customer_id, customer_name, city, loyalty_tier
FROM dev.demo_iceberg.customer
ORDER BY customer_id;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div style="width: 810px" class="wp-caption alignnone"&gt;
 &lt;a href="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/24/BDB-5994-4.png" target="_blank" rel="noopener"&gt;&lt;img src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/24/BDB-5994-4.png" alt="Query results showing loyalty_tier as NULL for existing customer rows" width="800"&gt;&lt;/a&gt;
 &lt;p class="wp-caption-text"&gt;Figure 4: Query results showing loyalty_tier as NULL for existing rows&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;Populate the new column by aggregating order totals from the &lt;code&gt;orders&lt;/code&gt; table in S3 Tables:&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-sql"&gt;-- Set loyalty_tier based on total spend from orders
UPDATE dev.demo_iceberg.customer
SET loyalty_tier = CASE
WHEN a.total_spend &amp;gt; 300 THEN 'Gold'
ELSE 'Silver'
END
FROM (
SELECT customer_id, SUM(total_order_amt) AS total_spend
FROM "iceberg-write-blog@s3tablescatalog".iceberg_write_namespace.orders
GROUP BY customer_id
) AS a
WHERE dev.demo_iceberg.customer.customer_id = a.customer_id;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;The following output shows customer loyalty tiers after the update:&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-sql"&gt;SELECT customer_id, customer_name, loyalty_tier
FROM dev.demo_iceberg.customer
ORDER BY customer_id;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div style="width: 810px" class="wp-caption alignnone"&gt;
 &lt;a href="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/24/BDB-5994-5.png" target="_blank" rel="noopener"&gt;&lt;img src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/24/BDB-5994-5.png" alt="Customer table query results showing Gold and Silver loyalty tiers" width="800"&gt;&lt;/a&gt;
 &lt;p class="wp-caption-text"&gt;Figure 5: Customer table showing Gold and Silver loyalty tiers&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Customer IDs 11, 13, and 15 show NULL for &lt;code&gt;loyalty_tier&lt;/code&gt; because they have no matching orders in the &lt;code&gt;orders&lt;/code&gt; table.&lt;/p&gt;
&lt;h3 id="drop-a-column"&gt;Drop a column&lt;/h3&gt;
&lt;p&gt;Remove columns that are no longer needed. The column is removed from the current schema, but data in existing files remains untouched and simply becomes invisible to queries.&lt;/p&gt;
&lt;p&gt;Verify the current schema:&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-sql"&gt;SHOW TABLE dev.demo_iceberg.customer;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div style="width: 598px" class="wp-caption alignnone"&gt;
 &lt;a href="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/24/BDB-5994-6.png" target="_blank" rel="noopener"&gt;&lt;img src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/24/BDB-5994-6.png" alt="SHOW TABLE output showing the customer schema before dropping loyalty_tier" width="588"&gt;&lt;/a&gt;
 &lt;p class="wp-caption-text"&gt;Figure 6: SHOW TABLE output showing the current customer table schema before dropping loyalty_tier&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;Drop the column:&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-sql"&gt;-- Drop the loyalty_tier column
ALTER TABLE dev.demo_iceberg.customer
DROP COLUMN loyalty_tier;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Verify the schema change:&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-sql"&gt;SHOW TABLE dev.demo_iceberg.customer;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div style="width: 606px" class="wp-caption alignnone"&gt;
 &lt;a href="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/24/BDB-5994-7.png" target="_blank" rel="noopener"&gt;&lt;img src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/24/BDB-5994-7.png" alt="SHOW TABLE output showing the customer schema after loyalty_tier is dropped" width="596"&gt;&lt;/a&gt;
 &lt;p class="wp-caption-text"&gt;Figure 7: SHOW TABLE output showing the customer table schema after loyalty_tier is dropped&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;Verify the column is dropped:&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-sql"&gt;SELECT * FROM dev.demo_iceberg.customer
ORDER BY customer_id;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div style="width: 810px" class="wp-caption alignnone"&gt;
 &lt;a href="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/24/BDB-5994-8.png" target="_blank" rel="noopener"&gt;&lt;img src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/24/BDB-5994-8.png" alt="Query results confirming the loyalty_tier column no longer appears" width="800"&gt;&lt;/a&gt;
 &lt;p class="wp-caption-text"&gt;Figure 8: Query results confirming the loyalty_tier column has been dropped&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; To drop a column used in the current partition spec, first drop or replace the partition field, then drop the column.&lt;/p&gt;
&lt;h3 id="rename-a-column"&gt;Rename a column&lt;/h3&gt;
&lt;p&gt;Rename a column without affecting data types or partition specs:&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-sql"&gt;-- Rename city to location
ALTER TABLE dev.demo_iceberg.customer
RENAME COLUMN city TO location;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;The following output confirms the column has been renamed to location:&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-sql"&gt;SELECT customer_id, customer_name, location
FROM dev.demo_iceberg.customer
ORDER BY customer_id;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div style="width: 612px" class="wp-caption alignnone"&gt;
 &lt;a href="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/24/BDB-5994-9.png" target="_blank" rel="noopener"&gt;&lt;img src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/24/BDB-5994-9.png" alt="Query results showing the city column renamed to location" width="602"&gt;&lt;/a&gt;
 &lt;p class="wp-caption-text"&gt;Figure 9: Query results showing the renamed column location&lt;/p&gt;
&lt;/div&gt;
&lt;h3 id="widen-a-column-type"&gt;Widen a column type&lt;/h3&gt;
&lt;p&gt;Widen a column’s data type without rewriting data. This is useful when your data outgrows the original precision, for example when order amounts exceed the original decimal range.&lt;/p&gt;
&lt;p&gt;Verify the current column type:&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-sql"&gt;SHOW TABLE "iceberg-write-blog@s3tablescatalog".iceberg_write_namespace.orders;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div style="width: 703px" class="wp-caption alignnone"&gt;
 &lt;a href="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/24/BDB-5994-10.png" target="_blank" rel="noopener"&gt;&lt;img src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/24/BDB-5994-10.png" alt="SHOW TABLE output showing total_order_amt as DECIMAL(10,2)" width="693"&gt;&lt;/a&gt;
 &lt;p class="wp-caption-text"&gt;Figure 10: SHOW TABLE output showing total_order_amt as DECIMAL(10,2)&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;Now run the ALTER to widen the column:&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-sql"&gt;-- Widen total_order_amt to support larger order values
ALTER TABLE "iceberg-write-blog@s3tablescatalog".iceberg_write_namespace.orders
ALTER COLUMN total_order_amt TYPE DECIMAL(18,2);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Verify the updated column type:&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-sql"&gt;SHOW TABLE "iceberg-write-blog@s3tablescatalog".iceberg_write_namespace.orders;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div style="width: 688px" class="wp-caption alignnone"&gt;
 &lt;a href="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/24/BDB-5994-11.png" target="_blank" rel="noopener"&gt;&lt;img src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/24/BDB-5994-11.png" alt="SHOW TABLE output showing total_order_amt widened to DECIMAL(18,2)" width="678"&gt;&lt;/a&gt;
 &lt;p class="wp-caption-text"&gt;Figure 11: SHOW TABLE output confirming total_order_amt widened to DECIMAL(18,2)&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Amazon Redshift supports safe type promotions (for example, INT to BIGINT, FLOAT to DOUBLE, DECIMAL(10,2) to DECIMAL(18,2)). Plan column types accordingly for future growth.&lt;/p&gt;
&lt;h3 id="set-table-properties"&gt;Set table properties&lt;/h3&gt;
&lt;p&gt;Change the compression type for future writes:&lt;/p&gt;
&lt;p&gt;Verify the current compression type:&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-sql"&gt;SHOW TABLE "iceberg-write-blog@s3tablescatalog".iceberg_write_namespace.orders;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div style="width: 810px" class="wp-caption alignnone"&gt;
 &lt;a href="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/24/BDB-5994-12.png" target="_blank" rel="noopener"&gt;&lt;img src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/24/BDB-5994-12.png" alt="SHOW TABLE output showing the orders table compression type before the change" width="800"&gt;&lt;/a&gt;
 &lt;p class="wp-caption-text"&gt;Figure 12: SHOW TABLE output showing the current compression type before the update&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;Now run the ALTER to change the compression type:&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-sql"&gt;-- Switch to zstd compression for better ratios
ALTER TABLE "iceberg-write-blog@s3tablescatalog".iceberg_write_namespace.orders
SET TABLE PROPERTIES ('compression_type'='zstd');&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;The following SHOW TABLE output confirms the updated compression setting:&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-sql"&gt;SHOW TABLE "iceberg-write-blog@s3tablescatalog".iceberg_write_namespace.orders;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div style="width: 810px" class="wp-caption alignnone"&gt;
 &lt;a href="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/24/BDB-5994-13.png" target="_blank" rel="noopener"&gt;&lt;img src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/24/BDB-5994-13.png" alt="SHOW TABLE output showing compression_type set to zstd" width="800"&gt;&lt;/a&gt;
 &lt;p class="wp-caption-text"&gt;Figure 13: SHOW TABLE output showing compression_type set to zstd&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; This affects only future writes. Existing data files retain their original compression.&lt;/p&gt;
&lt;h2 id="partition-evolution"&gt;Partition evolution&lt;/h2&gt;
&lt;p&gt;A powerful feature of Iceberg is partition evolution, the ability to change how a table is partitioned without rewriting existing data. Amazon Redshift writes new data with the updated partition scheme, while existing data remains in the old layout. Query engines handle both layouts transparently.&lt;/p&gt;
&lt;h3 id="adding-a-partition-field"&gt;Adding a partition field&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;orders&lt;/code&gt; table from Part 1 is partitioned by &lt;code&gt;DAY(order_date)&lt;/code&gt;. Add an additional bucket partition to distribute data across hash buckets:&lt;/p&gt;
&lt;p&gt;Verify the current partition spec:&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-sql"&gt;SHOW TABLE "iceberg-write-blog@s3tablescatalog".iceberg_write_namespace.orders;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div style="width: 694px" class="wp-caption alignnone"&gt;
 &lt;a href="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/24/BDB-5994-14.png" target="_blank" rel="noopener"&gt;&lt;img src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/24/BDB-5994-14.png" alt="SHOW TABLE output showing the orders partition spec before adding a field" width="684"&gt;&lt;/a&gt;
 &lt;p class="wp-caption-text"&gt;Figure 14: SHOW TABLE output showing the current partition spec before adding a partition field&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;Add the partition field:&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-sql"&gt;-- Add bucket partitioning on customer_id
ALTER TABLE "iceberg-write-blog@s3tablescatalog".iceberg_write_namespace.orders
ADD PARTITION FIELD bucket(16, customer_id);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;After this change, new data is partitioned by both &lt;code&gt;DAY(order_date)&lt;/code&gt; and &lt;code&gt;bucket(16, customer_id)&lt;/code&gt;, while existing data remains in the original day-only layout.&lt;/p&gt;
&lt;p&gt;Verify the updated spec:&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-sql"&gt;SHOW TABLE "iceberg-write-blog@s3tablescatalog".iceberg_write_namespace.orders;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div style="width: 702px" class="wp-caption alignnone"&gt;
 &lt;a href="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/24/BDB-5994-15.png" target="_blank" rel="noopener"&gt;&lt;img src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/24/BDB-5994-15.png" alt="SHOW TABLE output showing partition spec with DAY(order_date) and bucket(16, customer_id)" width="692"&gt;&lt;/a&gt;
 &lt;p class="wp-caption-text"&gt;Figure 15: SHOW TABLE output showing the updated partition spec with DAY(order_date) and bucket(16, customer_id)&lt;/p&gt;
&lt;/div&gt;
&lt;h3 id="replacing-a-partition-field"&gt;Replacing a partition field&lt;/h3&gt;
&lt;p&gt;Instead of separately dropping and adding, use REPLACE PARTITION FIELD as a single atomic operation. This is the recommended approach when swapping one transform for another on the same source column, because it makes the intent explicit and avoids a transient state where the table is unpartitioned between operations.&lt;/p&gt;
&lt;p&gt;Verify the current partition spec:&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-sql"&gt;SHOW TABLE "iceberg-write-blog@s3tablescatalog".iceberg_write_namespace.orders;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div style="width: 702px" class="wp-caption alignnone"&gt;
 &lt;a href="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/24/BDB-5994-16.png" target="_blank" rel="noopener"&gt;&lt;img src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/24/BDB-5994-16.png" alt="SHOW TABLE output showing the current DAY(order_date) partition spec" width="692"&gt;&lt;/a&gt;
 &lt;p class="wp-caption-text"&gt;Figure 16: SHOW TABLE output showing the current partition spec with DAY(order_date) and bucket(16, customer_id)&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;Replace the partition field:&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-sql"&gt;-- Replace daily partitioning with monthly
ALTER TABLE "iceberg-write-blog@s3tablescatalog".iceberg_write_namespace.orders
REPLACE PARTITION FIELD DAY(order_date) WITH MONTH(order_date);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;After this change:&lt;/p&gt;
&lt;ul&gt;
 &lt;li&gt;Existing data remains in day-based partition folders.&lt;/li&gt;
 &lt;li&gt;Amazon Redshift writes new data into month-based partition folders.&lt;/li&gt;
 &lt;li&gt;The query engine reads both layouts transparently.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Confirm the new partition spec:&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-sql"&gt;SHOW TABLE "iceberg-write-blog@s3tablescatalog".iceberg_write_namespace.orders;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div style="width: 810px" class="wp-caption alignnone"&gt;
 &lt;a href="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/24/BDB-5994-17.png" target="_blank" rel="noopener"&gt;&lt;img src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/24/BDB-5994-17.png" alt="SHOW TABLE output confirming the partition field replaced with MONTH(order_date)" width="800"&gt;&lt;/a&gt;
 &lt;p class="wp-caption-text"&gt;Figure 17: SHOW TABLE output confirming the partition field replaced with MONTH(order_date)&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;Insert new data and verify that both partition layouts are queryable:&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-sql"&gt;-- New data follows monthly partitioning
INSERT INTO "iceberg-write-blog@s3tablescatalog".iceberg_write_namespace.orders
(order_date, order_id, customer_id, total_order_amt, total_order_tax_amt,
tax_pct, order_created_at_tz, is_active_ind)
VALUES
('2025-01-15', 1018, 3, 210.00, 16.80, 0.08, '2025-01-15 09:00:00-06:00', true);
-- Query spans both old (daily) and new (monthly) layouts transparently
SELECT order_id, order_date, total_order_amt
FROM "iceberg-write-blog@s3tablescatalog".iceberg_write_namespace.orders
WHERE order_date &amp;gt;= '2024-11-01'
ORDER BY order_date;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div style="width: 615px" class="wp-caption alignnone"&gt;
 &lt;a href="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/24/BDB-5994-18.png" target="_blank" rel="noopener"&gt;&lt;img src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/24/BDB-5994-18.png" alt="Query results spanning both the daily and monthly partition layouts" width="605"&gt;&lt;/a&gt;
 &lt;p class="wp-caption-text"&gt;Figure 18: Query results spanning both partition layouts&lt;/p&gt;
&lt;/div&gt;
&lt;h3 id="converting-to-a-multi-level-partition"&gt;Converting to a multi-level partition&lt;/h3&gt;
&lt;p&gt;Iceberg supports multi-level (composite) partition specs, where data is organized by more than one partition field. You can evolve an existing single-level spec into a multi-level spec by adding partition fields one at a time. Each ADD PARTITION FIELD is a lightweight metadata operation, and no data is rewritten.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;orders&lt;/code&gt; table is currently partitioned by &lt;code&gt;MONTH(order_date)&lt;/code&gt; and &lt;code&gt;bucket(16, customer_id)&lt;/code&gt;. Add one more partition field to create a three-level spec:&lt;/p&gt;
&lt;p&gt;Verify the current partition spec:&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-sql"&gt;SHOW TABLE "iceberg-write-blog@s3tablescatalog".iceberg_write_namespace.orders;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div style="width: 810px" class="wp-caption alignnone"&gt;
 &lt;a href="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/24/BDB-5994-19.png" target="_blank" rel="noopener"&gt;&lt;img src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/24/BDB-5994-19.png" alt="SHOW TABLE output showing the two-level partition spec of MONTH(order_date) and bucket(16, customer_id)" width="800"&gt;&lt;/a&gt;
 &lt;p class="wp-caption-text"&gt;Figure 19: SHOW TABLE output showing the current two-level partition spec of MONTH(order_date) and bucket(16, customer_id)&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;Add partition field to build the three-level spec:&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-sql"&gt;-- Add a day-level partition field on order_created_at_tz
ALTER TABLE "iceberg-write-blog@s3tablescatalog".iceberg_write_namespace.orders
ADD PARTITION FIELD day(order_created_at_tz);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Verify the new multi-level partition spec:&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-sql"&gt;SHOW TABLE "iceberg-write-blog@s3tablescatalog".iceberg_write_namespace.orders;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div style="width: 810px" class="wp-caption alignnone"&gt;
 &lt;a href="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/24/BDB-5994-20.png" target="_blank" rel="noopener"&gt;&lt;img src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/24/BDB-5994-20.png" alt="SHOW TABLE output showing the three-level MONTH, bucket, and day partition spec" width="800"&gt;&lt;/a&gt;
 &lt;p class="wp-caption-text"&gt;Figure 20: SHOW TABLE output showing the three-level partition spec of MONTH(order_date), bucket(16, customer_id), and day(order_created_at_tz)&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;After these changes:&lt;/p&gt;
&lt;ul&gt;
 &lt;li&gt;Existing data remains in the original single-level layout (month-based folders).&lt;/li&gt;
 &lt;li&gt;Amazon Redshift writes new data into the multi-level layout (month, then bucket, then day folders).&lt;/li&gt;
 &lt;li&gt;The query engine reads both layouts transparently.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="dropping-partition-fields-from-a-multi-level-partition"&gt;Dropping partition fields from a multi-level partition&lt;/h3&gt;
&lt;p&gt;You can also evolve in the other direction by removing partition fields from a multi-level spec to simplify the partition layout. Like adding fields, dropping a partition field is a metadata-only operation and removes one field per statement.&lt;/p&gt;
&lt;p&gt;Verify the current multi-level partition spec:&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-sql"&gt;SHOW TABLE "iceberg-write-blog@s3tablescatalog".iceberg_write_namespace.orders;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div style="width: 810px" class="wp-caption alignnone"&gt;
 &lt;a href="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/24/BDB-5994-21.png" target="_blank" rel="noopener"&gt;&lt;img src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/24/BDB-5994-21.png" alt="SHOW TABLE output showing the three-level partition spec before dropping fields" width="800"&gt;&lt;/a&gt;
 &lt;p class="wp-caption-text"&gt;Figure 21: SHOW TABLE output showing the three-level partition spec before dropping fields&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;Drop the partition fields one at a time:&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-sql"&gt;-- Drop the bucket partition field
ALTER TABLE "iceberg-write-blog@s3tablescatalog".iceberg_write_namespace.orders
DROP PARTITION FIELD bucket(16, customer_id);
-- Drop the day-level partition field
ALTER TABLE "iceberg-write-blog@s3tablescatalog".iceberg_write_namespace.orders
DROP PARTITION FIELD day(order_created_at_tz);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Verify the table is back to its original single-level spec:&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-sql"&gt;SHOW TABLE "iceberg-write-blog@s3tablescatalog".iceberg_write_namespace.orders;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div style="width: 810px" class="wp-caption alignnone"&gt;
 &lt;a href="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/24/BDB-5994-22.png" target="_blank" rel="noopener"&gt;&lt;img src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/24/BDB-5994-22.png" alt="SHOW TABLE output confirming the table back to a single-level MONTH(order_date) spec" width="800"&gt;&lt;/a&gt;
 &lt;p class="wp-caption-text"&gt;Figure 22: SHOW TABLE output confirming the table is back to a single-level MONTH(order_date) partition spec&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;After dropping a partition field:&lt;/p&gt;
&lt;ul&gt;
 &lt;li&gt;Data written under the dropped field’s layout stays in place and remains queryable.&lt;/li&gt;
 &lt;li&gt;Amazon Redshift writes new data using only the remaining partition fields.&lt;/li&gt;
 &lt;li&gt;Queries that filtered on the dropped field still work, but they no longer benefit from partition pruning on that field for newly written data.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="supported-partition-transforms"&gt;Supported partition transforms&lt;/h3&gt;
&lt;p&gt;The following table lists the partition transforms available for Iceberg tables in Amazon Redshift:&lt;/p&gt;
&lt;table border="1px" width="100%" cellpadding="10px"&gt;
 &lt;tbody&gt;
  &lt;tr&gt;
   &lt;td&gt;&lt;strong&gt;Partition transform&lt;/strong&gt;&lt;/td&gt;
   &lt;td&gt;&lt;strong&gt;Syntax example&lt;/strong&gt;&lt;/td&gt;
   &lt;td&gt;&lt;strong&gt;What it does&lt;/strong&gt;&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;Year&lt;/td&gt;
   &lt;td&gt;&lt;code&gt;year(order_date)&lt;/code&gt;&lt;/td&gt;
   &lt;td&gt;Groups data into yearly partitions based on a date or timestamp column.&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;Month&lt;/td&gt;
   &lt;td&gt;&lt;code&gt;month(order_date)&lt;/code&gt;&lt;/td&gt;
   &lt;td&gt;Groups data into monthly partitions based on a date or timestamp column.&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;Day&lt;/td&gt;
   &lt;td&gt;&lt;code&gt;day(order_date)&lt;/code&gt;&lt;/td&gt;
   &lt;td&gt;Groups data into daily partitions based on a date or timestamp column.&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;Hour&lt;/td&gt;
   &lt;td&gt;&lt;code&gt;hour(event_ts)&lt;/code&gt;&lt;/td&gt;
   &lt;td&gt;Groups data into hourly partitions based on a timestamp column.&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;Bucket&lt;/td&gt;
   &lt;td&gt;&lt;code&gt;bucket(16, customer_id)&lt;/code&gt;&lt;/td&gt;
   &lt;td&gt;Distributes data across N hash buckets for even distribution on high-cardinality columns.&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;Truncate&lt;/td&gt;
   &lt;td&gt;&lt;code&gt;truncate(3, zip_code)&lt;/code&gt;&lt;/td&gt;
   &lt;td&gt;Truncates column values to a fixed width W for grouping similar values together.&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;Identity&lt;/td&gt;
   &lt;td&gt;&lt;code&gt;identity(region)&lt;/code&gt;&lt;/td&gt;
   &lt;td&gt;Partitions by the exact column value with no transformation applied.&lt;/td&gt;
  &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; A column that is already part of an existing partition field can’t be used in a new partition field. Drop or replace the existing field first.&lt;/p&gt;
&lt;h2 id="accessing-s3-tables-with-external-schemas"&gt;Accessing S3 Tables with external schemas&lt;/h2&gt;
&lt;p&gt;Lake Formation resource links provide cross-engine access to your S3 Tables through centralized governance. You create a resource link in the default AWS Glue Data Catalog that points to your S3 Tables database. Amazon Redshift, &lt;a href="https://aws.amazon.com/athena/" target="_blank" rel="noopener"&gt;Amazon Athena&lt;/a&gt;, &lt;a href="https://aws.amazon.com/emr/" target="_blank" rel="noopener"&gt;Amazon EMR&lt;/a&gt;, and other engines can then discover and query the tables using a single permission model.&lt;/p&gt;
&lt;div style="width: 810px" class="wp-caption alignnone"&gt;
 &lt;a href="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/24/BDB-5994-23.png" target="_blank" rel="noopener"&gt;&lt;img src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/24/BDB-5994-23.png" alt="Diagram of S3 Tables integration with AWS Glue Data Catalog and Lake Formation" width="800"&gt;&lt;/a&gt;
 &lt;p class="wp-caption-text"&gt;Figure 23: S3 Tables integration with AWS Glue Data Catalog and Lake Formation&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;For the complete setup walkthrough, including Lake Formation prerequisites, resource link creation, and permission grants, see &lt;a href="https://aws.amazon.com/blogs/big-data/optimize-amazon-s3-tables-queries-with-amazon-redshift/" target="_blank" rel="noopener"&gt;Optimize Amazon S3 Tables queries with Amazon Redshift&lt;/a&gt;. For conceptual details on resource links and S3 Tables catalog integration, see &lt;a href="https://docs.aws.amazon.com/lake-formation/latest/dg/resource-links-about.html" target="_blank" rel="noopener"&gt;About resource links&lt;/a&gt; and &lt;a href="https://docs.aws.amazon.com/lake-formation/latest/dg/create-s3-tables-catalog.html" target="_blank" rel="noopener"&gt;Creating an S3 Tables catalog&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The following steps show how to query S3 Tables through a resource link after completing the setup from the referenced blog.&lt;/p&gt;
&lt;h3 id="grant-access-to-the-resource-link"&gt;Grant access to the resource link&lt;/h3&gt;
&lt;p&gt;In the Lake Formation console, the resource link appears as a database named &lt;code&gt;iceberg_write_blog_rl&lt;/code&gt; (type: Resource link). To grant access to the resource link:&lt;/p&gt;
&lt;ol type="1"&gt;
 &lt;li&gt;In the Lake Formation console, choose &lt;strong&gt;Databases&lt;/strong&gt;.&lt;/li&gt;
 &lt;li&gt;Locate &lt;code&gt;iceberg_write_blog_rl&lt;/code&gt; (type: Resource link).&lt;/li&gt;
 &lt;li&gt;Choose &lt;strong&gt;Actions&lt;/strong&gt;, then &lt;strong&gt;Grant&lt;/strong&gt;.&lt;/li&gt;
 &lt;li&gt;Grant DESCRIBE permission to RedshiftIcebergRole.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="create-an-external-schema"&gt;Create an external schema&lt;/h3&gt;
&lt;p&gt;With the resource link in place, create an external schema in Amazon Redshift for two-part notation access.&lt;/p&gt;
&lt;p&gt;For IAM federated users:&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-sql"&gt;CREATE EXTERNAL SCHEMA s3tables_iceberg
FROM DATA CATALOG
DATABASE 'iceberg_write_blog_rl'
CATALOG_ID '&amp;lt;ACCOUNT_ID&amp;gt;'
IAM_ROLE 'SESSION';&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;For database users and business intelligence (BI) tools:&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-sql"&gt;CREATE EXTERNAL SCHEMA s3tables_iceberg
FROM DATA CATALOG
DATABASE 'iceberg_write_blog_rl'
IAM_ROLE 'arn:aws:iam::&amp;lt;ACCOUNT&amp;gt;:role/RedshifticebergRole';&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Grant access to specific users or roles:&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-sql"&gt;-- Grant to the IAM role used in this walkthrough
GRANT USAGE ON SCHEMA s3tables_iceberg TO "IAMR:RedshifticebergRole";&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div style="width: 498px" class="wp-caption alignnone"&gt;
 &lt;a href="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/24/BDB-5994-24.png" target="_blank" rel="noopener"&gt;&lt;img src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/24/BDB-5994-24.png" alt="Amazon Redshift query showing S3 Tables available through the external schema" width="488"&gt;&lt;/a&gt;
 &lt;p class="wp-caption-text"&gt;Figure 24: S3 Tables available through an external schema&lt;/p&gt;
&lt;/div&gt;
&lt;h3 id="query-with-two-part-notation"&gt;Query with two-part notation&lt;/h3&gt;
&lt;p&gt;With the external schema created, query S3 Tables using two-part notation:&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-sql"&gt;-- Instead of: "iceberg-write-blog@s3tablescatalog".iceberg_write_namespace.orders
SELECT * FROM s3tables_iceberg.orders;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div style="width: 810px" class="wp-caption alignnone"&gt;
 &lt;a href="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/24/BDB-5994-25.png" target="_blank" rel="noopener"&gt;&lt;img src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/24/BDB-5994-25.png" alt="Query results from S3 Tables through the external schema using two-part notation" width="800"&gt;&lt;/a&gt;
 &lt;p class="wp-caption-text"&gt;Figure 25: Query results from S3 Tables through an external schema using two-part notation&lt;/p&gt;
&lt;/div&gt;
&lt;h3 id="access-methods-comparison"&gt;Access methods comparison&lt;/h3&gt;
&lt;p&gt;The following table compares the available methods for accessing Iceberg tables in Amazon Redshift:&lt;/p&gt;
&lt;table border="1px" width="100%" cellpadding="10px"&gt;
 &lt;tbody&gt;
  &lt;tr&gt;
   &lt;td&gt;&lt;strong&gt;Access method&lt;/strong&gt;&lt;/td&gt;
   &lt;td&gt;&lt;strong&gt;Query syntax&lt;/strong&gt;&lt;/td&gt;
   &lt;td&gt;&lt;strong&gt;Authentication&lt;/strong&gt;&lt;/td&gt;
   &lt;td&gt;&lt;strong&gt;Best for&lt;/strong&gt;&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;S3 Tables three-part notation&lt;/td&gt;
   &lt;td&gt;&lt;code&gt;"bucket@s3tablescatalog".namespace.table&lt;/code&gt;&lt;/td&gt;
   &lt;td&gt;IAM federated identity only&lt;/td&gt;
   &lt;td&gt;Interactive queries in Query Editor v2 with direct catalog access.&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;External schema through resource link&lt;/td&gt;
   &lt;td&gt;&lt;code&gt;schema_name.table&lt;/code&gt;&lt;/td&gt;
   &lt;td&gt;Any (IAM role defined in schema)&lt;/td&gt;
   &lt;td&gt;BI tools, Data API, JDBC/ODBC applications, and shared team access.&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;awsdatacatalog&lt;/td&gt;
   &lt;td&gt;&lt;code&gt;awsdatacatalog.database.table&lt;/code&gt;&lt;/td&gt;
   &lt;td&gt;IAM federated identity only&lt;/td&gt;
   &lt;td&gt;Multi-database access in a single session without creating external schemas.&lt;/td&gt;
  &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id="bringing-it-together"&gt;Bringing it together&lt;/h2&gt;
&lt;p&gt;Combine schema evolution with cross-engine access in a single workflow. The following example adds a column to the &lt;code&gt;orders&lt;/code&gt; table and immediately queries it through the external schema:&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-sql"&gt;-- 1. Add a column to the S3 Tables orders table
ALTER TABLE s3tables_iceberg.orders
ADD COLUMN fulfillment_status VARCHAR;
-- 2. Update the new column
UPDATE s3tables_iceberg.orders
SET fulfillment_status = 'shipped'
WHERE order_date &amp;lt; '2024-11-01';
UPDATE s3tables_iceberg.orders
SET fulfillment_status = 'pending'
WHERE order_date &amp;gt;= '2024-11-01';
-- 3. Query immediately via the external schema (no schema recreation needed)
SELECT o.order_id, o.order_date, o.fulfillment_status, c.customer_name
FROM s3tables_iceberg.orders o JOIN demo_iceberg.customer c
ON o.customer_id = c.customer_id
ORDER BY o.order_date DESC;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div style="width: 810px" class="wp-caption alignnone"&gt;
 &lt;a href="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/24/BDB-5994-26.png" target="_blank" rel="noopener"&gt;&lt;img src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/24/BDB-5994-26.png" alt="Query results of a cross-catalog join showing the evolved schema through the external schema" width="800"&gt;&lt;/a&gt;
 &lt;p class="wp-caption-text"&gt;Figure 26: Cross-catalog join showing the evolved schema immediately visible through the external schema&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;The new column is visible through both the three-part notation and the external schema without any additional configuration, because the schema evolution in Iceberg propagates automatically.&lt;/p&gt;
&lt;h2 id="best-practices"&gt;Best practices&lt;/h2&gt;
&lt;ul&gt;
 &lt;li&gt;&lt;strong&gt;Test ALTER operations in non-production first.&lt;/strong&gt; While metadata-only, schema changes affect all readers immediately.&lt;/li&gt;
 &lt;li&gt;&lt;strong&gt;Use REPLACE PARTITION FIELD instead of DROP + ADD.&lt;/strong&gt; The atomic operation avoids a transient unpartitioned state.&lt;/li&gt;
 &lt;li&gt;&lt;strong&gt;Monitor partition spec changes with SHOW TABLE.&lt;/strong&gt; Verify the current spec after any partition evolution.&lt;/li&gt;
 &lt;li&gt;&lt;strong&gt;Choose partition transforms based on query patterns.&lt;/strong&gt; Use &lt;code&gt;month()&lt;/code&gt; or &lt;code&gt;day()&lt;/code&gt; for time-range filters. Use &lt;code&gt;bucket()&lt;/code&gt; for high-cardinality join keys.&lt;/li&gt;
 &lt;li&gt;&lt;strong&gt;Set table properties before bulk loads.&lt;/strong&gt; Change compression type (&lt;code&gt;zstd&lt;/code&gt; for better ratios, &lt;code&gt;snappy&lt;/code&gt; for speed) before large INSERT operations.&lt;/li&gt;
 &lt;li&gt;&lt;strong&gt;Run table maintenance after mutations.&lt;/strong&gt; After performing multiple UPDATE, DELETE, or MERGE operations, run &lt;a href="https://docs.aws.amazon.com/glue/latest/dg/table-optimizers.html" target="_blank" rel="noopener"&gt;AWS Glue table optimizers&lt;/a&gt; to compact deletion files and improve read performance.&lt;/li&gt;
 &lt;li&gt;&lt;strong&gt;Use Lake Formation for fine-grained access.&lt;/strong&gt; Column-level and row-level security can be applied through Lake Formation on tables accessed through resource links.&lt;/li&gt;
 &lt;li&gt;&lt;strong&gt;Grant schema access to specific users or roles.&lt;/strong&gt; Avoid granting to PUBLIC. Use named IAM roles or database users for least-privilege access.&lt;/li&gt;
 &lt;li&gt;&lt;strong&gt;Monitor query performance.&lt;/strong&gt; Use Amazon Redshift query monitoring features to track performance of write operations and optimize partitioning strategies as needed.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="considerations"&gt;Considerations&lt;/h2&gt;
&lt;p&gt;Keep the following in mind when working with ALTER TABLE and partition evolution on Iceberg tables:&lt;/p&gt;
&lt;ul&gt;
 &lt;li&gt;&lt;strong&gt;Plan for metadata-only behavior.&lt;/strong&gt; ALTER TABLE operations update metadata instantly, and existing data files remain unchanged. All readers see the new schema immediately after the operation completes.&lt;/li&gt;
 &lt;li&gt;&lt;strong&gt;Drop partition fields before dropping partitioned columns.&lt;/strong&gt; To remove a column used in the current partition spec, first drop or replace the partition field, then drop the column.&lt;/li&gt;
 &lt;li&gt;&lt;strong&gt;Use safe type promotions for ALTER COLUMN TYPE.&lt;/strong&gt; Amazon Redshift supports widening within compatible families (INT to BIGINT, FLOAT to DOUBLE, DECIMAL(10,2) to DECIMAL(18,2)). Plan column types with future growth in mind.&lt;/li&gt;
 &lt;li&gt;&lt;strong&gt;Account for mixed partition layouts after evolution.&lt;/strong&gt; Partition evolution doesn’t re-partition existing data. Old files remain in their original layout, and the query engine reads both layouts transparently.&lt;/li&gt;
 &lt;li&gt;&lt;strong&gt;Use external schemas for database user access.&lt;/strong&gt; The auto-mounted three-part notation (&lt;code&gt;"bucket@s3tablescatalog"&lt;/code&gt;) requires IAM federated authentication. For database users and BI tools, create an external schema with an explicit IAM role.&lt;/li&gt;
 &lt;li&gt;&lt;strong&gt;Use full three-part notation with awsdatacatalog.&lt;/strong&gt; The USE statement isn’t supported with awsdatacatalog, so always specify the full path.&lt;/li&gt;
 &lt;li&gt;&lt;strong&gt;Clean up S3 data separately after dropping tables.&lt;/strong&gt; Dropping an Iceberg table removes only the catalog entry from AWS Glue Data Catalog. Delete the underlying S3 data files separately, or use AWS Glue table optimizers to remove orphaned files.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="clean-up"&gt;Clean up&lt;/h2&gt;
&lt;p&gt;To avoid ongoing charges, run the following:&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-sql"&gt;-- Drop the fulfillment_status column added during testing
ALTER TABLE "iceberg-write-blog@s3tablescatalog".iceberg_write_namespace.orders
DROP COLUMN fulfillment_status;
-- Restore original partition spec (if changed)
ALTER TABLE "iceberg-write-blog@s3tablescatalog".iceberg_write_namespace.orders
REPLACE PARTITION FIELD MONTH(order_date) WITH DAY(order_date);
-- Drop external schema
DROP SCHEMA IF EXISTS s3tables_iceberg;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;In this post, you evolved Apache Iceberg table schemas using ALTER TABLE operations. You added, dropped, and renamed columns, widened data types, changed compression, and evolved partition specs, all as metadata-only operations without rewriting data. You also created Lake Formation resource links to provide governed cross-engine access to S3 Tables, and simplified query syntax with external schemas.&lt;/p&gt;
&lt;p&gt;This concludes the three-part series on getting started with Apache Iceberg write support in Amazon Redshift:&lt;/p&gt;
&lt;ol type="1"&gt;
 &lt;li&gt;&lt;a href="https://aws.amazon.com/blogs/big-data/getting-started-with-apache-iceberg-write-support-in-amazon-redshift-part-1/" target="_blank" rel="noopener"&gt;Part 1&lt;/a&gt;: Create Iceberg tables and perform INSERT operations.&lt;/li&gt;
 &lt;li&gt;&lt;a href="https://aws.amazon.com/blogs/big-data/getting-started-with-apache-iceberg-write-support-in-amazon-redshift-part-2/" target="_blank" rel="noopener"&gt;Part 2&lt;/a&gt;: Run DELETE, UPDATE, and MERGE for row-level modifications.&lt;/li&gt;
 &lt;li&gt;Part 3: Evolve schemas with ALTER TABLE and add cross-engine access with Lake Formation resource links.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;If you have questions or feedback about this series, leave a comment on this post.&lt;/p&gt;
&lt;h2 id="additional-resources"&gt;Additional resources&lt;/h2&gt;
&lt;ul&gt;
 &lt;li&gt;&lt;a href="https://docs.aws.amazon.com/redshift/latest/dg/querying-iceberg.html" target="_blank" rel="noopener"&gt;Amazon Redshift Iceberg integration&lt;/a&gt; – Complete syntax reference.&lt;/li&gt;
 &lt;li&gt;&lt;a href="https://docs.aws.amazon.com/redshift/latest/dg/iceberg-writes-sql-syntax.html" target="_blank" rel="noopener"&gt;Writing to Apache Iceberg tables&lt;/a&gt; – Detailed examples.&lt;/li&gt;
 &lt;li&gt;&lt;a href="https://docs.aws.amazon.com/redshift/latest/dg/iceberg-alter-table.html" target="_blank" rel="noopener"&gt;ALTER TABLE for Iceberg&lt;/a&gt; – Full ALTER reference.&lt;/li&gt;
 &lt;li&gt;&lt;a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/s3-tables.html" target="_blank" rel="noopener"&gt;Amazon S3 Tables&lt;/a&gt; – Managed Iceberg storage.&lt;/li&gt;
 &lt;li&gt;&lt;a href="https://docs.aws.amazon.com/lake-formation/latest/dg/what-is-lake-formation.html" target="_blank" rel="noopener"&gt;AWS Lake Formation&lt;/a&gt; – Centralized data governance.&lt;/li&gt;
 &lt;li&gt;&lt;a href="https://aws.amazon.com/blogs/big-data/optimize-amazon-s3-tables-queries-with-amazon-redshift/" target="_blank" rel="noopener"&gt;Optimize S3 Tables queries with Amazon Redshift&lt;/a&gt; – Resource links and performance tuning.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr style="width: 100%"&gt;
&lt;h2&gt;About the authors&lt;/h2&gt;
&lt;footer&gt;
 &lt;div class="blog-author-box" style="padding-top: 2.0em"&gt;
  &lt;div class="blog-author-image" style="margin-right: 1.0em"&gt;
   &lt;img loading="lazy" class="alignnone size-full" src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/03/17/BD5701a2.png" alt="Raghu Kuppala" width="100" height="133"&gt;
  &lt;/div&gt;
  &lt;h3 class="lb-h4"&gt;Raghu Kuppala&lt;/h3&gt;
  &lt;p style="overflow: hidden"&gt;Raghu is an Analytics Specialist Solutions Architect experienced working in the databases, data warehousing, and analytics space. Outside of work, he enjoys trying different cuisines and spending time with his family and friends.&lt;/p&gt;
 &lt;/div&gt;
 &lt;div class="blog-author-box" style="padding-top: 2.0em"&gt;
  &lt;div class="blog-author-image" style="margin-right: 1.0em"&gt;
   &lt;img loading="lazy" class="alignnone size-full" src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/24/BDB-5994-27.jpg" alt="Tanishq Goyal" width="100" height="133"&gt;
  &lt;/div&gt;
  &lt;h3 class="lb-h4"&gt;Tanishq Goyal&lt;/h3&gt;
  &lt;p style="overflow: hidden"&gt;Tanishq is a Software Development Engineer at AWS.&lt;/p&gt;
 &lt;/div&gt;
 &lt;div class="blog-author-box" style="padding-top: 2.0em"&gt;
  &lt;div class="blog-author-image" style="margin-right: 1.0em"&gt;
   &lt;img loading="lazy" class="alignnone size-full" src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2025/11/25/5576a1.jpg" alt="Sanket Hase" width="100" height="133"&gt;
  &lt;/div&gt;
  &lt;h3 class="lb-h4"&gt;Sanket Hase&lt;/h3&gt;
  &lt;p style="overflow: hidden"&gt;Sanket is an Engineering Manager with the Amazon Redshift team, leading query execution teams in the areas of data lake analytics, hardware-software co-design, and vectorized query execution.&lt;/p&gt;
 &lt;/div&gt;
 &lt;div class="blog-author-box" style="padding-top: 2.0em"&gt;
  &lt;div class="blog-author-image" style="margin-right: 1.0em"&gt;
   &lt;img loading="lazy" class="alignnone size-full" src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/24/Vlad.png" alt="Vlad Ponomarenko" width="100" height="133"&gt;
  &lt;/div&gt;
  &lt;h3 class="lb-h4"&gt;Vlad Ponomarenko&lt;/h3&gt;
  &lt;p style="overflow: hidden"&gt;Vlad is a Senior Software Development Engineer with the Amazon Redshift team, working on query processing, serverless, and integrations. Outside of work, he enjoys watching and playing sports and live music.&lt;/p&gt;
 &lt;/div&gt;
 &lt;div class="blog-author-box" style="padding-top: 2.0em"&gt;
  &lt;div class="blog-author-image" style="margin-right: 1.0em"&gt;
   &lt;img loading="lazy" class="alignnone size-full" src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/24/SamWang.png" alt="Sam Wang" width="100" height="133"&gt;
  &lt;/div&gt;
  &lt;h3 class="lb-h4"&gt;Sam Wang&lt;/h3&gt;
  &lt;p style="overflow: hidden"&gt;Sam works query processing and data ingestion as a Software Development Engineer on the Amazon Redshift team. When he’s not writing code, you’ll find him on the slopes.&lt;/p&gt;
 &lt;/div&gt;
 &lt;div class="blog-author-box" style="padding-top: 2.0em"&gt;
  &lt;div class="blog-author-image" style="margin-right: 1.0em"&gt;
   &lt;img loading="lazy" class="alignnone size-full" src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/24/Fahim.png" alt="Fahim Chodhury" width="100" height="133"&gt;
  &lt;/div&gt;
  &lt;h3 class="lb-h4"&gt;Fahim Chowdhury&lt;/h3&gt;
  &lt;p style="overflow: hidden"&gt;Fahim works on data lake query execution engine and query processing as a Software Development Engineer on the Amazon Redshift team.&lt;/p&gt;
 &lt;/div&gt;
&lt;/footer&gt;</content:encoded>
					
		
		
			</item>
		<item>
		<title>Enforce IAM permissions boundaries for Amazon SageMaker Unified Studio Tooling blueprints</title>
		<link>https://aws.amazon.com/blogs/big-data/enforce-iam-permissions-boundaries-for-amazon-sagemaker-unified-studio-tooling-blueprints/</link>
		
		<dc:creator><![CDATA[Sanjana Sekar]]></dc:creator>
		<pubDate>Mon, 28 Sep 2026 15:45:42 +0000</pubDate>
				<category><![CDATA[Advanced (300)]]></category>
		<category><![CDATA[Amazon SageMaker Unified Studio]]></category>
		<category><![CDATA[AWS Identity and Access Management (IAM)]]></category>
		<guid isPermaLink="false">a3b83b12e35d386c992aabcdd9057e367ca5fb40</guid>

					<description>Amazon SageMaker Unified Studio now supports custom permissions boundaries for the IAM roles its Tooling blueprint creates. Learn how to create a permissions boundary that restricts AI agent capabilities, configure it on the Tooling blueprint with the AWS CLI, and validate that every provisioned role carries the boundary.</description>
										<content:encoded>&lt;p&gt;Amazon SageMaker Unified Studio now supports custom permissions boundaries for IAM roles created by the Tooling blueprint. Organizations that enforce Service Control Policies (SCPs) requiring permissions boundaries on all AWS Identity and Access Management (IAM) roles can now adopt Amazon SageMaker Unified Studio without modifying their security posture.&lt;/p&gt;
&lt;p&gt;Amazon SageMaker Unified Studio is a unified development environment that brings together data engineering, machine learning, and analytics tools into a single workspace. In Amazon SageMaker Unified Studio, a project is a collaborative workspace that bundles people, tools, and access permissions together. It builds every project from a project profile, which defines a list of blueprints. Blueprints are pre-configured infrastructure templates that provision AWS resources at project creation time or on demand, along with their default parameters. The Tooling blueprint is the only mandatory one. Amazon SageMaker Unified Studio deploys it with every project, creating foundational resources such as the project IAM role and security groups.&lt;/p&gt;
&lt;p&gt;In this post, you learn how to create a permissions boundary that restricts AI agent capabilities. You then configure it on the Tooling blueprint using the AWS Command Line Interface (AWS CLI). Finally, you validate that the boundary is enforced on all provisioned roles.&lt;/p&gt;
&lt;h2 id="the-problem"&gt;The problem&lt;/h2&gt;
&lt;p&gt;Enterprises in regulated industries use SCPs to require that every IAM role in an account carries a permissions boundary. A well-scoped boundary prevents privilege escalation and verifies no role exceeds the maximum permissions defined by the organization’s security team. Before this feature, Amazon SageMaker Unified Studio Tooling blueprints created IAM roles without permissions boundaries. When an SCP enforced permissions boundaries, project creation failed with an explicit deny:&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-plaintext"&gt;User: arn:aws:sts::&amp;lt;account-id&amp;gt;:assumed-role/AmazonSageMakerProvisioning-&amp;lt;account-id&amp;gt;/AmazonDataZoneEnvironmentDeployer-&amp;lt;account-id&amp;gt; is not authorized to perform: iam:CreateRole on resource: arn:aws:iam::&amp;lt;account-id&amp;gt;:role/AmazonBedrockServiceRole-&amp;lt;project-id&amp;gt;-&amp;lt;env-id&amp;gt; with an explicit deny in a service control policy&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Amazon SageMaker Unified Studio surfaces the blocked role creation as a Tooling environment provisioning failure, as shown in Figure 1.&lt;/p&gt;
&lt;div style="width: 810px" class="wp-caption alignnone"&gt;
 &lt;a href="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/08/25/BDB-6010-1.jpg" target="_blank" rel="noopener"&gt;&lt;img src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/08/25/BDB-6010-1.jpg" alt="SMUS project overview showing the Tooling environment in a failed state from a permissions boundary SCP denial" width="800"&gt;&lt;/a&gt;
 &lt;p class="wp-caption-text"&gt;Figure 1: Project creation fails when the SCP requires a permissions boundary that is not attached&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;The project is marked as failed because its Tooling environment couldn’t deploy in the US East (N. Virginia) AWS Region (us-east-1). The details show a 403 permissions error, while the preceding IAM message identifies the underlying &lt;code&gt;iam:CreateRole&lt;/code&gt; SCP denial. This blocked adoption for any organization with SCP-enforced permissions boundaries. The AWS CloudFormation event for the Tooling stack exposes the IAM failure behind the project-level error, as shown in Figure 2.&lt;/p&gt;
&lt;div style="width: 810px" class="wp-caption alignnone"&gt;
 &lt;a href="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/08/25/BDB-6010-2.jpg" target="_blank" rel="noopener"&gt;&lt;img src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/08/25/BDB-6010-2.jpg" alt="CloudFormation stack events showing the BedrockServiceRole in CREATE_FAILED from an iam:CreateRole SCP explicit deny" width="800"&gt;&lt;/a&gt;
 &lt;p class="wp-caption-text"&gt;Figure 2: Detailed error showing the SCP denial in the Tooling blueprint AWS CloudFormation stack&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;The &lt;code&gt;AmazonBedrockServiceRole&lt;/code&gt; resource entered &lt;code&gt;CREATE_FAILED&lt;/code&gt; because &lt;code&gt;iam:CreateRole&lt;/code&gt; was explicitly denied by the SCP, even though AWS CloudFormation surfaced the wrapper error as &lt;code&gt;UnauthorizedTaggingOperation&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="granular-control-using-a-permissions-boundary-example-use-case"&gt;Granular control using a permissions boundary: Example use case&lt;/h2&gt;
&lt;p&gt;Beyond satisfying SCP requirements, permissions boundaries give administrators granular control over what the Tooling blueprint roles can do. For instance, some organizations have SecOps policies that require disabling Data Agent and Data Notebook capabilities across their accounts. These organizations want project members to access data connections and run SQL queries directly, but must block conversational AI, code generation, and notebook cell execution through the agent.&lt;/p&gt;
&lt;p&gt;When &lt;code&gt;PermissionsBoundaryArn&lt;/code&gt; is configured on the Tooling blueprint, SageMaker Unified Studio attaches the specified customer-managed permissions boundary to all IAM roles provisioned by that blueprint. If your governance requires a boundary, configure it explicitly and verify the resulting roles.&lt;/p&gt;
&lt;p&gt;The following permissions boundary policy scopes the roles to the AWS services that Amazon SageMaker Unified Studio uses and then explicitly denies the Amazon DataZone actions that power the AI agent. This permissions boundary is provided for illustrative purposes only and isn’t a recommendation or reference for environment configuration. You should tailor your permissions boundaries to your specific workloads in accordance with the principle of least privilege.&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-json"&gt;{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AllowSmusServiceScope",
      "Effect": "Allow",
      "Action": [
        "datazone:*",
        "sagemaker:*",
        "glue:*",
        "s3:*",
        "lakeformation:*",
        "redshift:*",
        "redshift-data:*",
        "redshift-serverless:*",
        "athena:*",
        "q:*",
        "elasticmapreduce:*",
        "bedrock:*",
        "lambda:*",
        "kms:*",
        "secretsmanager:*",
        "codecommit:*",
        "logs:*",
        "cloudwatch:*",
        "sts:AssumeRole",
        "iam:PassRole",
        "ec2:Describe*",
        "ec2:CreateNetworkInterface",
        "ec2:DeleteNetworkInterface",
        "ec2:CreateNetworkInterfacePermission",
        "ec2:DeleteNetworkInterfacePermission"
      ],
      "Resource": "*"
    },
    {
      "Sid": "DenyDataNotebookAndDataAgent",
      "Effect": "Deny",
      "Action": [
        "datazone:*Notebook*",
        "datazone:*Cell*",
        "datazone:*Conversation*",
        "datazone:SendMessage",
        "datazone:GenerateCode",
        "datazone:CancelMessage"
      ],
      "Resource": "*"
    }
  ]
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Warning:&lt;/strong&gt; validate before using in production. This example scopes the roles to the service namespaces Amazon SageMaker Unified Studio uses, but it is still coarse (it allows each listed service in full) and is provided only for illustration. Because a permissions boundary is a ceiling, it must remain a superset of everything the three Tooling roles (&lt;code&gt;datazone_usr_role&lt;/code&gt;, &lt;code&gt;AmazonBedrockServiceRole&lt;/code&gt;, and &lt;code&gt;AmazonBedrockLambdaExecutionRole&lt;/code&gt;) actually need. If Amazon SageMaker Unified Studio adds a dependency that isn’t listed, provisioning or in-console actions will fail with an access denied error. Validate in a non-production domain first.&lt;/p&gt;
&lt;p&gt;With this boundary attached, the Tooling blueprint provisions normally, project members can access data connections and run SQL queries. However, any attempt to invoke the AI assistant or execute notebook cells through the agent returns an access denied error. The boundary acts as a ceiling that no policy attached to the role can override.&lt;/p&gt;
&lt;h2 id="how-it-works"&gt;How it works&lt;/h2&gt;
&lt;p&gt;The custom permissions boundary feature operates at the blueprint configuration level. An administrator sets a &lt;code&gt;PermissionsBoundaryArn&lt;/code&gt; in the Tooling blueprint’s regional parameters. When a user creates a new project that includes the Tooling blueprint, Amazon SageMaker Unified Studio provisions an AWS CloudFormation stack that creates three IAM roles and attaches the specified boundary to each:&lt;/p&gt;
&lt;ul&gt;
 &lt;li&gt;&lt;strong&gt;&lt;code&gt;datazone_usr_role&lt;/code&gt;&lt;/strong&gt; – the role that all project members assume to access data and resources in that project.&lt;/li&gt;
 &lt;li&gt;&lt;strong&gt;&lt;code&gt;AmazonBedrockServiceRole&lt;/code&gt;&lt;/strong&gt; – for Amazon Bedrock operations.&lt;/li&gt;
 &lt;li&gt;&lt;strong&gt;&lt;code&gt;AmazonBedrockLambdaExecutionRole&lt;/code&gt;&lt;/strong&gt; – for Amazon Bedrock-related AWS Lambda functions.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Because the boundary is set at the blueprint level, it applies to every project created under that blueprint. No per-project configuration is needed.&lt;/p&gt;
&lt;h2 id="prerequisites"&gt;Prerequisites&lt;/h2&gt;
&lt;p&gt;Before you begin, make sure that you have:&lt;/p&gt;
&lt;ul&gt;
 &lt;li&gt;An &lt;a href="https://docs.aws.amazon.com/accounts/latest/reference/accounts-welcome.html" target="_blank" rel="noopener"&gt;AWS account&lt;/a&gt; with a Amazon SageMaker Unified Studio &lt;a href="https://docs.aws.amazon.com/sagemaker-unified-studio/latest/adminguide/domain.html" target="_blank" rel="noopener"&gt;Identity Center-based domain&lt;/a&gt; created.&lt;/li&gt;
 &lt;li&gt;The &lt;a href="https://docs.aws.amazon.com/sagemaker-unified-studio/latest/adminguide/enable-disable-blueprints.html" target="_blank" rel="noopener"&gt;Tooling blueprint enabled&lt;/a&gt; in the domain.&lt;/li&gt;
 &lt;li&gt;&lt;a href="https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html" target="_blank" rel="noopener"&gt;AWS CLI v2&lt;/a&gt; configured with administrator access.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If your organization uses AWS Organizations with SCPs that require permissions boundaries, you will also need an organization with the target account as a member and permissions to create and attach SCPs in the management account.&lt;/p&gt;
&lt;h2 id="setting-up-the-scp-optional"&gt;Setting up the SCP (optional)&lt;/h2&gt;
&lt;p&gt;This section provides instructions to create an SCP and attach it to your AWS Organizations organizational unit or accounts. If your organization already enforces permissions boundaries through SCPs, skip this section. Otherwise, create an SCP in your AWS Organizations management account that denies IAM role creation unless an approved permissions boundary is attached. This also prevents the boundary from being removed, swapped, or weakened afterward:&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="lang-code"&gt;{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "DenyRoleWithoutApprovedBoundary",
      "Effect": "Deny",
      "Action": [
        "iam:CreateRole",
        "iam:PutRolePermissionsBoundary"
      ],
      "Resource": "*",
      "Condition": {
        "StringNotEquals": {
          "iam:PermissionsBoundary": "arn:aws:iam::${aws:PrincipalAccount}:policy/SMUSToolingBoundary"
        }
      }
    },
    {
      "Sid": "DenyRemovingBoundary",
      "Effect": "Deny",
      "Action": "iam:DeleteRolePermissionsBoundary",
      "Resource": "*"
    },
    {
      "Sid": "ProtectBoundaryPolicy",
      "Effect": "Deny",
      "Action": [
        "iam:DeletePolicy",
        "iam:CreatePolicyVersion",
        "iam:SetDefaultPolicyVersion"
      ],
      "Resource": "arn:aws:iam::${aws:PrincipalAccount}:policy/SMUSToolingBoundary"
    }
  ]
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;This policy does three things:&lt;/p&gt;
&lt;ul&gt;
 &lt;li&gt;&lt;strong&gt;&lt;code&gt;DenyRoleWithoutApprovedBoundary&lt;/code&gt;&lt;/strong&gt; blocks creating a role, or attaching a boundary to an existing role, with anything other than the approved boundary ARN. Denying &lt;code&gt;iam:PutRolePermissionsBoundary&lt;/code&gt; in addition to &lt;code&gt;iam:CreateRole&lt;/code&gt; stops a privileged principal from swapping in a weaker boundary after the role exists.&lt;/li&gt;
 &lt;li&gt;&lt;strong&gt;&lt;code&gt;DenyRemovingBoundary&lt;/code&gt;&lt;/strong&gt; blocks &lt;code&gt;iam:DeleteRolePermissionsBoundary&lt;/code&gt; outright, so the boundary cannot be stripped off. (This action doesn’t support the &lt;code&gt;iam:PermissionsBoundary&lt;/code&gt; condition key, so it must be denied unconditionally.)&lt;/li&gt;
 &lt;li&gt;&lt;strong&gt;&lt;code&gt;ProtectBoundaryPolicy&lt;/code&gt;&lt;/strong&gt; prevents tampering with the boundary policy itself. Deleting it, or publishing and defaulting a new version that quietly widens what it allows.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: Scope these denies so you don’t lock yourself out. A broad deny on &lt;code&gt;iam:PutRolePermissionsBoundary&lt;/code&gt; and &lt;code&gt;iam:DeleteRolePermissionsBoundary&lt;/code&gt; also applies to your own administrators. Add an exception for a break-glass or IAM-admin role (for example, an &lt;code&gt;aws:PrincipalArn&lt;/code&gt; &lt;code&gt;StringNotLike&lt;/code&gt; condition) so a trusted principal can still manage boundaries.&lt;/p&gt;
&lt;p&gt;To create the SCP, sign in to the AWS Organizations console with your management account and go to &lt;b&gt;AWS Organizations&lt;/b&gt; → &lt;b&gt;Policies&lt;/b&gt; → &lt;b&gt;Service control policies&lt;/b&gt;. If SCPs aren’t enabled for your organization yet, choose &lt;b&gt;Enable service control policies&lt;/b&gt; first. Choose &lt;b&gt;Create policy&lt;/b&gt;, give it a name (for example, &lt;code&gt;test_scp&lt;/code&gt;), and replace the default content in the policy editor with the JSON above substituting &lt;code&gt;&amp;lt;account-id&amp;gt;&lt;/code&gt; with your account ID. Choose &lt;b&gt;Create policy&lt;/b&gt; to save it.&lt;/p&gt;
&lt;p&gt;After creating the SCP in the management account, verify its content before attaching it. Figure 3 shows the core create-role control. The full example above adds controls that prevent replacing or removing the boundary and modifying the protected policy.&lt;/p&gt;
&lt;div id="attachment_94611" style="width: 1087px" class="wp-caption alignnone"&gt;
 &lt;img aria-describedby="caption-attachment-94611" loading="lazy" class="size-full wp-image-94611" src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/25/figure3.png" alt="" width="1077" height="1051"&gt;
 &lt;p id="caption-attachment-94611" class="wp-caption-text"&gt;Figure 3: Service Control Policy defined in the AWS Organizations management account&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;The AWS Organizations &lt;strong&gt;Content&lt;/strong&gt; tab displays the customer-managed &lt;code&gt;test_scp&lt;/code&gt; policy. Its visible statement denies &lt;code&gt;iam:CreateRole&lt;/code&gt; unless the request uses the &lt;code&gt;SMUSToolingBoundary&lt;/code&gt; policy.&lt;/p&gt;
&lt;p&gt;Attach this SCP to the organizational unit or account where your Amazon SageMaker Unified Studio domain and domain-associated accounts reside. To do so, open the &lt;code&gt;test_scp&lt;/code&gt; service control policy, choose the &lt;b&gt;Targets&lt;/b&gt; tab, and choose &lt;b&gt;Attach&lt;/b&gt;. The AWS organization structure appears; select the OU or account where the SCP should apply, then choose &lt;b&gt;Attach policy&lt;/b&gt;.&lt;/p&gt;
&lt;p&gt;Figure 4 identifies the member account that must inherit the SCP in this example organization. The target member account, &lt;code&gt;datazone-account2&lt;/code&gt;, resides under OU2, while &lt;code&gt;datazone-account1&lt;/code&gt; is the organization’s management account. Attaching the SCP to the target account or a parent organizational unit enforces it there.&lt;/p&gt;
&lt;div id="attachment_94610" style="width: 1308px" class="wp-caption alignnone"&gt;
 &lt;img aria-describedby="caption-attachment-94610" loading="lazy" class="size-full wp-image-94610" src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/25/figure_4.png" alt="" width="1298" height="820"&gt;
 &lt;p id="caption-attachment-94610" class="wp-caption-text"&gt;Figure 4: AWS Organizations account structure showing the management account and the target member account&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;After attaching the policy, verify the association on the SCP’s &lt;strong&gt;Targets&lt;/strong&gt; tab, as shown in Figure 5.&lt;/p&gt;
&lt;div style="width: 810px" class="wp-caption alignnone"&gt;
 &lt;a href="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/08/25/BDB-6010-5.jpg" target="_blank" rel="noopener"&gt;&lt;img src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/08/25/BDB-6010-5.jpg" alt="SCP Targets tab listing datazone-account2 as an account target where test_scp is enforced" width="800"&gt;&lt;/a&gt;
 &lt;p class="wp-caption-text"&gt;Figure 5: Service Control Policy attached to the target account where it should be enforced&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;The &lt;strong&gt;Targets&lt;/strong&gt; tab lists &lt;code&gt;datazone-account2&lt;/code&gt; as an ACCOUNT target, confirming that &lt;code&gt;test_scp&lt;/code&gt; is enforced directly on the intended member account.&lt;/p&gt;
&lt;h2 id="configuring-the-permissions-boundary"&gt;Configuring the permissions boundary&lt;/h2&gt;
&lt;p&gt;In this section you will execute the required steps to create the permissions boundary and enable it in the Tooling blueprint. The example in this walkthrough uses us-east-1. Change it to the Region where your Amazon SageMaker Unified Studio domain is deployed. You must execute the configuration in the account where you plan to create your project. This can be your Amazon SageMaker Unified Studio domain account or accounts associated to your Amazon SageMaker Unified Studio domain.&lt;/p&gt;
&lt;h3 id="step-1-create-the-permissions-boundary-policy"&gt;Step 1: Create the permissions boundary policy&lt;/h3&gt;
&lt;p&gt;If you haven’t already created the boundary policy, save the following JSON document as a &lt;code&gt;boundary-policy.json&lt;/code&gt; file on your workstation:&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="lang-code"&gt;{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "AllowSmusServiceScope",
            "Effect": "Allow",
            "Action": [
                "datazone:*",
                "sagemaker:*",
                "glue:*",
                "s3:*",
                "lakeformation:*",
                "redshift:*",
                "redshift-data:*",
                "redshift-serverless:*",
                "athena:*",
                "q:*",
                "elasticmapreduce:*",
                "bedrock:*",
                "lambda:*",
                "kms:*",
                "secretsmanager:*",
                "codecommit:*",
                "logs:*",
                "cloudwatch:*",
                "sts:AssumeRole",
                "iam:PassRole",
                "ec2:Describe*",
                "ec2:CreateNetworkInterface",
                "ec2:DeleteNetworkInterface",
                "ec2:CreateNetworkInterfacePermission",
                "ec2:DeleteNetworkInterfacePermission",
                "iam:GetRole",
                "sqlworkbench:*"
            ],
            "Resource": "*"
        },
        {
            "Sid": "DenyDataNotebookAndDataAgent",
            "Effect": "Deny",
            "Action": [
                "datazone:*Notebook*",
                "datazone:*Cell*",
                "datazone:*Conversation*",
                "datazone:SendMessage",
                "datazone:GenerateCode",
                "datazone:CancelMessage"
            ],
            "Resource": "*"
        }
    ]
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;As noted previously, this illustrative policy is scoped to the services Amazon SageMaker Unified Studio uses but is still coarse, and its allow list must stay a superset of what all three Tooling roles need.&lt;/p&gt;
&lt;p&gt;Then create the policy using the following command:&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-bash"&gt;aws iam create-policy \
--policy-name SMUSToolingBoundary \
--policy-document file://boundary-policy.json \
--description "Permissions boundary for SMUS Tooling roles - denies Data Agent and Data Notebook capabilities"&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Note the policy ARN from the output, because it will be used later in the procedure.&lt;/p&gt;
&lt;h3 id="step-2-retrieve-the-id-of-your-domain"&gt;Step 2: Retrieve the ID of your domain&lt;/h3&gt;
&lt;p&gt;Retrieve the ID of your domain by running the following command. Replace &lt;code&gt;&amp;lt;YOUR_DOMAIN_NAME&amp;gt;&lt;/code&gt; with the name of your SageMaker Unified Studio domain.&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-bash"&gt;aws datazone list-domains \
--region us-east-1 \
--query "items[?name=='&amp;lt;YOUR_DOMAIN_NAME&amp;gt;'].id | [0]" \
--output text&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Note the returned ID, because it will be used later in the procedure.&lt;/p&gt;
&lt;h3 id="step-3-identify-the-tooling-blueprint"&gt;Step 3: Identify the Tooling blueprint&lt;/h3&gt;
&lt;p&gt;Retrieve the Tooling blueprint ID by running the following command. Replace &lt;code&gt;&amp;lt;domain-id&amp;gt;&lt;/code&gt; with the ID you noted in Step 2.&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;div class="hide-language"&gt;
  &lt;pre&gt;&lt;code class="lang-code"&gt;aws datazone list-environment-blueprints \
  --domain-identifier &amp;lt;domain-id&amp;gt; \
  --managed \
  --region eu-west-1 \
  --query "items[?name=='Tooling'].id" \
  --output json | jq -r '.[0]'&lt;/code&gt;&lt;/pre&gt;
 &lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Note the returned ID, because it will be used later in the procedure.&lt;/p&gt;
&lt;h3 id="step-4-read-the-current-configuration"&gt;Step 4: Read the current configuration&lt;/h3&gt;
&lt;p&gt;Retrieve the current Tooling blueprint configuration by executing the following command. Replace &lt;code&gt;&amp;lt;domain-id&amp;gt;&lt;/code&gt; with the ID from Step 2 and &lt;code&gt;&amp;lt;tooling-bp-id&amp;gt;&lt;/code&gt; with the ID from Step 3.&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;div class="hide-language"&gt;
  &lt;pre&gt;&lt;code class="lang-code"&gt;aws datazone get-environment-blueprint-configuration \
--domain-identifier &amp;lt;domain-id&amp;gt; \
--environment-blueprint-identifier &amp;lt;tooling-bp-id&amp;gt; \
--region us-east-1 | tee tooling-bp-config-backup.json&lt;/code&gt;&lt;/pre&gt;
 &lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&lt;b&gt;Important&lt;/b&gt;: Back up the output of &lt;code&gt;get-environment-blueprint-configuration&lt;/code&gt; before making any changes. The command above pipes the response to &lt;code&gt;tooling-bp-config-backup.json&lt;/code&gt; so you have a restore point if you need to revert.&lt;/p&gt;
&lt;p&gt;Note the values of &lt;code&gt;provisioningRoleArn&lt;/code&gt;, &lt;code&gt;manageAccessRoleArn&lt;/code&gt;, &lt;code&gt;enabledRegions&lt;/code&gt;, and all fields inside &lt;code&gt;regionalParameters&lt;/code&gt; (&lt;code&gt;AZs&lt;/code&gt;, &lt;code&gt;S3Location&lt;/code&gt;, &lt;code&gt;Subnets&lt;/code&gt;, &lt;code&gt;VpcId&lt;/code&gt;). You will need all of these in the next step.&lt;/p&gt;
&lt;h3 id="step-5-set-the-permissions-boundary"&gt;Step 5: Set the permissions boundary&lt;/h3&gt;
&lt;p&gt;Update the blueprint configuration to include &lt;code&gt;PermissionsBoundaryArn&lt;/code&gt; in the regional parameters using the following command.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Important:&lt;/strong&gt; The &lt;code&gt;put-environment-blueprint-configuration&lt;/code&gt; API operates in overwrite mode, it replaces the entire configuration with what you provide. You must include all existing values from the previous step’s output. The only new addition is &lt;code&gt;PermissionsBoundaryArn&lt;/code&gt; inside the regional parameters. Omitting any existing parameter removes it.&lt;/p&gt;
&lt;p&gt;Make sure to replace &lt;code&gt;&amp;lt;domain-id&amp;gt;&lt;/code&gt; with the ID you noted in Step 2, &lt;code&gt;&amp;lt;tooling-bp-id&amp;gt;&lt;/code&gt; with the ID you noted in Step 3, and all other &lt;code&gt;&amp;lt;placeholder&amp;gt;&lt;/code&gt; values with the corresponding values from Step 4’s output.&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-bash"&gt;aws datazone put-environment-blueprint-configuration \
--domain-identifier &amp;lt;domain-id&amp;gt; \
--environment-blueprint-identifier &amp;lt;tooling-bp-id&amp;gt; \
--enabled-regions '&amp;lt;enabledRegions&amp;gt;' \
--provisioning-role-arn "&amp;lt;provisioningRoleArn&amp;gt;" \
--manage-access-role-arn "&amp;lt;manageAccessRoleArn&amp;gt;" \
--regional-parameters '{
  "&amp;lt;region&amp;gt;": {
    "AZs": "&amp;lt;AZs&amp;gt;",
    "S3Location": "&amp;lt;S3Location&amp;gt;",
    "Subnets": "&amp;lt;Subnets&amp;gt;",
    "VpcId": "&amp;lt;VpcId&amp;gt;",
    "PermissionsBoundaryArn": "arn:aws:iam::&amp;lt;account-id&amp;gt;:policy/SMUSToolingBoundary"
  }
}' \
--region &amp;lt;region&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;The following anonymized example is based on an existing Tooling blueprint configuration. Its S3Location reflects the bucket naming pattern used in that environment. Copy the exact S3Location returned in Step 4. Don’t use the following illustrative value. Here’s an example:&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-bash"&gt;aws datazone put-environment-blueprint-configuration \
--domain-identifier &amp;lt;domain-id&amp;gt; \
--environment-blueprint-identifier &amp;lt;tooling-bp-id&amp;gt; \
--enabled-regions '["us-east-1"]' \
--provisioning-role-arn "arn:aws:iam::&amp;lt;account-id&amp;gt;:role/service-role/AmazonSageMakerProvisioning-&amp;lt;account-id&amp;gt;" \
--manage-access-role-arn "arn:aws:iam::&amp;lt;account-id&amp;gt;:role/service-role/AmazonSageMakerManageAccess-us-east-1-&amp;lt;domain-id&amp;gt;" \
--regional-parameters '{
  "us-east-1": {
    "AZs": "us-east-1a,us-east-1b,us-east-1c,us-east-1d",
    "S3Location": "s3://amazon-sagemaker-&amp;lt;account-id&amp;gt;-us-east-1-&amp;lt;suffix&amp;gt;",
    "Subnets": "&amp;lt;subnet-1&amp;gt;,&amp;lt;subnet-2&amp;gt;,&amp;lt;subnet-3&amp;gt;,&amp;lt;subnet-4&amp;gt;",
    "VpcId": "&amp;lt;vpc-id&amp;gt;",
    "PermissionsBoundaryArn": "arn:aws:iam::&amp;lt;account-id&amp;gt;:policy/SMUSToolingBoundary"
  }
}' \
--region us-east-1&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h3 id="step-6-verify-the-configuration-was-applied"&gt;Step 6: Verify the configuration was applied&lt;/h3&gt;
&lt;p&gt;Confirm the permissions boundary ARN is now set in the blueprint configuration using the following command. Make sure to replace &lt;code&gt;&amp;lt;domain-id&amp;gt;&lt;/code&gt; with the ID you noted in Step 2 and &lt;code&gt;&amp;lt;tooling-bp-id&amp;gt;&lt;/code&gt; with the ID you noted in Step 3.&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-bash"&gt;aws datazone get-environment-blueprint-configuration \
--domain-identifier &amp;lt;domain-id&amp;gt; \
--environment-blueprint-identifier &amp;lt;tooling-bp-id&amp;gt; \
--region us-east-1 \
--query "regionalParameters.\"us-east-1\".PermissionsBoundaryArn"&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;The output should return your boundary policy ARN:&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-plaintext"&gt;"arn:aws:iam::&amp;lt;account-id&amp;gt;:policy/SMUSToolingBoundary"&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h2 id="validating-the-configuration"&gt;Validating the configuration&lt;/h2&gt;
&lt;p&gt;After configuring the permissions boundary, in this section you will get instructions to create a new project to verify it works end to end and that the IAM roles created with the project actually include the permissions boundary.&lt;/p&gt;
&lt;h3 id="step-1-select-a-project-profile-in-enabled-state"&gt;Step 1: Select a project profile in enabled state&lt;/h3&gt;
&lt;p&gt;Use the following command to list project profiles configured in your domain. Make sure to replace &lt;code&gt;&amp;lt;domain-id&amp;gt;&lt;/code&gt; with the ID you noted in Step 2 of the “Configuring the permissions boundary” section.&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-bash"&gt;aws datazone list-project-profiles \
--domain-identifier &amp;lt;domain-id&amp;gt; \
--region us-east-1&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Choose a project profile that has &lt;code&gt;"status": "ENABLED"&lt;/code&gt;. Note the &lt;code&gt;id&lt;/code&gt; of any project profile returned in the previous command.&lt;/p&gt;
&lt;h3 id="step-2-create-a-test-project"&gt;Step 2: Create a test project&lt;/h3&gt;
&lt;p&gt;Create a new project using the following command. Make sure to replace &lt;code&gt;&amp;lt;domain-id&amp;gt;&lt;/code&gt; with the ID you noted in Step 2 of the “Configuring the permissions boundary” section and to replace &lt;code&gt;&amp;lt;profile-id&amp;gt;&lt;/code&gt; with the project profile ID noted in Step 1 of this section.&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-bash"&gt;aws datazone create-project \
--domain-identifier &amp;lt;domain-id&amp;gt; \
--name "PB-Validation-$(date +%Y%m%d-%H%M%S)" \
--project-profile-id &amp;lt;profile-id&amp;gt; \
--region us-east-1&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Note the &lt;code&gt;id&lt;/code&gt; (project ID) returned in the response. Wait for the Tooling blueprint to provision. This typically takes a minute or two. After provisioning completes, confirm that the validation project reaches the &lt;strong&gt;Active&lt;/strong&gt; state, as shown in Figure 6.&lt;/p&gt;
&lt;div style="width: 810px" class="wp-caption alignnone"&gt;
 &lt;a href="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/08/25/BDB-6010-6.jpg" target="_blank" rel="noopener"&gt;&lt;img src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/08/25/BDB-6010-6.jpg" alt="SMUS Projects list showing the timestamped PB-Validation project in Active status after successful creation" width="800"&gt;&lt;/a&gt;
 &lt;p class="wp-caption-text"&gt;Figure 6: Project created successfully with the permissions boundary configured&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;The &lt;strong&gt;Projects&lt;/strong&gt; list shows the timestamped &lt;code&gt;PB-Validation-*&lt;/code&gt; project with an &lt;strong&gt;Active&lt;/strong&gt; status, confirming that project creation succeeded with the custom boundary configured.&lt;/p&gt;
&lt;h3 id="step-3-verify-the-roles-have-the-boundary-attached"&gt;Step 3: Verify the roles have the boundary attached&lt;/h3&gt;
&lt;p&gt;In this section you check that the IAM roles created with the project have the permissions boundary attached. Use the following commands to get the configuration for the IAM roles created with the project you just created. Replace &lt;code&gt;&amp;lt;domain-id&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;project-id&amp;gt;&lt;/code&gt; with the values from the previous steps.&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-bash"&gt;# Get the environment ID
ENV_ID=$(aws datazone list-environments \
--domain-identifier &amp;lt;domain-id&amp;gt; \
--project-identifier &amp;lt;project-id&amp;gt; \
--region us-east-1 \
--query "items[?name=='Tooling'].id" --output text)

# List IAM roles in the AWS CloudFormation stack
aws cloudformation describe-stack-resources \
--stack-name "DataZone-Env-${ENV_ID}" \
--region us-east-1 \
--query "StackResources[?ResourceType=='AWS::IAM::Role'].PhysicalResourceId" \
--output table

# Verify each role has the boundary
aws iam get-role \
--role-name "&amp;lt;role-name&amp;gt;" \
--query 'Role.PermissionsBoundary'&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;All three roles should return a response showing the permissions boundary ARN:&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-json"&gt;{
  "PermissionsBoundaryType": "Policy",
  "PermissionsBoundaryArn": "arn:aws:iam::&amp;lt;account-id&amp;gt;:policy/SMUSToolingBoundary"
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;You can also verify each role in the IAM console. Figure 7 shows the permissions boundary for the project user role.&lt;/p&gt;
&lt;div style="width: 810px" class="wp-caption alignnone"&gt;
 &lt;a href="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/08/25/BDB-6010-7.jpg" target="_blank" rel="noopener"&gt;&lt;img src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/08/25/BDB-6010-7.jpg" alt="IAM console Permissions tab showing SMUSToolingBoundary as the permissions boundary on datazone_usr_role" width="800"&gt;&lt;/a&gt;
 &lt;p class="wp-caption-text"&gt;Figure 7: IAM console showing the permissions boundary attached to the datazone_usr_role&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;The &lt;code&gt;datazone_usr_role&lt;/code&gt; &lt;strong&gt;Permissions&lt;/strong&gt; tab displays &lt;code&gt;SMUSToolingBoundary&lt;/code&gt; as its customer-managed permissions boundary.&lt;/p&gt;
&lt;p&gt;Figure 8 confirms that the same boundary is attached to the Amazon Bedrock service role.&lt;/p&gt;
&lt;div style="width: 810px" class="wp-caption alignnone"&gt;
 &lt;a href="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/08/25/BDB-6010-8.jpg" target="_blank" rel="noopener"&gt;&lt;img src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/08/25/BDB-6010-8.jpg" alt="IAM console showing SMUSToolingBoundary as the permissions boundary on AmazonBedrockServiceRole" width="800"&gt;&lt;/a&gt;
 &lt;p class="wp-caption-text"&gt;Figure 8: IAM console showing the permissions boundary attached to the AmazonBedrockServiceRole&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;The &lt;code&gt;AmazonBedrockServiceRole&lt;/code&gt; also displays &lt;code&gt;SMUSToolingBoundary&lt;/code&gt; as its customer-managed permissions boundary.&lt;/p&gt;
&lt;p&gt;Figure 9 verifies the boundary on the third Tooling role, the Bedrock Lambda execution role.&lt;/p&gt;
&lt;div style="width: 810px" class="wp-caption alignnone"&gt;
 &lt;a href="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/08/25/BDB-6010-9.jpg" target="_blank" rel="noopener"&gt;&lt;img src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/08/25/BDB-6010-9.jpg" alt="IAM console showing SMUSToolingBoundary as the permissions boundary on AmazonBedrockLambdaExecutionRole" width="800"&gt;&lt;/a&gt;
 &lt;p class="wp-caption-text"&gt;Figure 9: IAM console showing the permissions boundary attached to the AmazonBedrockLambdaExecutionRole&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;The &lt;code&gt;AmazonBedrockLambdaExecutionRole&lt;/code&gt; likewise displays &lt;code&gt;SMUSToolingBoundary&lt;/code&gt;, confirming that all three provisioned roles carry the boundary.&lt;/p&gt;
&lt;h3 id="step-4-verify-the-boundary-denies-ai-agent-actions"&gt;Step 4: Verify the boundary denies AI agent actions&lt;/h3&gt;
&lt;p&gt;In this section you verify the boundary actually denies AI agent actions. If you configured the boundary from the use case section earlier, the boundary blocks Data Notebooks and messages to the Data Agent, such as the Query Editor assistant. Any such attempt returns an access denied error. The project user role has the boundary attached, so even if the role’s identity policies grant the relevant APIs, the boundary’s explicit deny takes precedence.&lt;/p&gt;
&lt;p&gt;To confirm, navigate to your project in SageMaker Unified Studio and test the following actions:&lt;/p&gt;
&lt;ol type="1"&gt;
 &lt;li&gt;&lt;b&gt;Attempt to create a notebook&lt;/b&gt; – In the left sidebar, select &lt;b&gt;Notebooks&lt;/b&gt;. Select &lt;b&gt;Create notebook&lt;/b&gt;. The operation will fail because the permissions boundary prevents the &lt;code&gt;datazone:CreateNotebook&lt;/code&gt; action (Figure 10).&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="mceTemp"&gt;
 &lt;div id="attachment_94612" style="width: 1323px" class="wp-caption alignnone"&gt;
  &lt;img aria-describedby="caption-attachment-94612" loading="lazy" class="size-full wp-image-94612" src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/25/create_notebook.png" alt="" width="1313" height="744"&gt;
  &lt;p id="caption-attachment-94612" class="wp-caption-text"&gt;Figure 10: Permissions boundary preventing creation of Data Notebooks&lt;/p&gt;
 &lt;/div&gt;
 &lt;p&gt;After the create action, Amazon SageMaker Unified Studio reports &lt;b&gt;Failed to create notebook &lt;/b&gt;and identifies &lt;code&gt;datazone:CreateNotebook&lt;/code&gt; as explicitly denied by &lt;code&gt;SMUSToolingBoundary&lt;/code&gt;.&lt;/p&gt;
 &lt;ol start="2" type="1"&gt;
  &lt;li&gt;&lt;b&gt;Attempt to use Data Agent in the Query Editor &lt;/b&gt;– In the left sidebar, select Query Editor, then select the Chat with AI icon. The agent chat will fail to load because the permissions boundary blocks the APIs required by Data Agent (Figure 11).&lt;/li&gt;
 &lt;/ol&gt;
 &lt;div id="attachment_94613" style="width: 1751px" class="wp-caption alignnone"&gt;
  &lt;img aria-describedby="caption-attachment-94613" loading="lazy" class="size-full wp-image-94613" src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/25/query_editor.png" alt="" width="1741" height="736"&gt;
  &lt;p id="caption-attachment-94613" class="wp-caption-text"&gt;Figure 11: Permissions boundary preventing using Data Agent on Query Editor&lt;/p&gt;
 &lt;/div&gt;
 &lt;p&gt;The Query Editor remains available, but the Agent panel reports “&lt;b&gt;You don’t have access to Data Agent&lt;/b&gt;“. In this configured test, that message is the user-visible result of denying the Data Agent APIs. The screenshot itself doesn’t display the denied API or boundary ARN.&lt;/p&gt;
 &lt;h2 id="important-considerations"&gt;Important considerations&lt;/h2&gt;
 &lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Immutable after project creation&lt;/strong&gt; – The permissions boundary is set at provisioning time. Changing the boundary ARN on the blueprint configuration only affects &lt;em&gt;new&lt;/em&gt; projects. Existing projects retain their original boundary.&lt;/li&gt;
  &lt;li&gt;&lt;b&gt;Applies to all Tooling-provisioned roles&lt;/b&gt; – When &lt;code&gt;PermissionsBoundaryArn&lt;/code&gt; is configured on the Tooling blueprint, SageMaker Unified Studio attaches the specified customer-managed permissions boundary to all three IAM roles created by that blueprint. It’s applied uniformly — you can’t selectively apply it to individual roles. No boundary is attached unless you configure one, so if your governance requires a boundary, set it explicitly and verify the resulting roles rather than assuming one is present by default.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Policy must exist&lt;/strong&gt; – The IAM policy referenced by &lt;code&gt;PermissionsBoundaryArn&lt;/code&gt; must exist in the account before project creation. If the policy is deleted or the ARN is invalid, provisioning will fail.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Tooling blueprint only&lt;/strong&gt; – Among Amazon SageMaker Unified Studio provided blueprints, only the Tooling blueprint supports custom permissions boundaries. Other provided blueprints that create IAM roles (for example, the EmrOnEc2 blueprint) don’t currently support this feature. If your organization requires permissions boundaries on roles created by additional blueprints, you can build &lt;a href="https://docs.aws.amazon.com/sagemaker-unified-studio/latest/adminguide/custom-blueprint.html" target="_blank" rel="noopener"&gt;custom blueprints&lt;/a&gt; that include a permissions boundary configuration so you can extend this security control across your entire project infrastructure.&lt;/li&gt;
 &lt;/ul&gt;
 &lt;h2 id="clean-up"&gt;Clean up&lt;/h2&gt;
 &lt;p&gt;To remove test resources, delete the test project from the SageMaker Unified Studio UI. On the project’s &lt;b&gt;Overview&lt;/b&gt; page, choose the &lt;b&gt;⋮&lt;/b&gt; (more actions) menu in the top-right and choose &lt;b&gt;Delete project&lt;/b&gt;.&lt;/p&gt;
 &lt;div id="attachment_94614" style="width: 1391px" class="wp-caption alignnone"&gt;
  &lt;img aria-describedby="caption-attachment-94614" loading="lazy" class="size-full wp-image-94614" src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/25/delete_project.png" alt="" width="1381" height="242"&gt;
  &lt;p id="caption-attachment-94614" class="wp-caption-text"&gt;Figure 12: Deleting the test project from the project Overview page.&lt;/p&gt;
 &lt;/div&gt;
 &lt;p&gt;In the &lt;b&gt;Delete project&lt;/b&gt; dialog, type &lt;code&gt;confirm&lt;/code&gt; in the text box to acknowledge that the action is final, then choose &lt;b&gt;Delete project&lt;/b&gt;. This permanently deletes the project and its underlying resources, and triggers an asynchronous AWS CloudFormation stack deletion.&lt;/p&gt;
 &lt;div id="attachment_94615" style="width: 711px" class="wp-caption alignnone"&gt;
  &lt;img aria-describedby="caption-attachment-94615" loading="lazy" class="size-full wp-image-94615" src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/25/delete_project_2.png" alt="" width="701" height="326"&gt;
  &lt;p id="caption-attachment-94615" class="wp-caption-text"&gt;Figure 13: Confirming project deletion.&lt;/p&gt;
 &lt;/div&gt;
 &lt;p&gt;To remove the boundary from future projects, re-run the &lt;code&gt;put-environment-blueprint-configuration&lt;/code&gt; command from &lt;b&gt;Step 5: Set the permissions boundary&lt;/b&gt;, but omit the &lt;code&gt;PermissionsBoundaryArn&lt;/code&gt; field from the regional parameters. Because you backed up the original configuration in &lt;b&gt;Step 4: Read the current configuration&lt;/b&gt; (&lt;code&gt;tooling-bp-config-backup.json&lt;/code&gt;), you can reuse the exact same &lt;code&gt;provisioningRoleArn&lt;/code&gt;, &lt;code&gt;manageAccessRoleArn&lt;/code&gt;, &lt;code&gt;enabledRegions&lt;/code&gt;, and &lt;code&gt;regionalParameters&lt;/code&gt; values (&lt;code&gt;AZs&lt;/code&gt;, &lt;code&gt;S3Location&lt;/code&gt;, &lt;code&gt;Subnets&lt;/code&gt;, &lt;code&gt;VpcId&lt;/code&gt;) — just without &lt;code&gt;PermissionsBoundaryArn&lt;/code&gt; — so the blueprint returns to provisioning roles with no permissions boundary.&lt;/p&gt;
 &lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
 &lt;p&gt;With the custom permissions boundary feature for Amazon SageMaker Unified Studio, organizations can adopt Amazon SageMaker Unified Studio Tooling blueprints without compromising their IAM governance posture. By configuring a single parameter on the Tooling blueprint, all IAM roles provisioned by future projects automatically carry the specified permissions boundary. This satisfies SCPs that mandate a boundary on every role and gives administrators granular control over what the Tooling roles can do, for example disabling AI agent and notebook capabilities. Remember that the example boundary in this post is illustrative, because it scopes to the services SageMaker Unified Studio uses but is still coarse.&lt;/p&gt;
 &lt;blockquote&gt;
  &lt;p&gt;&lt;em&gt;“I just updated the EnvironmentBlueprintConfiguration for the Tooling blueprint to include the new PermissionsBoundaryArn param. After that the blueprint provisioned successfully with the required permissions boundary attached to all the IAM roles, in line with our security policies. In the end it was a one-line change.”&lt;/em&gt;&lt;/p&gt;
 &lt;/blockquote&gt;
 &lt;p&gt;— Nat Noordanus, Data Tech Lead at Nexthink&lt;/p&gt;
 &lt;p&gt;To get started, create your permissions boundary policy, configure it on the Tooling blueprint using the CLI, and create a project to verify the boundary is attached.&lt;/p&gt;
 &lt;p&gt;For more information, see the documentation for &lt;a href="https://docs.aws.amazon.com/sagemaker-unified-studio/latest/userguide/what-is-sagemaker-unified-studio.html" target="_blank" rel="noopener"&gt;Amazon SageMaker Unified Studio&lt;/a&gt;, &lt;a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_boundaries.html" target="_blank" rel="noopener"&gt;IAM permissions boundaries&lt;/a&gt;, and &lt;a href="https://docs.aws.amazon.com/organizations/latest/userguide/orgs_manage_policies_scps.html" target="_blank" rel="noopener"&gt;Service Control Policies&lt;/a&gt;.&lt;/p&gt;
 &lt;hr&gt;
 &lt;h2&gt;About the authors&lt;/h2&gt;
 &lt;footer&gt;
  &lt;div class="blog-author-box"&gt;
   &lt;div class="blog-author-image"&gt;
    &lt;p&gt;&lt;img loading="lazy" class="alignleft size-full" src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/08/25/BDB-6010-13.png" alt="Sanjana Sekar" width="100" height="100"&gt;&lt;/p&gt;
   &lt;/div&gt;
   &lt;h3 class="lb-h4"&gt;Sanjana Sekar&lt;/h3&gt;
   &lt;p&gt;Sanjana is a Software Development Engineer on the Amazon SageMaker Unified Studio team. She is focused on improving Data Agent capabilities and the compute blueprints experience within SageMaker Unified Studio. Outside of work, she enjoys hiking and biking.&lt;/p&gt;
  &lt;/div&gt;
  &lt;div class="blog-author-box"&gt;
   &lt;div class="blog-author-image"&gt;
    &lt;p&gt;&lt;img loading="lazy" class="alignleft size-full" src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/08/25/BDB-6010-14.png" alt="Luca Perrozzi" width="100" height="100"&gt;&lt;/p&gt;
   &lt;/div&gt;
   &lt;h3 class="lb-h4"&gt;Luca Perrozzi&lt;/h3&gt;
   &lt;p&gt;Luca is a Solutions Architect at AWS, based in Switzerland. He focuses on innovation topics at AWS, especially in Artificial Intelligence. Luca holds a PhD in particle physics and has 15 years of hands-on experience as a research scientist and software engineer.&lt;/p&gt;
  &lt;/div&gt;
  &lt;div class="blog-author-box"&gt;
   &lt;div class="blog-author-image"&gt;
    &lt;p&gt;&lt;img loading="lazy" class="alignleft size-full" src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/08/25/BDB-6010-15.png" alt="Ganesh Sambandan" width="100" height="100"&gt;&lt;/p&gt;
   &lt;/div&gt;
   &lt;h3 class="lb-h4"&gt;Ganesh Sambandan&lt;/h3&gt;
   &lt;p&gt;Ganesh is a Senior Technical Account Manager at AWS, helping organizations adopt best practices for running secure, reliable and well-architected workloads on AWS. He works closely with strategic customers to accelerate the adoption of AI-driven cloud operations, enabling more effective DevOps practices, automation and operational excellence.&lt;/p&gt;
  &lt;/div&gt;
  &lt;div class="blog-author-box"&gt;
   &lt;div class="blog-author-image"&gt;
    &lt;p&gt;&lt;img loading="lazy" class="alignleft size-full" src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/08/25/BDB-6010-16.png" alt="Stefano Sandona" width="100" height="100"&gt;&lt;/p&gt;
   &lt;/div&gt;
   &lt;h3 class="lb-h4"&gt;Stefano Sandona&lt;/h3&gt;
   &lt;p&gt;Stefano is a Senior Worldwide Specialist Solutions Architect for Big Data at AWS, helping customers build efficient, secure, and scalable data solutions.&lt;/p&gt;
  &lt;/div&gt;
  &lt;div class="blog-author-box"&gt;
   &lt;div class="blog-author-image"&gt;
    &lt;p&gt;&lt;img loading="lazy" class="alignleft size-full" src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/08/25/BDB-6010-17.png" alt="Paolo Romagnoli" width="100" height="100"&gt;&lt;/p&gt;
   &lt;/div&gt;
   &lt;h3 class="lb-h4"&gt;Paolo Romagnoli&lt;/h3&gt;
   &lt;p&gt;Paolo is a Senior Solutions Architect at AWS who helps global energy organizations design and build data and AI enterprise solutions at scale.&lt;/p&gt;
  &lt;/div&gt;
 &lt;/footer&gt;
&lt;/div&gt;</content:encoded>
					
		
		
			</item>
		<item>
		<title>How Delivery Hero rebuilt real-time ad measurement with Apache Flink</title>
		<link>https://aws.amazon.com/blogs/big-data/how-delivery-hero-rebuilt-real-time-ad-measurement-with-apache-flink/</link>
		
		<dc:creator><![CDATA[Kirill Tishenkov]]></dc:creator>
		<pubDate>Thu, 24 Sep 2026 16:09:12 +0000</pubDate>
				<category><![CDATA[Amazon Managed Service for Apache Flink]]></category>
		<category><![CDATA[Customer Solutions]]></category>
		<guid isPermaLink="false">dd28e81478d2c311d0337b82a3b8190de2a0b001</guid>

					<description>Learn how Delivery Hero rebuilt its ad measurement pipeline from hourly batch processing to real time on Amazon Managed Service for Apache Flink, cutting the gap between an event and its recording from 61 minutes to 1.2 seconds and reducing monthly operational costs by about 57%.</description>
										<content:encoded>&lt;p&gt;&lt;em&gt;This post is co-written with Kirill Tishenkov, Alexandru Pisarenco, Upendra Kambhampati, and Sabariesh Ganesan from Delivery Hero.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Real-time ad measurement is one of the harder streaming problems in advertising. Every impression and click has to be accurate enough to bill a vendor for, and fresh enough for the ad server to act on. In this post, we describe how Delivery Hero moved its ad measurement pipeline from hourly batch processing to real time on Amazon Managed Service for Apache Flink. Delivery Hero, based in Berlin, Germany, is one of the world’s leading local delivery platforms, operating across Asia, Europe, Latin America, the Middle East, and North Africa. Working with more than 1.5 million restaurant partners and local vendors in around 65 countries, Delivery Hero handles millions of orders for food, groceries, and everyday essentials daily.&lt;/p&gt;
&lt;p&gt;At the center of Delivery Hero’s business sits an advertising platform that connects vendors and brands with millions of active consumers. The platform handles tens of thousands of messages per second and processes billions of ad events per day, supporting an advertising revenue stream that reached almost EUR 1.5 billion in 2025. Every impression served and every click recorded must satisfy two requirements at once. The data must be accurate enough to bill vendors fairly, and fresh enough for the ad server to act on in real time. Delivery Hero replaced its batch-oriented measurement system with a fully real-time pipeline built on Amazon Managed Service for Apache Flink. The new pipeline cut infrastructure costs by more than half and reached a level of data quality the previous system could not.&lt;/p&gt;
&lt;h2 id="challenges-with-the-legacy-system"&gt;Challenges with the legacy system&lt;/h2&gt;
&lt;p&gt;The legacy ads measurement system consumed impression, click, and order events from message queues. It enriched them through synchronous API calls for campaign metadata and product lookups, then wrote hourly aggregated metrics to a reporting database. This design worked at a modest scale, but five structural problems emerged as traffic grew.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;No event-time semantics, and slow processing.&lt;/strong&gt; The pipeline bucketed events by the time it processed them rather than the time they occurred, because most events arrived without a usable event timestamp. Results were internally consistent, but they skewed whenever ingestion lagged or events arrived out of order. That widened the error bar on every time-sensitive metric, including return on ad spend (ROAS). The bigger cost was speed. Metrics were assembled in hourly batches, so the average gap between when an event occurred and when it was recorded was 61 minutes. The platform was reacting to clicks and impressions up to an hour after the fact, far too late for budget pacing or ad serving.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Synchronous enrichment capped how far the system could scale.&lt;/strong&gt; Enrichment is the step that attaches business context to a raw ad event: which campaign it belongs to, which vendor owns it, and which product was advertised. In the legacy system, every event triggered a chain of blocking external API calls to fetch that context. During traffic spikes, such as a flash sale or a back-to-school surge, exhausted connection pools cascaded into billing, ad serving, and reporting simultaneously. There was no back-pressure mechanism and no way to scale enrichment independently of event ingestion.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The database behind the pipeline was built for a very different access pattern.&lt;/strong&gt; The pipeline kept its working data in a NoSQL document database: deduplication keys, attribution history, and running totals. The platform inherited that database from its pre-streaming era, when ad measurement looked like document storage and retrieval. The workload then evolved into continuous deduplication, multi-day attribution lookups, and rolling aggregation. Every event ended up triggering a full document read and write against a database designed for occasional access, not per-event mutation. Read/write amplification stored far more data than the logic needed, every write triggered index updates and collection scans, and storage costs grew in lockstep with query latency. At peak load, this often tipped into production outages.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Reprocessing was a project, not a capability.&lt;/strong&gt; Recovery from a bug, a traffic spike, or a corrupted upstream batch required different tooling for every consuming system. Billing replay was a hand-rolled combination of Google Cloud BigQuery tables, Pub/Sub topics, and custom CLI scripts. Reporting replay ran as a separate daily Airflow job with a one-hour-per-day cost and a six-month horizon. Campaigns and credits events had no replay path at all. Every recovery was a coordination exercise across teams. Every event type that could not be replayed was a class of problems that could only be patched manually after the fact.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Incomplete event context corrupted downstream data quality.&lt;/strong&gt; Enrichment was synchronous and best-effort, so the pipeline still wrote through events that failed a lookup or arrived malformed, leaving their fields blank. The pipeline had no mechanism to recover the missing context later. Three gaps mattered most:&lt;/p&gt;
&lt;ul&gt;
 &lt;li&gt;&lt;strong&gt;Missing session rate&lt;/strong&gt;: the share of events that landed without a usable session ID, leaving the interaction unattached to the user browsing session it belonged to. At 30–40 percent, roughly a third of all events could not be tied back to a session, breaking any session-scoped analysis or feature.&lt;/li&gt;
 &lt;li&gt;&lt;strong&gt;Missing customer identifiers (IDs)&lt;/strong&gt;: the share of events with no customer ID, severing the link between an ad interaction and the customer who generated it and weakening attribution and personalization.&lt;/li&gt;
 &lt;li&gt;&lt;strong&gt;Missing impression timestamps:&lt;/strong&gt; the share of impression events lacking a reliable event-time timestamp (the same root cause as the processing-time fallback described earlier). At 91 percent, most impressions had no trustworthy event time, forcing the processing-time approximation and widening the error bar on every time-based metric.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These omissions propagated silently into the reporting metrics and into the session-scoped features consumed by machine learning (ML) models for campaign ranking, conversion-rate estimation, and anomaly detection.&lt;/p&gt;
&lt;h2 id="why-amazon-managed-service-for-apache-flink"&gt;Why Amazon Managed Service for Apache Flink&lt;/h2&gt;
&lt;p&gt;The team set three non-negotiable requirements. First, fault-tolerant data processing, to eliminate data loss. Second, stateful stream processing that could hold multiple days of interaction history in low-cost, low-latency storage. Third, fully managed infrastructure, so engineers could focus on application logic rather than cluster operations.&lt;/p&gt;
&lt;p&gt;The team selected Apache Flink because it satisfies all three requirements natively, without bolting on external systems. Its event-time watermark model helps place out-of-order events in the correct time window even when they arrive late. Its RocksDB state backend holds large keyed state on disk without Java Virtual Machine (JVM) heap pressure.&lt;/p&gt;
&lt;p&gt;The team chose Amazon Managed Service for Apache Flink over self-hosted Flink on Amazon Elastic Kubernetes Service (Amazon EKS) to eliminate the operational burden of managing JobManagers, TaskManagers, and checkpoint storage. Amazon Kinesis Data Streams serves as the upstream event bus, with two streams: one for user event actions (impressions and clicks) and one for orders. The team chose Kinesis Data Streams over Amazon Managed Streaming for Apache Kafka (Amazon MSK) for cost efficiency at this topology.&lt;/p&gt;
&lt;p&gt;Amazon DynamoDB holds campaign and product reference data, queried through Flink’s Async I/O API to enrich events without blocking the processing pipeline. AWS Secrets Manager stores ad event decryption keys, retrieved once at job startup. Amazon Simple Storage Service (Amazon S3) stores granular event logs in Avro format and serves as the incremental checkpoint store for Flink state. Amazon EventBridge Pipes bridged Amazon Simple Queue Service (Amazon SQS) to Kinesis in the minimum viable product (MVP) phase without any custom code, cutting time-to-production by two weeks.&lt;/p&gt;
&lt;h2 id="solution-architecture"&gt;Solution architecture&lt;/h2&gt;
&lt;p&gt;The following diagram shows the end-to-end pipeline.&lt;/p&gt;
&lt;div style="width: 810px" class="wp-caption alignnone"&gt;
 &lt;a href="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/18/BDB-6091-1.png" target="_blank" rel="noopener"&gt;&lt;img src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/18/BDB-6091-1.png" alt="Architecture diagram. Two Amazon Simple Notification Service (Amazon SNS) topics receive user event actions and order events. Amazon SQS buffers them, and Amazon EventBridge Pipes or AWS Fargate forwards them into two Amazon Kinesis Data Streams. Amazon Managed Service for Apache Flink then decrypts, deduplicates, enriches from Amazon DynamoDB, attributes, and aggregates the events. It writes granular events and checkpoints to Amazon S3, aggregated metrics to the reporting database, and billing events to Apache Kafka topics consumed by the ad server and budget service." width="800"&gt;&lt;/a&gt;
 &lt;p class="wp-caption-text"&gt;Figure 1: End-to-end architecture of the real-time ad measurement pipeline&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;Two Amazon Simple Notification Service (Amazon SNS) topics ingest events: one receives user event actions (compressed, encrypted ad tokens containing campaign, vendor, and placement metadata), the other receives order events. Amazon SQS buffers both before Amazon EventBridge Pipes (MVP) or an AWS Fargate service (production) forwards them into Kinesis.&lt;/p&gt;
&lt;p&gt;Amazon Managed Service for Apache Flink runs a five-stage Java pipeline:&lt;/p&gt;
&lt;ol&gt;
 &lt;li&gt;&lt;strong&gt;Decompress and decrypt.&lt;/strong&gt; The pipeline decrypts the ad event token using keys from AWS Secrets Manager.&lt;/li&gt;
 &lt;li&gt;&lt;strong&gt;Deduplicate.&lt;/strong&gt; The pipeline keys events on a composite of entity, ad, event, and customer identifiers. Flink’s RocksDB state tracks seen events over a 30-hour window (approximately 20 GB of state), filtering duplicates while preserving them in Amazon S3 for audit.&lt;/li&gt;
 &lt;li&gt;&lt;strong&gt;Enrich.&lt;/strong&gt; Flink’s Async I/O API queries Amazon DynamoDB concurrently for campaign metadata and product master codes, populated continuously from upstream Kafka topics by an AWS Fargate consumer.&lt;/li&gt;
 &lt;li&gt;&lt;strong&gt;Attribute.&lt;/strong&gt; A multi-day keyed interval join matches user event actions to subsequent orders on entity, customer, vendor, and campaign dimensions (approximately 100 GB of state). This stage emits attributed orders to Amazon S3.&lt;/li&gt;
 &lt;li&gt;&lt;strong&gt;Aggregate.&lt;/strong&gt; The pipeline accumulates impression, click, order, revenue, and ad spend metrics in RocksDB state, then batch-upserts them to the reporting database every 5 minutes.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The pipeline emits billing events (cost per mille (CPM) impressions and valid cost per click (CPC) clicks) to Apache Kafka topics. The ad server and budget service consume those topics in real time. Flink checkpoints all state incrementally to Amazon S3, so the job restores from the last checkpoint after a failure. Kinesis Data Streams and the upstream sources deliver at-least-once, and the deduplication stage in step 2 drops any event replayed during recovery. Billing is therefore effectively exactly-once, even though the transport underneath it is at-least-once.&lt;/p&gt;
&lt;h2 id="results-and-impact"&gt;Results and impact&lt;/h2&gt;
&lt;p&gt;The redesigned architecture achieved quantifiable performance gains across data fidelity, processing throughput, and operational expenditure, while introducing capabilities that were not feasible under the legacy model.&lt;/p&gt;
&lt;h3 id="processing-latency-from-hourly-windows-to-real-time"&gt;Processing latency: From hourly windows to real time&lt;/h3&gt;
&lt;p&gt;The average gap between when an event was published and when it was recorded dropped from 61 minutes to 1.2 seconds. Budget pacing and aggregated metrics now reflect activity within seconds rather than the following hour. Downstream ad serving and budget pacing systems act on real-time signals instead of reconciling after the fact.&lt;/p&gt;
&lt;h3 id="cost-efficiency"&gt;Cost efficiency&lt;/h3&gt;
&lt;p&gt;The migration reduced monthly operational costs by approximately 57 percent, which more than halves the annual run rate for the pipeline. The saving came alongside stronger reliability, not at its expense.&lt;/p&gt;
&lt;h3 id="system-reliability"&gt;System reliability&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Durable attribution window.&lt;/strong&gt; The multi-day attribution window lives in RocksDB-backed keyed state, roughly 100 GB on local TaskManager disks, checkpointed incrementally to Amazon S3. Per-key lookups stay in the low-millisecond range regardless of state size, and a crash or shard rebalance restores state from the last checkpoint rather than triggering a reconciliation job.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Elasticity replacing fragility.&lt;/strong&gt; Async I/O against DynamoDB removed the synchronous enrichment chain that previously gated every event. The pipeline sustains 20,000 messages per second at peak without back-pressure leaking into ad serving or billing, and enrichment scales independently of ingestion. Flash sales and seasonal surges no longer threaten upstream systems.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Replayable history.&lt;/strong&gt; The pipeline persists every raw event to Amazon S3 in Avro format the moment it lands, and Kinesis Data Streams retains the source stream for up to 7 days. When a logic bug surfaces or a downstream contract changes, the team reprocesses the affected time range deterministically against the original inputs. There is no bespoke backfill job and no reconciliation against external systems. Past data is a first-class input, not a frozen artifact.&lt;/p&gt;
&lt;h3 id="data-quality-at-the-source"&gt;Data quality at the source&lt;/h3&gt;
&lt;p&gt;The following table compares the three data quality gaps before and after the migration.&lt;/p&gt;
&lt;table border="1px" width="100%" cellpadding="10px"&gt;
 &lt;tbody&gt;
  &lt;tr&gt;
   &lt;td&gt;&lt;strong&gt;Metric&lt;/strong&gt;&lt;/td&gt;
   &lt;td&gt;&lt;strong&gt;Before&lt;/strong&gt;&lt;/td&gt;
   &lt;td&gt;&lt;strong&gt;After&lt;/strong&gt;&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;Missing session rate&lt;/td&gt;
   &lt;td&gt;30–40%&lt;/td&gt;
   &lt;td&gt;0%&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;Missing customer IDs&lt;/td&gt;
   &lt;td&gt;5%&lt;/td&gt;
   &lt;td&gt;0.8%&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;Missing impression timestamps&lt;/td&gt;
   &lt;td&gt;91%&lt;/td&gt;
   &lt;td&gt;0.2%&lt;/td&gt;
  &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Downstream applications now receive fully enriched transactional and session context. Machine learning models use session-scoped features for campaign ranking, conversion-rate estimation, and anomaly detection. The pipeline now computes those features from a complete event stream, rather than one in which roughly a third of events were missing session context and 91 percent of impressions were missing a reliable timestamp.&lt;/p&gt;
&lt;h2 id="whats-next"&gt;What’s next&lt;/h2&gt;
&lt;p&gt;The pipeline described here is the first of several planned migrations to Amazon Managed Service for Apache Flink. The team is extending the same architecture to additional ad formats, and connecting real-time Flink aggregations directly to the ad serving layer for sub-second budget pacing. The real-time data layer built for measurement also serves as the foundation for AI-driven use cases. The team plans to explore live user interaction streams feeding personalization ranking models and grounded large language model (LLM) recommendations, which were impractical with batch-oriented infrastructure.&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Delivery Hero’s migration to Amazon Managed Service for Apache Flink shows that effectively exactly-once billing, multi-day stateful attribution, and manageable operational complexity are not competing goals. The combination that made it work: Kinesis Data Streams for ingestion, DynamoDB for low-latency enrichment, Amazon S3 for event storage and checkpointing, and Amazon EventBridge Pipes for rapid MVP delivery. Together they produced a system that is more accurate, more resilient, and less expensive than the one it replaced. For advertising platforms where billing accuracy and attribution correctness are commercial imperatives, this architecture offers a replicable path from batch approximation to real-time measurement.&lt;/p&gt;
&lt;p&gt;To get started with Apache Flink on AWS, see the &lt;a href="https://docs.aws.amazon.com/managed-flink/" target="_blank" rel="noopener"&gt;Amazon Managed Service for Apache Flink Developer Guide&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="additional-resources"&gt;Additional resources&lt;/h2&gt;
&lt;ul&gt;
 &lt;li&gt;&lt;a href="https://aws.amazon.com/managed-service-apache-flink/" target="_blank" rel="noopener"&gt;Amazon Managed Service for Apache Flink&lt;/a&gt;&lt;/li&gt;
 &lt;li&gt;&lt;a href="https://aws.amazon.com/kinesis/data-streams/" target="_blank" rel="noopener"&gt;Amazon Kinesis Data Streams&lt;/a&gt;&lt;/li&gt;
 &lt;li&gt;&lt;a href="https://aws.amazon.com/dynamodb/" target="_blank" rel="noopener"&gt;Amazon DynamoDB&lt;/a&gt;&lt;/li&gt;
 &lt;li&gt;&lt;a href="https://aws.amazon.com/eventbridge/pipes/" target="_blank" rel="noopener"&gt;Amazon EventBridge Pipes&lt;/a&gt;&lt;/li&gt;
 &lt;li&gt;&lt;a href="https://nightlies.apache.org/flink/flink-docs-stable/docs/concepts/stateful-stream-processing/" target="_blank" rel="noopener"&gt;Apache Flink Exactly-Once Processing&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p style="clear: both"&gt;&lt;/p&gt;
&lt;hr style="width: 100%"&gt;
&lt;h2&gt;About the authors&lt;/h2&gt;
&lt;footer&gt;
 &lt;div class="blog-author-box" style="padding-top: 2.0em"&gt;
  &lt;div class="blog-author-image" style="margin-right: 1.0em"&gt;
   &lt;img loading="lazy" class="alignnone size-full" src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/18/BDB-6091-2.jpg" alt="Kirill Tishenkov" width="100" height="133"&gt;
  &lt;/div&gt;
  &lt;h3 class="lb-h4"&gt;Kirill Tishenkov&lt;/h3&gt;
  &lt;p style="overflow: hidden"&gt;Kirill is a Senior Software Engineer at Delivery Hero specializing in distributed stream processing and large-scale state management.&lt;/p&gt;
 &lt;/div&gt;
 &lt;div class="blog-author-box" style="padding-top: 2.0em"&gt;
  &lt;div class="blog-author-image" style="margin-right: 1.0em"&gt;
   &lt;img loading="lazy" class="alignnone size-full" src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/18/BDB-6091-3.jpg" alt="Alexandru Pisarenco" width="100" height="133"&gt;
  &lt;/div&gt;
  &lt;h3 class="lb-h4"&gt;Alexandru Pisarenco&lt;/h3&gt;
  &lt;p style="overflow: hidden"&gt;Alexandru is a Senior Software Engineer at Delivery Hero focusing on real-time data pipelines, backfill strategies, and multi-market rollouts.&lt;/p&gt;
 &lt;/div&gt;
 &lt;div class="blog-author-box" style="padding-top: 2.0em"&gt;
  &lt;div class="blog-author-image" style="margin-right: 1.0em"&gt;
   &lt;img loading="lazy" class="alignnone size-full" src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/18/BDB-6091-4.jpg" alt="Upendra Kambhampati" width="100" height="133"&gt;
  &lt;/div&gt;
  &lt;h3 class="lb-h4"&gt;Upendra Kambhampati&lt;/h3&gt;
  &lt;p style="overflow: hidden"&gt;Upendra is an Engineering Manager at Delivery Hero leading the AdTech Data Engineering team.&lt;/p&gt;
 &lt;/div&gt;
 &lt;div class="blog-author-box" style="padding-top: 2.0em"&gt;
  &lt;div class="blog-author-image" style="margin-right: 1.0em"&gt;
   &lt;img loading="lazy" class="alignnone size-full" src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/18/BDB-6091-5.jpg" alt="Sabariesh Ganesan" width="100" height="133"&gt;
  &lt;/div&gt;
  &lt;h3 class="lb-h4"&gt;Sabariesh Ganesan&lt;/h3&gt;
  &lt;p style="overflow: hidden"&gt;Sabariesh is a Senior Engineering Manager at Delivery Hero responsible for the Vendor AdTech Data platform and Ads measurement domain.&lt;/p&gt;
 &lt;/div&gt;
 &lt;div class="blog-author-box" style="padding-top: 2.0em"&gt;
  &lt;div class="blog-author-image" style="margin-right: 1.0em"&gt;
   &lt;img loading="lazy" class="alignnone size-full" src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/18/BDB-6091-6.jpg" alt="Joseph Idicula Watasseril" width="100" height="133"&gt;
  &lt;/div&gt;
  &lt;h3 class="lb-h4"&gt;Joseph Idicula Watasseril&lt;/h3&gt;
  &lt;p style="overflow: hidden"&gt;Joseph (he/him) is a Senior Solutions Architect at AWS, based in Berlin. With over 15 years of experience in tech consulting and software development, Joseph works with Delivery Hero to apply cloud solutions to their business challenges.&lt;/p&gt;
 &lt;/div&gt;
 &lt;div class="blog-author-box" style="padding-top: 2.0em"&gt;
  &lt;div class="blog-author-image" style="margin-right: 1.0em"&gt;
   &lt;img loading="lazy" class="alignnone size-full" src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/18/BDB-6091-7.jpg" alt="Francisco Morillo" width="100" height="133"&gt;
  &lt;/div&gt;
  &lt;h3 class="lb-h4"&gt;Francisco Morillo&lt;/h3&gt;
  &lt;p style="overflow: hidden"&gt;Francisco is a Senior Streaming Solutions Architect at AWS, specializing in real-time analytics architectures. With over five years in the streaming data space, Francisco has worked as a data analyst for startups and as a big data engineer for consultancies, building streaming data pipelines. He has deep expertise in Amazon Managed Streaming for Apache Kafka (Amazon MSK) and Amazon Managed Service for Apache Flink.&lt;/p&gt;
 &lt;/div&gt;
&lt;/footer&gt;</content:encoded>
					
		
		
			</item>
		<item>
		<title>Configure domain-level VPC networking in Amazon SageMaker Unified Studio</title>
		<link>https://aws.amazon.com/blogs/big-data/configure-domain-level-vpc-networking-in-amazon-sagemaker-unified-studio/</link>
		
		<dc:creator><![CDATA[Prasad Nadig]]></dc:creator>
		<pubDate>Wed, 23 Sep 2026 18:36:56 +0000</pubDate>
				<category><![CDATA[Advanced (300)]]></category>
		<category><![CDATA[Amazon SageMaker Unified Studio]]></category>
		<category><![CDATA[Amazon VPC]]></category>
		<category><![CDATA[Technical How-to]]></category>
		<guid isPermaLink="false">000f398b575e66a7b4cb86f99cebcb9fe2ffda39</guid>

					<description>Configuring VPC networking per project across a SageMaker Unified Studio domain creates inconsistent, hard-to-audit networks. This post shows administrators how to configure domain-level VPC networking once, so every new project automatically inherits consistent, private network isolation, then update existing projects and validate connectivity.</description>
										<content:encoded>&lt;p&gt;Enterprise operations teams that run domain-level VPC networking in &lt;a href="https://aws.amazon.com/sagemaker/unified-studio/" target="_blank" rel="noopener"&gt;Amazon SageMaker Unified Studio&lt;/a&gt; often support dozens of projects spanning data engineering, analytics, and machine learning (ML) teams. Each project requires private connectivity to internal databases, &lt;a href="https://aws.amazon.com/s3/" target="_blank" rel="noopener"&gt;Amazon Simple Storage Service&lt;/a&gt; (Amazon S3) buckets, and AWS services. Without a domain-level Amazon Virtual Private Cloud (Amazon VPC) configuration, project owners coordinate with the networking team individually. This piecemeal approach leads to inconsistent subnet choices, missing VPC endpoints, connectivity failures that are hard to troubleshoot, and a network posture that is difficult to audit.&lt;/p&gt;
&lt;p&gt;With domain-level VPC networking, you configure the network once, and all new projects get the right network immediately upon creation. In this post, you learn how to:&lt;/p&gt;
&lt;ul&gt;
 &lt;li&gt;Configure SageMaker Unified Studio domain-level VPC networking.&lt;/li&gt;
 &lt;li&gt;Select subnets and security groups that provide multi-Availability Zone (multi-AZ) resilience.&lt;/li&gt;
 &lt;li&gt;Update projects that have no VPC to inherit the domain VPC, and understand when a project must be recreated instead.&lt;/li&gt;
 &lt;li&gt;Validate network connectivity from within a project.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In this post, you learn how to configure VPC networking for a SageMaker Unified Studio domain that uses &lt;a href="https://docs.aws.amazon.com/sagemaker-unified-studio/latest/adminguide/iam-based-domains.html" target="_blank" rel="noopener"&gt;AWS Identity and Access Management (IAM)-based authentication&lt;/a&gt;. You see how network components map to domain and project resources, and how to plan a configuration that balances security, connectivity, and operational simplicity.&lt;/p&gt;
&lt;h2 id="solution-overview"&gt;Solution overview&lt;/h2&gt;
&lt;p&gt;Domain-level VPC networking provides a single network configuration that applies to all new projects in the domain. Projects automatically inherit the VPC settings, including subnets, security groups, and connectivity to AWS services through VPC endpoints. Existing projects are an exception and are handled separately (see Step 3).&lt;/p&gt;
&lt;p&gt;The following diagram shows a single VPC with private subnets across two Availability Zones configured at the domain level, with data engineering, analytics, and ML projects all inheriting that configuration.&lt;/p&gt;
&lt;div style="width: 810px" class="wp-caption alignnone"&gt;
 &lt;a href="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/18/BDB-6034-1.png" target="_blank" rel="noopener"&gt;&lt;img src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/18/BDB-6034-1.png" alt="Architecture diagram showing domain-level VPC configuration in Amazon SageMaker Unified Studio with private subnets across two Availability Zones." width="800"&gt;&lt;/a&gt;
 &lt;p class="wp-caption-text"&gt;Figure 1: Domain-level VPC configuration in Amazon SageMaker Unified Studio. A single VPC with private subnets across two Availability Zones is configured at the domain level. All projects (data engineering, analytics, ML) inherit this configuration automatically&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;Key benefits of this approach:&lt;/p&gt;
&lt;ul&gt;
 &lt;li&gt;&lt;strong&gt;Configure once, apply across projects:&lt;/strong&gt; New projects inherit the domain VPC without manual intervention.&lt;/li&gt;
 &lt;li&gt;&lt;strong&gt;Consistent security posture:&lt;/strong&gt; A single network boundary covers all data, analytics, and ML workloads.&lt;/li&gt;
 &lt;li&gt;&lt;strong&gt;Simplified auditing:&lt;/strong&gt; One VPC to audit rather than one per project. Turn on VPC Flow Logs and review AWS CloudTrail events for network-level auditing.&lt;/li&gt;
 &lt;li&gt;&lt;strong&gt;Reduced operational overhead:&lt;/strong&gt; Project teams start working immediately without submitting networking requests.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The following AWS services are used in this solution:&lt;/p&gt;
&lt;ul&gt;
 &lt;li&gt;&lt;a href="https://aws.amazon.com/sagemaker/" target="_blank" rel="noopener"&gt;Amazon SageMaker Unified Studio&lt;/a&gt; – The unified development environment for data, analytics, and AI.&lt;/li&gt;
 &lt;li&gt;&lt;a href="https://aws.amazon.com/vpc/" target="_blank" rel="noopener"&gt;Amazon Virtual Private Cloud (Amazon VPC)&lt;/a&gt; – Provides network isolation with private subnets and VPC endpoints.&lt;/li&gt;
 &lt;li&gt;&lt;a href="https://aws.amazon.com/iam/" target="_blank" rel="noopener"&gt;IAM&lt;/a&gt; – Controls domain administrator permissions.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="prerequisites"&gt;Prerequisites&lt;/h2&gt;
&lt;p&gt;Before configuring domain-level VPC networking, verify you have the following:&lt;/p&gt;
&lt;ul&gt;
 &lt;li&gt;&lt;strong&gt;Domain administrator permissions&lt;/strong&gt; for Amazon SageMaker Unified Studio.&lt;/li&gt;
 &lt;li&gt;&lt;strong&gt;An existing VPC&lt;/strong&gt; with the following requirements:
  &lt;ul&gt;
   &lt;li&gt;At least two private subnets in different Availability Zones.&lt;/li&gt;
   &lt;li&gt;DNS hostnames and DNS support enabled.&lt;/li&gt;
   &lt;li&gt;At least five available IP addresses per expected Amazon SageMaker Unified Studio project. This is a baseline minimum. Workloads using AWS Glue, Amazon EMR, or Amazon Redshift Serverless consume additional elastic network interfaces (ENIs) per worker or node. We recommend /24 or larger subnets for production domains and forward-looking capacity planning based on your expected users and compute types. For detailed guidance, see &lt;a href="https://aws.amazon.com/blogs/big-data/how-to-set-up-a-network-isolated-vpc-for-amazon-sagemaker-unified-studio/" target="_blank" rel="noopener"&gt;How to set up a network-isolated VPC for Amazon SageMaker Unified Studio&lt;/a&gt;.&lt;/li&gt;
  &lt;/ul&gt;&lt;/li&gt;
 &lt;li&gt;&lt;strong&gt;VPC endpoints&lt;/strong&gt; configured for the AWS services your projects access (for example, Amazon S3, AWS Glue, Amazon SageMaker AI).&lt;/li&gt;
 &lt;li&gt;Private DNS enabled on all interface VPC endpoints (you must enable this so that service DNS names resolve to private IPs). If you use centralized VPC endpoints through AWS Resource Access Manager (AWS RAM) or AWS Transit Gateway, configure Amazon Route 53 Resolver inbound endpoints instead.&lt;/li&gt;
 &lt;li&gt;S3 gateway endpoint route table associations configured for all selected private subnets (without this, S3 access fails in subnets whose route table lacks the prefix-list route).&lt;/li&gt;
 &lt;li&gt;&lt;strong&gt;A security group&lt;/strong&gt; (optional), if not provided, SageMaker Unified Studio creates one automatically.&lt;/li&gt;
 &lt;li&gt;The &lt;code&gt;SageMakerStudioAdminIAMConsolePolicy&lt;/code&gt; managed policy (or equivalent permissions including &lt;code&gt;ec2:Describe*&lt;/code&gt;, &lt;code&gt;ec2:CreateSecurityGroup&lt;/code&gt;, and &lt;code&gt;datazone:*&lt;/code&gt; actions) attached to the domain administrator IAM role. See &lt;code&gt;SageMakerStudioAdminIAMConsolePolicy&lt;/code&gt; in the AWS Managed Policy Reference for the full permission set.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; The VPC must be in the same AWS Region as the domain.&lt;/p&gt;
&lt;p&gt;For detailed guidance on VPC networking configuration, see &lt;a href="https://docs.aws.amazon.com/sagemaker-unified-studio/latest/adminguide/configure-vpc-networking-iam-based-domains.html" target="_blank" rel="noopener"&gt;Configure VPC networking for IAM-based domains&lt;/a&gt; in the SageMaker Unified Studio Administrator Guide.&lt;/p&gt;
&lt;h3 id="vpc-endpoint-requirements"&gt;VPC endpoint requirements&lt;/h3&gt;
&lt;p&gt;Because your subnets are private (no internet gateway route), compute resources access AWS services through VPC endpoints. At a minimum, configure the following interface and gateway endpoints (add Amazon Athena, AWS Lake Formation, or Amazon Redshift endpoints if you use them in your projects):&lt;/p&gt;
&lt;table border="1px" width="100%" cellpadding="10px"&gt;
 &lt;tbody&gt;
  &lt;tr&gt;
   &lt;td&gt;&lt;strong&gt;Endpoint&lt;/strong&gt;&lt;/td&gt;
   &lt;td&gt;&lt;strong&gt;Type&lt;/strong&gt;&lt;/td&gt;
   &lt;td&gt;&lt;strong&gt;Purpose&lt;/strong&gt;&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;&lt;em&gt;com.amazonaws.region.s3&lt;/em&gt;&lt;/td&gt;
   &lt;td&gt;Gateway&lt;/td&gt;
   &lt;td&gt;S3 access for data storage&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;&lt;em&gt;com.amazonaws.region.glue&lt;/em&gt;&lt;/td&gt;
   &lt;td&gt;Interface&lt;/td&gt;
   &lt;td&gt;AWS Glue job connectivity&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;&lt;em&gt;com.amazonaws.region.sagemaker.api&lt;/em&gt;&lt;/td&gt;
   &lt;td&gt;Interface&lt;/td&gt;
   &lt;td&gt;SageMaker API calls&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;&lt;em&gt;com.amazonaws.region.sagemaker.runtime&lt;/em&gt;&lt;/td&gt;
   &lt;td&gt;Interface&lt;/td&gt;
   &lt;td&gt;Model inference&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;&lt;em&gt;com.amazonaws.region.logs&lt;/em&gt;&lt;/td&gt;
   &lt;td&gt;Interface&lt;/td&gt;
   &lt;td&gt;Amazon CloudWatch Logs&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;&lt;em&gt;com.amazonaws.region.monitoring&lt;/em&gt;&lt;/td&gt;
   &lt;td&gt;Interface&lt;/td&gt;
   &lt;td&gt;Amazon CloudWatch metrics&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;&lt;em&gt;com.amazonaws.region.sts&lt;/em&gt;&lt;/td&gt;
   &lt;td&gt;Interface&lt;/td&gt;
   &lt;td&gt;IAM role assumption&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;&lt;em&gt;com.amazonaws.region.datazone&lt;/em&gt;&lt;/td&gt;
   &lt;td&gt;Interface&lt;/td&gt;
   &lt;td&gt;Amazon SageMaker Unified Studio service connectivity&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;com.amazonaws.region.ecr.api&lt;/td&gt;
   &lt;td&gt;Interface&lt;/td&gt;
   &lt;td&gt;ECR API calls (container image metadata)&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;com.amazonaws.region.ecr.dkr&lt;/td&gt;
   &lt;td&gt;Interface&lt;/td&gt;
   &lt;td&gt;ECR image layer pulls (Docker registry)&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;com.amazonaws.region.kms&lt;/td&gt;
   &lt;td&gt;Interface&lt;/td&gt;
   &lt;td&gt;AWS Key Management Service (AWS KMS) encryption/decryption operations&lt;/td&gt;
  &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Interface endpoints incur an hourly charge per Availability Zone plus data processing fees. Gateway endpoints (such as S3) have no hourly charge. Factor endpoint count and AZ spread into your cost estimate.&lt;/p&gt;
&lt;p&gt;For a comprehensive list of all mandatory and optional VPC endpoints for a fully network-isolated setup, see &lt;a href="https://aws.amazon.com/blogs/big-data/how-to-set-up-a-network-isolated-vpc-for-amazon-sagemaker-unified-studio/" target="_blank" rel="noopener"&gt;How to set up a network-isolated VPC for Amazon SageMaker Unified Studio&lt;/a&gt;. For current pricing details, see &lt;a href="https://aws.amazon.com/privatelink/pricing/" target="_blank" rel="noopener"&gt;AWS PrivateLink pricing&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Review your account’s service quotas for interface VPC endpoints per VPC (default 50) and ENIs per Region before scaling. Request increases through Service Quotas if needed.&lt;/p&gt;
&lt;h2 id="solution-walkthrough"&gt;Solution walkthrough&lt;/h2&gt;
&lt;p&gt;The following steps walk you through configuring the domain VPC and validating it, from signing in to the console through confirming private connectivity from a project.&lt;/p&gt;
&lt;h3 id="step-1-sign-in-and-navigate-to-networking-settings"&gt;Step 1: Sign in and navigate to networking settings&lt;/h3&gt;
&lt;ol type="1"&gt;
 &lt;li&gt;Sign in to the &lt;a href="https://console.aws.amazon.com/" target="_blank" rel="noopener"&gt;AWS Management Console&lt;/a&gt; as your Amazon SageMaker Unified Studio domain administrator (the IAM role designated as the domain login role).&lt;/li&gt;
 &lt;li&gt;Open the &lt;a href="https://console.aws.amazon.com/datazone" target="_blank" rel="noopener"&gt;Amazon SageMaker console&lt;/a&gt;.&lt;/li&gt;
 &lt;li&gt;Use the Region selector in the top navigation bar to select the Region where your domain exists.&lt;/li&gt;
 &lt;li&gt;On the Amazon SageMaker Unified Studio landing page, choose &lt;strong&gt;Open&lt;/strong&gt; to launch your IAM-based domain.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The following screenshot shows the Amazon SageMaker Unified Studio landing page, where you choose &lt;strong&gt;Open&lt;/strong&gt; to launch the domain.&lt;/p&gt;
&lt;div style="width: 810px" class="wp-caption alignnone"&gt;
 &lt;a href="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/18/BDB-6034-2.png" target="_blank" rel="noopener"&gt;&lt;img src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/18/BDB-6034-2.png" alt="Amazon SageMaker Unified Studio landing page with the Open button to launch the IAM-based domain." width="800"&gt;&lt;/a&gt;
 &lt;p class="wp-caption-text"&gt;Figure 2: Amazon SageMaker Unified Studio landing page with the Open button to launch the IAM-based domain&lt;/p&gt;
&lt;/div&gt;
&lt;ol start="5" type="1"&gt;
 &lt;li&gt;From the navigation pane, choose &lt;strong&gt;Domain management&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The following screenshot shows Domain management in the navigation pane.&lt;/p&gt;
&lt;div style="width: 810px" class="wp-caption alignnone"&gt;
 &lt;a href="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/18/BDB-6034-3.png" target="_blank" rel="noopener"&gt;&lt;img src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/18/BDB-6034-3.png" alt="Navigation pane showing Domain management link in Amazon SageMaker Unified Studio." width="800"&gt;&lt;/a&gt;
 &lt;p class="wp-caption-text"&gt;Figure 3: Domain management on navigation pane&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: Access to the domain administration page is restricted to the IAM role specified as the domain login role during domain creation.&lt;/p&gt;
&lt;h3 id="step-2-add-vpc-configuration"&gt;Step 2: Add VPC configuration&lt;/h3&gt;
&lt;ol type="1"&gt;
 &lt;li&gt;In the navigation pane, choose &lt;strong&gt;Settings&lt;/strong&gt;. In the &lt;strong&gt;Networking in this account&lt;/strong&gt; section, choose &lt;strong&gt;Add VPC&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The following screenshot shows the Networking in this account section with the Add VPC button.&lt;/p&gt;
&lt;div style="width: 810px" class="wp-caption alignnone"&gt;
 &lt;a href="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/18/BDB-6034-4.png" target="_blank" rel="noopener"&gt;&lt;img src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/18/BDB-6034-4.png" alt="Domain management Settings page showing the Networking in this account section with Add VPC button." width="800"&gt;&lt;/a&gt;
 &lt;p class="wp-caption-text"&gt;Figure 4: Domain management Settings page showing the Networking in this account section to add a VPC&lt;/p&gt;
&lt;/div&gt;
&lt;ol start="2" type="1"&gt;
 &lt;li&gt;For &lt;strong&gt;VPC&lt;/strong&gt;, select the VPC with connectivity to your compute, database, and storage resources. If no VPC exists, choose &lt;strong&gt;Create VPC&lt;/strong&gt; to provision one using AWS CloudFormation.&lt;/li&gt;
 &lt;li&gt;For &lt;strong&gt;Subnets&lt;/strong&gt;, select a minimum of two private subnets in different Availability Zones.&lt;/li&gt;
 &lt;li&gt;(Optional) For &lt;strong&gt;Security group&lt;/strong&gt;, select a security group to control inbound and outbound traffic. If you don’t choose one, SageMaker Unified Studio creates one automatically.&lt;/li&gt;
 &lt;li&gt;Choose &lt;strong&gt;Save&lt;/strong&gt;.&lt;/li&gt;
 &lt;li&gt;Verify the VPC configuration status shows &lt;strong&gt;Ready&lt;/strong&gt; in the Networking in this account section.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The following screenshots show the Add VPC dialog and the resulting Ready status in the &lt;strong&gt;Networking in this account&lt;/strong&gt; section.&lt;/p&gt;
&lt;div style="width: 657px" class="wp-caption alignnone"&gt;
 &lt;a href="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/18/BDB-6034-5.png" target="_blank" rel="noopener"&gt;&lt;img src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/18/BDB-6034-5.png" alt="Add VPC dialog with fields for VPC, subnets, and security group selection." width="647"&gt;&lt;/a&gt;
 &lt;p class="wp-caption-text"&gt;Figure 5: Add VPC dialog with fields for VPC, subnets, and security group selection&lt;/p&gt;
&lt;/div&gt;
&lt;div style="width: 810px" class="wp-caption alignnone"&gt;
 &lt;a href="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/18/BDB-6034-6.png" target="_blank" rel="noopener"&gt;&lt;img src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/18/BDB-6034-6.png" alt="VPC configuration status showing Ready in the Networking in this account section." width="800"&gt;&lt;/a&gt;
 &lt;p class="wp-caption-text"&gt;Figure 6: VPC configuration status showing Ready in the Networking in this account section&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; IAM-based domains support only one VPC configuration at a time. &lt;a href="https://docs.aws.amazon.com/singlesignon/latest/userguide/what-is.html" target="_blank" rel="noopener"&gt;AWS IAM Identity Center&lt;/a&gt;-based domains can have a VPC per Region. For details, see &lt;a href="https://docs.aws.amazon.com/sagemaker-unified-studio/latest/adminguide/vpc-networking-iam-based-domains.html" target="_blank" rel="noopener"&gt;Configure VPC networking for IAM-based domains&lt;/a&gt; in the SageMaker Unified Studio Administrator Guide.&lt;/p&gt;
&lt;p&gt;New projects created in the domain now automatically use the saved VPC configuration. Existing projects are an exception. See Step 3 to update them.&lt;/p&gt;
&lt;h3 id="step-3-update-existing-projects"&gt;Step 3: Update existing projects&lt;/h3&gt;
&lt;p&gt;Existing projects don’t automatically inherit the domain VPC configuration. How you apply the new settings depends on the project’s current state:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Projects with no VPC configured &lt;/strong&gt;– Update in place to adopt the domain VPC. See the following steps.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Projects that already have a VPC &lt;/strong&gt;– These can’t be switched to a different VPC configuration. To adopt the domain VPC:&lt;/p&gt;
&lt;ol type="1"&gt;
 &lt;li&gt;Create a new project (which inherits the domain VPC automatically).&lt;/li&gt;
 &lt;li&gt;Recreate connections in the new project.&lt;/li&gt;
 &lt;li&gt;Migrate assets from the old project.&lt;/li&gt;
 &lt;li&gt;Back up any data you need, then delete the original project.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Because recreation can disrupt in-progress work and doesn’t migrate project data automatically, schedule this as a planned maintenance window.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;To update a project that currently has no VPC configured:&lt;/strong&gt;&lt;/p&gt;
&lt;ol type="1"&gt;
 &lt;li&gt;From the domain administration page, choose &lt;strong&gt;Projects&lt;/strong&gt; in the navigation pane.&lt;/li&gt;
 &lt;li&gt;Choose the project you want to update.&lt;/li&gt;
 &lt;li&gt;On the project detail page, a banner appears: “Configurations have changed. Please update this project to access the latest configuration.”&lt;/li&gt;
 &lt;li&gt;In the banner, choose &lt;strong&gt;Update&lt;/strong&gt;.&lt;/li&gt;
 &lt;li&gt;Confirm the update when prompted.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Repeat this process for each existing project that should use the domain VPC. The following screenshot shows the project detail page with the configuration update banner.&lt;/p&gt;
&lt;div style="width: 810px" class="wp-caption alignnone"&gt;
 &lt;a href="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/18/BDB-6034-7.png" target="_blank" rel="noopener"&gt;&lt;img src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/18/BDB-6034-7.png" alt="Project detail page showing the update banner for VPC configuration changes." width="800"&gt;&lt;/a&gt;
 &lt;p class="wp-caption-text"&gt;Figure 7: Project detail page showing the configuration update banner&lt;/p&gt;
&lt;/div&gt;
&lt;h3 id="step-4-validate-connectivity"&gt;Step 4: Validate connectivity&lt;/h3&gt;
&lt;p&gt;After configuring the domain VPC and updating your projects, verify connectivity. Compute resources should have private connectivity to AWS services through the VPC, without any additional project-level network configuration.&lt;/p&gt;
&lt;p&gt;Create a notebook in one of your projects as shown in the following figure and run the following code:&lt;/p&gt;
&lt;div style="width: 810px" class="wp-caption alignnone"&gt;
 &lt;a href="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/18/BDB-6034-8.png" target="_blank" rel="noopener"&gt;&lt;img src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/18/BDB-6034-8.png" alt="Creating a notebook in a SageMaker Unified Studio project to validate VPC connectivity." width="800"&gt;&lt;/a&gt;
 &lt;p class="wp-caption-text"&gt;Figure 8: Creating a notebook in a SageMaker Unified Studio project to validate VPC connectivity&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;Requirements: Python 3.8+, Boto3 1.26 or later. Run in a notebook within your SageMaker Unified Studio project.&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-python"&gt;import boto3
import socket
import ipaddress

def validate_vpc_connectivity():
    """Validate that the project has private connectivity to AWS services
    through the domain-level VPC configuration."""

    results = {}
    region = boto3.session.Session().region_name
    if not region:
        raise RuntimeError('Could not determine AWS Region. Run this notebook inside a SageMaker Unified Studio project.')

    # Test Amazon S3 access via VPC endpoint
    try:
        s3 = boto3.client('s3')
        response = s3.list_buckets()
        results['S3'] = f"[PASS] Accessible ({len(response['Buckets'])} buckets)"
    except Exception as e:
        results['S3'] = f"[FAIL] Failed: {e}"

    # Test AWS Glue access via VPC endpoint
    try:
        glue = boto3.client('glue')
        dbs = glue.get_databases()
        results['Glue'] = f"[PASS] Accessible ({len(dbs['DatabaseList'])} databases)"
    except Exception as e:
        results['Glue'] = f"[FAIL] Failed: {e}"

    # Test STS (role assumption through VPC endpoint)
    try:
        sts = boto3.client('sts')
        identity = sts.get_caller_identity()
        results['STS'] = f"[PASS] Accessible (Account: {identity['Account']})"
    except Exception as e:
        results['STS'] = f"[FAIL] Failed: {e}"

    # Verify interface endpoint resolves to private IP
    try:
        sts_endpoint = f"sts.{region}.amazonaws.com"
        addr_info = socket.getaddrinfo(sts_endpoint, 443, family=socket.AF_INET)
        ip = addr_info[0][4][0]
        is_private = ipaddress.ip_address(ip).is_private
        if is_private:
            results['DNS Resolution'] = f"[PASS] Private IP ({ip}) (traffic stays on AWS network)"
        else:
            results['DNS Resolution'] = f"[WARN] Public IP ({ip}) - check VPC endpoint config"
    except Exception as e:
        results['DNS Resolution'] = f"[FAIL] Failed: {e}"

    # Print results
    print("-" * 40)
    print("Domain VPC Connectivity Validation")
    print("-" * 40)
    for service, status in results.items():
        print(f" {service}: {status}")
    print("-" * 40)
    print(f"\n Region: {region}")

    # Check if all tests passed
    all_passed = all("[PASS]" in status for status in results.values())
    has_warn = any("[WARN]" in status for status in results.values())
    if all_passed:
        print(f"\n [PASS] All services accessible via private VPC endpoints.")
        print(f" This project inherited its network configuration")
        print(f" from the domain without per-project setup.")
    elif has_warn and all("[PASS]" in s or "[WARN]" in s for s in results.values()):
        print(f"\n [WARN] Services are reachable, but DNS resolves to public IPs.")
        print(f" Verify that Private DNS is enabled on your interface VPC endpoints.")
    else:
        print(f"\n [FAIL] Some services are not reachable.")
        print(f" Check that VPC endpoints are configured and security")
        print(f" groups allow outbound traffic on port 443.")

validate_vpc_connectivity()&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Expected output when VPC is correctly configured:&lt;/p&gt;
&lt;div style="width: 810px" class="wp-caption alignnone"&gt;
 &lt;a href="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/18/BDB-6034-9.png" target="_blank" rel="noopener"&gt;&lt;img src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/18/BDB-6034-9.png" alt="Successful validation output showing all services accessible through private VPC endpoints." width="800"&gt;&lt;/a&gt;
 &lt;p class="wp-caption-text"&gt;Figure 9: Successful validation output showing all services accessible through private VPC endpoints&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;If any service shows a failure, one common cause is security groups preventing traffic on port 443 to the VPC endpoint. Other causes include missing VPC endpoints, incorrect route table entries, or DNS resolution issues. For more information, see &lt;a href="https://docs.aws.amazon.com/sagemaker-unified-studio/latest/adminguide/configure-vpc-networking-iam-based-domains.html" target="_blank" rel="noopener"&gt;Configure VPC networking for IAM-based domains&lt;/a&gt; in the SageMaker Unified Studio Administrator Guide.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; An AccessDenied error indicates the request reached the service. Connectivity is working, but IAM permissions need adjustment (for example, the S3 test requires s3:ListAllMyBuckets, which some project roles lack). A timeout or connection error points to a networking problem (missing endpoint, route, or security group rule). The following screenshot shows the validation output when VPC endpoints are missing, where the affected services report timeout errors.&lt;/p&gt;
&lt;div style="width: 810px" class="wp-caption alignnone"&gt;
 &lt;a href="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/18/BDB-6034-10.png" target="_blank" rel="noopener"&gt;&lt;img src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/18/BDB-6034-10.png" alt="Validation output when VPC endpoints are not configured showing timeout errors." width="800"&gt;&lt;/a&gt;
 &lt;p class="wp-caption-text"&gt;Figure 10: Validation output when VPC endpoints are not configured. Timeout errors indicate missing endpoints&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;The security group applied at the domain level controls network access for all projects. To review or tighten the rules:&lt;/p&gt;
&lt;ol type="1"&gt;
 &lt;li&gt;Navigate to the &lt;a href="https://console.aws.amazon.com/vpc/" target="_blank" rel="noopener"&gt;Amazon VPC console&lt;/a&gt;.&lt;/li&gt;
 &lt;li&gt;Choose &lt;strong&gt;Security groups&lt;/strong&gt; and choose the security group shown in your domain’s Networking settings.&lt;/li&gt;
 &lt;li&gt;Review the &lt;strong&gt;Inbound rules&lt;/strong&gt; and &lt;strong&gt;Outbound rules&lt;/strong&gt; tabs.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;By default, the auto-created security group allows all outbound traffic on port 443 (HTTPS) to reach AWS services through VPC endpoints. Consider restricting outbound rules to only the specific VPC endpoint security groups for least-privilege access. Additionally, make sure your VPC endpoint security groups allow inbound TCP 443 from the domain security group or subnet CIDRs. For distributed compute services (AWS Glue, Amazon EMR), add a self-referencing inbound rule to allow worker-to-worker communication.&lt;/p&gt;
&lt;h2 id="updating-vpc-configuration"&gt;Updating VPC configuration&lt;/h2&gt;
&lt;p&gt;After the initial setup, you can modify the VPC configuration to change the VPC, subnets, or security group:&lt;/p&gt;
&lt;ol type="1"&gt;
 &lt;li&gt;From the domain administration page, choose &lt;strong&gt;Settings&lt;/strong&gt; in the navigation pane.&lt;/li&gt;
 &lt;li&gt;In the Networking in this account section, under the Actions column, choose &lt;strong&gt;Update&lt;/strong&gt;.&lt;/li&gt;
 &lt;li&gt;Update the VPC, subnets, or security group as needed.&lt;/li&gt;
 &lt;li&gt;Choose &lt;strong&gt;Update&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The following screenshot shows the Update VPC dialog, where you modify the VPC, subnets, or security group.&lt;/p&gt;
&lt;div style="width: 810px" class="wp-caption alignnone"&gt;
 &lt;a href="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/18/BDB-6034-11.png" target="_blank" rel="noopener"&gt;&lt;img src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/18/BDB-6034-11.png" alt="Settings page with the Actions menu showing Update and Remove options for VPC configuration." width="800"&gt;&lt;/a&gt;
 &lt;p class="wp-caption-text"&gt;Figure 11: Update VPC dialog showing the option to modify VPC, subnets, or security group for the domain&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Important:&lt;/strong&gt; Updating the VPC does not affect already provisioned resources. Newly created resources in projects use the updated VPC. Existing projects that already have a VPC keep their original settings and must be recreated to adopt the change. Projects with no VPC can be updated in place (see Step 3).&lt;/p&gt;
&lt;h2 id="clean-up"&gt;Clean up&lt;/h2&gt;
&lt;p&gt;To remove the VPC configuration from your domain:&lt;/p&gt;
&lt;ol type="1"&gt;
 &lt;li&gt;From the domain administration page, choose &lt;strong&gt;Settings&lt;/strong&gt; in the navigation pane.&lt;/li&gt;
 &lt;li&gt;In the Networking in this account section, choose the &lt;strong&gt;Actions&lt;/strong&gt; menu (⋮) and choose &lt;strong&gt;Remove&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The following screenshot shows the Actions menu with the Remove option.&lt;/p&gt;
&lt;div style="width: 810px" class="wp-caption alignnone"&gt;
 &lt;a href="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/18/BDB-6034-12.png" target="_blank" rel="noopener"&gt;&lt;img src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/18/BDB-6034-12.png" alt="Actions menu in the Networking in this account section showing the Remove option." width="800"&gt;&lt;/a&gt;
 &lt;p class="wp-caption-text"&gt;Figure 12: Actions menu in the Networking in this account section showing the Remove option&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;If you created a dedicated VPC for this walkthrough and no longer need it:&lt;/p&gt;
&lt;ul&gt;
 &lt;li&gt;Delete the VPC and associated resources (subnets, VPC endpoints, security groups) from the &lt;a href="https://console.aws.amazon.com/vpc/" target="_blank" rel="noopener"&gt;Amazon VPC console&lt;/a&gt;. Before deleting, remove the domain VPC configuration and make sure all project resources are terminated. Active projects create ENIs that block VPC and subnet deletion.&lt;/li&gt;
 &lt;li&gt;If you used an &lt;a href="https://console.aws.amazon.com/cloudformation/" target="_blank" rel="noopener"&gt;AWS CloudFormation&lt;/a&gt; template to create the VPC, delete the stack to remove all resources cleanly. Open the AWS CloudFormation console and delete the stack.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: Removing the domain VPC configuration does not retroactively change projects that already have VPC applied. Those projects retain their existing network configuration. New projects created after removal do not have a VPC configured.&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;In this post, we showed how to configure domain-level VPC networking in Amazon SageMaker Unified Studio. A single domain-level VPC eliminates per-project networking overhead, enforces a consistent security posture, and simplifies compliance auditing.&lt;/p&gt;
&lt;p&gt;Key takeaways:&lt;/p&gt;
&lt;ul&gt;
 &lt;li&gt;Domain-level VPC is a one-time configuration that automatically applies to all new projects.&lt;/li&gt;
 &lt;li&gt;Projects with no VPC can be updated in place. Projects that already have a VPC must be recreated to adopt a changed configuration.&lt;/li&gt;
 &lt;li&gt;Private subnets with VPC endpoints provide secure, private connectivity to AWS services without traversing the public internet.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;As next steps, consider:&lt;/p&gt;
&lt;ul&gt;
 &lt;li&gt;Reviewing your auto-created security group rules and tightening them for least-privilege access.&lt;/li&gt;
 &lt;li&gt;Adding VPC endpoints for additional AWS services as your projects’ needs evolve.&lt;/li&gt;
 &lt;li&gt;Monitoring subnet IP address utilization to plan capacity as you add more projects. Use the AvailableIpAddressCount Amazon CloudWatch metric for your subnets to track utilization and set alarms.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For more information, see &lt;a href="https://docs.aws.amazon.com/sagemaker-unified-studio/latest/adminguide/vpc-networking-iam-based-domains.html" target="_blank" rel="noopener"&gt;Configure VPC networking for IAM-based domains&lt;/a&gt; in the Amazon SageMaker Unified Studio Administrator Guide.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style="clear: both"&gt;&lt;/p&gt;
&lt;hr style="width: 100%"&gt;
&lt;h2&gt;About the authors&lt;/h2&gt;
&lt;footer&gt;
 &lt;div class="blog-author-box" style="padding-top: 2.0em"&gt;
  &lt;div class="blog-author-image" style="margin-right: 1.0em"&gt;
   &lt;img loading="lazy" class="alignnone size-full" src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/18/BDB-6034-13.jpg" alt="Prasad Nadig" width="100" height="133"&gt;
  &lt;/div&gt;
  &lt;h3 class="lb-h4"&gt;Prasad Nadig&lt;/h3&gt;
  &lt;p style="overflow: hidden"&gt;&lt;a href="http://www.linkedin.com/in/prasad-nadig" target="_blank" rel="noopener"&gt;Prasad&lt;/a&gt; is a Senior Analytics Specialist Solutions Architect at Amazon Web Services (AWS), specializing in large-scale data analytics and AI. Prasad partners with customers to design, migrate, and modernize their analytics platforms on AWS into scalable, cost-effective solutions, with deep expertise in data lakes, data warehousing, distributed processing, and performance tuning at petabyte scale.&lt;/p&gt;
 &lt;/div&gt;
 &lt;div class="blog-author-box" style="padding-top: 2.0em"&gt;
  &lt;div class="blog-author-image" style="margin-right: 1.0em"&gt;
   &lt;img loading="lazy" class="alignnone size-full" src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/18/BDB-6034-14.jpg" alt="Amit Shyam Jaisinghani" width="100" height="133"&gt;
  &lt;/div&gt;
  &lt;h3 class="lb-h4"&gt;Amit Shyam Jaisinghani&lt;/h3&gt;
  &lt;p style="overflow: hidden"&gt;&lt;a href="https://www.linkedin.com/in/amit-jaisinghani" target="_blank" rel="noopener"&gt;Amit&lt;/a&gt; is a Software Engineer on the SageMaker Studio team at Amazon Web Services, and he earned his Master’s degree in Computer Science from Rochester Institute of Technology. Since joining Amazon in 2019, he has built and enhanced several AWS services, including Amazon WorkSpaces and Amazon SageMaker Studio. Outside of work, he explores hiking trails, plays with his two cats, Missy and Minnie, and enjoys playing Age of Empire.&lt;/p&gt;
 &lt;/div&gt;
 &lt;div class="blog-author-box" style="padding-top: 2.0em"&gt;
  &lt;div class="blog-author-image" style="margin-right: 1.0em"&gt;
   &lt;img loading="lazy" class="alignnone size-full" src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/18/BDB-6034-15.jpg" alt="Arun Shanmugam" width="100" height="133"&gt;
  &lt;/div&gt;
  &lt;h3 class="lb-h4"&gt;Arun Shanmugam&lt;/h3&gt;
  &lt;p style="overflow: hidden"&gt;&lt;a href="https://www.linkedin.com/in/arun-shanmugam/" target="_blank" rel="noopener"&gt;Arun&lt;/a&gt; is a Senior Analytics Solutions Architect at AWS, with a focus on building modern data architecture. He has been successfully delivering scalable data analytics solutions for customers across diverse industries. Outside of work, Arun is an avid outdoor enthusiast who actively engages in CrossFit, road biking, and cricket.&lt;/p&gt;
 &lt;/div&gt;
&lt;/footer&gt;</content:encoded>
					
		
		
			</item>
		<item>
		<title>Announcing Spark Connect on Amazon EMR on EC2: Interactive PySpark anywhere</title>
		<link>https://aws.amazon.com/blogs/big-data/announcing-spark-connect-on-amazon-emr-on-ec2-interactive-pyspark-anywhere/</link>
		
		<dc:creator><![CDATA[Al MS]]></dc:creator>
		<pubDate>Wed, 23 Sep 2026 18:28:32 +0000</pubDate>
				<category><![CDATA[Advanced (300)]]></category>
		<category><![CDATA[Amazon EC2]]></category>
		<category><![CDATA[Amazon EMR]]></category>
		<category><![CDATA[Announcements]]></category>
		<guid isPermaLink="false">fc3b7a589c51b65c2aded693082df21cb8819e93</guid>

					<description>Amazon EMR on EC2 now supports Spark Connect, so you can develop and debug PySpark interactively from Amazon SageMaker Unified Studio Data Notebooks or your own IDE while Spark runs on your cluster. This post shows you how to get started from both a Data Notebook and a local IDE.</description>
										<content:encoded>&lt;p&gt;Today, we’re announcing support for &lt;a href="https://docs.aws.amazon.com/emr/latest/ManagementGuide/emr-spark-connect-sessions.html" target="_blank" rel="noopener"&gt;Spark Connect on Amazon EMR on EC2&lt;/a&gt; with the AWS runtime for &lt;a href="https://docs.aws.amazon.com/emr/latest/ReleaseGuide/emr-spark800-release.html" target="_blank" rel="noopener"&gt;Apache Spark&lt;/a&gt; (emr-spark-8.0, Apache Spark 4.0.2 and later). You can now develop and debug PySpark interactively from Amazon SageMaker Unified Studio Data Notebooks or your own IDE, such as &lt;a href="https://code.visualstudio.com/" target="_blank" rel="noopener"&gt;Visual Studio Code&lt;/a&gt;, &lt;a href="https://www.jetbrains.com/pycharm/" target="_blank" rel="noopener"&gt;PyCharm&lt;/a&gt;, &lt;a href="https://kiro.dev/" target="_blank" rel="noopener"&gt;Kiro&lt;/a&gt;, or Jupyter. Spark runs on a dedicated Amazon EMR on EC2 cluster while your Python runs locally, so you can set breakpoints and inspect a DataFrame against full-size data from your IDE. In &lt;a href="https://docs.aws.amazon.com/sagemaker-unified-studio/latest/userguide/what-is-sagemaker-unified-studio.html" target="_blank" rel="noopener"&gt;SageMaker Unified Studio&lt;/a&gt; &lt;a href="https://docs.aws.amazon.com/sagemaker-unified-studio/latest/userguide/notebooks.html" target="_blank" rel="noopener"&gt;Data Notebooks&lt;/a&gt;, you connect to your cluster, catalog, and AI tools. Production-scale &lt;a href="https://spark.apache.org/docs/latest/api/python/index.html" target="_blank" rel="noopener"&gt;PySpark&lt;/a&gt; and SQL run without leaving the studio. Because each session is isolated with its own permissions, your whole team can share one cluster at the same time. This post shows you how to get started with both SageMaker Unified Studio Data Notebooks and your own IDE.&lt;/p&gt;
&lt;p&gt;Previously, developing Spark for an Amazon EMR on EC2 cluster meant working in a notebook tied to that cluster, or packaging your code as a job and submitting it before you could see a result. Local code often behaved differently on the cluster because of version and dependency mismatches, and the slow deploy-and-check loop made those differences hard to find. There was no way to attach your own IDE and debugger and inspect a DataFrame mid-transformation. Spark Connect closes that gap: your code runs against the cluster’s own Spark engine while you develop locally, so the environment you debug in is the one that runs your data.&lt;/p&gt;
&lt;h2 id="how-spark-connect-works-on-amazon-emr-on-ec2"&gt;How Spark Connect works on Amazon EMR on EC2&lt;/h2&gt;
&lt;p&gt;Spark Connect uses a client-server architecture that separates your application code from the Spark engine. The client is a lightweight PySpark library that runs in your notebook or IDE, and it sends DataFrame and SQL operations over a &lt;a href="https://grpc.io/docs/guides/auth/" target="_blank" rel="noopener"&gt;gRPC/TLS&lt;/a&gt; connection to a Spark Connect Server on your cluster. The server runs those operations and returns the results to your local session. Your machine does not need Spark installed and does not need to be sized for the workload.&lt;/p&gt;
&lt;div style="width: 810px" class="wp-caption alignnone"&gt;
 &lt;a href="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/22/BDB-6248-1.png" target="_blank" rel="noopener"&gt;&lt;img src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/22/BDB-6248-1.png" alt="Spark Connect client-server architecture connecting a local PySpark client to the Spark Connect Server on an Amazon EMR cluster" width="800"&gt;&lt;/a&gt;
 &lt;p class="wp-caption-text"&gt;Figure 1: Spark Connect client-server architecture on Amazon EMR on EC2&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;When you start a session, Amazon EMR launches the Spark Connect Server as a YARN application on your cluster and hands back an endpoint and a short-lived token. There’s no server for you to stand up or manage. Because that server runs on a cluster you already operate, your session inherits the instance types, libraries, bootstrap actions, and Spark configuration you use in production. What you see while debugging is what runs when the same code is scheduled as a batch job, since both use the same cluster and its configuration.&lt;/p&gt;
&lt;h2 id="share-one-cluster-across-your-team"&gt;Share one cluster across your team&lt;/h2&gt;
&lt;p&gt;Now that you can start sessions, a single dedicated cluster can serve your whole team, because each session is a separate resource with its own execution role, tags, and lifecycle. A single cluster supports up to 1,000 concurrent sessions and 1,000 concurrent execution roles. These values are service maximums, not sizing targets. Actual concurrency depends on cluster size and per-session workload. Because interactive sessions are bursty and rarely all active at once, one cluster typically serves a team larger than its peak concurrent-session count. Enable Amazon EMR managed scaling so that capacity tracks demand. If peak concurrency approaches these maximums, or to isolate cost and data access by group, use multiple clusters—for example, one per team, business unit, or environment. Sharing one cluster gives you:&lt;/p&gt;
&lt;ul&gt;
 &lt;li&gt;&lt;strong&gt;On-demand Spark without extra clusters&lt;/strong&gt; — Developers get interactive sessions without provisioning a cluster apiece, which keeps utilization high and removes the cost of idle per-person clusters.&lt;/li&gt;
 &lt;li&gt;&lt;strong&gt;Consistent environments&lt;/strong&gt; — Everyone runs the same Spark version, libraries, and security configuration, so results stay consistent, and your platform team patches and monitors one cluster.&lt;/li&gt;
 &lt;li&gt;&lt;strong&gt;Isolation and attribution&lt;/strong&gt; — Per-session execution roles and tags keep each person’s work separate, so you can scope data access by session, track cost by user, and stop one session without disturbing anyone else.&lt;/li&gt;
 &lt;li&gt;&lt;strong&gt;Full visibility and control&lt;/strong&gt; — View active sessions in the Spark UI, review finished ones in the Spark History Server, and manage them from the Amazon EMR console, API, CLI, or SDK.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="getting-started"&gt;Getting started&lt;/h2&gt;
&lt;p&gt;Getting started with Spark Connect on Amazon EMR on EC2 takes three steps: Create an Amazon EMR cluster with Spark Connect session enabled, start a session, and connect from your IDE or SageMaker Unified Studio Data Notebooks.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: In SageMaker Unified Studio, on-demand cluster creation is available for domains that use AWS IAM Identity Center. For domains that use AWS Identity and Access Management (IAM), attach an existing cluster. If your cluster runs in a private subnet, make sure that your network configuration allows connectivity between SageMaker Unified Studio and the cluster endpoint.&lt;/p&gt;
&lt;h3 id="prerequisites"&gt;Prerequisites&lt;/h3&gt;
&lt;p&gt;You must have the following prerequisites in place.&lt;/p&gt;
&lt;ul&gt;
 &lt;li&gt;An Amazon EMR cluster running release emr-spark-8.0.0 or later with &lt;code&gt;SessionEnabled&lt;/code&gt; set to &lt;code&gt;true&lt;/code&gt;.&lt;/li&gt;
 &lt;li&gt;The Spark application is installed on the cluster.&lt;/li&gt;
 &lt;li&gt;Python 3.9 or later with &lt;code&gt;pyspark[connect]&lt;/code&gt; installed locally. The PySpark version must match the Spark version on your cluster.&lt;/li&gt;
 &lt;li&gt;For clusters in private subnets, the Amazon EMR service role must include the &lt;code&gt;AmazonEMRServicePolicyForSessions&lt;/code&gt; managed policy, which grants permissions to create Network Load Balancers and virtual private cloud (VPC) endpoint services in your account.&lt;/li&gt;
 &lt;li&gt;To use Spark Connect sessions, you need permissions to start and list sessions on the cluster (&lt;code&gt;elasticmapreduce:StartSession&lt;/code&gt;, &lt;code&gt;ListSessions&lt;/code&gt;), get session details and endpoints and terminate sessions (&lt;code&gt;elasticmapreduce:GetSession&lt;/code&gt;, &lt;code&gt;GetSessionEndpoint&lt;/code&gt;, &lt;code&gt;TerminateSession&lt;/code&gt;), and pass the execution role to the Amazon EMR service (&lt;code&gt;iam:PassRole&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="working-with-interactive-sessions"&gt;Working with interactive sessions&lt;/h3&gt;
&lt;p&gt;To create a session-enabled cluster and connect to it, follow these steps.&lt;/p&gt;
&lt;h4 id="to-start-a-spark-connect-session"&gt;To start a Spark Connect session&lt;/h4&gt;
&lt;ol type="1"&gt;
 &lt;li&gt;Create a cluster with sessions enabled, running emr-spark-8.0.0 or later. The following is a sample command that you can modify for your needs, such as the instance types and counts:
  &lt;div class="hide-language"&gt;
   &lt;pre&gt;&lt;code class="language-bash"&gt;aws emr create-cluster \
  --name "spark-connect-cluster" \
  --release-label emr-spark-8.0.0 \
  --applications Name=Spark \
  --service-role EMR_DefaultRole \
  --ec2-attributes InstanceProfile=EMR_EC2_DefaultRole,SubnetId=subnet-id \
  --instance-groups '[
    {"InstanceCount":1,"InstanceGroupType":"MASTER","InstanceType":"m8g.xlarge"},
    {"InstanceCount":2,"InstanceGroupType":"CORE","InstanceType":"m8g.xlarge"}
  ]' \
  --session-enabled \
  --tags Key=for-use-with-amazon-emr-managed-policies,Value=true&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
  &lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; The following steps use the AWS Command Line Interface (AWS CLI) directly. If you develop in SageMaker Unified Studio (Option 1), cluster attachment and session creation are handled for you, so you can skip steps 2 through 6.&lt;/p&gt;&lt;/li&gt;
 &lt;li&gt;After the cluster reaches the WAITING state, start a session and wait for it to reach IDLE:
  &lt;div class="hide-language"&gt;
   &lt;pre&gt;&lt;code class="language-bash"&gt;aws emr start-session --cluster-id j-XXXXXXXXXXXXX --name "my-session"
aws emr get-session --cluster-id j-XXXXXXXXXXXXX --session-id is-XXXXXXXXXXXXX&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
  &lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; For runtime role sessions, add the &lt;code&gt;--execution-role-arn&lt;/code&gt; parameter to the start-session command.&lt;/p&gt;&lt;/li&gt;
 &lt;li&gt;Retrieve the endpoint and token, and build your connection string from the returned Endpoint value rather than hardcoding a host:
  &lt;div class="hide-language"&gt;
   &lt;pre&gt;&lt;code class="language-bash"&gt;aws emr get-session-endpoint --cluster-id j-XXXXXXXXXXXXX --session-id is-XXXXXXXXXXXXX&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
  &lt;p&gt;The response includes the endpoint URL and an authentication token:&lt;/p&gt;
  &lt;div class="hide-language"&gt;
   &lt;pre&gt;&lt;code class="language-json"&gt;{
  "Endpoint": "https://session-id.emr-spark-connect.region.amazonaws.com",
  "AuthToken": "v2.local.xxx...",
  "AuthTokenExpirationTime": "2026-01-01T01:00:00Z"
}&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;/li&gt;
 &lt;li&gt;Install the matching PySpark client and connect. GetSessionEndpoint returns an https:// URL with no port. Build the connection string by converting it to the sc:// scheme and appending :443. Without the port, the PySpark client defaults to 15002, which isn’t reachable. Your Python code runs locally. The SQL and DataFrame operations run on the cluster:
  &lt;div class="hide-language"&gt;
   &lt;pre&gt;&lt;code class="language-bash"&gt;pip install 'pyspark[connect]==4.0.2' boto3&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
  &lt;div class="hide-language"&gt;
   &lt;pre&gt;&lt;code class="language-python"&gt;from pyspark.sql import SparkSession

session_id = "is-XXXXXXXXXXXXX"
auth_token = "&amp;lt;AuthToken from get-session-endpoint&amp;gt;"
host = "&amp;lt;Endpoint from get-session-endpoint, without https://&amp;gt;"

url = f"sc://{host}:443/;use_ssl=true;x-aws-proxy-auth={auth_token};authorization={session_id}"
spark = SparkSession.builder.remote(url).getOrCreate()
spark.sql("SELECT 'Hello from EMR on EC2' AS message").show()&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;/li&gt;
 &lt;li&gt;Run a transformation against full-size data. This groups a DataFrame, writes the result to Amazon Simple Storage Service (Amazon S3), and reads it back:
  &lt;div class="hide-language"&gt;
   &lt;pre&gt;&lt;code class="language-python"&gt;import pyspark.sql.functions as F

df = spark.range(0, 1000).withColumn(
    "category", F.when(F.col("id") % 2 == 0, "even").otherwise("odd")
)
df.groupBy("category").count().show()
df.write.mode("overwrite").parquet("s3://amzn-s3-demo-bucket/demo/")
spark.read.parquet("s3://amzn-s3-demo-bucket/demo/").filter("id &amp;lt; 50").orderBy("id").show()&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;/li&gt;
 &lt;li&gt;When you finish, terminate the session to release cluster resources. Calling spark.stop() only closes the local connection. The session keeps running until you terminate it or it reaches the idle timeout:
  &lt;div class="hide-language"&gt;
   &lt;pre&gt;&lt;code class="language-bash"&gt;aws emr terminate-session --cluster-id j-XXXXXXXXXXXXX --session-id is-XXXXXXXXXXXXX&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;/li&gt;
 &lt;li&gt;When you’re done with the walkthrough, terminate the cluster you created in step 1 so it stops incurring charges. Terminating the cluster also ends any sessions still running on it:
  &lt;div class="hide-language"&gt;
   &lt;pre&gt;&lt;code class="language-bash"&gt;aws emr terminate-clusters --cluster-ids j-XXXXXXXXXXXXX&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;You can start a Spark Connect session in two ways: from SageMaker Unified Studio or from your own IDE client.&lt;/p&gt;
&lt;h2 id="option-1-develop-in-sagemaker-unified-studio-data-notebooks"&gt;Option 1: Develop in SageMaker Unified Studio Data Notebooks&lt;/h2&gt;
&lt;p&gt;Amazon SageMaker Unified Studio brings your data, catalogs, and analytics and AI tools into one place, and Amazon EMR on EC2 is now one of the Spark runtimes a Data Notebook can use. When you choose that cluster as the notebook runtime, SageMaker Unified Studio connects to it over Spark Connect. The same runtime then drives both your PySpark and SQL cells, so a single notebook can query the &lt;a href="https://docs.aws.amazon.com/glue/latest/dg/start-data-catalog.html" target="_blank" rel="noopener"&gt;AWS Glue Data Catalog&lt;/a&gt; and transform the data without switching tools. The built-in AI assistant generates code and execution plans from natural-language prompts, and the &lt;a href="https://spark.apache.org/docs/latest/web-ui.html" target="_blank" rel="noopener"&gt;Spark UI&lt;/a&gt; shows running work alongside your other runtimes.&lt;/p&gt;
&lt;p&gt;To start a session from SageMaker Unified Studio:&lt;/p&gt;
&lt;ol type="1"&gt;
 &lt;li&gt;Open a Data Notebook in SageMaker Unified Studio.&lt;/li&gt;
 &lt;li&gt;In the Compute panel, do one of the following:
  &lt;ol type="a"&gt;
   &lt;li&gt;To create a new cluster, choose &lt;strong&gt;Create cluster&lt;/strong&gt; and configure an Amazon EMR on EC2 cluster.&lt;/li&gt;
   &lt;li&gt;To use an existing cluster, choose &lt;strong&gt;Attach cluster&lt;/strong&gt; and select a running Amazon EMR on EC2 cluster.&lt;/li&gt;
  &lt;/ol&gt;&lt;/li&gt;
 &lt;li&gt;Select the cluster as the notebook’s runtime.&lt;/li&gt;
 &lt;li&gt;Begin writing PySpark or SQL code in the notebook cells.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;For a complete example, open the &lt;a href="https://github.com/aws-samples/sample-amazon-emr-spark4-examples/blob/main/spark-connect/SMUS_EMR_ON_EC2_DEMO.ipynb" target="_blank" rel="noopener"&gt;SageMaker Unified Studio Spark Connect example notebook&lt;/a&gt; , which connects a Data Notebook to an Amazon EMR on EC2 cluster and runs PySpark and SQL cells against the AWS Glue Data Catalog.&lt;/p&gt;
&lt;p&gt;Watch a walkthrough: Develop in a SageMaker Unified Studio Data Notebook. The preceding steps cover the same workflow, so you can complete it from the notebook without the video.&lt;/p&gt;
&lt;div style="width: 640px;" class="wp-video"&gt;
 &lt;video class="wp-video-shortcode" id="video-94507-4" width="640" height="360" preload="metadata" controls="controls"&gt;&lt;source type="video/mp4" src="https://d2908q01vomqb2.cloudfront.net/artifacts/DBSBlogs/BDB-6248/smus_ec2_speed.mp4?_=4"&gt;&lt;/video&gt;
&lt;/div&gt;
&lt;figure&gt;
 &lt;figcaption aria-hidden="true"&gt;Video walkthrough of developing in a SageMaker Unified Studio Data Notebook with Spark Connect&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id="option-2-develop-in-your-own-ide"&gt;Option 2: Develop in your own IDE&lt;/h2&gt;
&lt;p&gt;Use the IDE of your choice, such as Visual Studio Code, PyCharm, Kiro, or a local Jupyter notebook. You debug Spark the way you debug any Python program: set a breakpoint, inspect a variable, and step through your code, all while the Spark work runs on the cluster. Your libraries, source control, and continuous integration and continuous delivery (CI/CD) stay on your local machine, and only your Spark operations are sent to the cluster.&lt;/p&gt;
&lt;p&gt;To see this end to end, the following example attaches an IDE to a Spark Connect session and steps through a breakpoint against cluster data.&lt;/p&gt;
&lt;p&gt;Open the &lt;a href="https://github.com/aws-samples/sample-amazon-emr-spark4-examples/blob/main/spark-connect/LOCAL_IDE_EMR_ON_EC2_DEMO.ipynb" target="_blank" rel="noopener"&gt;local IDE Spark Connect example notebook&lt;/a&gt; then use the connection steps in the preceding Getting started section to attach your client.&lt;/p&gt;
&lt;p&gt;Watch a walkthrough: Develop your own IDE with Spark Connect. The written connection steps in Getting started cover the same workflow, so you can complete it without the video.&lt;/p&gt;
&lt;div style="width: 640px;" class="wp-video"&gt;
 &lt;video class="wp-video-shortcode" id="video-94507-5" width="640" height="360" preload="metadata" controls="controls"&gt;&lt;source type="video/mp4" src="https://d2908q01vomqb2.cloudfront.net/artifacts/DBSBlogs/BDB-6248/Local_IDE_speed.mp4?_=5"&gt;&lt;/video&gt;
&lt;/div&gt;
&lt;figure&gt;
 &lt;figcaption aria-hidden="true"&gt;Video walkthrough of developing in a local IDE with Spark Connect&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id="use-cases"&gt;Use cases&lt;/h2&gt;
&lt;p&gt;Spark Connect on Amazon EMR on EC2 supports the following interactive workflows:&lt;/p&gt;
&lt;ul&gt;
 &lt;li&gt;&lt;strong&gt;Interactive extract, transform, and load (ETL) development:&lt;/strong&gt; Build and test pipelines against full-size data on the cluster, then schedule the same transformations as a Spark step on that cluster, where the Spark version, libraries, and configuration already match what you validated.&lt;/li&gt;
 &lt;li&gt;&lt;strong&gt;Exploratory data analysis and feature engineering:&lt;/strong&gt; Analyze production-scale data from your notebook or IDE instead of sampled subsets, so you catch data quality issues earlier.&lt;/li&gt;
 &lt;li&gt;&lt;strong&gt;Notebook-driven analytics in SageMaker Unified Studio:&lt;/strong&gt; Run PySpark and SQL next to your catalogs and AI tools, switching runtimes per notebook.&lt;/li&gt;
 &lt;li&gt;&lt;strong&gt;Apache Iceberg lakehouse analytics:&lt;/strong&gt; Query and manage Iceberg tables through the AWS Glue Data Catalog, with time travel, schema evolution, and partition management.&lt;/li&gt;
 &lt;li&gt;&lt;strong&gt;Compute standardization:&lt;/strong&gt; Point interactive development at the same clusters that run your production batch jobs, so development and production share one engine and configuration.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="release-information"&gt;Release information&lt;/h2&gt;
&lt;p&gt;Spark Connect on Amazon EMR on EC2 is &lt;a href="https://aws.amazon.com/about-aws/whats-new/2026/08/amazon-emr-ec2-spark-connect/" target="_blank" rel="noopener"&gt;available&lt;/a&gt; with the AWS runtime for Apache Spark (emr-spark-8.0, Apache Spark 4.0.2) and later. It’s available in all AWS Regions where Amazon EMR is available, except the AWS GovCloud (US) Regions and the China Regions. The SageMaker Unified Studio experience is available in its &lt;a href="https://docs.aws.amazon.com/sagemaker-unified-studio/latest/adminguide/supported-regions.html" target="_blank" rel="noopener"&gt;supported Regions&lt;/a&gt;. There’s no additional charge for Spark Connect. You pay for the Amazon Elastic Compute Cloud (Amazon EC2) instances in your cluster. Because these sessions run on your own clusters, they use the Amazon EMR on EC2 capabilities you already rely on, including AWS Graviton processors for price-performance and your choice of On-Demand, Reserved, AWS Savings Plans, or Spot capacity.&lt;/p&gt;
&lt;p&gt;Considerations for the release are as follows:&lt;/p&gt;
&lt;ul&gt;
 &lt;li&gt;The PySpark version that you install locally must match the Apache Spark version on your cluster.&lt;/li&gt;
 &lt;li&gt;Spark Connect supports the DataFrame and SQL APIs. RDD-based APIs aren’t supported.&lt;/li&gt;
 &lt;li&gt;Authentication tokens expire after 1 hour, and sessions end after a configurable idle timeout (60 minutes by default, up to 24 hours).&lt;/li&gt;
 &lt;li&gt;High-availability clusters with multiple primary nodes, Trusted Identity Propagation, and fine-grained access control through AWS Lake Formation aren’t supported for Spark Connect sessions in this release.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Spark Connect on Amazon EMR on EC2 brings interactive, debuggable PySpark development to the clusters you already run. Develop on a SageMaker Unified Studio Data Notebook or in your own IDE, debug against full-size data while the cluster runs the work and share a single cluster across your whole team. To get started, see the &lt;a href="https://docs.aws.amazon.com/emr/latest/ManagementGuide/emr-spark-connect-sessions.html" target="_blank" rel="noopener"&gt;Interactive sessions with Spark Connect&lt;/a&gt; guide or open a Data Notebook in &lt;a href="https://docs.aws.amazon.com/sagemaker-unified-studio/latest/userguide/what-is-sagemaker-unified-studio.html" target="_blank" rel="noopener"&gt;Amazon SageMaker Unified Studio&lt;/a&gt;. To learn more about the service, see the &lt;a href="https://aws.amazon.com/emr/" target="_blank" rel="noopener"&gt;Amazon EMR detail page&lt;/a&gt;.&lt;/p&gt;
&lt;p style="clear: both"&gt;&lt;/p&gt;
&lt;hr style="width: 100%"&gt;
&lt;h2&gt;About the authors&lt;/h2&gt;
&lt;footer&gt;
 &lt;div class="blog-author-box" style="padding-top: 2.0em"&gt;
  &lt;div class="blog-author-image" style="margin-right: 1.0em"&gt;
   &lt;img loading="lazy" class="alignnone size-full" src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/22/BDB-6248-4.jpg" alt="Al MS" width="100" height="133"&gt;
  &lt;/div&gt;
  &lt;h3 class="lb-h4"&gt;Al MS&lt;/h3&gt;
  &lt;p style="overflow: hidden"&gt;Al is a product manager for Amazon EMR at AWS.&lt;/p&gt;
 &lt;/div&gt;
 &lt;div class="blog-author-box" style="padding-top: 2.0em"&gt;
  &lt;div class="blog-author-image" style="margin-right: 1.0em"&gt;
   &lt;img loading="lazy" class="alignnone size-full" src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/22/BDB-6248-5.jpg" alt="Karthik Prabhakar" width="100" height="133"&gt;
  &lt;/div&gt;
  &lt;h3 class="lb-h4"&gt;Karthik Prabhakar&lt;/h3&gt;
  &lt;p style="overflow: hidden"&gt;Karthik is a Data Processing Engines Architect for Amazon EMR at AWS, where he specializes in distributed systems architecture and query optimization. He partners with customers to solve complex performance challenges in large-scale data processing workloads. His work centers on engine internals, cost optimization, and architectural patterns for efficient petabyte-scale analytics.&lt;/p&gt;
 &lt;/div&gt;
 &lt;div class="blog-author-box" style="padding-top: 2.0em"&gt;
  &lt;div class="blog-author-image" style="margin-right: 1.0em"&gt;
   &lt;img loading="lazy" class="alignnone size-full" src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/22/BDB-6248-6.jpg" alt="Arun Prabakaran" width="100" height="133"&gt;
  &lt;/div&gt;
  &lt;h3 class="lb-h4"&gt;Arun Prabakaran&lt;/h3&gt;
  &lt;p style="overflow: hidden"&gt;Arun is a Senior Software Engineer working at AWS. His expertise spans distributed data processing and large-scale systems. He is passionate about building reliable data platforms and enabling organizations to run analytics and AI workloads at scale.&lt;/p&gt;
 &lt;/div&gt;
 &lt;div class="blog-author-box" style="padding-top: 2.0em"&gt;
  &lt;div class="blog-author-image" style="margin-right: 1.0em"&gt;
   &lt;img loading="lazy" class="alignnone size-full" src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/22/BDB-6248-7.jpg" alt="Rekha Veeraraghavan" width="100" height="133"&gt;
  &lt;/div&gt;
  &lt;h3 class="lb-h4"&gt;Rekha Veeraraghavan&lt;/h3&gt;
  &lt;p style="overflow: hidden"&gt;Rekha is a Technical Account Manager at AWS and a Subject Matter Expert in AWS Analytics. She helps enterprise and strategic customers optimize their data analytics solutions with expert guidance and technical support. Drawing deep data engineering expertise, she enables organizations to build scalable, efficient, and cost-effective data processing pipelines on AWS.&lt;/p&gt;
 &lt;/div&gt;
&lt;/footer&gt;</content:encoded>
					
		
		<enclosure length="23786653" type="video/mp4" url="https://d2908q01vomqb2.cloudfront.net/artifacts/DBSBlogs/BDB-6248/smus_ec2_speed.mp4"/>
<enclosure length="12968952" type="video/mp4" url="https://d2908q01vomqb2.cloudfront.net/artifacts/DBSBlogs/BDB-6248/Local_IDE_speed.mp4"/>

			</item>
		<item>
		<title>Query unstructured data in Amazon SageMaker Catalog using generative AI</title>
		<link>https://aws.amazon.com/blogs/big-data/query-unstructured-data-in-amazon-sagemaker-catalog-using-generative-ai/</link>
		
		<dc:creator><![CDATA[Nishchai JM]]></dc:creator>
		<pubDate>Wed, 23 Sep 2026 18:22:00 +0000</pubDate>
				<category><![CDATA[Advanced (300)]]></category>
		<category><![CDATA[Amazon SageMaker Unified Studio]]></category>
		<category><![CDATA[Technical How-to]]></category>
		<guid isPermaLink="false">99198b9ff86a6b1ba97b5676673212e5f615a34b</guid>

					<description>In Part 2 of this series, sign in as a data consumer, subscribe to enriched unstructured data assets in Amazon SageMaker Catalog, and query them using natural language through a no-code Amazon Bedrock chat agent and Amazon Bedrock model inference.</description>
										<content:encoded>&lt;p&gt;Each day, businesses generate massive amounts of unstructured data, such as PDFs, images, email, customer feedback, and medical reports. But knowing data exists isn’t enough. You need to find it, access it, and extract answers from it fast. In &lt;a href="https://aws.amazon.com/blogs/big-data/improve-the-discoverability-of-your-unstructured-data-in-amazon-sagemaker-catalog-using-generative-ai/" target="_blank" rel="noopener"&gt;Part 1&lt;/a&gt; of this series, you saw how to set up the producer side of the pipeline: using &lt;a href="https://aws.amazon.com/textract/" target="_blank" rel="noopener"&gt;Amazon Textract&lt;/a&gt; and Anthropic Claude on &lt;a href="https://aws.amazon.com/bedrock/" target="_blank" rel="noopener"&gt;Amazon Bedrock&lt;/a&gt; to extract and enrich metadata, and then publish those enriched assets to Amazon SageMaker Catalog so your organization can discover them.&lt;/p&gt;
&lt;p&gt;In this post, you take the next step: the consumer side. You sign in as a data consumer, search for and subscribe to the enriched unstructured data assets, and then query them using two approaches. The first is a no-code chat agent for natural language queries. The second is Amazon Bedrock model inference for programmatic access. By the end of this post, you will know how to unlock the business knowledge inside your unstructured data and make it available to analysts and application engineers alike.&lt;/p&gt;
&lt;h2 id="solution-overview"&gt;Solution overview&lt;/h2&gt;
&lt;p&gt;This post continues the two-part series architecture, where Amazon SageMaker Catalog acts as the central hub connecting data producers and consumers through a publish-subscribe model.&lt;/p&gt;
&lt;p&gt;The consumer workflow picks up after the producer has enriched and published the unstructured data assets. As a consumer, you will:&lt;/p&gt;
&lt;ul&gt;
 &lt;li&gt;Sign in to your &lt;a href="https://aws.amazon.com/sagemaker/unified-studio/" target="_blank" rel="noopener"&gt;SageMaker Unified Studio&lt;/a&gt; consumer project and search the catalog using keywords from the enriched metadata README.&lt;/li&gt;
 &lt;li&gt;Subscribe to the published Amazon Simple Storage Service (Amazon S3) asset and get the subscription approved by the producer.&lt;/li&gt;
 &lt;li&gt;Interact with the subscribed data through two options:
  &lt;ul&gt;
   &lt;li&gt;&lt;strong&gt;Option 1&lt;/strong&gt; – A no-code chat agent for natural language queries (NLQs), ideal for data analysts and business users.&lt;/li&gt;
   &lt;li&gt;&lt;strong&gt;Option 2&lt;/strong&gt; – Amazon Bedrock model inference for programmatic NLQ integration, suited for application engineers building data-driven applications.&lt;/li&gt;
  &lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The following diagram illustrates the consumer workflow in this solution. The consumer (1) signs in to SageMaker Unified Studio, (2) searches the Amazon SageMaker Catalog for enriched unstructured data assets using keywords from the AI-generated metadata, (3) subscribes to the S3 data asset and receives approval from the producer, and then (4) queries the data using either the Amazon Bedrock chat agent app (Option 1) or Amazon Bedrock model inference through a Jupyter notebook (Option 2).&lt;/p&gt;
&lt;p&gt;With both a no-code and a programmatic path, consumers across different roles, from analysts to engineers, can query data in the way that fits their workflow, while the SageMaker Catalog approval workflow maintains governed access throughout.&lt;/p&gt;
&lt;div style="width: 810px" class="wp-caption alignnone"&gt;
 &lt;a href="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/15/BDB-5969-1.png" target="_blank" rel="noopener"&gt;&lt;img src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/15/BDB-5969-1.png" alt="Consumer workflow architecture: sign in to SageMaker Unified Studio, search the SageMaker Catalog, subscribe to the S3 asset with producer approval, then query with the Amazon Bedrock chat agent or model inference" width="800"&gt;&lt;/a&gt;
 &lt;p class="wp-caption-text"&gt;Figure 1: Consumer workflow for the publish-subscribe solution&lt;/p&gt;
&lt;/div&gt;
&lt;h2 id="prerequisites"&gt;Prerequisites&lt;/h2&gt;
&lt;p&gt;Before you begin, make sure you have completed all steps in &lt;a href="https://aws.amazon.com/blogs/big-data/improve-the-discoverability-of-your-unstructured-data-in-amazon-sagemaker-catalog-using-generative-ai/" target="_blank" rel="noopener"&gt;Part 1 of this series&lt;/a&gt;, including:&lt;/p&gt;
&lt;ul&gt;
 &lt;li&gt;An &lt;a href="https://aws.amazon.com/resources/create-account/" target="_blank" rel="noopener"&gt;AWS account&lt;/a&gt;.&lt;/li&gt;
 &lt;li&gt;An &lt;a href="https://aws.amazon.com/pm/serv-s3/" target="_blank" rel="noopener"&gt;Amazon Simple Storage Service (Amazon S3)&lt;/a&gt; bucket with the enriched documents and &lt;code&gt;results.txt&lt;/code&gt; file uploaded.&lt;/li&gt;
 &lt;li&gt;An &lt;a href="https://docs.aws.amazon.com/sagemaker-unified-studio/latest/adminguide/create-domain-sagemaker-unified-studio-quick.html" target="_blank" rel="noopener"&gt;Amazon SageMaker Unified Studio&lt;/a&gt; domain with two projects:
  &lt;ul&gt;
   &lt;li&gt;&lt;strong&gt;Producer project&lt;/strong&gt; (&lt;code&gt;unstructured-producer-project&lt;/code&gt;) – with the asset published to SageMaker Catalog.&lt;/li&gt;
   &lt;li&gt;&lt;strong&gt;Consumer project&lt;/strong&gt; (&lt;code&gt;unstructured-consumer-project&lt;/code&gt;) – where you will subscribe and query the data.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; You can use custom blueprints to tailor your Amazon SageMaker Unified Studio project environments to your specific needs. For more information, see &lt;a href="https://aws.amazon.com/blogs/big-data/tailor-amazon-sagemaker-unified-studio-project-environments-to-your-needs-using-custom-blueprints/" target="_blank" rel="noopener"&gt;Tailor Amazon SageMaker Unified Studio project environments to your needs using custom blueprints&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
 &lt;li&gt;The AWS Identity and Access Management (IAM) permissions added to the producer project role in Part 1.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="consume-published-data-from-the-consumer-project"&gt;Consume published data from the consumer project&lt;/h2&gt;
&lt;p&gt;In this section, you sign in as a consumer user in the SageMaker Unified Studio consumer project. You then subscribe to the S3 bucket by searching for a keyword that is part of the README published in Part 1.&lt;/p&gt;
&lt;ol type="1"&gt;
 &lt;li&gt;Sign in to the consumer project and search for the keyword &lt;code&gt;emergency&lt;/code&gt;, which was added to the README file during publishing. The search returns the enriched asset that the producer published in Part 1.
  &lt;p&gt;&lt;/p&gt;
  &lt;div style="width: 810px" class="wp-caption alignnone"&gt;
   &lt;a href="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/15/BDB-5969-2.png" target="_blank" rel="noopener"&gt;&lt;img src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/15/BDB-5969-2.png" alt="SageMaker Unified Studio catalog search for the emergency keyword, returning the enriched asset published in Part 1" width="800"&gt;&lt;/a&gt;
   &lt;p class="wp-caption-text"&gt;Figure 2: Catalog search results for the emergency keyword&lt;/p&gt;
  &lt;/div&gt;&lt;/li&gt;
 &lt;li&gt;Choose the asset from the results to view its details, including the AI-generated business metadata, glossary terms, and README content. Then choose &lt;strong&gt;Subscribe&lt;/strong&gt;.
  &lt;p&gt;&lt;/p&gt;
  &lt;div style="width: 810px" class="wp-caption alignnone"&gt;
   &lt;a href="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/15/BDB-5969-3.png" target="_blank" rel="noopener"&gt;&lt;img src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/15/BDB-5969-3.png" alt="Asset details page showing AI-generated business metadata, glossary terms, and README content, with the Subscribe button" width="800"&gt;&lt;/a&gt;
   &lt;p class="wp-caption-text"&gt;Figure 3: Asset details with AI-generated metadata and the Subscribe option&lt;/p&gt;
  &lt;/div&gt;&lt;/li&gt;
 &lt;li&gt;Enter &lt;code&gt;analysis&lt;/code&gt; as the &lt;strong&gt;Reason for request&lt;/strong&gt; in the &lt;strong&gt;Comment&lt;/strong&gt; section, then choose &lt;strong&gt;Request&lt;/strong&gt;.
  &lt;p&gt;&lt;/p&gt;
  &lt;div style="width: 810px" class="wp-caption alignnone"&gt;
   &lt;a href="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/15/BDB-5969-4.png" target="_blank" rel="noopener"&gt;&lt;img src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/15/BDB-5969-4.png" alt="Subscription request dialog with analysis entered as the reason for request in the Comment box" width="800"&gt;&lt;/a&gt;
   &lt;p class="wp-caption-text"&gt;Figure 4: Subscription request with the reason for request entered&lt;/p&gt;
  &lt;/div&gt;&lt;/li&gt;
 &lt;li&gt;Sign back in to the producer project (&lt;code&gt;unstructured-producer-project&lt;/code&gt;) to approve the subscription request.&lt;/li&gt;
 &lt;li&gt;After approval, return to the consumer project and confirm that the subscribed asset now appears under &lt;strong&gt;Manage&lt;/strong&gt;, &lt;strong&gt;Assets&lt;/strong&gt;, &lt;strong&gt;Subscribed&lt;/strong&gt; assets.
  &lt;p&gt;&lt;/p&gt;
  &lt;div style="width: 810px" class="wp-caption alignnone"&gt;
   &lt;a href="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/15/BDB-5969-5.png" target="_blank" rel="noopener"&gt;&lt;img src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/15/BDB-5969-5.png" alt="Consumer project Subscribed assets list confirming the approved subscription" width="800"&gt;&lt;/a&gt;
   &lt;p class="wp-caption-text"&gt;Figure 5: Approved subscription under the Subscribed assets tab&lt;/p&gt;
  &lt;/div&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;With the subscription approved, you can now access the enriched unstructured data through two approaches.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Option 1:&lt;/strong&gt; As a data or business analyst, you can use the &lt;a href="https://docs.aws.amazon.com/sagemaker-unified-studio/latest/userguide/create-chat-app.html" target="_blank" rel="noopener"&gt;Amazon Bedrock chat agent app&lt;/a&gt; for natural language queries.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Option 2:&lt;/strong&gt; As an application engineer, you can use Amazon Bedrock model inference for programmatic natural language queries.&lt;/p&gt;
&lt;p&gt;Let’s explore both options.&lt;/p&gt;
&lt;h2 id="option-1-amazon-bedrock-chat-agent-app"&gt;Option 1: Amazon Bedrock chat agent app&lt;/h2&gt;
&lt;p&gt;The Amazon Bedrock chat agent app gives you a no-code, conversational interface to query your enriched unstructured data using natural language. As a data analyst or business user, you can ask questions in plain English. You get answers grounded in the documents your organization has ingested, without writing any code. For production workloads, especially in sensitive domains such as healthcare, you can apply &lt;a href="https://aws.amazon.com/bedrock/guardrails/" target="_blank" rel="noopener"&gt;Amazon Bedrock Guardrails&lt;/a&gt; to add content filtering and grounding validation to your model responses.&lt;/p&gt;
&lt;p&gt;Data scientists and application engineers can also extend these capabilities by integrating the chat agent app APIs into custom applications, so users can interact with unstructured Amazon S3 data programmatically.&lt;/p&gt;
&lt;p&gt;To set up the Amazon Bedrock chat agent app on your subscribed dataset, complete the following steps.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Prerequisite:&lt;/strong&gt; Add the S3 data location.&lt;/p&gt;
&lt;p&gt;Before creating the chat agent app, you need to add the S3 location of your subscribed data as a registered location in your project.&lt;/p&gt;
&lt;ol type="1"&gt;
 &lt;li&gt;Choose the &lt;strong&gt;Data&lt;/strong&gt; tab in &lt;strong&gt;Overview&lt;/strong&gt;.&lt;/li&gt;
 &lt;li&gt;Choose the S3 bucket, and then choose &lt;strong&gt;Add&lt;/strong&gt; to add the S3 location.
  &lt;p&gt;&lt;/p&gt;
  &lt;div style="width: 708px" class="wp-caption alignnone"&gt;
   &lt;a href="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/15/BDB-5969-6.png" target="_blank" rel="noopener"&gt;&lt;img src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/15/BDB-5969-6.png" alt="Data tab in the project Overview with the S3 bucket selected and the Add button to register the S3 location" width="698"&gt;&lt;/a&gt;
   &lt;p class="wp-caption-text"&gt;Figure 6: Adding the S3 location from the Data tab&lt;/p&gt;
  &lt;/div&gt;&lt;/li&gt;
 &lt;li&gt;On the S3 location page, provide the following details:
  &lt;ul&gt;
   &lt;li&gt;Add a name: &lt;code&gt;producerprojectdata&lt;/code&gt;.&lt;/li&gt;
   &lt;li&gt;Add the producer’s S3 path as a new S3 location: &lt;code&gt;s3://amzn-sagemaker-bucket-&amp;lt;domain-id&amp;gt;-&amp;lt;project-id&amp;gt;/medical/&lt;/code&gt;.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; You can get the S3 location details from the technical name of your subscribed asset.&lt;/p&gt;
  &lt;ul&gt;
   &lt;li&gt;Choose the AWS Region, and then choose &lt;strong&gt;Add data&lt;/strong&gt; to add this as a new location.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Make sure the AWS Region you select supports the Amazon Bedrock foundation models used later in this post. For a list of available models by Region, see &lt;a href="https://docs.aws.amazon.com/bedrock/latest/userguide/models-regions.html" target="_blank" rel="noopener"&gt;Supported Regions and models for Amazon Bedrock&lt;/a&gt;.&lt;/p&gt;
  &lt;div style="width: 810px" class="wp-caption alignnone"&gt;
   &lt;a href="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/15/BDB-5969-7.png" target="_blank" rel="noopener"&gt;&lt;img src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/15/BDB-5969-7.png" alt="S3 location page with the location name, producer S3 path, and AWS Region entered before choosing Add data" width="800"&gt;&lt;/a&gt;
   &lt;p class="wp-caption-text"&gt;Figure 7: S3 location details and AWS Region selection&lt;/p&gt;
  &lt;/div&gt;
  &lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Make sure to select only the PDF files within the S3 path for the data source.&lt;/p&gt;
  &lt;p&gt;&lt;/p&gt;
  &lt;div style="width: 810px" class="wp-caption alignnone"&gt;
   &lt;a href="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/15/BDB-5969-8.png" target="_blank" rel="noopener"&gt;&lt;img src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/15/BDB-5969-8.png" alt="Data source selection showing only the PDF files within the S3 path selected" width="800"&gt;&lt;/a&gt;
   &lt;p class="wp-caption-text"&gt;Figure 8: Selecting the PDF files as the data source&lt;/p&gt;
  &lt;/div&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;After the location is added, it appears as a selectable S3 location when creating a knowledge base in AI Apps.&lt;/p&gt;
&lt;p&gt;Complete the following steps to configure the chat agent app:&lt;/p&gt;
&lt;ol type="1"&gt;
 &lt;li&gt;In the left navigation pane, under &lt;strong&gt;Generative AI&lt;/strong&gt;, choose &lt;strong&gt;AI Apps&lt;/strong&gt;.&lt;/li&gt;
 &lt;li&gt;In the &lt;strong&gt;Build&lt;/strong&gt; section of the page, choose &lt;strong&gt;Chat agent&lt;/strong&gt;.
  &lt;p&gt;&lt;/p&gt;
  &lt;div style="width: 810px" class="wp-caption alignnone"&gt;
   &lt;a href="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/15/BDB-5969-9.png" target="_blank" rel="noopener"&gt;&lt;img src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/15/BDB-5969-9.png" alt="AI Apps Build section with Chat agent selected in the left navigation under Generative AI" width="800"&gt;&lt;/a&gt;
   &lt;p class="wp-caption-text"&gt;Figure 9: Choosing Chat agent in the AI Apps Build section&lt;/p&gt;
  &lt;/div&gt;&lt;/li&gt;
 &lt;li&gt;Expand the &lt;strong&gt;Data&lt;/strong&gt; tab to create a knowledge base with your S3 bucket. On the Create a new knowledge base page, enter the following:
  &lt;ul&gt;
   &lt;li&gt;Add a name: &lt;code&gt;MedicalKB&lt;/code&gt;.&lt;/li&gt;
   &lt;li&gt;Add a description: &lt;code&gt;Knowledge base built from subscribed medical S3 data assets. Contains medical documents used to provide grounded, context-aware responses to medical domain queries.&lt;/code&gt;&lt;/li&gt;
   &lt;li&gt;Choose the data source. You will see the S3 bucket that you added in the previous step.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p&gt;&lt;/p&gt;
  &lt;div style="width: 810px" class="wp-caption alignnone"&gt;
   &lt;a href="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/15/BDB-5969-10.png" target="_blank" rel="noopener"&gt;&lt;img src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/15/BDB-5969-10.png" alt="Create a new knowledge base page with the MedicalKB name, description, and the added S3 bucket as the data source" width="800"&gt;&lt;/a&gt;
   &lt;p class="wp-caption-text"&gt;Figure 10: Creating the MedicalKB knowledge base from the S3 data source&lt;/p&gt;
  &lt;/div&gt;&lt;/li&gt;
 &lt;li&gt;Choose your embedding model. You can leave the default settings and choose &lt;strong&gt;Create&lt;/strong&gt;. It might take 10–15 minutes to create the knowledge base, depending on file sizes.&lt;/li&gt;
 &lt;li&gt;After the knowledge base is created, on the Chat agent page:
  &lt;ul&gt;
   &lt;li&gt;Choose your preferred model from the &lt;strong&gt;Model&lt;/strong&gt; menu (you can switch between different large language models as needed).&lt;/li&gt;
   &lt;li&gt;Under &lt;strong&gt;Data&lt;/strong&gt;, choose your published S3 bucket as the knowledge base.&lt;/li&gt;
   &lt;li&gt;Begin interacting with the agent by entering questions in the &lt;strong&gt;Enter prompt&lt;/strong&gt; field.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p&gt;&lt;/p&gt;
  &lt;div style="width: 810px" class="wp-caption alignnone"&gt;
   &lt;a href="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/15/BDB-5969-11.png" target="_blank" rel="noopener"&gt;&lt;img src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/15/BDB-5969-11.png" alt="Chat agent page with a model selected and the MedicalKB knowledge base chosen, ready to enter a prompt" width="800"&gt;&lt;/a&gt;
   &lt;p class="wp-caption-text"&gt;Figure 11: Chat agent page with the model and knowledge base selected&lt;/p&gt;
  &lt;/div&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;For example, entering “Which age groups had the highest rates of emergency department visits for tooth disorders?” returns an answer grounded in the enriched dental dataset published in Part 1.&lt;/p&gt;
&lt;p&gt;The chat agent uses the enriched README metadata along with the underlying documents to surface contextually relevant answers. Analysts can explore unstructured content without needing to know where the data lives or how it’s structured.&lt;/p&gt;
&lt;h2 id="option-2-natural-language-queries-using-amazon-bedrock-model-inference"&gt;Option 2: Natural language queries using Amazon Bedrock model inference&lt;/h2&gt;
&lt;p&gt;This option demonstrates how to use Amazon Bedrock model inference to query subscribed data using natural language. You can integrate this capability with external chat applications so users can run natural language queries through Amazon Bedrock.&lt;/p&gt;
&lt;ol type="1"&gt;
 &lt;li&gt;In your consumer project, choose &lt;strong&gt;Manage&lt;/strong&gt;, &lt;strong&gt;Assets&lt;/strong&gt; from the bottom of the left navigation pane. On the &lt;strong&gt;Subscribed&lt;/strong&gt; tab, choose your subscribed S3 asset. Under &lt;strong&gt;Actions&lt;/strong&gt;, choose &lt;strong&gt;Open JupyterLab notebook&lt;/strong&gt;.
  &lt;p&gt;&lt;/p&gt;
  &lt;div style="width: 810px" class="wp-caption alignnone"&gt;
   &lt;a href="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/15/BDB-5969-12.png" target="_blank" rel="noopener"&gt;&lt;img src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/15/BDB-5969-12.png" alt="Subscribed S3 asset Actions menu with Open JupyterLab notebook selected in the consumer project" width="800"&gt;&lt;/a&gt;
   &lt;p class="wp-caption-text"&gt;Figure 12: Opening the JupyterLab notebook from the subscribed asset&lt;/p&gt;
  &lt;/div&gt;&lt;/li&gt;
 &lt;li&gt;This opens the JupyterLab notebook environment. Upload the &lt;code&gt;s3_document_consumer_v2.ipynb&lt;/code&gt; notebook and run all the cells. You can download the notebook from &lt;a href="https://aws-blogs-artifacts-public.s3.us-east-1.amazonaws.com/bdb-4720/script/s3_document_consumer_v2.ipynb" target="_blank" rel="noopener"&gt;s3_document_consumer_v2.ipynb&lt;/a&gt;.&lt;strong&gt;Note:&lt;/strong&gt; The project role requires permissions for Amazon S3, Amazon Textract, and Amazon Bedrock. If you followed Part 1, you might already have these policies attached. For details on the required policies and guidance, see the &lt;a href="https://aws.amazon.com/blogs/big-data/improve-the-discoverability-of-your-unstructured-data-in-amazon-sagemaker-catalog-using-generative-ai/" target="_blank" rel="noopener"&gt;prerequisites in Part 1&lt;/a&gt;.&lt;/li&gt;
 &lt;li&gt;Review the notebook cells.
  &lt;div style="width: 810px" class="wp-caption alignnone"&gt;
   &lt;a href="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/15/BDB-5969-13.png" target="_blank" rel="noopener"&gt;&lt;img src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/15/BDB-5969-13.png" alt="JupyterLab notebook cells with the final cell showing a sample question answered by Amazon Bedrock" width="800"&gt;&lt;/a&gt;
   &lt;p class="wp-caption-text"&gt;Figure 13: Sample question answered by Amazon Bedrock in the notebook&lt;/p&gt;
  &lt;/div&gt;
  &lt;p&gt;In the final cell, you find a sample question that Amazon Bedrock answers: “Which primary payer types (Medicare, Medicaid, private insurance, and so on) account for the highest proportion of dental-related emergency department visits?”&lt;/p&gt;
  &lt;p&gt;Amazon Bedrock processes the question against the enriched content in the S3 bucket and returns a grounded answer. You can replace this sample question with any query relevant to your documents.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The Amazon Bedrock model inference approach gives you programmatic control, making it possible to embed natural language query capabilities directly into your existing data applications and business intelligence tools.&lt;/p&gt;
&lt;h2 id="clean-up"&gt;Clean up&lt;/h2&gt;
&lt;p&gt;To avoid ongoing charges, make sure to delete the resources used in this solution immediately after completing the walkthrough. The primary cost drivers are SageMaker Unified Studio notebook instances, Amazon Bedrock model inference calls, and Amazon S3 storage.&lt;/p&gt;
&lt;ol type="1"&gt;
 &lt;li&gt;Stop SageMaker Unified Studio resources:
  &lt;ul&gt;
   &lt;li&gt;Close running notebooks.&lt;/li&gt;
   &lt;li&gt;Stop running notebook instances.&lt;/li&gt;
   &lt;li&gt;Shut down unused kernels.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Running notebook instances continue to incur charges even when not in use.&lt;/p&gt;&lt;/li&gt;
 &lt;li&gt;Clean Amazon S3 storage:
  &lt;ul&gt;
   &lt;li&gt;Delete temporary files created during processing.&lt;/li&gt;
   &lt;li&gt;Remove uploaded test documents that are no longer needed.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Although Amazon S3 costs are minimal, large volumes of data can accumulate significant charges, so it’s best to remove unneeded data.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;In this post, you saw how to consume and query the enriched unstructured data assets published in &lt;a href="https://aws.amazon.com/blogs/big-data/improve-the-discoverability-of-your-unstructured-data-in-amazon-sagemaker-catalog-using-generative-ai/" target="_blank" rel="noopener"&gt;Part 1&lt;/a&gt; of this series. By subscribing to assets through the Amazon SageMaker Catalog publish-subscribe model, you can discover, access, and interact with your organization’s unstructured data, whether through the no-code chat agent or &lt;a href="https://docs.aws.amazon.com/bedrock/latest/userguide/inference.html" target="_blank" rel="noopener"&gt;Amazon Bedrock model inference&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Together, both parts of this series show you how to build a comprehensive pipeline that transforms raw unstructured documents into governed, queryable knowledge assets. The combination of Amazon Textract for extraction, Amazon Bedrock for intelligent summarization and NLQ, and Amazon SageMaker Catalog for governance and discoverability means your teams can focus on extracting business insights rather than managing infrastructure.&lt;/p&gt;
&lt;p&gt;To continue your Amazon SageMaker journey, see the following resources:&lt;/p&gt;
&lt;ul&gt;
 &lt;li&gt;Explore the &lt;a href="https://docs.aws.amazon.com/sagemaker-unified-studio/latest/userguide/what-is-sagemaker-unified-studio.html" target="_blank" rel="noopener"&gt;Amazon SageMaker Developer Guide&lt;/a&gt;.&lt;/li&gt;
 &lt;li&gt;Find complete setup guides in the &lt;a href="https://docs.aws.amazon.com/sagemaker/latest/dg/unified-studio.html" target="_blank" rel="noopener"&gt;Amazon SageMaker Unified Studio documentation&lt;/a&gt;.&lt;/li&gt;
 &lt;li&gt;Get started with these new integrations through the &lt;a href="https://console.aws.amazon.com/sagemaker/unified-studio" target="_blank" rel="noopener"&gt;Amazon SageMaker Unified Studio console&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style="clear: both"&gt;&lt;/p&gt;
&lt;hr style="width: 100%"&gt;
&lt;h2&gt;About the authors&lt;/h2&gt;
&lt;footer&gt;
 &lt;div class="blog-author-box" style="padding-top: 2.0em"&gt;
  &lt;div class="blog-author-image" style="margin-right: 1.0em"&gt;
   &lt;img loading="lazy" class="alignnone size-full" src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/15/BDB-5969-14.jpg" alt="Nishchai JM" width="100" height="133"&gt;
  &lt;/div&gt;
  &lt;h3 class="lb-h4"&gt;Nishchai JM&lt;/h3&gt;
  &lt;p style="overflow: hidden"&gt;&lt;a href="https://www.linkedin.com/in/nishchai-jm/" target="_blank" rel="noopener"&gt;Nishchai&lt;/a&gt; is an Analytics and generative AI Specialist Solutions Architect at Amazon Web Services. He specializes in building larger scale distributed applications and helps customers modernize their workloads on AWS. He thinks Data is new oil and spends most of his time deriving insights from data.&lt;/p&gt;
 &lt;/div&gt;
 &lt;div class="blog-author-box" style="padding-top: 2.0em"&gt;
  &lt;div class="blog-author-image" style="margin-right: 1.0em"&gt;
   &lt;img loading="lazy" class="alignnone size-full" src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/15/BDB-5969-15.jpg" alt="KiKi Nwangwu" width="100" height="133"&gt;
  &lt;/div&gt;
  &lt;h3 class="lb-h4"&gt;KiKi Nwangwu&lt;/h3&gt;
  &lt;p style="overflow: hidden"&gt;&lt;a href="https://www.linkedin.com/in/nkeirunwangwuokafor/" target="_blank" rel="noopener"&gt;KiKi&lt;/a&gt; is an Analytics and generative AI Specialist Solutions Architect at AWS. She specializes in helping customers architect, build, and modernize scalable data analytics and generative AI solutions. She enjoys traveling and exploring new cultures.&lt;/p&gt;
 &lt;/div&gt;
 &lt;div class="blog-author-box" style="padding-top: 2.0em"&gt;
  &lt;div class="blog-author-image" style="margin-right: 1.0em"&gt;
   &lt;img loading="lazy" class="alignnone size-full" src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/15/BDB-5969-16.jpg" alt="Narendra Gupta" width="100" height="133"&gt;
  &lt;/div&gt;
  &lt;h3 class="lb-h4"&gt;Narendra Gupta&lt;/h3&gt;
  &lt;p style="overflow: hidden"&gt;&lt;a href="https://www.linkedin.com/in/narendra-gupta-5862338/" target="_blank" rel="noopener"&gt;Narendra&lt;/a&gt; is a Sr.&amp;nbsp;Specialist Solutions Architect for Data &amp;amp; AI (Analytics) at AWS. He works with customers to design data-driven solutions and has deep expertise in data governance and cataloging.&lt;/p&gt;
 &lt;/div&gt;
 &lt;div class="blog-author-box" style="padding-top: 2.0em"&gt;
  &lt;div class="blog-author-image" style="margin-right: 1.0em"&gt;
   &lt;img loading="lazy" class="alignnone size-full" src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/15/BDB-5969-17.jpg" alt="Aditya Edara" width="100" height="133"&gt;
  &lt;/div&gt;
  &lt;h3 class="lb-h4"&gt;Aditya Edara&lt;/h3&gt;
  &lt;p style="overflow: hidden"&gt;&lt;a href="https://www.linkedin.com/in/aditya-e-a0026a104/" target="_blank" rel="noopener"&gt;Aditya&lt;/a&gt; is a Support Engineer at AWS. He serves as a Subject Matter Expert in AWS Analytics services, specializing in Amazon EMR and AWS Glue. Aditya provides expert guidance and technical support to enterprise and strategic customers, helping them optimize data analytics solutions.&lt;/p&gt;
 &lt;/div&gt;
&lt;/footer&gt;</content:encoded>
					
		
		
			</item>
		<item>
		<title>Best practices for scaling large consumer groups on Amazon MSK</title>
		<link>https://aws.amazon.com/blogs/big-data/best-practices-for-scaling-large-consumer-groups-on-amazon-msk/</link>
		
		<dc:creator><![CDATA[Pallavi Jha]]></dc:creator>
		<pubDate>Tue, 22 Sep 2026 15:16:06 +0000</pubDate>
				<category><![CDATA[Amazon Managed Streaming for Apache Kafka (Amazon MSK)]]></category>
		<category><![CDATA[Best Practices]]></category>
		<category><![CDATA[Expert (400)]]></category>
		<guid isPermaLink="false">91fbbf9981b2c753273c0c5f0c49f4784dfe9d87</guid>

					<description>As consumer groups on Amazon MSK scale to thousands of members, the metadata record Kafka writes during rebalances can exceed the 1 MB limit and stall the group. Learn how to estimate metadata size, raise the topic-level limit safely, plan capacity, and apply complementary strategies for scaling large consumer groups.</description>
										<content:encoded>&lt;p&gt;When scaling large consumer groups on Amazon Managed Streaming for Apache Kafka (Amazon MSK), a common challenge is managing the size of internal metadata records. During rebalances, Kafka persists a metadata record to the internal &lt;code&gt;__consumer_offsets&lt;/code&gt; topic. If the consumer group is large enough, this record can exceed the default 1 MB limit. This causes a &lt;code&gt;RecordTooLargeException&lt;/code&gt; and a rebalance retry loop.&lt;/p&gt;
&lt;p&gt;In this post, we explain how consumer group metadata grows and how to estimate your metadata size. We provide a step-by-step walkthrough for increasing the topic-level size limit (the most common remediation), along with guidance on three complementary strategies: splitting groups, right-sizing partitions, and optimizing naming conventions. We also discuss capacity planning, monitoring, and how &lt;a href="https://cwiki.apache.org/confluence/spaces/KAFKA/pages/217387038/KIP-848+The+Next+Generation+of+the+Consumer+Rebalance+Protocol" target="_blank" rel="noopener"&gt;KIP-848&lt;/a&gt; in Apache Kafka 4.0 addresses this constraint at the protocol level.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Prerequisites&lt;/strong&gt;: This post assumes familiarity with Apache Kafka consumer groups, rebalance protocols, and Amazon MSK cluster configuration. You should have access to the &lt;code&gt;kafka-configs.sh&lt;/code&gt; CLI tool or the Amazon MSK console. The configuration approaches described apply to Amazon MSK Provisioned clusters with Standard brokers. The KIP-848 section covers a forward-looking protocol change in Apache Kafka 4.0 that applies across deployment types.&lt;/p&gt;
&lt;h2 id="how-consumer-group-metadata-grows"&gt;How consumer group metadata grows&lt;/h2&gt;
&lt;p&gt;The following diagram illustrates how consumer group metadata flows through the system during a rebalance:&lt;/p&gt;
&lt;div style="width: 810px" class="wp-caption alignnone"&gt;
 &lt;a href="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/21/BDB-6164-1.png" target="_blank" rel="noopener"&gt;&lt;img src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/21/BDB-6164-1.png" alt="Flow of a GroupMetadata record from the Group Coordinator to the internal offsets topic and follower brokers during a rebalance" width="800"&gt;&lt;/a&gt;
 &lt;p class="wp-caption-text"&gt;Figure 1: Consumer group metadata flow during a rebalance&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;During a rebalance, the Group Coordinator serializes a &lt;code&gt;GroupMetadata&lt;/code&gt; record containing information about every member in the group and persists it to the &lt;code&gt;__consumer_offsets&lt;/code&gt; topic. This record must fit within the topic’s &lt;code&gt;max.message.bytes&lt;/code&gt; limit. Follower brokers must also replicate it, constrained by &lt;code&gt;replica.fetch.max.bytes&lt;/code&gt;. For each member, the record includes:&lt;/p&gt;
&lt;ul&gt;
 &lt;li&gt;&lt;strong&gt;Subscription topics&lt;/strong&gt; – The list of topics the member subscribes to.&lt;/li&gt;
 &lt;li&gt;&lt;strong&gt;Owned partitions&lt;/strong&gt; – Partitions currently held by the member.&lt;/li&gt;
 &lt;li&gt;&lt;strong&gt;Assignment&lt;/strong&gt; – The new partition assignment after rebalancing.&lt;/li&gt;
 &lt;li&gt;&lt;strong&gt;Client ID&lt;/strong&gt; – The configured &lt;code&gt;client.id&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Apache Kafka’s serialization format repeats topic names multiple times per member: once in subscription, once in &lt;code&gt;ownedPartitions&lt;/code&gt;, and once in assignment. The &lt;code&gt;client.id&lt;/code&gt; adds further per-member overhead. The broker stores these metadata records uncompressed in &lt;code&gt;__consumer_offsets&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id="estimating-your-metadata-record-size"&gt;Estimating your metadata record size&lt;/h3&gt;
&lt;p&gt;You can approximate your consumer group’s metadata record size with the following formula:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;record_size ≈ member_count × (3 × avg_topic_name_bytes + 2 × client_id_bytes + ~200 bytes overhead)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Example&lt;/strong&gt;: A group with 1,000 members, a 50-byte topic name, and a 40-byte client ID:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1,000 × (150 + 80 + 200) = ~430 KB&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;At 1,500 members with the same parameters: ~645 KB. With multiple topic subscriptions or longer naming conventions, the record can exceed 1 MB well before 2,000 members.&lt;/p&gt;
&lt;h2 id="approaches-to-handle-large-consumer-group-metadata"&gt;Approaches to handle large consumer group metadata&lt;/h2&gt;
&lt;p&gt;The following sections describe four strategies for managing large consumer group metadata, starting with the most direct remediation.&lt;/p&gt;
&lt;h3 id="increase-max.message.bytes-on-__consumer_offsets"&gt;Increase max.message.bytes on __consumer_offsets&lt;/h3&gt;
&lt;p&gt;If your consumer group metadata exceeds 1 MB, you can increase the maximum record size on the internal topic. This is the most direct path to help unblock consumer groups that have already scaled beyond the default. Note that &lt;code&gt;max.message.bytes&lt;/code&gt; is the topic-level configuration name, while &lt;code&gt;message.max.bytes&lt;/code&gt; is the equivalent broker-level default.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1. Update the topic-level configuration:&lt;/strong&gt;&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-bash"&gt;kafka-configs.sh --bootstrap-server &amp;lt;bootstrap-server&amp;gt; \
    --entity-type topics \
    --entity-name __consumer_offsets \
    --alter \
    --add-config max.message.bytes=2097152 # topic-level config for max record size&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;2. Update&lt;/strong&gt; &lt;code&gt;replica.fetch.max.bytes&lt;/code&gt; &lt;strong&gt;at the cluster level:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This broker-level setting controls the maximum fetch size for inter-broker replication. Set it equal to or greater than &lt;code&gt;max.message.bytes&lt;/code&gt; on &lt;code&gt;__consumer_offsets&lt;/code&gt; so follower brokers can replicate large metadata records.&lt;/p&gt;
&lt;div class="hide-language"&gt;
 &lt;pre&gt;&lt;code class="language-plaintext"&gt;replica.fetch.max.bytes=2097152&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;You can apply this through the Amazon MSK console under Cluster configuration or using the AWS Command Line Interface (AWS CLI) with &lt;code&gt;update-cluster-configuration&lt;/code&gt;.&lt;/p&gt;
&lt;div style="width: 810px" class="wp-caption alignnone"&gt;
 &lt;a href="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/21/BDB-6164-2.png" target="_blank" rel="noopener"&gt;&lt;img src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/21/BDB-6164-2.png" alt="Amazon MSK console cluster configuration editor with the replica.fetch.max.bytes property set" width="800"&gt;&lt;/a&gt;
 &lt;p class="wp-caption-text"&gt;Figure 2: Setting replica.fetch.max.bytes in the Amazon MSK cluster configuration console&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Important: &lt;/strong&gt;Always make sure that &lt;code&gt;replica.fetch.max.bytes&lt;/code&gt; ≥ &lt;code&gt;max.message.bytes&lt;/code&gt; for &lt;code&gt;__consumer_offsets&lt;/code&gt;. Without this, you might observe &lt;code&gt;UnderReplicatedPartitions&lt;/code&gt; on the internal topic.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3. Test in a non-production environment first:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
 &lt;li&gt;Trigger a consumer group rebalance (restart consumers or scale the group).&lt;/li&gt;
 &lt;li&gt;Verify no &lt;code&gt;RecordTooLargeException&lt;/code&gt; in broker logs.&lt;/li&gt;
 &lt;li&gt;Confirm broker heap usage and replication lag remain healthy.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="split-large-consumer-groups"&gt;Split large consumer groups&lt;/h3&gt;
&lt;p&gt;Breaking a single large consumer group into multiple smaller groups reduces the per-group metadata record size proportionally. To split a group, deploy multiple connector or consumer instances, each with a distinct &lt;code&gt;group.id&lt;/code&gt;, subscribing to the same topic but consuming from a subset of partitions. The system preserves offset tracking within each sub-group independently.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;When to use&lt;/strong&gt;: Consumer group membership is growing unboundedly through Auto Scaling, and you want to keep each group’s metadata well within limits without modifying internal topic configuration.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Trade-off&lt;/strong&gt;: Increases operational complexity. You have multiple groups to monitor and manage instead of one.&lt;/p&gt;
&lt;h3 id="right-size-partition-count-and-auto-scaling-bounds"&gt;Right-size partition count and auto scaling bounds&lt;/h3&gt;
&lt;p&gt;Over-partitioned topics require more consumers to fully parallelize, which inflates group membership. Unbounded auto scaling policies can grow consumer groups beyond what was originally planned.&lt;/p&gt;
&lt;ul&gt;
 &lt;li&gt;Review whether your topic’s partition count matches your actual throughput requirements.&lt;/li&gt;
 &lt;li&gt;Configure auto scaling policies with an upper bound on consumer replicas (for example, Horizontal Pod Autoscaler on Amazon Elastic Kubernetes Service (Amazon EKS)).&lt;/li&gt;
 &lt;li&gt;Align partition count with the maximum number of consumers you intend to support.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This is a proactive measure, best applied during topic design and capacity planning to prevent the metadata size issue from occurring in the first place.&lt;/p&gt;
&lt;h3 id="optimize-naming-conventions"&gt;Optimize naming conventions&lt;/h3&gt;
&lt;p&gt;Consumer group names and client IDs contribute to the overall metadata size. Shorter, standardized naming reduces per-member overhead.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Considerations&lt;/strong&gt;: Changing an active consumer group’s name means the new group starts with no committed offsets and all tracking history is lost. For this reason, naming optimization is most practical for new deployments rather than existing production groups.&lt;/p&gt;
&lt;h2 id="capacity-planning-for-larger-metadata-records"&gt;Capacity planning for larger metadata records&lt;/h2&gt;
&lt;p&gt;When you increase &lt;code&gt;max.message.bytes&lt;/code&gt; on &lt;code&gt;__consumer_offsets&lt;/code&gt;, larger metadata records consume more broker heap during rebalance processing. Proper capacity planning helps you select the right broker instance type and configuration value before hitting production issues.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Planning steps:&lt;/strong&gt;&lt;/p&gt;
&lt;ol type="1"&gt;
 &lt;li&gt;&lt;strong&gt;Calculate your current record size&lt;/strong&gt; using: member_count × (3 × topic_name_bytes + 2 × client_id_bytes + ~200).&lt;/li&gt;
 &lt;li&gt;&lt;strong&gt;Project peak membership&lt;/strong&gt; based on your auto scaling upper bound (maximum consumer replicas × number of tasks per connector, if using Kafka Connect).&lt;/li&gt;
 &lt;li&gt;&lt;strong&gt;Apply a 2× safety margin&lt;/strong&gt; to account for protocol overhead, multi-topic subscriptions, and burst scaling events.&lt;/li&gt;
 &lt;li&gt;&lt;strong&gt;Select your &lt;code&gt;max.message.bytes&lt;/code&gt; value from the following guidance table&lt;/strong&gt;.&lt;/li&gt;
 &lt;li&gt;&lt;strong&gt;Choose your broker instance type&lt;/strong&gt; based on heap requirements. Larger metadata records increase heap pressure during rebalances. For groups exceeding 1,000 members with 2+ MB metadata records, use &lt;code&gt;kafka.m5.xlarge&lt;/code&gt; or larger to provide sufficient heap headroom.&lt;/li&gt;
 &lt;li&gt;&lt;strong&gt;Validate in non-production&lt;/strong&gt; by running a consumer group at projected peak membership and monitoring &lt;code&gt;HeapMemoryAfterGC&lt;/code&gt; during rebalances.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The following table provides sizing guidance based on consumer group size:&lt;/p&gt;
&lt;table border="1px" width="100%" cellpadding="10px"&gt;
 &lt;tbody&gt;
  &lt;tr&gt;
   &lt;td&gt;&lt;strong&gt;Consumer Group Size&lt;/strong&gt;&lt;/td&gt;
   &lt;td&gt;&lt;strong&gt;Guidance&lt;/strong&gt;&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;&lt;strong&gt;&amp;lt; 500 members&lt;/strong&gt;&lt;/td&gt;
   &lt;td&gt;Default 1 MB is typically sufficient. &lt;code&gt;kafka.m5.large&lt;/code&gt; or larger.&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;&lt;strong&gt;500–1,000 members&lt;/strong&gt;&lt;/td&gt;
   &lt;td&gt;Monitor metadata size. Consider increasing to 2 MB. &lt;code&gt;kafka.m5.xlarge&lt;/code&gt; or larger.&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;&lt;strong&gt;1,000–2,000 members&lt;/strong&gt;&lt;/td&gt;
   &lt;td&gt;Increase to 2–5 MB. &lt;code&gt;kafka.m5.2xlarge&lt;/code&gt; or larger for adequate heap headroom.&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;&lt;strong&gt;&amp;gt; 2,000 members&lt;/strong&gt;&lt;/td&gt;
   &lt;td&gt;Combine increased limit with group splitting. &lt;code&gt;kafka.m5.2xlarge&lt;/code&gt; minimum. Consider &lt;code&gt;kafka.m5.4xlarge&lt;/code&gt; for high rebalance frequency.&lt;/td&gt;
  &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id="key-metrics-to-monitor"&gt;Key metrics to monitor&lt;/h3&gt;
&lt;p&gt;The following Amazon CloudWatch metrics help you track consumer group metadata health:&lt;/p&gt;
&lt;table border="1px" width="100%" cellpadding="10px"&gt;
 &lt;tbody&gt;
  &lt;tr&gt;
   &lt;td&gt;&lt;strong&gt;Metric&lt;/strong&gt;&lt;/td&gt;
   &lt;td&gt;&lt;strong&gt;What it tells you&lt;/strong&gt;&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;&lt;code&gt;HeapMemoryAfterGC&lt;/code&gt; (Amazon CloudWatch)&lt;/td&gt;
   &lt;td&gt;Percentage of heap memory in use after garbage collection. Indicates memory pressure from larger metadata records during rebalances.&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;&lt;code&gt;UnderReplicatedPartitions&lt;/code&gt; (Amazon CloudWatch)&lt;/td&gt;
   &lt;td&gt;Replication health. Non-zero may indicate &lt;code&gt;replica.fetch.max.bytes&lt;/code&gt; is too low.&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;GC pause duration (broker logs)&lt;/td&gt;
   &lt;td&gt;Prolonged GC can trigger session timeouts and cascading rebalances.&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;Consumer group rebalance rate&lt;/td&gt;
   &lt;td&gt;Stable groups should not rebalance frequently after configuration changes.&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;Consumer lag&lt;/td&gt;
   &lt;td&gt;Confirms consumers are making progress after rebalances complete.&lt;/td&gt;
  &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;We recommend creating two Amazon CloudWatch alarms for &lt;code&gt;HeapMemoryAfterGC&lt;/code&gt;. Set a warning alarm at &lt;strong&gt;60%&lt;/strong&gt; to indicate potential performance degradation. Set a critical alarm at 80 percent, at which point you should scale brokers or reduce consumer group size. For &lt;code&gt;UnderReplicatedPartitions&lt;/code&gt;, alarm at any value&lt;strong&gt;&amp;gt; &lt;/strong&gt;0 sustained for more than 5 minutes after a configuration change.&lt;/p&gt;
&lt;h3 id="looking-ahead-kip-848-and-apache-kafka-4.0"&gt;Looking ahead: KIP-848 and Apache Kafka 4.0&lt;/h3&gt;
&lt;p&gt;Apache Kafka 4.0 (released March 2025) adopted &lt;a href="https://cwiki.apache.org/confluence/display/KAFKA/KIP-848" target="_blank" rel="noopener"&gt;KIP-848&lt;/a&gt; as the default consumer protocol. The broker now computes partition assignments server-side rather than delegating to a consumer group leader. Because each member no longer carries full subscription and assignment data on the wire, the new protocol reduces per-member metadata size. For details on the protocol changes that achieve this reduction, see the &lt;a href="https://cwiki.apache.org/confluence/display/KAFKA/KIP-848%3A+The+Next+Generation+of+the+Consumer+Rebalance+Protocol#KIP848:TheNextGenerationoftheConsumerRebalanceProtocol-Motivation" target="_blank" rel="noopener"&gt;KIP-848 design document&lt;/a&gt;. KIP-848 also introduces incremental rebalances.&lt;/p&gt;
&lt;p&gt;Newer Apache Kafka versions on Amazon MSK bring smaller metadata records by default. The following steps help you prepare for KIP-848 adoption:&lt;/p&gt;
&lt;ol type="1"&gt;
 &lt;li&gt;Track Amazon MSK version support for Apache Kafka 4.0+.&lt;/li&gt;
 &lt;li&gt;Verify your Kafka client libraries support the new consumer protocol.&lt;/li&gt;
 &lt;li&gt;Test the new protocol in a non-production environment before migrating production consumer groups.&lt;/li&gt;
 &lt;li&gt;Plan for a phased rollout, starting with non-critical consumer groups.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;The following table summarizes when to apply each approach. The &lt;code&gt;max.message.bytes&lt;/code&gt; increase (covered step-by-step earlier) is the primary remediation. The other strategies are complementary guidance you can adapt to your environment:&lt;/p&gt;
&lt;table border="1px" width="100%" cellpadding="10px"&gt;
 &lt;tbody&gt;
  &lt;tr&gt;
   &lt;td&gt;&lt;strong&gt;Situation&lt;/strong&gt;&lt;/td&gt;
   &lt;td&gt;&lt;strong&gt;Recommended approach&lt;/strong&gt;&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;Already hitting &lt;code&gt;RecordTooLargeException&lt;/code&gt; in production&lt;/td&gt;
   &lt;td&gt;Increase &lt;code&gt;max.message.bytes&lt;/code&gt; on &lt;code&gt;__consumer_offsets&lt;/code&gt; + set &lt;code&gt;replica.fetch.max.bytes&lt;/code&gt; accordingly&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;Planning for growth&lt;/td&gt;
   &lt;td&gt;Right-size partitions, set auto scaling bounds, monitor metadata size&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;Naming overhead is significant&lt;/td&gt;
   &lt;td&gt;Optimize naming conventions for new deployments&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;Operating at very large scale (2,000+ members)&lt;/td&gt;
   &lt;td&gt;Combine increased limits with consumer group splitting&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;Long-term architecture&lt;/td&gt;
   &lt;td&gt;Plan migration path to KIP-848 (Apache Kafka 4.0)&lt;/td&gt;
  &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Test configuration changes in non-production first, monitor broker metrics during and after rebalances, and scale incrementally. With these practices in place, you can operate consumer groups at the scale your streaming workloads require.&lt;/p&gt;
&lt;p&gt;To get started:&lt;/p&gt;
&lt;ol type="1"&gt;
 &lt;li&gt;Review the &lt;a href="https://docs.aws.amazon.com/msk/latest/developerguide/" target="_blank" rel="noopener"&gt;Amazon MSK Developer Guide&lt;/a&gt; for cluster configuration steps.&lt;/li&gt;
 &lt;li&gt;Use the sizing formula in this post to estimate your current metadata record size.&lt;/li&gt;
 &lt;li&gt;Set up &lt;a href="https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/AlarmThatSendsEmail.html" target="_blank" rel="noopener"&gt;Amazon CloudWatch alarms&lt;/a&gt; on &lt;code&gt;HeapMemoryAfterGC&lt;/code&gt; to monitor broker health proactively.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="related-resources"&gt;Related resources&lt;/h2&gt;
&lt;ul&gt;
 &lt;li&gt;&lt;a href="https://docs.aws.amazon.com/msk/latest/developerguide/" target="_blank" rel="noopener"&gt;Amazon MSK Developer Guide — Configuration&lt;/a&gt;&lt;/li&gt;
 &lt;li&gt;&lt;a href="https://repost.aws/articles/ARfShOOzvBSra6UwgWOz9GXg/handling-large-messages-in-amazon-managed-streaming-for-apache-kafka-msk" target="_blank" rel="noopener"&gt;Handling large messages in Amazon MSK&lt;/a&gt;&lt;/li&gt;
 &lt;li&gt;&lt;a href="https://cwiki.apache.org/confluence/display/KAFKA/KIP-848" target="_blank" rel="noopener"&gt;KIP-848: The next generation of the consumer rebalance protocol&lt;/a&gt;&lt;/li&gt;
 &lt;li&gt;&lt;a href="https://issues.apache.org/jira/browse/KAFKA-3810" target="_blank" rel="noopener"&gt;KAFKA-3810: Replication of internal topics and replica.fetch.max.bytes&lt;/a&gt;&lt;/li&gt;
 &lt;li&gt;&lt;a href="https://aws.amazon.com/blogs/big-data/amazon-msk-express-brokers-now-support-intelligent-rebalancing-for-180-times-faster-operation-performance/" target="_blank" rel="noopener"&gt;Amazon MSK Express brokers — Intelligent Rebalancing&lt;/a&gt; (benchmark details in linked post)&lt;/li&gt;
&lt;/ul&gt;
&lt;p style="clear: both"&gt;&lt;/p&gt;
&lt;hr style="width: 100%"&gt;
&lt;h2&gt;About the authors&lt;/h2&gt;
&lt;footer&gt;
 &lt;div class="blog-author-box" style="padding-top: 2.0em"&gt;
  &lt;div class="blog-author-image" style="margin-right: 1.0em"&gt;
   &lt;img loading="lazy" class="alignnone size-full" src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/21/BDB-6164-3.png" alt="Pallavi Jha" width="100" height="133"&gt;
  &lt;/div&gt;
  &lt;h3 class="lb-h4"&gt;Pallavi Jha&lt;/h3&gt;
  &lt;p style="overflow: hidden"&gt;Pallavi is a Technical Consultant at Amazon Web Services, helping customers architect and optimize their streaming workloads on Amazon MSK. She works with enterprises running large-scale data pipelines on Apache Kafka, focusing on performance, resilience, and operational best practices. Outside work, she enjoys exploring creating music and hiking. Connect with her on &lt;a href="https://www.linkedin.com/in/pallavi-jha-aws/" target="_blank" rel="noopener"&gt;LinkedIn&lt;/a&gt;.&lt;/p&gt;
 &lt;/div&gt;
 &lt;div class="blog-author-box" style="padding-top: 2.0em"&gt;
  &lt;div class="blog-author-image" style="margin-right: 1.0em"&gt;
   &lt;img loading="lazy" class="alignnone size-full" src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/21/BDB-6164-4.png" alt="Sunil Kumar Patro" width="100" height="133"&gt;
  &lt;/div&gt;
  &lt;h3 class="lb-h4"&gt;Sunil Kumar Patro&lt;/h3&gt;
  &lt;p style="overflow: hidden"&gt;Sunil is a Senior Technical Account Manager at Amazon Web Services with over 21 years of experience driving architecture and delivery for multi-technology platforms. He works with global enterprise customers to build scalable, modern, and cost-effective solutions on AWS. He specializes in Amazon EKS, Amazon MSK, Amazon OpenSearch Service, and Data Lakehouse architectures, helping customers design high-performing, real-time streaming and analytics platforms at scale.&lt;/p&gt;
 &lt;/div&gt;
&lt;/footer&gt;</content:encoded>
					
		
		
			</item>
		<item>
		<title>How Moeve standardized dbt runs across data lakes with Amazon Athena</title>
		<link>https://aws.amazon.com/blogs/big-data/how-moeve-standardized-dbt-runs-across-data-lakes-with-amazon-athena/</link>
		
		<dc:creator><![CDATA[Rubén Romero Córdoba]]></dc:creator>
		<pubDate>Tue, 22 Sep 2026 15:13:31 +0000</pubDate>
				<category><![CDATA[Amazon Athena]]></category>
		<category><![CDATA[Customer Solutions]]></category>
		<category><![CDATA[Intermediate (200)]]></category>
		<guid isPermaLink="false">edccae8f8147ce129dd7541f01c316b3e4ca47b7</guid>

					<description>Moeve standardized how it runs dbt across multiple data lakes by building a centralized, serverless launcher on Amazon Athena, AWS Step Functions, AWS Fargate, Amazon DynamoDB, and Amazon EventBridge, cutting new-project onboarding from days to about 15 minutes while keeping compute close to the data and orchestration loosely coupled.</description>
										<content:encoded>&lt;p&gt;As organizations grow, data processing often becomes fragmented across teams, environments, and orchestration tools. This fragmentation leads to inconsistent patterns, duplicated logic, limited cost visibility, and operational overhead.&lt;/p&gt;
&lt;p&gt;At Moeve we were no exception. Our analytics teams build their transformations with dbt, an open source tool that defines transformations as SQL models, resolves the references between them, and works out the order in which they run. dbt describes what to transform, but it does not define where or how a project runs. We left that decision to each team, and as the number of projects grew we found fragmented pipelines, inconsistent compute engines, and limited cost visibility slowing every project down. Standardizing our dbt runs on &lt;a href="https://aws.amazon.com/athena/" target="_blank" rel="noopener"&gt;Amazon Athena&lt;/a&gt; was how we worked our way out of that.&lt;/p&gt;
&lt;p&gt;This post describes the architecture of the centralized, serverless solution we built on Athena, which reduced onboarding for a new dbt project from days to about 15 minutes. The solution centralizes how dbt runs across our data lakes while staying loosely coupled from orchestration. It uses Amazon Athena as the default processing engine, a centralized dbt launcher, and a shared event bus for downstream orchestration.&lt;/p&gt;
&lt;p&gt;In the sections that follow we explain how we decoupled dbt runs from orchestration using &lt;a href="https://aws.amazon.com/step-functions/" target="_blank" rel="noopener"&gt;AWS Step Functions&lt;/a&gt; and &lt;a href="https://aws.amazon.com/fargate/" target="_blank" rel="noopener"&gt;AWS Fargate&lt;/a&gt;, why Athena fits our workloads from a cost and operational perspective, how storing run parameters in &lt;a href="https://aws.amazon.com/dynamodb/" target="_blank" rel="noopener"&gt;Amazon DynamoDB&lt;/a&gt; rather than in pipeline code removed the infrastructure deployment step from onboarding, and how publishing results to &lt;a href="https://aws.amazon.com/eventbridge/" target="_blank" rel="noopener"&gt;Amazon EventBridge&lt;/a&gt; lets our run and orchestration layers evolve independently.&lt;/p&gt;
&lt;h2 id="the-challenge-of-running-dbt-at-scale"&gt;The challenge of running dbt at scale&lt;/h2&gt;
&lt;p&gt;Before the dbt launcher, our dbt runs had grown in different directions. Run logic was embedded in project-specific pipelines, orchestration and processing were tightly coupled, teams selected different compute engines for comparable workloads, and we had no consistent governance over run parameters and retries.&lt;/p&gt;
&lt;p&gt;As the number of dbt projects increased, this made it difficult to enforce consistent standards and to evolve the solution without touching every pipeline. We needed a way to standardize dbt runs across our data lakes, decouple running a project from deciding what to run, improve cost control and observability, and deliver faster and safer continuous integration and continuous delivery (CI/CD) iterations.&lt;/p&gt;
&lt;h2 id="why-amazon-athena-as-the-default-dbt-engine"&gt;Why Amazon Athena as the default dbt engine&lt;/h2&gt;
&lt;p&gt;Choosing the processing engine for dbt is a foundational architectural decision, so we made it first.&lt;/p&gt;
&lt;h3 id="serverless-processing"&gt;Serverless processing&lt;/h3&gt;
&lt;p&gt;Athena is fully serverless. There are no clusters to provision, scale, or maintain. Our teams run their queries with the default Athena pricing, which charges for the data a query scans and gives us elasticity with no capacity planning.&lt;/p&gt;
&lt;p&gt;Because each data lake lives in its own account, each team makes its own decision about Athena payment. A team whose workload grows into continuous, high-concurrency usage can move to Athena capacity reservations with no change to the launcher, to their dbt profiles, or to their project configuration. None of our teams have needed to do so yet, and the architecture keeps that choice independent per team.&lt;/p&gt;
&lt;p&gt;Our workload is predictable but not continuous. Each dbt project runs for a few minutes when its schedule fires or when its upstream data lands, then stays idle until the next trigger. That shape is what made serverless the right fit for us.&lt;/p&gt;
&lt;h3 id="why-athena-fit-our-solution"&gt;Why Athena fit our solution&lt;/h3&gt;
&lt;p&gt;For Moeve, the decision to standardize on dbt and Amazon Athena was driven by our goal of creating a common transformation solution that could be adopted across multiple teams and AWS accounts while keeping operations lightweight.&lt;/p&gt;
&lt;p&gt;Our data was already stored in Amazon S3 and registered in the &lt;a href="https://docs.aws.amazon.com/glue/latest/dg/catalog-and-crawler.html" target="_blank" rel="noopener"&gt;AWS Glue Data Catalog&lt;/a&gt;, making Athena a natural processing layer. Athena allowed us to run transformations without managing clusters, capacity, or infrastructure, which was particularly important for a small central team supporting multiple domains.&lt;/p&gt;
&lt;p&gt;We evaluated alternative processing engines, but for our workload profile and data volumes, Athena provided the best balance between scalability, operational simplicity, and maintainability. Adapter maturity was another important factor. The dbt Athena adapter offered strong integration with testing, CI/CD workflows, and the broader dbt ecosystem, reducing the operational risk of maintaining custom solutions.&lt;/p&gt;
&lt;p&gt;Athena also aligned naturally with our architecture. Transformations run in the AWS account that owns the data through cross-account role assumption, while orchestration remains centralized. As a result, we standardized how projects run across teams while keeping compute close to the data.&lt;/p&gt;
&lt;p&gt;Finally, the Apache Iceberg support in Athena underpins the idempotent incremental processing model described in this post, so incremental loads and historical reprocessing follow the same path with minimal operational overhead.&lt;/p&gt;
&lt;h3 id="optimized-incremental-processing"&gt;Optimized incremental processing&lt;/h3&gt;
&lt;p&gt;Our largest cost was not reading source data. It was merging into it.&lt;/p&gt;
&lt;p&gt;Our fact tables are Apache Iceberg tables in the data lake, and most of our models are incremental. Each run brings in new or corrected records and merges them into a target that can hold several years of history. A merge has to locate the rows it is about to update, and without a predicate on the target the query reads far more of the table than the incoming data can affect. The common approach is a static filter such as the last 30 days, which is wrong in both directions: too wide for an ordinary daily load, and too narrow as soon as a correction arrives for an older partition.&lt;/p&gt;
&lt;p&gt;Instead of a fixed window, the platform derives the predicate from the data. Before the merge runs, it reads the distinct values of the partition column present in the incoming dataset and builds the target predicate from them. For a single partition it applies an equality predicate, for a small set an &lt;code&gt;IN&lt;/code&gt; list, and for a larger set a bounded range. The merge then reads only the partitions the incoming data can affect.&lt;/p&gt;
&lt;p&gt;The same principle applies on the source side. The launcher builds the source filter from the parameters given for that run: an explicit range, an arbitrary SQL condition, or, when neither is supplied, a default window taken from the project configuration. Input is therefore bounded to the subset each run needs.&lt;/p&gt;
&lt;p&gt;Two results mattered to us. Because Athena charges for the data a query scans, narrowing both ends of the merge reduces cost without any team hand-tuning individual models. More importantly, a daily load and a full historical reprocess became the same operation with different inputs. Every run is idempotent, so the same input always produces the same result regardless of how many times it runs. That removed the distinction between processing and reprocessing from our runbooks and simplified incident response.&lt;/p&gt;
&lt;p&gt;Table design is what makes this pruning possible. Partitioning, columnar formats, and compression all contribute, and the AWS Big Data Blog post &lt;a href="https://aws.amazon.com/blogs/big-data/top-10-performance-tuning-tips-for-amazon-athena/" target="_blank" rel="noopener"&gt;Top 10 performance tuning tips for Amazon Athena&lt;/a&gt; covers the general techniques. We have deliberately not published a before and after figure here, because the saving depends so heavily on partition design and data distribution that a single number would mislead without extensive context.&lt;/p&gt;
&lt;h2 id="architecture-overview"&gt;Architecture overview&lt;/h2&gt;
&lt;p&gt;Moeve built a centralized dbt launcher that runs dbt jobs in a uniform way, regardless of the project or the target data lake.&lt;/p&gt;
&lt;h3 id="architecture-diagram"&gt;Architecture diagram&lt;/h3&gt;
&lt;div style="width: 810px" class="wp-caption alignnone"&gt;
 &lt;a href="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/11/BDB-6045-1.png" target="_blank" rel="noopener"&gt;&lt;img src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/11/BDB-6045-1.png" alt="Architecture of the centralized dbt launcher running cross-account dbt jobs on Step Functions, Fargate, and Amazon Athena" width="800"&gt;&lt;/a&gt;
 &lt;p class="wp-caption-text"&gt;Figure 1: Centralized dbt launcher and cross-account run flow&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;At a high level, the architecture consists of:&lt;/p&gt;
&lt;ul&gt;
 &lt;li&gt;AWS Step Functions to control the run lifecycle.&lt;/li&gt;
 &lt;li&gt;AWS Fargate to run dbt in an isolated, ephemeral container.&lt;/li&gt;
 &lt;li&gt;Amazon Athena as the default dbt processing engine.&lt;/li&gt;
 &lt;li&gt;Amazon DynamoDB to store dbt project configuration.&lt;/li&gt;
 &lt;li&gt;Amazon EventBridge to publish run results.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Every dbt run follows the same contract, which gives us consistency and reduces the operational surface we must maintain.&lt;/p&gt;
&lt;h3 id="cross-account-processing-model"&gt;Cross-account processing model&lt;/h3&gt;
&lt;p&gt;The solution operates in a centralized account while running transformations in domain-specific data lake accounts: corporate, marketing, and manufacturing. The Fargate container assumes a &lt;code&gt;dbt-child&lt;/code&gt; IAM role in the target account, so the container processes data where it lives while governance stays centralized.&lt;/p&gt;
&lt;p&gt;Each data lake account keeps control of its own IAM permissions and manages its own storage and catalog without affecting the solution. This also puts costs in the right account. We could have attributed Athena spend using Athena workgroups, but Athena is only part of what a query costs. The &lt;a href="https://aws.amazon.com/s3/" target="_blank" rel="noopener"&gt;Amazon Simple Storage Service (Amazon S3)&lt;/a&gt; requests it makes and the &lt;a href="https://aws.amazon.com/kms/" target="_blank" rel="noopener"&gt;AWS Key Management Service (AWS KMS)&lt;/a&gt; operations it triggers are real costs as well, and running in the owning account attributes all of them to the team that owns the data, per project and per run.&lt;/p&gt;
&lt;h2 id="centralizing-dbt-runs-with-the-dbt-launcher"&gt;Centralizing dbt runs with the dbt launcher&lt;/h2&gt;
&lt;p&gt;To stop every team inventing its own way of running dbt, we built a single launcher that all of them go through. Instead of dbt logic living inside multiple pipelines, every run is triggered through one well-defined path.&lt;/p&gt;
&lt;h3 id="run-lifecycle"&gt;Run lifecycle&lt;/h3&gt;
&lt;p&gt;The launcher is an AWS Step Functions state machine. It receives a run request with its parameters, starts an AWS Fargate task from our dbt container image, and the container assumes the IAM role of the target data lake account. dbt then runs its SQL transformations in Athena, reading the source tables and materializing the targets. Alongside the run, &lt;a href="https://www.elementary-data.com/" target="_blank" rel="noopener"&gt;Elementary&lt;/a&gt;, an open source dbt package, records model-level results and data quality test outcomes. When the run finishes, the launcher publishes a completion event to Amazon EventBridge.&lt;/p&gt;
&lt;p&gt;The state machine can be started in different ways depending on the scenario. Some projects run on a schedule, others are triggered when upstream data lands, and teams can request a run on demand. Those decisions are made by our orchestration layer, which submits a standardized run request to the launcher. The launcher therefore stays focused on running dbt projects, regardless of how the run was initiated.&lt;/p&gt;
&lt;p&gt;This sequence is identical for all projects and environments. The launcher is responsible only for running the project it was asked to run. It doesn’t decide what should run next, and that responsibility is intentionally delegated to downstream consumers through Amazon EventBridge.&lt;/p&gt;
&lt;h2 id="configuration-driven-runs-with-amazon-dynamodb"&gt;Configuration-driven runs with Amazon DynamoDB&lt;/h2&gt;
&lt;p&gt;We had to decide where a project’s run parameters would live. In the pipeline definition, changing a timeout would be a code change, a review, a build, and a deployment, for a value we sometimes need to change while an incident is open. We put the parameters in DynamoDB instead, keyed by project, and the launcher reads them at the start of every run. That lets us decouple code deployment from run behavior, update parameters without redeploying services, and enforce consistent defaults across all dbt projects.&lt;/p&gt;
&lt;p&gt;The parameters themselves are modest. They cover the target environment and AWS Identity and Access Management (IAM) role, the default processing engine, how long to allow a run to take, how many times to retry on failure, and how many days of data to process by default. These values change for operational reasons rather than logical ones, which is why we did not want them coupled to a release cycle.&lt;/p&gt;
&lt;p&gt;The effect on onboarding was larger than we expected. Deploying a new dbt project is now a merge of the dbt models and one configuration entry. There is no Terraform change, no infrastructure review, and nothing to provision, because the compute the project needs already exists and is shared. What used to be a multi-step infrastructure pipeline is now a single CI workflow that validates the project’s SQL and lineage locally, then merges and registers it. We run that local validation with DuckDB, which returns feedback in under two minutes without consuming cloud resources.&lt;/p&gt;
&lt;p&gt;Governance did not weaken as a result. Who may change a configuration entry is controlled the same way as any other production change. What changed is that the change no longer has to travel through an infrastructure deployment to take effect.&lt;/p&gt;
&lt;h3 id="cicd-pipeline-diagram"&gt;CI/CD pipeline diagram&lt;/h3&gt;
&lt;div style="width: 644px" class="wp-caption alignnone"&gt;
 &lt;a href="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/11/BDB-6045-2.png" target="_blank" rel="noopener"&gt;&lt;img src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/11/BDB-6045-2.png" alt="A pull request triggers local validation of the project’s SQL and lineage. On merge, the dbt models are deployed and the project’s configuration entry is registered in DynamoDB, after which the launcher can run the project." width="634"&gt;&lt;/a&gt;
 &lt;p class="wp-caption-text"&gt;Figure 2: CI workflow for onboarding and updating a dbt project&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;Athena remains the engine of record. Local validation catches Jinja errors, unresolved references, and obvious SQL mistakes, but Athena-specific behavior, cross-account permissions, and AWS Glue Data Catalog interactions are only proven in the target environment. It’s important to be explicit about that boundary with the teams, so that a green CI run is not read as a guarantee.&lt;/p&gt;
&lt;h2 id="publishing-run-results-with-amazon-eventbridge"&gt;Publishing run results with Amazon EventBridge&lt;/h2&gt;
&lt;p&gt;After a dbt run finishes, the launcher publishes a structured event to a central Amazon EventBridge bus recording whether the run succeeded, which project and source it covered, when it started and finished, how long it took, and which datasets it updated. The launcher does not know which consumers are subscribed.&lt;/p&gt;
&lt;p&gt;This event-driven approach gives us loose coupling between running a project and orchestrating what comes next, multiple downstream consumers for the same signal, and independent evolution of both layers.&lt;/p&gt;
&lt;p&gt;At Moeve the main consumer is our orchestration layer, which models the dependencies between datasets as a graph. Each completion event tells it that a node is now up to date, so it can determine which downstream projects have all their inputs ready and start them. That consumer has no special status. An &lt;a href="https://aws.amazon.com/lambda/" target="_blank" rel="noopener"&gt;AWS Lambda&lt;/a&gt; function, a monitoring dashboard, or a notification integration can subscribe to the same events without any change to the launcher.&lt;/p&gt;
&lt;h2 id="validation-and-observability"&gt;Validation and observability&lt;/h2&gt;
&lt;p&gt;Because every project follows the same lifecycle, we get validation and observability in one place instead of per pipeline. Step Functions shows the state of any run and the step at which it failed, and error handling and retries are defined once. Fargate logs carry the container runtime detail. dbt and Elementary report model-level results and data quality test outcomes. The completion event on the bus is the auditable record that a project finished and what it produced. Together these layers give us operational visibility without coupling the components to each other.&lt;/p&gt;
&lt;h2 id="cost-control-and-resource-cleanup"&gt;Cost control and resource cleanup&lt;/h2&gt;
&lt;p&gt;The platform is serverless end to end, which keeps idle cost close to zero and removes a class of operational mistake. Fargate tasks are created for a run and destroyed when it ends, so no long-running container needs maintenance. Athena has no persistent compute. An idle Step Functions state machine costs nothing. Nothing is left running unintentionally, which matters when the number of projects on the platform keeps growing.&lt;/p&gt;
&lt;h2 id="results"&gt;Results&lt;/h2&gt;
&lt;p&gt;The metrics in the following table are the ones our teams notice day to day, and the reason the solution is maintainable by a small central team.&lt;/p&gt;
&lt;table border="1px" width="100%" cellpadding="10px"&gt;
 &lt;tbody&gt;
  &lt;tr&gt;
   &lt;td&gt;&lt;strong&gt;Metric&lt;/strong&gt;&lt;/td&gt;
   &lt;td&gt;&lt;strong&gt;Before&lt;/strong&gt;&lt;/td&gt;
   &lt;td&gt;&lt;strong&gt;After&lt;/strong&gt;&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;Onboarding time for a new dbt project&lt;/td&gt;
   &lt;td&gt;Days, including pipeline and infrastructure setup&lt;/td&gt;
   &lt;td&gt;About 15 minutes, configuration only&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;Run consistency&lt;/td&gt;
   &lt;td&gt;Varied by team&lt;/td&gt;
   &lt;td&gt;Same lifecycle and contract for every project&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;CI feedback time&lt;/td&gt;
   &lt;td&gt;8 to 10 minutes on Jenkins&lt;/td&gt;
   &lt;td&gt;Under 2 minutes with local validation&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;Cost visibility&lt;/td&gt;
   &lt;td&gt;Per-account aggregate&lt;/td&gt;
   &lt;td&gt;Per-project and per-run attribution&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;Operational overhead&lt;/td&gt;
   &lt;td&gt;One pipeline per project&lt;/td&gt;
   &lt;td&gt;One solution for all projects&lt;/td&gt;
  &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Standardizing how we run dbt turned out to depend less on dbt than on defining two boundaries clearly.&lt;/p&gt;
&lt;p&gt;The first is the contract of the launcher: parameters in, event out. We defined that interface before building the internals, and it has stayed stable while the implementation changed several times. The second is the separation between running a project and deciding what to run next. Making the launcher publish events without knowing its consumers is why our orchestration layer could be rebuilt while the launcher stayed as it was, and the launcher has never been modified to accommodate a new orchestration requirement.&lt;/p&gt;
&lt;p&gt;Two smaller decisions carried more weight than we expected. Keeping run parameters in DynamoDB rather than in pipeline definitions means timeouts, retries, and engine selection can be changed without a deployment, which is valuable during incident response. Making every run idempotent removed the distinction between processing and reprocessing, so a daily load and a full historical reprocess are the same operation with different inputs.&lt;/p&gt;
&lt;p&gt;Athena is serverless, so our teams did not need to set up infrastructure of their own. That is what made it practical for everyone to run dbt in a standardized way, and why onboarding a new project went from days to about 15 minutes.&lt;/p&gt;
&lt;p&gt;If your organization runs dbt across multiple accounts and teams, pair Amazon Athena with a clear contract for the component that runs your projects: fixed parameters in, a published event out.&lt;/p&gt;
&lt;h2 id="resources"&gt;Resources&lt;/h2&gt;
&lt;ul&gt;
 &lt;li&gt;&lt;a href="https://docs.aws.amazon.com/athena/" target="_blank" rel="noopener"&gt;Amazon Athena documentation&lt;/a&gt;&lt;/li&gt;
 &lt;li&gt;&lt;a href="https://docs.aws.amazon.com/step-functions/" target="_blank" rel="noopener"&gt;AWS Step Functions Developer Guide&lt;/a&gt;&lt;/li&gt;
 &lt;li&gt;&lt;a href="https://docs.aws.amazon.com/eventbridge/" target="_blank" rel="noopener"&gt;Amazon EventBridge User Guide&lt;/a&gt;&lt;/li&gt;
 &lt;li&gt;&lt;a href="https://aws.amazon.com/blogs/big-data/top-10-performance-tuning-tips-for-amazon-athena/" target="_blank" rel="noopener"&gt;Top 10 performance tuning tips for Amazon Athena&lt;/a&gt;&lt;/li&gt;
 &lt;li&gt;&lt;a href="https://docs.aws.amazon.com/athena/latest/ug/querying-iceberg.html" target="_blank" rel="noopener"&gt;Using Apache Iceberg tables in Amazon Athena&lt;/a&gt;&lt;/li&gt;
 &lt;li&gt;&lt;a href="https://docs.getdbt.com/" target="_blank" rel="noopener"&gt;dbt documentation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p style="clear: both"&gt;&lt;/p&gt;
&lt;hr style="width: 100%"&gt;
&lt;h2&gt;About the authors&lt;/h2&gt;
&lt;footer&gt;
 &lt;div class="blog-author-box" style="padding-top: 2.0em"&gt;
  &lt;div class="blog-author-image" style="margin-right: 1.0em"&gt;
   &lt;img loading="lazy" class="alignnone size-full" src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/11/BDB-6045-3.jpg" alt="Rubén Romero Córdoba" width="100" height="133"&gt;
  &lt;/div&gt;
  &lt;h3 class="lb-h4"&gt;Rubén Romero Córdoba&lt;/h3&gt;
  &lt;p style="overflow: hidden"&gt;Rubén is a Cloud and Data Architect at Keepler with 10+ years spanning AI research, software engineering, and AWS data architecture. He designs secure, scalable, and maintainable data platforms focused on lakehouse architectures, governance, and operational efficiency. Curious and pragmatic, he studies how systems work, explores emerging tech, shares knowledge, and favors simple solutions that deliver real value without unnecessary complexity.&lt;/p&gt;
 &lt;/div&gt;
 &lt;div class="blog-author-box" style="padding-top: 2.0em"&gt;
  &lt;div class="blog-author-image" style="margin-right: 1.0em"&gt;
   &lt;img loading="lazy" class="alignnone size-full" src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/11/BDB-6045-4.jpg" alt="Ricardo Bravo Panes" width="100" height="133"&gt;
  &lt;/div&gt;
  &lt;h3 class="lb-h4"&gt;Ricardo Bravo Panes&lt;/h3&gt;
  &lt;p style="overflow: hidden"&gt;Ricardo is a Data Architect at Moeve, where he designs cloud-native data solutions on AWS, turning complex challenges into scalable and sustainable solutions. He is passionate about data, distributed systems, and simplifying complexity to help teams move faster.&lt;/p&gt;
 &lt;/div&gt;
 &lt;div class="blog-author-box" style="padding-top: 2.0em"&gt;
  &lt;div class="blog-author-image" style="margin-right: 1.0em"&gt;
   &lt;img loading="lazy" class="alignnone size-full" src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/11/BDB-6045-5.jpg" alt="Álvaro Ponce Cabrera" width="100" height="133"&gt;
  &lt;/div&gt;
  &lt;h3 class="lb-h4"&gt;Álvaro Ponce Cabrera&lt;/h3&gt;
  &lt;p style="overflow: hidden"&gt;Álvaro is a Data Engineer and Data Platform Lead at Moeve, focused on building scalable data products and cloud-native solutions that connect industrial and business data. His interests span data architecture, governance, AI, and developer experience, always seeking pragmatic solutions that maximize business value while keeping complexity under control.&lt;/p&gt;
 &lt;/div&gt;
 &lt;div class="blog-author-box" style="padding-top: 2.0em"&gt;
  &lt;div class="blog-author-image" style="margin-right: 1.0em"&gt;
   &lt;img loading="lazy" class="alignnone size-full" src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2026/09/11/BDB-6045-6.jpg" alt="Gonzalo Guerrero Leon" width="100" height="133"&gt;
  &lt;/div&gt;
  &lt;h3 class="lb-h4"&gt;Gonzalo Guerrero Leon&lt;/h3&gt;
  &lt;p style="overflow: hidden"&gt;Gonzalo is a TAM at AWS who empowers enterprise customers through strategic technical guidance. Throughout his 10-year tenure at Amazon, he’s contributed to multiple cornerstone divisions, including HR, IT, Alexa, Amazon Business, and AWS, gaining insight into the technology landscape. Outside of work, Gonzalo enjoys playing volleyball with his wife and exploring the world alongside their adventurous Boston Terrier, Tigre.&lt;/p&gt;
 &lt;/div&gt;
&lt;/footer&gt;</content:encoded>
					
		
		
			</item>
	</channel>
</rss>