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

<channel>
	<title>Oracle | John Nessime</title>
	<atom:link href="https://john-nessime.com/blog/tag/oracle/feed/" rel="self" type="application/rss+xml" />
	<link>https://john-nessime.com/blog/tag/oracle/</link>
	<description>Cloud, DevOps, Data &#38; AI — Built, Tested, Explained</description>
	<lastBuildDate>Mon, 12 Oct 2026 06:08:11 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=7.1.3</generator>

<image>
	<url>https://john-nessime.com/blog/wp-content/uploads/2026/07/cropped-jn-32x32.png</url>
	<title>Oracle | John Nessime</title>
	<link>https://john-nessime.com/blog/tag/oracle/</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>Oracle on EC2 vs RDS: Why the License Math Doesn&#8217;t Save You</title>
		<link>https://john-nessime.com/blog/technical-guides/oracle-on-ec2-vs-rds/</link>
		
		<dc:creator><![CDATA[John Nessime]]></dc:creator>
		<pubDate>Mon, 12 Oct 2026 06:08:11 +0000</pubDate>
				<category><![CDATA[Cloud Computing]]></category>
		<category><![CDATA[Databases]]></category>
		<category><![CDATA[Technical Guides]]></category>
		<category><![CDATA[Amazon EC2]]></category>
		<category><![CDATA[Amazon RDS]]></category>
		<category><![CDATA[Architecture]]></category>
		<category><![CDATA[AWS]]></category>
		<category><![CDATA[BYOL]]></category>
		<category><![CDATA[Cost Optimization]]></category>
		<category><![CDATA[Data Guard]]></category>
		<category><![CDATA[Database Migration]]></category>
		<category><![CDATA[FinOps]]></category>
		<category><![CDATA[Multi-AZ]]></category>
		<category><![CDATA[Oracle]]></category>
		<category><![CDATA[Oracle Licensing]]></category>
		<category><![CDATA[RDS Custom]]></category>
		<category><![CDATA[Vendor Lock-In]]></category>
		<guid isPermaLink="false">https://john-nessime.com/blog/?p=386</guid>

					<description><![CDATA[<p>Oracle counts a vCPU the same way on EC2 and on RDS, so the platform choice never saves you licensing money. What it does change is sysdba access, block size and file limits, who owns backups and failover, and the Multi-AZ setting that quietly doubles your BYOL count.</p>
<p>The post <a href="https://john-nessime.com/blog/technical-guides/oracle-on-ec2-vs-rds/">Oracle on EC2 vs RDS: Why the License Math Doesn&#8217;t Save You</a> appeared first on <a href="https://john-nessime.com/blog">John Nessime</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">The finance lead asks a fair question in the migration kickoff: &#8220;If we move Oracle to RDS instead of EC2, how much do we save on licensing?&#8221;</p>



<p class="wp-block-paragraph">Nothing. Not a cent. Oracle counts a vCPU on Amazon RDS exactly the way it counts a vCPU on Amazon EC2, and it says so in the same paragraph of the same policy document. The managed service does not buy you a discount on the software.</p>



<p class="wp-block-paragraph">That single fact reshapes the whole argument. Once the license line is identical on both sides of the page, Oracle on EC2 vs RDS stops being a cost comparison and becomes a much more interesting question: which set of constraints can your application actually live inside? This post walks through the real differences, the places each option quietly hurts, the middle option that is being retired, and a decision procedure you can run before anyone opens a pricing calculator.</p>



<h2 class="wp-block-heading">Start with the thing that is not a difference</h2>



<p class="wp-block-paragraph">Oracle publishes a policy called &#8220;Licensing Oracle Software in the Cloud Computing Environment.&#8221; It names a short list of Authorized Cloud Environments, and both Amazon EC2 and Amazon RDS are on it, alongside Microsoft Azure and Google Cloud Platform. Inside an Authorized Cloud Environment the counting rules are these:</p>



<ul class="wp-block-list">
<li>Two vCPUs count as one Oracle Processor license when multi-threading of processor cores is enabled.</li>



<li>One vCPU counts as one Processor license when multi-threading is not enabled.</li>



<li>The Oracle Processor Core Factor Table does not apply. The 0.5 multiplier you rely on for x86 hardware in your own data center is simply gone.</li>



<li>For Standard Edition products, instances with four or fewer vCPUs count as one socket. Above four, every four vCPUs rounded up to the nearest multiple of four equals one socket.</li>



<li>Standard Edition 2 may only be licensed on instances up to eight AWS vCPUs. That is a hard ceiling, and it is the single most common reason an SE2 estate has to become an Enterprise Edition estate on migration.</li>
</ul>



<p class="wp-block-paragraph">None of those rules distinguish EC2 from RDS. The policy treats them as the same environment for counting purposes.</p>



<p class="wp-block-paragraph">There is one genuine licensing difference, and it is narrow. RDS offers a License Included model where AWS holds the Oracle license and bundles it into the instance-hour rate. That model covers Standard Edition 2 only. Enterprise Edition on RDS is bring-your-own-license, full stop. On EC2 there is no License Included option at all, so every Oracle database you run there is BYOL by definition.</p>



<p class="wp-block-paragraph">Worth reading the footer of that Oracle policy document, too. It states that the document is for educational purposes and may not be incorporated into any contract. Your Oracle Master Agreement and ordering documents are what actually bind you. The cloud policy is how Oracle says it will behave, not what it promised in writing, and it can change without notice. Pin a copy of the version in force when you deploy and file it with your architecture decision record.</p>



<h3 class="wp-block-heading">Find out what you are actually using first</h3>



<p class="wp-block-paragraph">Before you compare anything, find out which licensable options and management packs your current database is touching. Oracle tracks this itself. The feature usage view records every separately licensed feature the database has detected in use, which is exactly what an auditor looks at.</p>



<pre class="wp-block-code"><code>-- Which separately licensed features has this database touched?
SELECT name,
       detected_usages,
       currently_used,
       first_usage_date,
       last_usage_date
FROM   dba_feature_usage_statistics
WHERE  detected_usages &gt; 0
ORDER  BY name;</code></pre>



<p class="wp-block-paragraph">Oracle also publishes an options and packs usage script through My Oracle Support that goes further and correlates usage across several dictionary views. If you have a support contract, use that one rather than rolling your own. The output frequently surprises people. Partitioning, Advanced Compression, and the Diagnostics and Tuning Packs get switched on by a well-meaning DBA or by a default setting and then sit there generating an entitlement gap.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">Amazon RDS for Oracle: where it wins and where it hurts</h2>



<h3 class="wp-block-heading">Where RDS wins</h3>



<ul class="wp-block-list">
<li><strong>The operational floor is done for you.</strong> Automated backups with point-in-time recovery, minor version patching in a maintenance window, Multi-AZ synchronous standby with automatic failover, storage autoscaling. None of that needs a person on a rota.</li>



<li><strong>License Included removes the Oracle relationship entirely.</strong> For an SE2 workload where you own no licenses, this is genuinely hard to beat. There is no entitlement to track, no support renewal to chase, and no Oracle audit surface for the database software.</li>



<li><strong>Read replicas are configured for you.</strong> RDS manages the Data Guard setup behind cross-region replicas, including the network path between primary and standby. If the replica runs open read-only, that is Active Data Guard and you need the license for it, but the plumbing is not your problem.</li>



<li><strong>Parameter groups make configuration reviewable.</strong> Database parameters live in a versioned object you can diff, rather than in a spfile someone edited at two in the morning.</li>
</ul>



<h3 class="wp-block-heading">Where RDS hurts</h3>



<p class="wp-block-paragraph">The master user on an RDS for Oracle instance gets the DBA role with pieces removed. AWS documents exactly which privileges are withheld to keep the managed experience intact:</p>



<ul class="wp-block-list">
<li><code>ALTER DATABASE</code></li>



<li><code>ALTER SYSTEM</code></li>



<li><code>CREATE ANY DIRECTORY</code> and <code>DROP ANY DIRECTORY</code></li>



<li><code>GRANT ANY PRIVILEGE</code> and <code>GRANT ANY ROLE</code></li>
</ul>



<p class="wp-block-paragraph">You also cannot log in as <code>SYS</code>, <code>SYSTEM</code>, or any other Oracle-supplied administrative account. This is the wall most migrations hit, and they hit it late. A packaged vendor application ships an installer that expects to connect as sysdba, create a directory object pointing at a local path, and run <code>ALTER SYSTEM</code> a dozen times. On RDS that installer does not complete. There is no workaround, because the privilege is not withheld by policy, it is withheld by design.</p>



<p class="wp-block-paragraph">Three more constraints that bite later rather than sooner:</p>



<ul class="wp-block-list">
<li><strong>Block size is fixed at creation.</strong> RDS for Oracle instances are created with an 8 KB default block size and it cannot be changed afterwards. The SYSTEM and temporary tablespaces always use it. You can create additional tablespaces at other block sizes by allocating the matching cache parameter and using the <code>BLOCKSIZE</code> clause, but the core of the database is fixed.</li>



<li><strong>Single files cap at 16 TiB.</strong> That is the ext4 filesystem limit on the underlying host, so bigfile data files inherit it. Try to grow past it and you get an ORA-01237 with a &#8220;File too large&#8221; error underneath.</li>



<li><strong>No host access at all.</strong> No shell, no third-party backup agent, no monitoring agent installed on the database server, no custom scripts on the filesystem. If your compliance tooling or your APM vendor requires an on-host agent, that requirement decides the platform for you.</li>
</ul>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">Oracle on EC2: where it wins and where it hurts</h2>



<h3 class="wp-block-heading">Where EC2 wins</h3>



<ul class="wp-block-list">
<li><strong>Full sysdba and full root.</strong> Everything the vendor installer wants is available. Directory objects, external tables reading from local paths, custom PL/SQL that shells out, agents, whatever the packaged application assumed about its host.</li>



<li><strong>The full replication toolbox.</strong> Data Guard, Active Data Guard, and GoldenGate all run natively on EC2, and you control the protection mode, the apply lag tolerance, and the failover behaviour rather than accepting a managed default.</li>



<li><strong>Instance shape is a license lever.</strong> This is the underrated one. Oracle bills you on vCPUs, and most Oracle databases are memory bound rather than thread bound. On EC2 you can pick a memory-dense family and hit your SGA target without buying vCPUs, and therefore licenses, that the workload never uses. RDS gives you a narrower catalogue to choose from.</li>



<li><strong>You choose when to patch.</strong> No maintenance window imposed from outside, no forced minor version upgrade, no surprise reboot on a night your business cannot take one.</li>



<li><strong>Storage is yours to shape.</strong> Separate EBS volumes for data, redo, and archive logs, with independent IOPS and throughput. RDS abstracts that away.</li>
</ul>



<h3 class="wp-block-heading">Where EC2 hurts</h3>



<p class="wp-block-paragraph">Every one of those wins is a job someone now has to do, forever. The honest cost of Oracle on EC2 is not the instance-hour, it is the rota.</p>



<ul class="wp-block-list">
<li><strong>Backups you have to prove.</strong> RMAN to S3 is a solved pattern, but a backup that has never been restored is a hypothesis. Schedule a real restore into a throwaway instance on a cadence and treat a failed restore as an incident.</li>



<li><strong>A standby that drifts silently.</strong> Data Guard apply lag is the classic invisible failure. Everything looks green, the standby is mounted, redo is shipping, and apply stopped three days ago because of a single nologging operation or a filesystem that filled. You find out during the failover.</li>



<li><strong>Patching is now a project.</strong> Quarterly Oracle patches, OS patches, and the interaction between them. On a Data Guard pair that means a standby-first sequence and a rollback plan.</li>



<li><strong>Monitoring you have to build.</strong> Tablespace growth, apply lag, wait events, ASM disk group headroom, archive log destination fullness. Amazon CloudWatch gets you host metrics and nothing about the database. Teams typically land on a Prometheus exporter feeding Grafana, either self-hosted or through Grafana Cloud or Amazon Managed Grafana, plus alerting that someone actually answers.</li>



<li><strong>Cost attribution gets murky.</strong> Instance, EBS, snapshots, cross-AZ traffic, and the license amortisation all land in different places. A cost visibility tool such as Vantage or CloudZero earns its keep here, because the Oracle line is the one finance will ask about.</li>
</ul>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">The middle option that is being retired</h2>



<p class="wp-block-paragraph">For a while the answer to &#8220;I need sysdba but I also want managed backups&#8221; was Amazon RDS Custom for Oracle. It gave you shell access to the underlying EC2 instance and sysdba on the database, while keeping automated backups and host recovery, policed by a mechanism called the support perimeter that flagged your instance as unsupported if a change broke the automation.</p>



<p class="wp-block-paragraph">AWS has announced that support for RDS Custom for Oracle ends on 31 March 2027. After that date the console and the resources go away, including instances, snapshots, and custom engine versions. AWS&#8217;s own guidance is to move those workloads to self-managed Oracle on EC2.</p>



<p class="wp-block-paragraph">This is one of the few dates worth putting in an article, because it removes an option from your decision tree. If you are choosing a platform today, RDS Custom for Oracle is not a candidate. If you are already on it, the migration is on your roadmap whether you have written it down or not. Note that this retirement does not touch RDS for Oracle or RDS Custom for SQL Server.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">The cost levers that actually move the number</h2>



<p class="wp-block-paragraph">Once you accept that the platform choice does not change the license rate, these are the things that do change the bill.</p>



<ol class="wp-block-list">
<li><strong>Memory-to-vCPU ratio.</strong> Reaching a memory target by moving up within a general purpose family buys vCPUs you did not need, and each pair of them is another Processor license. Picking a memory-optimised family instead is often the largest single saving available.</li>



<li><strong>Multi-AZ under BYOL doubles the count.</strong> AWS states this plainly in its own documentation: under BYOL you must have a license for both the primary and the standby DB instance in a Multi-AZ deployment. Enabling the checkbox on a BYOL instance without buying the second set of licenses puts you out of compliance the moment it comes up.</li>



<li><strong>Options and management packs.</strong> These are priced per Processor on top of the database license. On RDS, option groups can put some of them one click away, which is convenient and expensive in equal measure.</li>



<li><strong>Edition, forced by the vCPU ceiling.</strong> SE2 stops at eight vCPUs in an Authorized Cloud Environment. A workload that needs more compute has to move to Enterprise Edition, and that is a different order of magnitude, not a percentage increase.</li>



<li><strong>Infrastructure commitments.</strong> Reserved Instances and Savings Plans reduce the AWS side of the bill on both platforms. They do nothing to the Oracle side.</li>
</ol>



<p class="wp-block-paragraph">Two quick checks worth running before you model anything. First, confirm what an instance is actually presenting, because the multi-threading state is what decides whether you divide by two:</p>



<pre class="wp-block-code"><code># On an EC2 instance: threads per core tells you the multi-threading state.
# "Thread(s) per core: 2" means the 2 vCPU = 1 Processor rule applies.
lscpu | grep -E 'Thread(s) per core|Core(s) per socket|^CPU(s):'</code></pre>



<p class="wp-block-paragraph">Second, check what your existing RDS instances are declaring, since the license model and the Multi-AZ flag together determine your exposure:</p>



<pre class="wp-block-code"><code># Every Oracle RDS instance with its class, license model and Multi-AZ state.
aws rds describe-db-instances 
  --query "DBInstances[?starts_with(Engine, 'oracle')].
[DBInstanceIdentifier,Engine,DBInstanceClass,LicenseModel,MultiAZ]" 
  --output table</code></pre>



<p class="wp-block-paragraph">A <code>MultiAZ</code> value of <code>true</code> next to a <code>LicenseModel</code> of <code>bring-your-own-license</code> is the line to look at twice.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">How I would decide between Oracle on EC2 vs RDS</h2>



<p class="wp-block-paragraph">Run these in order. The first question that returns a hard &#8220;no&#8221; makes the decision for you, and you can stop.</p>



<ol class="wp-block-list">
<li><strong>Does anything need sysdba or host access?</strong> Vendor installers, on-host agents, directory objects pointing at local paths, external tables reading local files, custom OS-level jobs. If yes, it is EC2. Nothing else in this list matters.</li>



<li><strong>Run the feature usage query.</strong> Establish which options and packs you are on the hook for today. That number travels with you regardless of platform, and it is usually larger than the team expects.</li>



<li><strong>Check the physical constraints.</strong> Non-8 KB block size on the SYSTEM tablespace, or a single data file heading past 16 TiB, both rule RDS out.</li>



<li><strong>Settle the edition.</strong> If SE2 fits inside eight vCPUs and you own no perpetual licenses, RDS with License Included is very hard to argue against. If you need Enterprise Edition, you are BYOL on either platform and the licensing argument is over.</li>



<li><strong>Count licenses at the shape you would really run.</strong> Production instance, plus standby if you want Multi-AZ or Data Guard, plus every option pack from step two. Do this before you look at instance-hour rates, because it is the bigger number.</li>



<li><strong>Price the rota honestly.</strong> Who runs restore tests, who owns the patch calendar, who gets paged at three in the morning when apply lag alerts. If the answer is &#8220;we will figure it out,&#8221; that is a vote for RDS.</li>



<li><strong>Pick, then write down why.</strong> Record the Oracle policy version you counted against and the instance shapes you assumed. When an audit letter arrives, that document is the difference between a conversation and a negotiation.</li>
</ol>



<p class="wp-block-paragraph">One useful side effect of building a dev or reporting copy outside AWS entirely: a modest VPS from a provider like Contabo or InterServer running Oracle&#8217;s free Express Edition gives developers a schema to work against without touching a licensed instance. It will not stand in for production, but it takes pressure off the environments that cost money.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">Arguments that do not survive contact</h2>



<ul class="wp-block-list">
<li><strong>&#8220;RDS is cheaper because AWS manages it.&#8221;</strong> Under BYOL the license line is identical. What you save is operational labour, which is a real saving but a different budget line, and you should say which one you mean.</li>



<li><strong>&#8220;We will disable multi-threading to reduce licenses.&#8221;</strong> Turning it off halves your usable threads and flips the ratio to one vCPU per Processor license. You end up at the same license count with less compute.</li>



<li><strong>&#8220;The core factor gives us 0.5 on x86.&#8221;</strong> Not in an Authorized Cloud Environment. Oracle states the table does not apply there. This is the source of the widely repeated claim that moving to the cloud doubles Oracle licensing on identical hardware.</li>



<li><strong>&#8220;The standby is passive, so it is free.&#8221;</strong> AWS documents that BYOL Multi-AZ requires licensing both instances. There is a separate Oracle provision about genuinely cold failover nodes, but a synchronous standby that is always running is not that. Get your specific position confirmed in writing by whoever advises you on Oracle contracts, not from a blog, including this one.</li>



<li><strong>&#8220;Graviton is a different architecture, so it licenses differently.&#8221;</strong> The cloud policy counts vCPUs. It does not carve out processor architectures on shared tenancy.</li>



<li><strong>&#8220;We will start on RDS and move to EC2 if it does not work.&#8221;</strong> Technically true and practically expensive. Moving off RDS means a full migration project with a cutover window, not a configuration change. Establish the sysdba requirement before you build, not after.</li>
</ul>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">Frequently asked questions</h2>



<h3 class="wp-block-heading">Is Oracle licensing cheaper on RDS than on EC2?</h3>



<p class="wp-block-paragraph">Under bring-your-own-license, no. Oracle&#8217;s cloud policy names EC2 and RDS together and applies the same vCPU counting to both. The only structural difference is that RDS offers a License Included model, which is available for Standard Edition 2 only.</p>



<h3 class="wp-block-heading">Can I run Oracle Enterprise Edition with License Included on RDS?</h3>



<p class="wp-block-paragraph">No. AWS supports License Included for Standard Edition 2 only. Enterprise Edition on RDS is BYOL, and you need the appropriate licenses with active Software Update License and Support.</p>



<h3 class="wp-block-heading">Do I need to license the Multi-AZ standby?</h3>



<p class="wp-block-paragraph">Under BYOL, AWS documentation states you must have a license for both the primary and the standby DB instance. Budget for double the Processor count before you enable it. Under License Included the standby is reflected in the instance-hour rate instead.</p>



<h3 class="wp-block-heading">What is the largest Standard Edition 2 instance I can run on AWS?</h3>



<p class="wp-block-paragraph">Oracle&#8217;s cloud policy caps Standard Edition 2 at eight AWS vCPUs. Past that you are into Enterprise Edition. This ceiling catches a lot of migrations that were sized on memory requirements rather than vCPU count.</p>



<h3 class="wp-block-heading">Can I get sysdba access on RDS for Oracle?</h3>



<p class="wp-block-paragraph">No. The master user receives the DBA role minus a documented set of privileges including <code>ALTER SYSTEM</code>, <code>ALTER DATABASE</code>, <code>CREATE ANY DIRECTORY</code>, and the <code>GRANT ANY</code> privileges, and the Oracle-supplied administrative accounts are not available for login. If an application requires sysdba, EC2 is your platform.</p>



<h3 class="wp-block-heading">Is RDS Custom for Oracle still a viable middle ground?</h3>



<p class="wp-block-paragraph">Not for new work. AWS ends support for RDS Custom for Oracle on 31 March 2027 and recommends moving those workloads to EC2. Existing users should treat the migration as a scheduled project rather than a future problem.</p>



<h3 class="wp-block-heading">Does turning off hyperthreading reduce my Oracle license count?</h3>



<p class="wp-block-paragraph">No. With multi-threading enabled the ratio is two vCPUs per Processor license. With it disabled the ratio becomes one to one. The license requirement stays where it was while your available compute halves.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">Conclusion</h2>



<p class="wp-block-paragraph">If you remember one thing from the Oracle on EC2 vs RDS comparison, make it this: the platform choice does not change what Oracle charges you. It changes who does the work and what your application is allowed to do.</p>



<p class="wp-block-paragraph">So run the decision in that order. Establish the sysdba requirement first, because it is binary and it ends the discussion. Count your options and packs second, because that number is usually wrong in the direction that costs money. Compare instance-hour rates last, because it is the smallest term in the equation and the one everyone starts with.</p>



<p class="wp-block-paragraph">And write down which version of Oracle&#8217;s cloud policy you counted against. It is a non-contractual document that can change, and the copy you filed on the day you deployed is worth more than any argument you can make from memory later.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">Need a second pair of eyes on an Oracle migration to AWS?</h2>



<p class="wp-block-paragraph">Most of the expensive mistakes here happen before anything is built, in the assumptions nobody wrote down. Things I can help with:</p>



<ul class="wp-block-list">
<li>Auditing feature and option pack usage on your existing databases so you know your real entitlement position before you size anything</li>



<li>Working through the sysdba and host-access requirements of packaged applications to determine whether RDS is even on the table</li>



<li>Modelling Processor counts across candidate instance shapes, including standby and Multi-AZ exposure, so the number in the business case survives review</li>



<li>Building the operational layer that self-managed Oracle on EC2 needs: RMAN to S3, tested restores, Data Guard apply-lag alerting, and patch runbooks</li>



<li>Planning migrations off RDS Custom for Oracle to EC2 ahead of the end-of-support date</li>



<li>Setting up monitoring and cost attribution that separates the AWS line from the Oracle line, so finance gets an answer instead of a total</li>
</ul>



<p class="wp-block-paragraph">Send me the output of your feature usage query, or a list of the instance classes you are considering, and I will tell you what I would check first.</p>



<div class="wp-block-buttons is-layout-flex wp-block-buttons-is-layout-flex">
<div class="wp-block-button"><a class="wp-block-button__link wp-element-button" href="https://www.upwork.com/freelancers/~01f15a912ad84a6620" target="_blank" rel="noreferrer noopener">Work with me on Upwork</a></div>
</div>
<p>The post <a href="https://john-nessime.com/blog/technical-guides/oracle-on-ec2-vs-rds/">Oracle on EC2 vs RDS: Why the License Math Doesn&#8217;t Save You</a> appeared first on <a href="https://john-nessime.com/blog">John Nessime</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>AWS Schema Conversion Tool for Oracle: What Converts and What Quietly Doesn&#8217;t</title>
		<link>https://john-nessime.com/blog/technical-guides/aws-schema-conversion-tool-oracle/</link>
		
		<dc:creator><![CDATA[John Nessime]]></dc:creator>
		<pubDate>Sat, 03 Oct 2026 06:43:14 +0000</pubDate>
				<category><![CDATA[Cloud Computing]]></category>
		<category><![CDATA[Databases]]></category>
		<category><![CDATA[Technical Guides]]></category>
		<category><![CDATA[Amazon Aurora]]></category>
		<category><![CDATA[Amazon RDS]]></category>
		<category><![CDATA[AWS DMS]]></category>
		<category><![CDATA[AWS SCT]]></category>
		<category><![CDATA[Cutover Planning]]></category>
		<category><![CDATA[Data Type Mapping]]></category>
		<category><![CDATA[Database Migration]]></category>
		<category><![CDATA[Migration Assessment]]></category>
		<category><![CDATA[Oracle]]></category>
		<category><![CDATA[PL/SQL]]></category>
		<category><![CDATA[PostgreSQL]]></category>
		<category><![CDATA[Schema Conversion]]></category>
		<category><![CDATA[SQL]]></category>
		<category><![CDATA[Vendor Lock-In]]></category>
		<guid isPermaLink="false">https://john-nessime.com/blog/?p=388</guid>

					<description><![CDATA[<p>The assessment report grades syntax, not behavior. A four-tier breakdown of what AWS SCT converts cleanly, what it converts while silently changing meaning, what it only handles through the extension pack, what it refuses outright, and the whole category it never looks at.</p>
<p>The post <a href="https://john-nessime.com/blog/technical-guides/aws-schema-conversion-tool-oracle/">AWS Schema Conversion Tool for Oracle: What Converts and What Quietly Doesn&#8217;t</a> appeared first on <a href="https://john-nessime.com/blog">John Nessime</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">Open the assessment report on a mid-sized Oracle schema and the top-line number looks fantastic. Ninety-something percent of objects convert automatically. The red items are counted, categorized, and given effort estimates. It reads like a project plan you could take to a steering committee.</p>



<p class="wp-block-paragraph">The part that hurts you later is not on that page.</p>



<p class="wp-block-paragraph">The AWS Schema Conversion Tool for Oracle grades <em>syntax</em>. It tells you whether it could emit valid PostgreSQL for an object. It does not tell you whether that object will behave the same way at runtime. A trigger can convert with a green tick and fire in a different order. A column can convert with a green tick and silently drop the time component off every date in the table. A procedure can convert with a green tick and run twenty times slower because every string function call now goes through a wrapper.</p>



<p class="wp-block-paragraph">This post sorts the tool&#8217;s output into four buckets: what converts cleanly, what converts while quietly changing meaning, what converts only through the extension pack, and what it refuses outright. Then the category nobody budgets for, which is everything it never looks at.</p>



<h2 class="wp-block-heading">What the AWS Schema Conversion Tool for Oracle actually does</h2>



<p class="wp-block-paragraph">It is a Java desktop application. It connects over JDBC to source and target, reads the Oracle data dictionary, parses the DDL and PL/SQL it finds, and generates equivalent PostgreSQL objects and PL/pgSQL. The converted code stays inside the project until you explicitly apply it to the target or save it out as SQL files.</p>



<p class="wp-block-paragraph">Three things follow from that description, and they matter more than any feature list.</p>



<ul class="wp-block-list">
<li>It only sees what is stored in the database. Code that lives in your application, your job scheduler, or a reporting tool is invisible to it unless you point the separate application SQL conversion feature at those files yourself.</li>

<li>It only sees what the connecting user can see. Connect with a thin account and half the schema will not appear in the report at all.</li>

<li>It moves structure and code. It does not move rows. That is AWS DMS, and it is a separate exercise with its own failure modes.</li>
</ul>



<p class="wp-block-paragraph">There is also a command line interface, which is the part most teams discover too late. It uses scenario files rather than flag-style arguments, and it needs Amazon Corretto 11 installed alongside the tool.</p>



<pre class="wp-block-code"><code># Interactive mode, run from the AWS SCT app folder.
# End each command with a / on its own line.
java -jar AWSSchemaConversionToolBatch.jar -type interactive

# Script mode, running a saved .scts scenario end to end.
java -jar /opt/aws-schema-conversion-tool/lib/app/AWSSchemaConversionToolBatch.jar 
  -type scts -script /home/sct/project/convert.scts</code></pre>



<p class="wp-block-paragraph">Script mode turns a one-off conversion into something you can rerun, which matters because on a real migration you will convert the schema many times as you clean up the source. Re-clicking through the GUI each time is how mistakes creep in.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">Tier one: converts cleanly, and you can trust it</h2>



<p class="wp-block-paragraph">Credit where it is due. For the bulk of an ordinary OLTP schema the tool is good, and hand-writing this would be miserable.</p>



<ul class="wp-block-list">
<li>Table definitions: names, columns, ordinary data types, NOT NULL, defaults, primary keys, unique constraints, foreign keys.</li>

<li>Standard b-tree indexes, including most single-column functional indexes.</li>

<li>Ordinary views. The basic CREATE VIEW syntax is close to identical between the two engines.</li>

<li>Sequences and identity columns, despite the syntax looking nothing alike.</li>

<li>ANSI-standard SQL, joins, subqueries, and almost all window and analytic functions.</li>

<li>PL/SQL flow control: loops, blocks, conditionals, exception structure.</li>

<li>The four standard transaction isolation levels, plus BEGIN, COMMIT, and ROLLBACK.</li>
</ul>



<p class="wp-block-paragraph">If your schema is mostly tables, views, and reporting SQL with modest procedural code, the automation figure in the report is roughly honest and you should believe it.</p>



<h2 class="wp-block-heading">Tier two: converts green, behaves differently</h2>



<p class="wp-block-paragraph">This is the tier that costs money, because none of it shows up as a red action item. The object converted. The DDL applied. The application connected. Something is wrong three weeks later and nobody thinks to look back at the schema conversion.</p>



<h3 class="wp-block-heading">Oracle DATE is not PostgreSQL date</h3>



<p class="wp-block-paragraph">Oracle&#8217;s DATE type carries a time component down to the second. PostgreSQL&#8217;s <code>date</code> is a calendar day and nothing more. Map an Oracle DATE to a PostgreSQL <code>date</code> and every timestamp in that column is truncated, permanently, without an error. The correct target is <code>timestamp</code>. Check the conversion settings and check the generated DDL before you apply, because this one is not recoverable after the data lands.</p>



<h3 class="wp-block-heading">NUMBER without precision becomes numeric, and numeric is slow</h3>



<p class="wp-block-paragraph">An unqualified Oracle NUMBER is an extremely flexible container, so the default mapping to PostgreSQL <code>numeric</code> is technically correct. It is also arbitrary-precision arithmetic implemented in software. Put that on a surrogate key that participates in every join and index in your application and you have bought yourself a performance problem that no amount of index tuning will fix.</p>



<p class="wp-block-paragraph">Find the candidates before conversion, not after:</p>



<pre class="wp-block-code"><code>-- Oracle: unqualified NUMBER columns, the ones that default to numeric.
SELECT table_name, column_name, data_precision, data_scale
FROM   user_tab_columns
WHERE  data_type = 'NUMBER'
AND    data_precision IS NULL
ORDER  BY table_name, column_name;</code></pre>



<p class="wp-block-paragraph">For each one, look at the actual minimum, maximum, and scale of the data, then decide whether it is really an <code>integer</code>, a <code>bigint</code>, or a genuine <code>numeric(p,s)</code>. Surrogate keys and foreign keys are almost always integers pretending to be NUMBER. Money columns usually are not.</p>



<h3 class="wp-block-heading">Empty string is NULL in Oracle and is not in PostgreSQL</h3>



<p class="wp-block-paragraph">This is the single most reliable source of post-cutover bugs, and it is not a conversion defect. Oracle treats an empty string as NULL. PostgreSQL treats it as a zero-length string, which is a real value. Every <code>IS NULL</code> check in your procedural code and your application was written under Oracle&#8217;s rule.</p>



<p class="wp-block-paragraph">Concatenation flips too. In Oracle, concatenating a NULL is a no-op. In PostgreSQL, concatenating a NULL with the <code>||</code> operator yields NULL for the whole expression. A report that used to print a name with a missing middle initial now prints nothing at all.</p>



<p class="wp-block-paragraph">After the data lands, count the divergence before you let users near it:</p>



<pre class="wp-block-code"><code>-- PostgreSQL: how many rows are empty string rather than NULL?
SELECT count(*) FILTER (WHERE middle_name = '')   AS empty_string,
       count(*) FILTER (WHERE middle_name IS NULL) AS real_null
FROM   app.customer;</code></pre>



<p class="wp-block-paragraph">If <code>empty_string</code> is non-zero anywhere, you have a decision to make: normalize the data on load, or audit every NULL check that touches those columns. Normalizing is usually cheaper and is far easier to verify.</p>



<h3 class="wp-block-heading">The warnings the tool does raise, that people scroll past</h3>



<p class="wp-block-paragraph">Some of these semantic differences do appear in the action list, just not in red. They are worth reading individually rather than in aggregate.</p>



<ul class="wp-block-list">
<li>GREATEST and LEAST are flagged as potentially producing different results from the source, because of how each engine handles NULL arguments and type coercion.</li>

<li>Timestamp handling inside converted partitioned tables is flagged as potentially divergent.</li>

<li>Converted code is flagged as possibly incorrect where bind variable names, user-defined functions, or dynamic SQL are involved. That is the tool telling you it guessed.</li>

<li>Trigger firing order is not carried over. Oracle&#8217;s FOLLOWS and PRECEDES clauses have no equivalent, and PostgreSQL fires triggers on the same event in alphabetical order by trigger name. If your Oracle triggers depended on sequencing, you now depend on naming.</li>

<li>An empty exception block in the converted output is called out explicitly. Oracle code that swallowed errors will now swallow them differently.</li>
</ul>



<p class="wp-block-paragraph">One more that is not an action code at all: Oracle PL/SQL routinely issues COMMIT in the middle of a procedure. A PostgreSQL function cannot do that. Either the logic becomes a procedure invoked with CALL, or the transaction boundary moves out into the calling application. That is a design decision, not a find-and-replace.</p>



<h2 class="wp-block-heading">Tier three: converts, but only through the extension pack</h2>



<p class="wp-block-paragraph">When there is no native PostgreSQL equivalent for something Oracle provides, the tool installs an extension pack: an additional schema on the target, named <code>aws_oracle_ext</code> for Oracle sources, containing wrapper functions, tables, and supporting objects that emulate the source behavior. For some capabilities like job scheduling and sending mail, the pack also deploys AWS Lambda functions.</p>



<p class="wp-block-paragraph">So a call to <code>INSTR</code> in your PL/SQL comes out the other side as <code>aws_oracle_ext.INSTR</code>. Package variables, which have no PostgreSQL equivalent because PL/pgSQL has no packages, get routed through getter and setter functions in the pack.</p>



<p class="wp-block-paragraph">The case for accepting this is real. If the goal is off Oracle licensing by a fixed date, the pack converts code that would otherwise need a developer per object. That is the difference between a migration that ships and one that stalls in refactoring, and nobody gets a bonus for elegant PL/pgSQL on a project that missed its window.</p>



<p class="wp-block-paragraph">The case against is equally real:</p>



<ul class="wp-block-list">
<li>Wrapper functions sit between your code and the engine. In hot loops, that overhead is measurable, and the package-variable emulation in particular is not free.</li>

<li>You have swapped one dependency for another. The pack is AWS-specific code living in your database, which is an awkward thing to explain if the stated goal was ending vendor lock-in.</li>

<li>Every time the pack is applied, its components are overwritten. Anything you hand-tuned inside that schema is gone.</li>

<li>Upgrades and target-version changes now have an extra moving part to validate.</li>
</ul>



<p class="wp-block-paragraph">There is a middle path. The tool can be configured to use the open-source <code>orafce</code> extension, which implements a subset of Oracle functions and packages natively in PostgreSQL, in place of some pack wrappers. My rule of thumb: use the pack to get the migration over the line, then treat the size of the <code>aws_oracle_ext</code> call graph as a debt register you burn down deliberately, starting with whatever appears in your slowest queries. Rewriting collections as native arrays or JSON, and package variables as parameters, buys back most of the performance.</p>



<h2 class="wp-block-heading">Tier four: it stops and hands you the work</h2>



<p class="wp-block-paragraph">These are the red items: the honest part of the report, and paradoxically the least dangerous, because you can see them and plan around them. Not exhaustive, but this is what turns up on real Oracle schemas.</p>



<ul class="wp-block-list">
<li><strong>Database links.</strong> No automation at all. The mechanism has to be rebuilt, usually on <code>postgres_fdw</code>, and foreign data wrappers do not support calling user-defined functions on the remote side. If your architecture leans on links, this is a workstream, not a task.</li>

<li><strong>Autonomous transactions.</strong> PostgreSQL has no direct equivalent. Logging patterns that relied on committing independently of the parent transaction need redesign, often with <code>dblink</code> back to the same instance or an out-of-band queue.</li>

<li><strong>ROWID and UROWID.</strong> No equivalent, and any code that reads ROWID from a table is flagged separately. Row-identity tricks in PL/SQL are a rewrite.</li>

<li><strong>Dynamic SQL and DBMS_SQL.</strong> Anything assembled at runtime the parser cannot resolve statically comes back untouched or with a health warning.</li>

<li><strong>Synonyms.</strong> Not supported, and neither is their usage, so every reference through a synonym has to be resolved by hand or mimicked with views and search paths.</li>

<li><strong>Global temporary tables, index-organized tables, external tables, clustered tables, object tables, nested tables, virtual columns, BFILE.</strong> Storage and type features with no target equivalent.</li>

<li><strong>Bitmap indexes, domain indexes, multi-column functional indexes.</strong> The first two have no counterpart; the third is a documented conversion gap.</li>

<li><strong>Associative arrays, VARRAY of VARRAY, multi-dimensional arrays, object type methods, member functions in user-defined types.</strong> Collection-heavy PL/SQL is where the automation percentage collapses.</li>

<li><strong>Compound triggers, system triggers, REFERENCING clauses, conditional predicates.</strong> The trigger model differs enough that anything beyond a plain row trigger needs attention.</li>

<li><strong>Java stored routines and table functions.</strong> Not converted.</li>

<li><strong>Query hints.</strong> Partially converted, and you should delete them all first anyway. Hints tuned for Oracle&#8217;s optimizer are noise to a different planner. Add them back only after schema, index, and query work have failed.</li>

<li><strong>Materialized view incremental refresh and DML on materialized views.</strong> The view itself may convert; the refresh strategy will not.</li>
</ul>



<h3 class="wp-block-heading">Some red items are more conservative than your actual target</h3>



<p class="wp-block-paragraph">Here is a detail that saves real budget. The action code index and the migration playbook were written against a baseline target, and PostgreSQL has moved since. MERGE is listed as unsupported, but PostgreSQL has had a native MERGE statement since version 15. FOR UPDATE SKIP LOCKED is listed as unsupported, and it has been in PostgreSQL for a long time. GROUPING SETS, CUBE, and ROLLUP are listed as unsupported and are also long-standing PostgreSQL features.</p>



<p class="wp-block-paragraph">What this means practically: an action code tells you the converter will not emit the construct for you. It is not a statement about your target engine. Before you scope a rewrite for anything on that list, check the feature against the PostgreSQL version you are actually deploying. Some of those &#8220;rewrites&#8221; are a copy-paste with a syntax tweak.</p>



<h2 class="wp-block-heading">What it never looks at</h2>



<p class="wp-block-paragraph">This is the category that wrecks timelines, because it is not in the report at all. Not green, not red. Out of scope, therefore invisible.</p>



<ul class="wp-block-list">
<li><strong>Application code.</strong> There is a separate application SQL conversion feature that scans source files for embedded SQL, but it is opt-in, file-based, and you have to aim it. Nothing happens automatically.</li>

<li><strong>Scheduled work.</strong> DBMS_SCHEDULER jobs, cron entries calling SQL*Plus, shell wrappers, ETL controllers. The schema converts; the thing that runs it every night does not.</li>

<li><strong>Grants, roles, profiles, and proxy authentication.</strong> Your security model is a separate design exercise on the target.</li>

<li><strong>Oracle features with no schema footprint.</strong> Data Guard, RAC, ASM, RMAN, Oracle Text indexes, fine-grained access control policies. Each is its own replacement decision.</li>

<li><strong>Optimizer statistics and execution plans.</strong> Nothing about performance carries over. You are starting from zero on the target, and that is the right expectation to set with stakeholders early.</li>

<li><strong>Everything downstream.</strong> BI dashboards, reporting layers, and integration jobs that embed Oracle-flavored SQL will break on the day you cut over, and none of them appear in the assessment.</li>
</ul>



<p class="wp-block-paragraph">Inventory these before conversion, not after. A one-page list of every consumer of the schema is worth more than a week of tuning.</p>



<h2 class="wp-block-heading">Desktop tool or DMS Schema Conversion?</h2>



<p class="wp-block-paragraph">DMS Schema Conversion is the managed, browser-based version built on the same engine. Instead of a laptop install you create an instance profile, register source and target data providers with credentials in Secrets Manager, and run the project from the console, with metadata staged in S3.</p>



<p class="wp-block-paragraph">AWS now points standard OLTP conversions at the managed option, and generative AI assisted conversion is available there on supported paths. The desktop tool remains available and still covers ground the managed feature does not.</p>



<ul class="wp-block-list">
<li><strong>Pick the desktop tool</strong> when you need scripting through the CLI, want to edit the SQL for source and target inside the project, need migration rules and tree filters, are converting a data warehouse target, or want the application SQL and ETL conversion features.</li>

<li><strong>Pick DMS Schema Conversion</strong> when your source is reachable from a VPC, you would rather not manage a workstation with database credentials on it, your conversion path and region are supported, and the generative AI assist is useful to you.</li>
</ul>



<p class="wp-block-paragraph">Check supported paths, versions, and regions before committing either way; they differ between the two. On the desktop side, note the supported operating systems are Windows, Fedora, and Ubuntu, all 64-bit. If your analysts are on Macs, plan for a Linux jump box. A small VPS from a provider like Contabo or InterServer, or an EC2 instance inside the VPC, is the usual answer, and it has the side benefit of keeping the JDBC connection close to the source rather than dragging a dictionary read across a home broadband link.</p>



<p class="wp-block-paragraph">If the Oracle source is on-premises and the conversion box is not, you need a network path that a DBA will sign off on. That is normally a site-to-site VPN or Direct Connect. For an individual consultant working against a client&#8217;s staging environment, a commercial VPN such as NordVPN or Surfshark is sometimes what is on offer, but treat it as a stopgap: it gets you connectivity, not an audit trail.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">Troubleshooting</h2>



<h3 class="wp-block-heading">The CLI jar refuses to open</h3>



<p class="wp-block-paragraph">A common failure is an unexpected error opening <code>AWSSchemaConversionToolBatch.jar</code>. Recent OpenJDK builds cap the size of jar signature files and the SCT jar exceeds the default. Raising that limit on the java invocation is the widely reported workaround:</p>



<pre class="wp-block-code"><code>java -Djdk.jar.maxSignatureFileSize=20000000 
  -jar AWSSchemaConversionToolBatch.jar -type interactive</code></pre>



<p class="wp-block-paragraph">Confirm you are on the Corretto 11 runtime the tool expects before chasing anything more exotic.</p>



<h3 class="wp-block-heading">It hangs on a large schema, or will not connect at all</h3>



<p class="wp-block-paragraph">Thousands of objects in one schema puts the JVM under memory pressure; Global settings exposes JVM options, so raise the heap and retry on a machine with real memory. Separately, JDBC driver paths live in Global settings under Drivers, per engine. A driver sitting on disk but not registered there fails in a way that looks like a network problem and is not.</p>



<h3 class="wp-block-heading">&#8220;The package body doesn&#8217;t include source code&#8221;</h3>



<p class="wp-block-paragraph">The source is wrapped. Obfuscated PL/SQL cannot be parsed, so it cannot be converted. Either find the original in version control or accept a from-scratch rewrite. Finding this on a critical package late in the project is a bad day, so check for wrapped objects on day one.</p>



<h3 class="wp-block-heading">Objects will not apply, or the report is missing things</h3>



<p class="wp-block-paragraph">Failures on apply are usually dependency order: an object references a parent that was not converted or sits outside the schema mapping, and the action list says so explicitly. Convert the whole dependency closure, not just the objects you care about. A short report is usually privileges, since the tool reads through the connecting account&#8217;s visibility. Compare its object counts against a direct dictionary count before trusting any percentage in it.</p>



<h2 class="wp-block-heading">Common mistakes</h2>



<ul class="wp-block-list">
<li>Treating the automation percentage as a completion estimate. It measures objects, not effort, and one gnarly package can outweigh four hundred clean tables.</li>

<li>Applying to the target before reviewing the generated DDL. Save to SQL, read it, diff it on the next run.</li>

<li>Accepting default type mappings wholesale. DATE and unqualified NUMBER are the two that reliably hurt.</li>

<li>Carrying Oracle hints across. Start with none and earn each one back.</li>

<li>Scoping rewrites straight off the action codes without checking your target PostgreSQL version first.</li>

<li>Leaving project files and scenario scripts containing database credentials sitting on a laptop after the engagement. They are plain files; when the work is done, wipe them properly with a tool like O&amp;O SafeErase rather than dragging them to the trash.</li>
</ul>



<h2 class="wp-block-heading">Best practices</h2>



<ol class="wp-block-list">
<li>Connect with an account that can see the whole schema, then verify object counts against the dictionary before reading any percentage.</li>

<li>Profile your NUMBER and DATE columns first and set the type mappings deliberately. Do this before the first conversion, not after the first load.</li>

<li>Script the conversion with the CLI so every run is identical and reviewable.</li>

<li>Sort red action items by object criticality, not by count. Twenty flags on a nightly batch job matter more than two hundred on a deprecated reporting view.</li>

<li>Write the semantic test suite before cutover: empty string versus NULL, date boundaries, numeric rounding, trigger ordering, transaction behavior in converted routines.</li>

<li>Instrument the target from day one. Query-level metrics in Amazon CloudWatch, or a Grafana stack against pg_stat_statements, will show you which extension pack wrappers are actually costing you before users complain.</li>
</ol>



<h2 class="wp-block-heading">Frequently asked questions</h2>



<h3 class="wp-block-heading">Is the AWS Schema Conversion Tool free?</h3>



<p class="wp-block-paragraph">The tool itself is a free download from AWS. What you pay for is everything around it: the compute you run it on, the target database, DMS replication instances for the data move, and the engineering time on the objects it hands back to you. That last item is the whole budget on most migrations.</p>



<h3 class="wp-block-heading">Does it migrate data as well as schema?</h3>



<p class="wp-block-paragraph">No. It converts structure and code. Moving rows is AWS DMS, which handles both a full load and ongoing change capture. They are designed to be used together, and each has its own set of behaviors worth understanding separately.</p>



<h3 class="wp-block-heading">Can I skip the extension pack entirely?</h3>



<p class="wp-block-paragraph">You can, but you are then hand-writing PostgreSQL equivalents for every Oracle built-in the pack would have covered. Configuring the tool to prefer <code>orafce</code> where possible is the usual compromise: fewer AWS-specific wrappers, open-source implementation, still automated. Full independence is achievable and is a refactoring project, not a setting.</p>



<h3 class="wp-block-heading">Why did my report say 95% automatic and the project still take months?</h3>



<p class="wp-block-paragraph">Because the percentage counts objects, ignores runtime semantics entirely, and excludes application code, scheduled jobs, security, and downstream consumers. Tier two and the out-of-scope list in this post are where the months go.</p>



<h3 class="wp-block-heading">Should I clean up the Oracle schema before or after conversion?</h3>



<p class="wp-block-paragraph">Before, every time. Dropping dead objects, unwrapping code you own, and removing synonyms on the source reduces the surface the tool has to reason about and shrinks the red list. It is also the only cleanup you can do while you still have a working system to test against.</p>



<h3 class="wp-block-heading">How do I verify the conversion actually worked?</h3>



<p class="wp-block-paragraph">Row counts and DDL comparison are the floor, not the ceiling. Run the same business queries against both engines and compare results, with deliberate coverage of NULL handling, date boundaries, sorting and collation, and numeric rounding. Anything that only shows up under concurrency needs a load test, not a smoke test.</p>



<h2 class="wp-block-heading">The one thing worth remembering</h2>



<p class="wp-block-paragraph">The AWS Schema Conversion Tool for Oracle is a good parser and an honest one about the things it cannot parse. The red list is trustworthy. The trouble is that a green tick means &#8220;I emitted valid PostgreSQL,&#8221; not &#8220;this behaves the way it did.&#8221; Dates lose their time, unqualified NUMBER lands as slow arithmetic, empty strings stop being NULL, triggers reorder themselves alphabetically, and none of that appears as a problem in the report.</p>



<p class="wp-block-paragraph">Use it for what it is good at, which is the tedium you should never do by hand. Then spend your review time on the objects that passed, because that is where the expensive surprises live. Treat the assessment report as an inventory, never as a schedule.</p>



<h2 class="wp-block-heading">Need a second pair of eyes on an Oracle to PostgreSQL conversion?</h2>



<p class="wp-block-paragraph">Most of the value in this work is in the review, not the click. Things I help with:</p>



<ul class="wp-block-list">
<li>Reading an assessment report properly: separating object counts from real effort, and re-scoping red items against the PostgreSQL version you are actually deploying.</li>

<li>Auditing type mappings before you apply, with a column-level profile of NUMBER and DATE usage and a concrete mapping decision for each.</li>

<li>Building the semantic test suite that catches empty-string and NULL divergence, trigger ordering, and rounding differences before users find them.</li>

<li>Scripting the conversion through the SCT CLI so it is repeatable, reviewable, and lives in version control alongside the generated DDL.</li>

<li>Planning the extension pack burn-down: which <code>aws_oracle_ext</code> calls are costing you, and what the native PostgreSQL replacement looks like.</li>

<li>Mapping the out-of-scope surface, meaning scheduled jobs, grants, downstream reports and integrations, so cutover day has no surprises in it.</li>
</ul>



<p class="wp-block-paragraph">Send me an assessment report, a converted DDL file, or the action item list, and I will tell you what I would actually worry about in it.</p>



<div class="wp-block-buttons is-layout-flex wp-block-buttons-is-layout-flex">
<div class="wp-block-button"><a class="wp-block-button__link wp-element-button" href="https://www.upwork.com/freelancers/~01f15a912ad84a6620" target="_blank" rel="noreferrer noopener">Work with me on Upwork</a></div>
</div>

<p>The post <a href="https://john-nessime.com/blog/technical-guides/aws-schema-conversion-tool-oracle/">AWS Schema Conversion Tool for Oracle: What Converts and What Quietly Doesn&#8217;t</a> appeared first on <a href="https://john-nessime.com/blog">John Nessime</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Primavera P6 vs Unifier vs Aconex vs Textura: Which Oracle Product Do You Actually Need?</title>
		<link>https://john-nessime.com/blog/devops/primavera-p6-vs-unifier-vs-aconex/</link>
		
		<dc:creator><![CDATA[John Nessime]]></dc:creator>
		<pubDate>Fri, 28 Aug 2026 18:00:00 +0000</pubDate>
				<category><![CDATA[Cloud Computing]]></category>
		<category><![CDATA[DevOps]]></category>
		<category><![CDATA[Technical Guides]]></category>
		<category><![CDATA[Architecture]]></category>
		<category><![CDATA[BIM]]></category>
		<category><![CDATA[Capital Projects]]></category>
		<category><![CDATA[Cloud]]></category>
		<category><![CDATA[Common Data Environment]]></category>
		<category><![CDATA[Construction Technology]]></category>
		<category><![CDATA[Cost Optimization]]></category>
		<category><![CDATA[Data Integration]]></category>
		<category><![CDATA[Enterprise Software]]></category>
		<category><![CDATA[Infrastructure]]></category>
		<category><![CDATA[Oracle]]></category>
		<category><![CDATA[Oracle Aconex]]></category>
		<category><![CDATA[Oracle Textura]]></category>
		<category><![CDATA[Primavera Gateway]]></category>
		<category><![CDATA[Primavera P6]]></category>
		<category><![CDATA[Primavera Unifier]]></category>
		<category><![CDATA[Project Controls]]></category>
		<category><![CDATA[REST API]]></category>
		<category><![CDATA[Self Hosting]]></category>
		<category><![CDATA[Software Selection]]></category>
		<category><![CDATA[System of Record]]></category>
		<category><![CDATA[Vendor Lock-In]]></category>
		<guid isPermaLink="false">https://john-nessime.com/blog/?p=252</guid>

					<description><![CDATA[<p>All four Oracle Construction and Engineering products overlap on purpose, and three of them hold cost. The damage comes from owning two and never deciding which one is authoritative. A practical comparison by whose record each product holds, plus a decision procedure and the integration constraints nobody scopes.</p>
<p>The post <a href="https://john-nessime.com/blog/devops/primavera-p6-vs-unifier-vs-aconex/">Primavera P6 vs Unifier vs Aconex vs Textura: Which Oracle Product Do You Actually Need?</a> appeared first on <a href="https://john-nessime.com/blog">John Nessime</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">The forecast in the monthly report doesn&#8217;t match the forecast in the cost system. Someone asks which one is right, and the honest answer is that nobody knows, because there are three places holding a number and nobody ever wrote down which one wins.</p>



<p class="wp-block-paragraph">That conversation is usually the first sign that an organisation bought two Oracle products without deciding what each one was for. It is a common outcome, and it is not because the products are bad. It&#8217;s because they are sold as a suite, they overlap on purpose, and the sales conversation is about capability rather than authority.</p>



<p class="wp-block-paragraph">This post is a working comparison of the four Oracle Construction and Engineering products people most often confuse: <strong>Primavera P6 vs Unifier vs Aconex</strong>, plus Textura. One profile each, what it genuinely owns, where it wins, where it doesn&#8217;t, and then the part nobody covers: how to decide which system holds the authoritative number when three of them offer a cost module.</p>



<h2 class="wp-block-heading">The question that actually sorts them</h2>



<p class="wp-block-paragraph">Feature lists will not separate these products. All four manage documents. Three of them track cost. Two of them hold a schedule. If you compare capability columns you will conclude that you need all four, which is exactly the conclusion the suite is designed to produce.</p>



<p class="wp-block-paragraph">The question that does separate them is: <em>whose record is this, and who else needs to write to it?</em></p>



<p class="wp-block-paragraph">Two of these products are single-organisation systems. You run them, you own the data, and outside parties get in only if you give them a seat and they accept your governance. One of them is deliberately multi-organisation, where every company on the project holds its own workspace and decides what it shares. And one is a transactional network that touches money leaving your bank account, which makes it a finance system wearing a construction badge.</p>



<p class="wp-block-paragraph">Get that axis right and the rest of the decision mostly falls out.</p>



<h2 class="wp-block-heading">Primavera P6: the schedule, and only the schedule</h2>



<p class="wp-block-paragraph">P6 is a critical path method scheduling engine with resource and cost loading on top. It exists to answer questions about time: what drives the finish date, what happens if this activity slips, what does the resource histogram look like if we accelerate.</p>



<p class="wp-block-paragraph">Two things confuse people here. First, P6 comes in two shapes. P6 Professional is the desktop client that planners actually live in. P6 EPPM is the enterprise web layer with the shared database, portfolio views and web services. Most organisations run both against the same database, and most planners will fight you to keep the desktop client.</p>



<p class="wp-block-paragraph">Second, Oracle Primavera Cloud is a separate product, not P6 in a browser. Oracle says so on its own product page, which is unusually direct for vendor copy. Primavera Cloud shares a lineage with P6 and is positioned as the cloud destination, but the data model is not identical and things planners rely on in P6 do not all have equivalents. If someone tells you a move to Primavera Cloud is a hosting change, they have not tried it.</p>



<h3 class="wp-block-heading">Where P6 wins</h3>



<ul class="wp-block-list">
<li>Contractual programme scheduling, where the schedule is a deliverable and gets audited</li>

<li>Forensic delay work, because the XER and PMXML formats are what every expert and every third-party analysis tool expects</li>

<li>Resource-loaded planning across a portfolio with a shared resource pool</li>

<li>Anywhere the client&#8217;s contract names it, which is more places than you would expect</li>
</ul>



<h3 class="wp-block-heading">Where P6 doesn&#8217;t</h3>



<ul class="wp-block-list">
<li>Cost control. P6 has cost fields and they are useful for cash flow shape, but a resource-loaded schedule is not a commitment ledger. It has no concept of a purchase order lifecycle, a variation approval or a payment certificate</li>

<li>Correspondence and document control. There is nothing here for transmittals, RFIs or drawing registers</li>

<li>Collaboration across companies. Subcontractors do not get P6 logins, and you would not want them writing to your programme if they did</li>
</ul>



<h2 class="wp-block-heading">Primavera Unifier: a business process engine that happens to know about projects</h2>



<p class="wp-block-paragraph">Unifier is the one people describe worst, usually as &#8220;the cost one&#8221;. That undersells it and also sets bad expectations.</p>



<p class="wp-block-paragraph">Unifier is a configurable workflow platform for capital programmes. You define business processes: a change order, a funding request, an invoice, a payment application, a site instruction. Each one has a form, a workflow with approval steps and routing rules, and a defined effect on the cost sheet. Oracle ships preconfigured processes and a no-code designer called uDesigner for changing them or building your own. Alongside the business processes sit the managers, including the Cost Manager, Schedule Manager and Document Manager.</p>



<p class="wp-block-paragraph">It&#8217;s sold as base products, principally Project Controls and Facilities and Asset Management, which matters because Unifier&#8217;s remit runs past handover into operating the asset.</p>



<p class="wp-block-paragraph">The honest trade-off with Unifier is this: its configurability is the reason to buy it and the reason implementations run long. A platform that can model any approval workflow will not tell you what your approval workflow should be. If your organisation has not written its cost control process down, Unifier will faithfully automate the argument you are having instead of the process you wanted.</p>



<h3 class="wp-block-heading">Where Unifier wins</h3>



<ul class="wp-block-list">
<li>Owner-side capital programme control, where budget, funding, commitments, forecast and actuals need to roll up across many projects</li>

<li>Anywhere approvals need an audit trail with defined routing, delegation and thresholds</li>

<li>Public sector and utility programmes with fund tracking, where money has strings attached and the strings must be reportable</li>

<li>Organisations that want one governed process applied to every project rather than per-project improvisation</li>
</ul>



<h3 class="wp-block-heading">Where Unifier doesn&#8217;t</h3>



<ul class="wp-block-list">
<li>Supply chain collaboration. It&#8217;s your instance, your governance, your seats. External parties can be given access, but they are guests in your system and they behave like it</li>

<li>Drawing and model control at scale. The Document Manager is real, but it is not a common data environment and it is not built for design coordination</li>

<li>Fast starts. Configuration is a project in its own right, and treating it as an install is the most common way to burn a first year</li>
</ul>



<h2 class="wp-block-heading">Aconex: the project record, owned by nobody in particular</h2>



<p class="wp-block-paragraph">Aconex is a common data environment for correspondence, documents, workflows and models across every organisation on a project. Document control, transmittals, RFIs, submittals, drawing registers, model coordination, field inspections.</p>



<p class="wp-block-paragraph">Its defining property is not a feature. It is the data ownership model. Every organisation on the project gets its own workspace and controls what it shares and when. That neutrality is the entire commercial argument, and it is the thing you cannot replicate by inviting subcontractors into your instance of something else.</p>



<p class="wp-block-paragraph">This matters more than it sounds. A designer who believes the main contractor can read their internal drafts will keep their real work somewhere else, and your &#8220;single source of truth&#8221; quietly becomes a partial one. The reason Aconex accumulates a complete project record is that nobody is afraid of it. Oracle&#8217;s own materials lean on that neutrality hard, and in this case the marketing claim and the engineering consequence are the same thing.</p>



<p class="wp-block-paragraph">On the model side, Aconex Model Coordination is design-neutral and built on open BIM standards including IFC and BCF, so it sits alongside the authoring tools rather than replacing them.</p>



<h3 class="wp-block-heading">Where Aconex wins</h3>



<ul class="wp-block-list">
<li>Multi-party projects where the correspondence record will be read by lawyers later</li>

<li>Design-build and joint venture delivery, where no single party should hold everyone&#8217;s data</li>

<li>Document and model volume, which Oracle&#8217;s datasheets describe as unbounded on data and participants</li>

<li>Handover, because the audit trail and the linked document set are the deliverable</li>
</ul>



<h3 class="wp-block-heading">Where Aconex doesn&#8217;t</h3>



<ul class="wp-block-list">
<li>Portfolio-level capital planning. Aconex is organised around projects, not around a programme of investment decisions</li>

<li>Asset lifecycle after handover. The record ends where the project ends</li>

<li>Deep enterprise cost governance. Connected Cost exists and is genuinely useful for project cost, but if your organisation needs funding, capital planning and multi-year rollup, that is Unifier&#8217;s shape</li>
</ul>



<h2 class="wp-block-heading">Textura: money leaving the building</h2>



<p class="wp-block-paragraph">Textura came into Oracle by acquisition and still feels different from the rest of the suite, because it is solving a different class of problem.</p>



<p class="wp-block-paragraph">Textura Payment Management runs the construction payment cycle: initiating a draw, subcontractor pay applications, markups and approvals, owner billing, collecting lien waivers and compliance documents, enforcing compliance holds, payment authorisation and disbursement. It&#8217;s a network product. Your subcontractors transact in it, and the value comes from them being there.</p>



<p class="wp-block-paragraph">The compliance side is the part that decides whether it&#8217;s worth buying. If your risk is a subcontractor&#8217;s insurance lapsing quietly and nobody noticing until a claim, an automated payment hold tied to a missing document is a control you cannot easily build with a spreadsheet and good intentions.</p>



<h3 class="wp-block-heading">Where Textura wins</h3>



<ul class="wp-block-list">
<li>General contractors with a wide subcontractor base and a monthly draw cycle that eats accounts payable alive</li>

<li>Jurisdictions with real lien exposure, where waiver collection is a legal control rather than paperwork</li>

<li>Compliance enforcement at the point of payment, which is the only moment anyone reliably pays attention</li>

<li>Audit trails on who approved what, signed what and released what</li>
</ul>



<h3 class="wp-block-heading">Where Textura doesn&#8217;t</h3>



<ul class="wp-block-list">
<li>It is not a cost control system. It handles the payment leg, not the budget, forecast or commitment ledger</li>

<li>It does not replace your ERP. It integrates with it, and that integration is part of the implementation cost</li>

<li>Adoption is a supply chain problem, not a software problem. If your subcontractors will not onboard, you own a slower process than the one you replaced</li>
</ul>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">The overlap trap, and why the cost module is where it bites</h2>



<p class="wp-block-paragraph">Here&#8217;s the failure mode that is invisible at procurement and expensive eighteen months later.</p>



<p class="wp-block-paragraph">Three of these products can hold cost. P6 has cost-loaded activities. Unifier has a full Cost Manager. Aconex has Connected Cost. Nobody sets out to run three cost systems, but it happens gradually: the schedule gets cost-loaded because the client asked for an S-curve, the finance team runs the commitment ledger in Unifier, and the project team starts logging variations in Aconex because that&#8217;s where the correspondence already is.</p>



<p class="wp-block-paragraph">Now you have three forecasts. They disagree. Not because anyone is wrong, but because they are answering different questions with different cut-off dates and different accrual rules. And the reconciliation lands in a spreadsheet maintained by one person, which is the outcome every one of these products was bought to prevent.</p>



<p class="wp-block-paragraph">The fix is a decision, not a tool. Before anything is configured, write down one sentence per data class: which system is authoritative, and what the others are allowed to hold.</p>



<ul class="wp-block-list">
<li><strong>Schedule dates and logic:</strong> P6 is authoritative. Everything else receives dates, nothing else edits them</li>

<li><strong>Budget, commitments and forecast:</strong> pick one. If you own Unifier, it is Unifier. If you don&#8217;t, Connected Cost is a reasonable home, but say so out loud</li>

<li><strong>Correspondence and controlled documents:</strong> Aconex if it&#8217;s on the project, and if it is, do not run a parallel drawing register anywhere else</li>

<li><strong>Subcontractor payment and compliance status:</strong> Textura, with the resulting actuals flowing to whichever system owns cost</li>

<li><strong>Everything the authoritative system does not own:</strong> read-only, refreshed by integration, and clearly labelled in reports as a copy</li>
</ul>



<p class="wp-block-paragraph">That list takes an afternoon and saves a year. It&#8217;s also the single most common thing missing from Oracle Primavera implementations I get asked to look at.</p>



<h2 class="wp-block-heading">Integration is the real cost, and it has a shape</h2>



<p class="wp-block-paragraph">Once you own two of these, you own an integration. Budget for it as a workstream, not a checkbox.</p>



<p class="wp-block-paragraph">Oracle offers more than one path. <strong>Primavera Gateway</strong> is the established broker for P6 and Unifier, with built-in providers per application and a canonical intermediate format so mappings survive version changes. In the P6 to Unifier direction, Gateway maps the P6 Project ID to Unifier&#8217;s Project Number, and what flows depends on how the schedule is built: a duration-based schedule sends durations, a resource-loaded one sends durations and units. Oracle also has a lighter integration platform for exchanging data between Unifier, P6, Aconex and Primavera Cloud, so check which one your version and deployment model actually supports before designing anything.</p>



<p class="wp-block-paragraph">Aconex Connected Cost takes a different route, talking to P6 over its web services API. Two constraints from that integration are worth knowing before you draw an architecture diagram:</p>



<ul class="wp-block-list">
<li>You can configure both a P6 and a Primavera Cloud integration for the organisation, but an individual project can only link to one of them at a time</li>

<li>Self-hosted or third-party-hosted P6 servers have to be approved by Oracle and reachable over the internet before the integration can be established. Oracle-hosted P6 skips that approval step</li>
</ul>



<p class="wp-block-paragraph">That second one has teeth if you are running P6 EPPM on your own infrastructure. &#8220;Internet accessible&#8221; and &#8220;exposed&#8221; are not the same thing, and the gap between them is where your security review lives. If you&#8217;re standing up middleware or a reverse proxy in front of an on-premise P6 or Unifier deployment, that box needs to be treated as production: a dedicated host from a provider like Contabo or InterServer rather than a corner of an existing server, TLS terminated properly, access restricted to known networks, and remote administrative access over a business VPN such as NordLayer or Surfshark rather than a port forward someone opened for a consultant and forgot about.</p>



<p class="wp-block-paragraph">Back up the database host properly too, and test a restore. On-premise P6 and Unifier estates are frequently the last thing anyone thinks about until they need it, and imaging tools from vendors like O&amp;O Software earn their keep on exactly this kind of long-lived, rarely-touched server.</p>



<h2 class="wp-block-heading">How I&#8217;d decide</h2>



<p class="wp-block-paragraph">Work through this in order and stop at the first answer that fits. Buying more than you need is far more common than buying too little.</p>



<ol class="wp-block-list">
<li><strong>Name the pain in one sentence.</strong> &#8220;We cannot defend our programme in a delay claim&#8221; points at P6. &#8220;We cannot tell the board what the capital programme will cost&#8221; points at Unifier. &#8220;We cannot find the approved revision&#8221; points at Aconex. &#8220;Subcontractor payments take six weeks and compliance is a filing cabinet&#8221; points at Textura</li>

<li><strong>Count the organisations that must write to the system.</strong> One, and you are choosing between P6 and Unifier. Many, and you are choosing between Aconex and Textura</li>

<li><strong>Ask whether the problem outlives the project.</strong> Assets, maintenance and multi-year capital planning are Unifier&#8217;s territory. Anything that ends at handover is not</li>

<li><strong>Check what the contract already mandates.</strong> Plenty of clients name a scheduling tool or a project CDE in the contract documents. That decision is made, so spend your effort elsewhere</li>

<li><strong>Ask the reseller how each product meters.</strong> Per named user, per project, per transaction or per value processed changes the architecture, not just the invoice. A per-user product pushes you towards fewer, more senior users, and that changes who does data entry</li>

<li><strong>Only then decide on a second product,</strong> and write the system-of-record sentence for every data class the two of them share before signing anything</li>
</ol>



<h2 class="wp-block-heading">Arguments that don&#8217;t survive contact</h2>



<ul class="wp-block-list">
<li><strong>&#8220;Unifier can do document control, so we don&#8217;t need Aconex.&#8221;</strong> Technically true, practically false on multi-party projects. The obstacle is not features, it is that other companies will not put their real correspondence in your instance</li>

<li><strong>&#8220;Aconex has cost, so we don&#8217;t need Unifier.&#8221;</strong> Fine for project-level cost. It falls down when you need portfolio funding, capital planning and asset lifecycle</li>

<li><strong>&#8220;Primavera Cloud is just P6 in the browser.&#8221;</strong> Oracle itself states that Primavera Cloud is not P6. Treat any migration as a data model exercise with a gap analysis, not a lift and shift</li>

<li><strong>&#8220;We&#8217;ll integrate it later.&#8221;</strong> Later means after users have created data under two incompatible breakdown structures. Agree the project and cost breakdown codes on day one, even if the integration itself waits</li>

<li><strong>&#8220;The suite integrates out of the box.&#8221;</strong> Connectors exist and they are good. Mapping your codes, your workflow states and your calendars is still work, and it is work that needs someone who understands both sides</li>
</ul>



<h2 class="wp-block-heading">Practices worth adopting early</h2>



<ul class="wp-block-list">
<li>Standardise the project ID before anything else. Gateway maps P6&#8217;s Project ID to Unifier&#8217;s Project Number, and a free-text naming convention will hurt on the day you connect them</li>

<li>Keep the schedule in one place and let it flow outward. Bidirectional date editing across two systems is a support ticket generator</li>

<li>Pilot Unifier configuration on one real process end to end before configuring twenty. uDesigner makes it easy to build the wrong thing quickly</li>

<li>Onboard the supply chain before go-live on Aconex or Textura. Network products fail on adoption, not on uptime</li>

<li>Export your own data on a schedule. XER and PMXML for schedules, and regular extracts from the cloud products, so you are not negotiating from zero at renewal</li>

<li>Write the system-of-record statement into the implementation charter, where people will actually see it</li>
</ul>



<h2 class="wp-block-heading">FAQ</h2>



<h3 class="wp-block-heading">What is the difference between Primavera P6 and Unifier?</h3>



<p class="wp-block-paragraph">P6 is a critical path scheduling tool that answers questions about time and resources. Unifier is a configurable business process and cost platform that answers questions about money, approvals and governance across a capital programme. They overlap slightly because P6 can hold cost data and Unifier has a Schedule Manager, but neither is a substitute for the other.</p>



<h3 class="wp-block-heading">Do I need Aconex if I already have Unifier?</h3>



<p class="wp-block-paragraph">If the project involves several independent organisations exchanging drawings and correspondence, usually yes. Unifier&#8217;s Document Manager works well for your own records, but external parties are guests in your instance. Aconex gives every organisation its own workspace and control over what it shares, which is what makes the project record complete rather than partial.</p>



<h3 class="wp-block-heading">Is Oracle Primavera Cloud replacing P6?</h3>



<p class="wp-block-paragraph">Oracle positions Primavera Cloud as the cloud platform and publishes migration guidance from P6, while continuing to sell and support P6 EPPM. Oracle also states plainly that Primavera Cloud is not P6. Plan on the two coexisting for now, and treat any migration as a data model exercise with a proper gap analysis rather than a hosting change.</p>



<h3 class="wp-block-heading">Can Textura replace our accounting system?</h3>



<p class="wp-block-paragraph">No. Textura handles the construction payment cycle: pay applications, approvals, lien waivers, compliance holds and disbursement. It integrates with ERP systems rather than replacing them, and that integration is a real line item in the implementation.</p>



<h3 class="wp-block-heading">Which product should a general contractor start with?</h3>



<p class="wp-block-paragraph">It depends on which pain is loudest. If schedules are being disputed, P6. If the project record is scattered across email, Aconex. If the monthly draw cycle and subcontractor compliance are consuming the finance team, Textura. Contractors reach for Unifier less often than owners do, because Unifier&#8217;s strengths are programme-level funding and governance.</p>



<h3 class="wp-block-heading">How hard is it to integrate P6 with Unifier?</h3>



<p class="wp-block-paragraph">The mechanism is well trodden through Primavera Gateway, which ships providers for both. The hard part is not the connector, it&#8217;s agreement: matching project identifiers, deciding which system owns dates, and reconciling breakdown structures that were designed independently. Expect the mapping work to take longer than the technical setup.</p>



<h3 class="wp-block-heading">What data should I be exporting for portability?</h3>



<p class="wp-block-paragraph">For schedules, take regular XER and PMXML exports, since those formats are what independent analysis tools and delay experts read. For the cloud products, set up scheduled extracts of documents, transaction records and audit trails. The point is not distrust of the vendor, it&#8217;s that a project record you cannot read without an active subscription is a liability during a dispute.</p>



<h2 class="wp-block-heading">The one thing worth remembering</h2>



<p class="wp-block-paragraph">The <strong>Primavera P6 vs Unifier vs Aconex</strong> comparison, with Textura alongside them, is not really a features question. Each of these products is good at the thing it was built for, and Oracle is not trying to trick anyone by giving three of them a cost module.</p>



<p class="wp-block-paragraph">The damage comes from owning two of them and never deciding which one is authoritative. Sort the products by whose record they hold and how many organisations write to them, pick the smallest set that covers your loudest pain, and write down the system of record for every data class before a single workflow gets configured.</p>



<p class="wp-block-paragraph">Do that and the monthly report matches the cost system. Skip it and you&#8217;ll meet the spreadsheet.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">Need help untangling this?</h2>



<p class="wp-block-paragraph">Most of the work in this area is not clicking through screens, it&#8217;s the plumbing and the decisions around it. That part I can help with:</p>



<ul class="wp-block-list">
<li>Writing the system-of-record map for your data classes before you commit to a second Oracle product, so cost and dates have one owner each</li>

<li>Designing and building integrations between P6, Unifier, Aconex, Textura and your ERP, including the field mapping and error handling nobody scopes</li>

<li>Hardening on-premise P6 EPPM or Unifier deployments that need to be reachable for a cloud integration, with proper TLS, network restrictions and audited access</li>

<li>Automating schedule and cost data extraction into a warehouse or BI layer, so reporting stops depending on someone manually exporting on a Friday</li>

<li>Building repeatable backup, restore-testing and disaster recovery for self-hosted Primavera estates</li>

<li>Second-opinion reviews on an implementation or integration design that already exists and is not behaving</li>
</ul>



<p class="wp-block-paragraph">If you&#8217;re stuck on something concrete, send it over: an integration mapping spec, a Gateway error, a network diagram for an on-premise P6 that needs to talk to a cloud service. Easier to give you something useful when I can see the actual thing.</p>



<div class="wp-block-buttons is-layout-flex wp-block-buttons-is-layout-flex">
<div class="wp-block-button"><a class="wp-block-button__link wp-element-button" href="https://www.upwork.com/freelancers/~01f15a912ad84a6620" target="_blank" rel="noreferrer noopener">Work with me on Upwork</a></div>
</div>
<p>The post <a href="https://john-nessime.com/blog/devops/primavera-p6-vs-unifier-vs-aconex/">Primavera P6 vs Unifier vs Aconex vs Textura: Which Oracle Product Do You Actually Need?</a> appeared first on <a href="https://john-nessime.com/blog">John Nessime</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Oracle to Aurora Migration: The Failures Nobody Catches Until Cutover</title>
		<link>https://john-nessime.com/blog/devops/oracle-to-aurora-migration/</link>
		
		<dc:creator><![CDATA[John Nessime]]></dc:creator>
		<pubDate>Mon, 17 Aug 2026 13:00:00 +0000</pubDate>
				<category><![CDATA[Cloud Computing]]></category>
		<category><![CDATA[DevOps]]></category>
		<category><![CDATA[Technical Guides]]></category>
		<category><![CDATA[Amazon Aurora]]></category>
		<category><![CDATA[AWS]]></category>
		<category><![CDATA[AWS DMS]]></category>
		<category><![CDATA[Change Data Capture]]></category>
		<category><![CDATA[Cloud]]></category>
		<category><![CDATA[Cutover Planning]]></category>
		<category><![CDATA[Data Integration]]></category>
		<category><![CDATA[Database Migration]]></category>
		<category><![CDATA[Infrastructure]]></category>
		<category><![CDATA[Oracle]]></category>
		<category><![CDATA[PostgreSQL]]></category>
		<category><![CDATA[Schema Conversion]]></category>
		<category><![CDATA[SQL]]></category>
		<guid isPermaLink="false">https://john-nessime.com/blog/?p=218</guid>

					<description><![CDATA[<p>Most Oracle to Aurora migrations don't fail loudly. They finish green, pass validation, and quietly hand you truncated LOBs, sequences stuck at 1, and empty strings that used to be NULL. Here are the four failure families to check before you cut over, with the SQL and task settings that catch them.</p>
<p>The post <a href="https://john-nessime.com/blog/devops/oracle-to-aurora-migration/">Oracle to Aurora Migration: The Failures Nobody Catches Until Cutover</a> appeared first on <a href="https://john-nessime.com/blog">John Nessime</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">Three weeks after cutover, someone in finance sends you a spreadsheet. Forty-one invoice records have a description field that stops mid-sentence. Not blank, not garbled. Just shorter than it used to be.</p>



<p class="wp-block-paragraph">So you open the AWS DMS console. The task is green. Full load completed, change data capture running, zero errors. Data validation passed on every table in scope.</p>



<p class="wp-block-paragraph">Both of those things are true at the same time, and that is the part that catches people out. An Oracle to Aurora migration almost never fails with a stack trace and a rollback. It fails with a green dashboard and a handful of rows that are subtly, permanently wrong.</p>



<p class="wp-block-paragraph">This post is not a walkthrough of the console wizard. AWS documents that well enough. It covers the four families of failure I would go looking for before signing off on an Oracle to Aurora migration: large objects that arrive truncated, change capture that records less than you assume, the semantic gaps between Oracle and PostgreSQL that compile cleanly and behave differently, and the database state that DMS simply does not move for you. Then how to validate properly, and what to do when a task stalls.</p>



<h2 class="wp-block-heading">Pick the Aurora engine before you pick the migration tool</h2>



<p class="wp-block-paragraph">Amazon Aurora comes in a PostgreSQL-compatible edition and a MySQL-compatible edition, and the choice shapes everything downstream. Do not let it get decided by whoever on the team happens to like MySQL.</p>



<p class="wp-block-paragraph">Aurora PostgreSQL is the realistic default coming from Oracle, and the reason is procedural code. PL/SQL and PL/pgSQL are close enough that automated conversion gets you a long way: packages, cursors, exceptions, custom types, and analytic functions all have recognisable counterparts. PostgreSQL also has the <code>orafce</code> extension, which reimplements a chunk of Oracle&#8217;s built-in functions and packages inside a compatibility schema, so a lot of converted code can keep calling things it already calls.</p>



<p class="wp-block-paragraph">The honest case for Aurora MySQL: if the Oracle database is essentially a table store with the business logic living in the application tier, and your team already runs MySQL in production, you will move faster on the engine you can operate at 3am. Migration difficulty is real, but so is the cost of running a database nobody on the rota understands. If your schema has no packages, no triggers worth keeping and no PL/SQL, that argument holds.</p>



<p class="wp-block-paragraph">Where it falls apart is the moment you find twelve thousand lines of PL/SQL that someone will have to rewrite by hand. At that point the engine choice has stopped being an operational preference and become a rewrite project.</p>



<h3 class="wp-block-heading">Schema conversion: SCT or the DMS console</h3>



<p class="wp-block-paragraph">There are two ways to convert the schema. The AWS Schema Conversion Tool is a desktop Java application that connects to both databases, reads the source objects, and emits target DDL plus an assessment report of everything it could not convert. DMS Schema Conversion is the same idea run from the DMS console using instance profiles and data providers, with no local install and no JDBC drivers to manage.</p>



<p class="wp-block-paragraph">Start with the console version. It is less setup and the assessment report is the thing you actually want early: a list of objects with action items, so you can size the manual work before committing to a date. Fall back to the desktop tool when you need something the console flow does not cover, such as converting application SQL files or SQL*Plus scripts.</p>



<p class="wp-block-paragraph">Either way, treat the assessment report as a project plan, not a progress bar. The red items are the schedule.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">Failure family one: LOB columns that arrive shorter than they left</h2>



<p class="wp-block-paragraph">This is the invoice description problem, and it is the single most common way an Oracle to Aurora migration loses data without telling anyone loudly.</p>



<p class="wp-block-paragraph">DMS gives you three ways to handle CLOB, NCLOB and BLOB columns:</p>



<ul class="wp-block-list">
<li><strong>Full LOB mode</strong> moves every LOB regardless of size. DMS has no idea how big they are, so it moves them one at a time, piece by piece. Correct, and slow.</li>



<li><strong>Limited LOB mode</strong> asks you for a maximum size up front. DMS pre-allocates memory and loads LOBs in bulk, which is dramatically faster. Anything larger than the limit is <em>truncated to the limit</em>, and a warning goes into the task log.</li>



<li><strong>Inline LOB mode</strong> sets a threshold. LOBs under it go inline with the row; larger ones fall back to the full LOB path with a lookup against the source. Good when most of your LOBs are small and a few are not.</li>
</ul>



<p class="wp-block-paragraph">Read the middle one again. Limited LOB mode is the default answer to &#8220;the full load is too slow&#8221;, it is the mode most tutorials use, and it silently clips your data. The warning lands in the task log, mixed in with everything else, and nobody is reading task logs line by line on a table with four million rows.</p>



<p class="wp-block-paragraph">Worse, the task still reports success. Truncation is not an error condition in DMS. It is expected behaviour for the mode you asked for.</p>



<h3 class="wp-block-heading">Measure before you set the limit</h3>



<p class="wp-block-paragraph">The fix is boring: go and find out how big your LOBs actually are, per column, on the source. Run this against Oracle for every LOB column in scope.</p>



<pre class="wp-block-code"><code>-- Largest LOB values in a single column, biggest first.
-- dbms_lob.getlength returns length in characters for CLOB/NCLOB,
-- bytes for BLOB. Check the top rows, not the average.
SELECT dbms_lob.getlength(description) AS lob_length
FROM   app.invoices
ORDER  BY dbms_lob.getlength(description) DESC
FETCH  FIRST 10 ROWS ONLY;

-- Same thing expressed in KB, which is the unit the DMS
-- "Max LOB size (K)" setting expects.
SELECT MAX(dbms_lob.getlength(description)) / 1024 AS max_kb
FROM   app.invoices;</code></pre>



<p class="wp-block-paragraph">Two things to know once you have the numbers.</p>



<p class="wp-block-paragraph">First, with Oracle as a source, DMS treats LOBs as VARCHAR data wherever it can, because bulk-fetching them is much faster than the LOB API. Oracle&#8217;s VARCHAR ceiling is 32K, so a limited LOB size under 32K is where the mode performs best. If your real maximum is comfortably under that, limited LOB mode is genuinely the right call.</p>



<p class="wp-block-paragraph">Second, memory. During full load, DMS pre-allocates roughly the max LOB size multiplied by the commit rate multiplied by the number of LOB columns. Push the limit up and that product grows fast. When the replication instance cannot allocate it, it starts swapping, and your &#8220;faster&#8221; mode becomes slower than full LOB mode. If limited LOB mode is dragging, drop the commit rate before you drop the size limit.</p>



<h3 class="wp-block-heading">Split the LOB tables into their own task</h3>



<p class="wp-block-paragraph">The pattern I reach for first: one task with limited LOB mode for the bulk of the schema, a second task for the handful of tables with genuinely large LOBs, using full or inline mode. You can also override task-level LOB settings per table inside the table mapping rules, which keeps it to a single task if you prefer.</p>



<pre class="wp-block-code"><code>{
  "TargetMetadata": {
    "SupportLobs": true,
    "FullLobMode": false,
    "LimitedSizeLobMode": true,
    "LobMaxSize": 16,
    "InlineLobMaxSize": 0,
    "LobChunkSize": 64,
    "BatchApplyEnabled": false
  }
}</code></pre>



<p class="wp-block-paragraph">That is limited mode with a 16 KB ceiling. To use inline mode instead, set <code>FullLobMode</code> to true and give <code>InlineLobMaxSize</code> a non-zero value, which is the threshold below which LOBs travel inline.</p>



<p class="wp-block-paragraph">One requirement that bites during ongoing replication: for CDC, tables with LOB columns need a primary key, because DMS looks the LOB value up in the source rather than reading it out of the redo stream. Full load does not care. CDC does. A LOB table with no primary key will load fine and then fail to replicate changes.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">Failure family two: CDC that captures less than you think</h2>



<p class="wp-block-paragraph">Ongoing replication is what buys you a short cutover window. It is also where the assumptions hide.</p>



<p class="wp-block-paragraph">DMS reads Oracle&#8217;s redo logs. For that to work at all, the source has to be in ARCHIVELOG mode with enough retention that DMS can still find the logs it needs after a pause, a network blip or an overnight stall. Retention is the one people get wrong: a task that stops for six hours against a two-hour retention window does not resume, it fails, and you restart from a full load.</p>



<h3 class="wp-block-heading">Supplemental logging is not optional</h3>



<p class="wp-block-paragraph">Oracle&#8217;s redo logs, by default, contain enough information for Oracle to recover the database. That is not the same as enough information to reconstruct a row-level change for a different database. Supplemental logging is what closes that gap.</p>



<pre class="wp-block-code"><code>-- Minimal supplemental logging at the database level.
ALTER DATABASE ADD SUPPLEMENTAL LOG DATA;

-- Identification key logging: writes every column of a row's
-- primary key into redo on UPDATE, even when the key didn't change.
-- Without this, DMS cannot reliably match the row on the target.
ALTER DATABASE ADD SUPPLEMENTAL LOG DATA (PRIMARY KEY) COLUMNS;

-- Per-table alternative, if you only need a subset of tables
-- and want to keep the source overhead down.
ALTER TABLE app.invoices ADD SUPPLEMENTAL LOG DATA (PRIMARY KEY) COLUMNS;</code></pre>



<p class="wp-block-paragraph">The case that actually bites is a table with no primary key and no unique index. For those, key-level logging gives DMS nothing to work with, and it needs supplemental logging on <em>all</em> columns so the before-and-after image of the row lands in redo. Miss that, and DMS cannot build a safe UPDATE or DELETE statement for the target. You will not notice during full load. You will notice when an update on the source produces no change on the target, or changes the wrong row.</p>



<p class="wp-block-paragraph">DMS ships premigration assessments that check exactly this. Run them. There are individual checks for supplemental logging on keyed tables, supplemental logging on all columns for unkeyed tables, ARCHIVELOG configuration, the CDC method in use, target permissions, and triggers enabled on the target. It takes minutes and it replaces a category of guesswork.</p>



<h3 class="wp-block-heading">LogMiner or Binary Reader</h3>



<p class="wp-block-paragraph">DMS can read redo two ways. Oracle LogMiner is an Oracle-side API; DMS calls it and Oracle does the parsing. Binary Reader is DMS&#8217;s own parser reading the redo files directly.</p>



<p class="wp-block-paragraph">If you specify nothing, you get LogMiner. That is usually correct. It needs fewer privileges, it handles ASM without extra plumbing, and it supports Oracle features like encryption and compression that the direct parser does not cover. The grants are small:</p>



<pre class="wp-block-code"><code>GRANT EXECUTE ON DBMS_LOGMNR TO dms_user;
GRANT SELECT ON V_$LOGMNR_LOGS TO dms_user;
GRANT SELECT ON V_$LOGMNR_CONTENTS TO dms_user;
GRANT LOGMINING TO dms_user;</code></pre>



<p class="wp-block-paragraph">Binary Reader earns its keep in one situation: a source generating redo faster than LogMiner can chew through it, or several concurrent migration tasks against the same database. LogMiner runs inside the source instance, so its CPU cost lands on the database you are trying not to disturb. Binary Reader shifts that work to the replication instance.</p>



<p class="wp-block-paragraph">The trade-off is setup. Binary Reader needs additional privileges and file-level access to the redo logs, which means either a directory the replication instance can reach or a copy step. If your source is a busy production Oracle instance and CDC latency is climbing steadily rather than spiking, that is the signal to switch. Otherwise stay on LogMiner and spend the complexity budget elsewhere.</p>



<p class="wp-block-paragraph">Whichever you pick, put CDC latency on a dashboard for the whole replication window, not just cutover day. The DMS task metrics publish to CloudWatch, and pulling them into whatever you already run for alerting, whether that is Grafana Cloud, Datadog or New Relic, is worth the twenty minutes. Source latency climbing while target latency stays flat means you are not reading redo fast enough. Both climbing together usually means the target cannot apply fast enough.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">Failure family three: the semantics Oracle and PostgreSQL do not share</h2>



<p class="wp-block-paragraph">Schema conversion produces code that compiles. Compiling is not the same as behaving identically, and this is where a migration goes wrong in ways no tool flags.</p>



<h3 class="wp-block-heading">Empty string is not NULL</h3>



<p class="wp-block-paragraph">Oracle treats the empty string and NULL as the same thing. PostgreSQL does not. This one difference propagates into comparisons, concatenation, unique constraints, and any procedural code that checks whether a value is &#8220;empty&#8221;.</p>



<pre class="wp-block-code"><code>-- PostgreSQL
SELECT '' IS NULL;        -- false
SELECT NULL || 'abc';     -- NULL
SELECT '' || 'abc';       -- abc

-- Oracle
SELECT CASE WHEN '' IS NULL THEN 'yes' ELSE 'no' END FROM dual;  -- yes
SELECT NULL || 'abc' FROM dual;                                  -- abc
SELECT '' || 'abc' FROM dual;                                    -- abc</code></pre>



<p class="wp-block-paragraph">Every one of those lines is a place a report can quietly change its numbers. Unique constraints are worse. Oracle stores an empty string as NULL, and NULLs are not compared for uniqueness, so it will happily accept many rows with an empty value in a uniquely constrained column. PostgreSQL sees zero-length strings, which are equal to each other, so the second one is rejected. Same schema, same data, different outcome, and the failure surfaces as an application error nobody can reproduce locally.</p>



<p class="wp-block-paragraph">There is no switch for this. Either the application changes, or you wrap the affected functions, or you normalise on write with a trigger or a check constraint. Decide which, per column, before cutover. The orafce extension helps with function behaviour but it is not a global compatibility mode, and enabling the parts that change NULL semantics has its own consequences.</p>



<h3 class="wp-block-heading">The rest of the list</h3>



<ul class="wp-block-list">
<li><strong>ROWID</strong> has no PostgreSQL equivalent. DMS Schema Conversion can emulate it with a bigint or a character varying column, but if application code depends on ROWID ordering or reuse semantics, emulation will not save you.</li>



<li><strong>SYSDATE and time zones.</strong> Conversion settings let you choose whether to emulate Oracle&#8217;s time zone handling or use native PostgreSQL behaviour. Native is faster. It is only safe if the database and the application genuinely run in the same time zone.</li>



<li><strong>TO_CHAR, TO_DATE, TO_NUMBER format masks.</strong> Oracle accepts parameters PostgreSQL does not. The converter emulates them by default, which is correct and slower. Turning emulation off is a per-codebase decision, not a global one.</li>



<li><strong>DUAL</strong> does not exist. PostgreSQL does not need a FROM clause, so most references just get dropped, but any dynamic SQL that builds a query string around DUAL needs finding.</li>



<li><strong>Synonyms</strong> have no direct equivalent. Search path adjustments and views cover most cases.</li>



<li><strong>Materialized views</strong> can be converted to real materialized views or to plain tables. The refresh semantics differ, so check what the reporting layer expects.</li>



<li><strong>NUMBER without a scale.</strong> An Oracle NUMBER column with no scale specified converts to something with a default, and if the source holds values wider than that default you get rounding or overflow. There is a premigration assessment specifically for this.</li>
</ul>



<p class="wp-block-paragraph">One data-level quirk worth knowing: if an Oracle column contains a NULL character (hex U+0000), DMS converts it to a space (U+0020) on a PostgreSQL target, because PostgreSQL cannot store it in a text column. Rare, but if you are migrating anything that stuffed binary into a VARCHAR, that is a real change to your data.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">Failure family four: the state DMS never moves</h2>



<p class="wp-block-paragraph">DMS moves rows. It does not move everything that makes a database work, and the gaps are not obvious until you point traffic at the new cluster.</p>



<h3 class="wp-block-heading">Sequences</h3>



<p class="wp-block-paragraph">Schema conversion creates the sequences. DMS copies the rows. Nothing advances the sequence counters to match the data that just arrived, so your target sequences sit at their starting value while the table already contains eight million rows.</p>



<p class="wp-block-paragraph">The first insert after cutover collides with an existing primary key. Then the second. Then every write for the next eight million attempts.</p>



<p class="wp-block-paragraph">Fix this after you stop replication and before you open traffic, not earlier, or you will do it twice.</p>



<pre class="wp-block-code"><code>-- Advance a sequence to match the data already loaded.
-- Run once per sequence, after CDC has stopped.
SELECT setval('app.invoices_id_seq',
              (SELECT max(id) FROM app.invoices));

-- Confirm before you open the doors.
SELECT last_value FROM app.invoices_id_seq;</code></pre>



<h3 class="wp-block-heading">Triggers on the target</h3>



<p class="wp-block-paragraph">If your converted schema has triggers and they are enabled during the load, they fire on every row DMS inserts. Audit tables get populated with a migration&#8217;s worth of fake activity. Derived columns get recalculated against half-loaded data. Anything that writes to another table doubles your write volume.</p>



<p class="wp-block-paragraph">Disable target triggers for the load, re-enable at cutover. There is a premigration assessment that checks for enabled triggers on target tables in task scope, which is a decent backstop against forgetting.</p>



<h3 class="wp-block-heading">Secondary indexes and foreign keys</h3>



<p class="wp-block-paragraph">Every secondary index present during full load is an index being maintained row by row while you are trying to move data as fast as possible. Foreign keys are worse, because DMS does not guarantee parent-before-child load order across tables.</p>



<p class="wp-block-paragraph">Standard sequence: drop or disable secondary indexes and foreign key constraints before full load, recreate them afterwards, then let CDC catch up. Keep primary keys, since CDC needs them. Script the recreation and check it into version control alongside everything else, because rebuilding forty indexes from memory at 2am is not a plan.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">Validation you can actually trust</h2>



<p class="wp-block-paragraph">DMS data validation compares source and target row by row and reports mismatches. It supports Oracle and PostgreSQL-compatible endpoints in both directions, so an Oracle to Aurora PostgreSQL path is covered. Turn it on.</p>



<p class="wp-block-paragraph">Then understand what it will not tell you.</p>



<ul class="wp-block-list">
<li>It needs a primary key or unique index on both sides. Unkeyed tables are not validated, and those are exactly the tables most likely to have gone wrong.</li>



<li>Collation and sort order differ between Oracle and PostgreSQL. When they disagree, validation reports failures on records that are actually fine, and the noise trains people to ignore the report.</li>



<li>It does not run when the migration uses custom filtering, or when you are consolidating several source databases into one target.</li>



<li>It costs real resources. Validation issues its own queries against both databases, on top of the migration traffic. Budget for that on the source.</li>



<li>For LOB columns it compares checksums rather than values, using DBMS_CRYPTO on the Oracle side.</li>
</ul>



<p class="wp-block-paragraph">Because validation runs alongside migration, a task that stops takes validation with it. Validation-only tasks solve that: same endpoints, same table mappings, no data movement, running independently of the migration task. Use one when the validation load is hurting the source and you want to run it off-peak, or when you want validation to survive a migration task restart.</p>



<p class="wp-block-paragraph">None of that replaces your own reconciliation. Before cutover, run business-level checks on both sides: rows per table, sums of every monetary column, min and max of every date column, distinct counts on anything you join on. Those catch things row comparison does not, because they answer the question the business actually asks.</p>



<p class="wp-block-paragraph">And specifically for the LOB problem, compare maximum lengths, not just row counts.</p>



<pre class="wp-block-code"><code>-- On Aurora PostgreSQL, after the load.
-- Compare this against dbms_lob.getlength on the Oracle source.
SELECT count(*)                 AS row_count,
       max(length(description)) AS max_len,
       count(*) FILTER (WHERE description IS NULL) AS null_count
FROM   app.invoices;</code></pre>



<p class="wp-block-paragraph">If the target maximum is suspiciously round, exactly 16384 or exactly 32768, you have found your truncation.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">Troubleshooting an Oracle to Aurora migration that stalls</h2>



<h3 class="wp-block-heading">Full load runs, then one table sits at zero</h3>



<p class="wp-block-paragraph">Almost always LOBs. Full LOB mode moves large objects one at a time with a source lookup per value, and on a table with a million large CLOBs that is a very long time with no visible progress. Check the table statistics for that table, then check whether it has LOB columns. Move it to its own task with limited or inline mode and an appropriate size.</p>



<h3 class="wp-block-heading">CDC latency climbs and never comes down</h3>



<p class="wp-block-paragraph">Look at which latency is climbing. Source latency means redo is arriving faster than DMS can read it, which is the LogMiner to Binary Reader conversation, or a redo volume problem on the source. Target latency means Aurora cannot apply fast enough, which points at indexes still present, triggers still enabled, or an undersized writer instance.</p>



<h3 class="wp-block-heading">Task fails on restart with missing archived logs</h3>



<p class="wp-block-paragraph">Retention. The logs DMS needed were removed before it came back. Increase archived log retention on the source to comfortably exceed your worst realistic outage, then restart from a fresh full load for the affected tables. There is no recovering the gap.</p>



<h3 class="wp-block-heading">Updates on the source do not appear on the target</h3>



<p class="wp-block-paragraph">Supplemental logging, nine times out of ten. Check whether the affected table has a primary key or unique index, and whether logging is configured at the right level for that answer. The premigration assessments cover both cases.</p>



<h3 class="wp-block-heading">Errors about a unique index on the target</h3>



<p class="wp-block-paragraph">DMS does not support replication to a table with a unique index built on a <code>coalesce</code> expression. If schema conversion produced one, or someone added it by hand to work around the empty-string problem, replication to that table will not work. Restructure the constraint.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">Common mistakes</h2>



<ul class="wp-block-list">
<li>Accepting the default limited LOB size without measuring the source. This is the truncation bug, and it has a specific, preventable cause.</li>



<li>Treating a green task and a passed validation as proof of correctness. Both are true when data has been truncated.</li>



<li>Skipping the premigration assessments because the wizard let you continue without them.</li>



<li>Forgetting to advance sequences, then discovering it under production write load.</li>



<li>Leaving triggers enabled on the target during full load and polluting audit tables.</li>



<li>Assuming schema conversion handled empty strings. It converted the syntax, not the semantics.</li>



<li>Setting archived log retention to the length of a normal outage rather than a bad one.</li>



<li>Doing the first end-to-end rehearsal on cutover night.</li>
</ul>



<h2 class="wp-block-heading">Best practices</h2>



<ol class="wp-block-list">
<li><strong>Run the assessment report first and size the manual work from it.</strong> The red items are your timeline, and they are better known in week one than week nine.</li>



<li><strong>Inventory LOB columns before configuring anything.</strong> Maximum size per column, in KB, written down.</li>



<li><strong>Separate the LOB-heavy tables into their own task.</strong> Different tables want different modes, and one global setting will always be wrong for someone.</li>



<li><strong>Enable supplemental logging deliberately, per table class.</strong> Key-level for keyed tables, all-columns for unkeyed ones.</li>



<li><strong>Drop secondary indexes and foreign keys for the load.</strong> Script the recreation, version it, test it.</li>



<li><strong>Rehearse the whole cutover at least twice</strong>, including the sequence advance and the trigger re-enable. A scratch environment for the dry run does not have to be expensive: a temporary Aurora cluster you tear down afterwards, or a cheap dedicated box from somewhere like InterServer or Hetzner running a trimmed source copy, both work.</li>



<li><strong>Run your own reconciliation alongside DMS validation.</strong> Counts, sums, min and max dates, maximum LOB lengths.</li>



<li><strong>Keep the Oracle source running and readable for a while after cutover.</strong> The bugs that surface at week three are the ones you can only diagnose by comparing against the original.</li>
</ol>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">Frequently asked questions</h2>



<h3 class="wp-block-heading">Should I migrate Oracle to Aurora PostgreSQL or Aurora MySQL?</h3>



<p class="wp-block-paragraph">Aurora PostgreSQL for anything with meaningful PL/SQL, because PL/pgSQL is close enough that automated conversion does most of the work, and the orafce extension covers a chunk of the rest. Aurora MySQL is defensible when the schema is plain tables, the logic lives in the application, and your team already operates MySQL. The deciding question is how many lines of procedural code you would have to rewrite by hand.</p>



<h3 class="wp-block-heading">Can AWS DMS migrate stored procedures and packages?</h3>



<p class="wp-block-paragraph">No. DMS moves data. Schema and code conversion is a separate job, handled by DMS Schema Conversion in the console or the AWS Schema Conversion Tool on the desktop. Both convert most objects automatically and produce an action-item list for the rest.</p>



<h3 class="wp-block-heading">Why is my DMS task green when data is missing?</h3>



<p class="wp-block-paragraph">Because truncation in limited LOB mode is expected behaviour, not an error. DMS writes a warning to the task log and carries on. Task status reflects whether the task is running, not whether the data is faithful. Compare maximum LOB lengths between source and target to catch it.</p>



<h3 class="wp-block-heading">Do I need downtime for an Oracle to Aurora migration?</h3>



<p class="wp-block-paragraph">Some, but it can be short. Full load plus CDC means the target stays current while you test, so the outage is limited to stopping writes, letting CDC drain, advancing sequences, re-enabling triggers, recreating indexes and repointing the application. Rehearse it and that usually fits a maintenance window rather than a weekend.</p>



<h3 class="wp-block-heading">Does DMS migrate sequences?</h3>



<p class="wp-block-paragraph">It copies row data, not sequence state. Schema conversion creates the sequence objects; you advance them yourself after stopping replication, using <code>setval</code> against the current maximum key value. Skipping this produces primary key collisions on the first write after cutover.</p>



<h3 class="wp-block-heading">LogMiner or Binary Reader for Oracle CDC?</h3>



<p class="wp-block-paragraph">LogMiner unless you have a reason. It is the default, needs fewer privileges, and handles ASM, encryption and compression without extra work. Move to Binary Reader when redo volume is high enough that LogMiner cannot keep up, when you are running several tasks against the same source, or when the CPU cost of parsing on the source database is unacceptable.</p>



<h3 class="wp-block-heading">Will DMS data validation catch every problem?</h3>



<p class="wp-block-paragraph">No. It needs a primary key or unique index on both sides, it can report false failures where Oracle and PostgreSQL collations disagree, and it does not run with custom filtering or multi-source consolidation. Treat it as one layer and add business-level reconciliation on top.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">The one thing worth remembering</h2>



<p class="wp-block-paragraph">An Oracle to Aurora migration does not usually announce its failures. The task goes green, validation passes, and the damage shows up weeks later as a truncated field, a duplicate key, a report whose totals moved slightly.</p>



<p class="wp-block-paragraph">So build the migration around proving correctness rather than proving completion. Measure your LOBs before you set a limit. Configure supplemental logging for the tables that have no key, not just the ones that do. Assume the semantic gaps between Oracle and PostgreSQL are still there after conversion, because they are. Advance the sequences. Then reconcile at the level the business cares about, not just row by row.</p>



<p class="wp-block-paragraph">Green means the pipe is open. It does not mean what came out the other end is what went in.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">Need a second pair of eyes on your Oracle to Aurora migration?</h2>



<p class="wp-block-paragraph">Most of the work on these projects is not running the wizard. It is finding the handful of things that will be wrong afterwards. That is the part I help with:</p>



<ul class="wp-block-list">
<li>Reviewing DMS task settings and table mappings for LOB truncation risk, per column, before you run a full load you cannot easily repeat.</li>



<li>Working through the schema conversion assessment report and turning the red items into an actual estimate rather than a wall of warnings.</li>



<li>Setting up supplemental logging and CDC correctly for tables with no primary key, including the LogMiner versus Binary Reader call for your redo volume.</li>



<li>Writing the cutover runbook: index recreation scripts, sequence advance, trigger re-enable, ordered, tested, and timed against a real rehearsal.</li>



<li>Building the reconciliation layer that sits on top of DMS validation, so you can sign off on the data with something better than a green tick.</li>



<li>Post-cutover triage when something on Aurora behaves differently from Oracle and nobody can work out which layer changed.</li>
</ul>



<p class="wp-block-paragraph">If you have a task settings JSON, an assessment report, or a validation failure log you cannot make sense of, send it over and I will tell you what I see in it.</p>



<div class="wp-block-buttons is-layout-flex wp-block-buttons-is-layout-flex">
<div class="wp-block-button"><a class="wp-block-button__link wp-element-button" href="https://www.upwork.com/freelancers/~01f15a912ad84a6620" target="_blank" rel="noreferrer noopener">Work with me on Upwork</a></div>
</div>
<p>The post <a href="https://john-nessime.com/blog/devops/oracle-to-aurora-migration/">Oracle to Aurora Migration: The Failures Nobody Catches Until Cutover</a> appeared first on <a href="https://john-nessime.com/blog">John Nessime</a>.</p>
]]></content:encoded>
					
		
		
			</item>
	</channel>
</rss>
