Friday, July 10, 2026

Why Your Cloud SQL CPU is Hitting 100%: The Hidden Cost of Table Bloat

 

Introduction: The Hidden Cost of Table Bloat

It is a scenario every Database Reliability Engineer dreads: the midnight pager alert. You log in to find your production Google Cloud SQL PostgreSQL instance—which was performing perfectly during yesterday’s peak—pinned at 100% CPU utilization. Application latency has spiked, and queries that usually take milliseconds are now timing out.

Often, this isn't the result of a sudden traffic surge or a DDoS attack. It is the result of a silent performance killer known as "table bloat." This phenomenon occurs when a database engine can no longer efficiently navigate its own storage, turning a once-optimized engine into a resource-starved bottleneck.

1. The "Ghost Rows" Sabotaging Your Performance

PostgreSQL uses a concurrency control mechanism (MVCC) that treats data modifications in a unique way. When you execute an UPDATE or DELETE, the database doesn't immediately overwrite or remove the old data. Instead, it leaves behind "dead tuples"—old versions of rows that are no longer visible to transactions but still occupy physical space on the disk.

These are essentially "ghost rows." They sit in your data files, forcing the database to work harder to find the "live" data your application actually needs. It is a counter-intuitive reality: aggressively cleaning up or updating data can actually make your database significantly slower if not managed correctly.

"Table bloat occurs when a high volume of UPDATE or DELETE statements leave behind 'dead tuples' (old rows) that take up space, forcing the query planner to perform massive sequential disk scans."

2. Why Your Query Planner Suddenly Goes "Blind"

The PostgreSQL query planner is the brain of your database, responsible for choosing the fastest path to your data. To do this, it relies on table statistics. When table bloat becomes excessive, statistics can become stale, leading to a "tipping point."

The planner, blinded by inaccurate data regarding the ratio of live rows to dead tuples, may shift its strategy from an efficient index scan to a disastrous sequential scan. In a sequential scan, the engine must parse through every single row—dead or alive—to find the result set. When your engine is forced to read through millions of "garbage" rows for every query, the result is 100% CPU utilization as the processor spends all its cycles on needless I/O and parsing.

To identify exactly which queries are causing this saturation, you must enable the pg_stat_statements flag in your Cloud SQL configuration. Once enabled, you can pinpoint the top CPU consumers with this query:

SELECT query, calls, total_exec_time, rows 
FROM pg_stat_statements 
ORDER BY total_exec_time DESC 
LIMIT 5;

If you see queries on bloated tables performing sequential scans, adding a targeted index is often the best defense, as it allows the engine to bypass the bloat entirely.

3. The "Danger" of Default Autovacuum Settings

Cloud SQL’s default settings are designed for general safety, not for the aggressive cleanup required by high-throughput, modern applications. Specifically, the default autovacuum_vacuum_scale_factor of 0.20 is far too conservative. It requires 20% of a table to change before a cleanup is triggered—on a 100-million-row table, that is 20 million dead tuples before the database even attempts to help itself.

To fix this, navigate to your GCP Console > Cloud SQL Instances > Edit Configuration and tune your database flags to be more aggressive:

Database Flag

Default Setting

Recommended "High-Throughput" Setting

autovacuum_vacuum_scale_factor

0.20 (20%)

0.05 (5%)

autovacuum_vacuum_cost_limit

200

1000

autovacuum_max_workers

3

5

By lowering the scale factor, you trigger vacuums earlier. Increasing the cost limit gives worker processes more "credit" to complete their work without sleeping, while more workers ensure that one massive, bloated table doesn't block the cleanup of smaller ones.

4. Immediate Relief vs. The Surgical Fix

When your CPU is at 100%, you need a hierarchy of response.

Step 1: Diagnostic. Use the pgstattuple extension to confirm bloat:

CREATE EXTENSION IF NOT EXISTS pgstattuple;
SELECT table_len, tuple_count, dead_tuple_count, dead_tuple_percent 
FROM pgstattuple('your_table_name');

Step 2: Immediate Relief. Run a standard VACUUM ANALYZE your_table_name;. This does not lock the table to writes. It clears dead tuples and updates statistics, which can immediately lower CPU overhead by allowing the query planner to revert to index scans.

Step 3: Zero-Downtime Compacting. VACUUM ANALYZE does not reclaim disk space; it only makes it available for new rows. To physically shrink the file and return space to the OS, you might be tempted to use VACUUM FULL, but this is a non-starter in production as it places an exclusive lock on the table. Instead, use the pg_repack extension, which is fully supported by Cloud SQL for zero-downtime compacting. Run it via Cloud Shell:

pg_repack -h [INSTANCE_IP] -d [DB_NAME] -U [USER] -t your_table_name

5. The "Silent Blocker"—Long-Running Transactions

Even the best-tuned autovacuum cannot clean up dead rows if an application-side transaction remains open. PostgreSQL must keep those dead rows available in case an old transaction needs to see them. This "silent blocker" can also be caused by stagnant replication slots.

You must maintain strict application-side hygiene. Use the following query to find transactions that have been open for more than five minutes:

SELECT pid, age(clock_timestamp(), query_start), state, query 
FROM pg_stat_activity 
WHERE state != 'idle' 
AND age(clock_timestamp(), query_start) > interval '5 minutes';

If your application allows transactions to hang open indefinitely, your database will eventually drown in bloat, regardless of your hardware specs.

Conclusion: Moving Toward Proactive Maintenance

Stability in Google Cloud SQL isn't just about choosing a larger instance; it’s about the aggressive maintenance of the underlying data structure. High CPU utilization is rarely the primary disease—it is a symptom of a database struggling to breathe under the weight of its own history.

By tuning your autovacuum flags, monitoring for long-running transactions, and utilizing tools like pg_repack, you move from reactive firefighting to proactive reliability.

When was the last time you checked your dead tuple percentage, or is your database currently holding onto a past it no longer needs?

Saturday, April 18, 2026

The Oracle Patching Paradox: Why Your Latest RU 19.30.0.0.0 (REL-JAN260130) Update Might Require a Manual Intervention

 


Introduction: The High-Stakes Routine of Patching


For a Database Administrator, the Release Update (RU) cycle is typically a routine exercise in maintaining system health. We have come to expect that automated tools will handle the heavy lifting, allowing for a predictable transition to the latest version. However, the reality of enterprise infrastructure often presents scenarios where automation reaches its limits.


The recent RU 19.30.0.0.0 (REL-JAN260130) for Oracle RAC Database is a prime example of this paradox. While it promises essential fixes, the path to a successful deployment requires stepping away from standard automated scripts and returning to manual processes to ensure environment stability and data integrity.


Takeaway 1: When Automation Fails—The OPATCHAUTO-72053 Error


The first sign that this update requires a non-traditional approach is the failure of the standard opatchauto tool. During the analysis phase of the 19.30.0.0.0 patch, administrators are frequently met with a critical failure: OPATCHAUTO-72053.


This error code signifies a total command execution failure, halting the automated workflow before the patching can even begin. In an era of "set and forget" automation, this error forces an immediate pivot in strategy. This failure is a clear signal that the automated analysis tool cannot navigate the complexities of this specific update, necessitating a manual bypass.


Takeaway 2: The High Stakes of Database and Redo Corruption


The necessity of a manual approach is driven by more than just a software error; it is a critical safeguard against catastrophic data loss. Oracle support documentation indicates that standard automated procedures for these specific updates can introduce severe risks to RAC clusters.


"19.29 and 19.30 Database RU May Cause Corruption in RAC Databases and Redo Corruption Impacting Recovery."


This warning highlights a counter-intuitive reality: the very update intended to improve the system could, if handled through standard automated means, compromise it. The risk of redo corruption is particularly concerning as it directly impacts the ability to recover a database. Because of this risk, the manual application process is a technical necessity for maintaining data integrity.


Takeaway 3: The Secret to the Bypassing Strategy—Hidden Parameters


The path to a successful manual installation begins with a specific maneuver involving hidden system parameters. This step prepares the environment to receive the patch without triggering the underlying issues that cause automated tools to fail.


This change must be executed on any one RAC instance. However, for Data Guard environments, this must be executed on both the Primary and Standby RAC databases.


The critical first step requires executing the following SQL command: alter system set "_gcs_recoverable_asserts"=3 scope=both sid=' *';


Immediately followed by the reset command: alter system reset "_gcs_recoverable_asserts" scope=both sid='* ';


This sequence functions as a technical "prime," readying the system for the complex manual workflow that follows.


Takeaway 4: The "Manual-First" Workflow for RAC Success


Navigating a manual RAC patch requires a disciplined progression through different user roles, specific zip file installations, and the use of the -local apply flag. The core of this bypass involves an intentional sequence where the 19.29 patch is used as a bridge before moving to 19.30.


Initial Patch Prep: Install the first zip file: p38854064_1929000DBRU_Linux-x86-64.zip.


Root User Operations: Execute the prepatch script to prepare the cluster: <GI_HOME>/crs/install/rootcrs.sh -prepatch


Grid User Operations: 


Apply the patches manually to the Grid Infrastructure Home (<GI_HOME>) using the -local flag for every component: 

<GI_HOME>/OPatch/opatch apply -oh <GI_HOME> -local <UNZIPPED_PATCH_LOCATION>/DBRU 

<GI_HOME>/OPatch/opatch apply -oh <GI_HOME> -local <UNZIPPED_PATCH_LOCATION>/OCW 

<GI_HOME>/OPatch/opatch apply -oh <GI_HOME> -local <UNZIPPED_PATCH_LOCATION>/ACFS 

<GI_HOME>/OPatch/opatch apply -oh <GI_HOME> -local <UNZIPPED_PATCH_LOCATION>/DBWLM 

<GI_HOME>/OPatch/opatch apply -oh <GI_HOME> -local <UNZIPPED_PATCH_LOCATION>/TOMCAT


Oracle User Operations: This stage requires rolling back the 19.29 patch before applying the new components to the Database Home (<DB_HOME>) using the -local flag: 

Roll back p38854064_1929000DBRU_Linux-x86-64.zip 


<DB_HOME>/OPatch/opatch apply -oh <DB_HOME> -local <UNZIPPED_PATCH_LOCATION>/DBRU 

<DB_HOME>/OPatch/opatch apply -oh <DB_HOME> -local <UNZIPPED_PATCH_LOCATION>/OCW


Final Patch Application: Install the second zip file: p38854064_1930000DBRU_Linux-x86-64.zip.


Final Root Execution and Restart: Complete the process by running the RDBMS installation script and the post-patch script: 


<GI_HOME>/rdbms/install/rootadd_rdbms.sh <GI_HOME>/crs/install/rootcrs.sh -postpatch


Once these steps are complete, you can safely start the database.


Conclusion: Rethinking the Future of Database Maintenance


Successfully transitioning a RAC environment from RU 19.29 to 19.30.0.0.0 serves as a reminder that automation is a tool, not a substitute for architectural expertise. While automated patching is the ideal for modern data centers, the complexities of mission-critical environments occasionally demand a return to foundational manual techniques to bypass critical errors and prevent data corruption.


As we look toward future updates, we must ask: In an increasingly automated world, how can organizations ensure their teams maintain the deep technical knowledge required to intervene when the automation fails?