Start here →

Global Manufacturer Converts 6,400 SAS Jobs and 5.4 Million Lines to Apache Iceberg — Same Tables on AWS, Azure, and GCP

SAS2PY Case Study • April 2026 • Industrial Manufacturing

Executive Summary

A diversified manufacturer (industrial equipment, after two acquisitions) had three clouds and one SAS problem. Plants and MES extracts already sat on AWS. Corporate finance and a Synapse estate sat on Azure. The acquired electronics division sat on GCP. Each region had its own SAS Grid. Combined: 6,400 scheduled jobs, 5.4 million lines, 2,400 macros, three format-catalog cultures. The board would not pick Databricks, Snowflake, or BigQuery as the storage standard — that fight had already wasted a year. They picked Apache Iceberg as the table contract. Compute could stay native: EMR/Glue and Athena on AWS, Synapse Spark and Fabric on Azure, Dataproc and BigQuery external tables on GCP. MigryX converted the SAS estate to Iceberg-writing PySpark (and Trino SQL where the job was scan-and-aggregate). Twenty months. Three catalogs. One spec. $9.2 million three-year gap versus three SAS enterprise renewals plus three Grids.

Client Overview

SAS grew up next to plants: yield, warranty, supplier quality, then finance consolidations after each acquisition. Nobody was going to move a German plant’s historians off AWS, or convince corporate treasury to leave Azure, or replatform the GCP division before the earn-out. A single vendor lakehouse was politically dead. An open table format was not.

The constraint we took as real: same Iceberg spec (v2, hidden partitions, snapshot isolation), different catalogs, no cross-cloud query in v1. Cross-cloud copies are explicit jobs, not “the mesh will handle it.” That sentence saved more time than any parser feature.

Business Challenge

The MigryX Approach

One inventory across three Git remotes. Same AST, same PROC maps, same equivalence harness. Three emitters only where the catalog writer and the orchestrator differ. PySpark modules are cloud-agnostic except for a 40-line catalog.py per cloud (Glue vs HMS vs BigLake). That is the whole multi-cloud story in code terms.

Partition specs came from Grid scan patterns, not from a textbook:

Jobs that were PROC SQL + MEANS only became Trino (AWS Athena, Azure Trino-on-AKS, GCP BigQuery external / Dataproc Trino where already present). DATA-step and DI flows stayed PySpark writers. Compaction is a separate DAG (rewrite-data-files weekly on hot tables). We did not leave 10k tiny files because “Iceberg handles it.”

Shared Conversion Plane

Three SAS Grids → one Iceberg v2 spec → native catalog per cloud

Three GridsThree Grids
AWS plantsAWS plants2,480 jobs
Azure HQAzure HQ2,150 jobs
GCP divisionGCP division1,770 jobs
MigryXMigryX
Iceberg DDLIceberg DDLv2 + partitions
PySpark writerPySpark writercatalog.py × 3
AWSAWS
Amazon S3Amazon S3plant / warranty
Glue CatalogGlue CatalogLake Formation
EMR ServerlessEMR ServerlessWriters
AthenaAthenaSQL path
AzureAzure
ADLS Gen2ADLS Gen2finlake
Hive MetastoreSynapse + Fabric
Synapse SparkSynapse SparkWriters
Microsoft PurviewMicrosoft PurviewScan + STTM
Google CloudGoogle Cloud
Cloud StorageCloud Storagesupply / field
BigLakeBigLakeExternal Iceberg
DataprocDataprocWriters
ComposerComposerDivision DAGs

6,400 jobs / 5.4M LOC is the sum of three estates after de-duplicating 180 programs that existed in two clouds as copies. Those copies became one module and two catalog registrations — not two conversions.

AWS Architecture — Plants and MES

MES files land every few minutes. Iceberg commits are batched to 5–15 minutes on hot plant tables so the catalog is not a commit storm. Athena is for analysts; writers stay on EMR so a fat join cannot take the SQL warehouse hostage.

Azure Architecture — Corporate Finance

Finance refused “date=” partitions. fiscal_period is an identity partition. Time travel for close is “snapshot recorded in the close ticket,” which is a column on the audit table, not a story about Delta vs Iceberg.

GCP Architecture — Acquired Division

BigQuery users in the division query Iceberg in place. They do not get a second copy “for BQ.” The few consolidations that must land in Azure are Composer → authenticated copy jobs of specific snapshots, not a standing multi-cloud lake.

Estate Inventory

Cloud / domain Jobs LOC Writer Catalog
AWS — yield / MES 1,520 1.2M EMR PySpark Glue
AWS — warranty / quality 960 900k EMR + Athena SQL Glue
Azure — finance / statutory 1,340 1.1M Synapse Spark HMS
Azure — supply / HQ ops 810 700k Fabric Spark HMS
GCP — supply / field 1,770 1.5M Dataproc + BQ ext BigLake

20 months is three waves per cloud, sequenced so finance close (Azure) never overlapped a plant cutover (AWS). GCP went last because the earn-out team could not take risk in year one. That is a calendar, not a tool limitation.

01SpecIceberg v2 + partition doc per domain
02ConvertSame module, catalog.py per cloud
03CommitWrite snapshot_id into audit
04CompactWeekly on hot facts
05CopyExplicit snapshot to another cloud

What held up (and what we did not do)

Results

6,400
Jobs across three clouds
5.4M
Lines (macros once, copies once)
3
Catalogs: Glue, HMS, BigLake
$9.2M
3-year vs three SAS renewals
20 mo
Last Grid powered off
84%
No human rewrite
"We were not going to win a platform war. We needed a table format the plants, treasury, and the acquired division could all write. Iceberg is that. The conversion had to respect three catalogs or it would have been another strategy deck."

— VP of Enterprise Data, global industrial manufacturer

SAS to Iceberg on the cloud you already have

Glue, Hive/Purview, BigLake — same Iceberg spec, same parser, no forced lakehouse vendor.

Explore Iceberg modernization →

SAS modernization paths

Targets: Databricks Snowflake Google Cloud Azure AWS PySpark Polars Iceberg