F.70. sr_plan

F.70.1. Rationale

sr_plan allows the user to save a specific plan of a parameterized query for future usage regardless of how planner parameters may change.

sr_plan looks like Oracle Outline system. It can be used to lock the execution plan. It could help if you do not trust the planner or you can form a better plan. Typically, a DBA would play with queries interactively, save their plans, and enable use of saved plans for the queries where predictable response time is essential. Then the application that uses these queries would use saved plans.

F.70.2. Installation

The sr_plan extension is a built-in extension included into Postgres Pro Enterprise. To enable sr_plan, complete the following steps:

  1. Modify the postgresql.conf file as follows:

    shared_preload_libraries = 'sr_plan'
    sr_plan.enable = 'true'
    

    If you want to use sr_plan together with pgpro_stats, sr_plan should be the last on the list of shared_preload_libraries:

    shared_preload_libraries = 'pgpro_stats, sr_plan'
    
  2. Reload the database server for the changes to take effect.

    Note

    To verify that the sr_plan library was installed correctly, you can run the following command:

    SHOW shared_preload_libraries;
    
  3. Create the sr_plan extension using the following query:

    CREATE EXTENSION sr_plan;
    

    It is essential that the library is preloaded during server startup because sr_plan has a shared memory cache that can be initialized only during startup. The sr_plan extension should be created in each database.

F.70.3. Usage

In a typical case, you have a poorly-performing query with the bad plan choice (e.g. nested loop join instead of a hash join caused by selectivity underestimation), and you have an idea how to fix this plan (e.g. SET enable_nestloop = 'off'). sr_plan allows you to freeze plans for future usage regardless of how planner parameters may change. First, you have to register the query under the control of sr_plan:

SELECT sr_register_query(query_string, parameter_type, ...);

Here query_string is your query with $n parameters (same as in PREPARE statement_name AS). You can describe each parameter type with the optional parameter_type argument of the function or choose not to define parameter types explicitly. In the latter case, Postgres Pro attempts to determine each parameter type from the context. This function returns the unique pair of queryid and const_hash. Make sure to remember the queryid and const_hash until the end of the plan freezing. Now sr_plan will track executions of queries that fit the saved parameterized query template. You can use any techniques to get an acceptable query plan. After that, execute:

SELECT sr_plan_freeze(queryid, const_hash);

Here you must use the queryid and const_hash returned by the sr_register_query function. Now sr_plan stores the last used query plan in the file storage, shared memory, and local cache.

The below example illustrates the usage of sr_plan.

  1. Create a table:

    CREATE TABLE a AS (SELECT * FROM generate_series(1,30) AS x);
    CREATE INDEX ON a(x);
    ANALYZE;
    
  2. Register the query:

    SELECT queryid, const_hash
    FROM sr_register_query('SELECT count(*) FROM a
    WHERE x = 1 OR (x > $2 AND x < $1) OR x = $1', 'int', 'int')
           queryid       | const_hash
    ----------------------+------------
     5393873830515778388 |  15498345
    (1 row)
    
  3. Execute the query with specific parameter values:

    SELECT count(*) FROM a WHERE x = 1 OR (x > 11 AND x < 22) OR x = 22;
    

    With EXPLAIN, you can see that this query is tracked by sr_plan:

    EXPLAIN SELECT count(*) FROM a WHERE x = 1 OR (x > 11 AND x < 22) OR x = 22;
    
     Custom Scan (SRScan) (actual rows=1 loops=1)
       Plan is: tracked
       Query ID: 5393873830515778388
       Const hash: 15498345
       ->  Aggregate (actual rows=1 loops=1)
             ->  Seq Scan on a (actual rows=10 loops=1)
                   Filter: ((x = 1) OR ((x > $2) AND (x < $1)) OR (x = $1))
                   Rows Removed by Filter: 20
    
  4. Disable SeqScan and try again:

    SET enable_seqscan = 'off';
    
     Custom Scan (SRScan) (actual rows=1 loops=1)
       Plan is: tracked
       Query ID: 5393873830515778388
       Const hash: 15498345
       ->  Aggregate (actual rows=1 loops=1)
             ->  Index Only Scan using a_x_idx2 on a (actual rows=10 loops=1)
                   Filter: ((x = 1) OR ((x > $2) AND (x < $1)) OR (x = $1))
                   Rows Removed by Filter: 20
                   Heap Fetches: 30
    (5 rows)
    
  5. Freeze the plan. The query will be handled as an index-only scan even if you change the planner parameters:

    SELECT sr_plan_freeze(5393873830515778388, 15498345);
    RESET enable_seqscan;
    

The example below shows how to freeze the plan of the fully parameterized query using the sr_plan.auto_tracking configuration parameter:

SET sr_plan.auto_tracking = on;

EXPLAIN SELECT count(*) FROM a WHERE x = 1 OR (x > 11 AND x < 22) OR x = 22;

 Custom Scan (SRScan)  (cost=1.60..0.00 rows=1 width=8)
   Plan is: tracked
   Query ID: 5393873830515778388
   Const hash: 0
   ->  Aggregate  (cost=1.60..1.61 rows=1 width=8)
         ->  Seq Scan on a  (cost=0.00..1.60 rows=2 width=0)
               Filter: ((x = $1) OR ((x > $2) AND (x < $3)) OR (x = $4))

-- Specify the returned 'Tracked plan ID' below:
SELECT sr_plan_freeze(5393873830515778388, 0);

RESET sr_plan.auto_tracking;
 

F.70.4. Views

F.70.4.1. The sr_plan_storage View

The sr_plan_storage view provides detailed information about all frozen statements. The columns of the view are shown in Table F.128.

Table F.128. sr_plan_storage Columns

NameTypeDescription
dbidoidID of the database where the statement is executed
queryidbigintInternal query ID
const_hashbigintHash of non-parameterized constants
validbooleanFALSE if the plan was invalidated the last time it was used
query_stringtextQuery registered by the sr_register_query function
paramtypesregtype[]Array with parameter types used in the query
querytextInternal representation of the query
plantextInternal representation of the plan
hintstrtextSet of hints formed based on the frozen plan

F.70.4.2. The sr_plan_local_cache View

The sr_plan_local_cache view provides detailed information about registered and frozen statements in the local cache. The columns of the view are shown in Table F.129.

Table F.129. sr_plan_local_cache Columns

NameTypeDescription
queryidbigintInternal query ID
const_hashbigintHash of non-parameterized constants
fs_is_frozenbooleanTRUE if the statement is frozen
fs_is_validbooleanTRUE if the statement is valid
ps_is_validbooleanTRUE if the statement should be revalidated
query_stringtextQuery registered by the sr_register_query function
querytextInternal representation of the query
paramtypesregtype[]Array with parameter types used in the query
hintstrtextSet of hints formed based on the frozen plan

F.70.5. Functions

sr_register_query(query_string text) returns record

Saves the query described in the query_string in the local cache and returns the unique pair of queryid and const_hash.

sr_unregister_query(queryid bigint const_hash bigint) returns bool

Removes the query that was registered but not frozen from the local cache. Returns true if there are no errors.

sr_plan_freeze(queryid bigint const_hash bigint plan_type text) returns bool

Freezes the last used plan for the statement with the specified queryid, const_hash, and plan_type. The allowed values of the plan_type optional argument are serialized and hintset. The serialized value means that the query plan based on the serialized representation is used. With hintset, sr_plan uses the query plan based on the set of hints, which is formed at the stage of registered query execution. If the plan_type argument is omitted, the serialized query plan is used by default. Returns true if there are no errors.

sr_plan_unfreeze(queryid bigint const_hash bigint) returns bool

Removes the plan only from the storage and keeps the query registered in the local cache. Returns true if there are no errors.

sr_plan_remove(queryid bigint const_hash bigint) returns bool

Removes the frozen statement with the specified queryid and const_hash. Operates as sr_plan_unfreeze and sr_unregister_query called sequentially. Returns true if there are no errors.

sr_plan_reset(dbid oid) returns bigint

Removes all records in the sr_plan storage for the specified database. Omit dbid to remove the data collected by sr_plan for the current database. Set dbid to NULL to reset data for all databases.

sr_reload_frozen_plancache() returns bool

Drops all frozen plans and reloads them from the storage. It also drops statements that have been registered but not frozen.

sr_plan_fs_counter() returns table

Returns the number of times each frozen statement was used and the ID of the database where the statement was registered and used.

sr_show_registered_query(queryid bigint const_hash bigint) returns table

Returns the registered query with the specified queryid and const_hash even if it is not frozen, for debugging purposes only. This works if the query is registered in the current backend or frozen in the current database.

sr_set_plan_type(queryid bigint const_hash bigint plan_type text) returns bool

Sets the type of the query plan for the frozen statement. The allowed values of the plan_type argument are serialized and hintset. To be able to use the query plan of the hintset type, the pg_hint_plan module must be loaded. Returns true if the plan type has been changed successfully.

F.70.6. Configuration Parameters

sr_plan.enable (boolean)

Enables sr_plan to use frozen plans. The default value is off. Only superusers can change this setting.

sr_plan.max (integer)

Sets the maximum number of frozen statements returned by the sr_plan_fs_counter() function. The default value is 5000. This parameter can only be set at server start.

sr_plan.max_items (integer)

Sets the maximum number of entries sr_plan can operate with. The default value is 1000. This parameter can only be set at server start.

sr_plan.auto_tracking (boolean)

Enables sr_plan to normalize and register queries executed using the EXPLAIN command automatically. The default value is off. Only superusers can change this setting.

sr_plan.auto_freeze (boolean)

Forces to freeze each query processed by the planner. Use it only for debugging purposes. The default value is off. Only superusers can change this setting.

F.70.7. Tips

Use explicit cast to make sure that sr_plan finds a frozen plan for your query. For example, the following statements are recognized by sr_plan as different ones:

SELECT * FROM a WHERE x = 1::integer

SELECT * FROM a WHERE x = 1::bigint

If sr_plan is not the last hook in the queue of planner hooks, the standard planner called by the last hook in the queue would optimize workload (sometimes greatly). So if the plan is already frozen, sr_plan would drop such a newly created plan. To avoid this unnecessary overhead, sr_plan should be the last in the list of libraries shared_preload_libraries.