Metrics-driven migration sizing: why an 8 vCPU EC2 became 4 OCPUs (and the managed-MySQL traps)
Published on July 16, 2026

Size by metric, not by instinct
Migration is the right time to stop paying for capacity nobody uses. The source server was a c6g.2xlarge EC2 (8 vCPU), but average CPU in CloudWatch was 6.6% — the equivalent of ~0.5 effective vCPU. Sizing the target by the real metric, not the old instance, dropped the VM to an A1.Flex with 4 OCPU / 16 GB, with plenty of headroom.
# average/peak CPU of the source EC2 over the period (sizing basis)
aws cloudwatch get-metric-statistics --namespace AWS/EC2 --metric-name CPUUtilization \
--dimensions Name=InstanceId,Value=i-0aaaa1111aaaa1111 \
--start-time 2026-03-01T00:00:00Z --end-time 2026-04-01T00:00:00Z \
--period 86400 --statistics Average Maximum --query 'Datapoints[].{d:Timestamp,avg:Average,max:Maximum}' --output tableFor the database (RDS db.t3.large), the metrics that matter aren't just CPU: peak CPU 99.7%, peak connections 202, and storage using 9 GB of 30. Saturating CPU + high connections = the database is the bottleneck, and the managed-MySQL (MDS) sizing had to respect that.
Trap 1: EOL shapes and versions
Two EOLs nearly slipped through during MySQL Database Service provisioning:
VM.Standard.E2.1 shape at EOL → use the MySQL.2 family (MDS dedicated shape), not an end-of-life generic compute shape.
MySQL 8.0 at EOL (April 2026) → provision straight on 8.4.8 LTS (supported to 2032). Migrating to a version that dies in months is guaranteed rework.
Trap 2: the policy that must live at the tenancy root
When creating the MDS with NSGs (--nsg-ids), the command failed with AuthorizationFailed — even though the user had permission in the compartment. The reason: the MySQL service needs to manage the network on the user's behalf, and that policy must be at the TENANCY ROOT, not the compartment:
# policy at the TENANCY (root) level, not the compartment:
Allow service mysql to manage virtual-network-family in tenancy
# and the user needs:
NETWORK_SECURITY_GROUP_UPDATE_MEMBERSAuthorizationFailed on OCI is rarely "missing compartment permission". For managed services that touch your network (MDS, OKE, LB), the service policy lives at the tenancy root.
Trap 3: an ephemeral IP doesn't become reserved
The private IP the MDS gets by default is ephemeral and doesn't convert to reserved. For a stable address (one the app can point to safely), the path is to delete and recreate as RESERVED, referencing the private-ip-id:
oci network private-ip update --private-ip-id ocid1.privateip.oc1..aaaaEXAMPLE \
--vnic-id ocid1.vnic.oc1..aaaaEXAMPLE # the ephemeral default doesn't convert
# → create a new RESERVED one and repoint the app to itThe MDS takes 15–25 minutes to reach ACTIVE — plan the window. After the DB cutover, we validated integrity by comparing record counts (e.g. 12,636 posts) source × target before releasing traffic.
Lessons
1. Size by CloudWatch, not the old instance. 8 vCPU at 6.6% average = wasted capacity; the real metric defines the target.
2. Check EOL of shape AND version. Provision on LTS (MySQL 8.4) and a supported shape (MySQL.2); migrating onto something at EOL is rework.
3. Managed-service policy lives at the tenancy root. AuthorizationFailed with NSG = missing 'Allow service mysql to manage virtual-network-family in tenancy'.
4. Validate the count before releasing traffic. Comparing source×target record counts post-cutover is the cheap check that prevents silent loss.
Conclusion
Migration is the best moment to align capacity with real usage — an 8 vCPU EC2 running at 6.6% didn't need 8 vCPU. But managed MySQL charges its toll in gotchas: EOL shapes/versions, a service policy that lives at the tenancy root, and an ephemeral IP that won't convert. Metrics to size, a checklist not to trip.