
We were working with a customer operating in the energy and utilities space, where large volumes of metering and device data are continuously collected and processed. Their setup was fairly active, with data flowing in through message queues and getting written into backend systems.
At the center of all this was an Oracle SE2 database running on a Windows-based on-prem setup.
Over time, this database had grown quite a bit. It wasn't just handling one thing: it was tied into ingestion, some processing, reporting, and billing. So there was always something happening. It wasn't the kind of system you could just pause for a few hours without people noticing.
From an infra point of view, everything was still on Windows and on-prem. That was starting to become a bit of a limitation: scaling wasn't very flexible, and even routine maintenance needed careful planning.
They wanted to move to Linux and GCP to simplify things going forward and make the setup easier to manage.
The tricky part was downtime.
Because of the way their system works, with continuous data coming in, even a short interruption could cause gaps in ingestion or downstream issues with billing and reporting. So taking the system down for a long migration window really wasn't an option.
So from the start, the ask was pretty clear:
We needed to migrate this with as little disruption as possible.
We initially looked at the standard Oracle approach for cross-platform migrations, which is Transportable Tablespaces (TTS).
It works, but in this case it had some obvious drawbacks:
Datafile conversion happens during downtime
With a large database, that takes time
And since this is SE2, GoldenGate wasn't an option for this setup (pending Yukti's confirmation, see flags)
So realistically, we were looking at a long outage window.
That wasn't something the customer could accept.
Instead of trying to optimize the downtime window, we focused on reducing what actually needs to happen during downtime.
The idea was:
Do as much work as possible before the cutover.
That led us to a setup using:
RMAN backup and restore
Archive log shipping
Continuous apply on the target side
Nothing exotic, just using what's already available in SE2.
At a high level, the flow looked like this:

We started with basic validation:
Tablespace sizing
Archive log retention
General readiness checks
Mostly to avoid surprises later. We also used a pre-check script on the source to validate environment readiness before starting the migration.
We made sure archive log retention was configured properly so logs were available throughout the sync window.
On the GCP side, we provisioned a Linux-based Oracle SE2 instance using Tessell.
We kept things aligned with the source (same version, similar configuration) just to keep the restore straightforward.
We took a full RMAN backup from the Windows server and restored it on the Linux side.
At this point, the target database was up, but not in sync yet.
As part of the restore, things like file path adjustments and redo log setup for the Linux environment were also handled.
This is where things clicked.
Instead of waiting for cutover to catch up, we started shipping archive logs at regular intervals (around every 30 minutes) and applying them on the target.
So the target DB was gradually catching up with the source over time.
By the time we got close to cutover, the gap was already quite small.
This was automated to run at regular intervals, so the target stayed consistently in sync without manual intervention.
If we break it down technically:
![Technical breakdown of the continuous sync: archive logs shipped from the Windows source about every 30 minutes and applied on the Linux target to keep it close to the source.]](/_next/image?url=https%3A%2F%2Fcdn.sanity.io%2Fimages%2Fkrotrzct%2Fprod%2F4d50f6daafe733c3ba8b1152fbc5283e50fe7058-3200x1800.png%3Fw%3D1200%26q%3D80%26auto%3Dformat%26fm%3Dwebp&w=3840&q=75)
One thing we didn't want was to make this a one-off migration that depends too much on manual steps.
Since this involved multiple stages (restore, recovery, and post-cutover validation), doing everything manually would have been risky and time-consuming.
So we put a few scripts in place to standardize the flow.
Instead of a long checklist, we broke it into three simple steps:
Handled by a script that:
Cleans up existing setup if needed
Restores controlfile and datafiles
Sets up tempfiles and redo logs
The restore step also takes care of cleaning up any existing setup on the target to avoid conflicts.
This script:
Applies archive logs
Brings the database in sync
Handles differences between SE2 and EE where needed
Final script takes care of:
Activating the database
Running post-checks (invalid objects, datapatch, etc.)
Verifying listener, connectivity, and basic health
Overall, this helped in two ways:
Reduced chances of missing steps
Made the process easier to repeat across environments
And during cutover, it made things much smoother since most of the work was already scripted and tested.
At cutover:
We stopped application access
Shipped the final set of archive logs
Applied them on the target
Activated the database on Linux
After activation, we ran a set of validation checks to confirm things like database links, invalid objects, and overall connectivity were working as expected.
Since most of the work was already done earlier, this step was relatively quick.
Downtime ended up being in minutes rather than hours.
We also ran standard post-migration patching and validation steps to ensure the database was fully aligned.
A few things stood out from this approach:
No dependency on Enterprise Edition features
Everything built around RMAN and archive logs
The process felt more controlled compared to a big-bang migration
There are a few practical considerations:
Archive logs need to be retained properly
Missing logs can force you to restart the process
Any structural changes (like new datafiles) during migration need coordination
Rollback basically means going back to the source
So planning is important.
This wasn't about doing anything drastically different.
It was more about shifting when the work happens.
Instead of pushing everything into the downtime window, we spread it out beforehand.
That's what made the difference.



