Wednesday, February 4, 2026

Utilizing the shared plan cache for Amazon Aurora PostgreSQL


On this submit, we talk about how the Shared Plan Cache characteristic of the Amazon Aurora PostgreSQL-Appropriate Version can considerably scale back reminiscence consumption of generic SQL plans in high-concurrency environments, reworking what could possibly be a 40GB reminiscence overhead right into a manageable 400MB footprint.

Think about your Aurora PostgreSQL database cluster is serving hundreds of concurrent connections, every executing the identical ready statements. You discover reminiscence utilization growing to tens of gigabytes, but the queries themselves are easy. What’s taking place? You’re seemingly experiencing the hidden price of plan duplication, an issue that the shared plan cache can elegantly resolve.

Understanding generic plans in PostgreSQL

Ready statements are generally utilized in functions (once they outline capabilities or strategies that work together with the database), the place these statements are included of their database entry code/strategies. The preparation part contains each the SQL assertion construction and placeholders, which shall be stuffed with precise values when the applying executes the ready assertion. Within the preparation part, the assertion is parsed, analyzed and rewritten, thereby saving on repetitive parsing and analyzing work when it’s executed.

Earlier than diving into the answer, let’s perceive how PostgreSQL handles ready statements. In PostgreSQL and Aurora PostgreSQL, ready statements may be executed utilizing two sorts of plans:

  • Customized plans: Created contemporary for every execution with particular parameter values the place literals are included.
  • Generic plans: Parameter-independent plans which can be reused throughout executions the place literals are usually not included.

By default, PostgreSQL makes use of an clever strategy to determine between these two plan sorts:

  1. The primary 5 executions of a ready assertion use customized plans
  2. The typical price of those customized plans is calculated
  3. On the sixth execution, a generic plan is created
  4. If the generic plan’s price is corresponding to or higher than the typical customized plan price, it’s used for subsequent executions

This strategy saves planning time for continuously executed queries, but it surely comes with a hidden price in environments with many concurrent database connections.

The issue: reminiscence inefficiency at scale

Whereas this strategy works nicely for particular person connections, it creates two important inefficiencies in environments with many concurrent database connections:

  1. Pointless plan era: Even when a generic plan gained’t be used (as a result of customized plans are extra environment friendly), the system nonetheless creates and shops it in reminiscence for price comparability functions. For instance, for partitioned tables, there’s a larger probability of a generic plan not getting used as a result of the fee is calculated for the leaf partitions after which summed up.
  2. Plan duplication: When the identical question is executed throughout a whole lot or hundreds of classes, every session maintains its personal copy of the similar generic plan, resulting in large reminiscence duplication.

Let’s show this downside with a concrete instance:

Organising the take a look at surroundings

For this instance, we create 2 tables t1 and t2 every with 1000 partitions in a brand new session. We then insert 100,000 rows into every desk by looping 100 occasions with every loop iteration inserting 1000 values. We lastly collect contemporary statistics on each tables.

Word: With a purpose to use the shared plan cache characteristic you should be utilizing Aurora PostgreSQL model 17.6 and later or model 16.10 and later.

-- Create partitioned tables
CREATE TABLE t1(part_key int, c1 int) PARTITION BY RANGE(part_key);
CREATE TABLE t2(part_key int, c1 int) PARTITION BY RANGE(part_key);

pset pager

-- Generate 1000 partitions for every desk (simulating large-scale partitioning)
SELECT 'CREATE TABLE t1_' || x || ' PARTITION OF t1 FOR VALUES FROM (' || x || ') TO (' || x+1 || ')'
FROM generate_series(1, 1000) x;
gexec

SELECT 'CREATE TABLE t2_' || x || ' PARTITION OF t2 FOR VALUES FROM (' || x || ') TO (' || x+1 || ')'
FROM generate_series(1, 1000) x;
gexec

-- Populate tables with pattern knowledge
DO
$do$
BEGIN
FOR i IN 1..100 LOOP
INSERT INTO t1 SELECT x, i FROM generate_series(1, 1000) x;
INSERT INTO t2 SELECT x, i FROM generate_series(1, 1000) x;
END LOOP;
END
$do$;

-- Replace statistics for optimum question planning
ANALYZE t1, t2;

You should use the gexec change right here to run the output of our choose as an unbiased SQL assertion. You’ll be able to disable the psql pager utilizing pset pager to keep away from having to hit enter a number of occasions when creating your desk partitions.

Observing reminiscence consumption

In Session 1, we create and execute the next ready assertion:

-- Create a ready assertion with a easy be part of
PREPARE p2(int, int) AS 
SELECT sum(t1.c1) 
FROM t1, t2 
WHERE t1.part_key = t2.part_key 
AND t1.c1 = $1 
AND t1.part_key = $2;

-- Execute 6 occasions to set off generic plan creation
EXECUTE p2(1, 4); -- Execution 1: Customized plan
EXECUTE p2(1, 4); -- Execution 2: Customized plan
EXECUTE p2(1, 4); -- Execution 3: Customized plan
EXECUTE p2(1, 4); -- Execution 4: Customized plan
EXECUTE p2(1, 4); -- Execution 5: Customized plan
EXECUTE p2(1, 4); -- Execution 6: Generic plan created

Then, we examine the reminiscence consumption:

-- Test reminiscence utilization for cached plans
SELECT identify, ident, pg_size_pretty(total_bytes) as measurement
FROM pg_backend_memory_contexts 
WHERE identify="CachedPlan";
-[ RECORD 1 ]-+---------------------------------------
identify | CachedPlan
ident | put together p2(int, int) as +
| choose sum(t1.c1) +
| from t1, t2 +
| the place t1.part_key = t2.part_key and +
| t1.c1 = $1 and t1.part_key = $2;
measurement | 4161 kB

For this take a look at we observe that the generic plan consumes roughly 4MB and stays in reminiscence till the ready assertion is deallocated or the connection terminates.

The duplication downside

Now, let’s use one other session (Session 2) and execute the identical ready assertion:

-- Session 2: Utilizing the identical ready assertion
PREPARE p2(int, int) AS 
SELECT sum(t1.c1) 
FROM t1, t2 
WHERE t1.part_key = t2.part_key 
AND t1.c1 = $1 
AND t1.part_key = $2;

-- Execute 6 occasions
EXECUTE p2(1, 4);
EXECUTE p2(1, 4);
EXECUTE p2(1, 4);
EXECUTE p2(1, 4);
EXECUTE p2(1, 4);
EXECUTE p2(1, 4);

-- Test reminiscence utilization
SELECT identify, ident, pg_size_pretty(total_bytes) as measurement
FROM pg_backend_memory_contexts 
WHERE identify="CachedPlan";

-[ RECORD 1 ]-+---------------------------------------
identify | CachedPlan
ident | put together p2(int, int) as +
| choose sum(t1.c1) +
| from t1, t2 +
| the place t1.part_key = t2.part_key and +
| t1.c1 = $1 and t1.part_key = $2; 
measurement | 4161 kB

Session 2 additionally consumes 4MB for the very same generic plan!

The multiplication impact

This duplication occurs for each session that executes the ready assertion. Let’s calculate the affect:

  • 1 ready assertion × 100 connections × 4MB = 400MB of reminiscence
  • 100 completely different ready statements × 100 connections × 4MB = 40GB of reminiscence

This large reminiscence consumption happens although the classes are storing similar copies of the identical generic plan. In environments with many concurrent database connections, this may shortly exhaust obtainable reminiscence and drive you to make use of bigger, costlier occasion sorts.

The answer: Aurora PostgreSQL Shared Plan Cache

Aurora PostgreSQL solves this with shared plan cache (SPC), which retains only one copy of every generic plan that the classes can use. This dramatically reduces reminiscence consumption whereas sustaining the efficiency advantages of plan caching.

You’ll be able to allow the shared plan cache(SPC) utilizing a cluster or occasion parameter group:

apg_shared_plan_cache.allow = ON

As a result of apg_shared_plan_cache.allow is a dynamic parameter you don’t need to restart the occasion for the modifications to take impact.

SPC is carried out as a dynamic hash desk, shared throughout the classes, the place the variety of entries within the cache may be managed by through apg_shared_plan_cache.max. You may as well use the next parameters to manage the minimal and the utmost measurement of an entry.

apg_shared_plan_cache.min_size_per_entry
apg_shared_plan_cache.max_size_per_entry

Demonstrating the Shared Plan Cache in motion

Let’s repeat our earlier experiment with the shared plan cache enabled:

Session 1 (First Connection):

-- Create and execute the identical ready assertion
PREPARE p2(int, int) AS 
SELECT sum(t1.c1) 
FROM t1, t2 
WHERE t1.part_key = t2.part_key 
AND t1.c1 = $1 
AND t1.part_key = $2;

-- Execute 6 occasions
EXECUTE p2(1, 4);
EXECUTE p2(1, 4);
EXECUTE p2(1, 4);
EXECUTE p2(1, 4);
EXECUTE p2(1, 4);
EXECUTE p2(1, 4);

-- Test reminiscence utilization
SELECT identify, ident, pg_size_pretty(total_bytes) as measurement
FROM pg_backend_memory_contexts 
WHERE identify="CachedPlan";

The primary session nonetheless reveals the 4MB plan in its native reminiscence (wanted to populate the shared cache).

Session 2 (Subsequent Connection):

-- Create the identical ready assertion
PREPARE p2(int, int) AS 
SELECT sum(t1.c1) 
FROM t1, t2 
WHERE t1.part_key = t2.part_key 
AND t1.c1 = $1 
AND t1.part_key = $2;

-- Execute 6 occasions
EXECUTE p2(1, 4);
EXECUTE p2(1, 4);
EXECUTE p2(1, 4);
EXECUTE p2(1, 4);
EXECUTE p2(1, 4);
EXECUTE p2(1, 4);

-- Test reminiscence utilization
SELECT identify, ident, pg_size_pretty(total_bytes) as measurement
FROM pg_backend_memory_contexts 
WHERE identify="CachedPlan";
(0 rows)

No native plan storage! The second session is utilizing the shared plan cache.

Monitoring cache utilization

We run the next SQL to indicate what number of cache hits particular person shared plans saved in our cache have obtained. Each hit represents a plan that has not wanted to be duplicated in session reminiscence.

-- View shared plan cache statistics
SELECT cache_key, question, hits
FROM apg_shared_plan_cache();
-[ RECORD 1 ]-------------------------------------
cache_key | -5127257242415815179
question | put together p2(int, int) as +
| choose sum(t1.c1) +
| from t1, t2 +
| the place t1.part_key = t2.part_key and +
| t1.c1 = $1 and t1.part_key = $2;
hits | 2

Cleanup:

-- clear the cache
SELECT * FROM apg_shared_plan_cache_reset();

-- drop the tables
DROP TABLE t1;
DROP TABLE t2;

Efficiency affect

In our instance situation with 100 completely different ready statements throughout 100 connections, we noticed a metamorphosis from 40GB of duplicated plan storage down to only 400MB within the shared cache. The screenshot under reveals a graph of the Freeable Reminiscence Cloudwatch metric obtained from an occasion the place a take a look at was run utilizing pgbench with 100 distinct ready statements (used from the instance above) throughout 100 connections with apg_shared_plan_cache.allow = off. We are able to observe that between 02:05 and 02:10, FreeableMemory drops by roughly 40GB, which aligns with our anticipated duplicated plan storage footprint. After we enabled shared plan cache and ran the identical take a look at once more, the reminiscence affect was drastically diminished, requiring solely a small quantity of reminiscence as an alternative of 40GB.

This discount means you possibly can:

  • Run the identical workload on smaller situations, considerably decreasing your AWS prices
  • Assist extra concurrent connections with out hitting reminiscence limits
  • Keep away from out-of-memory errors throughout visitors spikes

Greatest practices

This characteristic is especially helpful when:

  • Your utility maintains a whole lot or hundreds of database connections
  • You employ ready statements extensively
  • Your queries contain partitioned tables or advanced operators (for instance joins and customary desk expressions) that generate giant plans
  • You observe excessive reminiscence utilization from backend processes
  • Your workload has repetitive question patterns with parameterized queries

Whereas Shared Plan Cache presents important advantages, observe that this characteristic might not be fitted to the next eventualities:

  • Workloads with extremely distinctive, ad-hoc queries
  • Purposes that hardly ever reuse ready statements
  • Environments with few concurrent connections

Conclusion

On this submit we confirmed you how one can allow the shared plan cache in Aurora PostgreSQL. We now have proven that when utilizing ready statements throughout many concurrent database classes, you save the identical generic question plan being duplicated in reminiscence.

By eradicating redundant plan storage throughout classes, you possibly can run extra connections on smaller situations, decreasing each operational complexity and prices. For additional particulars on the completely different plan sorts see the PostgreSQL documentation on the put together assertion and Amazon Cloudwatch metrics for Amazon Aurora for additional particulars on measuring freeable reminiscence.


In regards to the authors

Souvik Bhattacherjee

Souvik Bhattacherjee

Souvik is a Senior Software program Engineer at AWS the place he focuses on advancing question processing capabilities within the Aurora PostgreSQL database. He has over 8 years of expertise within the database/HPC trade the place he contributed to subjects associated to database techniques and high-performance computing techniques.

Jungkook Lee

Jungkook Lee

Jungkook is a Senior Software program Improvement Engineer at AWS, the place he leads a group targeted on bettering efficiency and lengthening functionalities for Aurora PostgreSQL. With over 10 years of expertise in database techniques and distributed computing architectures, he specialised in question optimization and database efficiency.

Stephen Wood

Stephen Wooden

Stephen is a Senior Specialist Database Options Architect at AWS. Stephen makes a speciality of Amazon RDS PostgreSQL, Amazon Aurora PostgreSQL, and Amazon Aurora DSQL. He has been working with database techniques throughout various kinds of enterprises for the previous 24 years and all the time loves working with new database know-how.

Related Articles

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Latest Articles