<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Aniruddha Writes</title>
    <description>The latest articles on DEV Community by Aniruddha Writes (@aniruddhawrites).</description>
    <link>https://dev.to/aniruddhawrites</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F459680%2Fe2363222-544a-4ae2-b559-5a6569630a1b.png</url>
      <title>DEV Community: Aniruddha Writes</title>
      <link>https://dev.to/aniruddhawrites</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/aniruddhawrites"/>
    <language>en</language>
    <item>
      <title>When Fast Ingestion Creates Slow Analytics</title>
      <dc:creator>Aniruddha Writes</dc:creator>
      <pubDate>Sat, 29 Aug 2026 05:02:20 +0000</pubDate>
      <link>https://dev.to/aniruddhawrites/when-fast-ingestion-creates-slow-analytics-4c6o</link>
      <guid>https://dev.to/aniruddhawrites/when-fast-ingestion-creates-slow-analytics-4c6o</guid>
      <description>&lt;h2&gt;
  
  
  Solving the S3 Small-File Problem with Apache Iceberg, Compaction, and Amazon Redshift Spectrum
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;A reference architecture case study&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Pipeline Was Fast. The Queries Were Slow.
&lt;/h2&gt;

&lt;p&gt;Every metric on the ingestion side looked healthy.&lt;/p&gt;

&lt;p&gt;Events from IoT devices, mobile apps, and web clients streamed into an Amazon S3 data lake continuously, durably, with low write latency. The pipeline was doing exactly what it was built to do.&lt;/p&gt;

&lt;p&gt;The dashboards built on top of that data told a different story.&lt;/p&gt;

&lt;p&gt;Queries that used to return in seconds were taking minutes. Analysts filed tickets. Someone suggested adding more Redshift compute. It didn't help much.&lt;/p&gt;

&lt;p&gt;The two halves of this system were never in conflict about correctness — the data was all there, intact and durable. They were in conflict about &lt;em&gt;shape&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;A streaming system optimizes for low-latency, continuous, durable writes.&lt;/p&gt;

&lt;p&gt;An analytical engine optimizes for the opposite: fewer, larger, well-organized files it can plan around efficiently.&lt;/p&gt;

&lt;p&gt;Nobody had designed for the second half of that sentence, because nothing about the ingestion pipeline was wrong on its own terms.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The problem wasn't necessarily the amount of data. It was the physical shape of the data.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Architecture Note
&lt;/h3&gt;

&lt;p&gt;This is a &lt;strong&gt;reference architecture&lt;/strong&gt;, not a specific customer engagement.&lt;/p&gt;

&lt;p&gt;The workload, file counts, file sizes, and query patterns described below are illustrative and intended to represent a common streaming-ingestion pattern.&lt;/p&gt;

&lt;p&gt;Where a claim is backed by AWS or Apache documentation, it is cited. Where a number is illustrative rather than measured, that is stated explicitly.&lt;/p&gt;

&lt;p&gt;No benchmark result in this article is presented as measured fact unless independently verified.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. The Architecture at a Glance
&lt;/h2&gt;

&lt;p&gt;The system has three stages, and the story of this article is what happens to the data's physical shape as it moves through them.&lt;/p&gt;

&lt;h3&gt;
  
  
  Ingestion
&lt;/h3&gt;

&lt;p&gt;IoT devices, mobile apps, and web applications emit events continuously.&lt;/p&gt;

&lt;p&gt;A streaming layer writes them to an Amazon S3 landing zone as JSON objects, favoring low write latency over downstream analytical concerns.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Bottleneck
&lt;/h3&gt;

&lt;p&gt;Over hours and days, that landing zone accumulates an enormous number of small JSON objects — illustratively, tens or hundreds of thousands of files averaging around 10 KB each.&lt;/p&gt;

&lt;p&gt;Amazon Redshift Spectrum, querying this layout as external tables, absorbs a large and largely avoidable planning and scanning overhead before it ever reaches a useful byte of data.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Optimization
&lt;/h3&gt;

&lt;p&gt;Apache Iceberg is introduced as a table format and metadata layer over the same S3 storage.&lt;/p&gt;

&lt;p&gt;AWS Glue or Spark jobs perform compaction — rewriting many small JSON files into fewer, larger Parquet files — and commit the result as a new Iceberg table snapshot.&lt;/p&gt;

&lt;p&gt;Redshift Spectrum then queries a physically consolidated, columnar, metadata-described table instead of a sprawling directory of small text files.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Architecture in One View
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fw8wqtjg6aropfqki4s86.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fw8wqtjg6aropfqki4s86.png" alt="From fast ingestion to slow analytics — and the optimized physical layout" width="799" height="388"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 1 — From fast ingestion to fragmented storage and finally to a metadata-aware, compacted analytical layout.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Three stages, three different concerns:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;how data arrives,&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;how data is stored for analysis,&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;how a query engine plans and executes against it.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Keeping those three concerns distinct is the throughline of everything that follows.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. The Incident: Fast Ingestion, Slow Analytics
&lt;/h2&gt;

&lt;p&gt;The presenting symptom was simple:&lt;/p&gt;

&lt;p&gt;Dashboard queries against recent event data were getting progressively slower, while the ingestion pipeline showed no degradation at all.&lt;/p&gt;

&lt;p&gt;Write throughput was steady.&lt;/p&gt;

&lt;p&gt;S3 PUT success rates were normal.&lt;/p&gt;

&lt;p&gt;No errors. No backpressure. No obvious infrastructure fault.&lt;/p&gt;

&lt;p&gt;That combination — a healthy write path and a degrading read path over the &lt;em&gt;same&lt;/em&gt; data — is the signature of a physical layout problem, not necessarily a capacity problem.&lt;/p&gt;

&lt;p&gt;If the cluster were simply undersized, you'd expect query performance to be fixable, at least to some degree, by scaling compute.&lt;/p&gt;

&lt;p&gt;Here, scaling compute produced only marginal improvement.&lt;/p&gt;

&lt;p&gt;That is itself a diagnostic clue worth taking seriously.&lt;/p&gt;

&lt;p&gt;Throwing more compute at a query engine doesn't reduce the number of small objects it has to enumerate, open, and parse before it reaches useful data. It can process some of that overhead in parallel, but only up to a point.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. The Investigation
&lt;/h2&gt;

&lt;p&gt;The investigation deliberately avoided jumping straight to:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"We need Iceberg."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It started with measurement.&lt;/p&gt;

&lt;p&gt;The team looked at:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Query latency&lt;/strong&gt;, broken into planning time and execution time separately, not as one combined number.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Files scanned per query&lt;/strong&gt;, compared against the amount of data a query's predicates should have actually required.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Bytes scanned&lt;/strong&gt;, to distinguish "we're touching too many objects" from "we're touching too much data."&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;S3 object layout&lt;/strong&gt; — file count per partition, average object size, and the distribution around that average.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;File format&lt;/strong&gt; — JSON, uncompressed, record-oriented.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Partition structure&lt;/strong&gt; — how data was organized under the landing prefix.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Query plans&lt;/strong&gt; — what Redshift Spectrum's planner was actually doing before scan work began.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This separation matters because file-count reduction and query-latency reduction are &lt;strong&gt;not the same claim&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;A compaction job can drastically cut the number of files a query touches while only moderately reducing the bytes scanned, particularly for queries that aren't highly selective.&lt;/p&gt;

&lt;p&gt;AWS's Spectrum performance guidance ties query performance to file size, file format, and partition design as separate, interacting levers — not one lever with one dial.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Root Cause #1: The Small-File Explosion
&lt;/h2&gt;

&lt;p&gt;Small files, on their own, aren't the problem.&lt;/p&gt;

&lt;p&gt;A single 10 KB file is nothing.&lt;/p&gt;

&lt;p&gt;The problem is what accumulates when a streaming pipeline optimized for write latency runs continuously against the same landing zone for hours and days without any downstream consolidation step.&lt;/p&gt;

&lt;p&gt;It's worth being precise about &lt;em&gt;why&lt;/em&gt; a large number of small objects is expensive for an analytical engine.&lt;/p&gt;

&lt;p&gt;"More files means more requests, therefore slow" is an oversimplification.&lt;/p&gt;

&lt;p&gt;The actual mechanism has several contributing layers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Object and file enumeration.&lt;/strong&gt; Before any scanning happens, the query engine has to discover which objects exist under the relevant prefixes or partitions.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Metadata discovery and planning overhead.&lt;/strong&gt; Building an execution plan against a directory structure with an extremely high object count takes measurably longer than planning against a smaller number of well-described files.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Object access overhead.&lt;/strong&gt; Each object that participates in a scan carries its own fixed cost — opening it, reading whatever header or footer metadata the format requires, and closing it — independent of how much useful data it contains.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Task scheduling.&lt;/strong&gt; A distributed query engine has to schedule work across many small units instead of fewer, larger, more efficient ones, and scheduling overhead doesn't scale to zero.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Parsing cost.&lt;/strong&gt; Record-oriented text formats like JSON have to be parsed row by row, with no ability to skip irrelevant fields during the read.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Reduced parallelism efficiency.&lt;/strong&gt; Extremely small files can create more scheduling overhead than benefit, because the fixed cost per file competes with the actual work being done.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The governing principle is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The smaller the object, the larger the fixed overhead becomes relative to the amount of useful data it contains.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;At 10 KB per object and tens of thousands of objects, an analytical engine can spend a disproportionate share of total query time on overhead that has nothing to do with the actual predicates in the query.&lt;/p&gt;

&lt;h3&gt;
  
  
  S3 Isn't the Bottleneck
&lt;/h3&gt;

&lt;p&gt;It's also worth being explicit about what &lt;em&gt;isn't&lt;/em&gt; the bottleneck here.&lt;/p&gt;

&lt;p&gt;Amazon S3 itself is not slow, and it is not inherently poorly suited to storing many small objects.&lt;/p&gt;

&lt;p&gt;The bottleneck is the interaction between:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;object granularity,&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;file format,&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;partitioning,&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;metadata,&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;and how an analytical query engine has to plan and execute a scan across that layout.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That distinction matters.&lt;/p&gt;

&lt;p&gt;"S3 is slow" and "this physical layout is expensive to scan" point to very different fixes.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Small-File Tax
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhu5drtisglpbmu8tlsn9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhu5drtisglpbmu8tlsn9.png" alt="The small-file tax — same data, different physical organization" width="800" height="425"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 2 — Illustrative comparison of the same 1 TB dataset represented as many tiny objects versus substantially fewer larger objects.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The important point is not the exact 10 KB or 128 MB figure.&lt;/p&gt;

&lt;p&gt;The point is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The amount of data is unchanged. The physical organization is not.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;More objects mean more fixed work relative to useful bytes.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Root Cause #2: JSON Is the Wrong Physical Shape for Analytics
&lt;/h2&gt;

&lt;p&gt;JSON is not a bad format.&lt;/p&gt;

&lt;p&gt;It's an excellent format for what it's usually used for: representing individual records in application and integration contexts where flexibility and human-readability matter more than analytical scan efficiency.&lt;/p&gt;

&lt;p&gt;The problem isn't that JSON is deficient.&lt;/p&gt;

&lt;p&gt;It's that JSON's design center and an analytical engine's access pattern point in different directions.&lt;/p&gt;

&lt;p&gt;JSON is text-based and record-oriented.&lt;/p&gt;

&lt;p&gt;To read any single field from any single record, the parser generally has to read and interpret the whole record.&lt;/p&gt;

&lt;p&gt;It isn't organized around columnar access, which means an analytical query that only needs three columns out of forty still pays the cost of parsing all forty for every record it touches.&lt;/p&gt;

&lt;p&gt;It's important not to overstate this.&lt;/p&gt;

&lt;p&gt;JSON isn't incompressible, and general-purpose compression can and does reduce its size on disk.&lt;/p&gt;

&lt;p&gt;But compression on a record-oriented text format doesn't grant the ability to skip columns during a scan.&lt;/p&gt;

&lt;p&gt;That's where a large share of analytical efficiency actually comes from.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why Parquet Changes the Equation
&lt;/h3&gt;

&lt;p&gt;Parquet addresses this directly.&lt;/p&gt;

&lt;p&gt;It's columnar, so a query that touches three columns can, subject to how the file was written and how the engine plans the scan, avoid reading data for the other thirty-seven.&lt;/p&gt;

&lt;p&gt;It supports efficient compression schemes suited to columnar layouts.&lt;/p&gt;

&lt;p&gt;And Parquet files carry embedded statistics — such as min/max values and null counts — that a query engine's planner can use to skip entire row groups without reading them at all.&lt;/p&gt;

&lt;p&gt;The underlying architectural principle is important:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Reducing file count addresses file-level overhead. Moving to a columnar format addresses per-byte scan efficiency.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;They are related, additive improvements, not the same improvement described twice.&lt;/p&gt;

&lt;p&gt;A workload that fixes file count but stays in JSON still pays the columnar-access penalty.&lt;/p&gt;

&lt;p&gt;A workload that moves to Parquet but keeps producing thousands of tiny files still pays the object-overhead penalty.&lt;/p&gt;

&lt;p&gt;Both matter independently.&lt;/p&gt;




&lt;h2&gt;
  
  
  6. Root Cause #3: The Need for Metadata-Aware Table Management
&lt;/h2&gt;

&lt;p&gt;The third root cause is less visible than file size or file format, and it's the one most often skipped.&lt;/p&gt;

&lt;p&gt;Even after you decide to compact files and move to Parquet, someone has to safely manage the &lt;em&gt;process&lt;/em&gt; of rewriting a live, continuously growing dataset without:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;breaking readers,&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;losing data,&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;or leaving the table in an inconsistent state mid-rewrite.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is a genuinely different problem from:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"What format should the files be?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;or:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"How many files should there be?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It's a &lt;strong&gt;table-management problem&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;How do you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;track which files currently constitute the authoritative state of a table?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;let a rewrite job replace a large batch of files atomically?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;let readers see a consistent view of the table while compaction is actively running?&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A raw S3 prefix with no table format has no native answer to all of these questions.&lt;/p&gt;

&lt;p&gt;This is the gap Apache Iceberg is designed to fill.&lt;/p&gt;

&lt;p&gt;And it is a distinct concern from file size or file format.&lt;/p&gt;




&lt;h2&gt;
  
  
  7. The Architectural Intervention
&lt;/h2&gt;

&lt;p&gt;The fix is not:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;"Adopt Iceberg."&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It's the deliberate combination of three things that each solve a different part of the problem.&lt;/p&gt;

&lt;h3&gt;
  
  
  Apache Iceberg
&lt;/h3&gt;

&lt;p&gt;Provides the table abstraction — the metadata layer that tracks table state, snapshots, and schema, and that makes atomic, consistent rewrites of the underlying files possible.&lt;/p&gt;

&lt;h3&gt;
  
  
  Parquet
&lt;/h3&gt;

&lt;p&gt;Provides the physical file format — columnar storage, efficient compression, and embedded statistics that a query planner can use.&lt;/p&gt;

&lt;h3&gt;
  
  
  Compaction
&lt;/h3&gt;

&lt;p&gt;Run as a Spark job on AWS Glue or Amazon EMR, compaction is the process that actually rewrites many small files into fewer, larger ones and commits the result through Iceberg.&lt;/p&gt;

&lt;h3&gt;
  
  
  Three Technologies. Three Different Jobs.
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Technology / Concept&lt;/th&gt;
&lt;th&gt;Primary responsibility&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Apache Iceberg&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Table abstraction, metadata, snapshots, atomic commits&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Apache Parquet&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Physical columnar file format&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Compaction&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Rewrites many smaller files into fewer larger files&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;These are three separable technologies performing three separable jobs.&lt;/p&gt;

&lt;p&gt;Iceberg is &lt;strong&gt;not&lt;/strong&gt; a file format.&lt;/p&gt;

&lt;p&gt;It doesn't itself make files larger, smaller, columnar, or compressed.&lt;/p&gt;

&lt;p&gt;Parquet is &lt;strong&gt;not&lt;/strong&gt; a table format.&lt;/p&gt;

&lt;p&gt;It has no concept of snapshots, schema evolution, or atomic multi-file commits on its own.&lt;/p&gt;

&lt;p&gt;Compaction is &lt;strong&gt;not free&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;It is compute work that has to be scheduled, run, monitored, and paid for.&lt;/p&gt;

&lt;p&gt;Keeping these boundaries distinct is what separates an accurate explanation of this architecture from a marketing summary of it.&lt;/p&gt;




&lt;h2&gt;
  
  
  8. How Iceberg Organizes the Table
&lt;/h2&gt;

&lt;p&gt;Iceberg's contribution is best understood as a metadata hierarchy sitting above the physical Parquet files in S3.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fq32rbnpu8u49klvwcz7z.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fq32rbnpu8u49klvwcz7z.png" alt="How Apache Iceberg tracks the physical table" width="552" height="1772"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 3 — Conceptual Iceberg metadata hierarchy from table state to physical Parquet objects.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;A simplified logical view is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Iceberg Table
      ↓
Snapshot
      ↓
Manifest List
      ↓
Manifests
      ↓
Data Files
      ↓
Parquet Objects in S3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A &lt;strong&gt;snapshot&lt;/strong&gt; represents the complete, consistent state of the table at a point in time.&lt;/p&gt;

&lt;p&gt;Each snapshot points to a &lt;strong&gt;manifest list&lt;/strong&gt;, which in turn references one or more &lt;strong&gt;manifests&lt;/strong&gt; — files that track metadata about groups of data files, including partition values and, when generated, column-level statistics.&lt;/p&gt;

&lt;p&gt;The manifests point to the actual &lt;strong&gt;data files&lt;/strong&gt;: the Parquet objects physically stored in S3.&lt;/p&gt;

&lt;h3&gt;
  
  
  A Necessary Caveat
&lt;/h3&gt;

&lt;p&gt;This hierarchy is a conceptual and logical model for how Iceberg organizes table state and enables metadata-driven planning.&lt;/p&gt;

&lt;p&gt;It is &lt;strong&gt;not&lt;/strong&gt; a literal claim about the exact sequence or count of network operations Redshift Spectrum performs for any given query.&lt;/p&gt;

&lt;p&gt;What it gives a query engine is the ability to plan from structured metadata about which files are relevant to a query, rather than having to enumerate an entire object listing from S3 directly.&lt;/p&gt;

&lt;p&gt;AWS documentation on querying Iceberg tables from Redshift describes this metadata-driven planning model and separately notes that AWS Glue-generated statistics for Iceberg tables can help Redshift's optimizer reduce the number of files it needs to scan.&lt;/p&gt;

&lt;p&gt;Iceberg also provides atomic commits and snapshot isolation.&lt;/p&gt;

&lt;p&gt;A compaction job can replace a large batch of small files with a smaller batch of large ones as a single atomic operation.&lt;/p&gt;

&lt;p&gt;Readers querying the table mid-rewrite see either the old consistent state or the new one — never a partial, broken mix of both.&lt;/p&gt;

&lt;p&gt;That property is what makes it &lt;strong&gt;safe to continuously reorganize a live analytical dataset&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;And that is a genuinely different contribution from:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"makes files bigger"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;or:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"makes queries faster."&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  9. How Compaction Changes the Physical Layout
&lt;/h2&gt;

&lt;p&gt;Compaction is the mechanical process that actually does the file rewriting.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Faniruddhawrites.github.io%2Fassets%2Fimg%2Fhow-compaction-changes-physical-layout.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Faniruddhawrites.github.io%2Fassets%2Fimg%2Fhow-compaction-changes-physical-layout.png" alt="How compaction changes the physical layout" width="798" height="112"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 4 — Compaction rewrites physical files while Iceberg manages the resulting table state.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The lifecycle is conceptually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Small Files
     ↓
Compaction Job
(Glue / Spark)
     ↓
Read Existing Data Files
     ↓
Rewrite / Merge into Larger Parquet Files
     ↓
Atomic Iceberg Commit
     ↓
New Table Snapshot
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The job reads a set of existing small files, rewrites their combined contents into a smaller number of larger Parquet files, and commits that rewrite as a new Iceberg snapshot.&lt;/p&gt;

&lt;p&gt;The old files aren't deleted the instant the new ones are written.&lt;/p&gt;

&lt;p&gt;They remain part of prior snapshots until retention and expiration policies clean them up.&lt;/p&gt;

&lt;p&gt;That is deliberate.&lt;/p&gt;

&lt;p&gt;It is what allows time-travel queries and safe rollback if something in the rewrite went wrong.&lt;/p&gt;

&lt;h3&gt;
  
  
  Compaction Is Recurring
&lt;/h3&gt;

&lt;p&gt;Compaction is recurring, not one-time, because ingestion doesn't stop.&lt;/p&gt;

&lt;p&gt;A landing zone under continuous write load will keep accumulating small files after every compaction run.&lt;/p&gt;

&lt;p&gt;That raises an operational question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;How often should compaction actually run?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;"Run it daily" is not a universal answer.&lt;/p&gt;

&lt;p&gt;The right cadence depends on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;rate of file accumulation,&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;query workload,&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;freshness requirements,&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;cost of the compaction job,&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;partition behavior,&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;and available rewrite capacity.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A workload accumulating files faster than a daily job can absorb needs more frequent, threshold-based triggering.&lt;/p&gt;

&lt;p&gt;Compact when a partition crosses a file-count or fragmentation threshold rather than on a fixed calendar regardless of actual need.&lt;/p&gt;

&lt;p&gt;A workload with a healthy, slowly growing partition doesn't need the compute cost of a daily rewrite it doesn't yet require.&lt;/p&gt;




&lt;h2&gt;
  
  
  10. What Changes at Query Time
&lt;/h2&gt;

&lt;p&gt;With the intervention in place, Redshift Spectrum is querying a table with a materially different physical and metadata profile.&lt;/p&gt;

&lt;p&gt;It's important to state precisely what's actually happening.&lt;/p&gt;

&lt;p&gt;This is exactly the point where oversimplified claims tend to creep in.&lt;/p&gt;

&lt;p&gt;Redshift Spectrum does &lt;strong&gt;not&lt;/strong&gt; perform:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"a single metadata lookup"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;and then instantly access all relevant data.&lt;/p&gt;

&lt;p&gt;That phrase overstates how metadata-driven planning works.&lt;/p&gt;

&lt;p&gt;The actual mechanism is a structured, hierarchical process of using Iceberg's snapshot and manifest metadata to identify a relevant, pruned set of data files before scan work begins.&lt;/p&gt;

&lt;p&gt;Nor is it accurate to describe the underlying mechanism as:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Spectrum issues one HTTP GET request for every file."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That's a plausible-sounding simplification, but stating it as a precise fact about Spectrum's execution path isn't something the available documentation directly confirms.&lt;/p&gt;

&lt;p&gt;The actual overhead mechanism is broader:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;enumeration,&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;planning,&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;parsing,&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;scheduling,&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;and object access.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  What Can Be Stated with Confidence
&lt;/h3&gt;

&lt;p&gt;Redshift Spectrum performance benefits from:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;columnar file formats,&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;appropriately sized files rather than KB-scale objects,&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;well-designed partitioning that supports pruning,&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;and, specifically for Iceberg tables, Glue-generated statistics that can help the optimizer reduce the number of files it needs to scan.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The architectural change is therefore not:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1 metadata lookup
        ↓
everything is fast
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Fragmented physical layout
        ↓
Metadata-aware table
        ↓
File / partition pruning
        ↓
Fewer, better-organized files
        ↓
More efficient analytical scanning
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  11. Before vs. After
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnxzaiqhbl8z3q3ikp9wy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnxzaiqhbl8z3q3ikp9wy.png" alt="Same data. Different physical layout." width="800" height="1189"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 5 — The ingestion path remains largely unchanged; the downstream analytical representation changes.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Before
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Applications (IoT / mobile / web)
     ↓
Streaming ingestion
     ↓
Amazon S3 landing zone
     ↓
Many small JSON objects
(~10 KB, illustrative)
     ↓
Amazon Redshift Spectrum
     ↓
Higher enumeration, planning,
and parsing overhead
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  After
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Applications (IoT / mobile / web)
     ↓
Streaming ingestion
     ↓
Amazon S3 landing zone
     ↓
Apache Iceberg table
     ↓
AWS Glue / Spark compaction
(threshold-based)
     ↓
Fewer, larger Parquet files
committed as new snapshots
     ↓
Amazon Redshift Spectrum
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Same Data. Different Physical Layout.
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Aspect&lt;/th&gt;
&lt;th&gt;Before&lt;/th&gt;
&lt;th&gt;After&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;File format&lt;/td&gt;
&lt;td&gt;JSON&lt;/td&gt;
&lt;td&gt;Parquet&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Average file size&lt;/td&gt;
&lt;td&gt;~10 KB (illustrative)&lt;/td&gt;
&lt;td&gt;64–128 MB (illustrative target range)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;File count&lt;/td&gt;
&lt;td&gt;Very high&lt;/td&gt;
&lt;td&gt;Substantially lower&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Metadata planning&lt;/td&gt;
&lt;td&gt;Directory-level object listing&lt;/td&gt;
&lt;td&gt;Iceberg snapshots and manifests&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Optimizer statistics&lt;/td&gt;
&lt;td&gt;Minimal&lt;/td&gt;
&lt;td&gt;Glue-generated Iceberg statistics&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Scan efficiency&lt;/td&gt;
&lt;td&gt;Lower — full-record parsing&lt;/td&gt;
&lt;td&gt;Higher — columnar, prunable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Compression&lt;/td&gt;
&lt;td&gt;Text-oriented&lt;/td&gt;
&lt;td&gt;Columnar compression&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Data freshness&lt;/td&gt;
&lt;td&gt;Immediate&lt;/td&gt;
&lt;td&gt;Slightly delayed, dependent on compaction cadence&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Ingestion behavior is unchanged in this picture.&lt;/p&gt;

&lt;p&gt;That's deliberate.&lt;/p&gt;

&lt;p&gt;The applications still write the way they were always going to write.&lt;/p&gt;

&lt;p&gt;What changed is entirely downstream, in how that data is subsequently organized for the workload that consumes it.&lt;/p&gt;




&lt;h2&gt;
  
  
  12. Benchmarking the Improvement
&lt;/h2&gt;

&lt;p&gt;Here is the point where this article deliberately declines to hand you a headline number.&lt;/p&gt;

&lt;p&gt;No specific production benchmark result — no percentage, no "Nx faster," no before/after latency figure — is presented as measured fact in this article because none was independently verified for this reference scenario.&lt;/p&gt;

&lt;p&gt;What follows instead is a reproducible methodology, along with hypotheses that AWS's own documentation supports.&lt;/p&gt;

&lt;p&gt;Those hypotheses should be tested against the actual workload before being turned into performance claims.&lt;/p&gt;

&lt;h3&gt;
  
  
  Benchmark at More Than One Scale
&lt;/h3&gt;

&lt;p&gt;Run the comparison at more than one scale — for example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;100 GB&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;1 TB&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;10 TB&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Overhead effects at small scale don't always predict behavior at larger scale.&lt;/p&gt;

&lt;h3&gt;
  
  
  Variants to Compare
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Variant&lt;/th&gt;
&lt;th&gt;Format&lt;/th&gt;
&lt;th&gt;Average file size&lt;/th&gt;
&lt;th&gt;Table layer&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Baseline&lt;/td&gt;
&lt;td&gt;JSON&lt;/td&gt;
&lt;td&gt;~10 KB&lt;/td&gt;
&lt;td&gt;Raw external table, no Iceberg&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Optimized — Parquet only&lt;/td&gt;
&lt;td&gt;Parquet&lt;/td&gt;
&lt;td&gt;64–128 MB&lt;/td&gt;
&lt;td&gt;No Iceberg / minimal metadata&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Optimized — Parquet + Iceberg&lt;/td&gt;
&lt;td&gt;Parquet&lt;/td&gt;
&lt;td&gt;64–128 MB&lt;/td&gt;
&lt;td&gt;Iceberg table + Glue-generated statistics&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Including the middle variant matters.&lt;/p&gt;

&lt;p&gt;It lets you separate the contribution of file format and file size from the contribution of Iceberg's metadata layer, rather than crediting one intervention for the combined effect of two.&lt;/p&gt;

&lt;h3&gt;
  
  
  Representative Query Workload
&lt;/h3&gt;

&lt;p&gt;Test several query shapes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;time-range filter,&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;highly selective predicate,&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;aggregation,&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;multi-column projection,&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;partition-pruned query.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This covers query patterns that can behave differently under fragmented versus consolidated layouts.&lt;/p&gt;

&lt;h3&gt;
  
  
  Measure the Layers Separately
&lt;/h3&gt;

&lt;p&gt;Measure:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;total query latency,&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;planning latency,&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;execution latency,&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;bytes scanned,&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;files scanned,&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;observable S3 request patterns where available,&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;relevant Spectrum-side query metrics,&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;compute consumption,&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;cost per query.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Control the Variables
&lt;/h3&gt;

&lt;p&gt;Keep the following identical between runs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;SQL text,&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Redshift cluster configuration,&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;concurrency,&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;S3 region,&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;partition scheme,&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;underlying row content,&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;compression settings.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Report More Than a Single Number
&lt;/h3&gt;

&lt;p&gt;Report:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;median,&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;p95,&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;and the distribution across repeated runs.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Don't report only the single best-case query.&lt;/p&gt;

&lt;p&gt;More importantly, report each metric layer separately rather than collapsing everything into one latency figure.&lt;/p&gt;

&lt;h3&gt;
  
  
  What the Documentation Supports as a Hypothesis
&lt;/h3&gt;

&lt;p&gt;A reasonable hypothesis is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;JSON at small file sizes will show the highest planning and scan overhead of the three variants.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Moving to Parquet will reduce per-byte scan cost through columnar access and compression.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Adding Iceberg with Glue-generated statistics can further reduce files scanned for selective queries because the optimizer has structured statistics available for pruning.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;These are &lt;strong&gt;hypotheses to benchmark&lt;/strong&gt;, not guaranteed results.&lt;/p&gt;

&lt;p&gt;If you run this methodology against your own workload and observe a specific speedup — 10x, 50x, 100x, whatever the number turns out to be — that number is a property of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;your workload,&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;your query selectivity,&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;your data shape,&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;your partitioning,&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;your infrastructure,&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;and your implementation.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It is not a universal property of Iceberg.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Benchmark the architecture, not the technology label.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  13. The Cost of Optimization
&lt;/h2&gt;

&lt;p&gt;It would be dishonest to present this as a pure win.&lt;/p&gt;

&lt;p&gt;Compaction is not free.&lt;/p&gt;

&lt;p&gt;Pretending otherwise undermines the credibility of everything else in this article.&lt;/p&gt;

&lt;h3&gt;
  
  
  Costs Introduced
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Glue or Spark compute to run the compaction jobs themselves.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The rewrite operation's own I/O — reading the small files being consolidated and writing the new Parquet output.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Temporary storage overhead during the rewrite.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Operational complexity: something new to monitor, alert on, and operate.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A freshness trade-off — data isn't reflected in the optimized table until it has been through a compaction cycle, so there is an inherent lag between arrival and full analytical optimization.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Costs Potentially Reduced
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Query-side compute spent on unnecessary scanning and parsing.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The operational and business cost of slow dashboards.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Reduced request and object-access overhead per query.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The governing economic principle is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Compaction is worthwhile when the recurring analytical cost and latency caused by a fragmented physical layout exceed the cost of maintaining an optimized one.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For a landing zone that's rarely queried directly, that calculus might not favor aggressive compaction at all.&lt;/p&gt;

&lt;p&gt;For a table backing high-frequency dashboards under continuous analytical load, it usually will.&lt;/p&gt;

&lt;p&gt;That's a workload-specific judgment, not a default answer.&lt;/p&gt;




&lt;h2&gt;
  
  
  14. Production Guardrails
&lt;/h2&gt;

&lt;p&gt;A physical-layout optimization introduces its own failure modes.&lt;/p&gt;

&lt;p&gt;None of these are exotic.&lt;/p&gt;

&lt;p&gt;They're the ordinary operational reality of running compaction as a continuous process against a continuously growing dataset.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Failure mode&lt;/th&gt;
&lt;th&gt;Why it happens&lt;/th&gt;
&lt;th&gt;Detection&lt;/th&gt;
&lt;th&gt;Mitigation&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Compaction lag&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Files accumulate faster than compaction jobs can process them&lt;/td&gt;
&lt;td&gt;File count and freshness lag trending upward&lt;/td&gt;
&lt;td&gt;Threshold-based triggers; sufficient rewrite capacity&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Compaction storms&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Multiple large rewrites trigger simultaneously&lt;/td&gt;
&lt;td&gt;Spikes in job concurrency and compute cost&lt;/td&gt;
&lt;td&gt;Scheduling controls, rate limiting, partition-scoped optimization&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Over-partitioning&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Partition design is too fine-grained&lt;/td&gt;
&lt;td&gt;High file count per partition&lt;/td&gt;
&lt;td&gt;Coarser partitioning aligned to actual query patterns&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Wrong target file size&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Files end up too small or too large&lt;/td&gt;
&lt;td&gt;File-size distribution tracked against target&lt;/td&gt;
&lt;td&gt;Tune thresholds against workload behavior&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Snapshot accumulation&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Old snapshots are retained indefinitely&lt;/td&gt;
&lt;td&gt;Snapshot count and age trending upward&lt;/td&gt;
&lt;td&gt;Retention and expiration policies&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Orphaned files&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Failed rewrites leave unreferenced objects&lt;/td&gt;
&lt;td&gt;Catalog-to-S3 mismatch&lt;/td&gt;
&lt;td&gt;Scheduled cleanup / orphan-file removal&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Failed compaction jobs&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Jobs fail without being noticed&lt;/td&gt;
&lt;td&gt;Job alerts and freshness monitoring&lt;/td&gt;
&lt;td&gt;Retries, backoff, lag-based alerting&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Raw-zone querying&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Consumers bypass the optimized table&lt;/td&gt;
&lt;td&gt;Query source auditing&lt;/td&gt;
&lt;td&gt;Access controls and catalog separation&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;None of these are reasons to avoid the architecture.&lt;/p&gt;

&lt;p&gt;They're the reason it needs &lt;strong&gt;monitoring, lifecycle management, and operational ownership&lt;/strong&gt; rather than being treated as a one-time migration.&lt;/p&gt;




&lt;h2&gt;
  
  
  15. When You Don't Actually Need Iceberg
&lt;/h2&gt;

&lt;p&gt;This is worth stating directly because it's the section most often missing from articles that are ultimately trying to sell a technology rather than solve a problem:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Iceberg is not the first thing to try.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The decision order is roughly in ascending order of complexity.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Fix Ingestion Batching or Windowing First
&lt;/h3&gt;

&lt;p&gt;If the root problem is that the producer is flushing far more frequently than necessary, adjusting write batching or windowing at the source can eliminate the small-file problem before it ever reaches S3.&lt;/p&gt;

&lt;p&gt;This is the cheapest fix and the one most often skipped in favor of a downstream architectural solution.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Write Directly to Parquet
&lt;/h3&gt;

&lt;p&gt;If the dataset is relatively simple, doesn't need concurrent readers and writers reasoning about consistent snapshots, and doesn't need to evolve its schema or partitioning over time, writing directly to Parquet may be sufficient.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Run Scheduled or Threshold-Based Compaction
&lt;/h3&gt;

&lt;p&gt;Where the operational need is genuinely just:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Consolidate files periodically."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;you may not need a table format if snapshot isolation, schema evolution, and other table semantics aren't required.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Adopt Iceberg with Compaction
&lt;/h3&gt;

&lt;p&gt;Iceberg becomes compelling when the dataset genuinely needs table abstraction:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;concurrent readers and writers operating safely against an evolving dataset,&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;schema evolution,&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;partition evolution,&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;atomic multi-file commits,&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;time travel,&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;and long-lived governance over an analytical table's lifecycle.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The conclusion is not:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Everyone should use Iceberg."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Use the simplest architecture that solves the actual problem.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Introduce a table format when the dataset's lifecycle actually justifies the complexity it adds.&lt;/p&gt;

&lt;p&gt;A batch job that compacts JSON into Parquet on a schedule, with no table format at all, may be entirely sufficient for a dataset that doesn't need concurrent writers or schema evolution.&lt;/p&gt;

&lt;p&gt;Iceberg earns its place when the table's operational lifecycle demands more than compaction alone can provide.&lt;/p&gt;




&lt;h2&gt;
  
  
  16. Architecture Decision Framework
&lt;/h2&gt;

&lt;p&gt;&lt;a href="/assets/img/iceberg-architectural-choice.png%20align=" class="article-body-image-wrapper"&gt;&lt;img src="/assets/img/iceberg-architectural-choice.png%20align=" alt="Iceberg is an architectural choice, not a reflex"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 6 — A decision framework for determining whether the workload actually needs Iceberg.&lt;/em&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Approach&lt;/th&gt;
&lt;th&gt;Complexity&lt;/th&gt;
&lt;th&gt;Solves Small Files?&lt;/th&gt;
&lt;th&gt;Adds Table Semantics?&lt;/th&gt;
&lt;th&gt;Best Fit&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Better ingestion batching&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Root cause is upstream write behavior&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;JSON → Parquet&lt;/td&gt;
&lt;td&gt;Low–Medium&lt;/td&gt;
&lt;td&gt;Partially&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Simple, relatively static analytical datasets&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Standalone scheduled / threshold compaction&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Periodic file consolidation without table semantics&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Iceberg + compaction&lt;/td&gt;
&lt;td&gt;Medium–High&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Evolving datasets requiring table governance&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Alternative table format&lt;/td&gt;
&lt;td&gt;Medium–High&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Workloads favoring another format's semantics&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This table isn't meant to declare a winner.&lt;/p&gt;

&lt;p&gt;It's meant to help you place your own workload against the actual axes that matter:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;How complex is the fix you're willing to operate?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Does the dataset genuinely need table-level semantics?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Or does it simply need a cleaner physical layout?&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  What This Article Is — and Isn't
&lt;/h2&gt;

&lt;p&gt;This article presents a &lt;strong&gt;reference architecture&lt;/strong&gt;, not a production benchmark or customer case study.&lt;/p&gt;

&lt;p&gt;It is not an argument that every S3 data lake should use Apache Iceberg.&lt;/p&gt;

&lt;p&gt;It is not a claim that 128 MB is the universally correct file size.&lt;/p&gt;

&lt;p&gt;It is not a guarantee that compaction produces a specific percentage improvement.&lt;/p&gt;

&lt;p&gt;It is not a claim that S3 performs poorly when a bucket contains many objects.&lt;/p&gt;

&lt;p&gt;And it is not a claim that Redshift Spectrum performs one specific network operation per file as the sole explanation for the small-file problem.&lt;/p&gt;

&lt;p&gt;The problem is the interaction between:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;file granularity + physical format + partitioning + metadata management + analytical workload&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is the architectural boundary that matters.&lt;/p&gt;




&lt;h2&gt;
  
  
  17. Conclusion
&lt;/h2&gt;

&lt;p&gt;The problem this article describes was never really that the data lake contained too much data.&lt;/p&gt;

&lt;p&gt;It was that the data had been physically organized around the needs of the producer — low-latency, continuous, durable writes — rather than the needs of the analytical consumer, which wants the opposite:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;fewer, larger, well-described files it can plan around efficiently.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The fix is not:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;"Use Iceberg."&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It's the deliberate combination of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;appropriate ingestion behavior,&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;an appropriate physical file format,&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;sensible file sizing tuned to the actual workload,&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;a compaction process that runs on a cadence matched to real accumulation and freshness needs,&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;and a metadata-aware table management layer introduced when — and only when — the dataset's lifecycle actually justifies it.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Design the way data arrives for ingestion.&lt;/p&gt;

&lt;p&gt;Design the way data is stored for analytics.&lt;/p&gt;

&lt;p&gt;And reach for a table format when the lifecycle of that analytical data genuinely demands one — not because it's the technology attached to the fastest-sounding benchmark number you've seen.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The goal isn't to make the data lake look modern. The goal is to make the physical data layout fit the workload.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://docs.aws.amazon.com/redshift/latest/dg/c-spectrum-external-performance.html" rel="noopener noreferrer"&gt;Amazon Redshift Spectrum query performance&lt;/a&gt; — AWS Documentation&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://docs.aws.amazon.com/redshift/latest/dg/c-spectrum-data-files.html" rel="noopener noreferrer"&gt;Data files for queries in Amazon Redshift Spectrum&lt;/a&gt; — AWS Documentation&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://docs.aws.amazon.com/redshift/latest/dg/querying-iceberg.html" rel="noopener noreferrer"&gt;Using Apache Iceberg tables with Amazon Redshift&lt;/a&gt; — AWS Documentation&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://aws.amazon.com/blogs/big-data/accelerate-query-performance-with-apache-iceberg-statistics-on-the-aws-glue-data-catalog/" rel="noopener noreferrer"&gt;Accelerate query performance with Apache Iceberg statistics on the AWS Glue Data Catalog&lt;/a&gt; — AWS Big Data Blog&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://aws.amazon.com/blogs/big-data/introducing-aws-glue-data-catalog-automation-for-table-statistics-collection-for-improved-query-performance-on-amazon-redshift-and-amazon-athena/" rel="noopener noreferrer"&gt;Introducing AWS Glue Data Catalog automation for table statistics collection&lt;/a&gt; — AWS Big Data Blog&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://aws.amazon.com/blogs/big-data/apache-iceberg-optimization-solving-the-small-files-problem-in-amazon-emr/" rel="noopener noreferrer"&gt;Apache Iceberg optimization: Solving the small files problem in Amazon EMR&lt;/a&gt; — AWS Big Data Blog&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://docs.aws.amazon.com/prescriptive-guidance/latest/apache-iceberg-on-aws/best-practices-read.html" rel="noopener noreferrer"&gt;Optimizing read performance&lt;/a&gt; — AWS Prescriptive Guidance&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://docs.aws.amazon.com/prescriptive-guidance/latest/apache-iceberg-on-aws/best-practices-workloads.html" rel="noopener noreferrer"&gt;Using Iceberg workloads in Amazon S3&lt;/a&gt; — AWS Prescriptive Guidance&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://docs.aws.amazon.com/prescriptive-guidance/latest/query-best-practices-redshift/best-practices-redshift-spectrum.html" rel="noopener noreferrer"&gt;Best practices for using Amazon Redshift Spectrum&lt;/a&gt; — AWS Prescriptive Guidance&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://aws.amazon.com/blogs/big-data/10-best-practices-for-amazon-redshift-spectrum/" rel="noopener noreferrer"&gt;10 Best Practices for Amazon Redshift Spectrum&lt;/a&gt; — AWS Big Data Blog&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://iceberg.apache.org/spec/" rel="noopener noreferrer"&gt;Apache Iceberg Table Specification&lt;/a&gt; — Apache Iceberg&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;




&lt;p&gt;This article was originally published on my blog: &lt;a href="https://aniruddhawrites.github.io/2026-08-27-when-fast-ingestion-creates-slow-analytics/" rel="noopener noreferrer"&gt;https://aniruddhawrites.github.io/2026-08-27-when-fast-ingestion-creates-slow-analytics/&lt;/a&gt; and is republished here for Dev.to readers.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>dataengineering</category>
      <category>icebarg</category>
      <category>redshift</category>
    </item>
    <item>
      <title>Independence Is Not an Event. It Is a Capability.</title>
      <dc:creator>Aniruddha Writes</dc:creator>
      <pubDate>Sat, 15 Aug 2026 08:47:01 +0000</pubDate>
      <link>https://dev.to/aniruddhawrites/independence-is-not-an-event-it-is-a-capability-3acf</link>
      <guid>https://dev.to/aniruddhawrites/independence-is-not-an-event-it-is-a-capability-3acf</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fob5b04ede4f532izwiu5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fob5b04ede4f532izwiu5.png" alt="India's journey from independence to Digital India shows that freedom is more than a historic event. It is a capability built through institutions, infrastructure, digital trust, and continuous innovation." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Editor’s Introduction
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;Every August, we celebrate the moment India became independent.&lt;/p&gt;

&lt;p&gt;But history suggests something deeper.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  11. Author's Note
&lt;/h2&gt;

&lt;p&gt;This article is a personal reflection on India's journey from political independence to digital capability. The views expressed are my own and are intended to explore the relationship between nation-building, technology, public infrastructure, and the responsibilities of the AI era.&lt;/p&gt;

&lt;p&gt;References to public digital platforms such as Aadhaar, UPI, DigiLocker, FASTag, and CoWIN are used as examples of capability-building at scale. The article is not a policy analysis, political commentary, or endorsement of any institution. It is an invitation to reflect on how capabilities are built, sustained, and strengthened across generations.&lt;/p&gt;




&lt;h2&gt;
  
  
  From nation-building to digital public infrastructure, India's journey reminds us that true independence is sustained through capabilities built and strengthened across generations.
&lt;/h2&gt;

&lt;p&gt;Independence is rarely secured in a day. It is built through capabilities that take decades to create and generations to sustain.&lt;/p&gt;

&lt;p&gt;There is a photograph most Indians have seen, even if they weren't alive to witness it: a flag rising over Red Fort as midnight becomes morning. It is one of the most reproduced images in the country's visual memory, and it captures, with total accuracy, a single instant. What it cannot capture is everything that instant required to mean something.&lt;/p&gt;

&lt;p&gt;1947 gave India the right to govern itself. It did not give India the roads to govern, the schools to educate the governed, the courts to adjudicate disputes among them, or the institutions to convert a subcontinent of extraordinary diversity into a functioning republic. That took decades. It is still taking decades. Independence, it turns out, is not the moment a nation is handed the keys. It is everything that happens afterward to make the keys worth having.&lt;/p&gt;

&lt;p&gt;This distinction — between the granting of independence and the building of it — is easy to state and hard to feel, because ceremonies are vivid and capability-building is slow. A flag-raising happens in a morning. An institution takes a generation. We remember the morning. We rarely notice the generation, because nothing about it looks like a single decisive event. It looks like thousands of unremarkable decisions, compounding.&lt;/p&gt;




&lt;h2&gt;
  
  
  The quiet decades
&lt;/h2&gt;

&lt;p&gt;Consider what had to happen between 1947 and any meaningful sense of an Indian citizen exercising self-determination in daily life. Universal adult suffrage had to be organized in a country where the majority of voters had never cast a ballot and much of the electorate could not read the ballot paper. A currency, a civil service, and a federal structure had to be built for a territory larger than most continents' worth of separate nations, each with its own language and history. Steel plants, dams, and agricultural research had to close the distance between a population and the food required to feed it.&lt;/p&gt;

&lt;p&gt;None of this made headlines the way independence itself did, because none of it was an event. It was a capability under construction — visible only in retrospect, and only in aggregate. A single dam commissioned in 1960 did not feel like sovereignty. A few hundred of them, alongside a green revolution, a literacy campaign, and a banking system extended into villages that had never seen a bank branch, eventually did. Sovereignty, it turns out, is rarely felt in the moment it is built. It is felt only once enough of it has accumulated to become invisible.&lt;/p&gt;

&lt;p&gt;This is worth sitting with, because the same pattern is now repeating in a domain that would have been unimaginable to the generation that built those dams: the digital one.&lt;/p&gt;




&lt;h2&gt;
  
  
  A second infrastructure
&lt;/h2&gt;

&lt;p&gt;Over the past decade, India has built something that rarely gets described with the gravity it deserves — not because it lacks importance, but because it doesn't look like infrastructure. It has no concrete, no steel, no ribbon to cut in front of cameras. It is a layer of shared digital rails, built by different agencies for different purposes — identity, payments, documents, mobility, public health — that together now function as one thing: a way for a citizen to be recognized, trusted, and served by systems that have never met them in person.&lt;/p&gt;

&lt;p&gt;Aadhaar, UPI, DigiLocker, FASTag, and CoWIN are, individually, products. Collectively, they are closer to what economists and technologists have started calling public digital infrastructure — shared, foundational systems that a private company, a government office, or an ordinary citizen can all build on top of, the way earlier generations built on top of roads and electricity. None of them earned their significance on launch day. Each was, for a time, simply an idea a ministry had funded and a small number of early users had tried. What made them consequential was not the announcement but the slow, cumulative process of becoming load-bearing — the moment a fruit vendor, a migrant worker, a retired teacher, and a college student each independently decided to trust the same system with something real.&lt;/p&gt;

&lt;p&gt;That moment is worth naming precisely, because it is the hinge on which this entire idea turns. A system becomes a capability at the exact moment people stop thinking about it as a system and start relying on it the way they rely on electricity — noticing it only when it's absent. A currency is not sovereign because a central bank prints it; it is sovereign because people accept it in exchange for their labor. A digital identity is not a capability because it exists in a database; it is a capability the day a citizen with no other paperwork can use it to open a bank account. Launch is permission. Adoption, sustained across millions of ordinary, unrecorded decisions to trust it again tomorrow, is proof.&lt;/p&gt;




&lt;h2&gt;
  
  
  The harder half of the work
&lt;/h2&gt;

&lt;p&gt;What is easy to overlook, amid all of this, is that building a capability and sustaining one are not the same skill, and the second is by far the less glamorous of the two. A dam can be commissioned once. It has to be maintained forever. A digital identity system can be launched once. It has to remain worthy of trust every single day after, through every new vulnerability, every policy change, every edge case that its designers never imagined. Trust of this kind is accumulated in decades and can be spent in an afternoon — which is precisely why it is not a feature to be shipped but a discipline to be practiced indefinitely. Every generation, in this sense, inherits infrastructure it did not build and did not fully choose, and its real inheritance is not the system itself but the obligation to keep it worth inheriting. The true measure of any capability, digital or institutional, is not how impressive it looked when it was new, but whether the generation that received it chose to strengthen it rather than simply spend it down.&lt;/p&gt;

&lt;p&gt;It is this obligation — not the technology itself — that connects the India of 1947 to the India now approaching a new technological threshold. The dam-builders and census-takers of the mid-twentieth century were not laying the groundwork for UPI specifically; they had no way to imagine it. What they were building was a habit: the expectation that each generation would extend what it inherited rather than merely use it up. Digital public infrastructure is that habit's most recent expression. Artificial intelligence will be its next test.&lt;/p&gt;




&lt;h2&gt;
  
  
  What the next chapter asks for
&lt;/h2&gt;

&lt;p&gt;If public digital infrastructure was the last decade's capability-building project, the next one is already visible on the horizon, and it will not forgive complacency about what was already achieved.&lt;/p&gt;

&lt;p&gt;Artificial intelligence built on top of Aadhaar-scale identity and UPI-scale transaction data carries a different order of consequence than either system carried alone. Readiness for that shift is not primarily a question of model access or computing capacity — it is a question of whether the country's data governance, consent frameworks, and grievance-redressal mechanisms are as mature as the infrastructure they will sit on top of. Digital literacy has to extend beyond the ability to scan a QR code toward the ability to reason about what a system built on one's own data is doing. Cybersecurity has to keep pace with the fact that infrastructure this widely relied upon is now, by definition, high-value infrastructure for anyone who wishes it harm. And inclusion has to remain a design constraint, not an afterthought — because a capability that works brilliantly for the digitally fluent and poorly for everyone else is not, in the fullest sense, a national capability at all.&lt;/p&gt;

&lt;p&gt;None of this is a criticism of what has been built. It is simply what capability-building has always demanded: that today's achievement be treated as tomorrow's foundation rather than tomorrow's finish line. The systems that got India this far were themselves built by people who inherited an earlier, incomplete independence and refused to treat it as complete.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkqdkx9ixuq4pvb0zbkxn.png%2520align%3D" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkqdkx9ixuq4pvb0zbkxn.png%2520align%3D" alt="The Lineage No One Had Mapped" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;India's journey from political independence to the AI era can be viewed as a continuous process of capability-building. The foundation laid after 1947 through institutions, infrastructure, education, and governance created the conditions for a new layer of digital public infrastructure. Systems such as Aadhaar, UPI, DigiLocker, and CoWIN became meaningful not simply because they were launched, but because they evolved into trusted capabilities used at national scale. The next stage is not just about adopting AI, but about building the trust, literacy, governance, security, and responsible innovation needed to sustain that capability.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Returning to the thesis
&lt;/h2&gt;

&lt;p&gt;It is tempting, every August, to let the story end at the flag. The story is more interesting, and more useful, if it doesn't. What 1947 actually started was not a condition but a project — one carried forward by dam engineers and census-takers, by the architects of a banking system that reached villages, by the teams who built an identity and payments layer relied upon by a scale of citizens no single generation could have imagined serving, and, soon enough, by whoever builds responsibly on top of what those systems have made possible.&lt;/p&gt;

&lt;p&gt;Independence is not an event.&lt;/p&gt;

&lt;p&gt;It is a capability — built once, and then rebuilt, generation after generation, by whoever is willing to treat the work as unfinished.&lt;/p&gt;




&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Government &amp;amp; Public Infrastructure
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.npci.org.in/" rel="noopener noreferrer"&gt;National Payments Corporation of India (NPCI)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://uidai.gov.in/" rel="noopener noreferrer"&gt;Aadhaar (UIDAI)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.digilocker.gov.in/" rel="noopener noreferrer"&gt;DigiLocker&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.netc.org.in/" rel="noopener noreferrer"&gt;FASTag&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.cowin.gov.in/home" rel="noopener noreferrer"&gt;CoWIN&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Additional Reading
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://indiastack.org/" rel="noopener noreferrer"&gt;India Stack&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.digitalindia.gov.in/" rel="noopener noreferrer"&gt;Digital Public Infrastructure initiatives&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://data.rbi.org.in/DBIE/" rel="noopener noreferrer"&gt;RBI Digital Payments Reports&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://meity.gov.in/" rel="noopener noreferrer"&gt;Ministry of Electronics and Information Technology (MeitY)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  16. About the Author
&lt;/h2&gt;

&lt;p&gt;Aniruddha Bhattacharya is a Data Platform Architect, BI and Analytics leader, and technology writer with nearly two decades of experience delivering enterprise data and analytics solutions across global organizations.&lt;/p&gt;

&lt;p&gt;His writing explores the intersection of data, AI, digital transformation, architecture, and technology leadership. Through Aniruddha Writes, he shares practical insights, long-form essays, and reflections on how technology shapes organizations, industries, and society.&lt;/p&gt;

&lt;p&gt;When not working with data platforms and analytics ecosystems, he enjoys writing on technology, innovation, and the evolving relationship between people and digital systems.&lt;/p&gt;

&lt;h1&gt;
  
  
  IndependenceDay #DigitalIndia #UPI #Aadhaar #DigiLocker #CoWIN #PublicDigitalInfrastructure #AI #AIGovernance #TechnologyLeadership #NationBuilding #Innovation #DigitalTransformation #India #CapabilityBuilding
&lt;/h1&gt;

</description>
      <category>ai</category>
      <category>nationbuilding</category>
      <category>independenceday</category>
      <category>digitalindia</category>
    </item>
    <item>
      <title>The DISTKEY That Was Right, and Still Wrong</title>
      <dc:creator>Aniruddha Writes</dc:creator>
      <pubDate>Thu, 06 Aug 2026 19:50:01 +0000</pubDate>
      <link>https://dev.to/aniruddhawrites/the-distkey-that-was-right-and-still-wrong-330j</link>
      <guid>https://dev.to/aniruddhawrites/the-distkey-that-was-right-and-still-wrong-330j</guid>
      <description>

&lt;h2&gt;
  
  
  Editor's Introduction
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;Enterprise data platforms rarely fail at the layer where the problem was created.&lt;/p&gt;

&lt;p&gt;More often, a modeling decision made months or years earlier quietly propagates through ingestion layers, work tables, conformed dimensions, materialized views, semantic models, and operational reporting systems until a seemingly unrelated incident finally exposes it.&lt;/p&gt;

&lt;p&gt;This case study examines one such investigation in Amazon Redshift, where a performance optimization that was technically correct revealed deeper challenges involving shared dimensions, physical design, lineage visibility, and downstream dependencies.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  The Morning Nothing Was Supposed to Be Wrong
&lt;/h2&gt;

&lt;p&gt;Eight-oh-four on a Tuesday. Three messages hit the team channel inside sixty seconds, from three people who hadn't talked to each other yet. That's the tell, more than the message itself — when three different people notice the same thing independently before anyone's had a chance to compare notes, you're not dealing with someone's misconfigured laptop.&lt;/p&gt;

&lt;p&gt;The exec sales dashboard — store performance, category rollups, day-over-day, built to be ready before the 8:15 leadership call — had been loading in six seconds for months. That morning it sat spinning somewhere north of ninety.&lt;/p&gt;

&lt;p&gt;Nothing had crashed. That's worth saying plainly, because it's the whole shape of the problem. No node down, no failed job, no red anything. I pulled the cluster health view at 8:06 and it looked like a Tuesday.&lt;/p&gt;

&lt;p&gt;An outage hands you a starting point — a stack trace, a failed health check, something that's already screaming at you. This wasn't that. This was a system that was healthy by every metric anyone had thought to alert on, and useless for the one thing that mattered that morning. Nobody had written an alert for "correct answer, too slow to matter," because nobody writes those until they've been burned by one.&lt;/p&gt;

&lt;p&gt;First move, before touching anything: figure out what kind of slow this is. Compute-starved, contending for something, or just doing more work than it needs to. From the outside these look identical. They are not the same fix.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;stl_query&lt;/code&gt; confirmed what the frozen screen already told us — elapsed time up roughly fifteenfold against the thirty-day baseline. More useful was what hadn't moved. &lt;code&gt;svv_table_info&lt;/code&gt; showed &lt;code&gt;fact_sales&lt;/code&gt; at 3% unsorted, same as a month prior — no sort-key decay. &lt;code&gt;skew_rows&lt;/code&gt; sat near 1.0 across the tables in the join path — nothing lopsided. Cluster CPU was up a few points, nowhere near saturated. Concurrency Scaling hadn't fired once. &lt;code&gt;stl_alert_event_log&lt;/code&gt; had nothing for this query in the prior 24 hours.&lt;/p&gt;

&lt;p&gt;That's three plausible stories ruled out inside twenty minutes — not decayed, not skewed, not starved. It's tempting to read that as "we're close." I'd push back on that instinct. Ruling something out narrows the search. It doesn't tell you where the answer is. Confusing the two is how you end up an hour later, confidently wrong about something adjacent to the truth.&lt;/p&gt;

&lt;p&gt;One more thing before we go further, because it changes how you read everything that follows: this dashboard sits in Power BI on a scheduled Import refresh, not DirectQuery. That matters. "The dashboard was frozen" and "the query was slow" are two different claims, and conflating them is how you waste an hour chasing a gateway timeout that was never the problem. We checked — the refresh history showed the query itself, not the connector, eating the ninety-plus seconds. Worth the thirty seconds it took to confirm, because if we'd guessed wrong here the whole morning goes sideways.&lt;/p&gt;

&lt;p&gt;The other belief worth naming and rejecting outright: that throwing more nodes at it is a safe, reversible first move while you think. It isn't neutral. It costs money, it needs a window, and if it happens to shave a few seconds off — which it usually does, a little, for reasons that have nothing to do with your actual problem — it will convince the room the theory behind it was right. It wasn't tested. It was coincidence wearing a lab coat. We didn't resize. Worth saying, because the temptation was there, and in a lot of rooms it wins.&lt;/p&gt;

&lt;p&gt;By nine we had a narrowed problem, a clean baseline, and genuinely no cause yet. What happened next looked, at the time, like the textbook move — and it was, technically, correct. That's exactly what makes it worth telling in detail.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Fix That Was Right on Paper
&lt;/h2&gt;

&lt;p&gt;By nine-fifteen someone had the EXPLAIN plan up, and you could feel the room relax the way rooms do when something finally has a name. There it was — &lt;code&gt;XN Hash Join DS_DIST_BOTH&lt;/code&gt; between &lt;code&gt;fact_sales&lt;/code&gt; and &lt;code&gt;dim_store&lt;/code&gt;. Not buried. First page of any Redshift tuning guide, plain as day: both sides of the join getting redistributed across every slice, on every execution.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;svl_query_summary&lt;/code&gt; backed it up — the join step alone was moving roughly 40M rows across the network per execution, accounting for the overwhelming majority of the query's runtime. Not a guess. A number, sitting in a system table, that told you exactly where the ninety seconds were going.&lt;/p&gt;

&lt;p&gt;This wasn't a routine change to a random table, so it didn't go through routine channels — &lt;code&gt;dim_store&lt;/code&gt; sits on the shared conformed layer, which around here means anything touching it goes through an emergency-change path with a rollback script attached, not the normal weekly release window. That's the only reason "afternoon window, same day" is a true sentence and not a red flag. Worth saying explicitly, because doing this on a shared dimension without that path would be a different, worse story.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;dim_store&lt;/code&gt; is Type 1 — current state only, no history retained. That's not incidental to this story; it's the reason &lt;code&gt;store_id&lt;/code&gt; is even a clean, unambiguous grain to distribute on. Had it been Type 2, with a surrogate key and multiple versions per store, "distribute on &lt;code&gt;store_id&lt;/code&gt;" stops being obviously correct — you're now co-locating on a business key that isn't the actual row-level grain, and the join locality argument gets murkier. Worth checking before you touch the DDL, not after.&lt;/p&gt;

&lt;p&gt;The change went in: DISTSTYLE from EVEN to KEY on &lt;code&gt;store_id&lt;/code&gt;, matching &lt;code&gt;fact_sales&lt;/code&gt;. Ran the query again. Eleven seconds. &lt;code&gt;svl_query_summary&lt;/code&gt; on the re-run showed the redistribution step gone entirely — replaced by a local join, no network shuffle. The ticket closed with a note that read, roughly, "root cause identified and resolved."&lt;/p&gt;

&lt;p&gt;Here's where I want to slow down, because skating past this is how the lesson gets cheapened. The fix was not wrong. I need to be precise about that — it would be a much less useful story if it were. The join genuinely was redistributing both sides, every time. The change genuinely eliminated that. Anyone in that room could defend the decision an hour later with a system-table printout in hand.&lt;/p&gt;

&lt;p&gt;The problem was the size of the question everyone agreed to answer. The question that got asked: is this DISTKEY right for this join? The question that actually mattered: is this DISTKEY right for every join that touches this table? Those sound like the same question stated more broadly. They're not.&lt;/p&gt;

&lt;p&gt;The first one you can verify in an afternoon with &lt;code&gt;svl_query_summary&lt;/code&gt;. The second one requires knowing something that lived nowhere in the platform — who else depends on &lt;code&gt;dim_store&lt;/code&gt; — because nobody had ever built a bus matrix, and the catalog tooling we had covered pipeline lineage within a team's own domain, not cross-team consumption of shared dimensions.&lt;/p&gt;

&lt;p&gt;That's not a process the team forgot to follow. It's a capability the platform never had.&lt;/p&gt;

&lt;h3&gt;
  
  
  Diagram D2 — The Lineage No One Had Mapped
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fruddhanib.github.io%2Faniruddhawrites%2Fcontent%2Fimages%2Fd2.png%2520align%3D" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fruddhanib.github.io%2Faniruddhawrites%2Fcontent%2Fimages%2Fd2.png%2520align%3D" alt="The Lineage No One Had Mapped" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;  A shared conformed dimension can act as an enterprise hub. Without complete lineage visibility, a local optimization may unknowingly impact downstream consumers that were never evaluated. *&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;A quick word on scope, because the mechanics here are Redshift's, specifically — DISTKEY, slice placement, &lt;code&gt;DS_DIST_BOTH&lt;/code&gt;. On Snowflake this conversation doesn't happen the same way; there's no distribution key to get wrong, clustering behaves on a completely different axis. On Fabric, you're reasoning about V-Order and file compaction, not which slice a row lives on.&lt;/p&gt;

&lt;p&gt;The lesson that survives the platform — a shared table has as many "correct" configurations as it has consumers, and optimizing for the loudest one is a trade, not a fix — that part travels. The mechanism doesn't.&lt;/p&gt;

&lt;p&gt;Ticket closed. Dashboard fast. Everyone went home Tuesday believing it was over.&lt;/p&gt;

&lt;p&gt;It took less than a week to learn it hadn't ended. It had moved. Two tickets landed forty-eight hours apart, filed by two teams who had no idea they were describing the same event.&lt;/p&gt;




&lt;h2&gt;
  
  
  Two Tickets, Same Week, No Shared Cause Line
&lt;/h2&gt;

&lt;p&gt;The first came from the store-traffic team Thursday afternoon.&lt;/p&gt;

&lt;p&gt;Their model — &lt;code&gt;fact_customer_traffic&lt;/code&gt;, feeding a staffing-optimization tool — had started throwing intermittent timeout errors on its late-afternoon refresh.&lt;/p&gt;

&lt;p&gt;Intermittent is the word that should slow you down.&lt;/p&gt;

&lt;p&gt;A deterministic failure is a bug.&lt;/p&gt;

&lt;p&gt;An intermittent one, on a schedule that hadn't changed, on a query that hadn't changed, on data volume that hadn't meaningfully grown — that's contention, and contention means something else is sharing the room.&lt;/p&gt;

&lt;p&gt;The second came Saturday morning, from the inventory team.&lt;/p&gt;

&lt;p&gt;Their overnight &lt;code&gt;fact_inventory&lt;/code&gt; load had blown past its four-hour SLA window twice in the same week, for the first time in over a year of stable history.&lt;/p&gt;

&lt;p&gt;Neither ticket mentioned &lt;code&gt;dim_store&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Neither team had touched anything of their own.&lt;/p&gt;

&lt;p&gt;That's precisely the trap: when the thing that broke isn't the thing you own, your first instinct is to assume the platform is having a bad week, not that someone else's Tuesday-afternoon fix reached into your pipeline uninvited.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A shared table doesn't announce who it's about to affect. It just changes, and waits for the tickets to arrive.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Nobody connected the tickets immediately.&lt;/p&gt;

&lt;p&gt;They were triaged separately.&lt;/p&gt;

&lt;p&gt;Different teams.&lt;/p&gt;

&lt;p&gt;Different symptoms.&lt;/p&gt;

&lt;p&gt;Different assumptions.&lt;/p&gt;

&lt;p&gt;What eventually connected them wasn't insight.&lt;/p&gt;

&lt;p&gt;It was discipline.&lt;/p&gt;

&lt;p&gt;Both query plans touched &lt;code&gt;dim_store&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That was enough reason to keep digging.&lt;/p&gt;

&lt;p&gt;The investigation stopped being about one dashboard.&lt;/p&gt;

&lt;p&gt;It became a question about the architecture itself.&lt;/p&gt;




&lt;h2&gt;
  
  
  Following the Lineage, Not the Symptom
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Environment at a Glance
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fruddhanib.github.io%2Faniruddhawrites%2Fcontent%2Fimagesimages%2Fdall.png%2520align%3D" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fruddhanib.github.io%2Faniruddhawrites%2Fcontent%2Fimagesimages%2Fdall.png%2520align%3D" alt="Environment at a Glance" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;At this point the investigation had to stop being reactive.&lt;/p&gt;

&lt;p&gt;Chasing each symptom individually would have produced three separate fixes on top of one shared cause.&lt;/p&gt;

&lt;p&gt;Instead, we traced the lineage.&lt;/p&gt;

&lt;p&gt;The platform looked like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;L1 — landing and standardization&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;WRK — transformation and business rules&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;DWH — conformed enterprise model&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Materialized Views — performance acceleration layer&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Data Marts and Semantic Models — consumer-facing analytics&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Diagram D3 — One Change, Three Symptoms
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fruddhanib.github.io%2Faniruddhawrites%2Fcontent%2Fimages%2Fd3.png%2520align%3D" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fruddhanib.github.io%2Faniruddhawrites%2Fcontent%2Fimages%2Fd3.png%2520align%3D" alt="One Change, Three Symptoms" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;  One physical design change produced four different outcomes. The original dashboard improved dramatically, while other consumers experienced entirely different performance symptoms through unrelated mechanisms. *&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;We traced each affected consumer through the stack.&lt;/p&gt;

&lt;p&gt;The traffic model showed memory spill.&lt;/p&gt;

&lt;p&gt;The inventory process showed I/O contention.&lt;/p&gt;

&lt;p&gt;The dashboard's materialized view showed silent fallback from incremental refresh to full recompute.&lt;/p&gt;

&lt;p&gt;Three symptoms.&lt;/p&gt;

&lt;p&gt;Three mechanisms.&lt;/p&gt;

&lt;p&gt;One upstream event.&lt;/p&gt;

&lt;p&gt;And for the first time, the investigation started moving away from the dashboard and toward the model itself.&lt;/p&gt;




&lt;h2&gt;
  
  
  What "Skewed" Actually Meant Here
&lt;/h2&gt;

&lt;p&gt;The traffic-model spill deserved its own look, because it's where the investigation nearly went sideways a second time.&lt;/p&gt;

&lt;p&gt;The instinct in the room, once &lt;code&gt;svv_table_info&lt;/code&gt; showed &lt;code&gt;skew_rows&lt;/code&gt; climbing on &lt;code&gt;fact_sales&lt;/code&gt; and &lt;code&gt;fact_customer_traffic&lt;/code&gt; post-change, was to run &lt;code&gt;VACUUM&lt;/code&gt; and see if it helped.&lt;/p&gt;

&lt;p&gt;It's worth stating precisely why that instinct is wrong, because it's one of the most common misdiagnoses in Redshift work, and it nearly cost us a maintenance window we didn't need to spend.&lt;/p&gt;

&lt;p&gt;VACUUM reclaims deleted-row space and re-sorts rows within a slice's existing boundaries.&lt;/p&gt;

&lt;p&gt;It does not move rows between slices.&lt;/p&gt;

&lt;p&gt;Distribution skew — an uneven number of rows landing on different slices — is a placement problem, not a sort problem, and no amount of re-sorting within a slice changes how many rows that slice was assigned in the first place.&lt;/p&gt;

&lt;p&gt;We ran the numbers before running the command.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;skew_rows&lt;/code&gt; for &lt;code&gt;fact_sales&lt;/code&gt; sat at roughly 2.3, meaning the busiest slice was holding well over double the rows of the least busy one.&lt;/p&gt;

&lt;p&gt;VACUUM was never going to touch that number.&lt;/p&gt;

&lt;p&gt;The actual cause was structural, and it was sitting in &lt;code&gt;dim_store&lt;/code&gt; all along.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;store_id&lt;/code&gt; was not an evenly distributed key.&lt;/p&gt;

&lt;p&gt;A handful of values — flagship stores and aggregated online channels — accounted for a disproportionate share of transaction volume across the platform.&lt;/p&gt;

&lt;p&gt;Co-locating fact tables on &lt;code&gt;store_id&lt;/code&gt; didn't just eliminate redistribution.&lt;/p&gt;

&lt;p&gt;It concentrated the heaviest-volume rows onto the same physical slices.&lt;/p&gt;

&lt;p&gt;For every fact table.&lt;/p&gt;

&lt;p&gt;At the same time.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The fix hadn't introduced a new problem so much as it had made an old, dormant one load-bearing for the first time.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That distinction mattered.&lt;/p&gt;

&lt;p&gt;The skew wasn't created by the DISTKEY change.&lt;/p&gt;

&lt;p&gt;The skew already existed in the business.&lt;/p&gt;

&lt;p&gt;The DISTKEY change simply exposed it.&lt;/p&gt;

&lt;p&gt;Under EVEN distribution, Redshift ignored business meaning and spread rows mechanically.&lt;/p&gt;

&lt;p&gt;Under KEY distribution, Redshift inherited the real-world imbalance embedded in the business key.&lt;/p&gt;

&lt;p&gt;The platform's physical layout became a reflection of the business itself.&lt;/p&gt;

&lt;p&gt;That's where the investigation changed direction.&lt;/p&gt;

&lt;p&gt;It stopped being about distribution keys.&lt;/p&gt;

&lt;p&gt;It started being about modeling decisions.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Grain Problem Underneath the Distribution Problem
&lt;/h2&gt;

&lt;p&gt;The question that actually needed answering wasn't:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What distribution style fixes this?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The question was:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Why does a single value in &lt;code&gt;store_id&lt;/code&gt; carry forty times the transaction volume of a normal store?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The answer wasn't hiding in a query plan.&lt;/p&gt;

&lt;p&gt;It was hiding in the data model.&lt;/p&gt;

&lt;p&gt;When we traced the lineage all the way back to the L1 and WRK layers, we found something that had existed for years.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;dim_store&lt;/code&gt; wasn't modeling a single business concept.&lt;/p&gt;

&lt;p&gt;It was modeling two.&lt;/p&gt;

&lt;p&gt;Physical retail locations.&lt;/p&gt;

&lt;p&gt;And aggregated digital sales channels.&lt;/p&gt;

&lt;p&gt;Online sales.&lt;/p&gt;

&lt;p&gt;Marketplace integrations.&lt;/p&gt;

&lt;p&gt;Digital storefronts.&lt;/p&gt;

&lt;p&gt;All represented as single synthetic "store" rows.&lt;/p&gt;

&lt;p&gt;From a reporting perspective, the design was elegant.&lt;/p&gt;

&lt;p&gt;Every downstream report could treat online and offline channels uniformly.&lt;/p&gt;

&lt;p&gt;No branching logic.&lt;/p&gt;

&lt;p&gt;No special-case joins.&lt;/p&gt;

&lt;p&gt;No additional dimensions.&lt;/p&gt;

&lt;p&gt;But that convenience came with a hidden cost.&lt;/p&gt;

&lt;p&gt;One synthetic row represented a volume of transactions that dwarfed every physical location.&lt;/p&gt;

&lt;p&gt;As digital sales grew, that imbalance grew with them.&lt;/p&gt;

&lt;p&gt;The model encoded business skew directly into the dimension grain.&lt;/p&gt;

&lt;p&gt;No DISTKEY could fix that.&lt;/p&gt;

&lt;p&gt;No SORTKEY could fix that.&lt;/p&gt;

&lt;p&gt;No VACUUM could fix that.&lt;/p&gt;

&lt;p&gt;You can redistribute rows.&lt;/p&gt;

&lt;p&gt;You cannot redistribute the business meaning behind a row.&lt;/p&gt;

&lt;p&gt;If one value legitimately represents thirty-five percent of enterprise transactions, every physical design strategy must eventually deal with that reality.&lt;/p&gt;

&lt;p&gt;EVEN distribution hides it.&lt;/p&gt;

&lt;p&gt;KEY distribution exposes it.&lt;/p&gt;

&lt;p&gt;Neither removes it.&lt;/p&gt;

&lt;p&gt;That's the thesis of the incident.&lt;/p&gt;

&lt;p&gt;The bottleneck wasn't born in the dashboard.&lt;/p&gt;

&lt;p&gt;It wasn't born in the materialized view.&lt;/p&gt;

&lt;p&gt;It wasn't born in the DWH layer.&lt;/p&gt;

&lt;p&gt;It originated years earlier when a modeling decision defined what a "store" meant.&lt;/p&gt;

&lt;p&gt;Everything downstream was merely experiencing the consequences.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why We Couldn't Just Fix dim_store
&lt;/h2&gt;

&lt;p&gt;The obvious solution seemed straightforward.&lt;/p&gt;

&lt;p&gt;Split the grain.&lt;/p&gt;

&lt;p&gt;Separate physical stores from digital channels.&lt;/p&gt;

&lt;p&gt;Give each concept its own dimension.&lt;/p&gt;

&lt;p&gt;Remove the concentration at the source.&lt;/p&gt;

&lt;p&gt;Technically, that would have been the cleanest solution.&lt;/p&gt;

&lt;p&gt;Operationally, it was impossible.&lt;/p&gt;

&lt;p&gt;At least not immediately.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;dim_store&lt;/code&gt; wasn't an internal implementation detail.&lt;/p&gt;

&lt;p&gt;It was a published enterprise contract.&lt;/p&gt;

&lt;p&gt;Power BI semantic models depended on it.&lt;/p&gt;

&lt;p&gt;Scheduled ETL jobs depended on it.&lt;/p&gt;

&lt;p&gt;Inventory systems depended on it.&lt;/p&gt;

&lt;p&gt;Forecasting applications depended on it.&lt;/p&gt;

&lt;p&gt;External vendor integrations depended on it.&lt;/p&gt;

&lt;p&gt;Changing the grain would have meant coordinating dozens of downstream consumers.&lt;/p&gt;

&lt;p&gt;Some owned by other teams.&lt;/p&gt;

&lt;p&gt;Some owned by vendors.&lt;/p&gt;

&lt;p&gt;Some with release schedules measured in months rather than days.&lt;/p&gt;

&lt;p&gt;The issue wasn't technical complexity.&lt;/p&gt;

&lt;p&gt;The issue was dependency complexity.&lt;/p&gt;

&lt;p&gt;This is one of the most important lessons in enterprise architecture.&lt;/p&gt;

&lt;p&gt;The technically correct solution and the shippable solution are often different solutions.&lt;/p&gt;

&lt;p&gt;Interfaces become expensive precisely because they succeed.&lt;/p&gt;

&lt;p&gt;The more consumers depend on them, the harder they become to change.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;dim_store&lt;/code&gt; had become successful enough to be dangerous.&lt;/p&gt;

&lt;p&gt;Changing it wasn't a database change.&lt;/p&gt;

&lt;p&gt;It was an organizational change.&lt;/p&gt;

&lt;p&gt;And organizational changes move slower than production incidents.&lt;/p&gt;

&lt;p&gt;So the redesign had to happen somewhere else.&lt;/p&gt;

&lt;p&gt;Somewhere invisible to consumers.&lt;/p&gt;

&lt;p&gt;Somewhere inside the platform.&lt;/p&gt;




&lt;h2&gt;
  
  
  Redesigning the Work Layer, Not the Interface
&lt;/h2&gt;

&lt;p&gt;The eventual solution came from separating two concepts that had accidentally become linked.&lt;/p&gt;

&lt;p&gt;Business grain.&lt;/p&gt;

&lt;p&gt;And physical distribution.&lt;/p&gt;

&lt;p&gt;Consumers cared about the business grain.&lt;/p&gt;

&lt;p&gt;Redshift cared about physical distribution.&lt;/p&gt;

&lt;p&gt;There was no requirement that they be represented by the same key.&lt;/p&gt;

&lt;p&gt;The redesign happened entirely within the WRK layer.&lt;/p&gt;

&lt;p&gt;No downstream consumer saw it.&lt;/p&gt;

&lt;p&gt;No contract changed.&lt;/p&gt;

&lt;p&gt;No semantic model needed modification.&lt;/p&gt;

&lt;p&gt;No vendor needed notification.&lt;/p&gt;

&lt;p&gt;For the handful of high-volume digital-channel records responsible for the skew, we introduced a derived distribution bucket.&lt;/p&gt;

&lt;p&gt;Instead of distributing those records solely by &lt;code&gt;store_id&lt;/code&gt;, we computed a synthetic distribution value using a secondary attribute and a hashing function.&lt;/p&gt;

&lt;p&gt;The objective wasn't to change business meaning.&lt;/p&gt;

&lt;p&gt;The objective was to spread physical placement across slices.&lt;/p&gt;

&lt;p&gt;Ordinary stores continued using their natural key.&lt;/p&gt;

&lt;p&gt;Only the concentrated digital-channel records were salted.&lt;/p&gt;

&lt;p&gt;The salting existed purely for distribution.&lt;/p&gt;

&lt;p&gt;Not for reporting.&lt;/p&gt;

&lt;p&gt;Not for analytics.&lt;/p&gt;

&lt;p&gt;Not for business logic.&lt;/p&gt;

&lt;h3&gt;
  
  
  Diagram D4 — WRK Layer Redesign: Salting Without Breaking the Contract
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fruddhanib.github.io%2Faniruddhawrites%2Fcontent%2Fimages%2Fd4.png%2520align%3D" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fruddhanib.github.io%2Faniruddhawrites%2Fcontent%2Fimages%2Fd4.png%2520align%3D" alt="WRK Layer Redesign: Salting Without Breaking the Contract" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Salting was introduced only within the WRK layer to improve physical distribution. The business-facing grain and consumer contracts remained unchanged after reconciliation in DWH.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The second part of the solution was equally important.&lt;/p&gt;

&lt;p&gt;Before anything crossed into the DWH layer, the salted buckets were re-aggregated back to the original business grain.&lt;/p&gt;

&lt;p&gt;Consumers never saw the buckets.&lt;/p&gt;

&lt;p&gt;Consumers never saw the salting.&lt;/p&gt;

&lt;p&gt;Consumers continued seeing the same &lt;code&gt;store_id&lt;/code&gt; values they always had.&lt;/p&gt;

&lt;p&gt;The contract remained unchanged.&lt;/p&gt;

&lt;p&gt;The optimization remained internal.&lt;/p&gt;

&lt;p&gt;One more thing worth flagging.&lt;/p&gt;

&lt;p&gt;The reconciliation process required rebuilding portions of the downstream structures.&lt;/p&gt;

&lt;p&gt;Whenever a large rebuild occurs, SORTKEY effectiveness should be revalidated rather than assumed.&lt;/p&gt;

&lt;p&gt;Eliminating redistribution only to lose scan pruning would have been a poor trade.&lt;/p&gt;

&lt;p&gt;We verified that the existing date-based SORTKEY strategy continued delivering the same zone-map pruning benefits after the redesign.&lt;/p&gt;

&lt;p&gt;Distribution improved.&lt;/p&gt;

&lt;p&gt;Scan efficiency remained intact.&lt;/p&gt;

&lt;p&gt;The redesign also included several supporting changes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Targeted &lt;code&gt;ANALYZE&lt;/code&gt; on frequently joined and filtered columns&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Explicit maintenance-window scheduling for future DISTKEY changes&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Ongoing monitoring of &lt;code&gt;skew_rows&lt;/code&gt; and &lt;code&gt;unsorted%&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Validation of materialized-view refresh behavior after physical redesign&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The goal wasn't simply to fix one incident.&lt;/p&gt;

&lt;p&gt;It was to create guardrails that would detect the next one sooner.&lt;/p&gt;




&lt;h2&gt;
  
  
  What We Rejected, and Why
&lt;/h2&gt;

&lt;p&gt;Several alternatives were considered.&lt;/p&gt;

&lt;p&gt;Some looked attractive.&lt;/p&gt;

&lt;p&gt;Some looked simpler.&lt;/p&gt;

&lt;p&gt;None addressed the actual problem.&lt;/p&gt;

&lt;h3&gt;
  
  
  Option 1: Revert &lt;code&gt;dim_store&lt;/code&gt; Back to EVEN Distribution
&lt;/h3&gt;

&lt;p&gt;This would have restored the original physical layout.&lt;/p&gt;

&lt;p&gt;It also would have restored the original dashboard problem.&lt;/p&gt;

&lt;p&gt;The Tuesday incident would return immediately.&lt;/p&gt;

&lt;p&gt;The ninety-second query would become normal again.&lt;/p&gt;

&lt;p&gt;Nothing would be learned.&lt;/p&gt;

&lt;p&gt;Nothing would be fixed.&lt;/p&gt;

&lt;p&gt;This option simply moved the pain back to its original owner.&lt;/p&gt;

&lt;p&gt;Rejected.&lt;/p&gt;

&lt;h3&gt;
  
  
  Option 2: DISTSTYLE ALL
&lt;/h3&gt;

&lt;p&gt;At first glance, this looked promising.&lt;/p&gt;

&lt;p&gt;Replicate &lt;code&gt;dim_store&lt;/code&gt; to every node.&lt;/p&gt;

&lt;p&gt;Eliminate redistribution entirely.&lt;/p&gt;

&lt;p&gt;Allow every consumer to perform local joins.&lt;/p&gt;

&lt;p&gt;Problem solved.&lt;/p&gt;

&lt;p&gt;Except it wasn't.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;dim_store&lt;/code&gt; wasn't static.&lt;/p&gt;

&lt;p&gt;It received daily updates.&lt;/p&gt;

&lt;p&gt;Type 1 changes.&lt;/p&gt;

&lt;p&gt;New channel additions.&lt;/p&gt;

&lt;p&gt;Attribute corrections.&lt;/p&gt;

&lt;p&gt;Store metadata updates.&lt;/p&gt;

&lt;p&gt;DISTSTYLE ALL makes every write more expensive because every node must maintain a copy.&lt;/p&gt;

&lt;p&gt;What appears inexpensive at today's scale often becomes technical debt at tomorrow's scale.&lt;/p&gt;

&lt;p&gt;A growing shared dimension replicated everywhere eventually becomes a write-side bottleneck.&lt;/p&gt;

&lt;p&gt;Rejected.&lt;/p&gt;

&lt;h3&gt;
  
  
  Option 3: Immediately Split the Grain
&lt;/h3&gt;

&lt;p&gt;From a modeling perspective, this was the cleanest solution.&lt;/p&gt;

&lt;p&gt;Separate physical stores.&lt;/p&gt;

&lt;p&gt;Separate digital channels.&lt;/p&gt;

&lt;p&gt;Eliminate the concentration.&lt;/p&gt;

&lt;p&gt;Create a more accurate business representation.&lt;/p&gt;

&lt;p&gt;Technically correct.&lt;/p&gt;

&lt;p&gt;Operationally unrealistic.&lt;/p&gt;

&lt;p&gt;Too many downstream consumers.&lt;/p&gt;

&lt;p&gt;Too many dependencies.&lt;/p&gt;

&lt;p&gt;Too many contracts.&lt;/p&gt;

&lt;p&gt;The cost of coordination exceeded the urgency of the incident.&lt;/p&gt;

&lt;p&gt;Rejected for now.&lt;/p&gt;

&lt;p&gt;Not rejected forever.&lt;/p&gt;

&lt;h3&gt;
  
  
  Option 4: Tune Each Consumer Individually
&lt;/h3&gt;

&lt;p&gt;This approach would have produced three separate fixes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;One for the dashboard&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;One for the traffic model&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;One for inventory processing&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The problem with symptom-based optimization is that it assumes you've already found every symptom.&lt;/p&gt;

&lt;p&gt;We hadn't.&lt;/p&gt;

&lt;p&gt;We couldn't.&lt;/p&gt;

&lt;p&gt;The actual number of consumers remained unknown.&lt;/p&gt;

&lt;p&gt;Fixing three visible problems while leaving the root cause intact simply guarantees a fourth ticket later.&lt;/p&gt;

&lt;p&gt;Rejected.&lt;/p&gt;

&lt;p&gt;The common theme across every rejected option was simple.&lt;/p&gt;

&lt;p&gt;They optimized symptoms.&lt;/p&gt;

&lt;p&gt;Not causes.&lt;/p&gt;

&lt;p&gt;The investigation had already proven where that approach leads.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Actually Changed, Measured Carefully
&lt;/h2&gt;

&lt;p&gt;Engineering articles often overstate outcomes.&lt;/p&gt;

&lt;p&gt;This one shouldn't.&lt;/p&gt;

&lt;p&gt;The results were meaningful.&lt;/p&gt;

&lt;p&gt;Not magical.&lt;/p&gt;

&lt;p&gt;The executive dashboard returned to single-digit response times.&lt;/p&gt;

&lt;p&gt;The original query stabilized near eleven seconds after the redesign.&lt;/p&gt;

&lt;p&gt;Materialized-view refreshes returned to incremental maintenance instead of full recomputation.&lt;/p&gt;

&lt;p&gt;The staffing model stopped experiencing intermittent timeout events.&lt;/p&gt;

&lt;p&gt;Across the observation period that followed, no new memory-spill events appeared in the relevant hash-join stages.&lt;/p&gt;

&lt;p&gt;The inventory pipeline returned to its SLA window.&lt;/p&gt;

&lt;p&gt;Overnight processing consistently completed within the operational threshold required by downstream warehouse operations.&lt;/p&gt;

&lt;p&gt;The skew metrics improved substantially.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;skew_rows&lt;/code&gt; decreased from approximately 2.3 to under 1.3.&lt;/p&gt;

&lt;p&gt;Not perfect.&lt;/p&gt;

&lt;p&gt;Better.&lt;/p&gt;

&lt;p&gt;Importantly, scan efficiency remained stable.&lt;/p&gt;

&lt;p&gt;Post-redesign analysis showed no meaningful increase in blocks scanned for the dashboard's date-filtered workloads.&lt;/p&gt;

&lt;p&gt;The skew reduction did not come at the expense of SORTKEY effectiveness or zone-map pruning.&lt;/p&gt;

&lt;p&gt;That validation mattered.&lt;/p&gt;

&lt;p&gt;Performance tuning often succeeds in one dimension while quietly regressing another.&lt;/p&gt;

&lt;p&gt;This time it didn't.&lt;/p&gt;

&lt;p&gt;The outcome most people remember is the faster dashboard.&lt;/p&gt;

&lt;p&gt;The outcome I remember is different.&lt;/p&gt;

&lt;p&gt;The platform became predictable again.&lt;/p&gt;

&lt;p&gt;Engineers stopped discovering hidden consequences several days after a change.&lt;/p&gt;

&lt;p&gt;That's harder to measure.&lt;/p&gt;

&lt;p&gt;And often more valuable.&lt;/p&gt;

&lt;h3&gt;
  
  
  Diagram D5 — Before vs After: Where the Skew Lives
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fruddhanib.github.io%2Faniruddhawrites%2Fcontent%2Fimages%2Fd5.png%2520align%3D" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fruddhanib.github.io%2Faniruddhawrites%2Fcontent%2Fimages%2Fd5.png%2520align%3D" alt="Before vs After: Where the Skew Lives" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The investigation succeeded because the team stopped treating the dashboard as the problem and followed the lineage upstream until reaching the original modeling decision that created the downstream symptoms.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  What's Still Unresolved
&lt;/h2&gt;

&lt;p&gt;Two things remain unresolved.&lt;/p&gt;

&lt;p&gt;Both matter.&lt;/p&gt;

&lt;p&gt;The first is the grain problem itself.&lt;/p&gt;

&lt;p&gt;Physical stores and digital channels still share the same published dimension.&lt;/p&gt;

&lt;p&gt;The salting strategy manages the consequences.&lt;/p&gt;

&lt;p&gt;It does not eliminate the underlying modeling decision.&lt;/p&gt;

&lt;p&gt;As online volume grows, the pressure on that design will grow with it.&lt;/p&gt;

&lt;p&gt;Eventually the organization may need to revisit the grain directly.&lt;/p&gt;

&lt;p&gt;The second issue is more concerning.&lt;/p&gt;

&lt;p&gt;The lineage gap still exists.&lt;/p&gt;

&lt;p&gt;The incident revealed it.&lt;/p&gt;

&lt;p&gt;The investigation worked around it.&lt;/p&gt;

&lt;p&gt;Nothing fundamentally removed it.&lt;/p&gt;

&lt;p&gt;We now know far more about &lt;code&gt;dim_store&lt;/code&gt; consumers than we did before.&lt;/p&gt;

&lt;p&gt;Mostly because we spent time manually discovering them.&lt;/p&gt;

&lt;p&gt;The next engineer facing a similar change would still need to perform much of that discovery themselves.&lt;/p&gt;

&lt;p&gt;The highest-priority follow-up is not another DISTKEY review.&lt;/p&gt;

&lt;p&gt;It is visibility.&lt;/p&gt;

&lt;p&gt;Shared assets require shared lineage.&lt;/p&gt;

&lt;p&gt;Without it, teams optimize locally and discover globally.&lt;/p&gt;

&lt;p&gt;That's exactly what happened here.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Lesson That Outlasted the Ticket
&lt;/h2&gt;

&lt;p&gt;Looking back, every individual decision made during the incident was defensible.&lt;/p&gt;

&lt;p&gt;Changing the DISTKEY was reasonable.&lt;/p&gt;

&lt;p&gt;Analyzing query plans was reasonable.&lt;/p&gt;

&lt;p&gt;Avoiding unnecessary VACUUM operations was reasonable.&lt;/p&gt;

&lt;p&gt;Rejecting DISTSTYLE ALL was reasonable.&lt;/p&gt;

&lt;p&gt;None of those decisions were mistakes.&lt;/p&gt;

&lt;p&gt;The mistake was assuming a shared enterprise object could be optimized in isolation.&lt;/p&gt;

&lt;p&gt;It couldn't.&lt;/p&gt;

&lt;p&gt;And it never could.&lt;/p&gt;

&lt;p&gt;The dashboard failure wasn't caused by the dashboard.&lt;/p&gt;

&lt;p&gt;The inventory delay wasn't caused by the inventory process.&lt;/p&gt;

&lt;p&gt;The staffing-model spill wasn't caused by the staffing team.&lt;/p&gt;

&lt;p&gt;Each symptom appeared in a different place.&lt;/p&gt;

&lt;p&gt;Each root cause pointed to the same place.&lt;/p&gt;

&lt;p&gt;A modeling decision made years earlier.&lt;/p&gt;

&lt;p&gt;One layer further upstream than anyone initially expected.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Downstream symptoms are where you notice a problem. They are rarely where the problem was made.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That's the lesson that survives beyond Amazon Redshift.&lt;/p&gt;

&lt;p&gt;The mechanism changes across platforms.&lt;/p&gt;

&lt;p&gt;Snowflake.&lt;/p&gt;

&lt;p&gt;Fabric.&lt;/p&gt;

&lt;p&gt;Databricks.&lt;/p&gt;

&lt;p&gt;BigQuery.&lt;/p&gt;

&lt;p&gt;The pattern remains remarkably consistent.&lt;/p&gt;

&lt;p&gt;A fix appears successful.&lt;/p&gt;

&lt;p&gt;A symptom disappears.&lt;/p&gt;

&lt;p&gt;A different symptom emerges elsewhere.&lt;/p&gt;

&lt;p&gt;The real challenge is resisting the urge to stop investigating when the first visible problem goes away.&lt;/p&gt;

&lt;p&gt;We didn't close this incident.&lt;/p&gt;

&lt;p&gt;We reduced its impact.&lt;/p&gt;

&lt;p&gt;We moved the cost to a layer where it could be managed deliberately.&lt;/p&gt;

&lt;p&gt;We documented the trade-offs openly.&lt;/p&gt;

&lt;p&gt;And we identified the work that still remains.&lt;/p&gt;

&lt;p&gt;That's not a lesser outcome than "solved."&lt;/p&gt;

&lt;p&gt;At enterprise scale, with shared models and long-lived contracts, it's usually the honest one.&lt;/p&gt;

&lt;p&gt;Somewhere in that same conformed layer, another perfectly reasonable design decision is sitting quietly.&lt;/p&gt;

&lt;p&gt;Waiting for an ordinary Tuesday morning to give it somewhere to concentrate.&lt;/p&gt;

&lt;p&gt;This time, at least, we'll know where to look before the third ticket lands.&lt;/p&gt;




&lt;h2&gt;
  
  
  Key Takeaways
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;A correct DISTKEY can still create enterprise-wide problems when applied to a shared conformed dimension.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Query-level optimization and platform-level optimization are not the same thing.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Distribution skew often originates from business-modeling decisions, not database-engine behavior.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;VACUUM cannot solve slice-level distribution skew.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Shared dimensions are architectural contracts, not local implementation details.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Physical optimization decisions should always be evaluated against all known consumers.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Lineage visibility is as important as performance visibility.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Sometimes the best fix happens in the WRK layer, not in the published model.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Enterprise performance issues frequently originate multiple layers upstream from where symptoms appear.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The most valuable outcome is often predictability, not speed.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Environment at a Glance
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fruddhanib.github.io%2Faniruddhawrites%2Fcontent%2Fimages%2Fdall.png%2520align%3D" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fruddhanib.github.io%2Faniruddhawrites%2Fcontent%2Fimages%2Fdall.png%2520align%3D" alt="Before vs After: Where the Skew Lives" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The investigation succeeded because the team stopped treating the dashboard as the problem and followed the lineage upstream until reaching the original modeling decision that created the downstream symptoms.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Amazon Redshift Distribution Styles&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Amazon Redshift Sort Keys&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Amazon Redshift Materialized Views&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Amazon Redshift System Tables and Views&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The Data Warehouse Toolkit — Ralph Kimball&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;AWS Prescriptive Guidance&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Related Articles
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Post-Acquisition Assumption Gap
&lt;/h3&gt;

&lt;p&gt;How inherited assumptions create hidden risks across enterprise data platforms.&lt;/p&gt;

&lt;h3&gt;
  
  
  When Data Becomes the Bottleneck
&lt;/h3&gt;

&lt;p&gt;Unmasking the real causes behind SLA misses in modern analytics ecosystems.&lt;/p&gt;

&lt;h3&gt;
  
  
  Slowly Changing Dimensions in the Real World
&lt;/h3&gt;

&lt;p&gt;Why SCD design decisions often create consequences years after implementation.&lt;/p&gt;




&lt;h2&gt;
  
  
  Author
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Aniruddha Banerjee&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Project Manager | Data Architect | Enterprise Data Engineering | Cloud &amp;amp; Analytics Platforms&lt;/p&gt;

&lt;p&gt;Sharing real-world lessons from enterprise-scale data platforms, architecture investigations, performance engineering, and operational excellence.&lt;/p&gt;

&lt;p&gt;GitHub Pages: &lt;a href="https://ruddhanib.github.io/aniruddhawrites/" rel="noopener noreferrer"&gt;https://ruddhanib.github.io/aniruddhawrites/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;LinkedIn: &lt;a href="https://www.linkedin.com/in/ruddhani/" rel="noopener noreferrer"&gt;https://www.linkedin.com/in/ruddhani/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Medium: &lt;a href="https://ruddhani.medium.com/" rel="noopener noreferrer"&gt;https://ruddhani.medium.com/&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Author's Note
&lt;/h2&gt;

&lt;p&gt;This article is inspired by recurring patterns observed across enterprise Amazon Redshift environments. Table names, business context, operational timelines, and selected implementation details have been generalized to protect confidentiality.&lt;/p&gt;

&lt;p&gt;The technical mechanisms, architectural trade-offs, investigation approaches, and performance-engineering principles described remain representative of genuine production challenges commonly encountered in large-scale analytical platforms.&lt;/p&gt;




&lt;blockquote&gt;
&lt;h1&gt;
  
  
  amazon-redshift #data-engineering #data-architecture #data-modeling #performance-optimization
&lt;/h1&gt;
&lt;/blockquote&gt;

</description>
      <category>dataengineering</category>
      <category>dataarchitecture</category>
      <category>datamodelling</category>
      <category>datawarehousing</category>
    </item>
    <item>
      <title>The Post-Acquisition Assumption Gap</title>
      <dc:creator>Aniruddha Writes</dc:creator>
      <pubDate>Sat, 18 Jul 2026 05:15:57 +0000</pubDate>
      <link>https://dev.to/aniruddhawrites/the-post-acquisition-assumption-gap-29ff</link>
      <guid>https://dev.to/aniruddhawrites/the-post-acquisition-assumption-gap-29ff</guid>
      <description>&lt;p&gt;Two years after we closed an acquisition, we found out our own pipeline still thought the company hadn't happened.&lt;/p&gt;

&lt;p&gt;Not the ERP. Not the org chart. The CDC job watching product category changes — built two years before the deal, quietly assuming there was exactly one PIM instance in the world, because at the time there was. Nobody rearchitected that assumption when the acquisition closed, because "does our pipeline's cardinality assumption survive an acquisition" isn't a line item anyone owns. Integration architecture disbands once systems are technically connected. Data engineering inherits the pipeline, not the reason it was built the way it was. The gap between those two teams is where this incident actually lives, and I want to say clearly: nobody in this story made a mistake. A category manager clicked save on a taxonomy cleanup that had been scheduled for weeks. That's the entire inciting event. Everything expensive that happened afterward was already true before she logged in.&lt;/p&gt;

&lt;p&gt;I'm going to move through the CDC-can't-capture-intent problem quickly, because if you've built distributed systems you already know this — row-level change capture answers "what changed," not "why," and no amount of tooling sophistication fixes that; you either capture intent at the write path (an outbox, a domain event) or you don't have it. What I want to spend real time on is what capturing intent actually costs you, because that part gets skipped in almost every version of this argument I've read.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Outbox Pattern — Mechanics and Its Gap
&lt;/h2&gt;

&lt;p&gt;Here's the part nobody puts in the diagram. The outbox pattern doesn't eliminate the intent-capture problem. It relocates it — from "we don't know why this changed" to "we have a &lt;code&gt;reason_code&lt;/code&gt; column, and now someone has to govern what goes in it, forever, with the same rigor you'd apply to a shared vocabulary in a multi-team API contract." That's a harder job than it sounds like, because unlike a schema, a taxonomy of &lt;em&gt;meaning&lt;/em&gt; has no compiler to enforce it. Marketing adds &lt;code&gt;'seasonal-refresh'&lt;/code&gt; because it unblocks their sprint. Nobody tells compliance, who six months later need to isolate &lt;code&gt;'regulatory-correction'&lt;/code&gt; events for an audit and discover the taxonomy has quietly drifted into unreliability.&lt;/p&gt;

&lt;p&gt;The uncomfortable version of this claim: &lt;strong&gt;most organizations that adopt the outbox pattern to solve the CDC-intent problem end up trading a technical debt they understood for a governance debt they don't have a process for at all.&lt;/strong&gt; Technical debt shows up in a code review. Taxonomy drift shows up eighteen months later, in an audit, as a surprise. I don't think this makes the pattern wrong. I think it means "we added an outbox" is not the finish line people treat it as, and if your architecture review signs off on intent-capture without also assigning a human owner to the reason-code vocabulary, you've solved the half of the problem that was easier to solve.&lt;/p&gt;




&lt;h2&gt;
  
  
  Reason-Code Governance Debt Over Time
&lt;/h2&gt;

&lt;p&gt;I think most of the SCD debate — Type 1 versus Type 2, table-level versus attribute-level, how much history is enough — is arguing about the wrong variable. The question isn't "what SCD type does this attribute deserve." It's "who is asking, and what does &lt;em&gt;they&lt;/em&gt; need to be true about the past."&lt;/p&gt;

&lt;p&gt;Finance, reconstructing last quarter's numbers, needs the category that was true on the transaction date — Type 2, strict effective dating. Merchandising, planning next season's assortment, wants the current hierarchy applied backward so trend lines are comparable to today's structure — which is functionally Type 1, applied retroactively, and is &lt;em&gt;correct&lt;/em&gt; for their question even though it's the same "wrong" behavior that blew up finance's quarter. Compliance, defending an audit, needs to know not just what the value was, but who decided it and why — which neither Type 1 nor Type 2 gives you without an outbox event attached.&lt;/p&gt;

&lt;p&gt;Three consumers. Three different correct answers. One physical attribute. Most warehouses pick one SCD treatment per attribute and call it done, because building consumer-specific temporal views is expensive — and then act surprised when one consumer's "correct" implementation is another consumer's incident. I don't think there's a clean fix for this that doesn't cost something. Type 6 hybrids (carry both the historical and current value on every row) get you partway there for two consumers at once, but they don't solve for compliance's need for decision-provenance, which requires the event-level metadata a bare dimension table was never built to hold. I'm not sure the honest answer is more sophisticated than "you need to know which consumer you're building for before you pick a strategy, and most teams pick the strategy before anyone's asked the question."&lt;/p&gt;




&lt;h2&gt;
  
  
  Consumer-Relative SCD — The Core Reframe
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;I used to tell every team the same rule: never hard-delete a crosswalk row, always end-date it. I don't hold that as a blanket rule anymore.&lt;/p&gt;

&lt;p&gt;End-dating solves a real problem — it lets you tell "this relationship expired" apart from "this relationship never existed," which a hard delete destroys completely. That part I still believe.&lt;/p&gt;

&lt;p&gt;What I left out for years: if that relationship touches anything adjacent to personal data, even indirectly, keeping it around forever in end-dated form is still retention — and some data minimization regimes have an affirmative deletion requirement that end-dating doesn't satisfy no matter how well-intentioned it is. "Never delete" and "comply with deletion law" are not always compatible instructions, and most SCD advice, mine included, has quietly assumed more history is an unqualified good. It isn't. It's a trade-off with a legal dimension I used to treat as someone else's problem.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Hard Delete vs. End-Date — The Privacy Tension
&lt;/h2&gt;

&lt;p&gt;Instead of presenting the typo/decision rows as "here's proof of the problem," frame it as a direct provocation, which lands harder and is more shareable as a standalone image:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Look at your own &lt;code&gt;dim_product&lt;/code&gt; right now. Pick any Type 2 attribute. Can you tell, from the table alone — no tribal knowledge, no Slack archaeology — which of its historical rows represent a business decision and which represent someone fixing a typo?&lt;/p&gt;

&lt;p&gt;If the answer is no, you don't have an audit trail. You have a very expensive list of things that happened, with the one piece of information that would make it useful — &lt;em&gt;why&lt;/em&gt; — living somewhere else, if it's living anywhere at all.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;(Table follows as before, now serving as evidence for a claim already made, not the claim itself.)&lt;/p&gt;




&lt;h2&gt;
  
  
  The Bridge-Table Wager
&lt;/h2&gt;

&lt;p&gt;Call it &lt;strong&gt;the bridge-table wager.&lt;/strong&gt; Every team building a crosswalk between two systems is making an implicit bet about how many independent dimensions of change that relationship will need over its lifetime. Bet low — one dimension, effective dating alone — and a Kimball bridge table is simpler, cheaper, and analyst-friendly. Bet high — effective dating plus provenance plus, eventually, some kind of confidence scoring you haven't thought of yet — and a Data Vault Link with its own Satellite ages better, because it doesn't force every new dimension of change into the same row-versioning event.&lt;/p&gt;

&lt;p&gt;Nobody knows which bet is right at design time. That's the actual disagreement, and I don't think it resolves with more expertise — it resolves with hindsight, which is exactly the thing architecture decisions never get to have. I've watched two architects I equally respect land on opposite sides of this exact wager for reasons that were both completely defensible given what they knew when they made the call.&lt;/p&gt;

&lt;h2&gt;
  
  
  Provenance You Can't Verify
&lt;/h2&gt;

&lt;p&gt;One thing worth keeping at full strength rather than trimming: when you retrofit a &lt;code&gt;source_system&lt;/code&gt; column onto a crosswalk table that's been running for five years across three different loaders, the rows written before that column existed don't get real provenance. They get your best guess, formatted to look like data. You can infer which loader probably wrote a row from column population patterns or ID formatting, but you can't verify it, and a compliance query built on that inference is answering a question with a guess wearing a structured column's clothing. I don't think this is a solvable problem after the fact. I think it's a reason to instrument provenance on day one even when it feels like premature process, because there is no retroactive fix that isn't fiction.&lt;/p&gt;

</description>
      <category>dataengineering</category>
      <category>enterprisedataarchitecture</category>
      <category>masterdatamanagement</category>
      <category>slowlychangingdimensions</category>
    </item>
    <item>
      <title>When Data Becomes the Bottleneck: Unmasking the Real Culprit Behind SLT Misses</title>
      <dc:creator>Aniruddha Writes</dc:creator>
      <pubDate>Sat, 11 Jul 2026 09:39:10 +0000</pubDate>
      <link>https://dev.to/aniruddhawrites/when-data-becomes-the-bottleneck-unmasking-the-real-culprit-behind-slt-misses-31f0</link>
      <guid>https://dev.to/aniruddhawrites/when-data-becomes-the-bottleneck-unmasking-the-real-culprit-behind-slt-misses-31f0</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;We kept blaming the data. Turns out, the data was innocent. 🔍&lt;br&gt;
For weeks, our SLTs were being missed. Query timeouts. Stale dashboards. Frustrated users. Every indicator pointed at the data layer — so that's where we dug.&lt;br&gt;
What we actually found:&lt;br&gt;
🔴 The root cause wasn't bad data — it was data CONTENTION. Hundreds of concurrent queries fighting over the same shared Fabric capacity, queuing, blocking, and cascading into failure.&lt;br&gt;
🌍 And then there was the geography problem nobody had mapped — our Fabric capacity tenant sitting across the Atlantic, adding 80–150ms of round-trip latency to EVERY query. Not slow computation. Slow continents.&lt;br&gt;
The result? Gateway timeouts that looked like data failures. Connection abortions mid-flight. SLT compliance sitting at 54%.&lt;br&gt;
Once we tackled concurrency limits, workload management, and migrated our tenant region closer to our users?&lt;br&gt;
✅ Response times dropped 68%&lt;br&gt;
✅ SLT compliance hit 91%&lt;br&gt;
✅ Gateway timeouts: near zero&lt;br&gt;
The data was fine the whole time. We were just asking too much of it, from too far away.&lt;br&gt;
I believe you will be more interested 👇 — especially relevant if you're running analytics on Microsoft Fabric or dealing with cross-region BI deployments.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;em&gt;A deep-dive into how high-concurrency query contention and cross-Atlantic infrastructure latency quietly erode service levels — and what to do about it.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Symptom Everyone Sees, The Cause Nobody Suspects
&lt;/h2&gt;

&lt;p&gt;In most performance post-mortems I've been part of, the conversation starts the same way: "The data is slow."&lt;/p&gt;

&lt;p&gt;Dashboards are lagging. Reports are timing out. Business users are frustrated. SLTs (Service Level Targets) are being breached, and fingers are pointing squarely at the data layer.&lt;/p&gt;

&lt;p&gt;And to be fair — the data is where the pain surfaces. But here's the uncomfortable truth that took us time to fully unpack:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The data wasn't broken. The data was overwhelmed — and it had a geography problem.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The Investigation: Peeling Back the Layers
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Layer 1 — SLT Misses and the Data Blame Game
&lt;/h3&gt;

&lt;p&gt;Our Service Level Targets were consistently missed during peak business hours. Query response times that should have landed under 3 seconds were stretching to 30, 60, sometimes over 120 seconds. Dashboards were either stale or outright failing to load.&lt;/p&gt;

&lt;p&gt;The immediate assumption? Data quality issues. Bad indexes. Poorly written queries. An overloaded dataset.&lt;/p&gt;

&lt;p&gt;We tuned queries. We rebuilt indexes. We optimised data models.&lt;/p&gt;

&lt;p&gt;The problem persisted.&lt;/p&gt;

&lt;h3&gt;
  
  
  Layer 2 — The Real Root Cause: Data Contention Under Concurrent Load
&lt;/h3&gt;

&lt;p&gt;What our monitoring eventually revealed was not a quality problem but a &lt;strong&gt;concurrency problem&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;During peak hours, hundreds of users were simultaneously firing queries against the same datasets. The underlying engine — running on a shared Microsoft Fabric capacity — was not failing because the data was wrong. It was failing because &lt;strong&gt;too many queries were competing for the same computational resources at the same time&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This is &lt;strong&gt;data contention&lt;/strong&gt;: a condition where high volumes of concurrent queries queue up, block each other, and create a cascading slowdown that ripples all the way to the end user experience.&lt;/p&gt;

&lt;p&gt;Key symptoms we identified:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Query queue depth spiking&lt;/strong&gt; dramatically during 9–11 AM and 2–4 PM windows&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Throttling events&lt;/strong&gt; logged at the Fabric capacity level&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Spill-to-disk&lt;/strong&gt; operations increasing as memory pressure mounted&lt;/li&gt;
&lt;li&gt;Individual query execution time remaining acceptable in isolation, but &lt;strong&gt;degrading 10–40x under concurrent load&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The data was never the culprit. &lt;strong&gt;Contention was&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Layer 3 — The Atlantic Problem: Latency Nobody Mapped
&lt;/h3&gt;

&lt;p&gt;Here is where the investigation took an unexpected turn.&lt;/p&gt;

&lt;p&gt;Our Microsoft Fabric capacity tenant was provisioned in a region &lt;strong&gt;across the Atlantic&lt;/strong&gt; — physically and network-topologically distant from the majority of our user base. What looked like slow query responses was, in many cases, not slow computation at all. It was &lt;strong&gt;network round-trip time compounding every single interaction&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The effects were insidious:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Gateway timeouts&lt;/strong&gt; — connections from local gateways to the remote Fabric tenant were breaching timeout thresholds before query results could be returned, not because queries were slow, but because the network handshake and data transfer time pushed the total wall-clock time over the edge.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Connection abortions&lt;/strong&gt; — under load, TCP connections to the distant tenant were being dropped mid-flight. The client saw a failure. The capacity was actually working — the work was simply never delivered.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compounding effect&lt;/strong&gt; — contention-induced delay + cross-Atlantic latency + gateway timeout threshold = a perfect storm that made every SLT miss look far worse than the underlying compute performance warranted.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A query that took 8 seconds to execute looked like a 45-second failure from the user's perspective. The network ate the difference.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;The Architecture of the Problem&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ff7dxns7913py05ku10qv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ff7dxns7913py05ku10qv.png" alt=" " width="777" height="494"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Every query traversed this path — &lt;strong&gt;twice&lt;/strong&gt; (request and response). Under contention, queries that lingered in the execution queue long enough would cause the gateway to give up before the answer arrived.&lt;/p&gt;

&lt;h2&gt;
  
  
  What We Did About It
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Addressed Contention at the Capacity Layer&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scaled up Fabric capacity&lt;/strong&gt; during peak windows using autoscale policies&lt;/li&gt;
&lt;li&gt;Implemented &lt;strong&gt;query concurrency limits&lt;/strong&gt; and workload management rules to prioritise critical service-level reports&lt;/li&gt;
&lt;li&gt;Introduced &lt;strong&gt;incremental refresh and aggregation tables&lt;/strong&gt; to reduce raw query payload sizes&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Shifted non-urgent batch workloads to off-peak hours to reduce simultaneous demand&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Tackled the Geography Problem&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Engaged with our Fabric tenant configuration to &lt;strong&gt;migrate capacity to a region closer to our primary user base&lt;/strong&gt; — a process that required planning but delivered the most dramatic latency improvement&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Increased &lt;strong&gt;gateway timeout thresholds&lt;/strong&gt; as an interim measure to prevent premature connection drops while longer queries completed legitimately&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Implemented &lt;strong&gt;connection pooling and keep-alive configurations&lt;/strong&gt; on the gateway layer to reduce connection setup overhead on every request&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Improved Observability&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Instrumented query telemetry to &lt;strong&gt;distinguish network latency from compute latency&lt;/strong&gt; — critical for correctly diagnosing future incidents&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Built a capacity utilisation dashboard to give early warning of contention events before they breached SLTs&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Established a baseline for expected cross-region RTT so anomalies could be detected faster&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The Results
&lt;/h2&gt;

&lt;p&gt;Once the contention management and regional migration work completed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Average query response time dropped by ~68%&lt;/strong&gt; during peak windows&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SLT compliance improved from ~54% to over 91%&lt;/strong&gt; within 6 weeks&lt;/li&gt;
&lt;li&gt;Gateway timeouts effectively dropped to near-zero&lt;/li&gt;
&lt;li&gt;User satisfaction scores for the analytics platform improved significantly&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The data, as it turned out, had been perfectly fine the whole time.&lt;/p&gt;




&lt;h2&gt;
  
  
  Key Takeaways for Data &amp;amp; Platform Engineers
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Don't confuse the symptom with the cause&lt;/strong&gt;. Slow data experiences are often infrastructure and concurrency problems wearing a data costume.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Concurrent query volume is a first-class concern&lt;/strong&gt;. Design your capacity with peak concurrency in mind, not just peak data volume.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Geography matters more than people think&lt;/strong&gt;. Cross-region tenancy is often an afterthought in platform provisioning. It shouldn't be. Measure your RTT early and often.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gateway timeouts are a network story, not a query story&lt;/strong&gt;. If timeouts correlate with specific time windows and user geographies, suspect latency before suspecting code.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Observability must separate layers&lt;/strong&gt;. If you can't distinguish network time from compute time in your telemetry, you will misdiagnose performance problems — repeatedly.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Final Thought
&lt;/h2&gt;

&lt;p&gt;The most dangerous performance problems are the ones that look obvious but aren't. SLT misses that appear to be data problems can spend months in the wrong queue — being "fixed" by data engineers while the real causes (concurrency limits, tenant geography, gateway configuration) sit untouched.&lt;/p&gt;

&lt;p&gt;Invest the time to instrument your full stack. The answer is rarely where the pain is loudest.&lt;/p&gt;




&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Have you encountered similar patterns in your data platforms? I'd love to hear how your teams approached contention and latency challenges — drop a comment below.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;code&gt;#DataEngineering #MicrosoftFabric #PowerBI #ServiceLevels #DataPlatform #CloudArchitecture #Latency #PerformanceEngineering #Analytics #DataContention #TechLeadership&lt;/code&gt;&lt;/p&gt;

</description>
      <category>dataengineering</category>
      <category>msfabric</category>
      <category>dataplatform</category>
      <category>latency</category>
    </item>
  </channel>
</rss>
