<?xml version='1.0' encoding='UTF-8'?><rss xmlns:atom="http://www.w3.org/2005/Atom" xmlns:openSearch="http://a9.com/-/spec/opensearchrss/1.0/" xmlns:blogger="http://schemas.google.com/blogger/2008" xmlns:georss="http://www.georss.org/georss" xmlns:gd="http://schemas.google.com/g/2005" xmlns:thr="http://purl.org/syndication/thread/1.0" version="2.0"><channel><atom:id>tag:blogger.com,1999:blog-25740336</atom:id><lastBuildDate>Tue, 24 Mar 2026 12:16:03 +0000</lastBuildDate><category>nvision</category><category>Application Engine</category><category>PS/Query</category><category>PeopleTools 8.54</category><category>Temporary Records</category><category>Performance Monitor</category><category>Process Scheduler</category><category>DBMS_STATS</category><category>Resource Manager</category><category>Statistics</category><category>cursor sharing</category><category>Hints</category><category>Partitioning</category><category>Tuxedo</category><category>%UpdateStats</category><category>Active Session History</category><category>Application Server</category><category>DBMS_APPLICATION_INFO</category><category>DDL trigger</category><category>Journal Line</category><category>PS_JRNL_LN</category><category>Performance Metrics</category><category>SQL Profiles</category><category>Tree Selectors</category><category>Truncate</category><category>psadmin.io</category><category>DDL Model</category><category>Indexes</category><category>PeopleTools 8.48</category><category>SQL Trace</category><category>Trigger</category><category>compression</category><category>global temporary tables</category><category>performance</category><category>COBOL</category><category>DBMS_MONITOR</category><category>Instrumentation</category><category>Materialized Views</category><category>MetaSQL</category><category>Optimizer Dynamic Sampling</category><category>Oracle 12c</category><category>Priority</category><category>Quarantined SQL Plan</category><category>ReUse Statement</category><category>SQL Tuning</category><category>Script</category><category>Trees</category><category>Views</category><category>local write wait</category><category>Active Data Guard</category><category>Adaptive Optimization</category><category>Analyze</category><category>Batch Timings</category><category>Bloom filters</category><category>CURRENT_SCHEMA</category><category>Cache</category><category>Conference</category><category>DDL</category><category>Deferred Segment Creation</category><category>Descending Key</category><category>Dynamic Code</category><category>Exadata</category><category>Fine-Grained Auditing</category><category>Interval Partitioning</category><category>Lookup Exclusion</category><category>OpenXML</category><category>PSQUERY</category><category>PTRef</category><category>Parse</category><category>PeopeTools Tables</category><category>PeopleTools 8.52</category><category>Redundant Indexes</category><category>Shell Script</category><category>Stored Statements</category><category>Version Numbers</category><category>enq: RO - fast object reuse</category><category>long</category><category>nVision Performance Options</category><category>ps360</category><category>sequences</category><category>%CurrentDateIn</category><category>%SQLHint</category><category>%Table</category><category>ASSM</category><category>AWR</category><category>Adaptive Plans</category><category>Adaptive Statistics</category><category>After Logon</category><category>Analytic Function</category><category>Append</category><category>Cartesian</category><category>Component Processor</category><category>DBMS_METADATA</category><category>DDL overrides</category><category>DUAL</category><category>DataGuard</category><category>Database Links</category><category>Dead Connect Detection</category><category>Dirty Reads</category><category>Effective-date</category><category>Environment Variables</category><category>Extended Statistics</category><category>Flashback Database</category><category>Flashback Query</category><category>Freelist</category><category>GetNextNumberWithGapsCommit</category><category>Global Payroll</category><category>Global Unique Identifier</category><category>Grid Control</category><category>Healthcheck</category><category>Invisible Indexes</category><category>Isolation Level</category><category>Load Balancing</category><category>Locking</category><category>Multi-versioning</category><category>Multitenant; SGA</category><category>Network Latency</category><category>Null</category><category>Oracle Enterprise Manager</category><category>PSAESRV</category><category>PSPMSESSIONS_VW</category><category>PeopleTools 8.1x</category><category>PeopleTools 8.49</category><category>PeopleTools 8.50</category><category>PeopleTools 8.53</category><category>PeopleTools Tables</category><category>Plan Stability</category><category>Podcast</category><category>Privileges</category><category>Query Security Records</category><category>Read Consistency</category><category>Record Locator Dialogue</category><category>Recyclebin</category><category>Redo</category><category>Report Reposiory</category><category>Restartability</category><category>Run Control</category><category>SQL Server 2005</category><category>SQL*Net</category><category>SQL*Plus login prompt</category><category>SQR</category><category>Scheduled Queries</category><category>Search Record</category><category>Standby Database</category><category>Statistics Retention</category><category>Stored Outlines</category><category>System Statistics</category><category>Temporary Tablespace</category><category>Terminated Connection Timeout</category><category>Tuxedo Queuing</category><category>Unix Process Limits Memory Application Engine</category><category>XML Reporting</category><category>clob</category><category>clone database</category><category>column default values</category><category>message log</category><category>multi-tenant</category><category>posting</category><category>re-posting</category><category>redo logging</category><category>row cache lock</category><category>row source alias</category><category>scalability</category><category>scalar queries</category><category>serialisation</category><category>session parameters</category><category>sparse index</category><category>trace &quot;external table&quot; directory</category><category>unicode</category><category>v$session_longops</category><category>varchar2</category><title>The PeopleSoft DBA Blog</title><description>This blog contains things about PeopleSoft, mostly performance related, that DBAs might find interesting.&lt;br&gt;Or then again they might not!  The non-PeopleSoft Oracle stuff is at &lt;a href=&quot;https://blog.go-faster.co.uk&quot;&gt;blog.go-faster.co.uk&lt;/a&gt;.</description><link>https://blog.psftdba.com/</link><managingEditor>noreply@blogger.com (David Kurtz)</managingEditor><generator>Blogger</generator><openSearch:totalResults>193</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>25</openSearch:itemsPerPage><item><guid isPermaLink="false">tag:blogger.com,1999:blog-25740336.post-1846325838446995058</guid><pubDate>Tue, 06 Jan 2026 15:30:00 +0000</pubDate><atom:updated>2026-01-06T15:30:37.982+00:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">PS/Query</category><title>Managing Ad Hoc PS/Queries in PeopleSoft</title><description>&lt;div&gt;This post draws together all the recommendations for managing ad hoc user queries in PeopleSoft PS/Query.&amp;nbsp; Some points relate to the database, some to the configuration of the middleware (Application servers and process schedulers), and some are human issues rather than technical ones.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;Different timeouts are set in different places, but some of these timeouts need to align.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;Long-running queries mostly consume CPU on the database, and in the cloud, most of what you pay for is CPU.&amp;nbsp; Nobody would want to allow an unconstrained query load to lead to an increase in the cloud subscription requirements!&lt;/div&gt;&lt;h4 style=&quot;text-align: left;&quot;&gt;Recommendations&lt;/h4&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Do not increase the &lt;a href=&quot;https://docs.oracle.com/cd/E92519_02/pt856pbr3/eng/pt/tsvt/task_WebServerTimeouts-071144.html#u34934667-92d7-43ea-8aee-3cdb78df3508&quot; target=&quot;_blank&quot;&gt;PIA inactivity timeout&lt;/a&gt; or PS/Query timeouts.&amp;nbsp; They are both delivered set at 20 minutes.&amp;nbsp; They are primarily there to protect users and the system as a whole.&lt;/li&gt;
&lt;ul&gt;&lt;li&gt;If you have already increased them, you may need to bring them down in stages, allowing users to get used to the new limits and handle exceptional queries.&lt;/li&gt;
&lt;li&gt;If you permit users to use PS/Query, then the PIA inactivity timeout should be the same &lt;a href=&quot;https://docs.oracle.com/cd/F44947_01/pt858pbr3/eng/pt/tsvt/task_ApplicationServerTimeouts-071147.html#uaa64da38-ff4f-4bce-9e1d-003c27cbf2fa&quot; target=&quot;_blank&quot;&gt;ICQuery Tuxedo service timeout&lt;/a&gt; set on the PSQRYSRV server (default 20 minutes)&lt;/li&gt;
&lt;li&gt;If you do not use online PS/Query, then the PIA timeout can be reduced as low as the PSAPPSRV timeout (default 5 minutes).&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;
&lt;li&gt;It is not possible to regulate when users run queries in the PIA.&amp;nbsp; Users, quite legitimately, also need to run queries that may take longer than the timeouts.&amp;nbsp; Encourage users to schedule such queries on the process scheduler.&amp;nbsp;&amp;nbsp;&lt;/li&gt;
&lt;ul&gt;&lt;li&gt;You can restrict the number of online queries that execute concurrently on the database by restricting the maximum number of PSQRYSRV processes.&amp;nbsp; There is still nothing to prevent users from starting queries which then just queue up on the application server.&amp;nbsp; Users cannot distinguish between a query that is queuing in the application server or executing on the database.&amp;nbsp; The service timeout starts from when the service request is queued in the application server.&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;
&lt;li&gt;However, you still don’t want even scheduled queries to run for hours on end.&amp;nbsp; Set a &lt;a href=&quot;https://docs.oracle.com/cd/F44947_01/pt858pbr3/eng/pt/tprs/task_DefiningProcessDefinitions-c0710f.html#u72c07dfc-83f9-47fc-9d18-bdbe1300d36d&quot; target=&quot;_blank&quot;&gt;maximum processing time in the process definition&lt;/a&gt; for the PSQUERY application engine.&amp;nbsp; Say 4 hours. The process scheduler will terminate the PSQUERY process after this amount of time.&amp;nbsp;&amp;nbsp;&lt;/li&gt;
&lt;ul&gt;&lt;li&gt;A common cause of long-running PS/Queries is a mistake in the query definition, resulting in missing or incorrect joins in the SQL.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;A SQL query can continue to run on the database server after the client-side process, the PSQRYSRV server process or the PSQUERY application engine process, has terminated, at least until the current fetch returns.&amp;nbsp; A common cause is that a large sort or hash operation must complete before the first fetch returns.&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;
&lt;li&gt;Enable dead connect detection by setting &lt;a href=&quot;https://docs.oracle.com/en/database/oracle/oracle-database/26/netrf/parameters-for-the-sqlnet.ora.html#GUID-1070805B-0703-457C-8D2E-4EEC26193E5F&quot; target=&quot;_blank&quot;&gt;SQLNET.EXPIRE_TIME&lt;/a&gt; in &lt;a href=&quot;https://docs.oracle.com/en/database/oracle/oracle-database/26/netrf/parameters-for-the-sqlnet.ora.html&quot; target=&quot;_blank&quot;&gt;SQLNET.ORA&lt;/a&gt;.&amp;nbsp; The idle Oracle shadow process will periodically poll the client process to check that it is still responsive.&amp;nbsp; If a PSQUERY process has been terminated by the process scheduler, this mechanism should also terminate the session if it is still running on the database.&lt;/li&gt;
  &lt;ul&gt;&lt;li&gt;See &lt;a href=&quot;https://blog.psftdba.com/2009/07/oracle-terminated-connection-timeout.html&quot;&gt;Oracle Terminated Connection Timeout&lt;/a&gt;&amp;nbsp;&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;
  &lt;li&gt;Use the database resource manager to allocate and manage CPU allocation and limit run time.&lt;/li&gt;
  &lt;ul&gt;&lt;li&gt;The resource manager only intervenes when the system runs out of free CPU.&lt;/li&gt;
&lt;li&gt;Define a hierarchy of resource manager consumer groups.&amp;nbsp; Map database sessions to consumer groups using session instrumentation.&amp;nbsp; In general, online processing is higher priority than scheduled processes, which are higher priority than ad-hoc queries.&lt;/li&gt;&lt;ul&gt;&lt;li&gt;See &lt;a href=&quot;https://blog.psftdba.com/2024/03/psftplan-sample-oracle-database.html&quot;&gt;PSFT_PLAN: A Sample Oracle Database Resource Manager Plan for PeopleSoft&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;
  &lt;li&gt;Set a maximum CPU/runtime limit for scheduled queries.&amp;nbsp; Using different consumer groups for online and scheduled batch queries.&lt;/li&gt;&lt;ul&gt;&lt;li&gt;For scheduled queries, match the consumer group timeout to the maximum runtime for the PSQUERY application engine.&lt;/li&gt;&lt;li&gt;For online queries, match the consumer group timeout to the PIA inactivity timeout (which should also be the same as the ICQuery Tuxedo service timeout).&lt;/li&gt;&lt;li&gt;See&amp;nbsp;&lt;a href=&quot;https://blog.psftdba.com/2025/03/resourcemanagericquerytimelimit.html&quot;&gt;A Resource Manager CPU Time Limit for PS/Queries Executed Online in the PIA&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;li&gt;NB: Resource Manager is an Oracle Enterprise Edition Feature.&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;
&lt;li&gt;On Exadata, use SQL Quarantine to identify queries with a track record of exceeding the consumer group timeout.&amp;nbsp;&amp;nbsp;&lt;/li&gt;
&lt;ul&gt;&lt;li&gt;Enable SQL Quarantine for scheduled queries only using a trigger on &lt;a href=&quot;https://www2.go-faster.co.uk/peopletools/psprcsrqst.htm&quot; target=&quot;_blank&quot;&gt;PSPRQSRQST&lt;/a&gt; – see&amp;nbsp; &lt;a href=&quot;https://github.com/davidkurtz/psscripts/blob/master/set_prcs_sess_parm_trg.sql&quot;&gt;Github: set_prcs_sess_parm_trg.sql&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Quarantined queries are immediately terminated with error &lt;a href=&quot;https://docs.oracle.com/en/error-help/db/ora-00040/?r=26ai&quot; target=&quot;_blank&quot;&gt;ORA-00040: active time limit exceeded - call aborted&lt;/a&gt;.&amp;nbsp; For scheduled queries, this error can be found in the process scheduler message log.&lt;/li&gt;&lt;li&gt;Enable SQL Quarantine for SQL Developer, etc, using an on-logon trigger – see &lt;a href=&quot;https://github.com/davidkurtz/psscripts/blob/master/set_sess_parm_trg.sql&quot;&gt;Github: set_sess_parm_trg.sql&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;
&lt;li&gt;Add run control logging for scheduled PS/Queries&lt;/li&gt;&lt;ul&gt;&lt;li&gt;Whenever a scheduled PS/Query, run via PSQUERY, terminates other than successfully, the SQL query can be found in the message log.&amp;nbsp; However, bind variable values are not recorded.&amp;nbsp;&amp;nbsp;&lt;/li&gt;
&lt;li&gt;Bind variable values may be changed between executions of the same query using the same run control by the same user.&amp;nbsp; Enable run control logging so that it is possible to reconstruct the query and bind variables run by the user.&lt;/li&gt;&lt;ul&gt;&lt;li&gt;See&amp;nbsp;&lt;a href=&quot;https://blog.psftdba.com/2025/04/loggingpsquerybindvalues.html&quot;&gt;Logging Run Controls and Bind Variables for Scheduled PS/Queries&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;
&lt;li&gt;There is nothing to stop the user from altering the PS/Query between executions, in which case you may need to get the SQL of a long-running query that completes successfully from AWR.&amp;nbsp;&amp;nbsp;&lt;/li&gt;
&lt;ul&gt;&lt;li&gt;See:&amp;nbsp;&lt;a href=&quot;https://blog.psftdba.com/2025/02/management-of-long-running-psqueries.html&quot;&gt;Management of Long Running PS/Queries Cancelled by Resource Manager CPU Limit&lt;/a&gt;&amp;nbsp;&lt;/li&gt;&lt;/ul&gt;
&lt;/ul&gt;&lt;br /&gt;
&lt;li&gt;It is not possible to allocate PS/Queries to a different temporary tablespace because that is determined by the database user ID.&amp;nbsp; Everything in PeopleSoft runs as the Owner ID (usually SYSADM).&amp;nbsp; Unless you are faking a reporting database with a second schema pointed to by a second entry in PS.PSDBOWNER.&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div class=&quot;blogger-post-footer&quot;&gt;&lt;a href=&quot;http://www.go-faster.co.uk/&quot;&gt;©David Kurtz&lt;/a&gt;&lt;/div&gt;</description><link>https://blog.psftdba.com/2026/01/managing-ad-hoc-psqueries-in-peoplesoft.html</link><author>noreply@blogger.com (David Kurtz)</author><thr:total>1</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-25740336.post-859557964846356395</guid><pubDate>Fri, 25 Jul 2025 10:25:00 +0000</pubDate><atom:updated>2025-12-21T22:03:58.421+00:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">compression</category><category domain="http://www.blogger.com/atom/ns#">Indexes</category><title>Determining Optimal Index Key Compression Length</title><description>&lt;p&gt;&amp;nbsp;In 2008, Richard Foote wrote this still excellent 4-part guide to index key compression.&amp;nbsp;&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;https://richardfoote.wordpress.com/2008/02/17/index-compression-part-i-low/&quot; target=&quot;_blank&quot;&gt;Index Compression Part I (Low)&lt;/a&gt;&amp;nbsp;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://richardfoote.wordpress.com/2008/02/20/index-compression-part-ii-down-is-the-new-up/&quot; target=&quot;_blank&quot;&gt;Index Compression Part II (Down Is The New Up)&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://richardfoote.wordpress.com/2008/02/22/index-compression-part-iii-225/&quot; target=&quot;_blank&quot;&gt;Index Compression Part III (2+2=5)&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://richardfoote.wordpress.com/2008/02/29/index-compression-part-iv-packt-like-sardines-in-a-crushd-tin-box/&quot; target=&quot;_blank&quot;&gt;Index Compression Part IV (Packt Like Sardines In a Crushd Tin Box)&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;He started with this comment, which I think is just as valid as it was then:&lt;/p&gt;&lt;p&gt;&lt;i&gt;“Index compression is perhaps one of the most under used and neglected index options available. It has the potential to substantially reduce the overall size of non-Unique indexes and multi-column unique indexes, in some scenarios dramatically so... Not only will it potentially save storage, but if the resultant index contains fewer leaf blocks, that’s potentially fewer LIOs and from the Cost Based Optimizer’s point of view, potentially a cheaper execution plan option.”&lt;/i&gt;&lt;/p&gt;
&lt;p&gt;Index key compression is a highly effective option for reducing index size and improving index performance.&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;i&gt;“Oracle stores each distinct combination of compressed column values found within a specific index leaf block in a ‘Prefix’ table within the leaf block and assigns each combination a unique prefix number.”&lt;/i&gt; If the prefix length (the number of leading columns to be compressed) is too great, then the prefix table will contain more entries, ultimately one for every row in the index. The compressed index could end up being larger than the uncompressed index!&amp;nbsp; If the prefix length is too small, then you might not get as much compression as you might with a longer prefix length.&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;In other words, there is a sweet spot where you will achieve optimal compression.&amp;nbsp; That sweet spot can vary from no compression to compressing all the columns.&amp;nbsp; It will vary from index to index, from partition to partition, and potentially over time as the data in an index changes.&lt;/p&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Test Every Option to Determine Optimal Compression&lt;/h3&gt;&lt;p&gt;One way to determine optimal compression is through exhaustive testing.&amp;nbsp; Each index could be rebuilt at each possible compression prefix length, and the size of the index could be compared, and the performance of application processes could be tested.&lt;/p&gt;&lt;p&gt;The following PL/SQL script (available on &lt;a href=&quot;https://github.com/davidkurtz/psscripts/blob/master/index_compression_test.sql&quot; target=&quot;_blank&quot;&gt;GitHub&lt;/a&gt;) rebuilds each index on a named table at each possible compression length, collects statistics and stores them in a table.&lt;/p&gt;
&lt;pre style=&quot;background-color: #eeeeee; font-family: &amp;quot;courier new&amp;quot;overflow: auto; line-height: 80%; text-align: left; width: X-SMALL;&quot;&gt;&lt;span style=&quot;font-size: x-small;&quot;&gt;REM &lt;a href=&quot;https://github.com/davidkurtz/psscripts/blob/master/index_compression_test.sql&quot; target=&quot;_blank&quot;&gt;index_compression_test.sql&lt;/a&gt;
create table gfc_index_compression_stats
(table_name varchar2(128)
,index_name varchar2(128)
,num_rows number
,last_analyzed date
,prefix_length number 
,blevel number 
,leaf_blocks number 
,avg_leaf_blocks_per_key number 
,avg_data_blocks_per_key number 
,clustering_factor number 
,constraint gfc_index_compression_stats_pk primary key (table_name, index_name, prefix_length)
);

DECLARE
  l_table_name VARCHAR2(128) := &#39;PSTREENODE&#39;;
  l_num_cols INTEGER;
  l_sql CLOB;
  e_invalid_compress_length EXCEPTION;
  PRAGMA EXCEPTION_INIT(e_invalid_compress_length,-25194); 
BEGIN
  FOR i IN (
    SELECT table_name, index_name, column_position prefix_length FROM user_ind_columns
    WHERE table_name = l_table_name
    UNION
    SELECT table_name, index_name, 0 FROM user_indexes
    WHERE table_name = l_table_name
    ORDER BY table_name, index_name, prefix_length DESC
  ) LOOP
   IF i.prefix_length &amp;gt; 0 THEN 
     l_sql := &#39;ALTER INDEX &#39;||i.index_name||&#39; REBUILD COMPRESS &#39;||i.prefix_length;
   ELSE
     l_sql := &#39;ALTER INDEX &#39;||i.index_name||&#39; REBUILD NOCOMPRESS&#39;;
   END IF;

   BEGIN
     dbms_output.put_line(l_sql);
     EXECUTE IMMEDIATE l_sql;
     dbms_stats.gather_index_stats(user,i.index_name);
   
     MERGE INTO gfc_index_compression_stats u
     USING (SELECT * FROM user_indexes WHERE table_name = i.table_name And index_name = i.index_name) s
     ON (u.table_name = s.table_name AND u.index_name = s.index_name AND u.prefix_length = NVL(s.prefix_length,0))
     WHEN MATCHED THEN UPDATE SET u.num_rows = s.num_rows, u.last_analyzed = s.last_analyzed, u.blevel = s.blevel, u.leaf_blocks = s.leaf_blocks, u.avg_leaf_blocks_per_key = s.avg_leaf_blocks_per_key, u.avg_data_blocks_per_key = s.avg_data_blocks_per_key, u.clustering_factor = s.clustering_factor
     WHEN NOT MATCHED THEN INSERT (table_name, index_name, num_rows, last_analyzed, prefix_length, blevel, leaf_blocks, avg_leaf_blocks_per_key, avg_data_blocks_per_key, clustering_factor)
     VALUES (s.table_name, s.index_name, s.num_rows, s.last_analyzed, NVL(s.prefix_length,0), s.blevel, s.leaf_blocks, s.avg_leaf_blocks_per_key, s.avg_data_blocks_per_key, s.clustering_factor);
   EXCEPTION 
     WHEN e_invalid_compress_length THEN NULL;
   END;  
  END LOOP; 
END;
/&lt;/span&gt;&lt;/pre&gt;The following chart presents the data collected by the script above for the&amp;nbsp;&lt;a href=&quot;https://www2.go-faster.co.uk/peopletools/pstreenode.htm&quot; target=&quot;_blank&quot;&gt;PSTREENODE&lt;/a&gt;&amp;nbsp;table in PeopleSoft.&amp;nbsp; The number of leaf blocks is graphed against the compression prefix length. The left-hand end of each line shows the uncompressed size of the index.&amp;nbsp;&lt;div&gt;For most indexes, the size decreases as the compression prefix length increases until it reaches a minimum.&amp;nbsp; That is the optimal compression.&amp;nbsp; Beyond that point, where the prefix columns are (or are almost) unique, the compressed index is larger than the uncompressed index. In my example, only two indexes benefit from the entire key index being compressed.&amp;nbsp; For all the other indexes, the optimal compression is obtained when some, but not all, of the key columns are compressed.&lt;/div&gt;&lt;div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEipEW6yBXpxLmbHWtbsOQnzuwYXqt6apZhyE4FeFwp7RJyDvOFJrRLM8c1CRv0Gwle8zAwjqrmfn7a4CDRBiUV8qiUoTFLeyNQ2O_H2RIdqwsyOcyR-xomTkN05xD0Eule3P6ShhhtPT2FOoyIBT4erHJuk5KJw7skmabujIZxAd5Emwqup1D97&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img alt=&quot;&quot; data-original-height=&quot;578&quot; data-original-width=&quot;886&quot; height=&quot;418&quot; src=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEipEW6yBXpxLmbHWtbsOQnzuwYXqt6apZhyE4FeFwp7RJyDvOFJrRLM8c1CRv0Gwle8zAwjqrmfn7a4CDRBiUV8qiUoTFLeyNQ2O_H2RIdqwsyOcyR-xomTkN05xD0Eule3P6ShhhtPT2FOoyIBT4erHJuk5KJw7skmabujIZxAd5Emwqup1D97=w640-h418&quot; width=&quot;640&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div&gt;There are 8 indexes on &lt;a href=&quot;https://www2.go-faster.co.uk/peopletools/pstreenode.htm&quot; target=&quot;_blank&quot;&gt;PSTREENODE&lt;/a&gt;, and in all, the script performed 53 index rebuilds.&amp;nbsp; On a small table such as this, it only takes a few minutes to work through this, but on a larger table, this could easily become prohibitively time-consuming.&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Let Oracle Calculate the Optimal Compression Prefix Length&lt;/h3&gt;&lt;div&gt;The alternative is to let Oracle calculate the optimal compression prefix length.&lt;/div&gt;&lt;div&gt;Before Oracle introduced the DBMS_STATS package, we used the &lt;a href=&quot;https://docs.oracle.com/en/database/oracle/oracle-database/19/refrn/INDEX_STATS.html&quot; target=&quot;_blank&quot;&gt;ANALYZE&lt;/a&gt; command to collect optimizer statistics.&amp;nbsp; The last remaining use of this command is to validate object structures.&lt;/div&gt;
  &lt;pre style=&quot;background-color: #eeeeee; font-family: &amp;quot;courier new&amp;quot;overflow: auto; line-height: 80%; text-align: left; width: X-SMALL;&quot;&gt;&lt;span&gt;&lt;span style=&quot;font-size: x-small;&quot;&gt;ANALYZE INDEX … VALIDATE STRUCTURE CASCADE;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;
  &lt;div&gt;&lt;i&gt;You can validate an object and all dependent objects (for example, indexes) by including the CASCADE option. The following statement validates the emp table and all associated indexes:&lt;/i&gt;&lt;/div&gt;
  &lt;pre style=&quot;background-color: #eeeeee; font-family: &amp;quot;courier new&amp;quot;overflow: auto; line-height: 80%; text-align: left; width: X-SMALL;&quot;&gt;&lt;span&gt;&lt;span style=&quot;font-size: x-small;&quot;&gt;ANALYZE TABLE emp VALIDATE STRUCTURE CASCADE;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;

  &lt;div&gt;&lt;/div&gt;&lt;div&gt;[&lt;a href=&quot;https://docs.oracle.com/cd/B28359_01/server.111/b28310/general002.htm#ADMIN11526&quot; target=&quot;_blank&quot;&gt;Validating Tables, Indexes, Clusters, and Materialized Views&lt;/a&gt;]&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;i&gt;For an index, Oracle Database verifies the integrity of each data block in the index and checks for block corruption. This clause does not confirm that each row in the table has an index entry or that each index entry points to a row in the table. You can perform these operations by validating the structure of the table with the CASCADE clause.&lt;/i&gt;&lt;/div&gt;&lt;div&gt;&lt;i&gt;Oracle Database also computes compression statistics (optimal prefix compression count) for all normal indexes.&lt;/i&gt;&lt;/div&gt;&lt;div&gt;&lt;i&gt;Oracle Database stores statistics about the index in the data dictionary views INDEX_STATS and INDEX_HISTOGRAM.&lt;/i&gt;&lt;/div&gt;&lt;div&gt;[&lt;a href=&quot;https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/ANALYZE.html#d297439e766&quot; target=&quot;_blank&quot;&gt;SQL Language Reference &amp;gt; Analyze&lt;/a&gt;]&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;The following script analyses each index.&lt;/div&gt;&lt;div&gt;&lt;a href=&quot;https://docs.oracle.com/en/database/oracle/oracle-database/19/refrn/INDEX_STATS.html&quot; rel=&quot;nofollow&quot; target=&quot;_blank&quot;&gt;INDEX_STATS&lt;/a&gt;&amp;nbsp;displays only the results for the last &lt;a href=&quot;https://docs.oracle.com/en/database/oracle/oracle-database/19/refrn/INDEX_STATS.html&quot; rel=&quot;nofollow&quot; target=&quot;_blank&quot;&gt;ANALYZE&lt;/a&gt; command in the current session, so the script transfers them to a permanent table.&amp;nbsp; It does not reanalyse indexes for which a result is already stored.&lt;/div&gt;&lt;/div&gt;&lt;pre style=&quot;background-color: #eeeeee; font-family: &amp;quot;courier new&amp;quot;overflow: auto; line-height: 80%; text-align: left; width: X-SMALL;&quot;&gt;&lt;span style=&quot;font-size: x-small;&quot;&gt;REM &lt;a href=&quot;https://github.com/davidkurtz/psscripts/blob/master/calc_opt_comp.sql&quot; target=&quot;_blank&quot;&gt;calc_opt_comp.sql&lt;/a&gt;
REM (c)Go-Faster Consultancy Ltd. 2014
REM see &lt;a href=&quot;https://blog.psftdba.com/2016/02/implementing-index-compression-and.html&quot; target=&quot;_blank&quot;&gt;https://blog.psftdba.com/2016/02/implementing-index-compression-and.html&lt;/a&gt;
set serveroutput on autotrace off
clear columns
SPOOL calc_opt_comp

REM DROP TABLE sysadm.gfc_index_stats PURGE;

--create working storage table with same structure as INDEX_STATS
CREATE TABLE sysadm.gfc_index_stats 
AS SELECT * FROM index_stats
WHERE 1=2
/

ALTER TABLE sysadm.gfc_index_stats
MODIFY name NOT NULL
/

CREATE UNIQUE INDEX sysadm.gfc_index_stats
ON sysadm.gfc_index_stats (name, partition_name)
/

undefine table_name
DECLARE
 l_sql        VARCHAR2(100);
 l_owner      VARCHAR2(8) := &#39;SYSADM&#39;;
 l_table_name VARCHAR2(30) := &#39;&amp;amp;&amp;amp;table_name&#39;;
BEGIN
 FOR i IN (
  SELECT i.index_name, ip.partition_name
  FROM   all_indexes i
  ,      all_ind_partitions ip
  WHERE  i.index_type like &#39;%NORMAL&#39;
  AND    i.table_owner = l_owner
  AND    i.partitioned = &#39;YES&#39;
  AND    i.table_name = l_table_name
  AND    ip.index_owner = i.owner
  AND    ip.index_name  = i.index_name
  AND    ip.subpartition_count = 0
  AND    ip.segment_created = &#39;YES&#39;
  UNION
  SELECT i.index_name, isp.subpartition_name
  FROM   all_indexes i
  ,      all_ind_subpartitions isp
  WHERE  i.index_type like &#39;%NORMAL&#39;
  AND    i.table_owner = l_owner
  AND    i.partitioned = &#39;YES&#39;
  AND    i.table_name = l_table_name
  AND    isp.index_owner = i.owner
  AND    isp.index_name  = i.index_name
  AND    isp.segment_created = &#39;YES&#39;
  UNION
  SELECT i.index_name, NULL
  FROM   all_indexes i
  WHERE  i.index_type like &#39;%NORMAL&#39;
  AND    i.table_owner = l_owner
  AND    i.table_name = l_table_name
  AND    i.partitioned = &#39;NO&#39;
  AND    i.segment_created = &#39;YES&#39;
  MINUS
  SELECT name, partition_name
  FROM   sysadm.gfc_index_stats
 ) LOOP
  IF i.partition_name IS NULL THEN
    l_sql := &#39;ANALYZE INDEX &#39;||l_owner||&#39;.&#39;||i.index_name||&#39; VALIDATE STRUCTURE&#39;;
  ELSE
    l_sql := &#39;ANALYZE INDEX &#39;||l_owner||&#39;.&#39;||i.index_name||&#39; PARTITION (&#39;||i.partition_name||&#39;) VALIDATE STRUCTURE&#39;;
  END IF;

  dbms_output.put_line(l_sql);
  EXECUTE IMMEDIATE l_sql;

  DELETE FROM sysadm.gfc_index_stats g
  WHERE EXISTS(
	SELECT  &#39;x&#39;
	FROM	index_stats i
	WHERE 	i.name = g.name
	AND	(i.partition_name = g.partition_name OR (i.partition_name IS NULL AND g.partition_name IS NULL)));

  INSERT INTO sysadm.gfc_index_stats 
  SELECT i.* FROM index_stats i;
  COMMIT;
 END LOOP;
END;
/
…&lt;/span&gt;&lt;/pre&gt;The script produces reports of its analysis.&amp;nbsp; The summary report shows the optimal compression length for each index and lists the columns that are and are not compressed.&amp;nbsp; We can see that the result of the ANALYZE command agrees with the result of the previous test that rebuilt each index at each compression length and measured the size of the index.
&lt;pre style=&quot;background-color: #eeeeee; font-family: &amp;quot;courier new&amp;quot;overflow: auto; line-height: 80%; text-align: left; width: X-SMALL;&quot;&gt;&lt;span style=&quot;font-size: x-small;&quot;&gt;                                                     Summary Report

                                      Opt Comp                         Weighted         Est.                            
                                        Prefix        Num               Average         Comp                            
Table Name         Index Name           Length FREQ Parts       Blocks Saving %       Blocks                            
------------------ ------------------ -------- ---- ----- ------------ -------- ------------                            
Compress Columns                                            Do Not Compress Columns                                     
----------------------------------------------------------- ----------------------------------------------------------- 
PSTREENODE         PSAPSTREENODE             4    1     0        2,048     41.0        1,208                            
SETID, TREE_NAME, EFFDT, TREE_BRANCH                        TREE_NODE, TREE_NODE_NUM, TREE_NODE_NUM_END, TREE_NODE_TYPE 
                                                                                                                        
                   PSBPSTREENODE             8    1     0        1,920     34.0        1,267                            
SETID, TREE_NAME, TREE_BRANCH, TREE_NODE_NUM, TREE_NODE, TR                                                             
EE_NODE_NUM_END, TREE_LEVEL_NUM, TREE_NODE_TYPE                                                                         
                                                                                                                        
                   PSDPSTREENODE             3    1     0        1,280     61.0          499                            
SETID, TREE_NAME, EFFDT                                     PARENT_NODE_NUM                                             
                                                                                                                        
                   PSFPSTREENODE             2    1     0        1,024     67.0          338                            
TREE_NAME, EFFDT                                                                                                        
                                                                                                                        
                   PSGPSTREENODE             2    1     0        2,304     35.0        1,498                            
PARENT_NODE_NAME, TREE_NAME                                 EFFDT, TREE_NODE, SETID                                     
                                                                                                                        
                   PSHPSTREENODE             2    1     0        2,048     24.0        1,556                            
TREE_NODE, TREE_NAME                                        EFFDT, SETID, SETCNTRLVALUE, TREE_NODE_NUM                  
                                                                                                                        
                   PSIPSTREENODE             3    1     0        1,152       .0        1,152                            
SETID, TREE_NAME, EFFDT                                     TREE_NODE, TREE_NODE_NUM, TREE_NODE_NUM_END                 
                                                                                                                        
                   PS_PSTREENODE             4    1     0        1,792     46.0          968                            
SETID, SETCNTRLVALUE, TREE_NAME, EFFDT                      TREE_NODE_NUM, TREE_NODE, TREE_BRANCH                       
                                                                                                                        
******************                                  ----- ------------          ------------                            
                                                                                                                        
                                                                                                                        
sum                                                     0       13,568                 8,486&lt;/span&gt;&lt;/pre&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Compression of Partitioned Indexes&lt;/h3&gt;&lt;div&gt;If you partition an index, then the script validates the structure of each physical partition.&amp;nbsp; The detailed report shows the optimal compression for each partition.&amp;nbsp; You may find that Oracle determines that the optimal compression is different for different partitions.&amp;nbsp; &amp;nbsp; &amp;nbsp;&amp;nbsp;&lt;/div&gt;&lt;div&gt;Only a single compression length can be specified for each index.&amp;nbsp; It is then applied to all the partitions, although compression can be disabled on specific partitions.&amp;nbsp; A judgement has to be made as to what is the best balance.&amp;nbsp;&amp;nbsp;&lt;/div&gt;&lt;pre style=&quot;background-color: #eeeeee; font-family: &amp;quot;courier new&amp;quot;overflow: auto; line-height: 80%; text-align: left; width: X-SMALL;&quot;&gt;&lt;span style=&quot;font-size: x-small;&quot;&gt;                                                     Detail Report

                                                                     Opt Comp                             Est.          
                                                                       Prefix              Saving         Comp          
Table Name         Index Name         Partition Name                   Length       Blocks      %       Blocks          
------------------ ------------------ ------------------------------ -------- ------------ ------ ------------          
…
                   PSHJRNL_LN         JRNL_LNH201612                        1      143,264  142.0      -60,171          
                                      JRNL_LNH201712                        0       88,192   74.0       22,930          
                                      JRNL_LNH201812                        6       12,240     .0       12,240          
                                      JRNL_LNH201912                        6       11,104     .0       11,104          
…
                                      JRNL_LNH202201                        6       13,752     .0       13,752          
                                      JRNL_LNH202202                        6        5,496     .0        5,496          
                                      JRNL_LNH202203                        6        6,504     .0        6,504          
                                      JRNL_LNH202204                        6        5,920     .0        5,920          
                                      JRNL_LNH202205                        6        6,864     .0        6,864          
                                      JRNL_LNH202206                        6       13,584     .0       13,584          
                                      JRNL_LNH202207                        6       12,408     .0       12,408          
                                      JRNL_LNH202208                        3      212,904  121.0      -44,710          
                                      JRNL_LNH202209                        0      262,472  111.0      -28,872          
                                      JRNL_LNH202210                        3      228,552  102.0       -4,571          
                   ******************                                         ------------        ------------          
                   sum                                                           1,625,328             574,550&lt;/span&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;NB: ANALYZE INDEX on some partitions predicted a saving greater than 100%, leading to a negative predicted size estimate.&amp;nbsp; This is obviously impossible. In the past, there was a bug (now resolved) that caused this behaviour.&amp;nbsp; This occurs when the predicted optimal compression length is less than the current compression length in an index that is already compressed.&amp;nbsp; However, this problem does not occur consistently.&amp;nbsp;&amp;nbsp;&lt;/div&gt;&lt;div&gt;In the above example, 6 columns of PSHJRLN_LN have already been compressed for all partitions.&amp;nbsp; The script has validated that, for the majority of partitions, this is optimal and calculates that there is no further space saving available.&amp;nbsp; However, some partitions require less compression.&amp;nbsp;&amp;nbsp;&lt;/div&gt;&lt;div&gt;The choice is between:&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Choosing to compress the entire index at a shorter compression.&amp;nbsp; In which case, most of the partitions will be larger, the exception partitions will be small, but the net effect is that the index will be larger.&amp;nbsp;&lt;/li&gt;&lt;li&gt;Disabling compression on these partitions.&amp;nbsp; Over-compressed indexes are generally only slightly larger than uncompressed indexes, so the benefit is probably only small&lt;/li&gt;&lt;li&gt;Leave compression at the length that is optimal for most of the partitions, accepting that a few partitions will be over-compressed.&amp;nbsp; This usually results in the smallest index overall.&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;I tend to favour the last option on the basis that an over-compressed index is only slightly larger than an uncompressed index, but your mileage will vary.&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;blogger-post-footer&quot;&gt;&lt;a href=&quot;http://www.go-faster.co.uk/&quot;&gt;©David Kurtz&lt;/a&gt;&lt;/div&gt;</description><link>https://blog.psftdba.com/2025/07/optimalindexcompression.html</link><author>noreply@blogger.com (David Kurtz)</author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/a/AVvXsEipEW6yBXpxLmbHWtbsOQnzuwYXqt6apZhyE4FeFwp7RJyDvOFJrRLM8c1CRv0Gwle8zAwjqrmfn7a4CDRBiUV8qiUoTFLeyNQ2O_H2RIdqwsyOcyR-xomTkN05xD0Eule3P6ShhhtPT2FOoyIBT4erHJuk5KJw7skmabujIZxAd5Emwqup1D97=s72-w640-h418-c" height="72" width="72"/><thr:total>0</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-25740336.post-8013771644728795197</guid><pubDate>Mon, 30 Jun 2025 13:05:00 +0000</pubDate><atom:updated>2025-06-30T14:05:50.159+01:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">Journal Line</category><category domain="http://www.blogger.com/atom/ns#">PS_JRNL_LN</category><title>Optimising Journal Line Queries: 5. Conclusion</title><description>&lt;p&gt;This is the last of five articles that examine the challenges posed by typical queries on the journal line table (PS_JRNL_LN).&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;ol style=&quot;text-align: left;&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;https://blog.psftdba.com/2024/12/journallinequeries1problemstatement.html&quot;&gt;Problem Statement&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://blog.psftdba.com/2024/12/journallinequeries2exadatasystemstatistics.html&quot;&gt;Exadata System Statistics&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://blog.psftdba.com/2024/12/optimisingjournalline3partitioning.html&quot;&gt;Partitioning&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://blog.psftdba.com/2024/12/optimisingjournalline4compression.html&quot;&gt;Compression&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://blog.psftdba.com/2025/05/optimisingjournalline5conclusion.html&quot;&gt;Conclusion&lt;/a&gt;&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;After introducing Exadata system statistics, partitioning, and compression and also archiving some historical data, we arrived at the point where the execution plan of the statement changes without needing to use hints.&lt;/p&gt;
&lt;h3 style=&quot;text-align: left;&quot;&gt;Original Execution Plan (With Hints)&lt;/h3&gt;&lt;p&gt;I have used hints to force the original execution plan.&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;pre style=&quot;background-color: #eeeeee; font-family: &amp;quot;courier new&amp;quot;overflow: auto; line-height: 80%; text-align: left; width: 95%;&quot;&gt;&lt;span style=&quot;font-size: 70%;&quot;&gt;ALTER SESSION SET STATISTICS_LEVEL=ALL;
SELECT /*+LEADING(A) USE_NL(B) 
          INDEX(B (PS_JRNL_LN.BUSINESS_UNIT PS_JRNL_LN.JOURNAL_ID PS_JRNL_LN.JOURNAL_DATE PS_JRNL_LN.UNPOST_SEQ PS_JRNL_LN.JOURNAL_LINE PS_JRNL_LN.LEDGER))*/
     A.FISCAL_YEAR, A.ACCOUNTING_PERIOD, B.BUSINESS_UNIT, B.JOURNAL_ID, TO_CHAR(B.JOURNAL_DATE,&#39;YYYY-MM-DD&#39;), B.LEDGER, B.ACCOUNT
, B.PRODUCT, B.PROJECT_ID, B.CHARTFIELD1, B.CHARTFIELD2, B.CURRENCY_CD, B.AFFILIATE, SUM( B.MONETARY_AMOUNT), B.FOREIGN_CURRENCY
, SUM( B.FOREIGN_AMOUNT), A.REVERSAL_CD, A.REVERSAL_ADJ_PER, A.JRNL_HDR_STATUS, TO_CHAR(A.POSTED_DATE,&#39;YYYY-MM-DD&#39;), A.OPRID, A.DESCR254
, B.DEPTID, A.SOURCE, B.ALTACCT, TO_CHAR(CAST((A.DTTM_STAMP_SEC) AS TIMESTAMP),&#39;YYYY-MM-DD-HH24.MI.SS.FF&#39;), B.LINE_DESCR 
FROM PS_JRNL_HEADER A, PS_JRNL_LN B 
WHERE (A.BUSINESS_UNIT = B.BUSINESS_UNIT 
AND A.JOURNAL_ID = B.JOURNAL_ID 
AND A.JOURNAL_DATE = B.JOURNAL_DATE 
AND A.UNPOST_SEQ = B.UNPOST_SEQ 
AND A.JRNL_HDR_STATUS IN(&#39;P&#39;,&#39;V&#39;,&#39;U&#39;) 
AND A.FISCAL_YEAR IN (2024) 
AND A.ACCOUNTING_PERIOD BETWEEN 1 AND 12
AND B.CHARTFIELD1 IN (&#39;1234567&#39;,&#39;1234568&#39;,&#39;1234569&#39;)
AND B.LEDGER IN (&#39;LEDGER&#39;)) 
GROUP BY A.FISCAL_YEAR, A.ACCOUNTING_PERIOD, B.BUSINESS_UNIT, B.JOURNAL_ID, B.JOURNAL_DATE, B.LEDGER, B.ACCOUNT, B.PRODUCT, B.PROJECT_ID, B.CHARTFIELD1, 
B.CHARTFIELD2, B.CURRENCY_CD, B.AFFILIATE, B.FOREIGN_CURRENCY, A.REVERSAL_CD, A.REVERSAL_ADJ_PER, A.JRNL_HDR_STATUS, A.POSTED_DATE, A.OPRID, A.DESCR254, 
B.DEPTID, A.SOURCE, B.ALTACCT, A.DTTM_STAMP_SEC, B.LINE_DESCR
/
select * from table(dbms_xplan.display_cursor(null,null,&#39;ADVANCED +ADAPTIVE&#39;));&lt;/span&gt;&lt;/pre&gt;
&lt;p&gt;Rather than get the execution plan from EXPLAIN PLAN, I have executed the query with STATISTICS_LEVEL set to ALL, and then displayed the cursor.&lt;/p&gt;&lt;p&gt;The execution plan starts with the PS_JRNL_HEADER table and uses a nested loop join with a lookup of the unique index on PS_JRNL_LN.&amp;nbsp; Although note that the optimizer costs were produced with Exadata system statistics.&lt;/p&gt;
&lt;pre style=&quot;background-color: #eeeeee; font-family: &amp;quot;courier new&amp;quot;overflow: auto; line-height: 80%; text-align: left; width: 95%;&quot;&gt;&lt;span style=&quot;font-size: 70%;&quot;&gt;Plan hash value: 4030641493&lt;/span&gt;&lt;span style=&quot;font-size: 60%;&quot;&gt;

---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| Id  | Operation                           | Name           | Starts | E-Rows |E-Bytes|E-Temp | Cost (%CPU)| E-Time   | Pstart| Pstop | A-Rows |   A-Time   | Buffers | Reads  |
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                    |                |      1 |        |       |       |  &lt;b&gt;1740K&lt;/b&gt;(100)|          |       |       |    209K|00:06:41.85 |     238M|     15M|
|   1 |  HASH GROUP BY                      |                |      1 |    498K|   108M|   129M|  1740K  (1)| 00:01:08 |       |       |    209K|00:06:41.85 |     238M|     15M|
|   2 |   NESTED LOOPS                      |                |      1 |    498K|   108M|       |  1722K  (1)| 00:01:08 |       |       |    495K|01:03:03.80 |     238M|     15M|
|   3 |    NESTED LOOPS                     |                |      1 |    498K|   108M|       |  1722K  (1)| 00:01:08 |       |       |    459M|00:11:20.66 |    5549K|   4259K|
|*  4 |     TABLE ACCESS STORAGE FULL       | PS_JRNL_HEADER |      1 |    &lt;b&gt;430K&lt;/b&gt;|    41M|       |  1135   (8)| 00:00:01 |       |       |    430K|00:00:00.34 |   88642 |  88637 |
|   5 |     PARTITION RANGE ITERATOR        |                |    430K|      1 |       |       |     3   (0)| 00:00:01 |   KEY |   KEY |    459M|00:10:38.60 |    5460K|   4170K|
|*  6 |      INDEX RANGE SCAN               | PS_JRNL_LN     |    430K|      1 |       |       |     3   (0)| 00:00:01 |   KEY |   KEY |    459M|00:09:55.80 |    5460K|   4170K|
|*  7 |    TABLE ACCESS BY LOCAL INDEX ROWID| PS_JRNL_LN     |    459M|      1 |   127 |       |     4   (0)| 00:00:01 |     1 |     1 |    495K|00:50:25.33 |     233M|     11M|
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
&lt;/span&gt;&lt;span style=&quot;font-size: 70%;&quot;&gt;…
Predicate Information (identified by operation id):
---------------------------------------------------

   4 - storage((&quot;A&quot;.&quot;FISCAL_YEAR&quot;=2024 AND INTERNAL_FUNCTION(&quot;A&quot;.&quot;JRNL_HDR_STATUS&quot;) AND &quot;A&quot;.&quot;ACCOUNTING_PERIOD&quot;&amp;lt;=12 AND &quot;A&quot;.&quot;ACCOUNTING_PERIOD&quot;&amp;gt;=1))
       filter((&quot;A&quot;.&quot;FISCAL_YEAR&quot;=2024 AND INTERNAL_FUNCTION(&quot;A&quot;.&quot;JRNL_HDR_STATUS&quot;) AND &quot;A&quot;.&quot;ACCOUNTING_PERIOD&quot;&amp;lt;=12 AND &quot;A&quot;.&quot;ACCOUNTING_PERIOD&quot;&amp;gt;=1))
   6 - access(&quot;A&quot;.&quot;BUSINESS_UNIT&quot;=&quot;B&quot;.&quot;BUSINESS_UNIT&quot; AND &quot;A&quot;.&quot;JOURNAL_ID&quot;=&quot;B&quot;.&quot;JOURNAL_ID&quot; AND &quot;A&quot;.&quot;JOURNAL_DATE&quot;=&quot;B&quot;.&quot;JOURNAL_DATE&quot; AND
              &quot;A&quot;.&quot;UNPOST_SEQ&quot;=&quot;B&quot;.&quot;UNPOST_SEQ&quot; AND &quot;B&quot;.&quot;LEDGER&quot;=&#39;LEDGER&#39;)
       filter(&quot;B&quot;.&quot;LEDGER&quot;=&#39;LEDGER&#39;)
   7 - filter((&quot;B&quot;.&quot;CHARTFIELD1&quot;=&#39;1234567&#39; OR &quot;B&quot;.&quot;CHARTFIELD1&quot;=&#39;1234568&#39; OR &quot;B&quot;.&quot;CHARTFIELD1&quot;=&#39;1234569&#39;))

Hint Report (identified by operation id / Query Block Name / Object Alias):
Total hints for statement: 3
---------------------------------------------------------------------------

1 -  SEL$1
	           -  LEADING(A)

   6 -  SEL$1 / B@SEL$1
           -  INDEX(B (PS_JRNL_LN.BUSINESS_UNIT PS_JRNL_LN.JOURNAL_ID PS_JRNL_LN.JOURNAL_DATE PS_JRNL_LN.UNPOST_SEQ PS_JRNL_LN.JOURNAL_LINE PS_JRNL_LN.LEDGER))
           -  USE_NL(B)&lt;/span&gt;&lt;/pre&gt;
&lt;p&gt;The cost of this execution plan depends mainly upon how many journal header rows are selected.&amp;nbsp; There is a cost of 3 per index lookup, plus another 1 for the table access, making a total of 4 per journal header row.&amp;nbsp; Here we selected 430K rows from PS_JRNL_HEADER, so &lt;i&gt;430K rows * 4/row = 1720K&lt;/i&gt;.&amp;nbsp; We got an actual cost of 1722K.&amp;nbsp; The discrepancy is because the 430K was rounded off by the representation of numbers in the execution plan.&amp;nbsp; Then the cost of the GROUP BY operation is 18K.&amp;nbsp; Hence, the overall cost is 1740K.&lt;/p&gt;&lt;p&gt;The actual execution time of the query was 63 minutes (78 minutes with time taken to fetch 211K rows across the network to the client).&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;text-align: left;&quot;&gt;New Execution Plan (Without Hints)&lt;/h3&gt;
&lt;div&gt;Without the hints, the execution plan changes.&amp;nbsp; It still starts with a full scan of PS_JRNL_HEADER, but then full scans PS_JRNL_LN, filtering the rows by LEDGER and CHARTFIELD1 (operation at line 12) and Bloom filtering by the columns joined to PS_JRNL_HEADER.&amp;nbsp; PS_JRNL_LN is then hash joined against the rows from PS_JRNL_HEADER to remove false positives returned by the Bloom filter&lt;/div&gt;
&lt;pre style=&quot;background-color: #eeeeee; font-family: &amp;quot;courier new&amp;quot;overflow: auto; line-height: 80%; text-align: left; width: 95%;&quot;&gt;&lt;span style=&quot;font-size: 60%;&quot;&gt;Plan hash value: 1053505630

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|   Id  | Operation                             | Name           | Starts | E-Rows |E-Bytes|E-Temp | Cost (%CPU)| E-Time   | Pstart| Pstop | A-Rows |   A-Time   | Buffers | Reads  |
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|     0 | SELECT STATEMENT                      |                |      1 |        |       |       |   535K(100)|          |       |       |    209K|00:00:10.06 |      27M|     27M|
|     1 |  HASH GROUP BY                        |                |      1 |    498K|   108M|   129M|   535K (24)| 00:00:21 |       |       |    209K|00:00:10.06 |      27M|     27M|
|  *  2 |   HASH JOIN                           |                |      1 |    498K|   108M|    46M|   517K (24)| 00:00:21 |       |       |    495K|00:00:09.23 |      27M|     27M|
|     3 |    PART JOIN FILTER CREATE            | :BF0000        |      1 |    498K|   108M|       |   517K (24)| 00:00:21 |       |       |    430K|00:00:00.66 |   88642 |  88638 |
|-    4 |     NESTED LOOPS                      |                |      1 |    498K|   108M|    46M|   517K (24)| 00:00:21 |       |       |    430K|00:00:00.30 |   88642 |  88638 |
|-    5 |      NESTED LOOPS                     |                |      1 |        |       |       |            |          |       |       |    430K|00:00:00.24 |   88642 |  88638 |
|-    6 |       STATISTICS COLLECTOR            |                |      1 |        |       |       |            |          |       |       |    430K|00:00:00.19 |   88642 |  88638 |
|  *  7 |        TABLE ACCESS STORAGE FULL      | PS_JRNL_HEADER |      1 |    430K|    41M|       |  1135   (8)| 00:00:01 |       |       |    430K|00:00:00.13 |   88642 |  88638 |
|-    8 |       PARTITION RANGE ITERATOR        |                |      0 |        |       |       |            |          |   KEY |   KEY |      0 |00:00:00.01 |       0 |      0 |
|- *  9 |        INDEX RANGE SCAN               | PS_JRNL_LN     |      0 |        |       |       |            |          |   KEY |   KEY |      0 |00:00:00.01 |       0 |      0 |
|- * 10 |      TABLE ACCESS BY LOCAL INDEX ROWID| PS_JRNL_LN     |      0 |      1 |   127 |       |   515K (24)| 00:00:21 |     1 |     1 |      0 |00:00:00.01 |       0 |      0 |
|    11 |    PARTITION RANGE JOIN-FILTER        |                |      1 |    498K|    60M|       |   515K (24)| 00:00:21 |:BF0000|:BF0000|    815K|00:00:07.65 |      27M|     27M|
|  * 12 |     TABLE ACCESS STORAGE FULL         | PS_JRNL_LN     |     18 |    498K|    60M|       |   515K (24)| 00:00:21 |:BF0000|:BF0000|    815K|00:00:07.55 |      27M|     27M|
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

&lt;/span&gt;&lt;span style=&quot;font-size: 70%;&quot;&gt;
Outline Data
-------------

  /*+
      BEGIN_OUTLINE_DATA
      IGNORE_OPTIM_EMBEDDED_HINTS
      OPTIMIZER_FEATURES_ENABLE(&#39;19.1.0&#39;)
      DB_VERSION(&#39;19.1.0&#39;)
      ALL_ROWS
      OUTLINE_LEAF(@&quot;SEL$1&quot;)
&lt;b&gt;      FULL(@&quot;SEL$1&quot; &quot;A&quot;@&quot;SEL$1&quot;)
      FULL(@&quot;SEL$1&quot; &quot;B&quot;@&quot;SEL$1&quot;)
      LEADING(@&quot;SEL$1&quot; &quot;A&quot;@&quot;SEL$1&quot; &quot;B&quot;@&quot;SEL$1&quot;)
      USE_HASH(@&quot;SEL$1&quot; &quot;B&quot;@&quot;SEL$1&quot;)
&lt;/b&gt;      USE_HASH_AGGREGATION(@&quot;SEL$1&quot;)
      END_OUTLINE_DATA
  */

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - access(&quot;A&quot;.&quot;JOURNAL_DATE&quot;=&quot;B&quot;.&quot;JOURNAL_DATE&quot; AND &quot;A&quot;.&quot;BUSINESS_UNIT&quot;=&quot;B&quot;.&quot;BUSINESS_UNIT&quot; AND &quot;A&quot;.&quot;JOURNAL_ID&quot;=&quot;B&quot;.&quot;JOURNAL_ID&quot; AND &quot;A&quot;.&quot;UNPOST_SEQ&quot;=&quot;B&quot;.&quot;UNPOST_SEQ&quot;)
   7 - storage((&quot;A&quot;.&quot;FISCAL_YEAR&quot;=2024 AND INTERNAL_FUNCTION(&quot;A&quot;.&quot;JRNL_HDR_STATUS&quot;) AND &quot;A&quot;.&quot;ACCOUNTING_PERIOD&quot;&amp;lt;=12 AND &quot;A&quot;.&quot;ACCOUNTING_PERIOD&quot;&amp;gt;=1))
       filter((&quot;A&quot;.&quot;FISCAL_YEAR&quot;=2024 AND INTERNAL_FUNCTION(&quot;A&quot;.&quot;JRNL_HDR_STATUS&quot;) AND &quot;A&quot;.&quot;ACCOUNTING_PERIOD&quot;&amp;lt;=12 AND &quot;A&quot;.&quot;ACCOUNTING_PERIOD&quot;&amp;gt;=1))
   9 - access(&quot;A&quot;.&quot;BUSINESS_UNIT&quot;=&quot;B&quot;.&quot;BUSINESS_UNIT&quot; AND &quot;A&quot;.&quot;JOURNAL_ID&quot;=&quot;B&quot;.&quot;JOURNAL_ID&quot; AND &quot;A&quot;.&quot;JOURNAL_DATE&quot;=&quot;B&quot;.&quot;JOURNAL_DATE&quot; AND &quot;A&quot;.&quot;UNPOST_SEQ&quot;=&quot;B&quot;.&quot;UNPOST_SEQ&quot;
              AND &quot;B&quot;.&quot;LEDGER&quot;=&#39;LEDGER&#39;)
       filter(&quot;B&quot;.&quot;LEDGER&quot;=&#39;LEDGER&#39;)
  10 - filter((&quot;B&quot;.&quot;CHARTFIELD1&quot;=&#39;1234567&#39; OR &quot;B&quot;.&quot;CHARTFIELD1&quot;=&#39;1234568&#39; OR &quot;B&quot;.&quot;CHARTFIELD1&quot;=&#39;1234569&#39;))
  12 - storage((&quot;B&quot;.&quot;LEDGER&quot;=&#39;LEDGER&#39; AND INTERNAL_FUNCTION(&quot;B&quot;.&quot;CHARTFIELD1&quot;)))
       filter((&quot;B&quot;.&quot;LEDGER&quot;=&#39;LEDGER&#39; AND INTERNAL_FUNCTION(&quot;B&quot;.&quot;CHARTFIELD1&quot;)))&lt;/span&gt;&lt;/pre&gt;
  &lt;div&gt;The actual execution time is just 21 seconds (or 13 minutes, including fetches).&amp;nbsp; So, this is much faster.&amp;nbsp;&amp;nbsp;&lt;/div&gt;&lt;div&gt;The optimizer cost drops to just 535K.&amp;nbsp; This is the cheapest plan, and therefore Oracle uses it without further intervention.&amp;nbsp;&amp;nbsp;515K of the cost comes from the full scan of PS_JRNL_LN, the Bloom filter takes it up by just 2K, and the GROUP BY operation by 18K.&amp;nbsp; It is an adaptive plan, so Oracle can still switch between the nested loop and the Bloom/hash join at run time on the basis of the statistics collected at run time (at line 6).&lt;/div&gt;
  &lt;div&gt;The full scan Bloom-Hash filter of PS_JRNL_LN is cheaper than the nested loop with 430K index probes.&amp;nbsp; In fact, the tipping point will be whenever the optimizer estimates that it gets at least 129K rows from PS_JRNL_HEADER (&lt;i&gt;535K / 4/row =128.75K&lt;/i&gt;).&lt;/div&gt;&lt;h4 style=&quot;text-align: left;&quot;&gt;TL;DR&lt;/h4&gt;&lt;div&gt;Through a combination of Exadata system stats, archiving, partitioning and compression, the cost of smart-full scanning the PS_JRNL_LN table, that contains several billion rows, Oracle has switched to a more efficient execution plan.&amp;nbsp;&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;
&lt;div class=&quot;blogger-post-footer&quot;&gt;&lt;a href=&quot;http://www.go-faster.co.uk/&quot;&gt;©David Kurtz&lt;/a&gt;&lt;/div&gt;</description><link>https://blog.psftdba.com/2025/05/optimisingjournalline5conclusion.html</link><author>noreply@blogger.com (David Kurtz)</author><thr:total>0</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-25740336.post-1535616222426626801</guid><pubDate>Fri, 27 Jun 2025 14:38:00 +0000</pubDate><atom:updated>2025-06-30T14:06:01.631+01:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">compression</category><category domain="http://www.blogger.com/atom/ns#">Journal Line</category><category domain="http://www.blogger.com/atom/ns#">PS_JRNL_LN</category><title>Optimising Journal Line Queries: 4. Compression</title><description>&lt;p&gt;This is the fourth of five articles that examine the challenges posed by typical queries on the journal line table (PS_JRNL_LN).&lt;/p&gt;&lt;ol&gt;&lt;li&gt;&lt;a href=&quot;https://blog.psftdba.com/2024/12/journallinequeries1problemstatement.html&quot;&gt;Problem Statement&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://blog.psftdba.com/2024/12/journallinequeries2exadatasystemstatistics.html&quot;&gt;Exadata System Statistics&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://blog.psftdba.com/2024/12/optimisingjournalline3partitioning.html&quot;&gt;Partitioning&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://blog.psftdba.com/2024/12/optimisingjournalline4compression.html&quot;&gt;Compression&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://blog.psftdba.com/2025/05/optimisingjournalline5conclusion.html&quot;&gt;Conclusion&lt;/a&gt;&lt;/li&gt;&lt;/ol&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;div&gt;If it had been possible to referentially partition PS_JRNL_LN by FISCAL_YEAR, then the sample query shown in earlier posts would have been able to prune partitions by fiscal year.&amp;nbsp; This would have significantly reduced the cost of the full scan.&amp;nbsp;&amp;nbsp;&lt;/div&gt;&lt;div&gt;Instead, if the sample query has to full scan PS_JRNL_LN, it has to scan all the partitions in PS_JRNL_LN and then filter them against the keys retrieved from PS_JRNL_HEADER.&amp;nbsp; The only way to reduce the cost of the full scan is to reduce the number of blocks being scanned by purge and compression.&amp;nbsp; On Exadata, Hybrid Columnar Compression (HCC) will achieve a much higher compression ratio, and unwanted compression units will be eliminated efficiently.&lt;/div&gt;&lt;div&gt;To meet reporting requirements, Financials systems inevitably need to keep journal data going back several years.&amp;nbsp; Nonetheless, purging unneeded historical journals as aggressively as permissible is the first step in improving or at least preserving query performance.&lt;/div&gt;&lt;/div&gt;
&lt;h3 style=&quot;text-align: left;&quot;&gt;Compression&lt;/h3&gt;&lt;div&gt;Compressing the historical journal line partitions will further reduce the number of blocks in the segments and reduce the cost of the full scan in the query, thus making the optimiser more likely to switch away from the nested loop join to the full scan/Bloom filter/hash join.&lt;/div&gt;&lt;div&gt;In Oracle, basic compression and Hybrid Columnar Compression (HCC) are well suited to data warehouse applications, but they also have application in OLTP systems.&amp;nbsp; Compression occurs during direct-path insert and segment reorganisation operations and does not apply to anything modified by PeopleSoft processes that use conventional DML.&amp;nbsp;&amp;nbsp;&lt;/div&gt;&lt;div&gt;Basic compression is available on any Oracle database; essentially, it avoids storing the same data values many times in the same data block.&amp;nbsp; Hybrid Columnar Compression (HCC) is available on Exadata. Different HCC compression levels use different compression algorithms.&amp;nbsp; DML on compressed data will decompress it.&lt;/div&gt;&lt;div&gt;I do not recommend using Advanced Compression to compress current periods due to the impact on day-to-day processing.&lt;/div&gt;&lt;h4 style=&quot;text-align: left;&quot;&gt;Which Partitions are Static?&lt;/h4&gt;&lt;div&gt;DBA_TAB_MODIFICATIONS shows the number of DML operations in each partition since statistics were last collected.&lt;/div&gt;
&lt;pre style=&quot;background-color: #eeeeee; font-family: &amp;quot;courier new&amp;quot;overflow: auto; line-height: 80%; text-align: left; width: 95%;&quot;&gt;&lt;span&gt;&lt;span style=&quot;font-size: x-small;&quot;&gt;SELECT p.partition_position, m.*, p.high_value
FROM dba_tab_modifications m
  INNER JOIN dba_tab_partitions p 
    ON p.table_owner = m.table_owner AND p.table_name = m.table_name AND p.partition_name = m.partition_name
WHERE m.table_owner = &#39;SYSADM&#39; AND m.table_name = &#39;PS_JRNL_LN&#39;
ORDER BY 1
/&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;
&lt;div&gt;This report was generated in December 2024.&amp;nbsp; Most of the updates are in the current and previous monthly periods.&amp;nbsp; There are almost no updates that are older than a year.&lt;/div&gt;
&lt;pre style=&quot;background-color: #eeeeee; font-family: &amp;quot;courier new&amp;quot;overflow: auto; line-height: 80%; text-align: left; width: 95%;&quot;&gt;&lt;span&gt;&lt;span style=&quot;font-size: 55%;&quot;&gt;Part Table                                                                                              Drop                                                                                 
Pos# Owner    TABLE_NAME   PARTITION_NAME       S   INSERTS   UPDATES   DELETES TIMESTAMP           TRU Segs HIGH_VALUE                                                                      
---- -------- ------------ -------------------- - --------- --------- --------- ------------------- --- ---- --------------------------------------------------------------------------------
…
  40 SYSADM   PS_JRNL_LN   JRNL_LN_202212                 0         0         0 13/10/2024 10:08:56 NO     0 TO_DATE(&#39; 2023-01-01 00:00:00&#39;, &#39;SYYYY-MM-DD HH24:MI:SS&#39;, &#39;NLS_CALENDAR=GREGORIA
  41 SYSADM   PS_JRNL_LN   JRNL_LN_202301                 0         0         0 29/09/2024 10:01:16 NO     0 TO_DATE(&#39; 2023-02-01 00:00:00&#39;, &#39;SYYYY-MM-DD HH24:MI:SS&#39;, &#39;NLS_CALENDAR=GREGORIA
  42 SYSADM   PS_JRNL_LN   JRNL_LN_202302                 0         0         0 29/09/2024 10:01:16 NO     0 TO_DATE(&#39; 2023-03-01 00:00:00&#39;, &#39;SYYYY-MM-DD HH24:MI:SS&#39;, &#39;NLS_CALENDAR=GREGORIA
  43 SYSADM   PS_JRNL_LN   JRNL_LN_202303                 0         0         0 29/09/2024 10:01:16 NO     0 TO_DATE(&#39; 2023-04-01 00:00:00&#39;, &#39;SYYYY-MM-DD HH24:MI:SS&#39;, &#39;NLS_CALENDAR=GREGORIA
  44 SYSADM   PS_JRNL_LN   JRNL_LN_202304                 0         0         0 29/09/2024 10:01:16 NO     0 TO_DATE(&#39; 2023-05-01 00:00:00&#39;, &#39;SYYYY-MM-DD HH24:MI:SS&#39;, &#39;NLS_CALENDAR=GREGORIA
  45 SYSADM   PS_JRNL_LN   JRNL_LN_202305                 0         0         0 29/09/2024 10:01:16 NO     0 TO_DATE(&#39; 2023-06-01 00:00:00&#39;, &#39;SYYYY-MM-DD HH24:MI:SS&#39;, &#39;NLS_CALENDAR=GREGORIA
  46 SYSADM   PS_JRNL_LN   JRNL_LN_202306                 0         0         0 29/09/2024 10:01:16 NO     0 TO_DATE(&#39; 2023-07-01 00:00:00&#39;, &#39;SYYYY-MM-DD HH24:MI:SS&#39;, &#39;NLS_CALENDAR=GREGORIA
  47 SYSADM   PS_JRNL_LN   JRNL_LN_202307                 0         0         0 29/09/2024 10:01:16 NO     0 TO_DATE(&#39; 2023-08-01 00:00:00&#39;, &#39;SYYYY-MM-DD HH24:MI:SS&#39;, &#39;NLS_CALENDAR=GREGORIA
  48 SYSADM   PS_JRNL_LN   JRNL_LN_202308                 0         0         0 29/09/2024 10:01:16 NO     0 TO_DATE(&#39; 2023-09-01 00:00:00&#39;, &#39;SYYYY-MM-DD HH24:MI:SS&#39;, &#39;NLS_CALENDAR=GREGORIA
  49 SYSADM   PS_JRNL_LN   JRNL_LN_202309                 0         0         0 13/10/2024 10:08:56 NO     0 TO_DATE(&#39; 2023-10-01 00:00:00&#39;, &#39;SYYYY-MM-DD HH24:MI:SS&#39;, &#39;NLS_CALENDAR=GREGORIA
  50 SYSADM   PS_JRNL_LN   JRNL_LN_202310                 0         0         0 27/10/2024 10:59:45 NO     0 TO_DATE(&#39; 2023-11-01 00:00:00&#39;, &#39;SYYYY-MM-DD HH24:MI:SS&#39;, &#39;NLS_CALENDAR=GREGORIA
  51 SYSADM   PS_JRNL_LN   JRNL_LN_202311                 0         0         0 29/09/2024 10:01:16 NO     0 TO_DATE(&#39; 2023-12-01 00:00:00&#39;, &#39;SYYYY-MM-DD HH24:MI:SS&#39;, &#39;NLS_CALENDAR=GREGORIA
  52 SYSADM   PS_JRNL_LN   JRNL_LN_202312                34       193        34 10/12/2024 14:21:38 NO     0 TO_DATE(&#39; 2024-01-01 00:00:00&#39;, &#39;SYYYY-MM-DD HH24:MI:SS&#39;, &#39;NLS_CALENDAR=GREGORIA

  53 SYSADM   PS_JRNL_LN   JRNL_LN_202401             42374    127736       210 12/12/2024 05:27:31 NO     0 TO_DATE(&#39; 2024-02-01 00:00:00&#39;, &#39;SYYYY-MM-DD HH24:MI:SS&#39;, &#39;NLS_CALENDAR=GREGORIA
  54 SYSADM   PS_JRNL_LN   JRNL_LN_202402             34803     92215         0 12/12/2024 05:26:30 NO     0 TO_DATE(&#39; 2024-03-01 00:00:00&#39;, &#39;SYYYY-MM-DD HH24:MI:SS&#39;, &#39;NLS_CALENDAR=GREGORIA
  55 SYSADM   PS_JRNL_LN   JRNL_LN_202403             54940    166263         0 12/12/2024 05:12:29 NO     0 TO_DATE(&#39; 2024-04-01 00:00:00&#39;, &#39;SYYYY-MM-DD HH24:MI:SS&#39;, &#39;NLS_CALENDAR=GREGORIA
  56 SYSADM   PS_JRNL_LN   JRNL_LN_202404              5900     13730         0 13/12/2024 05:29:32 NO     0 TO_DATE(&#39; 2024-05-01 00:00:00&#39;, &#39;SYYYY-MM-DD HH24:MI:SS&#39;, &#39;NLS_CALENDAR=GREGORIA
  57 SYSADM   PS_JRNL_LN   JRNL_LN_202405              6151     13869         0 13/12/2024 05:31:06 NO     0 TO_DATE(&#39; 2024-06-01 00:00:00&#39;, &#39;SYYYY-MM-DD HH24:MI:SS&#39;, &#39;NLS_CALENDAR=GREGORIA
  58 SYSADM   PS_JRNL_LN   JRNL_LN_202406             18317     58263         0 13/12/2024 16:15:49 NO     0 TO_DATE(&#39; 2024-07-01 00:00:00&#39;, &#39;SYYYY-MM-DD HH24:MI:SS&#39;, &#39;NLS_CALENDAR=GREGORIA
  59 SYSADM   PS_JRNL_LN   JRNL_LN_202407           5067792  14937405         0 13/12/2024 16:02:36 NO     0 TO_DATE(&#39; 2024-08-01 00:00:00&#39;, &#39;SYYYY-MM-DD HH24:MI:SS&#39;, &#39;NLS_CALENDAR=GREGORIA
  60 SYSADM   PS_JRNL_LN   JRNL_LN_202408           5217744  15378822         0 13/12/2024 18:02:57 NO     0 TO_DATE(&#39; 2024-09-01 00:00:00&#39;, &#39;SYYYY-MM-DD HH24:MI:SS&#39;, &#39;NLS_CALENDAR=GREGORIA
  61 SYSADM   PS_JRNL_LN   JRNL_LN_202409             65389    243360       160 13/12/2024 12:45:25 NO     0 TO_DATE(&#39; 2024-10-01 00:00:00&#39;, &#39;SYYYY-MM-DD HH24:MI:SS&#39;, &#39;NLS_CALENDAR=GREGORIA
  62 SYSADM   PS_JRNL_LN   JRNL_LN_202410             44839    152210         0 13/12/2024 00:28:54 NO     0 TO_DATE(&#39; 2024-11-01 00:00:00&#39;, &#39;SYYYY-MM-DD HH24:MI:SS&#39;, &#39;NLS_CALENDAR=GREGORIA
  63 SYSADM   PS_JRNL_LN   JRNL_LN_202411          28279594  53637873  27478940 13/12/2024 18:18:00 NO     0 TO_DATE(&#39; 2024-12-01 00:00:00&#39;, &#39;SYYYY-MM-DD HH24:MI:SS&#39;, &#39;NLS_CALENDAR=GREGORIA
  64 SYSADM   PS_JRNL_LN   JRNL_LN_202412          34761590  53485631  27484239 13/12/2024 19:16:11 NO     0 TO_DATE(&#39; 2025-01-01 00:00:00&#39;, &#39;SYYYY-MM-DD HH24:MI:SS&#39;, &#39;NLS_CALENDAR=GREGORIA

  65 SYSADM   PS_JRNL_LN   JRNL_LN_202501            137138    473452         0 13/12/2024 19:18:09 NO     0 TO_DATE(&#39; 2025-02-01 00:00:00&#39;, &#39;SYYYY-MM-DD HH24:MI:SS&#39;, &#39;NLS_CALENDAR=GREGORI1
  66 SYSADM   PS_JRNL_LN   JRNL_LN_202502                 0         0         0 10/11/2024 10:08:21 NO     0 TO_DATE(&#39; 2025-03-01 00:00:00&#39;, &#39;SYYYY-MM-DD HH24:MI:SS&#39;, &#39;NLS_CALENDAR=GREGORIA
  67 SYSADM   PS_JRNL_LN   JRNL_LN_202503               466         0         0 13/12/2024 03:59:20 NO     0 TO_DATE(&#39; 2025-04-01 00:00:00&#39;, &#39;SYYYY-MM-DD HH24:MI:SS&#39;, &#39;NLS_CALENDAR=GREGORIA
  68 SYSADM   PS_JRNL_LN   JRNL_LN_202504                 0         0         0 17/11/2024 10:03:01 NO     0 TO_DATE(&#39; 2025-05-01 00:00:00&#39;, &#39;SYYYY-MM-DD HH24:MI:SS&#39;, &#39;NLS_CALENDAR=GREGORIA
  69 SYSADM   PS_JRNL_LN   JRNL_LN_202505                 0         0         0 17/11/2024 10:03:01 NO     0 TO_DATE(&#39; 2025-06-01 00:00:00&#39;, &#39;SYYYY-MM-DD HH24:MI:SS&#39;, &#39;NLS_CALENDAR=GREGORIA
…&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;
&lt;div&gt;&lt;div&gt;Therefore, compressing journal lines in the current or previous fiscal years is not viable, as subsequent updates would destroy the compression. However, I can look at explicitly compressing older partitions.&amp;nbsp;&amp;nbsp;&lt;/div&gt;&lt;h4 style=&quot;text-align: left;&quot;&gt;Attribute Clustering&lt;/h4&gt;&lt;div&gt;Attribute Clustering sorts or clusters the rows by certain columns.&amp;nbsp; Like compression, this is declarative and will only take effect during direct-path load or segment reorganisation (such as a compression operation).&amp;nbsp; It will not take effect during normal DML.&amp;nbsp; It can be defined at table level and will be implemented during compression.&lt;/div&gt;&lt;div&gt;I have defined attribute clustering on PS_JRNL_LN to sort the rows by the unique key columns of its parent record, PS_JRNL_HEADER.&lt;/div&gt;
  &lt;pre style=&quot;background-color: #eeeeee; font-family: &amp;quot;courier new&amp;quot;overflow: auto; line-height: 80%; text-align: left; width: 95%;&quot;&gt;&lt;span&gt;&lt;span style=&quot;font-size: x-small;&quot;&gt;ALTER TABLE ps_jrnl_ln ADD CLUSTERING BY LINEAR ORDER (business_unit, journal_id, journal_date, unpost_Seq);
ALTER TABLE ps_jrnl_ln MODIFY CLUSTERING YES ON LOAD YES ON DATA MOVEMENT;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;
  &lt;div&gt;I have found it produces a small (approximately 2-5% of the original size) improvement in the resulting compression, further reducing the cost of the full table scan.&amp;nbsp; So, it is a marginal gain for no additional cost.&lt;/div&gt;&lt;h4 style=&quot;text-align: left;&quot;&gt;Hybrid Columnar Compression (HCC)&lt;/h4&gt;&lt;div&gt;I have chosen to compress partitions older than one complete fiscal year with QUERY LOW, and those than 3 complete fiscal years with QUERY HIGH.&lt;/div&gt;&lt;div&gt;Each historical partition can be compressed using an online compress operation.&amp;nbsp; This can be done in parallel.&amp;nbsp; It is important to specify that it maintains the indexes so that they remain valid and usable after the operation.&amp;nbsp; &amp;nbsp;This approach will not lock PS_JRNL_LN, and so the application can continue to run during this process.&lt;/div&gt;&lt;/div&gt;
&lt;pre style=&quot;background-color: #eeeeee; font-family: &amp;quot;courier new&amp;quot;overflow: auto; line-height: 80%; text-align: left; width: 95%;&quot;&gt;&lt;span&gt;&lt;span style=&quot;font-size: x-small;&quot;&gt;…
ALTER TABLE ps_jrnl_ln MOVE PARTITION jrnl_ln_202012 COMPRESS FOR QUERY HIGH UPDATE INDEXES ONLINE PARALLEL;
ALTER TABLE ps_jrnl_ln MOVE PARTITION jrnl_ln_202101 COMPRESS FOR QUERY LOW UPDATE INDEXES ONLINE PARALLEL;
…&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;
&lt;div&gt;&lt;div&gt;The statistics on the newly compressed partitions should be updated, as well as the global statistics.&amp;nbsp;&amp;nbsp;&lt;/div&gt;&lt;h4 style=&quot;text-align: left;&quot;&gt;Statistics&lt;/h4&gt;&lt;div&gt;Optimizer statistics have to be regathered on the freshly compressed partitions; otherwise, the reduction in the cost of the full scan will not occur.&amp;nbsp;&amp;nbsp;&lt;/div&gt;&lt;div&gt;On some systems, statistics collection on the very largest tables, including PS_JRNL_LN, may not be completed within the regular maintenance window.&amp;nbsp; Then, Oracle may repeatedly attempt to collect statistics on these segments, and other statistics can become stale too.&amp;nbsp; Instead, I usually lock the statistics on such tables (to remove them from the maintenance window job) and create a specific recurring job to collect statistics at a convenient time (e.g. at the weekend).&lt;/div&gt;&lt;div&gt;An interim option is to simply manually update the number of blocks in the partition statistics to the number of blocks in the compressed segment (if it is lower), and recalculate the total number of blocks in the whole table for the global statistics.&lt;/div&gt;&lt;/div&gt;
&lt;pre style=&quot;background-color: #eeeeee; font-family: &amp;quot;courier new&amp;quot;overflow: auto; line-height: 80%; text-align: left; width: 95%;&quot;&gt;&lt;span&gt;&lt;span style=&quot;font-size: xx-small;&quot;&gt;set serveroutput on timi on
DECLARE 
  l_table_name VARCHAR2(18) := &#39;PS_JRNL_LN&#39;;
  l_part_update BOOLEAN := FALSE;
BEGIN
  FOR i IN(
    select t.table_name, t.partition_name, t.num_rows, t.blocks stat_blocks, s.blocks seg_blocks
    ,      s.tablespace_name, p.compress_for, t.num_rows/NULLIF(LEAST(t.blocks,s.blocks),0) rpb
    from user_segments s
      inner join user_tab_partitions p ON p.table_name = s.segment_name AND p.partition_name = s.partition_name
      inner join user_tab_statistics t ON s.segment_name = t.table_name AND s.partition_name = t.partition_name &lt;b&gt;and t.blocks&amp;gt;s.blocks&lt;/b&gt;
    where s.segment_type = &#39;TABLE PARTITION&#39; and p.compress_for IS NOT NULL and s.segment_name = l_table_name 
  ) LOOP
    l_part_update := TRUE;
    dbms_output.put_line(i.table_name||&#39; (&#39;||i.partition_name||&#39;) &#39;||i.stat_blocks||&#39; =&amp;gt; &#39;||i.seg_blocks||&#39; blocks&#39;);
    dbms_stats.set_table_stats(ownname=&amp;gt;&#39;SYSADM&#39;,tabname=&amp;gt;i.table_name,partname=&amp;gt;i.partition_name,numblks=&amp;gt;i.seg_blocks,force=&amp;gt;TRUE);
  END LOOP;
  IF l_part_update THEN
    FOR i IN (
      SELECT table_name, sum(blocks) blocks
      FROM user_tab_statistics
      WHERE table_name = l_table_name AND partition_name IS NOT NULL 
      GROUP BY table_name
    ) LOOP
      dbms_output.put_line(i.table_name||&#39; = &#39;||i.blocks||&#39; blocks&#39;);
      dbms_stats.set_table_stats(ownname=&amp;gt;&#39;SYSADM&#39;,tabname=&amp;gt;i.table_name,numblks=&amp;gt;i.blocks,force=&amp;gt;TRUE);
    END LOOP;
  ELSE
    dbms_output.put_line(l_table_name||&#39; - no action required&#39;);
  END IF;
END;
/&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;
&lt;div&gt;&lt;h4 style=&quot;text-align: left;&quot;&gt;Orphaned Index Entries &amp;amp; Space Recovery&lt;/h4&gt;&lt;div&gt;One side effect of the table segment compression operation (or any other segment reorganisation operation) is that we get orphaned entries in any global indexes.&amp;nbsp; I could rebuild these indexes.&lt;/div&gt;&lt;/div&gt;
&lt;pre style=&quot;background-color: #eeeeee; font-family: &amp;quot;courier new&amp;quot;overflow: auto; line-height: 80%; text-align: left; width: 95%;&quot;&gt;&lt;span&gt;&lt;span style=&quot;font-size: x-small;&quot;&gt;ALTER INDEX psdjrnl_ln REBUILD ONLINE TABLESPACE psindex PARALLEL;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;
&lt;div&gt;&lt;div&gt;Or, I could wait for the PMO_DEFERRED_GIDX_MAINT_JOB job to run (scheduled by default during the maintenance window) to clean out orphaned index entries from all currently impacted global indexes. Another alternative is to manually run the &lt;i&gt;&lt;a href=&quot;https://docs.oracle.com/en/database/oracle/oracle-database/19/arpls/DBMS_PART.html#GUID-C6D0BD94-45B8-4371-931D-1EBF509E46B4&quot; rel=&quot;nofollow&quot; target=&quot;_blank&quot;&gt;dbms_part.cleanup_gidx&lt;/a&gt;&lt;/i&gt; procedure that is in turn called by this job (see also Richard Foote: &lt;a href=&quot;https://richardfoote.wordpress.com/2013/08/06/12c-asynchronous-global-index-maintenance-part-ii-the-space-between/&quot; target=&quot;_blank&quot;&gt;12c Asynchronous Global Index Maintenance Part II&lt;/a&gt;)&lt;/div&gt;&lt;div&gt;However, another side effect is that global indexes can grow as they are maintained by the table partition compression operations.&amp;nbsp; These indexes can be coalesced, cleaned up, and then shrunk.&amp;nbsp; The shrink implies a coalesce.&amp;nbsp; ALTER INDEX … SHRINK SPACE COMPACT is equivalent to ALTER INDEX … COALESCE - see &lt;a href=&quot;https://jonathanlewis.wordpress.com/2022/09/02/shrinking-indexes/&quot; target=&quot;_blank&quot;&gt;Jonathan Lewis&#39;s Oracle Scratchpad: Shrinking indexes&lt;/a&gt;).&amp;nbsp; However, the coalesce can be done in parallel, but the shrink cannot.&amp;nbsp; Therefore, I have chosen to do a parallel coalesce that includes a clean-up of orphaned entries, and then I shrink the segment.&lt;/div&gt;&lt;/div&gt;
&lt;pre style=&quot;background-color: #eeeeee; font-family: &amp;quot;courier new&amp;quot;overflow: auto; line-height: 80%; text-align: left; width: 95%;&quot;&gt;&lt;span&gt;&lt;span style=&quot;font-size: x-small;&quot;&gt;ALTER INDEX psdjrnl_ln COALESCE CLEANUP PARALLEL;
ALTER INDEX psdjrnl_ln SHRINK SPACE;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;
&lt;div&gt;I also rebuild corresponding partitions in locally partitioned indexes.&lt;/div&gt;
&lt;pre style=&quot;background-color: #eeeeee; font-family: &amp;quot;courier new&amp;quot;overflow: auto; line-height: 80%; text-align: left; width: 95%;&quot;&gt;&lt;span&gt;&lt;span style=&quot;font-size: x-small;&quot;&gt;…
ALTER INDEX ps_jrnl_ln REBUILD PARTITION jrnl_ln_202012 ONLINE;
ALTER INDEX ps_jrnl_ln REBUILD PARTITION jrnl_ln_202101 ONLINE;
…&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;
&lt;div&gt;&lt;div&gt;At the end of this process, space has been released back to the tablespace, but the free space will be distributed throughout the tablespace, so it probably won&#39;t be possible to release space back to the file system.&lt;/div&gt;&lt;div&gt;If you are compressing many partitions, perhaps because you are doing this for the first time, and they are in a dedicated tablespace, then I would suggest completely rebuilding the objects into new tablespaces.&amp;nbsp; Tablespaces can be renamed as required.&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;blogger-post-footer&quot;&gt;&lt;a href=&quot;http://www.go-faster.co.uk/&quot;&gt;©David Kurtz&lt;/a&gt;&lt;/div&gt;</description><link>https://blog.psftdba.com/2024/12/optimisingjournalline4compression.html</link><author>noreply@blogger.com (David Kurtz)</author><thr:total>0</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-25740336.post-7479514767663487751</guid><pubDate>Thu, 26 Jun 2025 12:58:00 +0000</pubDate><atom:updated>2026-01-09T13:20:14.171+00:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">Interval Partitioning</category><category domain="http://www.blogger.com/atom/ns#">Journal Line</category><category domain="http://www.blogger.com/atom/ns#">Partitioning</category><category domain="http://www.blogger.com/atom/ns#">PS_JRNL_LN</category><title>Optimising Journal Line Queries: 3. Partitioning</title><description>&lt;p&gt;This is the third of a series of five articles that examine the challenges posed by typical queries on the journal line table (PS_JRNL_LN).&lt;/p&gt;&lt;ol&gt;&lt;li&gt;&lt;a href=&quot;https://blog.psftdba.com/2024/12/journallinequeries1problemstatement.html&quot;&gt;Problem Statement&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://blog.psftdba.com/2024/12/journallinequeries2exadatasystemstatistics.html&quot;&gt;Exadata System Statistics&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://blog.psftdba.com/2024/12/optimisingjournalline3partitioning.html&quot;&gt;Partitioning&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://blog.psftdba.com/2024/12/optimisingjournalline4compression.html&quot;&gt;Compression&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://blog.psftdba.com/2025/05/optimisingjournalline5conclusion.html&quot;&gt;Conclusion&lt;/a&gt;&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;PeopleSoft does not partition tables by default.&amp;nbsp; Application Designer does not support partitioning, mainly because different databases implement partitioning differently.&amp;nbsp; Thus, it is always left to the customer to implement as a customisation.&amp;nbsp; In this article, I am only going to discuss partitioning on Oracle.&lt;/p&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Interval Partitioning&lt;/h3&gt;&lt;p&gt;This is the sample query that I started with.&lt;/p&gt;
&lt;pre style=&quot;background-color: #eeeeee; font-family: &amp;quot;courier new&amp;quot;overflow: auto; line-height: 80%; text-align: left; width: 95%;&quot;&gt;&lt;span style=&quot;font-size: xx-small;&quot;&gt;SELECT A.FISCAL_YEAR, A.ACCOUNTING_PERIOD, B.BUSINESS_UNIT, B.JOURNAL_ID, TO_CHAR(B.JOURNAL_DATE,&#39;YYYY-MM-DD&#39;), B.LEDGER, B.ACCOUNT
, B.PRODUCT, B.PROJECT_ID, B.CHARTFIELD1, B.CHARTFIELD2, B.CURRENCY_CD, B.AFFILIATE, SUM( B.MONETARY_AMOUNT), B.FOREIGN_CURRENCY
, SUM( B.FOREIGN_AMOUNT), A.REVERSAL_CD, A.REVERSAL_ADJ_PER, A.JRNL_HDR_STATUS, TO_CHAR(A.POSTED_DATE,&#39;YYYY-MM-DD&#39;), A.OPRID, A.DESCR254
, B.DEPTID, A.SOURCE, B.ALTACCT, TO_CHAR(CAST((A.DTTM_STAMP_SEC) AS TIMESTAMP),&#39;YYYY-MM-DD-HH24.MI.SS.FF&#39;), B.LINE_DESCR 
FROM PS_JRNL_HEADER A, PS_JRNL_LN B 
WHERE (&lt;b&gt;A.BUSINESS_UNIT = B.BUSINESS_UNIT 
AND A.JOURNAL_ID = B.JOURNAL_ID 
AND A.JOURNAL_DATE = B.JOURNAL_DATE 
AND A.UNPOST_SEQ = B.UNPOST_SEQ&lt;/b&gt; 
AND A.JRNL_HDR_STATUS IN(&#39;P&#39;,&#39;V&#39;,&#39;U&#39;) 
&lt;b&gt;AND A.FISCAL_YEAR IN (2024) 
AND A.ACCOUNTING_PERIOD BETWEEN 1 AND 12 
&lt;/b&gt;AND B.CHARTFIELD1 IN (&#39;1234567&#39;,&#39;1234568&#39;,&#39;1234569&#39;)
AND B.LEDGER IN (&#39;LEDGER&#39;)) 
GROUP BY A.FISCAL_YEAR, A.ACCOUNTING_PERIOD, B.BUSINESS_UNIT, B.JOURNAL_ID, B.JOURNAL_DATE, B.LEDGER, B.ACCOUNT, B.PRODUCT
, B.PROJECT_ID, B.CHARTFIELD1, B.CHARTFIELD2, B.CURRENCY_CD, B.AFFILIATE, B.FOREIGN_CURRENCY, A.REVERSAL_CD, A.REVERSAL_ADJ_PER
, A.JRNL_HDR_STATUS, A.POSTED_DATE, A.OPRID, A.DESCR254, B.DEPTID, A.SOURCE, B.ALTACCT, A.DTTM_STAMP_SEC, B.LINE_DESCR&lt;/span&gt;&lt;/pre&gt;
&lt;p&gt;It would have been desirable to have been able to partition PS_JRNL_LN by FISCAL_YEAR.&amp;nbsp; However, that column is only present on the parent table, PS_JRNL_HEADER.&amp;nbsp; Oracle can do referential partitioning, where the child table is partitioned by an attribute of a column in the parent table.&amp;nbsp; The parent table must also be partitioned similarly, thus producing a 1:1 mapping of partitions between the parent and child tables.&amp;nbsp; However, this feature also requires the presence of an enforced foreign key constraint between parent and child tables.&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;PeopleSoft has never implemented or supported database-enforced referential integrity constraints (again, mainly because it was done differently on different databases).&amp;nbsp; Although it is tempting to add a foreign key constraint between these tables, that would be a customisation to PeopleSoft that Oracle would not support.&amp;nbsp; The application would then have to insert parent rows before child rows and delete child rows before deleting parent rows.&amp;nbsp; It has never been tested against these constraints.&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;Therefore, it is only possible to consider partitioning by a column on PS_JRNL_LN.&amp;nbsp; A column in the unique key is an obvious choice.&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;
  &lt;li&gt;Depending on how BUSINESS_UNIT is set up and used, you might be able to list sub-partition by this column, and split journal lines down into several subpartitions.&amp;nbsp; However, it is almost inevitable that the volumes will be heavily skewed.&lt;/li&gt;
  &lt;li&gt;It is tempting to range partition on JOURNAL_ID.&amp;nbsp; Although this column usually contains an entirely numeric value, it is in fact defined as a character (VARCHAR2) data type.&amp;nbsp; Therefore, it is not possible to interval partition upon it.&amp;nbsp; Periodically, it would be necessary to add partitions manually.&lt;/li&gt;
  &lt;li&gt;The alternative is to interval range partition on JOURNAL_DATE.&amp;nbsp; I chose to define a monthly interval.&amp;nbsp; I specified the first few partitions for whole years because at this customer, these partitions contained less data after archiving.&amp;nbsp; Thereafter, Oracle automatically creates monthly partitions as data is inserted.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;/p&gt;
&lt;pre style=&quot;background-color: #eeeeee; font-family: &amp;quot;courier new&amp;quot;overflow: auto; line-height: 80%; text-align: left; width: 95%;&quot;&gt;&lt;span&gt;&lt;span style=&quot;font-size: x-small;&quot;&gt;CREATE TABLE PS_JRNL_LN 
(BUSINESS_UNIT VARCHAR2(5 CHAR) NOT NULL
,JOURNAL_ID VARCHAR2(10 CHAR) NOT NULL 
,JOURNAL_DATE DATE NOT NULL 
,UNPOST_SEQ NUMBER(*,0) NOT NULL 
,JOURNAL_LINE NUMBER(9,0) NOT NULL 
,LEDGER VARCHAR2(10 CHAR) NOT NULL 
…
) PARTITION BY RANGE (JOURNAL_DATE) INTERVAL (NUMTOYMINTERVAL(1, &#39;MONTH&#39;)) 
(PARTITION JRNL_LN_2016 VALUES LESS THAN (TO_DATE(&#39;2017-01-01&#39;, &#39;YYYY-MM-DD&#39;))
,PARTITION JRNL_LN_2017 VALUES LESS THAN (TO_DATE(&#39;2018-01-01&#39;, &#39;YYYY-MM-DD&#39;))
,PARTITION JRNL_LN_2018 VALUES LESS THAN (TO_DATE(&#39;2019-01-01&#39;, &#39;YYYY-MM-DD&#39;))
,PARTITION JRNL_LN_2019 VALUES LESS THAN (TO_DATE(&#39;2020-01-01&#39;, &#39;YYYY-MM-DD&#39;)) 
) 
/&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;
&lt;div&gt;&lt;div&gt;Partitioning on JOURNAL_DATE still arranges the journal line data into current and historical partitions. We find that the various financial processes only create journal lines in the current and previous fiscal years.&amp;nbsp; Therefore, earlier fiscal years are effectively static.&amp;nbsp; This presents an opportunity to compress these partitions because nothing will subsequently update them that would decompress compressed rows.&amp;nbsp; Thus, partitioning and compression go together.&lt;/div&gt;&lt;h4 style=&quot;text-align: left;&quot;&gt;Renaming Partitions&lt;/h4&gt;&lt;div&gt;By default, interval partitions are given system-generated names.&amp;nbsp; I find it convenient to rename them to something more meaningful.&amp;nbsp; This has no bearing on performance.&amp;nbsp;&amp;nbsp;In this case, I used something based on the date to which the partition relates.&amp;nbsp; There are 2 implicit cursors in the following PL/SQL block.&amp;nbsp; The first renames table partitions, and the second renames index partitions.&amp;nbsp; This script should be run periodically as new partitions are added. It is available on &lt;a href=&quot;https://github.com/davidkurtz/psscripts/blob/master/rename_jrnl_ln_partitions.sql&quot; target=&quot;_blank&quot;&gt;GitHub&lt;/a&gt;.&lt;/div&gt;&lt;/div&gt;
&lt;pre style=&quot;background-color: #eeeeee; font-family: &amp;quot;courier new&amp;quot;overflow: auto; line-height: 80%; text-align: left; width: 95%;&quot;&gt;&lt;span&gt;&lt;span style=&quot;font-size: xx-small;&quot;&gt;rem &lt;a href=&quot;https://github.com/davidkurtz/psscripts/blob/master/rename_jrnl_ln_partitions.sql&quot; target=&quot;_blank&quot;&gt;rename_jrnl_ln_partitions.sql&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;/a&gt;
rem requires &lt;a href=&quot;https://github.com/davidkurtz/psscripts/blob/master/psftapi.sql&quot; target=&quot;_blank&quot;&gt;https://github.com/davidkurtz/psscripts/blob/master/psftapi.sql&lt;/a&gt;
spool rename_jrnl_ln_partitions.lst
set serveroutput on
DECLARE
  l_high_value DATE;
  l_sql CLOB;
  l_new_partition_name VARCHAR2(30);
BEGIN
  psft_ddl_lock.set_ddl_permitted(TRUE);
  FOR i IN (
    select /*+LEADING(r upt upkc utc)*/ r.recname, upt.table_name, utp.partition_name, utp.high_value, upt.interval interval_size
    from sysadm.psrecdefn r 
      INNER JOIN user_part_tables upt ON upt.table_name = DECODE(r.sqltablename,&#39; &#39;,&#39;PS_&#39;||r.recname,r.sqltablename) 
        AND upt.partitioning_type = &#39;RANGE&#39; and upt.interval IS NOT NULL
      INNER JOIN user_part_key_columns upkc ON upkc.name = upt.table_name AND upkc.object_Type = &#39;TABLE&#39; and upkc.column_position = 1
      INNER JOIN user_tab_columns utc ON utc.table_name = upkc.name AND utc.column_name = upkc.column_name
      INNER JOIN user_tab_partitions utp ON utp.table_name = upt.table_name AND utp.partition_name like &#39;SYS_P%&#39;
    WHERE r.recname = &#39;JRNL_LN&#39; AND r.rectype = 0
    AND (utc.data_type = &#39;DATE&#39; OR utc.data_type like &#39;TIMESTAMP%&#39;)
  ) LOOP
    l_sql := &#39;SELECT &#39;||i.high_value||&#39;-&#39;||i.interval_size||&#39; FROM DUAL&#39;;
    EXECUTE IMMEDIATE l_sql INTO l_high_value;
    l_new_partition_name := i.recname||&#39;_&#39;||TO_CHAR(l_high_value,&#39;YYYYMM&#39;);
    l_sql := &#39;ALTER TABLE &#39;||i.table_name||&#39; RENAME PARTITION &#39;||i.partition_name||&#39; TO &#39;||l_new_partition_name;
    IF i.partition_name != l_new_partition_name THEN
      dbms_output.put_line(l_sql);
      EXECUTE IMMEDIATE l_sql;
    END IF;
  END LOOP;

  FOR i IN (
    select /*+LEADING(r upi upkc utc)*/ r.recname, upi.index_name, uip.partition_name, uip.high_value, upi.interval interval_size
    from sysadm.psrecdefn r 
      INNER JOIN user_part_indexes upi ON upi.table_name = DECODE(r.sqltablename,&#39; &#39;,&#39;PS_&#39;||r.recname,r.sqltablename) 
        AND upi.partitioning_type = &#39;RANGE&#39; and upi.interval IS NOT NULL
      INNER JOIN user_part_key_columns upkc ON upkc.name = upi.index_name AND upkc.object_Type = &#39;INDEX&#39; and upkc.column_position = 1
      INNER JOIN user_tab_columns utc ON utc.table_name = upi.table_name AND utc.column_name = upkc.column_name
      INNER JOIN user_ind_partitions uip ON uip.index_name = upi.index_name 
        AND (uip.partition_name like &#39;SYS_P%&#39; OR SUBSTR(uip.partition_name,1+LENGTH(r.recname),1) != SUBSTR(upi.index_name,3,1))
    WHERE r.recname = &#39;JRNL_LN&#39; AND r.rectype = 0
    AND (utc.data_type = &#39;DATE&#39; OR utc.data_type like &#39;TIMESTAMP%&#39;)
  ) LOOP
    l_sql := &#39;SELECT &#39;||i.high_value||&#39;-&#39;||i.interval_size||&#39; FROM DUAL&#39;;
    EXECUTE IMMEDIATE l_sql INTO l_high_value;
    l_new_partition_name := i.recname||SUBSTR(i.index_name,3,1)||TO_CHAR(l_high_value,&#39;YYYYMM&#39;);
    l_sql := &#39;ALTER INDEX &#39;||i.index_name||&#39; RENAME PARTITION &#39;||i.partition_name||&#39; TO &#39;||l_new_partition_name;
    IF i.partition_name != l_new_partition_name THEN
      dbms_output.put_line(l_sql);
      EXECUTE IMMEDIATE l_sql;
    END IF;
  END LOOP;
  psft_ddl_lock.set_ddl_permitted(FALSE);
END;
/
spool off&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;div class=&quot;blogger-post-footer&quot;&gt;&lt;a href=&quot;http://www.go-faster.co.uk/&quot;&gt;©David Kurtz&lt;/a&gt;&lt;/div&gt;</description><link>https://blog.psftdba.com/2024/12/optimisingjournalline3partitioning.html</link><author>noreply@blogger.com (David Kurtz)</author><thr:total>0</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-25740336.post-8205530342715545848</guid><pubDate>Tue, 24 Jun 2025 15:19:00 +0000</pubDate><atom:updated>2025-06-30T14:06:23.074+01:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">Exadata</category><category domain="http://www.blogger.com/atom/ns#">Journal Line</category><category domain="http://www.blogger.com/atom/ns#">PS/Query</category><category domain="http://www.blogger.com/atom/ns#">PS_JRNL_LN</category><category domain="http://www.blogger.com/atom/ns#">System Statistics</category><title>Optimising Journal Line Queries: 2. Exadata System Statistics</title><description>&lt;p&gt;This is the second of a series of five articles that examine the challenges posed by typical queries on the journal line table (PS_JRNL_LN).&lt;/p&gt;&lt;ol&gt;&lt;li&gt;&lt;a href=&quot;https://blog.psftdba.com/2024/12/journallinequeries1problemstatement.html&quot;&gt;Problem Statement&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://blog.psftdba.com/2024/12/journallinequeries2exadatasystemstatistics.html&quot;&gt;Exadata System Statistics&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://blog.psftdba.com/2024/12/optimisingjournalline3partitioning.html&quot;&gt;Partitioning&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://blog.psftdba.com/2024/12/optimisingjournalline4compression.html&quot;&gt;Compression&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://blog.psftdba.com/2025/05/optimisingjournalline5conclusion.html&quot;&gt;Conclusion&lt;/a&gt;&lt;/li&gt;&lt;/ol&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Exadata System Statistics&amp;nbsp;&lt;/h3&gt;&lt;p&gt;Many other people have written notes about how Oracle&#39;s optimizer costs a full table scan.&amp;nbsp; This is a selection:&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Neil Chandler:&amp;nbsp; &lt;a href=&quot;https://chandlerdba.com/2021/02/03/oracle-optimizer-system-statistics/&quot; target=&quot;_blank&quot;&gt;Oracle Optimizer System Statistics&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;John Brady: &lt;a href=&quot;http://databaseperformance.blogspot.com/2009/05/oracle-optimizer-plan-costing-full.html&quot; target=&quot;_blank&quot;&gt;Oracle Optimizer Plan Costing - Full Table Scans&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;Randolf Geist: &lt;a href=&quot;https://oracle-randolf.blogspot.com/2009/05/understanding-different-modes-of-system_24.html&quot; target=&quot;_blank&quot;&gt;Understanding the different modes of System Statistics aka. CPU Costing and the effects of multiple block sizes - part 4&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;Martin Widlake: &lt;a href=&quot;https://mwidlake.wordpress.com/2009/06/17/cost-of-full-table-scans/&quot; target=&quot;_blank&quot;&gt;Cost of Full Table Scans&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;Roughly speaking, the cost calculated by the optimizer that we see in an execution plan is an estimate of the time taken to perform an operation, where the unit of time is the duration of a single block read.&amp;nbsp; Although that statement is an oversimplification.&amp;nbsp; There are various guesses and assumptions built into the optimizer&#39;s calculation.&amp;nbsp; The cost-based optimizer looks for the cheapest plan, that ought to be the fastest to execute.&amp;nbsp; However, in many cases, cost does not correspond to execution time.&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;See also Jonathan Lewis: &lt;a href=&quot;https://jonathanlewis.wordpress.com/2006/12/11/cost-is-time/&quot; target=&quot;_blank&quot;&gt;Cost is Time&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;/p&gt;&lt;h4 style=&quot;text-align: left;&quot;&gt;Full Scan Cost&lt;/h4&gt;&lt;p&gt;The cost of a full table scan is made up of an I/O cost (the time taken to read the blocks from disk) and a CPU cost (the time taken to process the rows).&amp;nbsp; The I/O cost is the number of multi-block read operations, multiplied by the ratio of the duration of a multi-block read to a single-block read.&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;b style=&quot;font-family: courier;&quot;&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&lt;b style=&quot;font-family: courier;&quot;&gt;IO Cost = (HWM / MBRC) . (MREADTIM / SREADTIM)&lt;/b&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/b&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;Where&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;
  &lt;li&gt;&lt;span style=&quot;white-space: normal;&quot;&gt;HWM = the high water mark of the segment expressed as a number of blocks&lt;/span&gt;&lt;/li&gt;
  &lt;li&gt;&lt;span style=&quot;white-space: normal;&quot;&gt;MBRC = average multi-block read count for sequential read, in blocks (see parameter &lt;a href=&quot;https://docs.oracle.com/en/database/oracle/oracle-database/19/refrn/DB_FILE_MULTIBLOCK_READ_COUNT.html&quot; rel=&quot;nofollow&quot; target=&quot;_blank&quot;&gt;DB_FILE_MULTIBLOCK_READ_COUNT&lt;/a&gt;).&lt;/span&gt;&lt;/li&gt;
  &lt;li&gt;&lt;span style=&quot;white-space: normal;&quot;&gt;MREADTIME = average time to perform a multi-block read at once (sequential read), in milliseconds&lt;/span&gt;&lt;/li&gt;
  &lt;li&gt;&lt;span style=&quot;white-space: normal;&quot;&gt;SREADTIME = average time to read a single block (random read), in milliseconds&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;white-space: normal;&quot;&gt;&lt;span style=&quot;white-space: pre;&quot;&gt;	&lt;/span&gt;See &lt;a href=&quot;https://docs.oracle.com/en/database/oracle/oracle-database/21/arpls/DBMS_STATS.html#d1036151e43383&quot; target=&quot;_blank&quot;&gt;PL/SQL Packages and Types Reference: DBMS_STATS&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;The single and multi-block read times are derived from two system statistics, the block size and the multi-block read count.&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;b&gt;SREADTIM = IOSEEKTIM + DB_BLOCK_SIZE / IOTFRSPEED&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
    &lt;li&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;b&gt;MREADTIM = IOSEEKTIM + (DB_BLOCK_SIZE * MBRC) / IOTFRSPEED&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;Where&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&lt;span style=&quot;white-space: normal;&quot;&gt;IOSEEKTIM = Seek time + latency time + operating system overhead time, in milliseconds (default 10ms).&lt;/span&gt;&lt;/li&gt;
    &lt;li&gt;&lt;span style=&quot;white-space: normal;&quot;&gt;IOTFRSPEED = I/O transfer speed in bytes per millisecond (or if you prefer KBytes/second)&lt;/span&gt;&lt;/li&gt;
      &lt;li&gt;&lt;span style=&quot;white-space: normal;&quot;&gt;DB_BLOCK_SIZE = block size of the segment (usually 8Kb)&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;System statistics can be gathered based on actual system behaviour using DBMS_STATS, or set to pre-defined values using &lt;a href=&quot;https://docs.oracle.com/en/database/oracle/oracle-database/21/arpls/DBMS_STATS.html#GUID-257CDCB8-3595-464B-8337-43EF23A6068D&quot; rel=&quot;nofollow&quot; target=&quot;_blank&quot;&gt;DBMS_STATS.GATHER_SYSTEM_STATS&lt;/a&gt;.&amp;nbsp; Over the years many blogs, forums and presentations have discussed the merits or otherwise of collecting or setting system statistics.&amp;nbsp;&amp;nbsp;&lt;/p&gt;&lt;p&gt;Oracle&#39;s position is set out in the &lt;a href=&quot;https://blogs.oracle.com/optimizer/post/should-you-gather-system-statistics&quot; target=&quot;_blank&quot;&gt;Oracle Optimizer Blog: Should You Gather System Statistics?&lt;/a&gt;&amp;nbsp; It can be summarised as:&amp;nbsp;&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&lt;span style=&quot;white-space: normal;&quot;&gt;Do not gather your own system statistics.&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;white-space: normal;&quot;&gt;Use the Oracle-provided defaults.&lt;/span&gt;&lt;/li&gt;
      &lt;li&gt;&lt;span style=&quot;white-space: normal;&quot;&gt;Except on Exadata, where you can consider using the Exadata defaults, and perhaps not even then on a mixed workload. &lt;b&gt;You will have to test this for yourself.&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;On any Oracle system, the default system statistics can be reset with&amp;nbsp;&lt;/p&gt;
&lt;pre style=&quot;background-color: #eeeeee; font-family: &amp;quot;courier new&amp;quot;overflow: auto; line-height: 80%; text-align: left; width: 95%;&quot;&gt;&lt;span&gt;&lt;span&gt;exec DBMS_STATS.GATHER_SYSTEM_STATS(&#39;NOWORKLOAD&#39;);&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;
&lt;p&gt;This sets the system statistics as follows:&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&lt;span style=&quot;white-space: normal;&quot;&gt;MBRC=8&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;white-space: normal;&quot;&gt;IOSEEKTIM=10&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;white-space: normal;&quot;&gt;IOTFRSPEED=10&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;Thus:&lt;/p&gt;&lt;p style=&quot;text-align: left;&quot;&gt;&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;blockquote style=&quot;border: none; margin: 0px 0px 0px 40px; padding: 0px; text-align: left;&quot;&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;b&gt;SREADTIM = IOSEEKTIM + DB_BLOCK_SIZE / IOTFRSPEED &lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;&lt;blockquote style=&quot;border: none; margin: 0px 0px 0px 40px; padding: 0px; text-align: left;&quot;&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;b&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;= 10 + 8192 / 4096&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;&lt;blockquote style=&quot;border: none; margin: 0px 0px 0px 40px; padding: 0px; text-align: left;&quot;&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;b&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;= 12 (ms)&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;blockquote style=&quot;border: none; margin: 0px 0px 0px 40px; padding: 0px; text-align: left;&quot;&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;b&gt;MREADTIM = IOSEEKTIM + (DB_BLOCK_SIZE * MBRC) / IOTFRSPEED &lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;&lt;blockquote style=&quot;border: none; margin: 0px 0px 0px 40px; padding: 0px; text-align: left;&quot;&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;b&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;= 10 + (8192 * 8 ) / 4096 &lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;&lt;blockquote style=&quot;border: none; margin: 0px 0px 0px 40px; padding: 0px; text-align: left;&quot;&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;b&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;= 10 + 16&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;&lt;blockquote style=&quot;border: none; margin: 0px 0px 0px 40px; padding: 0px; text-align: left;&quot;&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;b&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;= 26 (ms)&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;However, on Exadata, you can set &#39;&lt;a href=&quot;https://docs.oracle.com/en/database/oracle/oracle-database/23/arpls/DBMS_STATS.html#GUID-257CDCB8-3595-464B-8337-43EF23A6068D&quot; target=&quot;_blank&quot;&gt;system statistics take into account the unique capabilities of Oracle Exadata, such as large I/O size and high I/O throughput&lt;/a&gt;&#39;&amp;nbsp;&lt;/p&gt;
&lt;pre style=&quot;background-color: #eeeeee; font-family: &amp;quot;courier new&amp;quot;overflow: auto; line-height: 80%; text-align: left; width: 95%;&quot;&gt;&lt;span&gt;&lt;span&gt;exec DBMS_STATS.GATHER_SYSTEM_STATS(&#39;EXADATA&#39;);&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;
&lt;p&gt;Some system statistics are then set differently:&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;
  &lt;li&gt;&lt;span style=&quot;white-space: normal;&quot;&gt;MBRC=128&lt;/span&gt;&lt;/li&gt;
  &lt;li&gt;&lt;span style=&quot;white-space: normal;&quot;&gt;IOSEEKTIM=10&lt;/span&gt;&lt;/li&gt;
  &lt;li&gt;&lt;span style=&quot;white-space: normal;&quot;&gt;IOTFRSPEED=204800&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;Thus&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;blockquote style=&quot;border: none; margin: 0px 0px 0px 40px; padding: 0px; text-align: left;&quot;&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;b&gt;SREADTIM = IOSEEKTIM + DB_BLOCK_SIZE / IOTFRSPEED&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;&lt;blockquote style=&quot;border: none; margin: 0px 0px 0px 40px; padding: 0px; text-align: left;&quot;&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;b&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;= 10 + 8192 / 204800&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;&lt;blockquote style=&quot;border: none; margin: 0px 0px 0px 40px; padding: 0px; text-align: left;&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; = 10.04 (ms)&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;&lt;p&gt;&lt;/p&gt;&lt;blockquote style=&quot;border: none; margin: 0px 0px 0px 40px; padding: 0px; text-align: left;&quot;&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;b&gt;MREADTIM = IOSEEKTIM + (DB_BLOCK_SIZE * MBRC) / IOTFRSPEED&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;&lt;blockquote style=&quot;border: none; margin: 0px 0px 0px 40px; padding: 0px; text-align: left;&quot;&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;b&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;= 10 + (8192 . 128 ) / 204800&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;&lt;blockquote style=&quot;border: none; margin: 0px 0px 0px 40px; padding: 0px; text-align: left;&quot;&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;b&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;= 10 + 5.1200&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;&lt;blockquote style=&quot;border: none; margin: 0px 0px 0px 40px; padding: 0px; text-align: left;&quot;&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;b&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;= 15.12000 (ms)&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;&lt;p&gt;Now, I can put these numbers back into the formula Oracle uses to calculate the I/O cost of a full scan.&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;
       &lt;li&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;b&gt;IO Cost = (HWM / MBRC) . (MREADTIM / SREADTIM)
&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;Let us suppose that we are going to read 100M blocks.&amp;nbsp; The I/O cost of that scan will be very different with Exadata system statistics rather than the normal default system statistics.&lt;/p&gt;&lt;p&gt;&lt;/p&gt;
&lt;blockquote style=&quot;border: none; margin: 0px 0px 0px 40px; padding: 0px; text-align: left;&quot;&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;b&gt;Normal IO Cost&amp;nbsp; = (100000000/8) . (26/12)&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;blockquote style=&quot;border: none; margin: 0px 0px 0px 40px; padding: 0px; text-align: left;&quot;&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;b&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; = 27,083,333.3&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;blockquote style=&quot;border: none; margin: 0px 0px 0px 40px; padding: 0px; text-align: left;&quot;&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;b&gt;&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;blockquote style=&quot;border: none; margin: 0px 0px 0px 40px; padding: 0px;&quot;&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;b&gt;Exadata IO Cost = (100000000/128) . (15.12/10.04)&lt;/b&gt;&lt;/span&gt;&lt;/div&gt;&lt;/blockquote&gt;
&lt;blockquote style=&quot;border: none; margin: 0px 0px 0px 40px; padding: 0px;&quot;&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;b&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; = 1,176,543.8&lt;/b&gt;&lt;/span&gt;&lt;/div&gt;&lt;/blockquote&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;Thus, introducing Exadata system statistics significantly reduces the cost of the full scan, making the database more likely to use a full scan than index lookups.&amp;nbsp; That may or may not be desirable.&lt;/p&gt;
&lt;pre style=&quot;background-color: #eeeeee; font-family: &amp;quot;courier new&amp;quot;overflow: auto; line-height: 80%; text-align: left; width: 95%;&quot;&gt;&lt;span&gt;&lt;span style=&quot;font-size: xx-small;&quot;&gt;-------------------------------------------------------------------------------------------------------------------------
| Id  | Operation                      | Name           | Rows  | Bytes |TempSpc| Cost (%CPU)| Time     | Pstart| Pstop |
-------------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT               |                |   428K|    93M|       |  2834K  (8)| 00:01:51 |       |       |
|   1 |  HASH GROUP BY                 |                |   428K|    93M|   111M|  2834K  (8)| 00:01:51 |       |       |
|*  2 |   HASH JOIN                    |                |   428K|    93M|    46M|  2819K  (8)| 00:01:51 |       |       |
|   3 |    JOIN FILTER CREATE          | :BF0001        |   428K|    41M|       |  1476   (7)| 00:00:01 |       |       |
|   4 |     PART JOIN FILTER CREATE    | :BF0000        |   428K|    41M|       |  1476   (7)| 00:00:01 |       |       |
|*  5 |      TABLE ACCESS STORAGE FULL | PS_JRNL_HEADER |   428K|    41M|       |  1476   (7)| 00:00:01 |       |       |
|   6 |    JOIN FILTER USE             | :BF0001        |  1120K|   136M|       |  2817K  (8)| 00:01:51 |       |       |
|   7 |     PARTITION RANGE JOIN-FILTER|                |  1120K|   136M|       |  2817K  (8)| 00:01:51 |:BF0000|:BF0000|
|*  8 |      TABLE ACCESS STORAGE FULL | PS_JRNL_LN     |  1120K|   136M|       |  &lt;b&gt;2817K&lt;/b&gt;  (8)| 00:01:51 |:BF0000|:BF0000|
-------------------------------------------------------------------------------------------------------------------------&lt;/span&gt;&lt;span style=&quot;font-size: 50%;&quot;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;
&lt;p&gt;If I look at the same example query that I used earlier, then with the Exadata default system statistics, the cost has come down significantly (from 66M to 2817K).&amp;nbsp; It is a significant improvement, but it is still greater than the cost of the nested loop (1730K).&amp;nbsp; Therefore, for this query, I still only get this execution plan if I hint the statement to force it.&amp;nbsp; I still need to make the full scan cheaper.&lt;/p&gt;&lt;p&gt;Different queries will have different costs and will flip between the nested loop and Full scan/Bloom filter/hash join at different points.&lt;/p&gt;&lt;h3&gt;Non-Exadata System Statistics&amp;nbsp;&lt;/h3&gt;&lt;div&gt;If you are not running on Exadata, then the advice from Oracle is clear and unambiguous: use the default system statistics that can be reset with&amp;nbsp;&lt;/div&gt;&lt;pre style=&quot;background-color: #eeeeee; font-family: &amp;quot;courier new&amp;quot;overflow: auto; line-height: 10.4px; width: 633.838px;&quot;&gt;&lt;span&gt;exec DBMS_STATS.GATHER_SYSTEM_STATS(&#39;NOWORKLOAD&#39;);&lt;/span&gt;&lt;/pre&gt;&lt;div&gt;&lt;span&gt;You will have to consider other techniques to reduce the cost of the full table scan.&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;blogger-post-footer&quot;&gt;&lt;a href=&quot;http://www.go-faster.co.uk/&quot;&gt;©David Kurtz&lt;/a&gt;&lt;/div&gt;</description><link>https://blog.psftdba.com/2024/12/journallinequeries2exadatasystemstatistics.html</link><author>noreply@blogger.com (David Kurtz)</author><thr:total>0</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-25740336.post-8616870654215531871</guid><pubDate>Mon, 23 Jun 2025 15:30:00 +0000</pubDate><atom:updated>2025-06-30T14:06:39.708+01:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">Exadata</category><category domain="http://www.blogger.com/atom/ns#">Journal Line</category><category domain="http://www.blogger.com/atom/ns#">PS/Query</category><category domain="http://www.blogger.com/atom/ns#">PS_JRNL_LN</category><title>Optimising Journal Line Queries: 1. Problem Statement</title><description>&lt;p&gt;In each PeopleSoft product, certain tables usually grow to become the largest in the implementations at most customers. The challenges they present and the options for dealing with them are also common to most systems.&amp;nbsp; Most PeopleSoft Financials systems use General Ledger.&amp;nbsp; In General Ledger, the ledger, summary ledger and journal line tables are usually the largest tables, and present the biggest challenges.&lt;/p&gt;&lt;p&gt;This is the first of five articles that examine the challenges typically presented by queries on the journal line table (PS_JRNL_LN).&lt;/p&gt;&lt;ol style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&lt;a href=&quot;https://blog.psftdba.com/2024/12/journallinequeries1problemstatement.html&quot;&gt;Problem Statement&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://blog.psftdba.com/2024/12/journallinequeries2exadatasystemstatistics.html&quot;&gt;Exadata System Statistics&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://blog.psftdba.com/2024/12/optimisingjournalline3partitioning.html&quot;&gt;Partitioning&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://blog.psftdba.com/2024/12/optimisingjournalline4compression.html&quot;&gt;Compression&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://blog.psftdba.com/2025/05/optimisingjournalline5conclusion.html&quot;&gt;Conclusion&lt;/a&gt;&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;&lt;/p&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Problem Statement&lt;/h3&gt;
In General Ledger, we typically see many queries on the ledger (or summary ledger) tables and also queries in the application, drill-down queries in nVision reporting, and ad-hoc PS/Queries that query details of journals posted to the ledger.  
Below is part of a typical query. The statement and execution plans below were taken from a PeopleSoft Financials system.&amp;nbsp; It is running on Oracle 19c on Exadata.&amp;nbsp; Making use of Exadata features will also be a topic.&lt;pre style=&quot;background-color: #eeeeee; font-family: &amp;quot;courier new&amp;quot;overflow: auto; line-height: 80%; text-align: left; width: 95%;&quot;&gt;&lt;span style=&quot;font-size: xx-small;&quot;&gt;SELECT A.FISCAL_YEAR, A.ACCOUNTING_PERIOD, B.BUSINESS_UNIT, B.JOURNAL_ID, TO_CHAR(B.JOURNAL_DATE,&#39;YYYY-MM-DD&#39;), B.LEDGER, B.ACCOUNT
, B.PRODUCT, B.PROJECT_ID, B.CHARTFIELD1, B.CHARTFIELD2, B.CURRENCY_CD, B.AFFILIATE, SUM( B.MONETARY_AMOUNT), B.FOREIGN_CURRENCY
, SUM( B.FOREIGN_AMOUNT), A.REVERSAL_CD, A.REVERSAL_ADJ_PER, A.JRNL_HDR_STATUS, TO_CHAR(A.POSTED_DATE,&#39;YYYY-MM-DD&#39;), A.OPRID, A.DESCR254
, B.DEPTID, A.SOURCE, B.ALTACCT, TO_CHAR(CAST((A.DTTM_STAMP_SEC) AS TIMESTAMP),&#39;YYYY-MM-DD-HH24.MI.SS.FF&#39;), B.LINE_DESCR 
FROM PS_JRNL_HEADER A, PS_JRNL_LN B 
WHERE (&lt;b&gt;A.BUSINESS_UNIT = B.BUSINESS_UNIT 
AND A.JOURNAL_ID = B.JOURNAL_ID 
AND A.JOURNAL_DATE = B.JOURNAL_DATE 
AND A.UNPOST_SEQ = B.UNPOST_SEQ&lt;/b&gt; 
AND A.JRNL_HDR_STATUS IN(&#39;P&#39;,&#39;V&#39;,&#39;U&#39;) 
&lt;b&gt;AND A.FISCAL_YEAR IN (2024) 
AND A.ACCOUNTING_PERIOD BETWEEN 1 AND 12 
&lt;/b&gt;AND B.CHARTFIELD1 IN (&#39;1234567&#39;,&#39;1234568&#39;,&#39;1234569&#39;)
AND B.LEDGER IN (&#39;LEDGER&#39;)) 
GROUP BY A.FISCAL_YEAR, A.ACCOUNTING_PERIOD, B.BUSINESS_UNIT, B.JOURNAL_ID, B.JOURNAL_DATE, B.LEDGER, B.ACCOUNT, B.PRODUCT
, B.PROJECT_ID, B.CHARTFIELD1, B.CHARTFIELD2, B.CURRENCY_CD, B.AFFILIATE, B.FOREIGN_CURRENCY, A.REVERSAL_CD, A.REVERSAL_ADJ_PER
, A.JRNL_HDR_STATUS, A.POSTED_DATE, A.OPRID, A.DESCR254, B.DEPTID, A.SOURCE, B.ALTACCT, A.DTTM_STAMP_SEC, B.LINE_DESCR&lt;/span&gt;&lt;/pre&gt;
&lt;div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;The journal line table (PS_JRNL_LN) is joined to its parent, the journal header table (PS_JRNL_HEADER), by the 4 key columns on the journal header (BUSINESS_UNIT, JOURNAL_ID, JOURNAL_DATE, UNPOST_SEQ).&lt;/li&gt;&lt;li&gt;There are criteria on both the journal header and line tables.&lt;/li&gt;&lt;ul&gt;&lt;li&gt;The number of journal line rows per header is usually highly variable, and it also varies from customer to customer depending on the shape of their data.&amp;nbsp; It is not unusual to see thousands of journal line rows per header row.&amp;nbsp; Filtering it by FISCAL_YEAR and perhaps also ACCOUNTING_PERIOD could be very effective.&amp;nbsp; However, these columns are on PS_JRNL_HEADER, and not on PS_JRNL_LN.&amp;nbsp;&lt;/li&gt;&lt;li&gt;Queries often include criteria on other attribute columns on PS_JRNL_LN.&amp;nbsp; However, these columns are not indexed by default, though many customers add such indexes.&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/ul&gt;&lt;/div&gt;
&lt;div&gt;Below is the execution plan that Oracle produced for this query.&amp;nbsp; The optimizer chose to full scan the PS_JRNL_HEADER table, expecting to find 428,000 rows, and then do an index look-up on PS_JRNL_LN for each header row.&amp;nbsp; The optimizer predicts that the query will run for about 68s.&amp;nbsp; In practice, this query runs for over an hour, spending most of its time on the index lookup of PS_JRNL_LN by its unique index of the same name.&lt;/div&gt;
&lt;pre style=&quot;background-color: #eeeeee; font-family: &amp;quot;courier new&amp;quot;overflow: auto; line-height: 80%; text-align: left; width: 95%;&quot;&gt;&lt;span style=&quot;font-size: xx-small;&quot;&gt;------------------------------------------------------------------------------------------------------------------------------
| Id  | Operation                           | Name           | Rows  | Bytes |TempSpc| Cost (%CPU)| Time     | Pstart| Pstop |
------------------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                    |                |   428K|    93M|       |  1730K  (1)| 00:01:08 |       |       |
|   1 |  HASH GROUP BY                      |                |   428K|    93M|   111M|  1730K  (1)| 00:01:08 |       |       |
|   2 |   NESTED LOOPS                      |                |   428K|    93M|       |  1715K  (1)| 00:01:07 |       |       |
|   3 |    NESTED LOOPS                     |                |   428K|    93M|       |  &lt;b&gt;1715K&lt;/b&gt;  (1)| 00:01:07 |       |       |
|*  4 |     TABLE ACCESS STORAGE FULL       | PS_JRNL_HEADER |   &lt;b&gt;428K&lt;/b&gt;|    41M|       |  1476   (7)| 00:00:01 |       |       |
|   5 |     PARTITION RANGE ITERATOR        |                |     1 |       |       |     3   (0)| 00:00:01 |   KEY |   KEY |
|*  6 |      &lt;b&gt;INDEX RANGE SCAN&lt;/b&gt;               | &lt;b&gt;PS_JRNL_LN&lt;/b&gt;     |     1 |       |       |     3   (0)| 00:00:01 |   KEY |   KEY |
|*  7 |    TABLE ACCESS BY LOCAL INDEX ROWID| PS_JRNL_LN     |     1 |   128 |       |     4   (0)| 00:00:01 |     1 |     1 |
------------------------------------------------------------------------------------------------------------------------------&lt;/span&gt;&lt;/pre&gt;
&lt;div&gt;If I add hints to the SQL statement forcing it to full scan PS_JRNL_HEADER and generate a&amp;nbsp;&lt;a href=&quot;https://antognini.ch/papers/BloomFilters20080620.pdf&quot; target=&quot;_blank&quot;&gt;Bloom&lt;/a&gt; filter that it will apply during a full scan of PS_JRNL_LN, then the cost of the execution plan goes up (from 1730K to 66M), and the estimated run time goes up to 43 minutes, due to the cost of the full scan.&amp;nbsp; However, the actual execution time comes down to under 3 minutes.&lt;/div&gt;
&lt;pre style=&quot;background-color: #eeeeee; font-family: &amp;quot;courier new&amp;quot;overflow: auto; line-height: 80%; text-align: left; width: 95%;&quot;&gt;&lt;span style=&quot;font-size: xx-small;&quot;&gt;-------------------------------------------------------------------------------------------------------------------------
| Id  | Operation                      | Name           | Rows  | Bytes |TempSpc| Cost (%CPU)| Time     | Pstart| Pstop |
-------------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT               |                |   428K|    93M|       |    66M  (1)| 00:43:30 |       |       |
|   1 |  HASH GROUP BY                 |                |   428K|    93M|   111M|    66M  (1)| 00:43:30 |       |       |
|*  2 |   HASH JOIN                    |                |   428K|    93M|    46M|    66M  (1)| 00:43:30 |       |       |
|   3 |    JOIN FILTER CREATE          | :BF0001        |   428K|    41M|       | 32501   (1)| 00:00:02 |       |       |
|   4 |     PART JOIN FILTER CREATE    | :BF0000        |   428K|    41M|       | 32501   (1)| 00:00:02 |       |       |
|*  5 |      TABLE ACCESS STORAGE FULL | PS_JRNL_HEADER |   428K|    41M|       | 32501   (1)| 00:00:02 |       |       |
|   6 |    JOIN FILTER USE             | :BF0001        |  1132K|   137M|       |    66M  (1)| 00:43:28 |       |       |
|   7 |     PARTITION RANGE JOIN-FILTER|                |  1132K|   137M|       |    66M  (1)| 00:43:28 |:BF0000|:BF0000|
|*  8 |      TABLE ACCESS STORAGE FULL | PS_JRNL_LN     |  1132K|   137M|       |    &lt;b&gt;66M&lt;/b&gt;  (1)| 00:43:28 |:BF0000|:BF0000|
-------------------------------------------------------------------------------------------------------------------------&lt;/span&gt;&lt;/pre&gt;
&lt;div&gt;I could solve the problem by adding hints either directly to the statement or with a SQL profile or SQL patch.&amp;nbsp; However, this is one of many statements, and the users will continuously produce more with the ad-hoc PS/Query tool.&amp;nbsp; I need a generic solution.&lt;/div&gt;&lt;div&gt;&lt;div&gt;The Cost-Based Optimizer chose the nested loop plan because it calculated that it was cheaper.&amp;nbsp; However, that does not correspond to the change in the actual execution time.&amp;nbsp; The plan that was executed more quickly cost more, indicating a problem with the cost model.&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Why is the full scan so expensive?&amp;nbsp;&amp;nbsp;&lt;/li&gt;&lt;li&gt;How can I make it cheaper?&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;&lt;a href=&quot;https://blog.psftdba.com/2024/12/journallinequeries2exadatasystemstatistics.html&quot;&gt;Now read on...&lt;/a&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;blogger-post-footer&quot;&gt;&lt;a href=&quot;http://www.go-faster.co.uk/&quot;&gt;©David Kurtz&lt;/a&gt;&lt;/div&gt;</description><link>https://blog.psftdba.com/2024/12/journallinequeries1problemstatement.html</link><author>noreply@blogger.com (David Kurtz)</author><thr:total>0</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-25740336.post-6791989796570934650</guid><pubDate>Thu, 08 May 2025 08:06:00 +0000</pubDate><atom:updated>2025-05-08T09:06:43.004+01:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">PS/Query</category><category domain="http://www.blogger.com/atom/ns#">PSQUERY</category><category domain="http://www.blogger.com/atom/ns#">Quarantined SQL Plan</category><title>Logging Run Controls and Bind Variables for Scheduled PS/Queries</title><description>&lt;p style=&quot;text-align: left;&quot;&gt;This blog proposes additional logging for scheduled PS/Queries so that long-running queries can be reconstructed and analysed.&lt;/p&gt;&lt;p style=&quot;text-align: left;&quot;&gt;Previous blog posts have discussed limiting PS/Query runtime with the resource manager (see&amp;nbsp;&lt;a href=&quot;https://blog.psftdba.com/2025/02/management-of-long-running-psqueries.html&quot;&gt;Management of Long Running PS/Queries Cancelled by Resource Manager CPU Limit&lt;/a&gt;).&amp;nbsp; From 19c, on Engineered Systems only, the &lt;i&gt;&#39;Oracle Database automatically quarantines the plans for SQL statements terminated by … the Resource Manager for exceeding resource limits&#39;.&amp;nbsp;&amp;nbsp;&lt;/i&gt;SQL Quarantine is enabled by default in Oracle 19c on Exadata (unless patch &lt;a href=&quot;https://support.oracle.com/epmos/faces/DocContentDisplay?id=30104721.8&quot; rel=&quot;nofollow&quot; target=&quot;_blank&quot;&gt;30104721&lt;/a&gt; is applied that backports the new 23c parameters, see&amp;nbsp;&lt;a href=&quot;https://support.oracle.com/epmos/faces/DocContentDisplay?id=2635030.1&quot; rel=&quot;nofollow&quot; target=&quot;_blank&quot;&gt;Oracle Doc ID 2635030.1: 19c New Feature SQL Quarantine - How To Stop Automatic SQL Quarantine&lt;/a&gt;).&lt;/p&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;What is the Problem?&lt;/h3&gt;&lt;p&gt;SQL Quarantine prevents a query from executing.&amp;nbsp; Therefore, AWR will not capture the execution plan.&amp;nbsp; AWR will also purge execution plans where an execution has not been captured within the AWR retention period.&amp;nbsp; The original long-running query execution that was quarantined, if captured by AWR, will be aged out because it will not execute again.&lt;/p&gt;&lt;p&gt;If we want to investigate PS/Queries that produced execution plans that exceeded the runtime limit and were then quarantined, we need to reproduce the execution plan, either with the &lt;a href=&quot;https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/EXPLAIN-PLAN.html&quot; rel=&quot;nofollow&quot; target=&quot;_blank&quot;&gt;EXPLAIN PLAN FOR&lt;/a&gt; command or by executing the query in a session where the limited resource manager consumer group does not apply.&lt;/p&gt;&lt;p&gt;However, PS/Queries with bind variables present a challenge.&amp;nbsp; A PS/Query run with different bind variables can produce different execution plans.&amp;nbsp; One execution plan might be quarantined and so never complete, while another may complete within an acceptable time.&amp;nbsp;&amp;nbsp;&lt;/p&gt;&lt;p&gt;In AWR, a plan is only captured once for each statement.&amp;nbsp; Therefore, it is possible to find one set of bind variables for each plan, although there may be many sets of bind variables that all produce the same execution plan.&amp;nbsp; However, we cannot obtain Oracle bind variables for quarantined execution plans that did not execute.&amp;nbsp; To regenerate their execution plans, we need another way to obtain their bind variables.&lt;/p&gt;&lt;p&gt;This problem occurs more generally where the Diagnostics Pack is not available, then it is not possible to reconstruct long-running queries without additional logging or tracing.&lt;/p&gt;
&lt;h3 style=&quot;text-align: left;&quot;&gt;Solution&lt;/h3&gt;
&lt;div&gt;Scheduled PS/Queries are executed by the PSQUERY application engine.&amp;nbsp; The name of the query and the bind variables are passed via two run control records.&amp;nbsp; Users typically reuse an existing run control but provide different bind variable values.&amp;nbsp; I propose to introduce two tables to hold a copy of the data in these tables for each process instance.&lt;/div&gt;
&lt;div&gt;&lt;ul&gt;&lt;li&gt;&lt;a href=&quot;https://www2.go-faster.co.uk/peopletools/query_run_cntrl.htm&quot; target=&quot;_blank&quot;&gt;PS_QUERY_RUN_CNTRL&lt;/a&gt;: Scheduled Query Run Control.&amp;nbsp; This record identifies the query executed.&amp;nbsp; Rows in this table will be copied to PS_QRYRUN_CTL_HST.&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://www2.go-faster.co.uk/peopletools/query_run_parm.htm&quot; target=&quot;_blank&quot;&gt;PS_QUERY_RUN_PARM&lt;/a&gt;: Scheduled Query Run Parameters.&amp;nbsp; This record holds the bind variables and the values passed to the query.&amp;nbsp; The table contains a row for each bind variable for each execution.&amp;nbsp; Rows in this table will be copied to PS_QRYRUN_PARM_HST&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;p style=&quot;text-align: left;&quot;&gt;Two database triggers manage the history tables:&lt;/p&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;
  &lt;li&gt;A database trigger that fires when the run status of the request is updated to &#39;7&#39; (processing).&amp;nbsp; It copies rows for the current run control into two corresponding history tables.&amp;nbsp; Thus, we will have a log of every bind variable for every scheduled query.&lt;/li&gt;&lt;li&gt;A second database trigger will fire when a PSQUERY request record is deleted.&amp;nbsp; It deletes the corresponding rows from these history tables.&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;
&lt;p style=&quot;text-align: left;&quot;&gt;When a PS/Query produces a quarantined execution plan, the PSQUERY process terminates with error &lt;i&gt;ORA-56955: quarantined plan used&lt;/i&gt; (see &lt;a href=&quot;https://blog.psftdba.com/2025/02/quarantined-sql-plans-for-psqueries.html&quot;&gt;Quarantined SQL Plans for PS/Queries&lt;/a&gt;).&amp;nbsp; Now we can obtain the bind variables that resulted in attempts to execute a quarantined query execution plan.&lt;/p&gt;&lt;div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Implementation&lt;/h3&gt;&lt;p style=&quot;text-align: left;&quot;&gt;The following script (&lt;a href=&quot;https://github.com/davidkurtz/psscripts/blob/master/ps_query_run_cntrl_hist_trigger.sql&quot; target=&quot;_blank&quot;&gt;ps_query_run_cntrl_hist_trigger.sql&lt;/a&gt;) creates the tables and triggers.&amp;nbsp;&amp;nbsp;&lt;/p&gt;&lt;p style=&quot;text-align: left;&quot;&gt;&lt;/p&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Application Designer record definitions should be created for the two history tables by importing the project QRYRUN_HST (&lt;a href=&quot;https://github.com/davidkurtz/psscripts/blob/master/QRYRUN_HST.zip&quot; target=&quot;_blank&quot;&gt;download QRYRUN_HST.zip from GitHub&lt;/a&gt;).&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;/p&gt;&lt;/div&gt;
&lt;pre style=&quot;background-color: #eeeeee; font-family: &amp;quot;courier new&amp;quot;overflow: auto; line-height: 80%; text-align: left; width: 95%;&quot;&gt;&lt;span style=&quot;font-size: xx-small;&quot;&gt;REM &lt;a href=&quot;https://github.com/davidkurtz/psscripts/blob/master/ps_query_run_cntrl_hist_trigger.sql&quot; target=&quot;_blank&quot;&gt;ps_query_run_cntrl_hist_trigger.sql&lt;/a&gt;
REM 21.4.2025 - trigger and history tables to capture 
set echo on serveroutput on timi on
clear screen
spool ps_query_run_cntrl_hist_trigger
rollback;

CREATE TABLE SYSADM.PS_QRYRUN_CTL_HST 
  (PRCSINSTANCE INTEGER  DEFAULT 0 NOT NULL,
   OPRID VARCHAR2(30)  DEFAULT &#39; &#39; NOT NULL,
   RUN_CNTL_ID VARCHAR2(30)  DEFAULT &#39; &#39; NOT NULL,
   DESCR VARCHAR2(30)  DEFAULT &#39; &#39; NOT NULL,
   QRYTYPE SMALLINT  DEFAULT 1 NOT NULL,
   PRIVATE_QUERY_FLAG VARCHAR2(1)  DEFAULT &#39;N&#39; NOT NULL,
   QRYNAME VARCHAR2(30)  DEFAULT &#39; &#39; NOT NULL,
   URL VARCHAR2(254)  DEFAULT &#39; &#39; NOT NULL,
   ASIAN_FONT_SETTING VARCHAR2(3)  DEFAULT &#39; &#39; NOT NULL,
   PTFP_FEED_ID VARCHAR2(30)  DEFAULT &#39; &#39; NOT NULL) TABLESPACE PTTBL
/
CREATE UNIQUE  iNDEX SYSADM.PS_QRYRUN_CTL_HST 
ON SYSADM.PS_QRYRUN_CTL_HST (PRCSINSTANCE) TABLESPACE PSINDEX PARALLEL NOLOGGING
/
ALTER INDEX SYSADM.PS_QRYRUN_CTL_HST NOPARALLEL LOGGING
/
CREATE TABLE SYSADM.PS_QRYRUN_PARM_HST 
  (PRCSINSTANCE INTEGER  DEFAULT 0 NOT NULL,
   OPRID VARCHAR2(30)  DEFAULT &#39; &#39; NOT NULL,
   RUN_CNTL_ID VARCHAR2(30)  DEFAULT &#39; &#39; NOT NULL,
   BNDNUM SMALLINT  DEFAULT 0 NOT NULL,
   FIELDNAME VARCHAR2(18)  DEFAULT &#39; &#39; NOT NULL,
   BNDNAME VARCHAR2(30)  DEFAULT &#39; &#39; NOT NULL,
   BNDVALUE CLOB) TABLESPACE PSIMAGE2 
/
CREATE UNIQUE  iNDEX SYSADM.PS_QRYRUN_PARM_HST 
ON SYSADM.PS_QRYRUN_PARM_HST (PRCSINSTANCE, BNDNUM) TABLESPACE PSINDEX PARALLEL NOLOGGING
/
ALTER INDEX SYSADM.PS_QRYRUN_PARM_HST NOPARALLEL LOGGING
/&lt;/span&gt;&lt;/pre&gt;
&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;PSQUERY is not a restartable Application Engine program.&amp;nbsp; Therefore, there is no risk of duplicate inserts into the history tables.&lt;/li&gt;&lt;li&gt;The exception handlers in the triggers deliberately suppress any error, in case that causes the process scheduler to crash.&lt;/li&gt;&lt;/ul&gt;&lt;pre style=&quot;background-color: #eeeeee; font-family: &amp;quot;courier new&amp;quot;overflow: auto; line-height: 80%; text-align: left; width: 95%;&quot;&gt;&lt;span style=&quot;font-size: xx-small;&quot;&gt;CREATE OR REPLACE TRIGGER sysadm.query_run_cntrl_hist_ins
BEFORE UPDATE OF runstatus ON sysadm.psprcsrqst
FOR EACH ROW
WHEN (new.runstatus =&#39;7&#39; AND old.runstatus != &#39;7&#39; AND new.prcsname = &#39;PSQUERY&#39; AND new.prcstype = &#39;Application Engine&#39;)
BEGIN
  INSERT INTO PS_QRYRUN_CTL_HST 
  (PRCSINSTANCE, OPRID, RUN_CNTL_ID, DESCR ,QRYTYPE, PRIVATE_QUERY_FLAG, QRYNAME, URL, ASIAN_FONT_SETTING, PTFP_FEED_ID)
  SELECT :new.prcsinstance, OPRID, RUN_CNTL_ID, DESCR ,QRYTYPE, PRIVATE_QUERY_FLAG, QRYNAME, URL, ASIAN_FONT_SETTING, PTFP_FEED_ID 
  FROM ps_query_run_cntrl WHERE oprid = :new.oprid AND run_cntl_id = :new.runcntlid;
  
  INSERT INTO PS_QRYRUN_PARM_HST
  (PRCSINSTANCE, OPRID, RUN_CNTL_ID, BNDNUM, FIELDNAME, BNDNAME, BNDVALUE) 
  SELECT :new.prcsinstance prcsinstance, OPRID, RUN_CNTL_ID, BNDNUM, FIELDNAME, BNDNAME, BNDVALUE
  FROM ps_query_run_parm WHERE oprid = :new.oprid AND run_cntl_id = :new.runcntlid;

  EXCEPTION WHEN OTHERS THEN NULL; --exception deliberately coded to suppress all exceptions 
END;
/

CREATE OR REPLACE TRIGGER sysadm.query_run_cntrl_hist_del
BEFORE DELETE ON sysadm.psprcsrqst
FOR EACH ROW
WHEN (old.prcsname = &#39;PSQUERY&#39; AND old.prcstype = &#39;Application Engine&#39;)
BEGIN
  DELETE FROM PS_QRYRUN_CTL_HST WHERE prcsinstance = :old.prcsinstance;
  DELETE FROM PS_QRYRUN_PARM_HST WHERE prcsinstance = :old.prcsinstance;

  EXCEPTION WHEN OTHERS THEN NULL; --exception deliberately coded to suppress all exceptions
END;
/ 
show errors

spool off&lt;/span&gt;&lt;/pre&gt;&lt;div&gt;&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Example&lt;/h3&gt;&lt;p style=&quot;text-align: left;&quot;&gt;When a query is scheduled to run on the process scheduler, the bind variables are specified through this generic dialogue.&lt;/p&gt;&lt;p style=&quot;text-align: left;&quot;&gt;&lt;/p&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: left;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEgdskXHaQNj6U-OPJPcw3df2sobo3QVih6Avz4moh7ixNbucQwqI-MPcax7cjGK5H_slLaZu8EJ_lKj5oKdpy1gHy8w_yAxxtZtaHJ0hRQMMDvcssqQ0EYS8MWNYT4BYnzpeN93H741f9VpAPPtGKELdgj3p9fXhvwcyTinENfsxNJqClErf54H&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img alt=&quot;Scheduled Query Bind Variable Diaglogue&quot; data-original-height=&quot;404&quot; data-original-width=&quot;484&quot; height=&quot;267&quot; src=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEgdskXHaQNj6U-OPJPcw3df2sobo3QVih6Avz4moh7ixNbucQwqI-MPcax7cjGK5H_slLaZu8EJ_lKj5oKdpy1gHy8w_yAxxtZtaHJ0hRQMMDvcssqQ0EYS8MWNYT4BYnzpeN93H741f9VpAPPtGKELdgj3p9fXhvwcyTinENfsxNJqClErf54H=w320-h267&quot; title=&quot;Scheduled Query Bind Variable Diaglogue&quot; width=&quot;320&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;p style=&quot;text-align: left;&quot;&gt;Once the PSQUERY process has started (it immediately commits its update to &lt;a href=&quot;https://www2.go-faster.co.uk/peopletools/psprcsrqst.htm#runstatus&quot; target=&quot;_blank&quot;&gt;RUNSTATUS&lt;/a&gt;), these values are written to the new history tables.&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;p style=&quot;text-align: left;&quot;&gt;
&lt;/p&gt;&lt;pre style=&quot;background-color: #eeeeee; font-family: &amp;quot;courier new&amp;quot;overflow: auto; line-height: 80%; text-align: left; width: 95%;&quot;&gt;&lt;span style=&quot;font-size: x-small;&quot;&gt;select * from ps_qryrun_ctl_hst;&lt;/span&gt;&lt;span style=&quot;font-size: 60%;&quot;&gt;

PRCSINSTANCE OPRID    RUN_CNTL_ID  DESCR                             QRYTYPE P QRYNAME                        URL                                                ASI PTFP_FEED_ID                  
------------ -------- ------------ ------------------------------ ---------- - ------------------------------ -------------------------------------------------- --- ------------------------------
    12345678 ABCDEF   042225       Journal Line Detail - Account           1 N XXX_JRNL_LINE_DTL_ACCT         https://xxxxxxx.yyyyy.com/psp/XXXXXXX/EMPLOYEE/ERP                                   

&lt;/span&gt;&lt;span style=&quot;font-size: x-small;&quot;&gt;&lt;span&gt;select * from ps_qryrun_ctl_hst;

PRCSINSTANCE OPRID    RUN_CNTL_ID  BNDNUM FIELDNAME          BNDNAME              BNDVALUE                      
------------ -------- ------------ ------ ------------------ -------------------- ------------------------------
    12345678 ABCDEF   042225            1 bind1              BUSINESS_UNIT        354XX 
    12345678 ABCDEF   042225            2 bind2              BUSINESS_UNIT        354XX 
    12345678 ABCDEF   042225            3 FISCAL_YEAR        FISCAL_YEAR          2025 
    12345678 ABCDEF   042225            4 ACCOUNTING_PD_FROM ACCOUNTING_PD_FROM   2 
    12345678 ABCDEF   042225            5 ACCOUNTING_PD_TO   ACCOUNTING_PD_TO     2 
    12345678 ABCDEF   042225            6 bind6              ACCOUNT              23882XXXXX 
    12345678 ABCDEF   042225            7 bind7              ACCOUNT              23882XXXXX 
    12345678 ABCDEF   042225            8 bind8              ALTACCOUNT           23882XXXXX 
    12345678 ABCDEF   042225            9 bind9              ALTACCOUNT           23882XXXXX&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;
&lt;p&gt;&lt;/p&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Conclusion&lt;/h3&gt;&lt;p&gt;If the query is quarantined, PSQUERY will terminate with error &lt;i&gt;ORA-56955: quarantined plan used&lt;/i&gt;. The SQL statement can be extracted from the message log, and the execution plan can be generated with the EXPLAIN PLAN FOR command, using the bind variable values captured in the history tables.&lt;/p&gt;&lt;p&gt;Note: The &lt;a href=&quot;https://docs.oracle.com/en/database/oracle/oracle-database/19/refrn/DBA_SQL_QUARANTINE.html#https://docs.oracle.com/en/database/oracle/oracle-database/19/refrn/DBA_SQL_QUARANTINE.html##https://docs.oracle.com/en/database/oracle/oracle-database/19/refrn/DBA_SQL_QUARANTINE.html#https://docs.oracle.com/en/database/oracle/oracle-database/19/refrn/DBA_SQL_QUARANTINE.html&quot; rel=&quot;nofollow&quot; target=&quot;_blank&quot;&gt;signature&lt;/a&gt; of the SQL Quarantine directive is the exact matching signature of the SQL text (it can be generated from the SQL text with &lt;a href=&quot;https://docs.oracle.com/en/database/oracle/oracle-database/19/arpls/DBMS_SQLTUNE.html#GUID-83AA153E-37F7-4D7A-AE21-9D633005B403&quot; rel=&quot;nofollow&quot; target=&quot;_blank&quot;&gt;dbms_sqltune.sqltext_to_signature&lt;/a&gt;).&amp;nbsp; There can be multiple PLAN_HASH_VALUEs for the same signature (because there can be multiple execution plans for the same SQL). Verify that the FULL_PLAN_HASH_VALUE of the execution plan generated with the captured bind variables corresponds to the PLAN_HASH_VALUE of a SQL Quarantine directive.&lt;/p&gt;&lt;div class=&quot;blogger-post-footer&quot;&gt;&lt;a href=&quot;http://www.go-faster.co.uk/&quot;&gt;©David Kurtz&lt;/a&gt;&lt;/div&gt;</description><link>https://blog.psftdba.com/2025/04/loggingpsquerybindvalues.html</link><author>noreply@blogger.com (David Kurtz)</author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/a/AVvXsEgdskXHaQNj6U-OPJPcw3df2sobo3QVih6Avz4moh7ixNbucQwqI-MPcax7cjGK5H_slLaZu8EJ_lKj5oKdpy1gHy8w_yAxxtZtaHJ0hRQMMDvcssqQ0EYS8MWNYT4BYnzpeN93H741f9VpAPPtGKELdgj3p9fXhvwcyTinENfsxNJqClErf54H=s72-w320-h267-c" height="72" width="72"/><thr:total>0</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-25740336.post-8234900963376462750</guid><pubDate>Thu, 06 Mar 2025 07:26:00 +0000</pubDate><atom:updated>2025-06-30T18:42:17.359+01:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">Quarantined SQL Plan</category><category domain="http://www.blogger.com/atom/ns#">Resource Manager</category><title>A Resource Manager CPU Time Limit for PS/Queries Executed On-line in the PIA</title><description>&lt;p&gt;In previous posts, I have proposed:&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://blog.psftdba.com/2024/03/psftplan-sample-oracle-database.html&quot;&gt;A Sample Oracle Database Resource Manager Plan for PeopleSoft&lt;/a&gt;. It can be downloaded from Github:&amp;nbsp;&lt;a href=&quot;https://github.com/davidkurtz/psscripts/blob/master/psft_resource_plan_simple.sql&quot; target=&quot;_blank&quot;&gt;PSFT_PLAN&lt;/a&gt;.&lt;/li&gt;
  &lt;li&gt;Setting a maximum CPU time for PS/Queries run on the process scheduler (see &lt;a href=&quot;https://blog.psftdba.com/2025/02/management-of-long-running-psqueries.html&quot;&gt;Management of Long Running PS/Queries Cancelled by Resource Manager CPU Limit&lt;/a&gt;).&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;This blog looks at whether a limit could also be set for PS/Queries run via the PeopleSoft Internet Architecture (PIA).&lt;/p&gt;&lt;p&gt;The main objection to having the database terminate a PS/Query running in the PIA is that the resulting Oracle error message will be displayed in the browser without any further explanation.&amp;nbsp; Therefore, I think it is better to allow the PIA to handle termination of the query.&amp;nbsp; However, I also think that it would be prudent to prevent queries continuing to run in background after the client session has been terminated.&lt;/p&gt;
&lt;h3 style=&quot;text-align: left;&quot;&gt;Timeout Settings for PS/Query&lt;/h3&gt;&lt;p&gt;The inactivity timeout in the PIA, is delivered at 20 minutes (1200 seconds), with a warning 2 minutes earlier.&amp;nbsp; Then the user&#39;s session in the PIA is terminated.&lt;/p&gt;&lt;p&gt;There are timeouts on every Tuxedo service.&amp;nbsp; In the PeopleSoft Application Server configuration, the service timeout is specified for each type of application server/queue.&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;pre style=&quot;background-color: #eeeeee; font-family: &amp;quot;courier new&amp;quot;overflow: auto; line-height: 80%; text-align: left; width: 95%;&quot;&gt;&lt;span style=&quot;font-size: xx-small;&quot;&gt;…
[PSQRYSRV]
;=========================================================================
; Settings for PSQRYSRV
;=========================================================================

;-------------------------------------------------------------------------
; UBBGEN settings
Min Instances=3
Max Instances=3
&lt;b&gt;Service Timeout=1200&lt;/b&gt;
…&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/pre&gt;&lt;div&gt;All the services advertised on the queue are given that timeout.&amp;nbsp;&amp;nbsp;&lt;/div&gt;
&lt;pre style=&quot;background-color: #eeeeee; font-family: &amp;quot;courier new&amp;quot;overflow: auto; line-height: 80%; text-align: left; width: 95%;&quot;&gt;&lt;span style=&quot;font-size: xx-small;&quot;&gt;…
ICQuery         SRVGRP=APPSRV
                LOAD=50 PRIO=50
{QUERYSRV}
                &lt;b&gt;SVCTIMEOUT={$PSQRYSRV\Service Timeout}&lt;/b&gt;
{QUERYSRV}
{!QUERYSRV}
                SVCTIMEOUT={$PSAPPSRV\Service Timeout}
{!QUERYSRV}
                BUFTYPE=&quot;ALL&quot;
…&lt;/span&gt;&lt;/pre&gt;
&lt;p&gt;PS/Queries are run by the ICQuery service that is advertised on PSQRYSRV server if configured, and whose service timeout is also delivered set to 1200s.&amp;nbsp; Otherwise, it is advertised on PSAPPSRV whose timeout is 300s.&amp;nbsp;&amp;nbsp;&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;b&gt;&lt;i&gt;Recommendations&lt;/i&gt;&lt;/b&gt;&lt;/div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;PSQRYSRV should always be configured if PS/Queries are to be run online in an application server domain.&amp;nbsp; Partly, so that it has the longer timeout, and partly so that long running PS/Queries do not block short on-line requests.&lt;/li&gt;&lt;li&gt;Please avoid the temptation to increase either of these timeouts.&amp;nbsp; If a query needs to run for more than 20 minutes, then it should be run on the process scheduler.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;When the service timeout is reached, the Tuxedo server process will terminate.&amp;nbsp; This may not terminate the query on the database until the current fetch operation completes.&amp;nbsp; If a query involves a large group or sort operation, it can be a long time before the first fetch returns.&lt;/p&gt;&lt;p style=&quot;text-align: left;&quot;&gt;Oracle &lt;a href=&quot;https://blog.psftdba.com/2009/07/oracle-terminated-connection-timeout.html&quot;&gt;Terminated Connection Timeout&lt;/a&gt; (sometimes known as Dead Connect Detection) should be configured by setting &lt;a href=&quot;https://docs.oracle.com/en/database/oracle/oracle-database/19/netrf/parameters-for-the-sqlnet.ora.html#GUID-1070805B-0703-457C-8D2E-4EEC26193E5F&quot; target=&quot;_blank&quot;&gt;SQLNET.EXPIRE_TIME&lt;/a&gt; in the &lt;a href=&quot;https://docs.oracle.com/en/database/oracle/oracle-database/19/netrf/parameters-for-the-sqlnet.ora.html#GUID-2041545B-58D4-48DC-986F-DCC9D0DEC642&quot; target=&quot;_blank&quot;&gt;SQLNET.ORA&lt;/a&gt; file.&amp;nbsp; Then database shadow process periodically sends a probe to the otherwise idle client, and if the client doesn&#39;t respond the session terminates.&amp;nbsp; However, this process is not perfect.&lt;/p&gt;&lt;p&gt;Therefore, it would be prudent to set a timeout in the consumer group for online PS/Query sessions.&amp;nbsp; In PSFT_PLAN, that is the PSQUERY_ONLINE_GROUP consumer group.&amp;nbsp; We don&#39;t want the timeout to terminate the session before either ICQuery service times out, nor do we want the query to run on afterwards.&amp;nbsp; Therefore, the consumer group timeout should be set to the same value as the PSQRYSRV timeout, so also 1200 seconds.
&lt;/p&gt;&lt;p&gt;
&lt;/p&gt;&lt;pre style=&quot;background-color: #eeeeee; font-family: &amp;quot;courier new&amp;quot;overflow: auto; line-height: 80%; text-align: left; width: 95%;&quot;&gt;&lt;span style=&quot;font-size: xx-small;&quot;&gt;  DBMS_RESOURCE_MANAGER.CREATE_PLAN_DIRECTIVE(
    &#39;PSFT_PLAN&#39;, &#39;PSQUERY_ONLINE_GROUP&#39;
    ,mgmt_p6 =&amp;gt; 90
    ,switch_group =&amp;gt; &#39;CANCEL_SQL&#39;
    ,switch_time =&amp;gt; 1200
    ,switch_for_call =&amp;gt; TRUE
  );&lt;/span&gt;&lt;/pre&gt;&lt;p style=&quot;text-align: left;&quot;&gt;If you change the default inactivity timeout, these three settings should all be set to the same value.&lt;/p&gt;&lt;p style=&quot;text-align: left;&quot;&gt;&lt;b&gt;If you are not running on Exadata, then it is safe to set this timeout without any further configuration.&lt;/b&gt;&lt;/p&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-weight: normal;&quot;&gt;Disabling SQL Quarantine on Exadata for PS/Query While Using it Elsewhere&lt;/span&gt;&lt;/h3&gt;&lt;p&gt;However, on Exadata on 19c, the database will automatically create SQL quarantine directives for statements that exceed the CPU time limit specified in the consumer group and are terminated with error &lt;i&gt;ORA-00040: active time limit exceeded - call aborted&lt;/i&gt;. It may take a few minutes for the database to create the quarantine directive - see &lt;a href=&quot;https://support.oracle.com/epmos/faces/DocContentDisplay?id=2634990.1&quot; target=&quot;_blank&quot;&gt;2634990.1: 19c New Feature SQL Quarantine Not Work&lt;/a&gt;.&amp;nbsp; Then, the next time the same query generates the same execution plan, it will immediately be terminated with error &lt;i&gt;ORA-56955: quarantined plan used&lt;/i&gt;.&amp;nbsp; Again, we don&#39;t want such SQL errors produced in the PIA.&lt;/p&gt;&lt;p&gt;In Oracle 21c, two new parameters have been introduced to control SQL Quarantine.&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&lt;a href=&quot;https://docs.oracle.com/en/database/oracle/oracle-database/21/refrn/OPTIMIZER_CAPTURE_SQL_QUARANTINE.html&quot; target=&quot;_blank&quot;&gt;OPTIMIZER_CAPTURE_SQL_QUARANTINE&lt;/a&gt; enables or disables the automatic creation of SQL Quarantine configurations.&amp;nbsp; The default value is FALSE.&amp;nbsp;&amp;nbsp;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://docs.oracle.com/en/database/oracle/oracle-database/21/refrn/OPTIMIZER_USE_SQL_QUARANTINE.html&quot; target=&quot;_blank&quot;&gt;OPTIMIZER_USE_SQL_QUARANTINE&lt;/a&gt; determines whether the optimizer considers SQL Quarantine configurations when choosing an execution plan for a SQL statement.&amp;nbsp; The default value is TRUE.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;The parameters can be backported to Oracle 19.3 or later by applying patch 30104721 (see &lt;a href=&quot;https://support.oracle.com/epmos/faces/DocContentDisplay?id=2635030.1&quot; target=&quot;_blank&quot;&gt;Oracle Doc ID 2635030.1: 19c New Feature SQL Quarantine - How To Stop Automatic SQL Quarantine&lt;/a&gt;, and &lt;a href=&quot;https://blog.go-faster.co.uk/2025/03/sqlquarantine19cbackport.html&quot;&gt;Go-Faster Oracle Blog: New Parameters In 21c To Control Automatic SQL Quarantine Can Be Backported To 19c&lt;/a&gt;).&amp;nbsp; Both these parameters can be set at system and session level.&lt;/p&gt;&lt;p&gt;If you want to take advantage of SQL Quarantine, you have to enable it.&amp;nbsp; However, I suggest leaving the new parameters at their default values at system level, even though this means a change of behaviour in 19c when the patch is applied.&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;OPTIMIZER_CAPTURE_SQL_QUARANTINE = FALSE&lt;/li&gt;&lt;li&gt;OPTIMIZER_USE_SQL_QUARANTINE = TRUE&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;Then set both parameters to TRUE at session level in the session for&amp;nbsp;&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;PSQUERY processes run on the process scheduler.&lt;/li&gt;&lt;li&gt;SQL*Plus and SQL Developer processes.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;Ensure they are not set for PIA application server processes&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;PSAPPSRV&lt;/li&gt;&lt;li&gt;PSQRYSRV&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;/p&gt;
&lt;h3 style=&quot;text-align: left;&quot;&gt;CPU Timeouts in the Sample PSFT_PLAN Resource Plan&lt;/h3&gt;&lt;p&gt;In my sample resource plan for PeopleSoft, three consumer groups now have timeouts.&amp;nbsp; SQL Quarantine works in conjunction with consumer groups that have CPU timeouts.&amp;nbsp; I want SQL Quarantine disabled in PSQUERY_ONLINE_GROUP, but enabled in PSQUERY_BATCH_GROUP and LOW_LIMITED_GROUP.&lt;/p&gt;
&lt;pre style=&quot;background-color: #eeeeee; font-family: &amp;quot;courier new&amp;quot;overflow: auto; line-height: 80%; text-align: left; width: 95%;&quot;&gt;&lt;span style=&quot;font-size: xx-small;&quot;&gt;  DBMS_RESOURCE_MANAGER.CREATE_PLAN_DIRECTIVE(
    &#39;PSFT_PLAN&#39;, &#39;PSQUERY_ONLINE_GROUP&#39;
    ,mgmt_p6 =&amp;gt; 90
    ,switch_group =&amp;gt; &#39;CANCEL_SQL&#39;
    ,switch_time =&amp;gt; 1200 /*same as ICQuery service timeout*/
    ,switch_elapsed_time =&amp;gt; 1200
    ,switch_estimate =&amp;gt; FALSE /*do not timeout on basis of estimated time*/
    ,switch_for_call =&amp;gt; TRUE
  );
  DBMS_RESOURCE_MANAGER.CREATE_PLAN_DIRECTIVE(
    &#39;PSFT_PLAN&#39;, &#39;PSQUERY_BATCH_GROUP&#39;
    ,mgmt_p6 =&amp;gt; 1
    ,switch_group =&amp;gt; &#39;CANCEL_SQL&#39;
    ,switch_time =&amp;gt; 14400
    ,switch_estimate =&amp;gt; TRUE 
    ,switch_for_call =&amp;gt; TRUE
  );
  DBMS_RESOURCE_MANAGER.CREATE_PLAN_DIRECTIVE(
    &#39;PSFT_PLAN&#39;, &#39;LOW_LIMITED_GROUP&#39;
    ,mgmt_p8 =&amp;gt; 1
    ,switch_group =&amp;gt; &#39;CANCEL_SQL&#39;
    ,switch_time =&amp;gt; 7200
    ,switch_elapsed_time =&amp;gt; 7200
    ,switch_estimate =&amp;gt; TRUE 
    ,switch_for_call =&amp;gt; TRUE
  );&lt;/span&gt;&lt;/pre&gt;&lt;div&gt;
&lt;p&gt;The consumer groups are mapped to sessions by program name, module and action.&lt;/p&gt;
&lt;pre style=&quot;background-color: #eeeeee; font-family: &amp;quot;courier new&amp;quot;overflow: auto; line-height: 80%; text-align: left; width: 95%;&quot;&gt;&lt;span style=&quot;font-size: 60%;&quot;&gt;DBMS_RESOURCE_MANAGER.set_consumer_group_mapping (attribute =&amp;gt; DBMS_RESOURCE_MANAGER.CLIENT_PROGRAM    , value =&amp;gt; &#39;PSQRYSRV%&#39;                 , consumer_group =&amp;gt; &#39;PSQUERY_ONLINE_GROUP&#39;);
DBMS_RESOURCE_MANAGER.set_consumer_group_mapping (attribute =&amp;gt; DBMS_RESOURCE_MANAGER.MODULE_NAME_ACTION, value =&amp;gt; &#39;QUERY_MANAGER.QUERY_VIEWER&#39;, consumer_group =&amp;gt; &#39;PSQUERY_ONLINE_GROUP&#39;);

DBMS_RESOURCE_MANAGER.set_consumer_group_mapping (attribute =&amp;gt; DBMS_RESOURCE_MANAGER.MODULE_NAME       , value =&amp;gt; &#39;PSQUERY&#39;                   , consumer_group =&amp;gt; &#39;PSQUERY_BATCH_GROUP&#39;);
DBMS_RESOURCE_MANAGER.set_consumer_group_mapping (attribute =&amp;gt; DBMS_RESOURCE_MANAGER.MODULE_NAME       , value =&amp;gt; &#39;PSAE.PSQUERY.%&#39;            , consumer_group =&amp;gt; &#39;PSQUERY_BATCH_GROUP&#39;);

DBMS_RESOURCE_MANAGER.set_consumer_group_mapping (attribute =&amp;gt; DBMS_RESOURCE_MANAGER.CLIENT_PROGRAM    , value =&amp;gt; &#39;SQL Developer%&#39;            , consumer_group =&amp;gt; &#39;LOW_LIMITED_GROUP&#39;);
DBMS_RESOURCE_MANAGER.set_consumer_group_mapping (attribute =&amp;gt; DBMS_RESOURCE_MANAGER.CLIENT_PROGRAM    , value =&amp;gt; &#39;sqlplus%&#39;                  , consumer_group =&amp;gt; &#39;LOW_LIMITED_GROUP&#39;);
DBMS_RESOURCE_MANAGER.set_consumer_group_mapping (attribute =&amp;gt; DBMS_RESOURCE_MANAGER.CLIENT_PROGRAM    , value =&amp;gt; &#39;Toad%&#39;                     , consumer_group =&amp;gt; &#39;LOW_LIMITED_GROUP&#39;);&lt;/span&gt;&lt;/pre&gt;&lt;div&gt;
&lt;h3 style=&quot;text-align: left;&quot;&gt;Enabling SQL Quarantine for Scheduled PSQUERY Processes&lt;/h3&gt;&lt;p&gt;Initialisation parameters can be set for processes run on the process scheduler using a trigger on the PSPRCSRQST table.&amp;nbsp; This technique was described in a previous blog post: &lt;a href=&quot;https://blog.psftdba.com/2018/03/setting-oracle-session-parameters-for.html&quot;&gt;Setting Oracle Session Parameters for Specific Process Scheduler Processes&lt;/a&gt;.&amp;nbsp;&amp;nbsp;&lt;/p&gt;&lt;p&gt;When a PeopleSoft process is initiated on the process scheduler, the first thing it does is to update its RUNSTATUS on PSPRCSRQST to &#39;7&#39; indicating that it is processing.&amp;nbsp; The SET_PRCS_SESS_PARM_TRIG trigger fires on that transition.&amp;nbsp; It dynamically generates ALTER SESSION commands for the metadata that matches the current process.&amp;nbsp;&amp;nbsp;&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Script to create trigger &lt;a href=&quot;https://github.com/davidkurtz/psscripts/blob/master/set_prcs_sess_parm_trg.sql&quot; target=&quot;_blank&quot;&gt;set_prcs_sess_parm_trg.sql&lt;/a&gt;&amp;nbsp;&lt;/li&gt;&lt;li&gt;Example script to create metadata: &lt;a href=&quot;https://github.com/davidkurtz/psscripts/blob/master/set_prcs_sess_parm.sql&quot; target=&quot;_blank&quot;&gt;set_prcs_sess_parm.sql&lt;/a&gt;.&amp;nbsp; It includes the following statement that will create metadata for PSQUERY to set the new SQL Quarantine parameters if they are available and if running on Exadata.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;/p&gt;
&lt;pre style=&quot;background-color: #eeeeee; font-family: &amp;quot;courier new&amp;quot;overflow: auto; line-height: 80%; text-align: left; width: 95%;&quot;&gt;&lt;span style=&quot;font-size: xx-small;&quot;&gt;----------------------------------------------------------------------------------------------------
--Settings for SQL Quarantine in PSQuery on Exadata -- note SQL checks that parameter is valid
----------------------------------------------------------------------------------------------------
DELETE FROM sysadm.PS_PRCS_SESS_PARM where prcsname = &#39;PSQUERY&#39; 
AND param_name IN(&#39;optimizer_capture_sql_quarantine&#39;,&#39;optimizer_use_sql_quarantine&#39;)
/
----------------------------------------------------------------------------------------------------
INSERT INTO sysadm.PS_PRCS_SESS_PARM (prcstype, prcsname, oprid, runcntlid, keyword, param_name, parmvalue)
with n as ( --returns a row on Exadata only
SELECT  COUNT(DISTINCT cell_name) num_exadata_cells
FROM    v$cell
HAVING  COUNT(DISTINCT cell_name)&amp;gt;0
), x (param_name, keyword, parmvalue) as ( --returns rows if parameters available
select  name, &#39;SET&#39;, &#39;TRUE&#39; 
from    v$parameter, n
where   name IN(&#39;optimizer_capture_sql_quarantine&#39;,&#39;optimizer_use_sql_quarantine&#39;)
), y (prcstype, prcsname, oprid, runcntlid) as (
select  prcstype, prcsname, &#39; &#39;, &#39; &#39; 
from    ps_prcsdefn
where   prcsname = &#39;PSQUERY&#39;
)
select  y.prcstype, y.prcsname, y.oprid, y.runcntlid, x.keyword, x.param_name, x.parmvalue
from    x,y
/&lt;/span&gt;&lt;/pre&gt;&lt;div&gt;
&lt;h3 style=&quot;text-align: left;&quot;&gt;Enabling/Disabling SQL Quarantine for Other Processes at Logon&lt;/h3&gt;&lt;p&gt;This is done with a AFTER LOGON trigger and another metadata data table that is similar to the scheduled process trigger.&amp;nbsp; The mappings in the metadata in this script must match the mappings for the consumer groups where automatic SQL quarantine capture is required.&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Script to create trigger and metadata:&amp;nbsp;&lt;a href=&quot;https://github.com/davidkurtz/psscripts/blob/master/set_sess_parm_trg.sql&quot; target=&quot;_blank&quot;&gt;set_sess_parm_trg.sql&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;/p&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;TL;DR&lt;/h3&gt;&lt;p&gt;&lt;/p&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Set the timeout for the consumer group for PS/Query to be the same value as the ICQuery Tuxedo service timeout (usually also the same value as the PIA inactivity timeout).&lt;/li&gt;&lt;li&gt;SQL Quarantine is only available on Exadata.&amp;nbsp; On other platforms, the query just runs to the CPU timeout, by which time the ICQuery service has already timed out.&lt;/li&gt;&lt;li&gt;If on Exadata, then&amp;nbsp;&lt;/li&gt;&lt;ul&gt;&lt;li&gt;Apply patch 30104721 on 19c to backport new 21c parameters.&lt;/li&gt;&lt;li&gt;Leave the quarantine parameters at their default values at system level&lt;/li&gt;&lt;li&gt;Set both parameters to TRUE at session level in the session for&amp;nbsp;&lt;/li&gt;&lt;ul&gt;&lt;ul&gt;&lt;li&gt;PSQUERY processes run on the process scheduler.&lt;/li&gt;&lt;li&gt;SQL*Plus and SQL Developer processes.&lt;/li&gt;&lt;/ul&gt;&lt;/ul&gt;&lt;/ul&gt;&lt;/ul&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;blogger-post-footer&quot;&gt;&lt;a href=&quot;http://www.go-faster.co.uk/&quot;&gt;©David Kurtz&lt;/a&gt;&lt;/div&gt;</description><link>https://blog.psftdba.com/2025/03/resourcemanagericquerytimelimit.html</link><author>noreply@blogger.com (David Kurtz)</author><thr:total>0</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-25740336.post-836179252447730101</guid><pubDate>Mon, 24 Feb 2025 17:46:00 +0000</pubDate><atom:updated>2025-02-24T22:20:46.822+00:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">PS/Query</category><category domain="http://www.blogger.com/atom/ns#">Quarantined SQL Plan</category><category domain="http://www.blogger.com/atom/ns#">Resource Manager</category><title>Quarantined SQL Plans for PS/Queries</title><description>&lt;p&gt;This follows on from my previous post,&amp;nbsp;&lt;a href=&quot;https://blog.psftdba.com/2025/02/management-of-long-running-psqueries.html&quot;&gt;Management of Long Running PS/Queries Cancelled by Resource Manager&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;From 19c, on Engineered Systems only (such as Exadata and Exadata Cloud Service) the &lt;i&gt;&#39;Oracle Database automatically quarantines the plans for SQL statements terminated by … the Resource Manager for exceeding resource limits.&lt;/i&gt;&lt;/p&gt;&lt;p&gt;&lt;i&gt;The Resource Manager can set a maximum estimated execution time for a SQL statement, for example, 20 minutes. If a statement execution exceeds this limit, then the Resource Manager terminates the statement. However, the statement may run repeatedly before being terminated, wasting 20 minutes of resources each time it is executed.&lt;/i&gt;&lt;/p&gt;&lt;p&gt;&lt;i&gt;Starting in Oracle Database 19c, if a statement exceeds the specified resource limit, then the Resource Manager terminates the execution and “quarantines” the plan. To quarantine the plan means to put it on a blacklist of plans that the database will not execute. Note that the plan is quarantined, not the statement itself.&#39;&lt;/i&gt;&lt;/p&gt;&lt;p style=&quot;text-align: right;&quot;&gt;&lt;i&gt;[Oracle SQL Tuning Guide:&amp;nbsp;&lt;a href=&quot;https://docs.oracle.com/en/database/oracle/oracle-database/19/tgsql/query-optimizer-concepts.html#GUID-E76CD42A-3D1B-4A0A-82F6-5270D3B32E7A&quot;&gt;4.7 About Quarantined SQL Plans&lt;/a&gt;]&lt;/i&gt;&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;See also Tim Hall&#39;s article: &lt;a href=&quot;https://oracle-base.com/articles/19c/sql-quarantine-19c&quot; target=&quot;_blank&quot;&gt;SQL Quarantine in Oracle Database 19c&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;When an attempt is made to execute a quarantined execution plan an error is produced: &lt;i&gt;ORA-56955: quarantined plan used.&lt;/i&gt;&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;Oracle does not log timed-out or quarantined queries.&amp;nbsp; On V$SQL and V$SQLSTAT, AVOIDED_EXECUTIONS&amp;nbsp;records the number of times a SQL query has been prevented from running.&amp;nbsp; However, this will not stay in the library cache long on a PeopleSoft system, due to the continual parse of dynamically generated SQL statements.&amp;nbsp; As of Oracle 19.20, it is not recorded in AWR on DBA_HIST_SQLSTAT.&amp;nbsp;&amp;nbsp;&lt;/p&gt;&lt;p&gt;If an error condition occurs during a PSQUERY process run on the process scheduler, the process terminates with an error.&amp;nbsp; The SQL statement and the error message are recorded in the Application Engine message log.&amp;nbsp; As demonstrated in the previous blog, we can detect such failures by inspecting the message log of the PSQUERY process that did not finish successfully (ie. it has an end time, but the run status does not indicate success).&lt;/p&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Matching Quarantine Directives to Cancelled Queries&lt;/h3&gt;&lt;p&gt;Quarantine directives are visible via &lt;a href=&quot;https://docs.oracle.com/en/database/oracle/oracle-database/19/refrn/DBA_SQL_QUARANTINE.html&quot; target=&quot;_blank&quot;&gt;DBA_SQL_QUARANTINE&lt;/a&gt;, including SQL text and execution plan hash value.&lt;/p&gt;&lt;p&gt;It would be useful to know which quarantine directive relates to which query.&amp;nbsp; However, it is not easy to match the SQL in the PeopleSoft message log entries with that in the quarantine entries. The SQL text in the message log can have multiple spaces. These are stripped out in the DBA_SQL_QUARANTINE view where the normalised SQL statement is visible.&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;The timestamp of creation and last execution of the quarantine directive is stored on it, but matching these to when the query was running can result in false positives.&lt;/p&gt;
&lt;p&gt;Also, you cannot tell which quarantine directive was created by which consumer group.&amp;nbsp; The maximum CPU timeout is recorded on DBA_SQL_QUARANTINE.&amp;nbsp; In my example, it is only possible to distinguish the originating consumer group because the two consumer groups happen to have different timeouts.&lt;/p&gt;&lt;p&gt;A method that matches exactly, but only returns partial rows is to:&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Obtain ASH data for queries terminated by the resource manager.&amp;nbsp; It can be matched by timestamp, MODULE, and ACTION (provided that EnableAEMonitoring is enabled).&lt;/li&gt;&lt;ul&gt;&lt;li&gt;Profile the ASH to find the statement that took the longest during each PSQUERY process, and that is almost certain to be the SQL query.&amp;nbsp; Thus obtaining the SQL_ID, SQL Plan Hash Value and consumer group ID.&amp;nbsp; It is also possible to determine the total database time for the query, and the database time spent on CPU.&lt;/li&gt;&lt;li&gt;The consumer group name can then be obtained from DBA_HIST_RSRC_CONSUMER_GROUP&lt;/li&gt;&lt;/ul&gt;
  &lt;li&gt;Obtain the SQL text for the long-running query.&amp;nbsp; It would also&amp;nbsp;have to be captured by an AWR snapshot.&amp;nbsp; This does often occur because it was a long-running SQL, but it is not certain.&lt;/li&gt;&lt;li&gt;The signature for the SQL statement (not the force-matching signature) can be derived using the SQLTEXT_TO_SIGNATURE function in DBMS_SQLTUNE.&amp;nbsp; This can be matched to the signature recorded in DBA_SQL_QUARANTINE.&lt;/li&gt;&lt;li&gt;You can have multiple quarantine directives for the same signature (i.e. the same SQL statement), each with a different plan hash value.&amp;nbsp;&amp;nbsp;&lt;/li&gt;&lt;ul&gt;&lt;li&gt;NB: The plan hash value on DBA_SQL_QUARANTINE is the adaptive plan hash value (with all of its possible plan alternatives), and therefore it matches SQL_FULL_PLAN_HASH_VALUE in the ASH data, and not SQL_PLAN_HASH_VALUE (the plan that actually executed).&lt;/li&gt;&lt;/ul&gt;&lt;/ul&gt;Note that&lt;br /&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;
  &lt;li&gt;When a query executes until timed-out, producing ORA-00040, you usually&amp;nbsp;can find the SQL statement in the AWR repository and so generate the signature to exactly match the quarantine record.&lt;/li&gt;
    &lt;li&gt;When an attempt is made to run a quarantined statement and execution plan, you usually cannot find the SQL statement because it hasn&#39;t run for long enough to produce an ASH sample.&amp;nbsp; Even when it has, you also have to rely on the statement having been captured previously by AWR.&amp;nbsp; Those conditions only come together occasionally.&lt;/li&gt;&lt;/ul&gt;This matching process is done by this query:&amp;nbsp;&lt;a href=&quot;https://github.com/davidkurtz/psscripts/blob/master/message_log_checker-psquery2.sql&quot; target=&quot;_blank&quot;&gt;message_log_checker-psquery2.sql&lt;/a&gt;.&amp;nbsp; Below is a sample output.&amp;nbsp;&amp;nbsp;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;We can see the quarantine directives that were created when the resource manager cancelled a query, raising error &lt;i&gt;ORA-00040: active time limit exceeded - call aborted&lt;/i&gt;.&amp;nbsp;&amp;nbsp;&lt;/li&gt;&lt;li&gt;However, where quarantine directives have prevented SQL from executing, raising error &lt;i&gt;ORA-56955: quarantined plan used&lt;/i&gt;, the ASH data from the event that originally created the directive has since been purged, so we cannot use it to match directives.&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;&lt;pre style=&quot;background-color: #eeeeee; font-family: &amp;quot;courier new&amp;quot;overflow: auto; line-height: 60%; text-align: left; width: 95%;&quot;&gt;&lt;span style=&quot;font-size: 60%;&quot;&gt;Mon Feb 24                                                                                                                                                           page    1
                                                     PS/Queries terminated by Resource Manager/quarantined Execution Plan

                                                          Public/                                                              ASH
                                                          Private                                Ru Oracle      Exec    ASH    CPU Message Log
     P.I. DBNAME  OPRID    RUNCNTLID                      Query   QRYNAME                        St Err. #      Secs   Secs   Secs Date/Time Stamp              SQL_ID
--------- ------- -------- ------------------------------ ------- ------------------------------ -- --------- ------ ------ ------ ---------------------------- -------------
   SQL Plan   Full Plan                                                                                      CPU
 Hash Value  Hash Value Consumer Group Name                   SIGNATURE Quarantine Name                      Time  Quarantine Created           Quarantine Last Executed
----------- ----------- ------------------------- --------------------- ------------------------------------ ----- ---------------------------- ----------------------------
…
 31452465 FSPROD  USR0001  GBR_JRNL_LN_DETAIL_ACCT        Public  GBR_JRNL_LN_DETAIL_ACCT        10 ORA-56955     36     10     10 20-FEB-25 06.28.03.578218 PM 0wm9g6xkys12h
 4009529842   653370716 PSQUERY_BATCH_GROUP         5584654620166156419

 31451318 FSPROD  USR0002  GBR_JRNL_LN_DETAIL_ACCT        Public  GBR_JRNL_LN_DETAIL_ACCT        10 ORA-56955     36               20-FEB-25 02.36.38.590841 PM


 31451292 FSPROD  USR0002  GBR_JRNL_LN_DETAIL_ACCT        Public  GBR_JRNL_LN_DETAIL_ACCT        10 ORA-56955     36               20-FEB-25 02.30.51.777351 PM


 31438602 FSPROD  USR0003  1                              Private DK_GBR_GL_DETAIL_NEW           10 ORA-00040  28316  28275  14203 18-FEB-25 11.39.19.502412 PM 5qrbrf775whky
 3446094907  3491308607 PSQUERY_BATCH_GROUP        16266909742923016361 SQL_QUARANTINE_f3gxc76u48u59d019243f 14400 18-FEB-25 11.49.33.091081 PM

 31437925 FSPROD  USR0004  16                             Private TB_TEST2                       10 ORA-00040  17684  17654  17541 18-FEB-25 06.09.14.060615 PM 06xqrgj18wp05
 4256462904  2062199471 PSQUERY_BATCH_GROUP         6341132966559464532 SQL_QUARANTINE_5h01uuscnrg2n7aeaaaaf 14400 18-FEB-25 06.17.20.679769 PM

 31437907 FSPROD  USR0004  16                             Private TB_TEST2                       10 ORA-00040  17694  17695  17592 18-FEB-25 06.04.05.942470 PM 4yurn75y2p0t2
 3232504707   121066138 PSQUERY_BATCH_GROUP         4966087806133732884 SQL_QUARANTINE_49usqjjc001hn0737529a 14400 18-FEB-25 06.17.24.869185 PM
 …&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;blogger-post-footer&quot;&gt;&lt;a href=&quot;http://www.go-faster.co.uk/&quot;&gt;©David Kurtz&lt;/a&gt;&lt;/div&gt;</description><link>https://blog.psftdba.com/2025/02/quarantined-sql-plans-for-psqueries.html</link><author>noreply@blogger.com (David Kurtz)</author><thr:total>0</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-25740336.post-5808793442949422438</guid><pubDate>Thu, 20 Feb 2025 11:46:00 +0000</pubDate><atom:updated>2025-03-06T07:31:23.962+00:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">PS/Query</category><category domain="http://www.blogger.com/atom/ns#">Resource Manager</category><title>Management of Long Running PS/Queries Cancelled by Resource Manager CPU Limit</title><description>&lt;p&gt;I have written previously about using the Oracle database resource manager to prioritise the allocation of CPU to different processes in a PeopleSoft system.&amp;nbsp; I proposed a sample resource plan that can be used as a starting point to build a resource plan that meets a system&#39;s specific objectives and requirements.&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;see&amp;nbsp;&lt;a href=&quot;https://blog.psftdba.com/2024/03/psftplan-sample-oracle-database.html&quot;&gt;PSFT_PLAN: A Sample Oracle Database Resource Manager Plan for PeopleSoft&lt;/a&gt;&lt;/li&gt;&lt;li&gt;The script to implement the sample&amp;nbsp;&lt;a href=&quot;https://github.com/davidkurtz/psscripts/blob/master/psft_resource_plan_simple.sql&quot; target=&quot;_blank&quot;&gt;PSFT_PLAN&lt;/a&gt;&amp;nbsp;resource plan is available from GitHub.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;This post looks at&amp;nbsp;&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;How to configure the resource manager to cancel long-running queries,&lt;/li&gt;
  &lt;li&gt;What happens when it does and what PeopleSoft users experience,&lt;/li&gt;
  &lt;li&gt;How the system administrators can monitor such queries,&lt;/li&gt;
  &lt;li&gt;What action could they take?&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;/p&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Configuring SQL Cancellation in a Resource Manager Consumer Group&lt;/h3&gt;&lt;div&gt;&lt;div&gt;The sample resource plan, PSFT_PLAN, contains various server consumer groups.&amp;nbsp; It relies upon MODULE and ACTION being set by enabling PeopleSoft instrumentation (&lt;a href=&quot;https://docs.oracle.com/cd/E92519_02/pt856pbr3/eng/pt/tadm/task_MonitoringPeopleSoftDatabaseConnections-077989.html#u711504d9-3503-43cc-90ae-8c0b800be515_is2&quot; target=&quot;_blank&quot;&gt;EnableAEMonitoring&lt;/a&gt;=1) and/or the &lt;a href=&quot;https://github.com/davidkurtz/psscripts/blob/master/psftapi.sql#L301&quot; target=&quot;_blank&quot;&gt;psftapi_store_prcsinstance&lt;/a&gt; trigger on &lt;a href=&quot;https://www2.go-faster.co.uk/peopletools/psprcsrqst.htm&quot; target=&quot;_blank&quot;&gt;PSPRCSRQST&lt;/a&gt; (see&amp;nbsp;&lt;a href=&quot;https://blog.psftdba.com/2019/03/effective-peoplesoft-performance.html&quot;&gt;Effective PeopleSoft Performance Monitoring&lt;/a&gt;),&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;PSQUERY_BATCH_GROUP&lt;/li&gt;&lt;ul&gt;&lt;li&gt;Applies to scheduled PSQUERY Application Engine Programs&lt;/li&gt;&lt;li&gt;Limited to 4 hours on CPU (or estimated at &amp;gt;= 4 hours)&lt;/li&gt;&lt;/ul&gt;&lt;li&gt;PSQUERY_ONLINE&lt;/li&gt;&lt;ul&gt;&lt;li&gt;Applies to queries run online via the PeopleSoft Internet Architecture (PIA).&lt;/li&gt;&lt;li&gt;There is no resource manager limit for this consumer group.&amp;nbsp;&amp;nbsp;&lt;/li&gt;&lt;ul&gt;&lt;li&gt;The PIA has a session timeout (default 20 minutes).&amp;nbsp;&amp;nbsp;&lt;/li&gt;&lt;li&gt;The ICQuery Tuxedo service that runs the queries also has a timeout (default 20 minutes)&lt;/li&gt;&lt;/ul&gt;&lt;li&gt;When the resource manager cancels a SQL call, it simply raises an Oracle error that appears in a PIA message box without further explanation.&amp;nbsp; It is better to let the PIA timeouts handle online queries in a more controlled fashion.&lt;/li&gt;&lt;li&gt;To prevent queries continuing to run on the database after the client has been terminated&lt;/li&gt;&lt;ul&gt;&lt;li&gt;Enable&amp;nbsp;&lt;a href=&quot;https://blog.psftdba.com/2009/07/oracle-terminated-connection-timeout.html&quot; target=&quot;_blank&quot;&gt;Terminated Connection Timeout&lt;/a&gt; by setting SQLNET.EXPIRE_TIME to 5 to 10 minutes in SQLNET.ORA.&amp;nbsp;&lt;/li&gt;&lt;li&gt;Set timeout CPU timeout to match PIA/ICQuery timeout.&amp;nbsp; However, if on Exadata only enable SQL Quarantine for scheduled PS/Queries (see&amp;nbsp;&lt;a href=&quot;https://blog.psftdba.com/2025/03/resourcemanagericquerytimelimit.html&quot;&gt;A Resource Manager CPU Time Limit for PS/Queries Executed On-line in the PIA&lt;/a&gt;)&lt;/li&gt;&lt;/ul&gt;&lt;/ul&gt;&lt;/ul&gt;&lt;/div&gt;&lt;/div&gt;
    &lt;li&gt;LOW_LIMITED_GROUP&lt;/li&gt;&lt;blockquote style=&quot;border: none; margin: 0px 0px 0px 40px; padding: 0px;&quot;&gt;&lt;li style=&quot;text-align: left;&quot;&gt;Applies to SQL*Plus, SQL Developer and Toad.&lt;/li&gt;&lt;/blockquote&gt;&lt;blockquote style=&quot;border: none; margin: 0px 0px 0px 40px; padding: 0px;&quot;&gt;&lt;li style=&quot;text-align: left;&quot;&gt;Limited to 2 hours on CPU (or estimated at &amp;gt;= 2 hours)&lt;/li&gt;&lt;/blockquote&gt;&lt;b&gt;&lt;div&gt;&lt;b&gt;&lt;br /&gt;&lt;/b&gt;&lt;/div&gt;Recommendations:&amp;nbsp;&lt;/b&gt;&lt;br /&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Users should generally be encouraged to schedule queries that will take more than a few minutes to run on the process scheduler.&amp;nbsp;&amp;nbsp;&lt;/li&gt;&lt;li&gt;Resist the temptation to increase either the PIA inactivity timeout or ICQuery service timeout from the delivered setting of 20 minutes.&amp;nbsp;&lt;/li&gt;&lt;/ul&gt;&lt;h4 style=&quot;text-align: left;&quot;&gt;Plan Directives&lt;/h4&gt;&lt;div&gt;Plan directives are created with DBMS_RESOURCE_MANAGER.&lt;a href=&quot;https://docs.oracle.com/en/database/oracle/oracle-database/19/arpls/DBMS_RESOURCE_MANAGER.html#GUID-8EC6C735-338D-46D4-B346-AD16D0622B30)&quot; target=&quot;_blank&quot;&gt;CREATE_PLAN_DIRECTIVE&lt;/a&gt;.&amp;nbsp;&lt;/div&gt;
&lt;pre style=&quot;background-color: #eeeeee; font-family: &amp;quot;courier new&amp;quot;overflow: auto; line-height: 80%; text-align: left; width: 95%;&quot;&gt;&lt;span style=&quot;font-size: small;&quot;&gt;  DBMS_RESOURCE_MANAGER.CREATE_PLAN_DIRECTIVE(
    &#39;PSFT_PLAN&#39;, &#39;PSQUERY_BATCH_GROUP&#39;
    ,mgmt_p6 =&amp;gt; 1
    ,switch_group =&amp;gt; &#39;CANCEL_SQL&#39;
    ,switch_time =&amp;gt; 14400
    ,switch_estimate =&amp;gt; TRUE 
    ,switch_for_call =&amp;gt; TRUE
  );

  DBMS_RESOURCE_MANAGER.CREATE_PLAN_DIRECTIVE(
    &#39;PSFT_PLAN&#39;, &#39;PSQUERY_ONLINE_GROUP&#39;
    ,mgmt_p6 =&amp;gt; 90
  );

  DBMS_RESOURCE_MANAGER.CREATE_PLAN_DIRECTIVE(
    &#39;PSFT_PLAN&#39;, &#39;LOW_LIMITED_GROUP&#39;
    ,mgmt_p8 =&amp;gt; 1
    ,switch_group =&amp;gt; &#39;CANCEL_SQL&#39;
    ,switch_time =&amp;gt; 7200
    ,switch_elapsed_time =&amp;gt; 7200
    ,switch_estimate =&amp;gt; TRUE 
    ,switch_for_call =&amp;gt; TRUE
  );&lt;/span&gt;&lt;/pre&gt;&lt;div&gt;Four parameters control cancellation behaviour.&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;SWITCH_GROUP specifies the consumer group to which the session is switched when a switch condition is met.&amp;nbsp; If the group switches to CANCEL_SQL the current call is cancelled, raising error ORA-00400.&lt;/li&gt;&lt;li&gt;SWITCH_TIME specified the number of seconds on CPU (not elapsed time).&lt;/li&gt;&lt;li&gt;If SWITCH_ESTIMATE is true, the resource manager also switches group if the estimated run time is greater than the switch time&lt;/li&gt;&lt;li&gt;SWITCH_FOR_CALL is set to true so that if the consumer group is switched, it is then restored to the original consumer group at the end of the top call.&amp;nbsp; Thus a persistent session is not permanently switched. This is important if switching an application engine server (PSAESRV) session.&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Cancellation Behaviour&lt;/h3&gt;&lt;div&gt;The resource manager can cancel long-running queries in these consumer groups raising &lt;i&gt;ORA-00040: active time limit exceeded - call aborted&amp;nbsp;&lt;/i&gt;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;The query may be cancelled immediately because the estimated execution time is greater than the limit.&amp;nbsp;&amp;nbsp;&lt;/li&gt;&lt;li&gt;Otherwise, it is quite likely to run for an elapsed time that is greater than the CPU time limit. The database session is only on CPU if the event reported on the session is NULL.&amp;nbsp; Otherwise, it is doing something else.&lt;/li&gt;&lt;li&gt;Some time will be consumed in the client process, during which the database session will be idle waiting for the next fetch request from the client.&amp;nbsp; This is usually reported as event &lt;i&gt;SQL*Net message from client&lt;/i&gt;.&lt;/li&gt;&lt;li&gt;Some of the database time may not be on CPU because it may be doing something else, such as physical IO.&lt;/li&gt;&lt;li&gt;The database session may be held back by the resource manager allocating CPU to higher priority processes,&amp;nbsp; in which case again&amp;nbsp;the session will&amp;nbsp;not be on CPU, and will report being on event &lt;i&gt;resmgr: cpu quantum.&lt;/i&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Querying the PeopleSoft Message Log&lt;/h3&gt;&lt;div&gt;The full message text is stored in multiple pieces in PS_MESSAGE_LOGPARM and must be reconstructed so that it can be searched for the error code.&amp;nbsp; I demonstrated this technique in another blog post: &lt;a href=&quot;https://blog.psftdba.com/2023/04/querying-peoplesoft-message-log-with-sql.html&quot; target=&quot;_blank&quot;&gt;Querying the PeopleSoft Message Log with SQL&lt;/a&gt;.&lt;/div&gt;&lt;p style=&quot;text-align: left;&quot;&gt;For this analysis, I have made some alterations to the message log query (see&amp;nbsp;&lt;a href=&quot;https://github.com/davidkurtz/psscripts/blob/master/message_log_checker-psquery.sql&quot; target=&quot;_blank&quot;&gt;message_log_checker-psquery.sql&lt;/a&gt;).&lt;/p&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;This query is restricted to messages generated by PSQUERY processes that did not run to success (not run status 9).&lt;/li&gt;&lt;li&gt;PeopleSoft messages are typically defined with up to 9 substitution variables, but long SQL statements can have many more entries in PS_MESSAGE_LOGPARM.&amp;nbsp; So the PL/SQL function in this query simply appends any additional log parameter rows beyond the 9 substitution variables to the end of the generated string.&lt;/li&gt;&lt;li&gt;Once the message has been generated we can look for one of the error messages associated with the resource manager terminating a query:&lt;/li&gt;&lt;ul&gt;&lt;li&gt;ORA-00040: active time limit exceeded - call aborted&lt;/li&gt;&lt;li&gt;ORA-56955: quarantined plan used&lt;/li&gt;&lt;/ul&gt;&lt;/ul&gt;&lt;/div&gt;&lt;p style=&quot;text-align: left;&quot;&gt;It is necessary to filter by message number because even in PS/Query users can write invalid SQL that produces other error messages.&amp;nbsp; However, all this text processing for each row retrieved makes the query quite slow.&lt;/p&gt;&lt;div&gt;&lt;div&gt;Here is an example output.&amp;nbsp;
User USR001 ran a private query MY_TEST2 with run control 42.&amp;nbsp; It ran for 20772s (5h 46m) until terminated by the resource manager.&amp;nbsp; As explained above, the 4-hour limit is on CPU time that will be less than the elapsed time.&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;pre style=&quot;background-color: #eeeeee; font-family: &amp;quot;courier new&amp;quot;overflow: auto; line-height: 80%; text-align: left; width: 95%;&quot;&gt;&lt;span style=&quot;font-size: 70%;&quot;&gt;                                                         Public/
                                                          Private                                Ru   Exec                              Msg Msg  Msg
     P.I. DBNAME  OPRID    RUNCNTLID                      Query   QRYNAME                        St   Secs DTTM_STAMP_SEC               Seq Set  Nbr
--------- ------- -------- ------------------------------ ------- ------------------------------ -- ------ ---------------------------- --- --- ----
MSG
---------------------------------------------------------------------------------------------------------------------------------------------------- 
 12395311 FSPROD  USR001   42                             Private MY_TEST2                       10  20772 10-FEB-25 04.41.47.384694 PM   1  65   30
File: C:\PT860P13B_2403250500-retail\peopletools\src\pssys\qpm.cppSQL error. Stmt #: 8526  Error Position: 189  Return: 40 - ORA-00040: active time
 limit exceeded - call abortedFailed SQL stmt: SELECT A.LEDGER, A.FISCAL_YEAR, A.BUSINESS_UNIT, …
                                                                                                           10-FEB-25 04.41.47.421800 PM   2  50  380
Error in running query because of SQL Error, Code=40, Message=ORA-00040: active time limit exceeded - call aborted
…&lt;/span&gt;&lt;/pre&gt;&lt;div&gt;From Oracle 19c on Exadata, timed-out statements are automatically quarantined.&amp;nbsp; If a quarantined statement is run and a quarantined execution plan is generated, then error ORA-56955 is generated immediately.&amp;nbsp; Therefore, it can also be detected in the logs.&amp;nbsp; The query searches for both messages.&lt;/div&gt;&lt;pre style=&quot;background-color: #eeeeee; font-family: &amp;quot;courier new&amp;quot;overflow: auto; line-height: 80%; text-align: left; width: 95%;&quot;&gt;&lt;span style=&quot;font-size: 70%;&quot;&gt;                                                          &lt;/span&gt;&lt;span style=&quot;font-size: xx-small;&quot;&gt;Public/
                                                          Private                                Ru   Exec                              Msg Msg  Msg
     P.I. DBNAME  OPRID    RUNCNTLID                      Query   QRYNAME                        St   Secs DTTM_STAMP_SEC               Seq Set  Nbr
--------- ------- -------- ------------------------------ ------- ------------------------------ -- ------ ---------------------------- --- --- ----
MSG
----------------------------------------------------------------------------------------------------------------------------------------------------
 12319513 FSPROD  USR002   Transactions                   Public  GBR_JRNL_LINE_DTL_ACCT         10     25 13-FEB-25 11.13.35.746644 PM   1  65   30
File: C:\PT860P13B_2403250500-retail\peopletools\src\pssys\qpm.cppSQL error. Stmt #: 8526  Error Position: 2783  Return: -8581 - ORA-56955: quarant
ined plan usedFailed SQL stmt: SELECT A.FISCAL_YEAR, A.ACCOUNTING_PERIOD, A.JRNL_HDR_STATUS, B.LED
…
                                                                                                           13-FEB-25 11.13.35.814112 PM   2  50  380
Error in running query because of SQL Error, Code=-8581, Message=ORA-56955: quarantined plan used&lt;/span&gt;&lt;/pre&gt;&lt;div&gt;I discuss automatic &lt;a href=&quot;https://blog.psftdba.com/2025/02/quarantined-sql-plans-for-psqueries.html&quot;&gt;SQL quarantine for PS/Query&lt;/a&gt; in a subsequent blog.&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Opinion&lt;/h3&gt;&lt;div&gt;So far, I have explained how to set a maximum CPU time limit for PS/Queries in a resource manager consumer group and how to detect cancelled PS/Queries by examining the message log.&lt;/div&gt;&lt;p style=&quot;text-align: left;&quot;&gt;The final stage is to close the feedback loop and go back to the users producing the queries, find out what they are trying to do, and why the queries are running for such a long time.&lt;/p&gt;&lt;div class=&quot;blogger-post-footer&quot;&gt;&lt;a href=&quot;http://www.go-faster.co.uk/&quot;&gt;©David Kurtz&lt;/a&gt;&lt;/div&gt;</description><link>https://blog.psftdba.com/2025/02/management-of-long-running-psqueries.html</link><author>noreply@blogger.com (David Kurtz)</author><thr:total>0</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-25740336.post-6087110798960449253</guid><pubDate>Tue, 28 Jan 2025 17:36:00 +0000</pubDate><atom:updated>2025-01-28T21:41:49.404+00:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">%Table</category><category domain="http://www.blogger.com/atom/ns#">Application Engine</category><category domain="http://www.blogger.com/atom/ns#">SQL Profiles</category><category domain="http://www.blogger.com/atom/ns#">Temporary Records</category><title>SQL Profiles for Application Engine Steps that Reference Temporary Records via the %Table() Meta-SQL</title><description>&lt;p&gt;Oracle generally advises using SQL Profiles/Patches/Baselines rather than introducing hints into application code.&amp;nbsp; Using one of these forms of plan stability saves you from having to alter the code, and then having to test and verify that the change is functionally neutral and release it to production. It also saves repeating that whole process if you ever choose to remove or change the hint.&amp;nbsp;&amp;nbsp;&lt;/p&gt;&lt;p&gt;I generally use SQL profiles with PeopleSoft, because they are the only plan stability mechanism that can force match a statement.&amp;nbsp; That is to say, a force-matching SQL profile will match other SQL statements that differ only in embedded literal values.&amp;nbsp; PeopleSoft code makes extensive use of literal values. SQL patches and baselines only exactly match statements (so they match the SQL ID and not the force matching signature). Note that SQL Profiles require that the Tuning Pack is licenced, and that is only available on Enterprise Edition or Oracle.&lt;/p&gt;&lt;p&gt;I have &lt;a href=&quot;https://blog.psftdba.com/2020/12/tuning-dynamically-generated-sql-from.html&quot; target=&quot;_blank&quot;&gt;written previously about using SQL Profiles to inject hints into dynamically generated code&lt;/a&gt;.&amp;nbsp; If any part of the SQL (other than literal values) changes, then a different SQL profile is needed for each variation.&amp;nbsp;&amp;nbsp;&lt;/p&gt;&lt;p&gt;When generating SQL profiles for dynamically generated code, you have to be able to predict every possible variation in the code and generate a SQL profile for every combination of every variation.&amp;nbsp; You end up writing code that mimics the dynamic code generation in the application&lt;/p&gt;&lt;p&gt;The same is true of Application Engine steps that reference &lt;a href=&quot;https://docs.oracle.com/cd/E25688_01/pt852pbr0/eng/psbooks/tape/book.htm?File=tape/htm/tape10.htm&quot; rel=&quot;nofollow&quot; target=&quot;_blank&quot;&gt;temporary records&lt;/a&gt; via the &lt;a href=&quot;https://docs.oracle.com/cd/E25688_01/pt852pbr0/eng/psbooks/tpcl/htm/tpcl03.htm#g037ee99c9453fb39_ef90c_10c791ddc07__3ba1&quot; rel=&quot;nofollow&quot; target=&quot;_blank&quot;&gt;%Table() meta-SQL&lt;/a&gt; but are otherwise static.&amp;nbsp; This variation is a special case because it is a part of PeopleTools&#39; behaviour.&amp;nbsp; It is easy to determine which tables could be referenced by querying some of the PeopleTools tables.&amp;nbsp; Although, the table name itself can be set dynamically!&lt;/p&gt;&lt;p&gt;Let&#39;s take an example statement.&amp;nbsp; I identified it as performing poorly during an analysis of ASH data.&amp;nbsp; I have a SQL_ID, a plan hash value, and ACTION indicates the name of the Application Engine step that generated it (because Application Engine instrumentation is enabled by setting EnableAEMonitoring - see https://blog.psftdba.com/2015/03/undocumented-application-engine.html).&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;SQL ID = &lt;i&gt;bk98x60cspttj&lt;/i&gt;&lt;/li&gt;
&lt;li&gt;SQL Plan Hash Value = &lt;i&gt;113493817&lt;/i&gt;&lt;/li&gt;
&lt;li&gt;Action = &lt;i&gt;XX_TREESEL.iDetVal.iDetVal.S&lt;/i&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;This is the statement and execution plan report generated by &lt;a href=&quot;https://docs.oracle.com/en/database/oracle/oracle-database/23/arpls/DBMS_XPLAN.html#GUID-D416125C-FED5-4704-A371-B5EECFCE1429&quot; rel=&quot;nofollow&quot; target=&quot;_blank&quot;&gt;dbms_xplan.display_workload_repository &lt;/a&gt;(formatted and edited slightly).&amp;nbsp; I have the statement, the execution plan and the outline of hints that describes the plan.&lt;/p&gt;
&lt;pre style=&quot;background-color: #eeeeee; font-family: &amp;quot;courier new&amp;quot;overflow: auto; line-height: 95%; text-align: left; width: 95%;&quot;&gt;&lt;span style=&quot;font-size: xx-small;&quot;&gt;SELECT * FROM table(dbms_xplan.display_workload_repository(&#39;bk98x60cspttj&#39;,113493817,&#39;ADVANCED +ADAPTIVE&#39;));

SQL_ID
-------------
INSERT INTO PS_TSEL_P_TAO12 (CHARTFIELD, CHARTFIELD_VALUE,
PROCESS_INSTANCE) SELECT DISTINCT &#39;ACCOUNT&#39;, DV.ACCOUNT, 12345678 
FROM PS_TSEL_R30_TAO12 A, PS_GL_ACCOUNT_TBL DV 
WHERE A.CHARTFIELD = &#39;ACCOUNT&#39; AND A.PROCESS_INSTANCE = 12345678 
AND DV.SETID = &#39;12345&#39; 
AND DV.EFFDT = (SELECT MAX(EFFDT) 
 	FROM PS_GL_ACCOUNT_TBL B WHERE SETID = DV.SETID AND ACCOUNT = DV.ACCOUNT 
 	AND EFFDT &amp;lt;= TO_DATE(&#39;2025-01-31&#39;,&#39;YYYY-MM-DD&#39;)) 
AND DV.ACCOUNT &amp;gt;= A.RANGE_FROM_30
AND DV.ACCOUNT &amp;lt;= A.RANGE_TO_30

Plan hash value: 113493817

--------------------------------------------------------------------------------------------------------
| Id  | Operation                          | Name              | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------------------------------------
|   0 | INSERT STATEMENT                   |                   |       |       | 38754 (100)|          |
|   1 |  LOAD TABLE CONVENTIONAL           | PS_TSEL_P_TAO17   |       |       |            |          |
|   2 |   SORT UNIQUE                      |                   |     1 |   161 | 38754   (1)| 00:00:02 |
|*  3 |    FILTER                          |                   |       |       |            |          |
|   4 |     MERGE JOIN                     |                   | 24219 |  3807K|     8  (50)| 00:00:01 |
|   5 |      SORT JOIN                     |                   |  1138 |   151K|     3  (34)| 00:00:01 |
|*  6 |       INDEX STORAGE FAST FULL SCAN | PSATSEL_R30_TAO17 |  1138 |   151K|     2   (0)| 00:00:01 |
|*  7 |      FILTER                        |                   |       |       |            |          |
|*  8 |       SORT JOIN                    |                   |  8513 |   207K|     3  (34)| 00:00:01 |
|*  9 |        INDEX STORAGE FAST FULL SCAN| PS_GL_ACCOUNT_TBL |  8513 |   207K|     2   (0)| 00:00:01 |
|  10 |     SORT AGGREGATE                 |                   |     1 |    25 |            |          |
|  11 |      FIRST ROW                     |                   |     1 |    25 |     2   (0)| 00:00:01 |
|* 12 |       INDEX RANGE SCAN (MIN/MAX)   | PS_GL_ACCOUNT_TBL |     1 |    25 |     2   (0)| 00:00:01 |
--------------------------------------------------------------------------------------------------------

Query Block Name / Object Alias (identified by operation id):
-------------------------------------------------------------
   1 - SEL$1
   6 - SEL$1 / A@SEL$1
   9 - SEL$1 / DV@SEL$1
  10 - SEL$2
  12 - SEL$2 / B@SEL$2

Outline Data
-------------
  /*+
      BEGIN_OUTLINE_DATA
      IGNORE_OPTIM_EMBEDDED_HINTS
      OPTIMIZER_FEATURES_ENABLE(&#39;19.1.0&#39;)
      DB_VERSION(&#39;19.1.0&#39;)
      ALL_ROWS
      OUTLINE_LEAF(@&quot;SEL$2&quot;)
      OUTLINE_LEAF(@&quot;SEL$1&quot;)
      OUTLINE_LEAF(@&quot;INS$1&quot;)
      FULL(@&quot;INS$1&quot; &quot;PS_TSEL_P_TAO17&quot;@&quot;INS$1&quot;)
      INDEX_FFS(@&quot;SEL$1&quot; &quot;A&quot;@&quot;SEL$1&quot; (&quot;PS_TSEL_R30_TAO17&quot;.&quot;PROCESS_INSTANCE&quot;
              &quot;PS_TSEL_R30_TAO17&quot;.&quot;CHARTFIELD&quot; &quot;PS_TSEL_R30_TAO17&quot;.&quot;RANGE_FROM_30&quot;
              &quot;PS_TSEL_R30_TAO17&quot;.&quot;RANGE_TO_30&quot;))
      INDEX_FFS(@&quot;SEL$1&quot; &quot;DV&quot;@&quot;SEL$1&quot; (&quot;PS_GL_ACCOUNT_TBL&quot;.&quot;SETID&quot; &quot;PS_GL_ACCOUNT_TBL&quot;.&quot;ACCOUNT&quot;
              &quot;PS_GL_ACCOUNT_TBL&quot;.&quot;EFFDT&quot;))
      LEADING(@&quot;SEL$1&quot; &quot;A&quot;@&quot;SEL$1&quot; &quot;DV&quot;@&quot;SEL$1&quot;)
      USE_MERGE(@&quot;SEL$1&quot; &quot;DV&quot;@&quot;SEL$1&quot;)
      PQ_FILTER(@&quot;SEL$1&quot; SERIAL)
      INDEX(@&quot;SEL$2&quot; &quot;B&quot;@&quot;SEL$2&quot; (&quot;PS_GL_ACCOUNT_TBL&quot;.&quot;SETID&quot; &quot;PS_GL_ACCOUNT_TBL&quot;.&quot;ACCOUNT&quot;
              &quot;PS_GL_ACCOUNT_TBL&quot;.&quot;EFFDT&quot;))
      END_OUTLINE_DATA
  */
…&lt;/span&gt;&lt;/pre&gt;&lt;div&gt;
&lt;p&gt;Below is the underlying Application Engine step that generated the SQL.&amp;nbsp; There is other dynamic code in this step that is driven by configuration data as well as the %Table() meta-SQLs.&amp;nbsp; Other reasons not to introduce hints into the statement include:&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;different dynamic variations of the code might require different hints, and then I would have to write more code to generate the hint dynamically.&amp;nbsp; However, in this case, I am only going to deal with a single variation, and I am only going to produce one set of SQL Profiles,&lt;/li&gt;
&lt;li&gt;this step and the code generation are delivered by PeopleSoft, any change in these areas would be considered as a customisation.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;
&lt;/p&gt;&lt;pre style=&quot;background-color: #eeeeee; font-family: &amp;quot;courier new&amp;quot;overflow: auto; line-height: 95%; text-align: left; width: 95%;&quot;&gt;&lt;span style=&quot;font-size: xx-small;&quot;&gt;%InsertSelect(DISTINCT
, %Bind(FT_TSEL_AET.RECNAME_SEL_TBL, NoQuotes)
, %Bind(FT_TSEL_STR_AET.DTL_RECNAME, NoQuotes
) DV
, %Bind(FT_TSEL_AET.FIELDNAME_CF, NoQuotes) = %Bind(FT_TSEL_STR_AET.DTL_FIELDNAME)
, %Bind(FT_TSEL_AET.FIELDNAME_VALUE, NoQuotes) = DV.%Bind(FT_TSEL_STR_AET.DTL_FIELDNAME, NoQuotes)
, PROCESS_INSTANCE = %Bind(PROCESS_INSTANCE)
, TREE_NAME = %Bind(FT_TSEL_AET.TREE_NAME)
, TREE_NODE = A.TREE_NODE
, TREE_NODE_NUM = A.TREE_NODE_NUM
, TREE_LEVEL_NUM = A.TREE_LEVEL_NUM, SETCNTRLVALUE =%Bind(FT_TSEL_AET.SETCNTRLVALUE)
, %Bind(FT_TSEL_GEN_AET.FIELDNAME_FROM, NoQuotes) = A.%Bind(FT_TSEL_GEN_AET.FIELDNAME_FROM, NoQuotes)
, %Bind(FT_TSEL_GEN_AET.FIELDNAME_TO, NoQuotes) = A.%Bind(FT_TSEL_GEN_AET.FIELDNAME_TO, NoQuotes)
, SETID_TREE = %Bind(FT_TSEL_GEN_AET.SETID_TREE)
, EFFDT = %Bind(FT_TSEL_STR_AET.EFFDT)
, CFV_SET = %Bind(FT_TSEL_AET.CFV_SET)) 
FROM &lt;a href=&quot;https://docs.oracle.com/cd/E25688_01/pt852pbr0/eng/psbooks/tpcl/htm/tpcl03.htm#g037ee99c9453fb39_ef90c_10c791ddc07__3ba1&quot; rel=&quot;nofollow&quot; target=&quot;_blank&quot;&gt;&lt;b&gt;%Table(&lt;/b&gt;&lt;/a&gt;%Bind(FT_TSEL_GEN_AET.RECNAME_SEL_TBL, NoQuotes)) A
, &lt;b&gt;%Table(&lt;/b&gt;%Bind(FT_TSEL_STR_AET.DTL_RECNAME, NoQuotes)) DV 
WHERE A.CHARTFIELD = %Bind(FT_TSEL_AET.FIELDNAME) 
AND A.PROCESS_INSTANCE = %Bind(PROCESS_INSTANCE) 
%Bind(FT_TSEL_WRK_AET.WHERE_TXT_LONG, NoQuotes)&lt;/span&gt;&lt;/pre&gt;&lt;div&gt;
&lt;div&gt;&lt;div&gt;This step references two temporary records.&amp;nbsp; It generates data in one temporary table TSEL_P_TAO based on data in another, TSEL_R30_TAO.&amp;nbsp; It happens to use instance 12 of both records.&amp;nbsp; This is really just a coincidence.&amp;nbsp; Temporary table instances are allocated and deallocated in a group when an application engine starts and ends, so it is common for one step to use the same instance of different tables, but there is nothing to stop different table instances from being used.&amp;nbsp; That can occur when concurrently executing multiple instances of a program or different programs that allocate just some of the same tables.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;I now need to produce SQL profiles for each permutation.&amp;nbsp; I will start by generating a script to create a single profile for a single SQL statement using Oracle&#39;s &lt;i&gt;coe_xfr_sql_profile.sql&lt;/i&gt; script that is delivered as a part of the SQLT utility.&amp;nbsp; It is available from the Oracle support website (&lt;a href=&quot;https://support.oracle.com/epmos/faces/DocContentDisplay?id=215187.1&quot; rel=&quot;nofollow&quot; target=&quot;_blank&quot;&gt;All About the SQLT Diagnostic Tool (Doc ID 215187.1)&lt;/a&gt;).&amp;nbsp;&amp;nbsp;&lt;/div&gt;&lt;div&gt;It produces a SQL script such as the one below (I have removed the comments).&amp;nbsp; The SQL text is in a CLOB variable, and all the hints required to reproduce the same execution plan are in an array.&amp;nbsp; The script generates a signature for the statement and then creates a SQL profile from that information.&lt;/div&gt;&lt;div&gt;I have already customised my version of &lt;i&gt;coe_xfr_sql_profile.sql&lt;/i&gt; so that &lt;i&gt;FORCE_MATCH =&amp;gt; TRUE&lt;/i&gt; in the generated scripts.&lt;/div&gt;&lt;/div&gt;
&lt;pre style=&quot;background-color: #eeeeee; font-family: &amp;quot;courier new&amp;quot;overflow: auto; line-height: 95%; text-align: left; width: 95%;&quot;&gt;&lt;span style=&quot;font-size: xx-small;&quot;&gt;WHENEVER SQLERROR EXIT SQL.SQLCODE;
VAR signature NUMBER;
DECLARE
sql_txt CLOB;
h       SYS.SQLPROF_ATTR;
BEGIN
sql_txt := q&#39;[
INSERT INTO &lt;b&gt;PS_TSEL_P_TAO17&lt;/b&gt; (CHARTFIELD, CHARTFIELD_VALUE, PROCESS_INSTANCE) SELECT DISTINCT &#39;ACCOUNT&#39;, DV.ACCOUNT, 12345678
FROM &lt;b&gt;PS_TSEL_R30_TAO17&lt;/b&gt; A, PS_GL_ACCOUNT_TBL DV WHERE A.CHARTFIELD = &#39;ACCOUNT&#39; AND A.PROCESS_INSTANCE = 12345678 AND DV.SETID 
= &#39;GLOBE&#39; AND DV.EFFDT = (SELECT MAX(EFFDT) FROM PS_GL_ACCOUNT_TBL B WHERE SETID = DV.SETID AND ACCOUNT = DV.ACCOUNT AND 
EFFDT &amp;lt;= TO_DATE(&#39;2025-01-31&#39;,&#39;YYYY-MM-DD&#39;)) AND DV.ACCOUNT &amp;gt;= A.RANGE_FROM_30 AND DV.ACCOUNT &amp;lt;= A.RANGE_TO_30
]&#39;;
h := SYS.SQLPROF_ATTR(
q&#39;[BEGIN_OUTLINE_DATA]&#39;,
q&#39;[IGNORE_OPTIM_EMBEDDED_HINTS]&#39;,
q&#39;[OPTIMIZER_FEATURES_ENABLE(&#39;19.1.0&#39;)]&#39;,
q&#39;[DB_VERSION(&#39;19.1.0&#39;)]&#39;,
q&#39;[ALL_ROWS]&#39;,
q&#39;[OUTLINE_LEAF(@&quot;SEL$683B0107&quot;)]&#39;,
q&#39;[OUTLINE_LEAF(@&quot;SEL$C772B8D1&quot;)]&#39;,
q&#39;[UNNEST(@&quot;SEL$2&quot;)]&#39;,
q&#39;[OUTLINE_LEAF(@&quot;INS$1&quot;)]&#39;,
q&#39;[OUTLINE(@&quot;SEL$2&quot;)]&#39;,
q&#39;[OUTLINE(@&quot;SEL$7511BFD2&quot;)]&#39;,
q&#39;[OUTLINE(@&quot;SEL$1&quot;)]&#39;,
q&#39;[FULL(@&quot;INS$1&quot; &quot;PS_TSEL_P_TAO17&quot;@&quot;INS$1&quot;)]&#39;,
q&#39;[NO_ACCESS(@&quot;SEL$C772B8D1&quot; &quot;VW_SQ_1&quot;@&quot;SEL$7511BFD2&quot;)]&#39;,
q&#39;[INDEX(@&quot;SEL$C772B8D1&quot; &quot;DV&quot;@&quot;SEL$1&quot; (&quot;PS_GL_ACCOUNT_TBL&quot;.&quot;SETID&quot; &quot;PS_GL_ACCOUNT_TBL&quot;.&quot;ACCOUNT&quot; &quot;PS_GL_ACCOUNT_TBL&quot;.&quot;EFFDT&quot;))]&#39;,
q&#39;[FULL(@&quot;SEL$C772B8D1&quot; &quot;A&quot;@&quot;SEL$1&quot;)]&#39;,
q&#39;[LEADING(@&quot;SEL$C772B8D1&quot; &quot;VW_SQ_1&quot;@&quot;SEL$7511BFD2&quot; &quot;DV&quot;@&quot;SEL$1&quot; &quot;A&quot;@&quot;SEL$1&quot;)]&#39;,
q&#39;[USE_NL(@&quot;SEL$C772B8D1&quot; &quot;DV&quot;@&quot;SEL$1&quot;)]&#39;,
q&#39;[USE_MERGE(@&quot;SEL$C772B8D1&quot; &quot;A&quot;@&quot;SEL$1&quot;)]&#39;,
q&#39;[PARTIAL_JOIN(@&quot;SEL$C772B8D1&quot; &quot;A&quot;@&quot;SEL$1&quot;)]&#39;,
q&#39;[INDEX_FFS(@&quot;SEL$683B0107&quot; &quot;B&quot;@&quot;SEL$2&quot; (&quot;PS_GL_ACCOUNT_TBL&quot;.&quot;SETID&quot; &quot;PS_GL_ACCOUNT_TBL&quot;.&quot;ACCOUNT&quot; &quot;PS_GL_ACCOUNT_TBL&quot;.&quot;EFFDT&quot;))]&#39;,
q&#39;[END_OUTLINE_DATA]&#39;);

:signature := DBMS_SQLTUNE.SQLTEXT_TO_SIGNATURE(sql_txt);

DBMS_SQLTUNE.IMPORT_SQL_PROFILE (
sql_text    =&amp;gt; sql_txt,
profile     =&amp;gt; h,
name        =&amp;gt; &#39;coe_g7wz6ctquwjcy_2476903986&#39;,
description =&amp;gt; &#39;coe g7wz6ctquwjcy 2476903986 &#39;||:signature||&#39;&#39;,
category    =&amp;gt; &#39;DEFAULT&#39;,
validate    =&amp;gt; TRUE,
replace     =&amp;gt; TRUE,
force_match =&amp;gt; TRUE /* TRUE:FORCE (match even when different literals in SQL). FALSE:EXACT (similar to CURSOR_SHARING) */ );
END;
/
WHENEVER SQLERROR CONTINUE
SET ECHO OFF;
PRINT signature
PRO
PRO ... manual custom SQL Profile has been created
PRO
SET TERM ON ECHO OFF LIN 80 TRIMS OFF NUMF &quot;&quot;;
SPO OFF;
PRO
PRO COE_XFR_SQL_PROFILE_g7wz6ctquwjcy_2476903986 completed&lt;/span&gt;&lt;/pre&gt;&lt;div&gt;
&lt;div&gt;In Application Engine, different tables will be used if multiple programs referencing the same table run concurrently, or if a process crashes and holds its allocation to a particular table instance.&amp;nbsp; Therefore, I need to create a set of similar SQL profiles, one for each statement that could be generated by this Application Designer step.&amp;nbsp; I need to iterate through all possibilities, so I have added some additional pieces to the script.&amp;nbsp; They are explained in the footnotes below.&lt;/div&gt;
&lt;pre style=&quot;background-color: #eeeeee; font-family: &amp;quot;courier new&amp;quot;overflow: auto; line-height: 95%; text-align: left; width: 95%;&quot;&gt;&lt;span&gt;&lt;span style=&quot;font-size: xx-small;&quot;&gt;REM coe_xfr_sql_profile_XX_TREESEL.iDetVal.iDetVal.sql
SPO coe_xfr_sql_profile_XX_TREESEL.iDetVal.iDetVal.log;
WHENEVER SQLERROR CONTINUE
SET serveroutput on ECHO ON TERM ON LIN 2000 TRIMS ON NUMF 99999999999999999999;
clear screen
ALTER SESSION SET CURRENT_SCHEMA=SYSADM;&lt;/span&gt;&lt;sup&gt;&lt;a href=&quot;#profile01&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-size: x-small;&quot;&gt;1&lt;/span&gt;&lt;/b&gt;&lt;/a&gt;&lt;/sup&gt;&lt;span style=&quot;font-size: xx-small;&quot;&gt;
…
WHENEVER SQLERROR EXIT SQL.SQLCODE;
VAR signature NUMBER;

DECLARE
  l_recname1 VARCHAR2(15) :=    &#39;TSEL_R30_TAO&#39;;&lt;/span&gt;&lt;sup style=&quot;font-size: x-small;&quot;&gt;&lt;a href=&quot;#profile02&quot;&gt;2&lt;/a&gt;&lt;/sup&gt;&lt;span style=&quot;font-size: xx-small;&quot;&gt;
  l_table1   VARCHAR2(18) := &#39;PS_TSEL_R30_TAO17&#39;;
  l_recname2 VARCHAR2(15) :=    &#39;TSEL_P_TAO&#39;;
  l_table2   VARCHAR2(18) := &#39;PS_TSEL_P_TAO17&#39;;
  l_name     VARCHAR2(30);
  sql_txt CLOB;
  h       SYS.SQLPROF_ATTR;

  e_no_sql_profile EXCEPTION;
  PRAGMA EXCEPTION_INIT(e_no_sql_profile, -13833);
BEGIN
FOR i IN (&lt;/span&gt;&lt;sup style=&quot;font-size: x-small;&quot;&gt;&lt;a href=&quot;#profile03&quot;&gt;3&lt;/a&gt;&lt;/sup&gt;&lt;span style=&quot;font-size: xx-small;&quot;&gt;
  WITH v&lt;/span&gt;&lt;sup style=&quot;font-size: x-small;&quot;&gt;&lt;a href=&quot;#profile04&quot;&gt;4&lt;/a&gt;&lt;/sup&gt;&lt;span style=&quot;font-size: xx-small;&quot;&gt;  AS (SELECT rownum-1 row_number FROM dual CONNECT BY LEVEL &amp;lt;= 100) 
  SELECT DISTINCT 
         v1.row_number id1, r1.recname recname1, t1.table_name table_name1
  ,      v2.row_number id2, r2.recname recname2, t2.table_name table_name2
  ,      o.TEMPTBLINSTANCES
  FROM   &lt;/span&gt;&lt;a href=&quot;https://www2.go-faster.co.uk/peopletools/psrecdefn.htm&quot; style=&quot;font-size: x-small;&quot; target=&quot;_blank&quot;&gt;psrecdefn&lt;/a&gt;&lt;span style=&quot;font-size: xx-small;&quot;&gt; r1
  ,      psrecdefn r2
  ,      &lt;/span&gt;&lt;a href=&quot;https://www2.go-faster.co.uk/peopletools/pstemptblcntvw.htm&quot; style=&quot;font-size: x-small;&quot; target=&quot;_blank&quot;&gt;pstemptblcntvw&lt;/a&gt;&lt;sup style=&quot;font-size: x-small;&quot;&gt;&lt;a href=&quot;#profile05&quot;&gt;5&lt;/a&gt;&lt;/sup&gt;&lt;span style=&quot;font-size: xx-small;&quot;&gt; i1
  ,      pstemptblcntvw i2
  ,      &lt;/span&gt;&lt;a href=&quot;https://docs.oracle.com/en/database/oracle/oracle-database/21/refrn/ALL_TABLES.html&quot; rel=&quot;nofollow&quot; style=&quot;font-size: x-small;&quot; target=&quot;_blank&quot;&gt;all_tables&lt;/a&gt;&lt;span style=&quot;font-size: xx-small;&quot;&gt; t1
  ,      all_tables t2
  ,      &lt;/span&gt;&lt;a href=&quot;https://www2.go-faster.co.uk/peopletools/psoptions.htm&quot; style=&quot;font-size: x-small;&quot; target=&quot;_blank&quot;&gt;psoptions&lt;/a&gt;&lt;span style=&quot;font-size: xx-small;&quot;&gt; o 
  ,      ps.psdbowner p 
  ,      v v1
  ,      v v2
  WHERE  r1.rectype = 7 AND r1.recname = i1.recname AND r1.recname = l_recname1
  AND    r2.rectype = 7 AND r2.recname = i2.recname AND r2.recname = l_recname2
  AND    v1.row_number &amp;lt;= i1.temptblinstances + o.temptblinstances&lt;/span&gt;&lt;sup style=&quot;font-size: x-small;&quot;&gt;&lt;a href=&quot;#profile06&quot;&gt;6&lt;/a&gt;&lt;/sup&gt;&lt;span style=&quot;font-size: xx-small;&quot;&gt; &lt;/span&gt;&lt;i style=&quot;font-size: x-small;&quot;&gt;--up to total number of instances&lt;/i&gt;&lt;span style=&quot;font-size: xx-small;&quot;&gt;
  AND    v2.row_number &amp;lt;= i2.temptblinstances + o.temptblinstances  &lt;/span&gt;&lt;i style=&quot;font-size: x-small;&quot;&gt;--up to total number of instances&lt;/i&gt;&lt;span style=&quot;font-size: xx-small;&quot;&gt;
  AND    (v1.row_number = 0 OR v1.row_number &amp;gt; o.temptblinstances)&lt;/span&gt;&lt;sup style=&quot;font-size: x-small;&quot;&gt;&lt;a href=&quot;#profile07&quot;&gt;7&lt;/a&gt;&lt;/sup&gt;&lt;span style=&quot;font-size: xx-small;&quot;&gt; &lt;/span&gt;&lt;i style=&quot;font-size: x-small;&quot;&gt;--omit online temp tables&lt;/i&gt;&lt;span style=&quot;font-size: xx-small;&quot;&gt;
  AND    (v2.row_number = 0 OR v2.row_number &amp;gt; o.temptblinstances)  &lt;/span&gt;&lt;i style=&quot;font-size: x-small;&quot;&gt;--omit online temp tables&lt;/i&gt;&lt;span style=&quot;font-size: xx-small;&quot;&gt;
  and    t1.owner = p.ownerid AND t1.table_name 
         = DECODE(r1.sqltablename,&#39; &#39;,&#39;PS_&#39;||r1.recname,r1.sqltablename) 
         ||DECODE(v1.row_number,0,&#39;&#39;,LTRIM(TO_NUMBER(v1.row_number)))&lt;/span&gt;&lt;sup style=&quot;font-size: x-small;&quot;&gt;&lt;a href=&quot;#profile08&quot;&gt;8&lt;/a&gt;&lt;/sup&gt;&lt;span style=&quot;font-size: xx-small;&quot;&gt; --derive table table
  and    t2.owner = p.ownerid AND t2.table_name 
         = DECODE(r2.sqltablename,&#39; &#39;,&#39;PS_&#39;||r2.recname,r2.sqltablename) 
         ||DECODE(v2.row_number,0,&#39;&#39;,LTRIM(TO_NUMBER(v2.row_number)))  --derive table table
  AND    ABS(v1.row_number-v2.row_number)&amp;lt;=1&lt;/span&gt;&lt;sup style=&quot;font-size: x-small;&quot;&gt;&lt;a href=&quot;#profile09&quot;&gt;9&lt;/a&gt;&lt;/sup&gt;&lt;span style=&quot;font-size: xx-small;&quot;&gt;  --max variation in instance numbers
  ORDER BY id1, id2
) LOOP
  -----------123456789012345678901234567890
  l_name := &#39;XX_TREESEL.iDetVal.iDetVal&#39;||i.id1||i.id2;&lt;/span&gt;&lt;sup style=&quot;font-size: x-small;&quot;&gt;&lt;a href=&quot;#profile10&quot;&gt;10&lt;/a&gt;&lt;/sup&gt;&lt;span style=&quot;font-size: xx-small;&quot;&gt;
  
sql_txt := q&#39;[
INSERT INTO &lt;/span&gt;&lt;b style=&quot;font-size: x-small;&quot;&gt;PS_TSEL_P_TAO17&lt;/b&gt;&lt;span style=&quot;font-size: xx-small;&quot;&gt; (CHARTFIELD, CHARTFIELD_VALUE, PROCESS_INSTANCE) SELECT DISTINCT &#39;ACCOUNT&#39;, DV.ACCOUNT, 12345678
FROM &lt;/span&gt;&lt;b style=&quot;font-size: x-small;&quot;&gt;PS_TSEL_R30_TAO17&lt;/b&gt;&lt;span style=&quot;font-size: xx-small;&quot;&gt; A, PS_GL_ACCOUNT_TBL DV WHERE A.CHARTFIELD = &#39;ACCOUNT&#39; AND A.PROCESS_INSTANCE = 12345678 AND DV.SETID 
= &#39;GLOBE&#39; AND DV.EFFDT = (SELECT MAX(EFFDT) FROM PS_GL_ACCOUNT_TBL B WHERE SETID = DV.SETID AND ACCOUNT = DV.ACCOUNT AND 
EFFDT &amp;lt;= TO_DATE(&#39;2025-01-31&#39;,&#39;YYYY-MM-DD&#39;)) AND DV.ACCOUNT &amp;gt;= A.RANGE_FROM_30 AND DV.ACCOUNT &amp;lt;= A.RANGE_TO_30
]&#39;;

h := SYS.SQLPROF_ATTR(
q&#39;[BEGIN_OUTLINE_DATA]&#39;,
q&#39;[IGNORE_OPTIM_EMBEDDED_HINTS]&#39;,
q&#39;[ALL_ROWS]&#39;,
q&#39;[LEADING(@&quot;SEL$1&quot; &quot;A&quot;@&quot;SEL$1&quot;)]&#39;,&lt;/span&gt;&lt;sup style=&quot;font-size: x-small;&quot;&gt;&lt;a href=&quot;#profile11&quot;&gt;11&lt;/a&gt;&lt;/sup&gt;&lt;span style=&quot;font-size: xx-small;&quot;&gt;
q&#39;[END_OUTLINE_DATA]&#39;);

sql_txt := REPLACE(sql_txt, l_table1,i.table_name1);&lt;/span&gt;&lt;sup style=&quot;font-size: x-small;&quot;&gt;&lt;a href=&quot;#profile12&quot;&gt;12&lt;/a&gt;&lt;/sup&gt;&lt;span style=&quot;font-size: xx-small;&quot;&gt;
sql_txt := REPLACE(sql_txt, l_table2,i.table_name2);
:signature := DBMS_SQLTUNE.SQLTEXT_TO_SIGNATURE(sql_txt);
dbms_output.put_line(l_name||&#39;:&#39;||:signature||&#39;:&#39;||sql_txt);

for n in 1 .. h.count LOOP&lt;/span&gt;&lt;sup style=&quot;font-size: x-small;&quot;&gt;&lt;a href=&quot;#profile13&quot;&gt;13&lt;/a&gt;&lt;/sup&gt;&lt;span style=&quot;font-size: xx-small;&quot;&gt;
  if h(n) LIKE &#39;%&#39;||l_table1||&#39;%&#39; THEN
    h(n) := REPLACE(h(n), l_table1,i.table_name1);
    dbms_output.put_line(n||&#39;:&#39;||h(n));
  end if;
  if h(n) LIKE &#39;%&#39;||l_table2||&#39;%&#39; THEN
    h(n) := REPLACE(h(n), l_table2,i.table_name2);
    dbms_output.put_line(n||&#39;:&#39;||h(n));
  end if;
end loop;

BEGIN --drop profile if already exists
  DBMS_SQLTUNE.drop_SQL_PROFILE(name =&amp;gt; l_name);
  EXCEPTION WHEN e_no_sql_profile THEN NULL;
END;

DBMS_SQLTUNE.IMPORT_SQL_PROFILE ( --create new profile
sql_text    =&amp;gt; sql_txt,
profile     =&amp;gt; h,
name        =&amp;gt; l_name,
description =&amp;gt; &#39;coe XX_TREESEL.iDetVal.iDetVal &#39;||l_name||&#39; &#39;||:signature||&#39;&#39;,
category    =&amp;gt; &#39;DEFAULT&#39;,
validate    =&amp;gt; TRUE,
replace     =&amp;gt; TRUE,
force_match =&amp;gt; TRUE /* TRUE:FORCE (match even when different literals in SQL). FALSE:EXACT (similar to CURSOR_SHARING) */ );
  END LOOP;
END;
/
WHENEVER SQLERROR CONTINUE
…
SPO OFF;
PRO
PRO coe_xfr_sql_profile_XX_TREESEL.iDetVal.iDetVal completed&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;div&gt;
&lt;div&gt;&lt;ol style=&quot;text-align: left;&quot;&gt;
&lt;p id=&quot;profile01&quot;&gt;&lt;/p&gt;&lt;li&gt;Set CURRENT_SCHEMA to specify PeopleSoft owning schema, SYSADM.&lt;/li&gt;&lt;p&gt;&lt;/p&gt;
&lt;p id=&quot;profile02&quot;&gt;&lt;/p&gt;&lt;li&gt;For each temporary record in the original SQL statement, add pairs of variables to specify the name of PeopleSoft record and Oracle temporary table instance referenced in the statement.&amp;nbsp; I could derive the record name from the table name, but it is easier just to hard-code it.&lt;/li&gt;&lt;p&gt;&lt;/p&gt;
&lt;p id=&quot;profile03&quot;&gt;&lt;/p&gt;&lt;li&gt;The SQL statement produces all combinations of temporary records that could appear in the SQL statement.&amp;nbsp; I will put it in an implicit cursor loop, and then for each row returned, the script will create a SQL profile.&lt;/li&gt;&lt;p&gt;&lt;/p&gt;
&lt;p id=&quot;&quot; profile04=&quot;&quot;&gt;&lt;/p&gt;&lt;li&gt;Common table expression V returns 100 rows, numbered 0 to 99.&amp;nbsp; Irrespective of the number of temporary table instances specified in each Application Engine program, there can only be non-shared 99 table instances for each PeopleTools record, plus the shared instance (that doesn&#39;t have a suffix number).&amp;nbsp;&amp;nbsp;&lt;/li&gt;&lt;p&gt;&lt;/p&gt;
&lt;p id=&quot;profile05&quot;&gt;&lt;/p&gt;&lt;li&gt;&lt;a href=&quot;https://www2.go-faster.co.uk/peopletools/pstemptblcntvw.htm&quot; target=&quot;_blank&quot;&gt;PSTEMPTBLCNTVW&lt;/a&gt; returns the number of non-shared batch (i.e. not online) instances of each temporary record that needs to be built.&amp;nbsp; This is in addition to the number of online temporary table instances.&lt;/li&gt;&lt;p&gt;&lt;/p&gt;
&lt;p id=&quot;profile06&quot;&gt;&lt;/p&gt;&lt;li&gt;The query will return a row for each instance of each temporary table up to the number of instances required by the application engines plus the number of online table instances, but not exceeding the 99 rows returned by CTE V.&lt;/li&gt;&lt;p&gt;&lt;/p&gt;
&lt;p id=&quot;profile07&quot;&gt;&lt;/p&gt;&lt;li&gt;Most Application Engines do not run online in the component processor, therefore there is no need to build SQL profiles on these instances.&amp;nbsp; There are exceptions, such as some as the journal and voucher edit and post processes in Financials, in which case these criteria should be removed.&lt;/li&gt;&lt;p&gt;&lt;/p&gt;
&lt;p id=&quot;profile08&quot;&gt;&lt;/p&gt;&lt;li&gt;This expression joins the record and instance number to the table in the database.&amp;nbsp; Instance 0 will be used to refer to the shared instance.&lt;/li&gt;&lt;p&gt;&lt;/p&gt;
&lt;p id=&quot;profile09&quot;&gt;&lt;/p&gt;&lt;li&gt;The instance numbers of the temporary records is permitted to vary by up to one in either direction.&amp;nbsp; Thus the script generates profiles with instance 12 of one table, and instances 11 to 13 of the other.&amp;nbsp; It is rare, but possible, for there to be any difference in instance numbers between tables.&amp;nbsp; It is possible, but rarer for the difference to be greater than one.&lt;/li&gt;&lt;p&gt;&lt;/p&gt;
&lt;p id=&quot;profile10&quot;&gt;&lt;/p&gt;&lt;li&gt;SQL Profile names are limited to 30 characters.&amp;nbsp; I have specified a meaningful name based on the Application Engine step, up to 26 characters, and then the two IDs (which can be up to 2 digits each).&lt;/li&gt;&lt;p&gt;&lt;/p&gt;
&lt;p id=&quot;profile11&quot;&gt;&lt;/p&gt;&lt;li&gt;In this example, I am not going to use the full set of hints in the captured profile.&amp;nbsp; I just want to introduce a single leading hint.&lt;/li&gt;&lt;p&gt;&lt;/p&gt;
&lt;p id=&quot;profile12&quot;&gt;&lt;/p&gt;&lt;li&gt;I will substitute each table name in the SQL text with the specific table instance name.&lt;/li&gt;&lt;p&gt;&lt;/p&gt;
&lt;p id=&quot;profile13&quot;&gt;&lt;/p&gt;&lt;li&gt;Tables are generally referenced in hints via the row source alias.&amp;nbsp; However, sometimes the table name appears in the hints, and must also be replaced with the specific table name.&amp;nbsp; So I also work through all the hints in array &lt;i&gt;h&lt;/i&gt; and substitute any table names that may be there.&amp;nbsp; Indexes are not referenced by their names but by a list of indexed columns.&lt;/li&gt;&lt;/ol&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;
The result is a set of profiles for each possible SQL statement.
&lt;pre style=&quot;background-color: #eeeeee; font-family: &amp;quot;courier new&amp;quot;overflow: auto; line-height: 95%; text-align: left; width: 95%;&quot;&gt;&lt;span style=&quot;font-size: xx-small;&quot;&gt;XX_TREESEL.iDetVal.iDetVal00:11943215885789639839:
INSERT INTO PS_TSEL_P_TAO (CHARTFIELD, CHARTFIELD_VALUE, PROCESS_INSTANCE) SELECT DISTINCT &#39;ACCOUNT&#39;, DV.ACCOUNT, 12345678 
FROM PS_TSEL_R30_TAO A, PS_GL_ACCOUNT_TBL DV WHERE A.CHARTFIELD = &#39;ACCOUNT&#39; AND A.PROCESS_INSTANCE = 12345678AND DV.SETID 
= &#39;GLOBE&#39; AND DV.EFFDT = (SELECT MAX(EFFDT) FROM PS_GL_ACCOUNT_TBL B WHERE SETID = DV.SETID AND ACCOUNT = DV.ACCOUNT AND 
EFFDT &amp;lt;= TO_DATE(&#39;2025-01-31&#39;,&#39;YYYY-MM-DD&#39;)) AND DV.ACCOUNT &amp;gt;= A.RANGE_FROM_30 AND DV.ACCOUNT &amp;lt;= A.RANGE_TO_30

XX_TREESEL.iDetVal.iDetVal1717:15747497907378648788:
INSERT INTO PS_TSEL_P_TAO17 (CHARTFIELD, CHARTFIELD_VALUE, PROCESS_INSTANCE) SELECT DISTINCT &#39;ACCOUNT&#39;, DV.ACCOUNT, 12345678 
FROM PS_TSEL_R30_TAO17 A, PS_GL_ACCOUNT_TBL DV WHERE A.CHARTFIELD = &#39;ACCOUNT&#39; AND A.PROCESS_INSTANCE = 12345678 AND DV.SETID 
= &#39;GLOBE&#39; AND DV.EFFDT = (SELECT MAX(EFFDT) FROM PS_GL_ACCOUNT_TBL B WHERE SETID = DV.SETID AND ACCOUNT = DV.ACCOUNT AND 
EFFDT &amp;lt;= TO_DATE(&#39;2025-01-31&#39;,&#39;YYYY-MM-DD&#39;)) AND DV.ACCOUNT &amp;gt;= A.RANGE_FROM_30 AND DV.ACCOUNT &amp;lt;= A.RANGE_TO_30

XX_TREESEL.iDetVal.iDetVal1718:12015611546030583918:
INSERT INTO PS_TSEL_P_TAO18 (CHARTFIELD, CHARTFIELD_VALUE, PROCESS_INSTANCE) SELECT DISTINCT &#39;ACCOUNT&#39;, DV.ACCOUNT, 12345678 
FROM PS_TSEL_R30_TAO17 A, PS_GL_ACCOUNT_TBL DV WHERE A.CHARTFIELD = &#39;ACCOUNT&#39; AND A.PROCESS_INSTANCE = 12345678 AND DV.SETID 
= &#39;GLOBE&#39; AND DV.EFFDT = (SELECT MAX(EFFDT) FROM PS_GL_ACCOUNT_TBL B WHERE SETID = DV.SETID AND ACCOUNT = DV.ACCOUNT AND 
EFFDT &amp;lt;= TO_DATE(&#39;2025-01-31&#39;,&#39;YYYY-MM-DD&#39;)) AND DV.ACCOUNT &amp;gt;= A.RANGE_FROM_30 AND DV.ACCOUNT &amp;lt;= A.RANGE_TO_30

XX_TREESEL.iDetVal.iDetVal1817:14883898515022367531:
INSERT INTO PS_TSEL_P_TAO17 (CHARTFIELD, CHARTFIELD_VALUE, PROCESS_INSTANCE) SELECT DISTINCT &#39;ACCOUNT&#39;, DV.ACCOUNT, 12345678 
FROM PS_TSEL_R30_TAO18 A, PS_GL_ACCOUNT_TBL DV WHERE A.CHARTFIELD = &#39;ACCOUNT&#39; AND A.PROCESS_INSTANCE = 12345678 AND DV.SETID 
= &#39;GLOBE&#39; AND DV.EFFDT = (SELECT MAX(EFFDT) FROM PS_GL_ACCOUNT_TBL B WHERE SETID = DV.SETID AND ACCOUNT = DV.ACCOUNT AND 
EFFDT &amp;lt;= TO_DATE(&#39;2025-01-31&#39;,&#39;YYYY-MM-DD&#39;)) AND DV.ACCOUNT &amp;gt;= A.RANGE_FROM_30 AND DV.ACCOUNT &amp;lt;= A.RANGE_TO_30
…&lt;/span&gt;&lt;/pre&gt;
  Note&lt;br /&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;If the number of non-shared instances of a table in an Application Engine is increased, you need to build add table instances with Application Designer.&lt;/li&gt;&lt;li&gt;If the number of online temporary instances is increased, you need to build additional instances for every temporary record in the database.&amp;nbsp; If the number is reduced some tables will cease to be used, and they ought to be dropped.&amp;nbsp;&amp;nbsp;&lt;/li&gt;&lt;li&gt;If either the number of application engine table instances or online temporary table instances are changed, then you will need to rerun the script to create additional SQL profiles.&lt;/li&gt;&lt;/ul&gt;&lt;div class=&quot;blogger-post-footer&quot;&gt;&lt;a href=&quot;http://www.go-faster.co.uk/&quot;&gt;©David Kurtz&lt;/a&gt;&lt;/div&gt;</description><link>https://blog.psftdba.com/2025/01/sql-profiles-for-application-engine.html</link><author>noreply@blogger.com (David Kurtz)</author><thr:total>0</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-25740336.post-3946539947385731259</guid><pubDate>Mon, 02 Dec 2024 22:40:00 +0000</pubDate><atom:updated>2025-02-18T20:26:35.385+00:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">Resource Manager</category><title>In the Cloud Performance is Instrumented as Cost - A Resource Plan for PeopleSoft</title><description>&lt;p&gt;In the cloud, either you are spending too much money on too much CPU, or your system is constrained by CPU at peak times.&amp;nbsp; You can have as much performance as you are willing to pay for.&amp;nbsp;&lt;/p&gt;&lt;p&gt;This presentation (from the &lt;a href=&quot;https://virtual.oxfordabstracts.com/event/73719/homepage&quot; target=&quot;_blank&quot;&gt;UKOUG 2024 conference&lt;/a&gt;) is the story of how one PeopleSoft customer improved performance and reduced cloud subscription costs, by clearly stating their performance goals, and creating a matching resource manager plan.&lt;/p&gt;
&lt;p&gt;&lt;iframe frameborder=&quot;0&quot; height=&quot;300&quot; marginheight=&quot;0&quot; marginwidth=&quot;0&quot; scrolling=&quot;no&quot; src=&quot;https://www.slideshare.net/slideshow/embed_code/key/NmXmbobg3pzeaQ?hostedIn=slideshare&amp;amp;page=upload&quot; width=&quot;450&quot;&gt;&lt;/iframe&gt;&lt;/p&gt;
&lt;p&gt;Effective use of machine resources has always been a challenge for PeopleSoft systems.&amp;nbsp; As systems move to the cloud that is in ever sharper focus.&amp;nbsp; In the cloud, you mostly pay for CPU.&amp;nbsp; You can generally have as much performance as you are willing to pay for, but every architectural decision you make has an immediate cost consequence. That drives out different behaviours.&amp;nbsp;&lt;/p&gt;&lt;p&gt;In the cloud, you rent hardware as an operational expense, rather than purchasing it as a capital expense.&amp;nbsp; If you are not short of CPU, you are probably spending too much. If you are short of CPU, then you need to the Oracle database&#39;s Resource Manager to manage what happens.&lt;/p&gt;&lt;p&gt;This presentation looks at how that played out at one PeopleSoft customer, who moved their GL reporting batch on Financials onto Exadata Cloud-at-Customer. The single most important thing they did was to clearly state their goals. That set the ground rules for sizing and configuring both their database and their application, implementing various database features, including defining a resource manager plan, as well as using partitioning, materialized views, compression, and in-memory.&amp;nbsp;&lt;/p&gt;&lt;p&gt;They have continued to improve performance and save money on their cloud costs.&amp;nbsp; They were recently able to switch off another CPU.&amp;nbsp;&lt;/p&gt;&lt;p&gt;The session also describes a generic resource plan that can be used as a starting point for any PeopleSoft system to which individual requirements can be added.&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;PeopleSoft DBA Blog:&amp;nbsp;&lt;a href=&quot;https://blog.psftdba.com/2024/03/psftplan-sample-oracle-database.html&quot;&gt;PSFT_PLAN: A Sample Oracle Database Resource Manager Plan for PeopleSoft&lt;/a&gt;&lt;/li&gt;&lt;li&gt;GitHub:&amp;nbsp;&lt;a href=&quot;https://github.com/davidkurtz/psscripts/blob/master/psft_resource_plan_simple.sql&quot; target=&quot;_blank&quot;&gt;psft_resource_plan_simple.sql&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;Finally, there are some ideas for prioritising Tuxedo server processes on Linux.&lt;/p&gt;&lt;div class=&quot;blogger-post-footer&quot;&gt;&lt;a href=&quot;http://www.go-faster.co.uk/&quot;&gt;©David Kurtz&lt;/a&gt;&lt;/div&gt;</description><link>https://blog.psftdba.com/2024/12/in-cloud-performance-is-instrumented-as.html</link><author>noreply@blogger.com (David Kurtz)</author><thr:total>0</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-25740336.post-68061353406921017</guid><pubDate>Tue, 26 Nov 2024 09:25:00 +0000</pubDate><atom:updated>2024-11-26T21:21:06.131+00:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">Cartesian</category><category domain="http://www.blogger.com/atom/ns#">PS/Query</category><title>PeopleSoft PS/Query: Finding Users&#39; Cartesian Joins</title><description>&lt;p&gt;Many PeopleSoft users like its ad hoc query tool because they can write their own queries directly on the system, without having to learn to write structured query language (SQL), or getting a developer to write it for them.&amp;nbsp; &amp;nbsp;&lt;/p&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;What is the Problem?&lt;/h3&gt;&lt;p&gt;It is easy for users to create poor queries, that either don&#39;t work as intended or can run for long periods, even indefinitely, consuming resources without ever producing results.&amp;nbsp; This can consume significant amounts of CPU, and in the cloud, that is mostly what you pay for!&amp;nbsp; The effect can be mitigated with the database&#39;s resource manager, but it is better not to do it in the first place.&lt;/p&gt;&lt;p&gt;One cause of long-running queries that I come across is missing join criteria leading the database to perform Cartesian Merge Joins.&amp;nbsp; I should stress that not all Cartesian joins are evil.&amp;nbsp; For example, in some data warehouse queries (e.g. GL nVision reporting), it can be a very effective strategy to Cartesian join dimension tables before visiting the fact table, especially if you can use Bloom filter a full scan on the fact table.&amp;nbsp; It works well with parallel query, and on engineered systems this can also be pushed down to the storage cells.&lt;/p&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Finding Execution Plans with Cartesian Joins&lt;/h3&gt;&lt;p&gt;The following query profiles database time by execution plan from ASH for SQL statements from PS/Queries run via the PSQUERY application engine program on a process scheduler.&amp;nbsp; It returns the longest-running statement for each execution plan.&lt;/p&gt;&lt;p&gt;The data is generated and processed through several common table expressions.&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;R returns the PSQUERY processes that ran in the time window of interest&lt;/li&gt;&lt;li&gt;P returns the execution plans captured by AWR that generate Cartesian products for which the SQL text is also captured.&amp;nbsp;&amp;nbsp;&lt;/li&gt;&lt;li&gt;X returns the ASH data for Cartesian join executions. When P is joined with the ASH data, then we just get the queries that performed Cartesian joins.&lt;/li&gt;&lt;li&gt;Y sums and groups the ASH data by statement and process&lt;/li&gt;&lt;li&gt;Z sums the data by execution plan and identifies the longest-running SQL statement for that plan.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;/p&gt;&lt;pre style=&quot;background-color: #eeeeee; font-family: &amp;quot;courier new&amp;quot;overflow: auto; line-height: 95%; text-align: left; width: 95%;&quot;&gt;&lt;span style=&quot;font-size: xx-small;&quot;&gt;REM &lt;a href=&quot;https://github.com/davidkurtz/psscripts/blob/master/qry_cartesianplans.sql&quot; target=&quot;_blank&quot;&gt;qry_cartesianplans.sql&lt;/a&gt;
WITH r as ( &lt;i&gt;/*processes of interest*/&lt;/i&gt;
SELECT /*+MATERIALIZE*/ r.oprid, r.prcsinstance, r.prcsname, r.begindttm, r.enddttm
,      DECODE(c.private_query_flag,&#39;Y&#39;,&#39;Private&#39;,&#39;N&#39;,&#39;Public&#39;) private_query_flag, c.qryname
FROM   psprcsrqst r
       LEFT OUTER JOIN ps_query_run_cntrl c ON c.oprid = r.oprid AND c.run_cntl_id = r.runcntlid
WHERE prcsname = &#39;PSQUERY&#39;
AND r.begindttm &amp;gt;= trunc(SYSDATE)-0+8/24
AND r.begindttm &amp;lt;= trunc(SYSDATE)-0+19/24
), p as ( &lt;i&gt;/*known Cartesian plans with SQL text*/&lt;/i&gt;
SELECT /*+MATERIALIZE*/ p.plan_hash_value, MAX(p.options) options
FROM   dbA_hist_sql_plan p
,      dba_hist_sqltext t
WHERE  t.sql_id = p.sql_id
AND    (p.id = 0 OR p.options = &#39;CARTESIAN&#39;)
GROUP BY p.plan_hash_Value
), x AS ( &lt;i&gt;/*ASH for processes*/&lt;/i&gt;
SELECT /*+materialize leading(r x)*/  r.prcsinstance, r.oprid, r.private_query_flag, r.qryname
,      h.event, x.dbid, h.sample_id, h.sample_time, h.instance_number
,      CASE WHEN h.module IS NULL       THEN REGEXP_SUBSTR(h.program, &#39;[^@]+&#39;,1,1)
            WHEN h.module LIKE &#39;PSAE.%&#39; THEN REGEXP_SUBSTR(h.module, &#39;[^.]+&#39;,1,2) 
            ELSE                             REGEXP_SUBSTR(h.module, &#39;[^.@]+&#39;,1,1) 
       END AS module
,      h.action
,      NULLIF(h.top_level_sql_id, h.sql_id) top_level_sql_id
,      h.sql_id, h.sql_plan_hash_value, h.force_matching_signature, h.sql_exec_id
,      h.session_id, h.session_serial#, h.qc_instance_id, h.qc_Session_id, h.qc_Session_serial#
,      f.name, p.options
,      NVL(usecs_per_row,1e7) usecs_per_row
,      CASE WHEN p.plan_hash_value IS NOT NULL THEN NVL(usecs_per_row,1e7) ELSE 0 END usecs_per_row2
FROM   dba_hist_snapshot x
,      dba_hist_active_sess_history h
       LEFT OUTER JOIN p ON p.plan_hash_value = h.sql_plan_hash_value
       LEFT OUTER JOIN dba_sql_profiles f ON h.force_matching_signature = f.signature
,      r
,      sysadm.psprcsque q
WHERE  h.SNAP_id = X.SNAP_id
AND    h.dbid = x.dbid
AND    h.instance_number = x.instance_number
AND    x.end_interval_time &amp;gt;= r.begindttm
AND    x.begin_interval_time &amp;lt;= NVL(r.enddttm,SYSDATE)
AND    h.sample_time BETWEEN r.begindttm AND NVL(r.enddttm,SYSDATE)
AND    q.prcsinstance = r.prcsinstance
AND    (  (h.module = r.prcsname AND h.action like &#39;PI=&#39;||r.prcsinstance||&#39;:Processing&#39;)
       OR  h.module like &#39;PSAE.&#39;||r.prcsname||&#39;.&#39;||q.sessionidnum)
), y as( &lt;i&gt;/*profile time by statement/process*/&lt;/i&gt;
SELECT prcsinstance, oprid, private_query_flag, qryname, sql_plan_hash_value, sql_id, force_matching_signature, name
,      dbid, module, action, top_level_sql_id
,      count(distinct qc_session_id||qc_session_serial#||sql_id||sql_exec_id) execs
,      sum(usecs_per_row)/1e6 ash_Secs
,      sum(usecs_per_Row2)/1e6 awr_secs
,      avg(usecs_per_row)/1e6*count(distinct sample_time) elapsed_secs
,      count(distinct instance_number||session_id||session_serial#) num_procs
,      max(options) options
FROM   x 
GROUP BY prcsinstance, oprid, private_query_flag, qryname, sql_plan_hash_value, sql_id, force_matching_signature, name
,      dbid, module, action, top_level_sql_id, qc_instance_id, qc_session_id, qc_session_serial#
), z as ( &lt;i&gt;/*find top statement per plan and sum across all executions*/&lt;/i&gt;
SELECT row_number() over (partition by force_matching_signature, sql_plan_hash_value order by awr_secs desc) plan_seq
,      prcsinstance, oprid, name, private_query_flag, NVL(qryname,action) qryname, options
,      sql_id, sql_plan_hash_Value, force_matching_signature
,      count(distinct sql_id) over (partition by force_matching_signature, sql_plan_hash_value) sql_ids
,      sum(execs) over (partition by force_matching_signature, sql_plan_hash_value) plan_execs
,      sum(ash_Secs) over (partition by force_matching_signature, sql_plan_hash_value) plan_ash_secs
,      sum(awr_Secs) over (partition by force_matching_signature, sql_plan_hash_value) plan_awr_secs
,      sum(elapsed_Secs) over (partition by force_matching_signature, sql_plan_hash_value) elap_secs
,      sum(num_procs) over (partition by force_matching_signature, sql_plan_hash_value) max_procs
FROM   y
)
Select z.*, z.plan_ash_secs/z.elap_secs eff_para
from   z
where  plan_seq = 1
and    sql_id is not null
and    plan_ash_secs &amp;gt;= 300
ORDER BY plan_ash_secs DESC
FETCH FIRST 50 ROWS ONLY
/&lt;/span&gt;&lt;/pre&gt;&lt;div&gt;There are two SQL statements for the same private query. XXX_GL_BJU run by user BXXXXXX that exhibited a Cartesian join.&lt;/div&gt;&lt;p&gt;&lt;/p&gt;&lt;pre style=&quot;background-color: #eeeeee; font-family: &amp;quot;courier new&amp;quot;overflow: auto; line-height: 80%; text-align: left; width: 95%;&quot;&gt;&lt;span&gt;&lt;span style=&quot;font-size: 50%;&quot;&gt;Plan    Plan
     Process                                          Private                                                           SQL Plan        Force Matching  SQL   Plan     ASH     AWR Elapsed  Max  Eff.
 #  Instance OPRID     NAME                           Query   QRYNAME                        OPTIONS   SQL_ID         Hash Value             Signature  IDs  Execs    Secs    Secs Seconds  Prc  Para
-- --------- --------- ------------------------------ ------- ------------------------------ --------- ------------- ----------- --------------------- ---- ------ ------- ------- ------- ---- -----
 1  12344342 NXXXXXX                                  Public  XXX_TRIAL_BALANCE_BY_BU_XXX_V2           c4zfcub2bnju8  2128864041   4468535744829993986    4      4  103473  103473  103473    4   1.0
 1  12344471 FXXXXXX                                  Public  XXXAM_FIN_GL_AP                          d8jnxzmgx20mq  4189069557  16033793374717384734    1      1   32599   32599   32599    1   1.0
 1  12344448 VXXXXXX                                  Private XXX1_LEDGERBAL1_UPRDAC_XXXX1             ftn7nz1xafh5z           0  15193759933860031914    2      2   20615   20615   20615    2   1.0
 1  12345574 BXXXXXX                                  Private XXX_GL_BJU                     CARTESIAN ab2v91h9zj3hv   603930234   4189289347608449750    1      1   16862   16862   16862    1   1.0
 1  12345681 BXXXXXX                                  Private XXX_GL_BJU                     CARTESIAN 05tphb379fu8j   603930234   6203431496815450503    1      1   15452   15452   15452    1   1.0
 1  12345852 WXXXXXX                                  Public  XXXINSOLVENTS_JRNL_DETAIL                51aw4ahxba0gq  3918624993  11145663850623390044    1      1   13435   13435   13435    1   1.0
 1  12345863 CXXXXXX                                  Public  XXX_COMMUTATIONS_JRNL_DTL                7q9kt75bh35dg           0  11985643849566057390    1      1   13283   13283   13283    1   1.0
 1  12344773 WXXXXXX                                  Private XXX_COMMUTATION_JRNL_DETAIL_2            361gck3w3mak7           0  18367721225324700858    1      2   12883   12883   12883    2   1.0
 1  12344682 DXXXXXX                                  Private COMBINED_JE_DETAIL_DV                    2gchgaf465ku5           0   5375582220398622005    1      1    9279    9279    9279    1   1.0
 1  12345618 DXXXXXX                                  Private COMBINED_JE_DETAIL_DV_NO_AFF             2q2faj9c6003u           0  15355473744647942117    1      1    5079    5079    5079    1   1.0
…&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;div&gt;The SQL statement and execution plan can be extracted from AWR using DBMS_XPLAN.DISPLAY_WORKLOAD_REPSITORY.&amp;nbsp;&amp;nbsp;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;&lt;pre style=&quot;background-color: #eeeeee; font-family: &amp;quot;courier new&amp;quot;overflow: auto; line-height: 95%; text-align: left; width: 95%;&quot;&gt;&lt;span style=&quot;font-size: xx-small;&quot;&gt;SELECT * FROM table(dbms_xplan.display_workload_repository(&#39;ab2v91h9zj3hv&#39;,603930234,&#39;ADVANCED +ADAPTIVE&#39;));
&lt;/span&gt;&lt;/pre&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;In this example, there are two similar SQL statements, with different force matching signatures, that produce the same execution plan.&amp;nbsp; The difference is that one has an IN list of 3 accounts, and the other has an equi-join to just one account.&amp;nbsp; This is enough to produce a different force matching signature.&amp;nbsp; This is why I often group ASH data by execution plan hash value.&amp;nbsp; Even if the SQL statement is different, if the execution plan is the same, then the issues and solutions tend to be the same.&lt;/p&gt;&lt;p&gt;The statements have been reformated to make them easier to read.&amp;nbsp; Both are just joins between two objects.&amp;nbsp; There are criteria on PS_JRNL_DRILL_VW (a view on PS_JRNL_LN), but there are no join criteria between it and its parent table JRNL_HEADER, thus a meaningless Cartesian product that joins every journal line to every journal header was created and sorted.&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;pre style=&quot;background-color: #eeeeee; font-family: &amp;quot;courier new&amp;quot;overflow: auto; line-height: 95%; text-align: left; width: 95%;&quot;&gt;&lt;span style=&quot;font-size: xx-small;&quot;&gt;SQL_ID ab2v91h9zj3hv 
--------------------
SELECT A.BUSINESS_UNIT, A.JOURNAL_ID,
TO_CHAR(A.JOURNAL_DATE,&#39;YYYY-MM-DD&#39;), B.DESCR254, A.ACCOUNT,
A.LINE_DESCR, SUM( A.MONETARY_AMOUNT), A.LEDGER, B.ACCOUNTING_PERIOD,
B.SOURCE, B.OPRID, A.PRODUCT, A.CLASS_FLD, A.PROGRAM_CODE,
A.CHARTFIELD1, A.CHARTFIELD3, A.CURRENCY_CD, A.FOREIGN_CURRENCY 
FROM PS_JRNL_DRILL_VW A, PS_JRNL_HEADER B 
WHERE ( A.BUSINESS_UNIT IN(&#39;12341&#39;,&#39;12347&#39;) 
AND A.LEDGER IN (&#39;CORE&#39;,&#39;LOCAL_ADJ&#39;,&#39;LOCAL_ADJ2&#39;) 
AND A.&lt;b&gt;ACCOUNT IN (&#39;1234510040&#39;,&#39;1234510000&#39;,&#39;1234510060&#39;)&lt;/b&gt; 
AND A.ACCOUNTING_PERIOD BETWEEN 1 AND 12 AND A.FISCAL_YEAR = 2023) 
GROUP BY A.BUSINESS_UNIT, A.JOURNAL_ID, A.JOURNAL_DATE, B.DESCR254, A.ACCOUNT,
A.LINE_DESCR, A.LEDGER, B.ACCOUNTING_PERIOD, B.SOURCE, B.OPRID,
A.PRODUCT, A.CLASS_FLD, A.PROGRAM_CODE, A.CHARTFIELD1, A.CHARTFIELD3,
A.CURRENCY_CD, A.FOREIGN_CURRENCY ORDER BY 11&lt;/span&gt;&lt;/pre&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;pre style=&quot;background-color: #eeeeee; font-family: &amp;quot;courier new&amp;quot;overflow: auto; line-height: 95%; text-align: left; width: 95%;&quot;&gt;&lt;span style=&quot;font-size: xx-small;&quot;&gt;SQL_ID 05tphb379fu8j
--------------------
SELECT A.BUSINESS_UNIT, A.JOURNAL_ID,
TO_CHAR(A.JOURNAL_DATE,&#39;YYYY-MM-DD&#39;), B.DESCR254, A.ACCOUNT,
A.LINE_DESCR, SUM( A.MONETARY_AMOUNT), A.LEDGER, B.ACCOUNTING_PERIOD,
B.SOURCE, B.OPRID, A.PRODUCT, A.CLASS_FLD, A.PROGRAM_CODE,
A.CHARTFIELD1, A.CHARTFIELD3, A.CURRENCY_CD, A.FOREIGN_CURRENCY 
FROM PS_JRNL_DRILL_VW A, PS_JRNL_HEADER B 
WHERE ( A.BUSINESS_UNIT IN(&#39;12341&#39;,&#39;12347&#39;) 
AND A.LEDGER IN (&#39;CORE&#39;,&#39;LOCAL_ADJ&#39;,&#39;LOCAL_ADJ2&#39;) 
AND A.&lt;b&gt;ACCOUNT = &#39;1234510000&#39;&lt;/b&gt; 
AND A.ACCOUNTING_PERIOD BETWEEN 1 AND 12 
AND A.FISCAL_YEAR = 2023) 
GROUP BY A.BUSINESS_UNIT, A.JOURNAL_ID, A.JOURNAL_DATE, B.DESCR254, A.ACCOUNT
, A.LINE_DESCR, A.LEDGER, B.ACCOUNTING_PERIOD, B.SOURCE, B.OPRID
, A.PRODUCT, A.CLASS_FLD,A.PROGRAM_CODE, A.CHARTFIELD1, A.CHARTFIELD3
, A.CURRENCY_CD, A.FOREIGN_CURRENCY ORDER BY 11
&lt;/span&gt;&lt;/pre&gt;&lt;div&gt;Line 2 of the execution plan reports a MERGE JOIN CARTESIAN operation that feeds into the SORT GROUP operation at line 1.&lt;/div&gt;&lt;p&gt;&lt;/p&gt;&lt;pre style=&quot;background-color: #eeeeee; font-family: &amp;quot;courier new&amp;quot;overflow: auto; line-height: 95%; text-align: left; width: 95%;&quot;&gt;&lt;span style=&quot;font-size: 75%;&quot;&gt;Plan hash value: 603930234

---------------------------------------------------------------------------------------------------------------------------------------------------
| Id  | Operation                                        | Name           | Rows  | Bytes | Cost (%CPU)| Time     | Pstart| Pstop | Inst   |IN-OUT|
---------------------------------------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                                 |                |       |       | 84648 (100)|          |       |       |        |      |
|   1 |  SORT GROUP BY                                   |                |    57 |  9063 | 84648   (1)| 00:00:04 |       |       |        |      |
|   2 |   &lt;b&gt;MERGE JOIN CARTESIAN&lt;/b&gt;                           |                |    57 |  9063 | 84647   (1)| 00:00:04 |       |       |        |      |
|   3 |    NESTED LOOPS                                  |                |     1 |   145 |  1636   (0)| 00:00:01 |       |       |        |      |
|   4 |     VIEW                                         | PS_JRNL_HEADER |   112 |  4032 |  1188   (0)| 00:00:01 |       |       |        |      |
|   5 |      UNION-ALL                                   |                |       |       |            |          |       |       |        |      |
|   6 |       REMOTE                                     | PS_JRNL_HEADER |    76 |  5624 |    18   (0)| 00:00:01 |       |       | FSARC~ | R-&amp;gt;S |
|   7 |       INLIST ITERATOR                            |                |       |       |            |          |       |       |        |      |
|   8 |        TABLE ACCESS BY INDEX ROWID BATCHED       | PS_JRNL_HEADER | 16679 |   586K| 11634   (1)| 00:00:01 |       |       |        |      |
|*  9 |         INDEX RANGE SCAN                         | PSEJRNL_HEADER | 16679 |       |   347   (0)| 00:00:01 |       |       |        |      |
|  10 |     VIEW                                         | PS_JRNL_LN     |     1 |   109 |     4   (0)| 00:00:01 |       |       |        |      |
|  11 |      UNION-ALL PARTITION                         |                |       |       |            |          |       |       |        |      |
|* 12 |       FILTER                                     |                |       |       |            |          |       |       |        |      |
|  13 |        REMOTE                                    | PS_JRNL_LN     |     1 |   217 |     5   (0)| 00:00:01 |       |       | FSARC~ | R-&amp;gt;S |
|* 14 |       FILTER                                     |                |       |       |            |          |       |       |        |      |
|  15 |        PARTITION RANGE SINGLE                    |                |     1 |   109 |     5   (0)| 00:00:01 |   KEY |   KEY |        |      |
|* 16 |         TABLE ACCESS BY LOCAL INDEX ROWID BATCHED| PS_JRNL_LN     |     1 |   109 |     5   (0)| 00:00:01 |   KEY |   KEY |        |      |
|* 17 |          INDEX RANGE SCAN                        | PS_JRNL_LN     |     1 |       |     4   (0)| 00:00:01 |   KEY |   KEY |        |      |
|  18 |    BUFFER SORT                                   |                |  7749K|   103M| 84644   (1)| 00:00:04 |       |       |        |      |
|  19 |     VIEW                                         | PS_JRNL_HEADER |  7749K|   103M| 83011   (1)| 00:00:04 |       |       |        |      |
|  20 |      UNION-ALL                                   |                |       |       |            |          |       |       |        |      |
|  21 |       REMOTE                                     | PS_JRNL_HEADER |  5698K|  1880M| 50467   (1)| 00:00:02 |       |       | FSARC~ | R-&amp;gt;S |
|  22 |       TABLE ACCESS STORAGE FULL                  | PS_JRNL_HEADER |  2050K|    86M| 32544   (1)| 00:00:02 |       |       |        |      |
---------------------------------------------------------------------------------------------------------------------------------------------------&lt;/span&gt;&lt;/pre&gt;&lt;div&gt;A profile of database time for that execution plan by
event and plan line ID shows that most of the time is spent on line 1, sorting the output of the Cartesian product.&lt;/div&gt;&lt;div&gt;&lt;pre style=&quot;background-color: #eeeeee; font-family: &amp;quot;courier new&amp;quot;overflow: auto; line-height: 95%; text-align: left; width: 95%;&quot;&gt;&lt;span style=&quot;font-size: xx-small;&quot;&gt;                                                                                                                    Stmt    Stmt
   SQL Plan SQL Plan                                                                  H   E I     ASH                ASH    Elap
 Hash Value  Line ID EVENT                                                            P P x M    Secs  ELAP_SECS    Secs    Secs
----------- -------- ---------------------------------------------------------------- - - - - ------- ---------- ------- -------
  603930234        1 CPU+CPU Wait                                                     N N Y N  217091 23405.3608  299088   32314
                  18 direct path read temp                                            N N Y N   64395 7034.44748  299088   32314
                  18 CPU+CPU Wait                                                     N N Y N   16998 1812.39445  299088   32314
                   1 ASM IO for non-blocking poll                                     N N Y N     195 20.4802032  299088   32314
                  21 CPU+CPU Wait                                                     N N Y N     195   20.47995  299088   32314
                  16 CPU+CPU Wait                                                     N N Y N     113   10.24021  299088   32314
                     CPU+CPU Wait                                                     N N Y N     103   10.25244  299088   32314&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div&gt;This query never finished because the Cartesian product was so large.&amp;nbsp; The time recorded was spent in two executions that were eventually cancelled by system operators.&lt;/div&gt;&lt;p&gt;The answer in this particular case is to fix the code.&amp;nbsp; We have to go back to the user, explain why it is necessary to join parent and child tables and get them to correct their PS/Query.&lt;/p&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Finding PS/Queries Without Joins on Related Records&lt;/h3&gt;&lt;p&gt;In PeopleSoft, the parent of a child record is recorded on PSRECDEFN in the column &lt;a href=&quot;https://www2.go-faster.co.uk/peopletools/psrecdefn.htm#parentrecname&quot; target=&quot;_blank&quot;&gt;PARENTRECNAME&lt;/a&gt;.&amp;nbsp; However, this does not translate into a foreign key relationship in any database supported by PeopleSoft.&amp;nbsp; This is part of PeopleSoft&#39;s original platform-agnosticism.&amp;nbsp; Not all databases previously supported by PeopleSoft supported database enforced referential integrity.&amp;nbsp; Therefore it never became part of the implementation, and there is no guarantee that the applications were written in such a way to honour foreign-key constraints (i.e. insert parents before children, delete children before parents etc.).&lt;/p&gt;&lt;p&gt;The below query looks at pairs of parent-child records in each select block of each PS/Query and counts the number of key columns for which there are criteria on the child record that are joined to the parent record.&amp;nbsp; It is restricted to just the journal header/line tables and views.&lt;/p&gt;&lt;p&gt;It returns rows where no joined key columns are found.&amp;nbsp; These queries are therefore suspected of being faulty.&amp;nbsp; However, there may be false positives where child records are joined to grandparents rather than immediate parents.&amp;nbsp; Such an approach in SQL is perfectly valid, and can even result in better performance.&amp;nbsp;&amp;nbsp;&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;pre style=&quot;background-color: #eeeeee; font-family: &amp;quot;courier new&amp;quot;overflow: auto; line-height: 95%; text-align: left; width: 95%;&quot;&gt;&lt;span style=&quot;font-size: 75%;&quot;&gt;WITH x as (
SELECT r1.oprid, r1.qryname, r1.selnum
, r1.rcdnum rcdnum1, r1.recname recname1, r1.corrname corrname1
, r2.rcdnum rcdnum2, r2.recname recname2, r2.corrname corrname2
, (SELECT count(*) 
   FROM &lt;a href=&quot;https://www2.go-faster.co.uk/peopletools/psqryfield.htm&quot; target=&quot;_blank&quot;&gt;psqryfield&lt;/a&gt; qf1 --INNER JOIN psrecfielddb f1 ON f1.recname = r1.recname AND f1.fieldname = qf1.fieldname
   ,    psqryfield qf2 INNER JOIN &lt;a href=&quot;https://www2.go-faster.co.uk/peopletools/psrecfielddb.htm&quot; target=&quot;_blank&quot;&gt;psrecfielddb&lt;/a&gt; f2 ON f2.recname = r2.recname AND f2.fieldname = qf2.fieldname 
                                                 AND MOD(f2.useedit,2)=1 /*key fields only*/
   , &lt;a href=&quot;https://www2.go-faster.co.uk/peopletools/psqrycriteria.htm&quot; target=&quot;_blank&quot;&gt;psqrycriteria&lt;/a&gt; c
   WHERE qf1.oprid = r1.oprid AND qf1.qryname = r1.qryname AND qf1.selnum = r1.selnum AND qf1.recname = r1.recname AND qf1.fldrcdnum = r1.rcdnum
   AND   qf2.oprid = r2.oprid AND qf2.qryname = r2.qryname AND qf2.selnum = r2.selnum AND qf2.recname = r2.recname AND qf2.fldrcdnum = r2.rcdnum
   AND    c.oprid = r1.oprid AND c.qryname = r1.qryname AND  c.selnum = r1.selnum 
   AND   (  (c.lcrtselnum = r1.selnum AND c.lcrtfldnum = qf1.fldnum AND c.r1crtselnum = r2.selnum AND c.r1crtfldnum = qf2.fldnum)
         OR (c.lcrtselnum = r2.selnum AND c.lcrtfldnum = qf2.fldnum AND c.r1crtselnum = r1.selnum AND c.r1crtfldnum = qf1.fldnum))
-- AND rownum = 1
  ) num_key_fields
FROM &lt;a href=&quot;https://www2.go-faster.co.uk/peopletools/psrecdefn.htm&quot; target=&quot;_blank&quot;&gt;psrecdefn&lt;/a&gt; r
, &lt;a href=&quot;https://www2.go-faster.co.uk/peopletools/psqryrecord.htm&quot; target=&quot;_blank&quot;&gt;psqryrecord&lt;/a&gt; r1
  INNER JOIN psqryrecord r2 ON r1.oprid = r2.oprid AND r1.qryname = r2.qryname AND r1.selnum = r2.selnum AND r1.rcdnum != r2.rcdnum --AND r1.corrname &amp;lt; r2.corrname
WHERE &lt;b&gt;r.recname = r2.recname AND r.parentrecname = r1.recname&lt;/b&gt;
)
SELECT x.* FROM x
WHERE num_key_fields = 0
&lt;b&gt;AND recname1 IN(&#39;JRNL_HEADER&#39;)
AND recname2 IN(&#39;JRNL_LN&#39;,&#39;JRNL_DRILL_VW&#39;)
&lt;/b&gt;ORDER BY 1,2,3
/&lt;/span&gt;&lt;/pre&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;However, these queries may not have been run recently.&amp;nbsp; Users tend to write queries, save a modification as a new version, and then abandon the old version.&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;pre style=&quot;background-color: #eeeeee; font-family: &amp;quot;courier new&amp;quot;overflow: auto; line-height: 95%; text-align: left; width: 95%;&quot;&gt;&lt;span style=&quot;font-size: x-small;&quot;&gt;                                          Sel Rec1                    Cor Rec2                    Cor #Key
OPRID     QRYNAME                           #    # Record 1           #1     # Record             #2  Flds
--------- ------------------------------ ---- ---- ------------------ --- ---- ------------------ --- ----
          2_XX_CHI_JOURNAL_MES2_RE          1    1 JRNL_HEADER        A      2 JRNL_LN            B      0
          12300_GL_ACCOUNT_DETAIL           1    1 JRNL_HEADER        A      2 JRNL_LN            B      0
          123_DK                            1    1 JRNL_HEADER        A      2 JRNL_LN            B      0
          123_NEW                           1    1 JRNL_HEADER        A      2 JRNL_LN            B      0
          12345_ACCRUAL_JE_DETAILS          1    1 JRNL_HEADER        A      2 JRNL_LN            C      0
          12345_ACCRUAL_JE_DETAILS_V2       1    1 JRNL_HEADER        A      2 JRNL_LN            C      0
          12345_ACCRUAL_JE_DETAILS_V3       1    1 JRNL_HEADER        A      2 JRNL_LN            C      0
          12345_HARDSOFT_JE_DETAILS_V3      1    1 JRNL_HEADER        A      2 JRNL_LN            C      0
          12345_BM_CURR_ACTIVITY2           1    1 JRNL_HEADER        A      2 JRNL_LN            B      0
          AAIC_CBP_POOLS                    1    1 JRNL_HEADER        A      2 JRNL_LN            B      0
…
&lt;/span&gt;&lt;/pre&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;I demonstrated how to &lt;a href=&quot;https://blog.psftdba.com/2024/11/peoplesoft-psquery-identify-long.html&quot;&gt;identify long-running PS/Queries on the process schedulers in an earlier blog post&lt;/a&gt;. The following query merges in that query, so that it only considers queries that have run on a process scheduler within the purge period, for which join criteria may be missing.&amp;nbsp; They are sorted by descending execution time.&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;pre style=&quot;background-color: #eeeeee; font-family: &amp;quot;courier new&amp;quot;overflow: auto; line-height: 95%; text-align: left; width: 95%;&quot;&gt;&lt;span style=&quot;font-size: 75%;&quot;&gt;REM &lt;a href=&quot;https://github.com/davidkurtz/psscripts/blob/master/qry_missingjoins.sql&quot; target=&quot;_blank&quot;&gt;qry_missingjoins.sql&lt;/a&gt;
WITH q1 as (
SELECT r.prcsinstance
, r.oprid runoprid, r.runcntlid
, DECODE(c.private_query_flag,&#39;Y&#39;,&#39;Private&#39;,&#39;N&#39;,&#39;Public&#39;) private_query_flag
, DECODE(c.private_query_flag,&#39;Y&#39;,r.oprid,&#39; &#39;) oprid
, c.qryname
, CAST(begindttm AS DATE) begindttm
, CAST(enddttm AS DATE) enddttm
, runstatus
, (CAST(NVL(enddttm,SYSDATE) AS DATE)-CAST(begindttm AS DATE))*86400 exec_Secs
FROM psprcsrqst r
  LEFT OUTER JOIN ps_query_run_cntrl c ON c.oprid = r.oprid AND c.run_cntl_id = r.runcntlid
WHERE prcsname = &#39;PSQUERY&#39;
AND dbname IN(select DISTINCT dbname from ps.psdbowner)
--AND r.begindttm &amp;gt;= trunc(SYSDATE)-2+8/24
--AND r.begindttm &amp;lt;= trunc(SYSDATE)-2+19/24
), q as (
Select /*+MATERIALIZE*/ oprid, qryname
, SUM(exec_secs) exec_secs
, COUNT(*) num_execs
, COUNT(DECODE(runstatus,&#39;9&#39;,1,NULL)) complete_execs
, COUNT(DISTINCT runoprid) runoprids
FROM q1
GROUP BY oprid, qryname
), x as (
SELECT r1.oprid, r1.qryname, r1.selnum
, r1.rcdnum rcdnum1, r1.recname recname1, r1.corrname corrname1
, r2.rcdnum rcdnum2, r2.recname recname2, r2.corrname corrname2
, (SELECT count(*) 
   FROM psqryfield qf1 --INNER JOIN psrecfielddb f1 ON f1.recname = r1.recname AND f1.fieldname = qf1.fieldname
   ,    psqryfield qf2 INNER JOIN psrecfielddb f2 ON f2.recname = r2.recname AND f2.fieldname = qf2.fieldname AND MOD(f2.useedit,2)=1
   , psqrycriteria c
   WHERE qf1.oprid = r1.oprid AND qf1.qryname = r1.qryname AND qf1.selnum = r1.selnum AND qf1.recname = r1.recname AND qf1.fldrcdnum = r1.rcdnum
   AND   qf2.oprid = r2.oprid AND qf2.qryname = r2.qryname AND qf2.selnum = r2.selnum AND qf2.recname = r2.recname AND qf2.fldrcdnum = r2.rcdnum
   AND    c.oprid = r1.oprid AND  c.qryname = r1.qryname AND  c.selnum = r1.selnum 
   AND   (  (c.lcrtselnum = r1.selnum AND c.lcrtfldnum = qf1.fldnum AND c.r1crtselnum = r2.selnum AND c.r1crtfldnum = qf2.fldnum)
         OR (c.lcrtselnum = r2.selnum AND c.lcrtfldnum = qf2.fldnum AND c.r1crtselnum = r1.selnum AND c.r1crtfldnum = qf1.fldnum))
   AND rownum = 1
  ) num_key_fields
FROM psrecdefn r
, psqryrecord r1
  INNER JOIN psqryrecord r2 ON r1.oprid = r2.oprid AND r1.qryname = r2.qryname AND r1.selnum = r2.selnum AND r1.rcdnum != r2.rcdnum --AND r1.corrname &amp;lt; r2.corrname
WHERE r.recname = r2.recname AND r.parentrecname = r1.recname
)
SELECT /*+LEADING(Q)*/ q.*, x.selnum
, x.rcdnum1, x.recname1, x.corrname1
, x.rcdnum2, x.recname2, x.corrname2, x.num_key_fields
FROM x
  INNER JOIN q ON q.oprid = x.oprid AND q.qryname = x.qryname
WHERE num_key_fields = 0
AND exec_secs &amp;gt;= 600
ORDER BY exec_secs desc

/&lt;/span&gt;&lt;/pre&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;Now I have a list of candidate queries that have been used recently and may be missing joins that I investigate further.&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;pre style=&quot;background-color: #eeeeee; font-family: &amp;quot;courier new&amp;quot;overflow: auto; line-height: 95%; text-align: left; width: 95%;&quot;&gt;&lt;span style=&quot;font-size: 75%;&quot;&gt;                                                                                          Sel Rec1                    Cor Rec2                    Cor #Key
OPRID     QRYNAME                         EXEC_SECS  NUM_EXECS COMPLETE_EXECS  RUNOPRIDS    #    # Record 1           #1     # Record 2           #2  Flds
--------- ------------------------------ ---------- ---------- -------------- ---------- ---- ---- ------------------ --- ---- ------------------ --- ----
UKXXXXXXX AR_VENDOR_LOCATION_DETAILB         264317        361            360          1    1    1 VENDOR             A      8 VNDR_LOC_SCROL     H      0
          XX_COL_MOV_ALT_ACCT2_PERIO         193692       2096           2051         14    1    1 JRNL_HEADER        A      3 OPEN_ITEM_GL       C      0
          APC_123_LEDGER_ACTIVITY_BY_BU      151438       2959           2938         73    2    1 JRNL_HEADER        B      2 JRNL_LN            C      0
MXXXXXX   MT_AUSTRALIA_TAX_PMTS              137471         36             28          1    1    1 JRNL_HEADER        A      2 JRNL_LN            B      0
          XX_PAN_ASIA_JOURNALS_REF           135825         48             47          4    1    1 JRNL_HEADER        A      5 JRNL_OPENITM_VW    E      0
          XXX_STKCOMP_LIFE                   120537        526            523          1    1    1 JRNL_HEADER        A      2 JRNL_LN            B      0
          XXX_123_TB_LEDGER_BAL_BU           100848       2093           2044         17    3    1 JRNL_HEADER        B      2 JRNL_LN            C      0
KXXXXXX   XXX_JRNL_LIST_AUDIT_KL              99843        489            482          1    1    2 JRNL_HEADER        B      1 JRNL_DRILL_VW      A      0
          XXX_JE_ID_QUERY                     86106        156            151          1    1    1 JRNL_HEADER        A      2 JRNL_LN            C      0
          XXX_ACTIVITY_DETAILS_2              85356        336            302          5    1    1 JRNL_HEADER        A      2 JRNL_LN            B      0
…&lt;/span&gt;&lt;/pre&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;Anyone can inspect any public queries, but you must be logged in as the owner of a private query to be able to see it.&lt;/p&gt;&lt;p&gt;The scripts in this article can be downloaded from GitHub&amp;nbsp;&lt;a href=&quot;https://github.com/davidkurtz/psscripts&quot; target=&quot;_blank&quot;&gt;davidkurtz/psscripts&lt;/a&gt;.&lt;/p&gt;&lt;div class=&quot;blogger-post-footer&quot;&gt;&lt;a href=&quot;http://www.go-faster.co.uk/&quot;&gt;©David Kurtz&lt;/a&gt;&lt;/div&gt;</description><link>https://blog.psftdba.com/2024/11/psquery-cartesian.html</link><author>noreply@blogger.com (David Kurtz)</author><thr:total>0</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-25740336.post-4484665071245588292</guid><pubDate>Mon, 25 Nov 2024 13:53:00 +0000</pubDate><atom:updated>2025-02-18T20:27:12.582+00:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">Application Engine</category><category domain="http://www.blogger.com/atom/ns#">PS/Query</category><category domain="http://www.blogger.com/atom/ns#">PSQUERY</category><category domain="http://www.blogger.com/atom/ns#">Resource Manager</category><title>PeopleSoft PS/Query: Identify Long Running Queries (on Process Schedulers)</title><description>&lt;div&gt;&lt;i&gt;This is the first of a series in which I will share some of my PeopleSoft scripts, and explain how they work.&lt;/i&gt;&lt;/div&gt;&lt;div&gt;Many PeopleSoft users like its ad hoc query tool because they can write their own queries directly on the system, without having to learn to write structured query language (SQL), or getting a developer to write it for them.&amp;nbsp;&amp;nbsp;&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;What is the Problem?&lt;/h3&gt;&lt;div&gt;This tool is disliked and even feared by database administrators (DBAs) and system administrators, because it is easy for users to create poor queries, that either don&#39;t work as intended or can run for long periods, sometimes indefinitely, without even producing results.&amp;nbsp; This can consume significant amounts of CPU.&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Managing Queries Scheduled on the Process Scheduler&lt;/h3&gt;&lt;div&gt;The PSQUERY application engine program runs queries on the process scheduler. Users should be encouraged to use this rather than running them online.&amp;nbsp;&amp;nbsp;&lt;/div&gt;&lt;div&gt;Queries run online via the PeopleSoft Internet Architecture (PIA) cannot be managed.&amp;nbsp;&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;There is no limit to the number of queries that users can initiate concurrently.&amp;nbsp;&amp;nbsp;&lt;/li&gt;&lt;li&gt;The number that can actually execute concurrently is limited by the number of PSQRYSRV processes in each application server domain.&amp;nbsp; Any additional requests will simply queue up in Tuxedo.&lt;/li&gt;&lt;li&gt;It is possible to set maximum execution times in the PeopleSoft configuration, on the ICQuery service on the PSQRYSRV server in the application server.&amp;nbsp;&amp;nbsp;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;It is easier to manage and monitor the queries run in PSQUERY processes on the process scheduler.&amp;nbsp; They don&#39;t put any load on the PIA, but they put load on the database.&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;A system-wide maximum number of concurrently executing instances of the application engine program can be set on the process definition.&lt;/li&gt;&lt;li&gt;A maximum number of concurrently executing instances of the application engine program per process scheduler can be set (by using a process class).&lt;/li&gt;&lt;li&gt;The application engine, or its process class, can be given a lower priority so that other queued processes are run in preference.&lt;/li&gt;&lt;/ul&gt;&lt;p style=&quot;text-align: left;&quot;&gt;PS/Queries run either in the PIA or on the process scheduler can be mapped to low-priority consumer groups in an Oracle database resource manager plan so that they do not starve the rest of the system of CPU (see &lt;a href=&quot;https://blog.psftdba.com/2024/03/psftplan-sample-oracle-database.html&quot;&gt;PeopleSoft DBA Blog: PSFT_PLAN: A Sample Oracle Database Resource Manager Plan for PeopleSoft&lt;/a&gt;).&lt;br /&gt;A maximum run time, or maximum estimated run time, can be defined for a consumer group.&amp;nbsp; If the limit is breached an Oracle error is raised:&amp;nbsp;&lt;i style=&quot;background-color: white; color: #333333; font-family: Georgia, serif; font-size: 13px; text-align: center;&quot;&gt;ORA-00040: active time limit exceeded - call aborte&lt;/i&gt;&lt;span style=&quot;background-color: white; color: #333333; font-family: Georgia, serif; font-size: 13px; text-align: center;&quot;&gt;&lt;i&gt;d&lt;/i&gt;&lt;/span&gt;&lt;span style=&quot;background-color: white; color: #333333; font-family: Georgia, serif; font-size: 13px; text-align: center;&quot;&gt;.&lt;i&gt;&amp;nbsp;&lt;/i&gt;&lt;/span&gt;In the PIA, the error message is simply presented to the user.&amp;nbsp; The scheduled PSQUERY application engine process will terminate and the error will be logged.&amp;nbsp; In both cases, the user has to recognise the error message and understand what it means.&amp;nbsp; Otherwise, they will raise the issue with support.&lt;br /&gt;The various methods of setting maximum execution time limits are quite blunt instruments.&amp;nbsp; They are essentially one-size-fits-all approaches.&amp;nbsp; Typically, some queries are expected to run for a long time, and then the limits must be set to accommodate them.&amp;nbsp;&lt;/p&gt;&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Queries Scheduled on the Process Scheduler&lt;/h3&gt;&lt;p style=&quot;text-align: left;&quot;&gt;I can query who has run which queries, and how long they ran for.&amp;nbsp; Simply outer join the run control record for the PSQUERY application engine (PS_QUERY_RUN_CNTL) to the process scheduler request table (&lt;a href=&quot;https://www2.go-faster.co.uk/peopletools/psprcsrqst.htm&quot; target=&quot;_blank&quot;&gt;PSPRCSRQST&lt;/a&gt;).&lt;/p&gt;&lt;div&gt;In this case, I am interested in&amp;nbsp;&lt;/div&gt;&lt;div&gt;•&lt;span style=&quot;white-space: pre;&quot;&gt;	&lt;/span&gt;the top 50 PS/Queries by cumulative execution&lt;/div&gt;&lt;div&gt;•&lt;span style=&quot;white-space: pre;&quot;&gt;	&lt;/span&gt;with a cumulative execution time of over 5 minutes (300s)&lt;/div&gt;&lt;div&gt;•&lt;span style=&quot;white-space: pre;&quot;&gt;	&lt;/span&gt;that were scheduled yesterday between 8am and 7pm&lt;/div&gt;
&lt;pre style=&quot;background-color: #eeeeee; font-family: &amp;quot;courier new&amp;quot;overflow: auto; line-height: 95%; text-align: left; width: 95%;&quot;&gt;&lt;span style=&quot;font-size: xx-small;&quot;&gt;REM &lt;a href=&quot;http://qry_missingjoins.sql&quot; target=&quot;_blank&quot;&gt;qry_missingjoins.sql&lt;/a&gt;
WITH x as (
SELECT r.prcsinstance, r.oprid, r.runcntlid
,      DECODE(c.private_query_flag,&#39;Y&#39;,&#39;Private&#39;,&#39;N&#39;,&#39;Public&#39;) private_query_flag, c.qryname
,      CAST(begindttm AS DATE) begindttm
,      CAST(enddttm AS DATE) enddttm
,      runstatus
,      (CAST(NVL(enddttm,SYSDATE) AS DATE)-CAST(begindttm AS DATE))*86400 exec_Secs
FROM   psprcsrqst r
  LEFT OUTER JOIN ps_query_run_cntrl c ON c.oprid = r.oprid AND c.run_cntl_id = r.runcntlid
WHERE  prcsname = &#39;PSQUERY&#39;
AND    r.begindttm &amp;gt;= TRUNC(SYSDATE)-0+8/24 /*from 8am*/
AND    r.begindttm &amp;lt;= TRUNC(SYSDATE)-0+19/24 /*to 7pm*/
)
SELECT x.* FROM x
WHERE  exec_Secs &amp;gt;= 300 /*Over 5 minutes*/
ORDER BY exec_secs desc /*descending order of elapsed time*/
FETCH FIRST 50 ROWS ONLY /*top 50 ROWS ONLY*/
/&lt;/span&gt;&lt;/pre&gt;
&lt;div&gt;I now have a profile of top queries that I can use to direct further investigation.&lt;/div&gt;
&lt;pre style=&quot;background-color: #eeeeee; font-family: &amp;quot;courier new&amp;quot;overflow: auto; line-height: 75%; text-align: left; width: 95%;&quot;&gt;&lt;span style=&quot;font-size: xx-small;&quot;&gt;  Process                                           Private                                                                    Run     Exec
 Instance OPRID      RUNCNTLID                      Query   QRYNAME                        BEGINDTTM         ENDDTTM           Stat    Secs
--------- ---------- ------------------------------ ------- ------------------------------ ----------------- ----------------- ---- -------
 12344471 F******    ***AM_FIN_GL_AP                Public  ***AM_FIN_GL_AP                10:06:21 19.**.** 19:08:52 19.**.** 8      32551
 12344342 N******    ownxxxxxxxxxxxx                Public  ***_TRIAL_BALANCE_BY_BU_***_V2 09:41:58 19.**.** 18:20:09 19.**.** 10     31091
 12344336 N******    ojnxxxxxxxxxx                  Public  ***_TRIAL_BALANCE_BY_BU_***    09:40:27 19.**.** 16:51:11 19.**.** 10     25844
 12345209 N******    eowxxxxxxxxxxxxx               Public  ***_TRIAL_BALANCE_BY_BU_***    12:41:17 19.**.** 19:08:30 19.**.** 8      23233
 12345213 N******    iwoxxxxxxxxxxxxx               Public  ***_TRIAL_BALANCE_BY_BU_***_V2 12:41:53 19.**.** 19:08:56 19.**.** 8      23223
 12345574 B******    gl                             Private ***_GL_BJU                     14:27:32 19.**.** 19:08:59 19.**.** 8      16887
 12345681 B******    gl                             Private ***_GL_BJU                     14:51:06 19.**.** 19:09:02 19.**.** 8      15476
 12345852 W******    insolvents                     Public  ***INSOLVENTS_JRNL_DETAIL      15:24:41 19.**.** 19:09:04 19.**.** 8      13463
…
                                                                                                                                    -------
sum                                                                                                                                  268112&lt;/span&gt;&lt;/pre&gt;&lt;div&gt;Notes:&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Some details have been redacted from this real-world example.&lt;/li&gt;&lt;li&gt;The result is not guaranteed to be completely accurate.&amp;nbsp; A user might have reused a run control record and can only get the current value.&lt;/li&gt;&lt;li&gt;This and other scripts can be downloaded from GitHub &lt;a href=&quot;https://github.com/davidkurtz/psscripts&quot; target=&quot;_blank&quot;&gt;davidkurtz/psscripts&lt;/a&gt;.&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class=&quot;blogger-post-footer&quot;&gt;&lt;a href=&quot;http://www.go-faster.co.uk/&quot;&gt;©David Kurtz&lt;/a&gt;&lt;/div&gt;</description><link>https://blog.psftdba.com/2024/11/peoplesoft-psquery-identify-long.html</link><author>noreply@blogger.com (David Kurtz)</author><thr:total>0</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-25740336.post-1578765265942818500</guid><pubDate>Fri, 04 Oct 2024 13:44:00 +0000</pubDate><atom:updated>2024-10-04T14:50:28.459+01:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">Active Session History</category><category domain="http://www.blogger.com/atom/ns#">cursor sharing</category><title>Cursor Sharing in Scheduled Processes: 4. How to Identify Candidate Processes for Cursor Sharing</title><description>&lt;div&gt;This is the last in a series of 4 posts about the selective use of cursor sharing in scheduled processes in PeopleSoft.&lt;/div&gt;&lt;div&gt;&lt;ol style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&lt;a href=&quot;https://blog.psftdba.com/2024/09/cursor-sharing-1.html&quot;&gt;Introduction&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://blog.psftdba.com/2024/09/cursor-sharing-101221066505.html&quot;&gt;What happens during SQL Parse?&amp;nbsp; What is a &#39;hard&#39; parse?&amp;nbsp; What is a &#39;soft&#39; parse?&amp;nbsp; The additional overhead of a hard parse.&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://blog.psftdba.com/2024/09/cursor-sharing-3.html&quot;&gt;How to set CURSOR_SHARING for specific scheduled processes.&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;u&gt;How to identify candidate processes for cursor sharing.&lt;/u&gt;&lt;/li&gt;&lt;/ol&gt;&lt;/div&gt;&lt;p style=&quot;text-align: left;&quot;&gt;In this article, I look at a method to identify candidate processes for cursor sharing.&amp;nbsp; Then it is necessary to test whether cursor sharing actually is beneficial.&lt;/p&gt;&lt;p style=&quot;text-align: left;&quot;&gt;My example is based on nVision reports in a PeopleSoft Financials system, but the technique can be applied to other processes and is not even limited to PeopleSoft.&amp;nbsp; nVision reports example because they vary from report to report, depending upon how they are written, and the nature of the reporting trees they use.&amp;nbsp; Some nVision reports benefit from cursor sharing, others it makes little difference, and for some it is detrimental.&lt;/p&gt;&lt;p style=&quot;text-align: left;&quot;&gt;As always Active Session History (ASH) is your friend.&amp;nbsp; First, you need to know which ASH data relates to which process, so you need to enable PeopleSoft instrumentation (see &lt;a href=&quot;https://blog.psftdba.com/2019/03/effective-peoplesoft-performance.html&quot;&gt;Effective PeopleSoft Performance Monitoring&lt;/a&gt;), and install my &lt;i&gt;&lt;a href=&quot;https://github.com/davidkurtz/psscripts/blob/master/psftapi.sql&quot; target=&quot;_blank&quot;&gt;psftapi&lt;/a&gt; &lt;/i&gt;package and trigger to enable instrumentation of Cobol, nVision and SQR.&lt;/p&gt;
&lt;h4 style=&quot;text-align: left;&quot;&gt;Candidates for Cursor Sharing&lt;/h4&gt;&lt;div&gt;Use ASH for a given process to identify candidate processes by calculating the following measures.&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;
&lt;li&gt;Elapsed time of the process from the first to the last ASH sample.&amp;nbsp; This is not the elapsed duration of the client process, but it will be a reasonable approximation. Otherwise, you can get the exact duration from the process request record (&lt;a href=&quot;https://www2.go-faster.co.uk/peopletools/psprcsrqst.htm&quot; target=&quot;_blank&quot;&gt;PSPRCSRQST&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;Total database time for a process (all ASH samples).&lt;/li&gt;&lt;li&gt;Total time that a process is restrained by the resource manager (where EVENT is &#39;resmgr: CPU quantum&#39;)&lt;/li&gt;&lt;li&gt;Total database&amp;nbsp;time spent on CPU (where EVENT is null).&lt;/li&gt;&lt;li&gt;Total database&amp;nbsp;time spent on SQL parse (where IN_PARSE flag is set to Y)&lt;/li&gt;&lt;li&gt;Number of distinct SQL IDs.&lt;/li&gt;&lt;li&gt;Number of distinct force matching signatures.&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;Look for processes with high elapsed time, of which a significant proportion is spent on both CPU and SQL parse.&amp;nbsp; This should correlate with processes where there are many more SQL IDs than force matching signatures.&lt;/div&gt;
&lt;h4 style=&quot;text-align: left;&quot;&gt;Is Cursor Sharing Enabled Already?&lt;/h4&gt;&lt;div&gt;It is possible to determine whether cursor sharing is already set for a process, although this is not explicitly recorded.&amp;nbsp;&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;If cursor sharing is not enabled then the number of distinct SQL_IDs should be greater than the number of distinct force-matching signatures. This may not be the case if you don&#39;t have enough ASH samples, but then the program probably doesn&#39;t consume enough time for it to be worth considering cursor sharing.&lt;/li&gt;&lt;li&gt;If the number of SQL_IDs is equal to the number of force matching signatures then cursor sharing is probably enabled, but again this could be unreliable if the number of ASH samples is low (and close to the number of SQL IDs).&lt;/li&gt;&lt;li&gt;It should be impossible for the number of distinct SQL IDs to be less than the number of distinct force matching signatures, but it can happen due to quirks in ASH sampling.&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;I have coded this into my queries.&amp;nbsp; It will be reasonably accurate if you have several ASH samples per SQL ID.&amp;nbsp; Otherwise, you may detect false positives.&lt;/div&gt;
&lt;h4 style=&quot;text-align: left;&quot;&gt;Sample Queries and Output&lt;/h4&gt;&lt;div&gt;I have written a couple of queries that I have published on GitHub.&amp;nbsp; They happen to be specific to nVision, but can easily be extended to other processes.&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;
&lt;li&gt;The first query calculates average values for each process/run control ID combination within the AWR retention period (&lt;a href=&quot;https://github.com/davidkurtz/nVision/blob/master/high_parse_nvision_avg.sql&quot; target=&quot;_blank&quot;&gt;&lt;b&gt;high_parse_nvision_avg.sql&lt;/b&gt;&lt;/a&gt;)&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;Having implemented cursor sharing for a particular process it is necessary to watch it over time and decide whether the change has been effective. The metrics shown below come from a real system (although actual run control IDs have been changed).&amp;nbsp;&amp;nbsp;&lt;/div&gt;&lt;div&gt;
&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;All the timings for NVS_RPTBOOK_1 have come down significantly. The number of SQL_IDs has dropped from 238 to 11.&amp;nbsp; The number of force matching signatures has also dropped, but that is because we have fewer ASH samples and some statements are no longer sampled at all.&amp;nbsp; Cursor sharing is beneficial and can be retained.&lt;/li&gt;
&lt;li&gt;However, this is not the case for the second process. Although NVS_RPTBOOK_2 looked like a good candidate for cursor sharing, and the parse time has indeed come down, all the other durations have gone up.&amp;nbsp; The cursor sharing setting will have to be removed for this report.&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;
&lt;pre style=&quot;background-color: #eeeeee; font-family: &amp;quot;courier new&amp;quot;overflow: auto; line-height: 95%; text-align: left; width: 95%;&quot;&gt;&lt;span style=&quot;font-size: xx-small;&quot;&gt;                                            Cursor           Avg StdDev    Avg StdDev    Avg StdDev    Avg StdDev    Avg StdDev   Avg
                                    Cursor  Sharing   Num   Elap   Elap    ASH    ASH ResMgr ResMgr  Parse  Parse    CPU    CPU   SQL  Avg
OPRID      RUNCNTLID                Sharing Setting Procs   Secs   Secs   Secs   Secs   Secs   Secs   Secs   Secs   Secs   Secs   IDs  FMS
---------- ------------------------ ------- ------- ----- ------ ------ ------ ------ ------ ------ ------ ------ ------ ------ ----- ----
…
NVISION    NVS_RPTBOOK_1            EXACT   FORCE      33   3691   1062   2687   1071    741    702   2232    932   1791    479   238   16
                                    FORCE   FORCE      13   1623    377    664    394    357    373     43     19    353     85    11   12
…
           NVS_RPTBOOK_2            EXACT   EXACT      39   3696   1435   3316   1431   1038    927   1026    661   2042    611   137   27
                                    FORCE   EXACT       7   4028   2508   3676   2490   1333   1563     17     12   2275    939    19   19&lt;/span&gt;
&lt;/pre&gt;
&lt;p style=&quot;text-align: left;&quot;&gt;It is always worth looking at individual process executions.&amp;nbsp;&amp;nbsp;&lt;/p&gt;&lt;p style=&quot;text-align: left;&quot;&gt;&lt;/p&gt;
&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;The second script (&lt;a href=&quot;https://github.com/davidkurtz/nVision/blob/master/high_parse_nvision.sql&quot; target=&quot;_blank&quot;&gt;&lt;b&gt;high_parse_nvision.sql&lt;/b&gt;&lt;/a&gt;) runs a similar query, but it reports each process individually.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;/p&gt;&lt;p style=&quot;text-align: left;&quot;&gt;We can see that cursor sharing was introduced on 31&lt;sup&gt;st&lt;/sup&gt; July.&amp;nbsp; Even though there is a lot of variance in runtimes due to variances in data volumes and other system activities, it is clear that cursor sharing is beneficial for this process.&lt;/p&gt;
&lt;pre style=&quot;background-color: #eeeeee; font-family: &amp;quot;courier new&amp;quot;overflow: auto; line-height: 95%; text-align: left; width: 95%;&quot;&gt;&lt;span style=&quot;font-size: 66%;&quot;&gt;                                                                                                                                       Cursor
                              Process R                                              Elap    ASH ResMgr  Parse    CPU   SQL        ASH Sharing Cursor  Parse   S:F
OPRID      RUNCNTLID         Instance S  MIN_SAMPLE_TIME      MAX_SAMPLE_TIME        Secs   Secs   Secs   Secs   Secs   IDs  FMS  Samp Setting Sharing     % Ratio
---------- ---------------- --------- -- -------------------- -------------------- ------ ------ ------ ------ ------ ----- ---- ----- ------- ------- ----- -----
NVISION    NVS_RPTBOOK_1     12447036 9  21.07.2024 21.03.25  21.07.2024 21.47.02    2645   1543    174   1297   1277   145   17   150 FORCE   EXACT      84   8.5
                             12452568 9  22.07.2024 21.02.04  22.07.2024 21.41.03    2373   1413    123   1188   1250   133   13   138 FORCE   EXACT      84  10.2
                             12458455 9  23.07.2024 21.07.15  23.07.2024 21.52.25    2759   1587     51   1372   1423   152   14   155 FORCE   EXACT      86  10.9
                             12465042 9  24.07.2024 20.58.08  24.07.2024 21.50.19    3154   2100    369   1782   1557   201   18   205 FORCE   EXACT      85  11.2
                             12471732 9  25.07.2024 21.25.34  25.07.2024 22.46.32    4885   3861   1946   3318   1843   333   14   377 FORCE   EXACT      86  23.8
                             12477118 9  26.07.2024 22.41.07  26.07.2024 23.26.07    2730   1791    113   1526   1586   173   14   174 FORCE   EXACT      85  12.4
                             12479163 9  27.07.2024 23.13.40  28.07.2024 00.01.23    2917   1688    161   1513   1260   156   14   164 FORCE   EXACT      90  11.1
                             12480710 9  28.07.2024 21.47.44  28.07.2024 22.29.08    2529   1586    205   1320   1238   149   12   154 FORCE   EXACT      83  12.4
                             12487744 9  29.07.2024 21.47.44  29.07.2024 22.51.05    3834   2815    797   2292   1843   248   16   273 FORCE   EXACT      81  15.5
                             12495417 9  30.07.2024 22.57.13  30.07.2024 23.46.48    3015   2084    307   1869   1592   200   15   203 FORCE   EXACT      90  13.3
…
                             12501446 9  31.07.2024 21.27.51  31.07.2024 21.51.18    1478    461     72     31    389    10   11    45 FORCE   FORCE       7   0.9
                             12507769 9  01.08.2024 21.44.01  01.08.2024 22.05.56    1387    357    100     21    246     7    8    34 FORCE   FORCE       6   0.9
                             12513527 9  02.08.2024 21.02.27  02.08.2024 21.27.47    1538    635    236     31    400    11   12    62 FORCE   FORCE       5   0.9
                             12515368 9  03.08.2024 22.12.50  03.08.2024 22.40.03    1682    686    143     51    532     9   10    67 FORCE   FORCE       7   0.9
                             12516959 9  04.08.2024 21.38.01  04.08.2024 21.57.00    1263    266            51    266     8    9    26 FORCE   FORCE      19   0.9
                             12522863 9  05.08.2024 21.14.36  05.08.2024 21.48.40    2082   1167    727     51    430    14   13   114 FORCE   EXACT       4   1.1
                             12529263 9  06.08.2024 21.02.59  06.08.2024 21.39.47    2223   1300    900     51    389    12   13   126 FORCE   FORCE       4   0.9
                             12535782 9  07.08.2024 21.08.23  07.08.2024 21.37.48    1774    974    585     52    379    12   13    94 FORCE   FORCE       5   0.9
                             12541727 9  08.08.2024 21.07.43  08.08.2024 21.40.54    2014   1085    809     51    276    16   17   106 FORCE   FORCE       5   0.9
                             12547232 9  09.08.2024 21.27.28  09.08.2024 21.47.08    1213    236            31    236     8    9    23 FORCE   FORCE      13   0.9
…&lt;/span&gt;&lt;/pre&gt;
Note that on 5&lt;sup&gt;th&lt;/sup&gt; August the report erroneously claims that cursor sharing went back to EXACT.&amp;nbsp; This is because there are more SQL_IDs than force matching signatures.&amp;nbsp; Again, this is a quirk of ASH sampling.&amp;nbsp;&lt;div class=&quot;blogger-post-footer&quot;&gt;&lt;a href=&quot;http://www.go-faster.co.uk/&quot;&gt;©David Kurtz&lt;/a&gt;&lt;/div&gt;</description><link>https://blog.psftdba.com/2024/09/psft-cursor-sharing-4.html</link><author>noreply@blogger.com (David Kurtz)</author><thr:total>0</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-25740336.post-3288479815248396461</guid><pubDate>Wed, 02 Oct 2024 15:30:00 +0000</pubDate><atom:updated>2025-02-20T10:39:30.429+00:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">cursor sharing</category><title>Cursor Sharing in Scheduled Processes: 3. How to Set Initialisation Parameters for Specific Scheduled Processes</title><description>&lt;div&gt;&lt;p class=&quot;MsoNormal&quot;&gt;This is the third in a series of 4 posts about the selective use of cursor sharing in scheduled processes in PeopleSoft.&lt;/p&gt;&lt;/div&gt;&lt;ol&gt;
  &lt;li&gt;&lt;a href=&quot;https://blog.psftdba.com/2024/09/cursor-sharing-1.html&quot;&gt;Introduction&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://blog.psftdba.com/2024/09/cursor-sharing-101221066505.html&quot;&gt;What happens during SQL Parse?&amp;nbsp; What is a &#39;hard&#39; parse?&amp;nbsp; What is a &#39;soft&#39; parse?&amp;nbsp; The additional overhead of a hard parse.&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;u&gt;How to set CURSOR_SHARING for specific scheduled processes&lt;/u&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://blog.psftdba.com/2024/09/psft-cursor-sharing-4.html&quot;&gt;How to identify candidate processes for cursor sharing.&lt;/a&gt;&lt;/li&gt;&lt;/ol&gt;&lt;h4 style=&quot;text-align: left;&quot;&gt;Cursor Sharing&lt;/h4&gt;&lt;p style=&quot;text-align: left;&quot;&gt;If you cannot remove the literal values in the application SQL code, then another option is to enable cursor sharing and have Oracle do it.&amp;nbsp; Literals are converted to bind variables before the SQL is parsed; thus, statements that only differ in the literal values can be treated as the same statement.&amp;nbsp; If the statement is still in the shared pool, it is not fully reparsed and uses the same execution plan.&lt;/p&gt;&lt;p style=&quot;text-align: left;&quot;&gt;Oracle cautions against using cursor sharing as a long-term fix: &quot;&lt;i&gt;The best practice is to write sharable SQL and use the default of EXACT for CURSOR_SHARING… FORCE is not meant to be a permanent development solution.&lt;/i&gt;&quot;&lt;/p&gt;&lt;p style=&quot;text-align: left;&quot;&gt;I realise that I am now about to suggest doing exactly that, but only for specific processes, and never for the whole database.&amp;nbsp; Over the years, I have tested enabling cursor sharing at database level a few times and have never had a good experience.&amp;nbsp;&amp;nbsp;&lt;/p&gt;&lt;p style=&quot;text-align: left;&quot;&gt;However, enabling cursor sharing in a few carefully selected processes can be beneficial.&amp;nbsp; It can save some of the time spent in the database on hard parse, but will have no effect on the time that PeopleSoft processes spend generating the SQL.&lt;/p&gt;&lt;h4 style=&quot;text-align: left;&quot;&gt;Session Settings for Processes Executed on the Process Scheduler&amp;nbsp;&lt;/h4&gt;&lt;div&gt;Setting a session for a specific process run on the PeopleSoft process scheduler is straightforward.&amp;nbsp; &amp;nbsp;The first thing a process does is set the status of its own request record to 7, indicating that it is processing. A database trigger can be created on this transition that will then be executed in the session of the process.&amp;nbsp;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;I initially used this technique to set other session settings for nVision reports.&amp;nbsp; I used a table to hold a list of the settings, and the trigger matches this metadata to the processes being run by up to 4 attributes: process type, process name, operation and run control.&lt;/div&gt;
&lt;div&gt;It is usual to set up different run controls to run different instances of the same code on different sets of data.&amp;nbsp; I sometimes see certain run controls set up to regularly run certain nVision or other reports.&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;see &lt;a href=&quot;https://blog.psftdba.com/2018/03/setting-oracle-session-parameters-for.html&quot; target=&quot;_blank&quot;&gt;Setting Oracle Session Parameters for Specific Process Scheduler Processes&lt;/a&gt;&amp;nbsp;&lt;/li&gt;
  &lt;li&gt;The scripts are available on GitHub&lt;/li&gt;&lt;ul&gt;&lt;li&gt;Trigger: &lt;a href=&quot;https://github.com/davidkurtz/psscripts/blob/master/set_prcs_sess_parm.sql&quot; target=&quot;_blank&quot;&gt;set_prcs_sess_parm_trg.sql&lt;/a&gt;.&amp;nbsp; The trigger expects that &lt;a href=&quot;https://github.com/davidkurtz/psscripts/blob/master/psftapi.sql&quot; target=&quot;_blank&quot;&gt;psftapi.sql&lt;/a&gt; has also been installed.&lt;/li&gt;&lt;li&gt;Example metadata &lt;a href=&quot;http://set_prcs_sess_parm.sql&quot; target=&quot;_blank&quot;&gt;set_prcs_sess_parm.sql&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;
&lt;pre style=&quot;background-color: #eeeeee; font-family: &amp;quot;courier new&amp;quot;overflow: auto; line-height: 95%; text-align: left; width: 95%;&quot;&gt;&lt;span style=&quot;font-size: x-small;&quot;&gt;CREATE OR REPLACE TRIGGER sysadm.set_prcs_sess_parm
BEFORE UPDATE OF runstatus ON sysadm.psprcsrqst
FOR EACH ROW
FOLLOWS sysadm.psftapi_store_prcsinstance 
WHEN (new.runstatus = 7 AND old.runstatus != 7 AND new.prcstype != &#39;PSJob&#39;)
DECLARE
  l_cmd VARCHAR2(100 CHAR);
…
BEGIN
  FOR i IN (
    WITH x as (
      SELECT p.*
      ,      row_number() over (partition by param_name 
             order by NULLIF(prcstype, &#39; &#39;) nulls last, NULLIF(prcsname, &#39; &#39;) nulls last, 
                      NULLIF(oprid   , &#39; &#39;) nulls last, NULLIF(runcntlid,&#39; &#39;) nulls last) priority
      FROM   sysadm.PS_PRCS_SESS_PARM p
      WHERE  (p.prcstype  = :new.prcstype  OR p.prcstype  = &#39; &#39;)
      AND    (p.prcsname  = :new.prcsname  OR p.prcsname  = &#39; &#39;)
      AND    (p.oprid     = :new.oprid     OR p.oprid     = &#39; &#39;)
      AND    (p.runcntlid = :new.runcntlid OR p.runcntlid = &#39; &#39;)) 
    SELECT * FROM x WHERE priority = 1 
  ) LOOP
…
    IF NULLIF(i.parmvalue,&#39; &#39;) IS NOT NULL THEN
      l_cmd := &#39;ALTER SESSION &#39;||i.keyword||&#39; &#39;||l_delim||i.param_name||l_delim||l_op||i.parmvalue;
      EXECUTE IMMEDIATE l_cmd;
    END IF;
  END LOOP;
EXCEPTION
  WHEN OTHERS THEN …
END;
/&lt;/span&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;The first delivered program that was a candidate for cursor sharing was GLPOCONS (GL Consolidations process).&amp;nbsp; It is only necessary is to insert the corresponding metadata, and it will apply the next time the process starts.&amp;nbsp; Anything you can set with an ALTER SESSION command can be put in the metadata.&amp;nbsp;&amp;nbsp;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;At times, other settings have been defined, hence in this example the insert statement is written in this way, and you can also see that in-memory query has been disabled for the same process.&amp;nbsp;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;
  &lt;pre style=&quot;background-color: #eeeeee; font-family: &amp;quot;courier new&amp;quot;overflow: auto; line-height: 95%; text-align: left; width: 95%;&quot;&gt;&lt;span style=&quot;font-size: x-small;&quot;&gt;INSERT INTO sysadm.ps_prcs_sess_parm (prcstype, prcsname, oprid, runcntlid, keyword, param_name, parmvalue)
with x as (
          select &#39;inmemory_query&#39; param_name, &#39;SET&#39; keyword, &#39;DISABLE&#39; parmvalue from dual --Disable inmemory 
union all select &#39;cursor_sharing&#39;           , &#39;SET&#39; keyword, &#39;FORCE&#39;             from dual --to mitigate excessive parse
), y as (
  select prcstype, prcsname, &#39; &#39; oprid, &#39; &#39; runcntlid
  from	 ps_prcsdefn
  where  prcsname IN(&#39;GLPOCONS&#39;)
)
select  y.prcstype, y.prcsname, y.oprid, y.runcntlid, x.keyword, x.param_name, x.parmvalue
from    x,y
/&lt;/span&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;h4 style=&quot;text-align: left;&quot;&gt;Stand-alone Application Engine (PSAE) -v- Application Engine Server PSAESRV&lt;/h4&gt;&lt;div&gt;In PeopleTools 8.4, the Process Scheduler became a fully-fledged Tuxedo domain and the Application Engine server process &lt;i&gt;PSAESRV&lt;/i&gt; was also introduced.&amp;nbsp; Stand-alone Application Engine executable &lt;i&gt;psae&lt;/i&gt; is still available, but the Tuxedo server process is configured by default.&amp;nbsp; The Tuxedo server process is a persistent process that creates a persistent database connection that may service many different Application Engine programs during its lifetime.&amp;nbsp; If you make session settings during the execution of one Application Engine program, they will still be set when the same server process executes the next program. Generally, you don&#39;t want session settings bleeding from one process request to another.&amp;nbsp; Instead, any setting made at the start of the process would have to be reverted to the previous value at the end.&amp;nbsp; That is not necessarily the same as resetting it back to the default.&lt;/div&gt;&lt;div&gt;This is, therefore, another reason why it is preferable to revert to using the legacy stand-alone Application Engine&amp;nbsp;process (&lt;i&gt;psae&lt;/i&gt;) that creates a new database session for each request.&lt;/div&gt;&lt;div&gt;The &lt;i&gt;set_prcs_sess_parm&amp;nbsp;&lt;/i&gt; trigger does not save or reset the previous value for settings it makes. Therefore, it should NOT be used in conjunction with &lt;i&gt;PSAESRV&lt;/i&gt;.&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;See also &lt;a href=&quot;https://blog.psftdba.com/2018/04/application-engine-in-process-scheduler.html&quot;&gt;Application Engine in Process Scheduler: PSAESRV Server Process -v- Standalone PSAE executable&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://support.oracle.com/epmos/faces/DocContentDisplay?id=747389.1&quot; target=&quot;_blank&quot;&gt;Oracle Support Document 747389.1: PeopleTools Performance Guidelines -&amp;gt; Using PSAE and PSESRV&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;h4 style=&quot;text-align: left;&quot;&gt;Cursor Sharing Application Engine Programs Spawned Directly by COBOL Programs&lt;/h4&gt;&lt;div&gt;In PeopleSoft, some COBOL programs directly spawn stand-alone Application Engine processes.&amp;nbsp; These processes do not update the status on the process request record, so the &lt;i&gt;set_prcs_sess_parm&lt;/i&gt; trigger described above does not fire.&amp;nbsp;&amp;nbsp;&lt;/div&gt;&lt;div&gt;A different data change must be found upon which to place a trigger.&amp;nbsp; It may be different for different processes.&amp;nbsp; In Financials, GL_JEDIT2 is such a process, and it is a good candidate for cursor sharing.&amp;nbsp;&amp;nbsp;&lt;/div&gt;&lt;div&gt;I chose to create a hard-coded compound trigger on the insert into the journal line table (PS_JRNL_LN).&amp;nbsp;&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;See&amp;nbsp;&lt;a href=&quot;https://github.com/davidkurtz/psscripts/blob/master/gfc_jrnl_ln_gl_jedit2_trigger.sql&quot; target=&quot;_blank&quot;&gt;gfc_jrnl_ln_gl_jedit2_trigger.sql&lt;/a&gt;&lt;/li&gt;&lt;li&gt;This update is specific to this process, so the trigger is simply hard-coded. It does not use any metadata.&lt;/li&gt;&lt;li&gt;The after row part of the trigger copies the process instance number from the JRNL_LN rows being inserted into a local variable. This is deliberately minimal so that overhead on the insert is minimal&lt;/li&gt;&lt;li&gt;The after statement part of the trigger cannot be directly read from the table that was updated.&amp;nbsp; Instead, it checks that the process instance number, that was captured during the after row section and stored in the local variable, is for an instance of FSPCCURR or GLPOCONS that is currently processing (PSPRCSRQST.RUNSTATUS = &#39;7&#39;).&amp;nbsp; If so it sets CURSOR_SHARING to FORCE at session level.&amp;nbsp;&amp;nbsp;&lt;/li&gt;&lt;li&gt;The ALTER SESSION command is Data Dictionary Language (DDL).&amp;nbsp; In PL/SQL this must be executed as dynamic code.&lt;/li&gt;&lt;li&gt;The FSPCCURR and GLPOCONS COBOL processes may each spawn GL_JEDIT2 many times. Each runs as a separate stand-alone PSAE process that makes a new connection to the database, runs and then disconnects.&amp;nbsp; Cursor sharing is enabled separately for each.&lt;/li&gt;&lt;/ul&gt;
&lt;pre style=&quot;background-color: #eeeeee; font-family: &amp;quot;courier new&amp;quot;overflow: auto; line-height: 95%; text-align: left; width: 95%;&quot;&gt;&lt;span style=&quot;font-size: x-small;&quot;&gt;REM &lt;a href=&quot;http://gfc_jrnl_ln_gl_jedit2_trigger.sql&quot; target=&quot;_blank&quot;&gt;gfc_jrnl_ln_gl_jedit2_trigger.sql&lt;/a&gt;
CREATE OR REPLACE TRIGGER gfc_jrnl_ln_gl_jedit2
FOR UPDATE OF process_instance ON ps_jrnl_ln
WHEN (new.process_instance != 0 and old.process_instance = 0)
COMPOUND TRIGGER
  l_process_instance INTEGER;
  l_runcntlid VARCHAR2(30);
  l_module VARCHAR2(64);
  l_action VARCHAR2(64);
  l_prcsname VARCHAR2(12);
  l_cursor_sharing CONSTANT VARCHAR2(64) := &#39;ALTER SESSION SET cursor_sharing=FORCE&#39;;

  AFTER EACH ROW IS 
  BEGIN
    l_process_instance := :new.process_instance;
  END AFTER EACH ROW;
  
  AFTER STATEMENT IS 
  BEGIN
    IF l_process_instance != 0 THEN
      dbms_application_info.read_module(l_module,l_action);
      IF l_module like &#39;PSAE.GL_JEDIT2.%&#39; THEN --check this session is instrumented as being GL_JEDIT2
        --check process instance being set is a running FSPCCURR process
        SELECT prcsname, runcntlid
        INTO l_prcsname, l_runcntlid
        FROM psprcsrqst
        WHERE prcsinstance = l_process_instance
        AND prcsname IN(&#39;FSPCCURR&#39;,&#39;GLPOCONS&#39;)
        AND runstatus = &#39;7&#39;;
        
        l_module := regexp_substr(l_module,&#39;PSAE\.GL_JEDIT2\.[0-9]+&#39;,1,1)
                    ||&#39;:&#39;||l_prcsname||&#39;:PI=&#39;||l_process_instance||&#39;:&#39;||l_runcntlid;
        dbms_application_info.set_module(l_module,l_action);
        EXECUTE IMMEDIATE l_cursor_sharing;
      END IF;
    END IF;
  EXCEPTION 
    WHEN NO_DATA_FOUND THEN 
      NULL; --cannot find running fspccurr/glpocons with this process instance number
    WHEN OTHERS THEN
      NULL;
  END AFTER STATEMENT;

END gfc_jrnl_ln_gl_jedit2;
/&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;blogger-post-footer&quot;&gt;&lt;a href=&quot;http://www.go-faster.co.uk/&quot;&gt;©David Kurtz&lt;/a&gt;&lt;/div&gt;</description><link>https://blog.psftdba.com/2024/09/cursor-sharing-3.html</link><author>noreply@blogger.com (David Kurtz)</author><thr:total>0</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-25740336.post-8889088112890453670</guid><pubDate>Tue, 01 Oct 2024 14:15:00 +0000</pubDate><atom:updated>2024-10-04T14:46:24.184+01:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">cursor sharing</category><category domain="http://www.blogger.com/atom/ns#">Parse</category><category domain="http://www.blogger.com/atom/ns#">ReUse Statement</category><title>Cursor Sharing in Scheduled Processes: 2. What happens during SQL Parse?  What is a &#39;hard&#39; parse?  What is a &#39;soft&#39; parse?</title><description>&lt;p&gt;This is the second in a series of 4 posts about the selective use of cursor sharing in scheduled processes in PeopleSoft.&lt;/p&gt;&lt;p class=&quot;MsoListParagraph&quot;&gt;&lt;/p&gt;&lt;ol&gt;&lt;li&gt;&lt;a href=&quot;https://blog.psftdba.com/2024/09/cursor-sharing-1.html&quot;&gt;Introduction&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;u&gt;What happens during SQL Parse?&amp;nbsp; What is a &#39;hard&#39; parse?&amp;nbsp; What is a &#39;soft&#39; parse?&amp;nbsp; The additional overhead of a hard parse.&lt;/u&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://blog.psftdba.com/2024/09/cursor-sharing-3.html&quot;&gt;How to set CURSOR_SHARING for specific scheduled processes.&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://blog.psftdba.com/2024/09/psft-cursor-sharing-4.html&quot; target=&quot;_blank&quot;&gt;How to identify candidate processes for cursor sharing.&lt;/a&gt;&lt;/li&gt;&lt;/ol&gt;&lt;div&gt;&lt;p class=&quot;MsoNormal&quot;&gt;To understand why cursor sharing can be beneficial it is
necessary to understand &lt;o:p&gt;&lt;/o:p&gt;&lt;/p&gt;

&lt;p class=&quot;MsoListParagraph&quot;&gt;&lt;/p&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;What happens when Oracle parses and executes a
SQL statement?. &lt;/li&gt;&lt;li&gt;How some PeopleSoft processes dynamically
construct SQL statements&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;/p&gt;&lt;h4 style=&quot;text-align: left;&quot;&gt;SQL Processing: &#39;Hard&#39; Parse -v- &#39;Soft&#39; Parse&lt;/h4&gt;&lt;p class=&quot;MsoListParagraph&quot;&gt;&lt;!--[if !supportLists]--&gt;&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://docs.oracle.com/en/database/oracle/oracle-database/23/tgsql/img/cncpt250.gif&quot; style=&quot;clear: right; float: right; margin-bottom: 1em; margin-left: 1em;&quot;&gt;&lt;img alt=&quot;Oracle SQL Parse Flow&quot; border=&quot;0&quot; data-original-height=&quot;558&quot; data-original-width=&quot;338&quot; height=&quot;400&quot; src=&quot;https://docs.oracle.com/en/database/oracle/oracle-database/23/tgsql/img/cncpt250.gif&quot; width=&quot;242&quot; /&gt;&lt;/a&gt;&lt;/div&gt;SQL parse processing is set out in various places in the Oracle database documentation&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&lt;a href=&quot;https://docs.oracle.com/en/database/oracle/oracle-database/23/tgsql/sql-processing.html#GUID-8CF633B1-EAC4-47C7-9189-C479ADEF1FFA&quot; target=&quot;_blank&quot;&gt;SQL Tuning Guide -&amp;gt; Optimizer Fundamentals -&amp;gt; SQL Processing&lt;/a&gt;.&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://docs.oracle.com/en/database/oracle/oracle-database/19/tgsql/improving-rwp-cursor-sharing.html#GUID-971F4652-3950-4662-82DE-713DDEED317C&quot; target=&quot;_blank&quot;&gt;Tuning Guide: Improving Real-World Performance Through Cursor Sharing&lt;/a&gt;.&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://docs.oracle.com/en/database/oracle/oracle-database/19/refrn/CURSOR_SHARING.html&quot; target=&quot;_blank&quot;&gt;Reference: Initialisation Parameter Cursor Sharing&lt;/a&gt;.&lt;/li&gt;&lt;ul&gt;&lt;li&gt;&lt;a href=&quot;https://docs.oracle.com/en/database/oracle/oracle-database/23/tgsql/img/cncpt250.gif&quot; target=&quot;_blank&quot;&gt;This diagram appears in the Oracle documentation&lt;/a&gt;.&lt;/li&gt;&lt;/ul&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;When a statement is submitted to the database it goes through a number of stages of parsing before it is executed.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Syntax Check: Is the statement syntactically valid?&lt;/li&gt;&lt;li&gt;Semantic Check:&amp;nbsp; Is the statement meaningful?&amp;nbsp; Do the referenced objects exist and is the user allowed to access them?&lt;/li&gt;&lt;li&gt;SGA Check: Does the statement already exist in the shared SQL area?&amp;nbsp;&amp;nbsp;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;The database looks for an exact matching statement in the shared SQL area. If it is not found, the database must perform additional tasks called &#39;hard&#39; parsing.&amp;nbsp; The stages performed up to this point are referred to as &#39;soft parsing&#39;.&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Generation of the optimal execution plan&lt;/li&gt;&lt;li&gt;Row Source Generation - The execution plan is used to generate an iterative execution plan that is usable by the rest of the database.&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;Thus the Oracle Database must perform a &#39;hard&#39; parse at least once for every unique DML statement.&lt;/div&gt;&lt;h4 style=&quot;text-align: left;&quot;&gt;What is Cursor Sharing?&lt;/h4&gt;&lt;div&gt;A cursor is a name or handle for a private SQL area that contains session-specific information about a statement, including bind variables, state information and result sets.&amp;nbsp; A cursor in the private area points to the shared SQL area in the library cache that contains the parse tree and execution plan for a statement.&amp;nbsp; Multiple private areas can reference a single shared SQL area.&amp;nbsp; This is known as cursor sharing.&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;See also&amp;nbsp;&lt;a href=&quot;https://docs.oracle.com/en/database/oracle/oracle-database/19/tgsql/improving-rwp-cursor-sharing.html#GUID-267E5860-A8D5-4E5F-AE60-16304E97E4EE&quot; target=&quot;_blank&quot;&gt;SQL Tuning Guide -&amp;gt; Improving Real-World Performance Through Cursor Sharing -&amp;gt; CURSOR_SHARING and Bind Variable Substitution&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;p style=&quot;text-align: left;&quot;&gt;The database allows only textually identical statements to share a cursor. By default, the CURSOR_SHARING parameter is set to EXACT, and thus is disabled.&amp;nbsp; &lt;i&gt;&quot;The optimizer generates a plan for each statement based on the literal value.&quot;&lt;br /&gt;&lt;/i&gt;When CURSOR_SHARING is set to FORCE, the database replaces literal values with system-generated variables.&amp;nbsp; The database still only exactly matches statements, but after the literal values have been substituted, thus giving the appearance of matching statements that differ only by their literal values.&amp;nbsp; &lt;i&gt;&quot;For statements that are identical after bind variables replace the literals, the optimizer uses the same plan. Using this technique, the database can sometimes reduce the number of parent cursors in the shared SQL area.&quot;&lt;/i&gt; The database only performs a soft parse.&amp;nbsp;&amp;nbsp;&lt;br /&gt;In systems, such as PeopleSoft, that generate many distinct statements, cursor sharing can significantly reduce hard parse, and therefore CPU and time spent on it.&lt;/p&gt;&lt;h4 style=&quot;text-align: left;&quot;&gt;Sources of Hard Parse in PeopleSoft&lt;/h4&gt;&lt;div&gt;PeopleSoft has a deserved reputation for suffering from high volumes of SQL hard parse.&amp;nbsp; Generally, the cause of this is dynamically generated code.&amp;nbsp; Often each statement has different literal values.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;In Application Engine, &lt;i&gt;%BIND()&lt;/i&gt; resolves to a literal value rather than bind variable in the resulting SQL statement unless the ReUseStatement attribute is enabled.&amp;nbsp; The problem is that it is disabled by default, and there are limitations to when it can be set.&lt;/li&gt;&lt;li&gt;Dynamic statements in COBOL processes.&amp;nbsp; This is effectively the same behaviour as Application Engine, but here the dynamic generation of SQL is hard-coded in the COBOL from a combination of static fragments and configuration data. PeopleSoft COBOL programs generally just embed literal values in such statements because it is easier than creating dynamic SQL statements with possibly varying numbers of bind variables.&lt;/li&gt;&lt;li&gt;In nVision where &#39;dynamic selectors&#39; and &#39;use literal values&#39; tree performance options are selected.&amp;nbsp; These settings are often preferable because the resulting SQL statements can make effective use of Bloom filters and Hybrid Column Compression (on Exadata).&amp;nbsp; The penalty is that it can lead to more hard parse operations.&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;h4 style=&quot;text-align: left;&quot;&gt;ReUseStatement -v- Cursor&amp;nbsp; Sharing&lt;/h4&gt;&lt;div&gt;Of course, it would be better if PeopleSoft SQL used bind variables more often, rather than literal values.&amp;nbsp;&lt;/div&gt;&lt;div&gt;In Application Engine, if the ReUseStatement attribute is set on a step, then bind variables in Application Engine remain bind variables in the resulting SQL and are not converted back to literals.&amp;nbsp;&amp;nbsp;&lt;/div&gt;&lt;div&gt;This can reduce both the amount of time Application Engine spends dynamically generating SQL statements before submitting them to the database as well as the time the database spends parsing them (see &lt;a href=&quot;https://blog.psftdba.com/2014/10/minimising-parse-time-in-application.html&quot;&gt;Minimising Parse Time in Application Engine with ReUseStatement&lt;/a&gt;).&amp;nbsp;&lt;/div&gt;&lt;div&gt;However, this attribute is not set by default on newly created Application Engine steps in Application Designer.&amp;nbsp; This was to maintain backward compatibility with programs created in earlier versions of PeopleTools.&lt;/div&gt;&lt;div&gt;However, there are restrictions to where it cannot be used.&amp;nbsp; Most commonly because &lt;i&gt;%BIND(…NOQUOTES)&lt;/i&gt; has been used to dynamically generate part of the statement based on configuration data.&lt;/div&gt;&lt;div&gt;Over the years, PeopleSoft development has got much better at setting this attribute where possible in the delivered application code.&amp;nbsp; Nonetheless, there are still places where it could be added.&amp;nbsp;&amp;nbsp;&lt;/div&gt;&lt;div&gt;See also:&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&lt;a href=&quot;https://blog.psftdba.com/2010/03/hinting-dynamic-generated-sql-in.html&quot; target=&quot;_blank&quot;&gt;Hinting Dynamically Generated SQL in Application Engine&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://docs.oracle.com/cd/F86650_01/pt859pbr5/eng/pt/tape/ReusingStatements-07720c.html&quot; target=&quot;_blank&quot;&gt;PeopleTools -&amp;gt; Development Tools -&amp;gt; Application Engine -&amp;gt; Reusing Statements&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;However, there are some considerations before we add it ourselves.&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;ReUseStatement cannot be introduced across the board, but only on steps that meet certain criteria set out in the documentation.&amp;nbsp; It doesn&#39;t work when dynamic code is generated with &lt;i&gt;%BIND(…,NOQUOTES)&lt;/i&gt;, or if a &lt;i&gt;%BIND()&lt;/i&gt; is used in a SELECT clause.&amp;nbsp; Worse, setting this attribute incorrectly can cause the application to function incorrectly.&amp;nbsp; So each change has to be tested carefully.&lt;/li&gt;&lt;li&gt;When a customer sets the ReUseStatement attribute in the delivered code, it is a customisation to an Application Engine step that has to be migrated using Application Designer.&amp;nbsp; It then has to be maintained to ensure that subsequent PeopleSoft releases and patches do not revert it.&lt;/li&gt;&lt;li&gt;There is no equivalent option for PeopleSoft COBOL, SQR, or nVision.&amp;nbsp; The way that SQL is generated in each is effectively hard-coded.&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;/div&gt;&lt;p style=&quot;text-align: left;&quot;&gt;&lt;b&gt;Recommendation:&lt;/b&gt;&amp;nbsp;It is not a case of either ReUseStatement or Cursor Sharing.&amp;nbsp; It may be both.&amp;nbsp; If you are writing your own Application Engine code, or customising delivered code anyway, then it is usually advantageous to set ReUseStatement where you can.&amp;nbsp; You will save non-database execution time as well as database time because you are then using bind variables, and Application Engine does not have to spend time generating the text of a new SQL statement with new literal values for every execution.&amp;nbsp; You may still benefit from cursor sharing for statements where you cannot set ReUseStatement.&amp;nbsp;&amp;nbsp;&lt;/p&gt;&lt;p style=&quot;text-align: left;&quot;&gt;However, as you will see in the &lt;a href=&quot;https://blog.psftdba.com/2024/09/psft-cursor-sharing-4.html&quot;&gt;last article&lt;/a&gt; in this series, cursor sharing is not always effective, you have to test.&lt;/p&gt;

&lt;/div&gt;&lt;div class=&quot;blogger-post-footer&quot;&gt;&lt;a href=&quot;http://www.go-faster.co.uk/&quot;&gt;©David Kurtz&lt;/a&gt;&lt;/div&gt;</description><link>https://blog.psftdba.com/2024/09/cursor-sharing-101221066505.html</link><author>noreply@blogger.com (David Kurtz)</author><thr:total>0</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-25740336.post-6560075714519275102</guid><pubDate>Mon, 30 Sep 2024 17:01:00 +0000</pubDate><atom:updated>2024-10-04T18:30:03.370+01:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">cursor sharing</category><title>Cursor Sharing in Scheduled Processes: 1. Introduction</title><description>&lt;p&gt;This is the first in a series of 4 posts about the selective use of cursor
sharing in scheduled processes in PeopleSoft.&lt;/p&gt;

&lt;p class=&quot;MsoListParagraph&quot;&gt;&lt;/p&gt;&lt;ol style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&lt;u&gt;Introduction&lt;/u&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://blog.psftdba.com/2024/09/cursor-sharing-101221066505.html&quot;&gt;What happens during SQL Parse?&amp;nbsp; What is a &#39;hard&#39; parse?&amp;nbsp; What is a &#39;soft&#39; parse?&amp;nbsp; The additional overhead of a hard parse.&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://blog.psftdba.com/2024/09/cursor-sharing-3.html&quot;&gt;How to set CURSOR_SHARING for specific scheduled processes.&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://blog.psftdba.com/2024/09/psft-cursor-sharing-4.html&quot;&gt;How to identify candidate processes for cursor sharing.&lt;/a&gt;&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;&lt;/p&gt;&lt;div&gt;PeopleSoft applications make extensive use of configuration data that is then interpreted by dynamic code. This leads to lots of similar statements with different literal values; statements that differ only by literal values are still different. In an Oracle database, each statement that cannot be exactly matched with a statement already in the Shared SQL Area (also known as the Cursor Cache) must be parsed.&amp;nbsp; Thus, each distinct statement must be parsed at least once.&amp;nbsp;&amp;nbsp;&lt;/div&gt;&lt;div&gt;Hence, PeopleSoft systems have many SQL statements that are only ever executed once,each of which has to be fully parsed.&amp;nbsp; This is often called a &#39;hard&#39; parse.&amp;nbsp; With PeopleSoft, this parse overhead can have a significant impact on performance.&lt;/div&gt;&lt;div&gt;&lt;div&gt;In an Oracle database, CURSOR_SHARING can be set to FORCE (the default is EXACT). Then, it will substitute the literals with system-generated bind variables. The statement is still exactly matched against the contents of the Shared SQL Area, but now statements that previously only differed by their literal values will match the same bind variables.&amp;nbsp; Oracle can omit some of the parse processing for matched statements, leaving what is often called a &#39;soft&#39; parse. This can significantly reduce the CPU overhead of SQL parse, and thus improve the performance of some processes.&lt;/div&gt;&lt;p style=&quot;text-align: left;&quot;&gt;&lt;b&gt;&lt;u&gt;Do not set CURSOR_SHARING to FORCE at database level&lt;/u&gt;&lt;/b&gt;.&amp;nbsp; Over the years, I have tried this several times with different PeopleSoft systems, and on various different versions of Oracle.&amp;nbsp; The net result was always negative. Some things improved, but many more degraded and often to a greater extent.&lt;/p&gt;&lt;div style=&quot;text-align: left;&quot;&gt;However, I have found that it can be effective to set it at session-level for specific processes, and sometimes just specific run controls for specific processes.&amp;nbsp;&amp;nbsp;&lt;/div&gt;&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;What is Oracle&#39;s advice?&lt;/h3&gt;&lt;div&gt;&lt;div&gt;Oracle has always been clear that it is always better to write sharable SQL (i.e. that uses bind variables), and that setting CURSOR_SHARING = FORCE should not be used as a permanent fix.&lt;/div&gt;&lt;div&gt;As a general guideline, the Oracle Real-World Performance group recommends against setting CURSOR_SHARING to FORCE except … when all of the following conditions are met:&lt;br /&gt;&lt;ol style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Statements in the shared pool differ only in the values of literals&amp;nbsp;&lt;/li&gt;&lt;li&gt;Response time is suboptimal because of a very high number of library cache misses.&lt;/li&gt;&lt;li&gt;Your existing code has a serious security and scalability bug—the absence of bind variables—and you need a temporary band-aid until the source code can be fixed.&lt;/li&gt;&lt;li&gt;You set this initialization parameter at the session level and not at the instance level.&lt;/li&gt;&lt;/ol&gt;See &lt;a href=&quot;https://docs.oracle.com/en/database/oracle/oracle-database/19/tgsql/improving-rwp-cursor-sharing.html#GUID-7FF4E133-06A7-401E-9BFC-3B0B9C902346&quot; target=&quot;_blank&quot;&gt;SQL Tuning Guide: Improving Real-World Performance Through Cursor Sharing -&amp;gt; Real-World Performance Guidelines for Cursor Sharing -&amp;gt; Do Not Use CURSOR_SHARING = FORCE as a Permanent Fix&lt;/a&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;I submit that my approach in PeopleSoft meets at least 3 these of conditions.&amp;nbsp; As for the third criterion, we are talking about SQL statements that are either delivered as part of PeopleSoft code or that are dynamically generated from configuration data, also by delivered code.&amp;nbsp; The reality is that these are as &#39;fixed&#39; as they are going to be within PeopleSoft.&amp;nbsp; Most of this code is at least very difficult, if not impossible, for the customer to alter, and the cost and risk of owning and maintaining any such customisation is almost certainly prohibitive.&amp;nbsp; Cursor sharing may be a band-aid, but it will be there for the long term.&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;The &lt;a href=&quot;https://blog.psftdba.com/2024/09/cursor-sharing-101221066505.html&quot;&gt;next article&lt;/a&gt; looks at how the Oracle database parses SQL statements.&lt;/div&gt;





&lt;div class=&quot;blogger-post-footer&quot;&gt;&lt;a href=&quot;http://www.go-faster.co.uk/&quot;&gt;©David Kurtz&lt;/a&gt;&lt;/div&gt;</description><link>https://blog.psftdba.com/2024/09/cursor-sharing-1.html</link><author>noreply@blogger.com (David Kurtz)</author><thr:total>0</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-25740336.post-8642200072606058003</guid><pubDate>Mon, 29 Apr 2024 12:26:00 +0000</pubDate><atom:updated>2024-11-05T15:34:12.894+00:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">cursor sharing</category><title>Enabling Cursor Sharing in PeopleSoft Processes</title><description>&lt;p&gt;This article has been rewritten and is now &lt;a href=&quot;https://blog.psftdba.com/2024/09/cursor-sharing-3.html&quot;&gt;part of a 4-part series&lt;/a&gt; about cursor sharing.&lt;/p&gt;&lt;div class=&quot;blogger-post-footer&quot;&gt;&lt;a href=&quot;http://www.go-faster.co.uk/&quot;&gt;©David Kurtz&lt;/a&gt;&lt;/div&gt;</description><link>https://blog.psftdba.com/2024/04/enabling-cursor-sharing-for-peoplesoft.html</link><author>noreply@blogger.com (David Kurtz)</author><thr:total>0</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-25740336.post-1626451107346879782</guid><pubDate>Thu, 11 Apr 2024 15:06:00 +0000</pubDate><atom:updated>2024-04-11T16:06:53.839+01:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">Multitenant; SGA</category><title>Configuring Shared Global Area (SGA) in a Multitenant Database with a PeopleSoft Pluggable Database (PDB)</title><description>&lt;p&gt;I have been working on a PeopleSoft Financials application that we have converted from a stand-alone database to be the only pluggable database (PDB) in an Oracle 19c container database (CDB).&amp;nbsp; We have been getting shared pool errors in the PDB that lead to ORA-4031 errors in the PeopleSoft application.&amp;nbsp;&amp;nbsp;&lt;/p&gt;&lt;p&gt;I have written a &lt;a href=&quot;https://blog.go-faster.co.uk/2024/04/sgapdbcdb.html&quot;&gt;longer version of this article&lt;/a&gt; on my Oracle blog, but here are the main points.&lt;/p&gt;&lt;h3&gt;SGA Management with a Parse Intensive System (PeopleSoft).&lt;/h3&gt;&lt;p&gt;PeopleSoft systems dynamically generate lots of non-shareable SQL code.&amp;nbsp; This leads to lots of parse and consumes more shared pool.&amp;nbsp; ASMM can respond by shrinking the buffer cache and growing the shared pool.&amp;nbsp; However, this can lead to more physical I/O and degrade performance and it is not beneficial for the database to cache dynamic SQL statements that are not going to be executed again.&amp;nbsp; Other parse-intensive systems can also exhibit this behaviour.&lt;/p&gt;&lt;p&gt;In PeopleSoft, I normally set DB_CACHE_SIZE and SHARED_POOL_SIZE to minimum values to stop ASMM shuffling too far in either direction.&amp;nbsp; With a large SGA, moving memory between these pools can become a performance problem in its own right.&amp;nbsp;&amp;nbsp;&lt;/p&gt;&lt;p&gt;We removed SHARED_POOL_SIZE, DB_CACHE_SIZE and SGA_MIN_SIZE settings from the PDB.&amp;nbsp; The only SGA parameters set at PDB level are SGA_TARGET and INMEMORY_SIZE.&amp;nbsp;&amp;nbsp;&lt;/p&gt;&lt;p&gt;SHARED_POOL_SIZE and DB_CACHE_SIZE are set as I usually would for PeopleSoft, but at CDB level to guarantee a minimum buffer cache size.&amp;nbsp;&amp;nbsp;&lt;/p&gt;&lt;p&gt;This is straightforward when there is only one PDB in the CDB.&amp;nbsp; &amp;nbsp;I have yet to see what happens when I have another active PDB with a non-PeopleSoft system and a different kind of workload that puts less stress on the shared pool and more on the buffer cache.&lt;/p&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Initialisation Parameters&lt;/h3&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&lt;a href=&quot;https://docs.oracle.com/en/database/oracle/oracle-database/21/refrn/SGA_TARGET.html&quot; target=&quot;_blank&quot;&gt;SGA_TARGET&lt;/a&gt; &quot;specifies the total size of all SGA components&quot;.&amp;nbsp; Use this parameter to control the memory usage of each PDB.&amp;nbsp; The setting at CDB must be at least the sum of the settings for each PDB.&lt;/li&gt;&lt;ul&gt;&lt;li&gt;&lt;b&gt;Recommendations:&lt;/b&gt;&lt;/li&gt;&lt;ul&gt;&lt;li&gt;Use only this parameter at PDB level to manage the memory consumption of the PDB.&lt;/li&gt;&lt;li&gt;In a CDB with only a single PDB, set SGA_TARGET to the same value at CDB and PDB levels.&amp;nbsp;&amp;nbsp;&lt;/li&gt;&lt;li&gt;Therefore, where there are multiple PDBs, SGA_TARGET at CDB level should be set to the sum of the setting for each PDB.&amp;nbsp; However, I haven&#39;t tested this yet.&lt;/li&gt;&lt;li&gt;There is no recommendation to reserve SGA for use by the CDB only, nor in my experience is there any need so to do.&lt;/li&gt;&lt;/ul&gt;&lt;/ul&gt;&lt;li&gt;&lt;a href=&quot;https://docs.oracle.com/en/database/oracle/oracle-database/21/refrn/SHARED_POOL_SIZE.html&quot; target=&quot;_blank&quot;&gt;SHARED_POOL_SIZE&lt;/a&gt; sets the minimum amount of shared memory reserved to the shared pool.&amp;nbsp; It can optionally be set in a PDB.&amp;nbsp;&amp;nbsp;&lt;/li&gt;&lt;ul&gt;&lt;li&gt;&lt;b&gt;Recommendation: &lt;/b&gt;However, do not set SHARED_POOL_SIZE at PDB level.&amp;nbsp; It can be set at CDB level.&lt;/li&gt;&lt;/ul&gt;&lt;li&gt;&lt;a href=&quot;https://docs.oracle.com/en/database/oracle/oracle-database/21/refrn/DB_CACHE_SIZE.html&quot; target=&quot;_blank&quot;&gt;DB_CACHE_SIZE&lt;/a&gt; sets the minimum amount of shared memory reserved to the buffer cache. It can optionally be set in a PDB.&amp;nbsp;&amp;nbsp;&lt;/li&gt;&lt;ul&gt;&lt;li&gt;&lt;b&gt;Recommendation: &lt;/b&gt;However, do not set DB_CACHE_SIZE at PDB level.&amp;nbsp; It can be set at CDB level.&lt;/li&gt;&lt;/ul&gt;&lt;li&gt;&lt;a href=&quot;https://docs.oracle.com/en/database/oracle/oracle-database/21/refrn/SGA_MIN_SIZE.html&quot; target=&quot;_blank&quot;&gt;SGA_MIN_SIZE&lt;/a&gt; has no effect at CDB level.&amp;nbsp; It can be set at PDB level at up to half of the manageable SGA&lt;/li&gt;&lt;ul&gt;&lt;li&gt;&lt;b&gt;Recommendation: &lt;/b&gt;However, do not set SGA_MIN_SIZE.&lt;/li&gt;&lt;/ul&gt;&lt;li&gt;&lt;a href=&quot;https://docs.oracle.com/en/database/oracle/oracle-database/21/refrn/INMEMORY_SIZE.html&quot; target=&quot;_blank&quot;&gt;INMEMORY_SIZE&lt;/a&gt;: If you are using in-memory query, this&amp;nbsp;must be set at CDB level in order to reserve memory for the in-memory store.&amp;nbsp; The parameter defaults to 0, in which case in-memory query is not available.&amp;nbsp; The in-memory pool is not managed by Automatic Shared Memory Management (ASMM), but it does count toward the total SGA used in SGA_TARGET.&lt;/li&gt;&lt;ul&gt;&lt;li&gt;&lt;b&gt;Recommendation: &lt;/b&gt;Therefore it must also be set in the PDB where in-memory is being used, otherwise we found(contrary to the documetntation) that the parameter defaults to 0, and in-memory query will be disabled in that PDB.&lt;/li&gt;&lt;/ul&gt;&lt;/ul&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Oracle Notes&lt;/h3&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&lt;a href=&quot;https://support.oracle.com/epmos/faces/DocContentDisplay?id=2590172.1&quot; target=&quot;_blank&quot;&gt;A-04031 on Multitenant Database with Excessive Amounts of KGLH0 and / or SQLA Memory and Parameter SHARED_POOL_SIZE or SGA_MIN_SIZE Set at the PDB Level (Doc ID 2590172.1)&lt;/a&gt; – December 2022, Updated April 2023&lt;/li&gt;&lt;ul&gt;&lt;li&gt;This one says “&lt;i&gt;&lt;b&gt;Remove the PDB-level SHARED_POOL_SIZE and/or SGA_MIN_SIZE initialization parameters. The only SGA memory sizing parameter that Oracle recommends setting at the PDB level is SGA_TARGET.&lt;/b&gt;&lt;/i&gt;”&lt;/li&gt;&lt;/ul&gt;&lt;/ul&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&lt;a href=&quot;https://support.oracle.com/epmos/faces/DocContentDisplay?id=2655314.1&quot; target=&quot;_blank&quot;&gt;About memory configuration parameter on each PDBs (Doc ID 2655314.1)&lt;/a&gt; – Nov 2023&lt;/li&gt;&lt;ul&gt;&lt;li&gt;“&lt;i&gt;&lt;b&gt;As a best practice, please do not to set SHARED_POOL_SIZE and DB_CACHE_SIZE on each PDBs and please manage automatically by setting SGA_TARGET.&lt;/b&gt;&lt;/i&gt;”&lt;/li&gt;&lt;li&gt;&quot;&lt;i&gt;This best practice is confirmed by development in Bug 30692720&lt;/i&gt;&quot;&lt;/li&gt;&lt;li&gt;Bug 30692720 discusses how the parameters are validated.&amp;nbsp; Eg. &quot;&lt;i&gt;Sum(PDB sga size) &amp;gt; CDB sga size&lt;/i&gt;&quot;&lt;/li&gt;&lt;li&gt;Bug 34079542: &quot;&lt;i&gt;&lt;b&gt;Unset sga_min_size parameter in PDB.&lt;/b&gt;&lt;/i&gt;&quot;&lt;/li&gt;&lt;/ul&gt;&lt;/ul&gt;&lt;div class=&quot;blogger-post-footer&quot;&gt;&lt;a href=&quot;http://www.go-faster.co.uk/&quot;&gt;©David Kurtz&lt;/a&gt;&lt;/div&gt;</description><link>https://blog.psftdba.com/2024/04/cdbpsftpdbsga.html</link><author>noreply@blogger.com (David Kurtz)</author><thr:total>0</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-25740336.post-7826941830378163191</guid><pubDate>Mon, 11 Mar 2024 11:55:00 +0000</pubDate><atom:updated>2025-02-18T20:27:49.563+00:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">Resource Manager</category><title>PSFT_PLAN: A Sample Oracle Database Resource Manager Plan for PeopleSoft</title><description>&lt;div style=&quot;text-align: left;&quot;&gt;In the cloud (or any virtualised environment), performance is instrumented as cost.&amp;nbsp; This is also true in any other on-premises environment, but it takes a lot longer to feedback!&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;If you never run out of CPU, then you have probably bought/rented/allocated/licensed too many CPUs.&lt;/li&gt;&lt;li&gt;If you do run out of CPU, then you should use the database resource manager to prioritise the processes that are most important to the business.&lt;/li&gt;&lt;li&gt;If you don&#39;t enable the resource manager, you will have less visibility of when you do run out of CPU.&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;At the very least, you can use one of the sample resource manager plans available in the Oracle database by default.&amp;nbsp;&amp;nbsp;&lt;/div&gt;&lt;div&gt;This article proposes a &lt;a href=&quot;https://github.com/davidkurtz/psscripts/blob/master/psft_resource_plan_simple.sql&quot; target=&quot;_blank&quot;&gt;resource plan for PeopleSoft&lt;/a&gt; systems.&amp;nbsp; It can be used as a starting point before enhancing it with your own specific requirements.&amp;nbsp;&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;See also Go-Faster Blog: &lt;a href=&quot;https://blog.go-faster.co.uk/2023/06/more-bang-for-your-buck-in-cloud-with.html&quot;&gt;More Bang for your Buck in the Cloud with Resource Manager&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;p class=&quot;MsoListParagraph&quot; style=&quot;mso-list: l0 level1 lfo1; text-indent: -18pt;&quot;&gt;&lt;o:p&gt;&lt;/o:p&gt;&lt;/p&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Resource Plan Design Goals&lt;/h3&gt;&lt;div&gt;&lt;div&gt;The purpose of a database resource plan is to prioritise important/urgent processes over less important/less urgent processes by allocating CPU, to the higher priority processes, and by restricting CPU, other resources, and the degree of parallelism for lower priority processes.&lt;/div&gt;&lt;div&gt;The design of a resource plan should reflect what the business defines as important.&amp;nbsp;&amp;nbsp;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Consumer Groups&lt;/h3&gt;&lt;div&gt;A resource plan consists of several resource groups with different priorities, and resource allocations. Each priority level defined by the business becomes a consumer group in the resource plan.&amp;nbsp; A consumer group can be allocated to one of 8 priority levels in a resource plan.&amp;nbsp; Multiple consumer groups can exist at the same priority level with different CPU guarantees (adding up to not more than 100%) and can include other limits.&lt;/div&gt;&lt;div&gt;I have made some assumptions about process priorities in a typical PeopleSoft system, and have grouped and ranked them in the table below starting with the highest priority.&amp;nbsp; Not all customers run all these processes.&amp;nbsp; Consumer groups and mappings that are not needed can be omitted. There are gaps in the priority levels to allow for other definitions to be introduced.&lt;/div&gt;&lt;/div&gt;
&lt;table border=&quot;1&quot; bordercolor=&quot;#808080&quot; cellspacing=&quot;0&quot; style=&quot;width: 100%;&quot; valign=&quot;top&quot;&gt;
&lt;tbody&gt;&lt;tr&gt;&lt;th style=&quot;width: 9%;&quot;&gt;Priority Level&lt;/th&gt;
  &lt;th style=&quot;width: 10%;&quot;&gt;Consumer Group&lt;/th&gt;
  &lt;th style=&quot;width: 11%;&quot;&gt;%CPU Guarantee&lt;/th&gt;
  &lt;th&gt;Comment&lt;/th&gt;&lt;/tr&gt;
  &lt;tr&gt;&lt;td align=&quot;center&quot;&gt;1&lt;/td&gt;&lt;td&gt;SYS&lt;br /&gt;_GROUP&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;100%&lt;/td&gt;&lt;td&gt;
	Oracle system processes. Defined automatically.&lt;/td&gt;&lt;/tr&gt;
  &lt;tr&gt;&lt;td align=&quot;center&quot;&gt;2&lt;/td&gt;&lt;td&gt;PSFT&lt;br /&gt;_GROUP&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;100%&lt;/td&gt;&lt;td&gt;
	Any process that connects to the database as either SYSADM (the default 
	PeopleSoft owner ID) or PS has higher priority than other processes unless other rules apply. The online application (other than ad hoc query) falls into this category so that the online user experience is safeguarded before other PeopleSoft processes. &lt;br /&gt;
	This includes remote call Cobol processes, but not remote call Application 
	Engine that should be run in the component processor.&lt;/td&gt;&lt;/tr&gt;
  &lt;tr&gt;&lt;td align=&quot;center&quot;&gt;4&lt;/td&gt;&lt;td&gt;BATCH&lt;br /&gt;_GROUP&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;100%&lt;/td&gt;&lt;td&gt;
	Process scheduler processes, and processes run by the process schedulers&lt;/td&gt;&lt;/tr&gt;
  &lt;tr&gt;&lt;td align=&quot;center&quot;&gt;5&lt;/td&gt;&lt;td&gt;NVISION&lt;br /&gt;_GROUP&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;100%&lt;/td&gt;&lt;td&gt;
	nVision (NVSRUN) and nVision report book (RPTBOOK) processes&lt;/td&gt;&lt;/tr&gt;
  &lt;tr&gt;&lt;td align=&quot;center&quot; rowspan=&quot;3&quot;&gt;6&lt;/td&gt;&lt;td&gt;PSQUERY&lt;br /&gt;_ONLINE&lt;br /&gt;_GROUP&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;90%&lt;/td&gt;&lt;td&gt;Ad hoc queries are allocated to one of three consumer groups with the same priority, but different CPU guarantees, comprising:
	&lt;ul&gt;&lt;li&gt;on-line PS/Queries, &lt;/li&gt;&lt;li&gt;nVision reports run through the PIA,&lt;/li&gt;&lt;/ul&gt;&lt;/td&gt;&lt;/tr&gt;
  &lt;tr&gt;&lt;td&gt;PSQUERY&lt;br /&gt;_BATCH&lt;br /&gt;_GROUP&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;9%&lt;/td&gt;&lt;td&gt;
	&lt;ul&gt;&lt;li&gt;PS/Queries run on the process scheduler using the PSQUERY application engine. A 4-hour maximum runtime limit is defined.&lt;/li&gt;&lt;/ul&gt;&lt;/td&gt;&lt;/tr&gt;
  &lt;tr&gt;&lt;td&gt;NVSRUN&lt;br /&gt;_GROUP&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;1%&lt;/td&gt;&lt;td&gt;&lt;ul&gt;&lt;li&gt;nVision through the 3-tier nVision client&lt;/li&gt;&lt;/ul&gt;&lt;/td&gt;&lt;/tr&gt;
  &lt;tr&gt;&lt;td align=&quot;center&quot; rowspan=&quot;3&quot;&gt;8&lt;/td&gt;&lt;td&gt;LOW&lt;br /&gt;_GROUP&lt;/td&gt;&lt;td style=&quot;text-align: center;&quot;&gt;1%&lt;/td&gt;&lt;td&gt;Other low-priority processes&lt;/td&gt;&lt;/tr&gt;
  &lt;tr&gt;&lt;td&gt;LOW&lt;br /&gt;_LIMITED&lt;br /&gt;_GROUP&lt;/td&gt;&lt;td style=&quot;text-align: center;&quot;&gt;1%&lt;/td&gt;&lt;td&gt;Other low-priority processes, but whose maximum query time is limited.&lt;/td&gt;&lt;/tr&gt;
  &lt;tr&gt;&lt;td&gt;OTHER&lt;br /&gt;_GROUPS&lt;/td&gt;&lt;td style=&quot;text-align: center;&quot;&gt;1%&lt;/td&gt;&lt;td&gt;All other processes.&amp;nbsp; Defined automatically.&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;div&gt;See also&amp;nbsp;&lt;a href=&quot;https://docs.oracle.com/en/database/oracle/oracle-database/19/arpls/DBMS_RESOURCE_MANAGER.html#GUID-5641221D-A03C-46DE-92D9-1DA400668385&quot; target=&quot;_blank&quot;&gt;DBMS_RESOURCE_MANAGER.CREATE_PLAN_DIRECTIVE&lt;/a&gt;&lt;/div&gt;&lt;div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Consumer Group Mapping Priority&lt;/h3&gt;&lt;div&gt;Sessions are allocated to the consumer groups.&amp;nbsp; They can be allocated explicitly, or via mapping rules that use various session attributes. As the attributes are set or changed, the consumer group will be set according to the matching rules.&amp;nbsp;&amp;nbsp;&lt;/div&gt;&lt;div&gt;I have set the following attributes to be mapped in the following order of precedence.&amp;nbsp; The more specific mappings take precedence over the more generic ones.&lt;/div&gt;&lt;/div&gt;
&lt;table border=&quot;1&quot; bordercolor=&quot;#808080&quot; cellspacing=&quot;0&quot; style=&quot;width: 100%;&quot; valign=&quot;top&quot;&gt;
&lt;tbody&gt;&lt;tr&gt;&lt;th style=&quot;width: 9%;&quot;&gt;Priority&lt;/th&gt;&lt;th style=&quot;width: 10%;&quot;&gt;Mapping 
		Attribute&lt;/th&gt;&lt;th style=&quot;width: 80%;&quot;&gt;
		Comment&lt;/th&gt;&lt;/tr&gt;
  &lt;tr&gt;&lt;td align=&quot;center&quot;&gt;2&lt;/td&gt;&lt;td&gt;Module, Action&lt;/td&gt;&lt;td&gt;The PIA 
		instrumentation sets attributes MODULE to the component name and ACTION 
		to the page name. Specific component pages are allocated to specific 
		consumer groups&lt;/td&gt;&lt;/tr&gt;
  &lt;tr&gt;&lt;td align=&quot;center&quot;&gt;3&lt;/td&gt;&lt;td&gt;Module&lt;/td&gt;&lt;td&gt;Specific scheduled processes are allocated by name to specific consumer groups.&amp;nbsp; PeopleSoft instrumentation puts this name in the MODULE attribute.&lt;/td&gt;&lt;/tr&gt;
  &lt;tr&gt;&lt;td align=&quot;center&quot;&gt;4&lt;/td&gt;&lt;td&gt;Client Program&lt;/td&gt;&lt;td&gt;Batch and query processes are identified by program name and allocated to certain consumer groups. &lt;/td&gt;&lt;/tr&gt;
  &lt;tr&gt;&lt;td align=&quot;center&quot;&gt;5&lt;/td&gt;&lt;td&gt;Oracle User&lt;/td&gt;&lt;td&gt;Anything that connects to the database as either SYSADM or PS is allocated to the PSFT_GROUP. So other mapping rules must take precedence over this mapping.&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;div&gt;&lt;div&gt;See also&amp;nbsp;&lt;a href=&quot;https://docs.oracle.com/en/database/oracle/oracle-database/19/arpls/DBMS_RESOURCE_MANAGER.html#GUID-32AA7BD1-5884-4C25-A67A-22F48983851B&quot; target=&quot;_blank&quot;&gt;DBMS_RESOURCE_MANAGER.SET_CONSUMER_GROUP_MAPPING_PRI&lt;/a&gt;&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Required PeopleSoft Configuration&lt;/h3&gt;&lt;div&gt;The PSFT_PLAN sample resource manager plan relies on MODULE and ACTION being set by the PeopleSoft Application.&amp;nbsp; Therefore, the following additional configuration is required.&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Enable PeopleSoft instrumentation: Set &lt;a href=&quot;https://docs.oracle.com/cd/E92519_02/pt856pbr3/eng/pt/tadm/task_MonitoringPeopleSoftDatabaseConnections-077989.html#u711504d9-3503-43cc-90ae-8c0b800be515_s&quot; target=&quot;_blank&quot;&gt;EnableAEMonitoring=1&lt;/a&gt; in ALL PeopleSoft application server and process scheduler domains so that PeopleSoft processes set MODULE and ACTION information in the session attributes (using &lt;a href=&quot;https://docs.oracle.com/en/database/oracle/oracle-database/19/arpls/DBMS_APPLICATION_INFO.html&quot; target=&quot;_blank&quot;&gt;DBMS_APPLICATION_INFO&lt;/a&gt;).&amp;nbsp;&amp;nbsp;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;/div&gt;&lt;blockquote style=&quot;border: none; margin: 0px 0px 0px 40px; padding: 0px;&quot;&gt;&lt;div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;See also:&lt;/div&gt;&lt;/div&gt;&lt;/blockquote&gt;&lt;div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;ul&gt;&lt;li&gt;&lt;a href=&quot;https://docs.oracle.com/cd/E92519_02/pt856pbr3/eng/pt/tadm/task_MonitoringPeopleSoftDatabaseConnections-077989.html#u711504d9-3503-43cc-90ae-8c0b800be515_s&quot; target=&quot;_blank&quot;&gt;PeopleTools PeopleBooks: Administration Tools -&amp;gt; Data Management -&amp;gt; Administering PeopleSoft Databases on Oracle -&amp;gt; Monitoring PeopleSoft MODULE and ACTION Information, Press Enter to collapse&lt;/a&gt;&lt;/li&gt;&lt;li&gt;PeopleSoft DBA Blog: &lt;a href=&quot;https://blog.psftdba.com/2015/03/undocumented-application-engine.html&quot; target=&quot;_blank&quot;&gt;Undocumented (until PeopleTools 8.55) Application Engine Parameter: EnableAEMonitoring&lt;/a&gt;&amp;nbsp;&lt;/li&gt;&lt;li&gt;PeopleSoft DBA Blog: &lt;a href=&quot;https://blog.psftdba.com/2010/11/peopletools-850-uses.html&quot; target=&quot;_blank&quot;&gt;PeopleTools 8.50 uses DBMS_APPLICATION_INFO to Identify Database Sessions&lt;/a&gt;&lt;/li&gt;&lt;li&gt;Go-Faster Blog: &lt;a href=&quot;https://blog.go-faster.co.uk/2016/09/dbmsapplicationinfo.html&quot; target=&quot;_blank&quot;&gt;One of my Favourite Database Things: DBMS_APPLICATION_INFO&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/ul&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Install instrumentation trigger for PeopleSoft (&lt;a href=&quot;https://github.com/davidkurtz/psscripts/blob/master/psftapi.sql&quot; target=&quot;_blank&quot;&gt;psftapi.sql&lt;/a&gt;).&amp;nbsp; Not all PeopleSoft processes are instrumented.&amp;nbsp; COBOL, SQR, and nVision do not set MODULE or ACTION.&amp;nbsp; When a PeopleSoft process is started by the process scheduler, the first thing it does is set its own status to 7, meaning that it is processing.&amp;nbsp; This script creates a database trigger that fires on that DML and sets the session attributes MODULE to the name of the process and ACTION to the process instance number.&amp;nbsp; Application Engine processes may then subsequently update these values again.&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Consumer Group Mappings&lt;/h3&gt;&lt;div&gt;Consumer groups are matched to session attributes.&amp;nbsp; The highest priority matching mapping is applied.&amp;nbsp; Mappings can be matched to literal values, or with LIKE or REGEXP_LIKE operations.&lt;/div&gt;&lt;/div&gt;
&lt;table border=&quot;1&quot; bordercolor=&quot;#808080&quot; cellspacing=&quot;0&quot; style=&quot;width: 100%;&quot; valign=&quot;top&quot;&gt;
&lt;tbody&gt;&lt;tr&gt;&lt;th style=&quot;width: 8%;&quot;&gt;Mapping Priority&lt;/th&gt;
&lt;th style=&quot;width: 16%;&quot;&gt;Attribute&lt;/th&gt;
&lt;th style=&quot;width: 30%;&quot;&gt;Value&lt;/th&gt;
&lt;th style=&quot;width: 9%;&quot;&gt;Consumer&lt;br /&gt;Group&lt;br /&gt;Priority&lt;/th&gt;
&lt;th style=&quot;width: 24%;&quot;&gt;Consumer Group&lt;/th&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;center&quot;&gt;2&lt;/td&gt;&lt;td&gt;MODULE_ACTION&lt;/td&gt;&lt;td&gt;QUERY_MANAGER.QUERY_VIEWER&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;6&lt;/td&gt;&lt;td&gt;PSQUERY_ONLINE_GROUP&lt;/td&gt;&lt;/tr&gt;
  &lt;tr&gt;&lt;td align=&quot;center&quot; rowspan=&quot;3&quot;&gt;3&lt;/td&gt;&lt;td rowspan=&quot;3&quot;&gt;MODULE&lt;/td&gt;&lt;td&gt;RPTBOOK&lt;br /&gt;NVSRUN&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;5&lt;/td&gt;&lt;td&gt;NVISION_GROUP&lt;/td&gt;&lt;/tr&gt;
  &lt;tr&gt;&lt;td&gt;PSQRYSRV%&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;6&lt;/td&gt;&lt;td&gt;PSQUERY_ONLINE_GROUP&lt;/td&gt;&lt;/tr&gt;
  &lt;tr&gt;&lt;td&gt;PSAE.PSQUERY.%&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;6&lt;/td&gt;&lt;td&gt;PSQUERY_BATCH_GROUP&lt;/td&gt;&lt;/tr&gt;
  &lt;tr&gt;&lt;td align=&quot;center&quot; rowspan=&quot;5&quot;&gt;4&lt;/td&gt;&lt;td rowspan=&quot;5&quot;&gt;CLIENT_PROGRAM&lt;/td&gt;&lt;td&gt;PSRUNRMT&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;2&lt;/td&gt;&lt;td&gt;PSFT_GROUP&lt;/td&gt;&lt;/tr&gt;
  &lt;tr&gt;&lt;td&gt;psae%&lt;br /&gt;PSAESRV%&lt;br /&gt;PSDSTSRV%&lt;br /&gt;PSMSTPRC%&lt;br /&gt;PSRUN@%&lt;br /&gt;PSSQR%&lt;br /&gt;pssqr%&lt;br /&gt;sqr%&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;4&lt;/td&gt;&lt;td&gt;BATCH_GROUP&lt;/td&gt;&lt;/tr&gt;
  &lt;tr&gt;&lt;td&gt;PSQRYSRV%&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;6&lt;/td&gt;&lt;td&gt;PSQUERY_ONLINE_GROUP&lt;/td&gt;&lt;/tr&gt;
  &lt;tr&gt;&lt;td&gt;PSNVSSRV%&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;6&lt;/td&gt;&lt;td&gt;NVSRUN_GROUP&lt;/td&gt;&lt;/tr&gt;
  &lt;tr&gt;&lt;td&gt;SQL Developer&lt;br /&gt;sqlplus%&lt;br /&gt;Toad%&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;8&lt;/td&gt;&lt;td&gt;LOW_GROUP / LOW_LIMITED_GROUP&lt;/td&gt;&lt;/tr&gt;
  &lt;tr&gt;&lt;td align=&quot;center&quot;&gt;5&lt;/td&gt;&lt;td&gt;ORACLE_USER&lt;/td&gt;&lt;td&gt;PS&lt;br /&gt;SYSADM&lt;/td&gt;&lt;td align=&quot;center&quot;&gt;2&lt;/td&gt;&lt;td&gt;PSFT_GROUP&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;See also&amp;nbsp;&lt;a href=&quot;https://docs.oracle.com/en/database/oracle/oracle-database/19/arpls/DBMS_RESOURCE_MANAGER.html#GUID-7B9B9B1D-53E3-4BF4-8BE6-858256702877&quot; target=&quot;_blank&quot;&gt;DBMS_RESOURCE_MANAGER.SET_CONSUMER_GROUP_MAPPING&lt;/a&gt;&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Resource Plan Script&lt;/h3&gt;&lt;div&gt;Two SQL scripts are available on GitHub&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&lt;a href=&quot;https://github.com/davidkurtz/psscripts/blob/master/psft_resource_plan_simple.sql&quot; target=&quot;_blank&quot;&gt;psft_resource_plan_simple.sql&lt;/a&gt; creates the resource plan.&amp;nbsp; This is intended to be a starting point to which either unwanted parts can be removed, or additional requirements can be added&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://github.com/davidkurtz/psscripts/blob/master/resource_plan_report.sql&quot; target=&quot;_blank&quot;&gt;resource_plan_report.sql&lt;/a&gt; reports on all the resource plan metadata.&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Other Options&lt;/h3&gt;&lt;p style=&quot;text-align: left;&quot;&gt;There are other resource manager options that are either not illustrated in the sample plan, or that are commented out.&amp;nbsp; They may be worth considering in some situations.&lt;/p&gt;&lt;div&gt;&lt;p&gt;&lt;/p&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;PeopleSoft does not use parallel query by default, but if you do use it, you may well want to limit which processes use how much parallelism.&amp;nbsp; Consumer groups can specify a limit to the parallel query degree.&lt;/li&gt;&lt;ul&gt;&lt;li&gt;If you use the resource plan to restrict the degree of parallelism, and you also plan to vary the number of CPUs in a cloud environment, then I suggest creating a resource plan for each number of CPUs and switch between the plans by changing the setting of&amp;nbsp; the&amp;nbsp;&lt;a href=&quot;https://docs.oracle.com/en/database/oracle/oracle-database/21/refrn/RESOURCE_MANAGER_PLAN.html#GUID-BFEF14A4-4822-4410-9B51-DCB3376268B3&quot; target=&quot;_blank&quot;&gt;RESOURCE_MANAGER_PLAN&lt;/a&gt; parameter.&lt;/li&gt;&lt;/ul&gt;&lt;/ul&gt;&lt;p&gt;&lt;/p&gt;&lt;/div&gt;
&lt;pre style=&quot;background-color: #eeeeee; font-family: &amp;quot;courier new&amp;quot;overflow: auto; line-height: 95%; text-align: left; width: 95%;&quot;&gt;&lt;span style=&quot;font-size: xx-small;&quot;&gt;&lt;code&gt;  DBMS_RESOURCE_MANAGER.CREATE_PLAN_DIRECTIVE(
    &#39;PSFT_PLAN&#39;, &#39;NVISION_GROUP&#39;, &#39;nVision Reports.&#39;
    ,mgmt_p5 =&amp;gt; 100
&lt;b&gt;    ,parallel_degree_limit_p1=&amp;gt;2&lt;/b&gt;
  );
&lt;/code&gt;&lt;/span&gt;&lt;/pre&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;A parallel query may queue waiting to obtain sufficient parallel query server processes.&amp;nbsp; A timeout can be specified to limit that wait and to determine the behaviour when the timeout is reached.&amp;nbsp; The query can either be cancelled raising error ORA-07454, or run at a reduced parallelism).&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
  &lt;pre style=&quot;background-color: #eeeeee; font-family: &amp;quot;courier new&amp;quot;overflow: auto; line-height: 95%; text-align: left; width: 95%;&quot;&gt;&lt;span style=&quot;font-size: xx-small;&quot;&gt;&lt;code&gt;
  DBMS_RESOURCE_MANAGER.CREATE_PLAN_DIRECTIVE(
    &#39;PSFT_PLAN&#39;, &#39;PSQUERY_ONLINE_GROUP&#39;
    ,mgmt_p6 =&amp;gt; 90
&lt;b&gt;    ,parallel_queue_timeout=&amp;gt;900
    ,pq_timeout_action=&amp;gt;&#39;RUN&#39;
&lt;/b&gt;  );
&lt;/code&gt;&lt;/span&gt;&lt;/pre&gt;
  &lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;A consumer group can restrict queries that run for a long time, or that are expected to run for a long time based on their optimizer cost.&amp;nbsp; They can be switched to the CANCEL_SQL group after a number of seconds and they will terminate with &lt;i&gt;ORA-00040: active time limit exceeded - call aborted:&lt;/i&gt;.&amp;nbsp; This has only specified for the LOW_LIMITED_GROUP, and the PSQUERY_BATCH_GROUP for scheduled queries because the message is captured by the process scheduler and logged.&amp;nbsp; It has not been specified for PSQUERY_ONLINE_GROUP because this error is not handled well by the online application.&amp;nbsp; Just the Oracle error message will be displayed to the user without further explanation, which is neither friendly nor helpful.&amp;nbsp; Instead, there are PeopleSoft configuration options to limit query runtime.&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;
&lt;pre style=&quot;background-color: #eeeeee; font-family: &amp;quot;courier new&amp;quot;overflow: auto; line-height: 95%; text-align: left; width: 95%;&quot;&gt;&lt;span style=&quot;font-size: xx-small;&quot;&gt;&lt;code&gt;DBMS_RESOURCE_MANAGER.CREATE_PLAN_DIRECTIVE(
    &#39;PSFT_PLAN&#39;, &#39;PSQUERY_BATCH_GROUP&#39;
    ,mgmt_p6 =&amp;gt; 1
&lt;b&gt;    ,switch_group =&amp;gt; &#39;CANCEL_SQL&#39;
    ,switch_time =&amp;gt; 14400
    ,switch_estimate =&amp;gt; TRUE 
    ,switch_for_call =&amp;gt; TRUE
&lt;/b&gt;    );
&lt;/code&gt;&lt;/span&gt;&lt;/pre&gt;
  &lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Sometimes customers may have different priorities at different times that cannot be satisfied by a single resource plan.&amp;nbsp; In this case, different resource plans can be activated at different times by different scheduler windows.&amp;nbsp;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Other Online Resources&lt;/h3&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Oracle White Paper:&amp;nbsp;&lt;a href=&quot;https://www.oracle.com/technetwork/database/performance/resource-manager-twp-133705.pdf&quot; target=&quot;_blank&quot;&gt;Using Oracle Database ResourceManager&lt;/a&gt;&lt;/li&gt;&lt;li&gt;Documentation:&amp;nbsp;&lt;a href=&quot;https://oracle-base.com/articles/10g/resource-manager-enhancements-10g&quot; target=&quot;_blank&quot;&gt;Oracle Resource Manager Enhancements in Oracle Database 10g&lt;/a&gt;&amp;nbsp;&lt;/li&gt;&lt;li&gt;Documentation:&amp;nbsp;&lt;a href=&quot;https://oracle-base.com/articles/12c/resource-manager-enhancements-12cr1&quot; target=&quot;_blank&quot;&gt;Oracle Resource Manager Enhancements in Oracle Database 12c Release 1 (12.1)&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;blogger-post-footer&quot;&gt;&lt;a href=&quot;http://www.go-faster.co.uk/&quot;&gt;©David Kurtz&lt;/a&gt;&lt;/div&gt;</description><link>https://blog.psftdba.com/2024/03/psftplan-sample-oracle-database.html</link><author>noreply@blogger.com (David Kurtz)</author><thr:total>0</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-25740336.post-9076670175303476601</guid><pubDate>Tue, 20 Feb 2024 12:05:00 +0000</pubDate><atom:updated>2024-11-25T20:44:01.155+00:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">PS/Query</category><title>What PS/Query is that?</title><description>&lt;p&gt;Sometimes, performance analysis will turn up a problem SQL query that is probably a PS/Query. However, I need to know which PS/Query it is should I wish to alter it or talk to the user who wrote it.&amp;nbsp;&lt;/p&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Is it a PS/Query?&lt;/h3&gt;&lt;p&gt;It is quite easy to spot SQL queries that are generated from queries defined in the PS/Query tool. These are typical characteristics:&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Single character row source aliases (eg. A, B, D)&amp;nbsp;&lt;/li&gt;&lt;li&gt;The same row source with a suffix 1 (eg. D1) for query security records.&lt;/li&gt;&lt;li&gt;Effective date/sequence subqueries are always correlated back to the same table.&lt;/li&gt;&lt;li&gt;Order by column position number rather than column names or aliases.&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;Sometimes, you may find SQL that looks like a PS/Query coming from other parts of PeopleSoft because a developer has copied the text of a PS/Query, usually into an Application Engine step.&lt;br /&gt;
&lt;pre style=&quot;background-color: #eeeeee; font-family: &amp;quot;courier new&amp;quot;overflow: auto; line-height: 95%; text-align: left; width: 95%;&quot;&gt;&lt;span style=&quot;font-size: xx-small;&quot;&gt;&lt;code&gt;SELECT A.EMPLID, A.ATTENDANCE, A.COURSE, B.DESCR, D.NAME, A.SESSION_NBR,
TO_CHAR(A.STATUS_DT,&#39;YYYY-MM-DD&#39;),B.COURSE
FROM PS_TRAINING &lt;b&gt;A&lt;/b&gt;, PS_COURSE_TBL &lt;b&gt;B&lt;/b&gt;, PS_PERSONAL_DTA_VW &lt;b&gt;D&lt;/b&gt;, PS_PERS_SRCH_QRY &lt;b&gt;D1&lt;/b&gt;
WHERE D.EMPLID = D1.EMPLID
AND D1.ROWSECCLASS = &#39;HCDPALL&#39;
AND ( A.COURSE = :1
AND A.ATTENDANCE IN (&#39;S&#39;,&#39;W&#39;)
AND A.COURSE = B.COURSE
AND A.EMPLID = D.EMPLID )&lt;br /&gt;&lt;/code&gt;&lt;/span&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: left;&quot;&gt;The text of a PS/Query is not stored in the database.&amp;nbsp; Instead, as with other objects in PeopleSoft, it is held as various rows in PeopleTools tables.&amp;nbsp; The &lt;a href=&quot;https://www2.go-faster.co.uk/peopletools/_ptindex.htm#PSQ&quot; target=&quot;_blank&quot;&gt;PSQRY%&lt;/a&gt;&amp;nbsp;tables are used to generate the SQL on demand.&amp;nbsp; We can query these tables to identify the query.&amp;nbsp;&amp;nbsp;&lt;/p&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;a href=&quot;https://www2.go-faster.co.uk/peopletools/psqryrecord.htm&quot; target=&quot;_blank&quot;&gt;PSQRYRECORD&lt;/a&gt; holds a row for every record referenced in the query (not including effective date/sequence subqueries).&amp;nbsp; My usual tactic is to write a SQL query on PSQRYRECORD, like the one below, that looks for PS/Queries that reference these tables with these table aliases (see &lt;a href=&quot;https://www.go-faster.co.uk/p/peoplesoft-for-oracle-dba.html&quot; target=&quot;_blank&quot;&gt;PeopleSoft for the Oracle DBA&lt;/a&gt;, Chapter 11).&amp;nbsp;&amp;nbsp;&lt;/div&gt;
&lt;pre style=&quot;background-color: #eeeeee; font-family: &amp;quot;courier new&amp;quot;overflow: auto; line-height: 95%; text-align: left; width: 95%;&quot;&gt;&lt;span style=&quot;font-size: xx-small;&quot;&gt;&lt;code&gt;REM &lt;a href=&quot;http://findqry.sql&quot; target=&quot;_blank&quot;&gt;findqry.sql&lt;/a&gt;
SELECT a.oprid, a.qryname
FROM   psqryrecord a
,      psqryrecord b
,      psqryrecord d
WHERE  a.oprid = b.oprid
AND    a.qryname = b.qryname
AND    a.oprid = d.oprid
AND    a.qryname = d.qryname
AND    a.corrname = &#39;A&#39;
AND    a.recname = &#39;TRAINING&#39;
AND    b.corrname = &#39;B&#39;
AND    b.recname = &#39;COURSE_TBL&#39;
AND    d.corrname = &#39;D&#39;
AND    d.recname = &#39;PERSONAL_DTA_VW&#39;
/
&lt;/code&gt;&lt;/span&gt;&lt;/pre&gt;
&lt;div style=&quot;text-align: left;&quot;&gt;The example PS/Query above is TRN003__COURSE_WAITING_LIST from the HCM demo database.&amp;nbsp; However, my query on PSQRYRECORD found another PS/Queries with the same 3 records using the same row source aliases.&amp;nbsp; It is worth looking at queries on the same tables as they often suffer from the same problems, and you might want to make the same fix.&amp;nbsp;&amp;nbsp;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;Another source of results for this query (though not this time) can be when users copy a public PS/Query to a private one so they can alter it in isolation.&lt;/div&gt;
&lt;pre style=&quot;background-color: #eeeeee; font-family: &amp;quot;courier new&amp;quot;overflow: auto; line-height: 95%; text-align: left; width: 95%;&quot;&gt;&lt;span style=&quot;font-size: xx-small;&quot;&gt;&lt;code&gt;OPRID                          QRYNAME
------------------------------ ------------------------------
                               TRN002__SESSION_ROSTER
                               TRN003__COURSE_WAITING_LIST
&lt;/code&gt;&lt;/span&gt;&lt;/pre&gt;&lt;p&gt;Writing the query on PSQRYRECORD to find queries, which always is slightly different each time, is&amp;nbsp;quite boring.&amp;nbsp; So I have written a &lt;a href=&quot;https://github.com/davidkurtz/psscripts/blob/master/findqry.sql&quot; target=&quot;_blank&quot;&gt;script&lt;/a&gt; that will dynamically generate the SQL to identify a PS/Query.&lt;/p&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Start with a SQL_ID&lt;/h3&gt;&lt;div style=&quot;text-align: left;&quot;&gt;A SQL tuning activity will usually identify the SQL_ID and plan hash value of a statement.&amp;nbsp; If you are lucky, AWR will have captured the text and execution plan.&amp;nbsp; If not, you may have to try looking for a different SQL_ID that produces the same execution plan.&amp;nbsp; From the statement text, it is easy to see whether it might be a PS/Query.&amp;nbsp;&amp;nbsp;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;In this example, I have cut the SQL statement and execution plan back to show just the tables and indexes referenced.&lt;/div&gt;
&lt;pre style=&quot;background-color: #eeeeee; font-family: &amp;quot;courier new&amp;quot;overflow: auto; line-height: 95%; text-align: left; width: 95%;&quot;&gt;&lt;span style=&quot;font-size: xx-small;&quot;&gt;&lt;code&gt;SQL_ID c3h6vf2w5fxgp
--------------------
SELECT …
FROM PSTREELEAF B, PSTREENODE C, PS_OPER_UNIT_TBL A, PS_PRODUCT_TBL G 
…
UNION SELECT …
FROM PSTREENODE D,PS_TREE_NODE_TBL E, PSTREELEAF F 
…

--------------------------------------------------------------------------------------------------------------------------
|   Id  | Operation                                   | Name             | Rows  | Bytes |TempSpc| Cost (%CPU)| Time     |
--------------------------------------------------------------------------------------------------------------------------
|  *  7 |        INDEX STORAGE FAST FULL SCAN         | PSBPSTREELEAF    |   426K|    19M|       |  1178   (1)| 00:00:01 |
|    10 |          TABLE ACCESS BY INDEX ROWID BATCHED| PS_PRODUCT_TBL   |     1 |    41 |       |     3   (0)| 00:00:01 |
|  * 11 |           INDEX RANGE SCAN                  | PS_PRODUCT_TBL   |     1 |       |       |     2   (0)| 00:00:01 |
|  * 14 |              INDEX RANGE SCAN (MIN/MAX)     | PS_PRODUCT_TBL   |     1 |    21 |       |     2   (0)| 00:00:01 |
|  * 15 |       TABLE ACCESS STORAGE FULL             | PSTREENODE       |   135K|  5709K|       |   663   (1)| 00:00:01 |
|  * 17 |       INDEX STORAGE FAST FULL SCAN          | PS_OPER_UNIT_TBL |  1791 | 35820 |       |     4   (0)| 00:00:01 |
|  * 20 |       INDEX RANGE SCAN (MIN/MAX)            | PS_PSTREENODE    |     1 |    33 |       |     3   (0)| 00:00:01 |
|  * 23 |       INDEX RANGE SCAN (MIN/MAX)            | PSAPSTREELEAF    |     1 |    32 |       |     3   (0)| 00:00:01 |
|  * 26 |       INDEX RANGE SCAN (MIN/MAX)            | PS_OPER_UNIT_TBL |     1 |    20 |       |     2   (0)| 00:00:01 |
|    33 |          TABLE ACCESS INMEMORY FULL         | PS_TREE_NODE_TBL | 35897 |  1647K|       |     6   (0)| 00:00:01 |
|  * 35 |          TABLE ACCESS STORAGE FULL          | PSTREENODE       |   167K|  9670K|       |   663   (1)| 00:00:01 |
|- * 36 |       INDEX RANGE SCAN                      | PS_PSTREELEAF    |     1 |    39 |       |  1267   (1)| 00:00:01 |
|    37 |      INDEX STORAGE FAST FULL SCAN           | PS_PSTREELEAF    |   480K|    17M|       |  1267   (1)| 00:00:01 |
|  * 40 |       INDEX RANGE SCAN (MIN/MAX)            | PS_PSTREENODE    |     1 |    33 |       |     3   (0)| 00:00:01 |
|  * 43 |       INDEX RANGE SCAN (MIN/MAX)            | PS_TREE_NODE_TBL |     1 |    28 |       |     2   (0)| 00:00:01 |
--------------------------------------------------------------------------------------------------------------------------

Query Block Name / Object Alias (identified by operation id):
-------------------------------------------------------------

…
   7 - SEL$1 / B@SEL$1
  10 - SEL$1 / G@SEL$1
  11 - SEL$1 / G@SEL$1
…
  15 - SEL$1 / C@SEL$1
  17 - SEL$1 / A@SEL$1
…
  33 - SEL$6 / E@SEL$6
  35 - SEL$6 / D@SEL$6
  36 - SEL$6 / F@SEL$6
  37 - SEL$6 / F@SEL$6
…
&lt;/code&gt;&lt;/span&gt;&lt;/pre&gt;&lt;p&gt;I use this query on &lt;a href=&quot;https://docs.oracle.com/en/database/oracle/oracle-database/19/refrn/DBA_HIST_SQL_PLAN.html&quot; target=&quot;_blank&quot;&gt;DBA_HIST_SQL_PLAN&lt;/a&gt; to extract the tables that have single-character row source aliases that correspond to PeopleSoft records, and put them into &lt;a href=&quot;https://docs.oracle.com/en/database/oracle/oracle-database/19/refrn/PLAN_TABLE.html&quot; target=&quot;_blank&quot;&gt;PLAN_TABLE&lt;/a&gt;. I use this table because it is delivered by Oracle as a global temporary table, so it is always there and I can make use of it even if I only have read-only access.&lt;/p&gt;
&lt;pre style=&quot;background-color: #eeeeee; font-family: &amp;quot;courier new&amp;quot;overflow: auto; line-height: 95%; text-align: left; width: 95%;&quot;&gt;&lt;span style=&quot;font-size: xx-small;&quot;&gt;&lt;code&gt;INSERT INTO plan_table (object_name, object_alias) 
with p as ( &lt;i&gt;--plan lines with single letter aliases&lt;/i&gt;
SELECT DISTINCT object_owner, object_type, object_name, regexp_substr(object_alias,&#39;[[:alpha:]]&#39;,2,1) object_alias
from dba_hist_sql_plan p
, ps.psdbowner d
where p.sql_id = &#39;&amp;amp;&amp;amp;sql_id&#39; &lt;i&gt;--put SQL ID here--&lt;/i&gt;
and p.object_name IS NOT NULL
and p.object_owner = d.ownerid
and regexp_like(object_alias,&#39;&quot;[[:alpha:]]&quot;&#39;) &lt;i&gt;--single character aliases&lt;/i&gt;
), r as ( --PeopleSoft table records and the table name
select r.recname, DECODE(r.sqltablename,&#39; &#39;,&#39;PS_&#39;||r.recname,r.sqltablename) sqltablename
from psrecdefn r
where r.rectype = 0 --PeopleSoft table records
)
select r.recname, object_alias &lt;i&gt;--referenced table&lt;/i&gt;
from p, r
where p.object_type like &#39;TABLE%&#39;
and p.object_name = r.sqltablename
union --a query plan may reference an index and not the table
select r.recname, object_alias &lt;i&gt;--table for referenced index&lt;/i&gt;
from p, r
, all_indexes i
where p.object_type like &#39;INDEX%&#39;
and i.index_name = p.object_name
and i.owner = p.object_owner
and i.table_name = r.sqltablename
order by 2,1
/
&lt;/code&gt;&lt;/span&gt;&lt;/pre&gt;
I now have a list of records and row source aliases aliases
&lt;pre style=&quot;background-color: #eeeeee; font-family: &amp;quot;courier new&amp;quot;overflow: auto; line-height: 95%; text-align: left; width: 95%;&quot;&gt;&lt;span style=&quot;font-size: xx-small;&quot;&gt;&lt;code&gt;RECNAME         O
--------------- -
OPER_UNIT_TBL   A
PSTREELEAF      B
PSTREENODE      C
PSTREENODE      D
TREE_NODE_TBL   E
PSTREELEAF      F
PRODUCT_TBL     G
&lt;/code&gt;&lt;/span&gt;&lt;/pre&gt;
&lt;p&gt;Next, I can run this anonymous PL/SQL block to dynamically build the SQL query on PSQRYRECORD (one reference for every table) and execute it to find the matching PS/Queries&lt;/p&gt;
&lt;pre style=&quot;background-color: #eeeeee; font-family: &amp;quot;courier new&amp;quot;overflow: auto; line-height: 95%; text-align: left; width: 95%;&quot;&gt;&lt;span style=&quot;font-size: xx-small;&quot;&gt;&lt;code&gt;DECLARE 
  l_sep1 VARCHAR2(20);
  l_sep2 VARCHAR2(20);
  l_counter INTEGER := 0;
  l_sql CLOB := &#39;SELECT r1.oprid, r1.qryname&#39;;
  l_where CLOB;
  
  TYPE t_query IS RECORD (oprid VARCHAR2(30), qryname VARCHAR2(30));
  TYPE a_query IS TABLE OF t_query INDEX BY PLS_INTEGER;
  l_query a_query;
BEGIN
  FOR i IN(
    SELECT *
    FROM plan_table
    ORDER BY object_alias
  ) LOOP
    l_counter := l_counter + 1;
    dbms_output.put_line(i.object_alias||&#39;:&#39;||i.object_name);
    IF l_counter = 1 THEN
      l_sep1 := &#39; FROM &#39;;
      l_sep2 := &#39; WHERE &#39;;
    ELSE
      l_sep1 := &#39; ,&#39;;
      l_sep2 := &#39; AND &#39;;
      l_where := l_where||&#39; AND r1.oprid = r&#39;||l_counter||&#39;.oprid AND r1.qryname = r&#39;||l_counter||&#39;.qryname&#39;;
    END IF;
    l_sql := l_sql||l_sep1||&#39;psqryrecord r&#39;||l_counter;
    l_where := l_where||l_sep2||&#39;r&#39;||l_counter||&#39;.corrname = &#39;&#39;&#39;||i.object_alias||&#39;&#39;&#39; AND r&#39;||l_counter||&#39;.recname = &#39;&#39;&#39;||i.object_name||&#39;&#39;&#39;&#39;;
  END LOOP;
  l_sql := l_sql||l_where||&#39; ORDER BY 1,2&#39;;
  dbms_output.put_line(l_sql);

  EXECUTE IMMEDIATE l_sql BULK COLLECT INTO l_query;

  FOR indx IN 1 .. l_query.COUNT
  LOOP
    DBMS_OUTPUT.put_line (indx||&#39;:&#39;||l_query(indx).oprid||&#39;.&#39;||l_query(indx).qryname);
  END LOOP;
END;
/
&lt;/code&gt;&lt;/span&gt;&lt;/pre&gt;
&lt;p&gt;The seven records found in my execution plan become a query of PSQRYRECORD 7 times, one for each record, joined on operator ID and query name.&lt;/p&gt;&lt;pre style=&quot;background-color: #eeeeee; font-family: &amp;quot;courier new&amp;quot;overflow: auto; line-height: 95%; text-align: left; width: 95%;&quot;&gt;&lt;span style=&quot;font-size: xx-small;&quot;&gt;&lt;code&gt;SELECT r1.oprid, r1.qryname 
FROM psqryrecord r1 ,psqryrecord r2 ,psqryrecord r3 ,psqryrecord r4 ,psqryrecord r5 ,psqryrecord r6 ,psqryrecord r7 
WHERE r1.corrname = &#39;A&#39; AND r1.recname = &#39;OPER_UNIT_TBL&#39;
AND r1.oprid = r2.oprid AND r1.qryname = r2.qryname AND r2.corrname = &#39;B&#39; AND r2.recname = &#39;PSTREELEAF&#39; 
AND r1.oprid = r3.oprid AND r1.qryname = r3.qryname AND r3.corrname = &#39;C&#39; AND r3.recname = &#39;PSTREENODE&#39; 
AND r1.oprid = r4.oprid AND r1.qryname = r4.qryname AND r4.corrname = &#39;D&#39; AND r4.recname = &#39;PSTREENODE&#39; 
AND r1.oprid = r5.oprid AND r1.qryname = r5.qryname AND r5.corrname = &#39;E&#39; AND r5.recname = &#39;TREE_NODE_TBL&#39; 
AND r1.oprid = r6.oprid AND r1.qryname = r6.qryname AND r6.corrname = &#39;F&#39; AND r6.recname = &#39;PSTREELEAF&#39; 
AND r1.oprid = r7.oprid AND r1.qryname = r7.qryname AND r7.corrname = &#39;G&#39; AND r7.recname = &#39;PRODUCT_TBL&#39; 
ORDER BY 1,2
&lt;/code&gt;&lt;/span&gt;&lt;/pre&gt;&lt;div style=&quot;text-align: left;&quot;&gt;The query finds several queries. I can look at the public PS/Queries in the Query Manager tool.&amp;nbsp; I can also see which users&#39; private queries exist.&lt;br /&gt;NB. You can only open public queries (where OPRID is a single space) or your own private queries.&amp;nbsp; In the Query Manager, you cannot see a private query owned by another user.&lt;/div&gt;&lt;pre style=&quot;background-color: #eeeeee; font-family: &amp;quot;courier new&amp;quot;overflow: auto; line-height: 95%; text-align: left; width: 95%;&quot;&gt;&lt;span style=&quot;font-size: xx-small;&quot;&gt;&lt;code&gt;…
3: .PS_TREE_PRODUCT
4: .QUERY_PRODUCT_TREE
5: .RM_TREE_PRODUCT
6:XXXXXX.PS_TREE_PRODUCT_XX
…
&lt;/code&gt;&lt;/span&gt;&lt;/pre&gt;
The new &lt;a href=&quot;https://github.com/davidkurtz/psscripts/blob/master/findqry.sql&quot; target=&quot;_blank&quot;&gt;findqry.sql script is available on Github&lt;/a&gt;.&lt;div class=&quot;blogger-post-footer&quot;&gt;&lt;a href=&quot;http://www.go-faster.co.uk/&quot;&gt;©David Kurtz&lt;/a&gt;&lt;/div&gt;</description><link>https://blog.psftdba.com/2024/02/what-psquery-is-that.html</link><author>noreply@blogger.com (David Kurtz)</author><thr:total>0</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-25740336.post-6309444875087579193</guid><pubDate>Thu, 25 Jan 2024 14:38:00 +0000</pubDate><atom:updated>2026-01-09T13:22:57.296+00:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">Priority</category><category domain="http://www.blogger.com/atom/ns#">Tuxedo</category><title>Reducing the Operating System Priority of PeopleSoft Processes</title><description>&lt;p style=&quot;text-align: left;&quot;&gt;&lt;/p&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://media.springernature.com/w306/springer-static/cover-hires/book/978-1-4302-3708-2&quot; style=&quot;clear: left; float: left; margin-bottom: 1em; margin-right: 1em;&quot;&gt;&lt;img alt=&quot;PeopleSoft for the Oracle DBA&quot; border=&quot;0&quot; data-original-height=&quot;376&quot; data-original-width=&quot;306&quot; height=&quot;100&quot; src=&quot;https://media.springernature.com/w306/springer-static/cover-hires/book/978-1-4302-3708-2&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;p style=&quot;text-align: left;&quot;&gt;I wrote about controlling the operating system priority of processes in PeopleSoft&amp;nbsp;Tuxedo domains in Chapters 13 of 14 of &lt;a href=&quot;https://www.go-faster.co.uk/p/peoplesoft-for-oracle-dba.html&quot;&gt;PeopleSoft for the Oracle DBA&lt;/a&gt;, but I think it is worth a note here.&lt;/p&gt;&lt;div&gt;&lt;p style=&quot;text-align: left;&quot;&gt;On Linux and Unix systems, the nice command can be used to lower the operating system scheduling priority of a process (or a privileged can increase the priority).  When a server has no free CPU, processes with a lower priority get less time on the CPU. However, when there is free CPU available, the scheduling priority does not affect the amount of CPU that the process can utilise.&amp;nbsp;&lt;/p&gt;&lt;div&gt;&lt;p style=&quot;text-align: left;&quot;&gt;On Unix, the priority of a Tuxedo server process can be adjusted using the &lt;a href=&quot;https://docs.oracle.com/cd/E13203_01/tuxedo/tux80/atmi/rf523.htm#1003316&quot; target=&quot;_blank&quot;&gt;-n server command line option&lt;/a&gt;&amp;nbsp;in the configuration. The parameters to this option are simply passed through to the &lt;a href=&quot;https://docs.oracle.com/cd/E26502_01/html/E29032/nice-2.html#REFMAN2nice-2&quot; rel=&quot;nofollow&quot; target=&quot;_blank&quot;&gt;nice(2)&lt;/a&gt; function. Hence, this option does not work on Windows.
&lt;/p&gt;&lt;pre style=&quot;background-color: #eeeeee; font-family: &amp;quot;courier new&amp;quot;overflow: auto; line-height: 95%; text-align: left; width: 95%;&quot;&gt;&lt;span style=&quot;font-size: xx-small;&quot;&gt;&lt;code&gt;PSPRCSRV        SRVGRP=BASE
                SRVID=101
                MIN=1
                MAX=1
                RQADDR=&quot;SCHEDQ&quot;
                REPLYQ=Y
                CLOPT=&quot;&lt;b&gt;-n 4&lt;/b&gt; -sInitiateRequest -- -C psprcs.cfg -CD HR88 -PS PSUNX -A start -S PSPRCSRV&quot;&lt;/code&gt;&lt;/span&gt;&lt;/pre&gt;
The operating system priority of a process is inherited from its parent. Therefore, lowering the priority of the Process Scheduler running under Tuxedo will also lower the priority of the batch processes that it spawns.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Therefore Stand-alone Application Engine processes (&lt;i&gt;psae&lt;/i&gt;) and Cobol processes inherit the priority of the process scheduler server process (&lt;i&gt;PSPRCSRV&lt;/i&gt;).&lt;/li&gt;&lt;li&gt;However, if the Application Engine server process (&lt;i&gt;PSAESRV&lt;/i&gt;) is used, its priority can be set directly.&amp;nbsp;&lt;/li&gt;&lt;/ul&gt;There are some potential uses for this approach.&lt;br /&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;If the process scheduler is co-resident with the application server, then it could be run at a lower priority to ensure the online users get preferential allocation of CPU, and that online performance does not suffer excessively at the hands of the batch.&lt;/li&gt;&lt;li&gt;A system might have two websites: one for self-service and the other for the &#39;back-office&#39; users. You could configure separate application servers for each site, and run the self-service application server is run at a lower priority.&amp;nbsp;&lt;/li&gt;&lt;/ul&gt;&lt;p style=&quot;text-align: left;&quot;&gt;In PeopleSoft, I prefer to create additional variables in the configuration file (&lt;i&gt;psprcs.cfg&lt;/i&gt;).&lt;/p&gt;&lt;/div&gt;&lt;div&gt;&lt;pre style=&quot;background-color: #eeeeee; font-family: &amp;quot;courier new&amp;quot;overflow: auto; line-height: 95%; text-align: left; width: 95%;&quot;&gt;&lt;span style=&quot;font-size: x-small;&quot;&gt;&lt;code&gt;&lt;b&gt;[Process Scheduler]&lt;/b&gt;
;=========================================================================
; General settings for the Process Scheduler
;=========================================================================
PrcsServerName=PSUNX
;-------------------------------------------------------------------------
;Reduce priority of Process Scheduler server process, set to 0 if not needed
&lt;b&gt;Niceness=4&lt;/b&gt;
…&lt;/code&gt;&lt;/span&gt;&lt;/pre&gt;From PeopleTools 8.4, the Application Engine server process is configured by default. The priority of the AE server processes can then be controlled independently of the process scheduler by creating a separate variable in the PSAESRV section of the configuration file.&amp;nbsp; However, it is generally better to use standalone PSAE, unless you have many short-lived application engine processes, as in CRM (see&amp;nbsp;&lt;a href=&quot;https://blog.psftdba.com/2018/04/application-engine-in-process-scheduler.html&quot;&gt;Application Engine in Process Scheduler: PSAESRV Server Process -v- Standalone PSAE executable&lt;/a&gt;).&amp;nbsp; &amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;pre style=&quot;background-color: #eeeeee; font-family: &amp;quot;courier new&amp;quot;overflow: auto; line-height: 95%; text-align: left; width: 95%;&quot;&gt;&lt;span style=&quot;font-size: x-small;&quot;&gt;&lt;code&gt;&lt;b&gt;[PSAESRV]&lt;/b&gt;
;=========================================================================
; Settings for Application Engine Tuxedo Server
;=========================================================================
;-------------------------------------------------------------------------
;Reduce priority of application engine server process, set to 0 if not needed
&lt;b&gt;Niceness=5&lt;/b&gt;
…&lt;/code&gt;&lt;/span&gt;&lt;/pre&gt;&lt;div&gt;In this example, I have reduced the priorities of both the process scheduler and AE servers, but the process scheduler is left with a higher priority than the AE servers. The new variables can then be referenced&amp;nbsp;Tuxedo template file (&lt;i&gt;psprcsrv.ubx&lt;/i&gt;).&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;pre style=&quot;background-color: #eeeeee; font-family: &amp;quot;courier new&amp;quot;overflow: auto; line-height: 95%; text-align: left; width: 95%;&quot;&gt;&lt;span style=&quot;font-size: 60%;&quot;&gt;&lt;code&gt;{APPENG}
#
# PeopleSoft Application Engine Server
#
PSAESRV         SRVGRP=AESRV
                SRVID=1
                MIN={$PSAESRV\Max Instances}
                MAX={$PSAESRV\Max Instances}
                REPLYQ=Y
                CLOPT=&quot;&lt;b&gt;-n {$PSAESRV\Niceness}&lt;/b&gt; -- -C {CFGFILE} -CD {$Startup\DBName} -S PSAESRV&quot;
{APPENG}
…
PSPRCSRV        SRVGRP=BASE
                SRVID=101
                MIN=1
                MAX=1
                RQADDR=&quot;SCHEDQ&quot;
                REPLYQ=Y
                CLOPT=&quot;&lt;b&gt;-n {$Process Scheduler\Niceness}&lt;/b&gt; -sInitiateRequest -- -C {CFGFILE} -CD {$Startup\DBName} -PS {$Process Scheduler\PrcsServerName} -A start -S PSPRCSRV&quot;&lt;/code&gt;&lt;/span&gt;&lt;/pre&gt;
When the domain is configured in &lt;i&gt;psadmin&lt;/i&gt;, the variables are resolved in the Tuxedo configuration file (&lt;i&gt;psprcsrv.ubb&lt;/i&gt;).&amp;nbsp; The -n option can be seen in the server command-line options (&lt;i&gt;CLOPT&lt;/i&gt;).&lt;pre style=&quot;background-color: #eeeeee; font-family: &amp;quot;courier new&amp;quot;overflow: auto; line-height: 95%; text-align: left; width: 95%;&quot;&gt;&lt;span style=&quot;font-size: xx-small;&quot;&gt;&lt;code&gt;#
# PeopleSoft Application Engine Server
#
PSAESRV         SRVGRP=AESRV
                SRVID=1
                MIN=1
                MAX=1
                REPLYQ=Y
                CLOPT=&quot;&lt;b&gt;-n 5&lt;/b&gt; -- -C psprcs.cfg -CD HR88 -S PSAESRV&quot;
…
PSPRCSRV        SRVGRP=BASE
                SRVID=101
                MIN=1
                MAX=1
                RQADDR=&quot;SCHEDQ&quot;
                REPLYQ=Y
                CLOPT=&quot;&lt;b&gt;-n 4&lt;/b&gt; -sInitiateRequest -- -C psprcs.cfg -CD HR88 -PS PSUNX -A start -S PSPRCSRV&quot;&lt;/code&gt;&lt;/span&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;blogger-post-footer&quot;&gt;&lt;a href=&quot;http://www.go-faster.co.uk/&quot;&gt;©David Kurtz&lt;/a&gt;&lt;/div&gt;</description><link>https://blog.psftdba.com/2024/01/reducing-operating-system-priority-of.html</link><author>noreply@blogger.com (David Kurtz)</author><thr:total>0</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-25740336.post-5987469583506009175</guid><pubDate>Fri, 10 Nov 2023 11:13:00 +0000</pubDate><atom:updated>2024-02-22T09:00:37.006+00:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">Priority</category><category domain="http://www.blogger.com/atom/ns#">Process Scheduler</category><title>Prioritising Scheduled Processes by Operator ID/Run Control</title><description>&lt;p style=&quot;text-align: left;&quot;&gt;Batch processing is like opera (and baseball) -&amp;nbsp;&lt;span style=&quot;text-align: center;&quot;&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/It_ain%27t_over_till_the_fat_lady_sings&quot; style=&quot;font-style: italic;&quot; target=&quot;_blank&quot;&gt;&quot;It ain&#39;t over till the fat lady sings&quot;&lt;/a&gt;.&amp;nbsp; Users care about when it starts and when it finishes.&amp;nbsp; If the last process finishes earlier, then that is an improvement in performance.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;&lt;p style=&quot;text-align: left;&quot;&gt;This note describes a method of additionally prioritising processes queued to run on the process scheduler in PeopleSoft by their requesting operator ID and run control.&amp;nbsp; Where processing consists of more instances of the same process than can run concurrently, it can be used to make the process scheduler run longer-running processes before shorter-running processes that were scheduled earlier, thus completing batch processing earlier.&lt;/p&gt;&lt;p style=&quot;text-align: left;&quot;&gt;In PeopleSoft, without customisation, it is only possible to prioritise processes queued to run on the process scheduler by assigning a priority to the process definition or their process category.&amp;nbsp; Higher priority processes are selected to be run in preference to lower priorities.&amp;nbsp; Otherwise, processes are run in the order of the time at which they are requested to run.&lt;/p&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Problem Statement&lt;/h3&gt;&lt;div&gt;During an overnight batch, many nVision report books are scheduled to run on the Windows process schedulers by one of several specific batch operator IDs.&amp;nbsp; Many more reports are scheduled than can run concurrently, so some execute while others queue.&amp;nbsp; Inevitably, the reports have widely varying execution times.&amp;nbsp; The maximum concurrency of the&amp;nbsp;&lt;a href=&quot;https://docs.oracle.com/cd/F44947_01/pt858pbr3/eng/pt/tnvs/task_UsingReportBooks-074eb6.html?pli=ul_d58e178_tnvs&quot; target=&quot;_blank&quot;&gt;nVision report book&lt;/a&gt;&amp;nbsp;(RPTBOOK) process definition has been set, and the Oracle database resource manager has also been configured, to prevent too many of these processes from overloading the database.&lt;/div&gt;&lt;p&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEiggKvNhw0KjfvuxFQfGwu7QCK3zlFFmvN6ISImdpay5JqUrcx367iGgdNJtsc8c2yg1Y7HkZoSIXOteaMSR2kxD5gGtlO5NILnhNpYljhFG5FCSx8EnaTCs5Yhio1YzzwZZS8w3NlTaEs6URis_s0UGU1UJKDwFzHPO_KYdcS2dRtLoeBzpGpI&quot; style=&quot;clear: right; float: right; margin-bottom: 1em; margin-left: 1em; text-align: center;&quot;&gt;&lt;img alt=&quot;CPU Utilisation of Batch&quot; data-original-height=&quot;3990&quot; data-original-width=&quot;6108&quot; height=&quot;210&quot; src=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEiggKvNhw0KjfvuxFQfGwu7QCK3zlFFmvN6ISImdpay5JqUrcx367iGgdNJtsc8c2yg1Y7HkZoSIXOteaMSR2kxD5gGtlO5NILnhNpYljhFG5FCSx8EnaTCs5Yhio1YzzwZZS8w3NlTaEs6URis_s0UGU1UJKDwFzHPO_KYdcS2dRtLoeBzpGpI=w320-h210&quot; title=&quot;CPU Utilisation of Batch&quot; width=&quot;320&quot; /&gt;&lt;/a&gt;This chart shows the database activity when the batch runs.&amp;nbsp; We often see what has come to be called the &#39;long tail&#39; while we wait for just a few long-running processes to complete.&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;separator&quot;&gt;The next chart&amp;nbsp;shows the processing time of each nVision report process.&amp;nbsp; The blue bars run from when the started to when it ended, ordered by start time.&amp;nbsp; &amp;nbsp;The clear boxes below run from the time when it was requested to when it started, thus showing the period for which the processes were queued on a process scheduler, but were blocked because the maximum number of processes were already processing.&lt;/div&gt;&lt;div class=&quot;separator&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEjpccI5vcNIF4IMtIrG1-gQp-GrqLlIT4eEWw3FiqZIR2ZOXW5rhVSmqeNoR3riNtNizgSncyE6LGf3S7mRXG6d3WbQ6AOxVX6PDug9j8FwFzWJ0jv0LTTfbXbkLptGri7OHdE3x4hlU9oTPncd0m1ps9ziiohsEPth0JT9sX6VyAVqnGg61oCR&quot; style=&quot;margin-left: 1em; margin-right: 1em; text-align: center;&quot;&gt;&lt;img alt=&quot;Process Map (without prioritisation)&quot; data-original-height=&quot;3990&quot; data-original-width=&quot;6108&quot; height=&quot;261&quot; src=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEjpccI5vcNIF4IMtIrG1-gQp-GrqLlIT4eEWw3FiqZIR2ZOXW5rhVSmqeNoR3riNtNizgSncyE6LGf3S7mRXG6d3WbQ6AOxVX6PDug9j8FwFzWJ0jv0LTTfbXbkLptGri7OHdE3x4hlU9oTPncd0m1ps9ziiohsEPth0JT9sX6VyAVqnGg61oCR=w400-h261&quot; title=&quot;Process Map (without prioritisation)&quot; width=&quot;400&quot; /&gt;&lt;/a&gt;&lt;/div&gt;All these processes run with the same priority because they are the same process definition.&amp;nbsp; Some long-running jobs execute earlier in the batch simply because they were scheduled earlier, but others that started later, run on beyond the end of the batch.&amp;nbsp;&amp;nbsp;&lt;div&gt;It would be better if the longest-running processes were executed earlier, irrespective of the order in which they were requested.&amp;nbsp; Thus the shorter processes can run later as slots on the scheduler become free, and thus all processes should finished both closer together and earlier.&lt;/div&gt;&lt;div&gt;There is nothing delivered in the PeopleSoft process scheduler configuration that will let you assign different priorities to different executions of the same process.&amp;nbsp; In PeopleSoft, only three priorities are defined &lt;a href=&quot;https://www2.go-faster.co.uk/peopletools/prcsdefn.htm#prcspriority&quot; target=&quot;_blank&quot;&gt;PRCSPRIORITY&lt;/a&gt; (1=Low, 5=Medium, 9=High) on the process definition and the server category.&amp;nbsp; These priorities are transferred to the process request queue (&lt;a href=&quot;https://www2.go-faster.co.uk/peopletools/psprcsque.htm#prcsprty&quot; target=&quot;_blank&quot;&gt;PSPRCSQUE.PRCSPRTY&lt;/a&gt;).&amp;nbsp;&amp;nbsp;&lt;/div&gt;&lt;div&gt;If we could put our own priority into that column we could control the priority of the request.&amp;nbsp;&amp;nbsp;&lt;h3 style=&quot;text-align: left;&quot;&gt;Solution&lt;/h3&gt;&lt;div&gt;Introduce a database trigger that fires on insert into PSPRCSQUE and sets a priority specified on a new metadata table.&amp;nbsp; PRCSPRTY is not validated by PeopleSoft, and therefore any value can be specified.&amp;nbsp;&amp;nbsp;&lt;/div&gt;&lt;div&gt;The files are available in a Github repository &lt;a href=&quot;https://github.com/davidkurtz/psprcsprty&quot; target=&quot;_blank&quot;&gt;davidkurtz/psprcspty&lt;/a&gt;. The exact metadata will vary with the use case and requirements, but I provided some examples of how it might be generated.&lt;/div&gt;&lt;p&gt;&lt;/p&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&lt;a href=&quot;https://github.com/davidkurtz/psprcsprty/blob/main/process_prioritisation_by_cumulative_runtime.sql&quot; target=&quot;_blank&quot;&gt;process_prioritisation_by_cumulative_runtime.sql&lt;/a&gt; - master script that creates metadata table and trigger and then:&lt;/li&gt;&lt;ul&gt;&lt;li&gt;&lt;a href=&quot;https://github.com/davidkurtz/psprcsprty/blob/main/nvision_prioritisation_by_cumulative_runtime.sql&quot; target=&quot;_blank&quot;&gt;nvision_prioritisation_by_cumulative_runtime.sql&lt;/a&gt; - example script to create a procedure to populate metadata for nVision batch.&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://github.com/davidkurtz/psprcsprty/blob/main/gppdprun_prioritisation_by_cumulative_runtime.sql&quot; target=&quot;_blank&quot;&gt;gppdprun_prioritisation_by_cumulative_runtime.sql&lt;/a&gt; - example script to create a procedure to populate metadata for Payroll/Absence calculation batch.&amp;nbsp;&amp;nbsp;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://github.com/davidkurtz/psprcsprty/blob/main/process_prioritisation_by_cumulative_runtime_test.sql&quot; target=&quot;_blank&quot;&gt;process_prioritisation_by_cumulative_runtime_test.sql&lt;/a&gt; - test trigger by inserting dummy data into process queue table.&lt;/li&gt;&lt;/ul&gt;&lt;li&gt;&lt;a href=&quot;https://github.com/davidkurtz/psprcsprty/blob/main/process_prioritisation_by_cumulative_runtime_report.sql&quot; target=&quot;_blank&quot;&gt;process_prioritisation_by_cumulative_runtime_report.sql&lt;/a&gt; - example of a report to compare median execution time with last execution time.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;/p&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Metadata Table: PS_XX_GFCPRCSPRTY&lt;/h3&gt;
&lt;p&gt;We need a table that will hold the priority for each combination of process type, process name, operation ID, and run control ID.&amp;nbsp; A corresponding record should be created using the Application Designer &lt;a href=&quot;https://github.com/davidkurtz/psprcsprty/tree/main/XX_GFCPRCSPRTY&quot; target=&quot;_blank&quot;&gt;project in the GitHub repository&lt;/a&gt;.&lt;/p&gt;
&lt;pre style=&quot;background-color: #eeeeee; font-family: &amp;quot;courier new&amp;quot;overflow: auto; line-height: 95%; text-align: left; width: 95%;&quot;&gt;&lt;span style=&quot;font-size: x-small;&quot;&gt;&lt;code&gt;create table sysadm.ps_xx_gfcprcsprty
(prcstype  VARCHAR2(30 CHAR) NOT NULL
,prcsname  VARCHAR2(12 CHAR) NOT NULL
,oprid     VARCHAR2(30 CHAR) NOT NULL
,runcntlid VARCHAR2(30 CHAR) NOT NULL
,prcsprty  NUMBER NOT NULL
--------------------optional columns
,avg_duration NUMBER NOT NULL
,med_duration NUMBER NOT NULL
,max_duration NUMBER NOT NULL
,cum_duration NUMBER NOT NULL
,tot_duration NUMBER NOT NULL
,num_samples  NUMBER NOT NULL
) tablespace ptapp;

create unique index sysadm.ps_xx_gfcprcsprty
on sysadm.ps_xx_gfcprcsprty(prcstype, prcsname, oprid, runcntlid) 
tablespace psindex compress 3;&lt;/code&gt;&lt;/span&gt;&lt;/pre&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Trigger Before Insert into PSPRCSQUE&lt;/h3&gt;&lt;p&gt;As processes are scheduled in PeopleSoft, a row is inserted into the process scheduler queue table&amp;nbsp;&lt;a href=&quot;https://www2.go-faster.co.uk/peopletools/psprcsque.htm&quot; target=&quot;_blank&quot;&gt;PSPRCSQUE&lt;/a&gt;.&amp;nbsp; A trigger will be created on this table that fires after the insert.&amp;nbsp; It will look for a matching row on the metadata table, PS_XX_GFCPRCSPRTY for the combination of process type, process name, operator ID, and run control ID.&amp;nbsp; If found, the trigger will assign the specified priority to the process request.&amp;nbsp; Otherwise, it will take no action.&lt;/p&gt;
&lt;pre style=&quot;background-color: #eeeeee; font-family: &amp;quot;courier new&amp;quot;overflow: auto; line-height: 95%; text-align: left; width: 95%;&quot;&gt;&lt;span style=&quot;font-size: x-small;&quot;&gt;&lt;code&gt;CREATE OR REPLACE TRIGGER sysadm.psprcsque_set_prcsprty
BEFORE INSERT ON sysadm.psprcsque
FOR EACH ROW
WHEN (new.prcsname = &#39;RPTBOOK&#39;)
DECLARE
  l_prcsprty NUMBER;
BEGIN
  SELECT prcsprty
  INTO   l_prcsprty
  FROM   ps_xx_gfcprcsprty
  WHERE  prcstype = :new.prcstype
  AND    prcsname = :new.prcsname
  AND    oprid = :new.oprid
  AND    runcntlid = :new.runcntlid;
 
  :new.prcsprty := l_prcsprty;
EXCEPTION
  WHEN no_data_found THEN NULL;
  WHEN others THEN NULL;
END;
/
show errors&lt;/code&gt;&lt;/span&gt;&lt;/pre&gt;
&lt;p&gt;In this case, I am only assigning priorities to RPTBOOK processes, so I have added a when clause to the trigger so that it only fires for RPTBOOK process requests.&amp;nbsp; This can either be changed for other processes or removed entirely.&lt;/p&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Priority Metadata&lt;/h3&gt;&lt;p&gt;How the priorities should be defined will depend on the specific use case.&amp;nbsp; In some cases, you may choose to create a set of metadata that remains unchanged.&lt;/p&gt;&lt;p&gt;In this case, the objective is that the processes to take the longest to run should be executed first.&amp;nbsp; Therefore, I decided that the priority of each nVision report book process (by operator ID and run control ID) will be determined by the median elapsed execution time in the last two months.&amp;nbsp; The priorities are allocated such that the sum of the median execution times for each priority will be as even as possible.&amp;nbsp;&amp;nbsp;&lt;/p&gt;&lt;p&gt;I have created a PL/SQL procedure GFCPRCSPRIORITY to truncate the metadata table and then repopulate it using a query on the process scheduler table (although, an Application Engine program could have been written to do this instead).&amp;nbsp; Some details of that query will vary with the exact use case. The procedure is executed daily, thus providing a feedback loop so if the run time varies over time, or new processes are added to the batch, it will be reflected in the priorities.&lt;/p&gt;
&lt;pre style=&quot;background-color: #eeeeee; font-family: &amp;quot;courier new&amp;quot;overflow: auto; line-height: 95%; text-align: left; width: 95%;&quot;&gt;&lt;span style=&quot;font-size: x-small;&quot;&gt;&lt;code&gt;REM &lt;a href=&quot;https://github.com/davidkurtz/psprcsprty/blob/main/nvision_prioritisation_by_cumulative_runtime.sql&quot; target=&quot;_blank&quot;&gt;nvision_prioritisation_by_cumulative_runtime.sql&lt;/a&gt;

set serveroutput on
create or replace procedure sysadm.gfcprcspriority as
  PRAGMA AUTONOMOUS_TRANSACTION; --to prevent truncate in this procedure affecting calling session
  l_hist INTEGER := 61 ; --consider nVision processes going back this many days
begin
  EXECUTE IMMEDIATE &#39;truncate table ps_xx_gfcprcsprty&#39;;

--populate priorty table with known nVision processes 
insert into ps_xx_gfcprcsprty
with r as (
select r.prcstype, r.prcsname, r.prcsinstance, r.oprid, r.runcntlid, r.runstatus, r.servernamerun
, CAST(r.rqstdttm AS DATE) rqstdttm
, CAST(r.begindttm AS DATE) begindttm
, CAST(r.enddttm AS DATE) enddttm
from t, psprcsrqst r
  inner join ps.psdbowner p on r.dbname = p.dbname -- in test exclude any history copied from another database
where r.prcstype like &#39;nVision%&#39; --limit to nVision processes
and r.prcsname like &#39;RPTBOOK&#39; -- limit to report books
and r.enddttm&amp;gt;r.begindttm  --it must have run to completion
and r.oprid IN(&#39;NVISION&#39;,&#39;NVISION2&#39;,&#39;NVISION3&#39;,&#39;NVISION4&#39;)  --limit to overnight batch operator IDs
and r.begindttm &amp;gt;= TRUNC(SYSDATE)+.5-l_hist --consider process going back l_hist days from midday today
and r.runstatus = &#39;9&#39; --limit to successful processes
and r.begindttm BETWEEN ROUND(r.begindttm)-5/24 AND ROUND(r.begindttm)+5/24 --started between 7pm and 5am
), x as (
select r.*, CEIL((enddttm-begindttm)*1440) duration -–rounded up to the next minute
from r
), y as (
select prcstype, prcsname, oprid, runcntlid
, AVG(duration) avg_duration
, MEDIAN(CEIL(duration)) med_duration 
, MAX(duration) max_duration
, SUM(CEIL(duration)) sum_duration
, COUNT(*) num_samples
from x
group by prcstype, prcsname, oprid, runcntlid
), z as (
select y.* 
, sum(med_duration) over (order by med_duration rows between unbounded preceding and current row) cum_duration 
, sum(med_duration) over () tot_duration
from y
)
select prcstype, prcsname, oprid, runcntlid 
, avg_duration, med_duration, max_duration, cum_duration, tot_duration, num_samples
--, CEIL(LEAST(tot_duration,cum_duration)/tot_duration*3)*4-3 prcsprty  --3 priorities
, CEIL(LEAST(tot_duration,cum_duration)/tot_duration*9) prcsprty  --9 priorities
--, DENSE_RANK() OVER (order by med_duration) prcsprty --unlimited priorities
from z
order by prcsprty, cum_duration;

  dbms_output.put_line(sql%rowcount||&#39; rows inserted&#39;);
  commit;

end gfcprcspriority;
/
show errors&lt;/code&gt;&lt;/span&gt;&lt;/pre&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-weight: 400;&quot;&gt;In testing, I found that using just the 3 delivered levels of priority was not sufficiently granular to prioritise the jobs adequately, so I chose to use 9 levels (1 to 9).&amp;nbsp; The process priority on PRCSQUE is not validated, so I can use any value.&amp;nbsp; I also found I could just rank the processes from 1 to &lt;i&gt;n&lt;/i&gt; by duration, and that would also work.&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-weight: 400;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-size: x-small; font-weight: 400;&quot;&gt;&lt;i&gt;It is possible to create additional priority levels for process categories (see also&amp;nbsp;&lt;a href=&quot;https://blog.psftdba.com/2011/03/more-process-priority-levels-for.html&quot;&gt;More Process Priority Levels for the Process Scheduler&lt;/a&gt;), but that still only works for prioritising different processes over each other.&lt;/i&gt;&lt;/span&gt;&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Metadata&lt;/h3&gt;&lt;p&gt;This is the metadata produced on a test system by the above query.&amp;nbsp; It will vary depending on what has been run recently, and how it performed.&amp;nbsp; There are more, shorter processes in the lower priority groups, and fewer, longer processes in the higher priority groups.&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;pre style=&quot;background-color: #eeeeee; font-family: &amp;quot;courier new&amp;quot;overflow: auto; line-height: 95%; text-align: left; width: 95%;&quot;&gt;&lt;span style=&quot;font-size: x-small;&quot;&gt;&lt;code&gt;PRCSTYPE                       PRCSNAME     OPRID        RUNCNTLID                        PRCSPRTY
------------------------------ ------------ ------------ ------------------------------ ----------
…
nVision-ReportBook             RPTBOOK      NVISION2     NVS_RPTBK_XXX1                          1
nVision-ReportBook             RPTBOOK      NVISION2     NVS_RPTBK_XXX3                          1
nVision-ReportBook             RPTBOOK      NVISION2     NVS_RPTBK_LLLL8                         1
…
nVision-ReportBook             RPTBOOK      NVISION2     NVS_RPTBK_LLLL9                         2
nVision-ReportBook             RPTBOOK      NVISION      NVS_RPTBOOK_STAT8                       2
nVision-ReportBook             RPTBOOK      NVISION      NVS_RPTBOOK_STAT1                       2
…
nVision-ReportBook             RPTBOOK      NVISION2     NVS_RPTBK_TEMPXX                        6
nVision-ReportBook             RPTBOOK      NVISION      NVS_RPTBOOK_3                           6
nVision-ReportBook             RPTBOOK      NVISION      NVS_RPTBOOK_17                          6
nVision-ReportBook             RPTBOOK      NVISION      NVS_RPTBOOK_STAT4                       7
nVision-ReportBook             RPTBOOK      NVISION      NVS_RPTBOOK_28                          7
nVision-ReportBook             RPTBOOK      NVISION2     NVS_RPTBK_INCXXX                        8
nVision-ReportBook             RPTBOOK      NVISION2     NVS_RPTBK_MORYYY1                       8
nVision-ReportBook             RPTBOOK      NVISION      NVS_RPTBOOK_24                          9
nVision-ReportBook             RPTBOOK      NVISION      NVS_RPTBOOK_16                          9&lt;br /&gt;&lt;/code&gt;&lt;/span&gt;&lt;/pre&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;A Test Script&lt;/h3&gt;&lt;p&gt;This test script inserts some dummy rows into PSPRCSQUE to check whether a priority is assigned by the trigger. The insert is then rolled back.&lt;/p&gt;
&lt;pre style=&quot;background-color: #eeeeee; font-family: &amp;quot;courier new&amp;quot;overflow: auto; line-height: 95%; text-align: left; width: 95%;&quot;&gt;&lt;span style=&quot;font-size: x-small;&quot;&gt;&lt;code&gt;INSERT INTO psprcsque (prcsinstance, prcstype, prcsname, oprid, runcntlid)
VALUES (-42, &#39;nVision-ReportBook&#39;, &#39;RPTBOOK&#39;, &#39;NVISION&#39;,&#39;NVS_RPTBOOK_17&#39;);
INSERT INTO psprcsque (prcsinstance, prcstype, prcsname, oprid, runcntlid)
VALUES (-43, &#39;nVision-ReportBook&#39;, &#39;RPTBOOK&#39;, &#39;NVISION&#39;,&#39;NVS_RPTBOOK_STAT1&#39;);
select prcsinstance, prcstype, prcsname, oprid, runcntlid, prcsprty from psprcsque where prcsinstance IN(-42,-43);
rollback;&lt;/code&gt;&lt;/span&gt;&lt;/pre&gt;
&lt;p&gt;You can see that it was successful because priorities 2 and 7 were assigned.&lt;/p&gt;
&lt;pre style=&quot;background-color: #eeeeee; font-family: &amp;quot;courier new&amp;quot;overflow: auto; line-height: 95%; text-align: left; width: 95%;&quot;&gt;&lt;span style=&quot;font-size: x-small;&quot;&gt;&lt;code&gt;PRCSINSTANCE PRCSTYPE                       PRCSNAME     OPRID        RUNCNTLID                        PRCSPRTY
------------ ------------------------------ ------------ ------------ ------------------------------ ----------
         -43 nVision-ReportBook             RPTBOOK      NVISION      NVS_RPTBOOK_STAT1                       2
         -42 nVision-ReportBook             RPTBOOK      NVISION      NVS_RPTBOOK_17                          7&lt;/code&gt;&lt;/span&gt;&lt;/pre&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Monitoring Script&lt;/h3&gt;&lt;p&gt;This query in script &lt;a href=&quot;https://github.com/davidkurtz/psprcsprty/blob/main/process_prioritisation_by_cumulative_runtime_report.sql&quot; target=&quot;_blank&quot;&gt;process_prioritisation_by_cumulative_runtime_report.sql&lt;/a&gt;&amp;nbsp;reports on the average, median, and cumulative median execution time for each nVision process that ran to success during the overnight processing window as calculated by the package GFCPRCSPRIORITY&amp;nbsp;and stored in PS_XX_GFCPRCSPRTY&lt;i&gt;.&amp;nbsp;&amp;nbsp;&lt;/i&gt;It also compares that to the priority and last actual run time for that process.&lt;/p&gt;
&lt;h4 style=&quot;text-align: left;&quot;&gt;Example Output&lt;/h4&gt;
&lt;pre style=&quot;background-color: #eeeeee; font-family: &amp;quot;courier new&amp;quot;overflow: auto; line-height: 95%; text-align: left; width: 95%;&quot;&gt;&lt;span style=&quot;font-size: 66%;&quot;&gt;&lt;code&gt;                                                                                                      Cum.                                            
                                                                        Average   Median            Median    Total         Last Run   Actual                   
                                                                  Prcs Duration Duration Duration Duration Duration     Num  Process Duration Duration Duration Priorty
PRCSTYPE             PRCSNAME   OPRID        RUNCNTLID            Prty   (mins)   (mins)   (mins)   (mins)   (mins) Samples Priority   (mins)    Diff    % Diff    Diff
-------------------- ---------- ------------ -------------------- ---- -------- -------- -------- -------- -------- ------- -------- -------- -------- -------- -------
nVision-ReportBook   RPTBOOK    NVISION      NVS_RPTBOOK_4           9    90.65      131      209     1834     1997      23        6      189       58       44       3 
nVision-ReportBook   RPTBOOK    NVISION      NVS_RPTBOOK_16          9   159.17      163      209     1997     1997      23        9      177       14        9       0

nVision-ReportBook   RPTBOOK    NVISION      NVS_RPTBOOK_14          8    89.26      127      215     1703     1997      23        6      167       40       31       2
nVision-ReportBook   RPTBOOK    NVISION      NVS_RPTBOOK_24          8   115.87      117      165     1576     1997      23        9      144       27       23      -1

nVision-ReportBook   RPTBOOK    NVISION2     NVS_RPTBK_MORYYY1       7    93.13       85      165     1459     1997      23        8      158       73       86      -1
nVision-ReportBook   RPTBOOK    NVISION      NVS_RPTBOOK_28          7    88.30       80      172     1374     1997      23        8      108       28       35      -1

nVision-ReportBook   RPTBOOK    NVISION2     NVS_RPTBK_INCXXX        6    83.61       79      149     1294     1997      18        7      118       39       49      -1
nVision-ReportBook   RPTBOOK    NVISION      NVS_RPTBOOK_17          6    70.96       69      105     1143     1997      23        7       81       12       17      -1
nVision-ReportBook   RPTBOOK    NVISION      NVS_RPTBOOK_STAT4       6    68.00       72       81     1215     1997       8        7       81        9       13      -1

nVision-ReportBook   RPTBOOK    NVISION2     NVS_RPTBK_MMMMMM        5    52.45       46      119      914     1997      22        5       91       46      100       0
nVision-ReportBook   RPTBOOK    NVISION2     NVS_RPTBK_TEMPXX        5    50.48       49      104      963     1997      23        5       94       45       92       0
nVision-ReportBook   RPTBOOK    NVISION      NVS_RPTBOOK_3           5    55.52       55       99     1018     1997      23        6       79       24       44      -1
nVision-ReportBook   RPTBOOK    NVISION      NVS_RPTBOOK_1           5    47.70       56      137     1074     1997      23        4       56        0        0       1
…
&lt;/code&gt;&lt;/span&gt;&lt;/pre&gt;
&lt;h3 style=&quot;text-align: left;&quot;&gt;Monitoring Query&lt;/h3&gt;&lt;p&gt;The query in &lt;a href=&quot;https://github.com/davidkurtz/psprcsprty/blob/main/prcsmap.sql&quot; target=&quot;_blank&quot;&gt;prcsmap.sql&lt;/a&gt; is used to produce the data for a map of the processes, showing request time, time spent queuing, and time spent executing.&amp;nbsp; It is the basis of the second chart above. I normally run this in SQL Developer and export the data as an Excel workbook.&amp;nbsp; There is an &lt;a href=&quot;https://github.com/davidkurtz/psprcsprty/blob/main/prcsmap.xlsx&quot; target=&quot;_blank&quot;&gt;example spreadsheet in the Github repository&lt;/a&gt;.&lt;/p&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;9 levels of Prioritisation&lt;/h3&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEj4uC3vZPQvBsodQhxPIHMCj4q5GKboYLlEKdEslW7v6VPeLd3dAqel3qAoxwJYit4ZrWlfotaJo4tyEYhW88teYE5FlDryka1xdZGMDo1X6kmQ8zPsqrZESofGgy7bnwCFpCCrYEeribZBpxnyIJzVtzoXvkLwlOgxcLXVAo85LVsqCyLjT9Si&quot; style=&quot;clear: left; float: left; margin-bottom: 1em; margin-right: 1em;&quot;&gt;&lt;img alt=&quot;&quot; data-original-height=&quot;997&quot; data-original-width=&quot;1526&quot; height=&quot;209&quot; src=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEj4uC3vZPQvBsodQhxPIHMCj4q5GKboYLlEKdEslW7v6VPeLd3dAqel3qAoxwJYit4ZrWlfotaJo4tyEYhW88teYE5FlDryka1xdZGMDo1X6kmQ8zPsqrZESofGgy7bnwCFpCCrYEeribZBpxnyIJzVtzoXvkLwlOgxcLXVAo85LVsqCyLjT9Si&quot; width=&quot;320&quot; /&gt;&lt;/a&gt;&lt;/div&gt;With prioritisation, we can see that the long-running jobs with higher priority ran earlier.&amp;nbsp;&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;We can also see that some of the higher-priority jobs that are scheduled later are running earlier than those scheduled earlier, and are thus finishing earlier.&lt;p&gt;&lt;/p&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEhWAITPHsw8eakcqeFQPgYdbeLeoVskoSqkkroE42PNtOsVtqTciTpww6AqmsANNBWBmqJaXqP0TYLuU3PuEgfWj2YRrqQymVJfircSzb0TuSQv6o4zBQ8zSmp3TIsfucw7wsDCrwlZRfn_vkFGn8wZMIeI1uF7LFTEJ1iAum6kfQi1j_QQtyX2&quot; style=&quot;clear: right; float: right; margin-bottom: 1em; margin-left: 1em;&quot;&gt;&lt;img alt=&quot;&quot; data-original-height=&quot;997&quot; data-original-width=&quot;1524&quot; height=&quot;210&quot; src=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEhWAITPHsw8eakcqeFQPgYdbeLeoVskoSqkkroE42PNtOsVtqTciTpww6AqmsANNBWBmqJaXqP0TYLuU3PuEgfWj2YRrqQymVJfircSzb0TuSQv6o4zBQ8zSmp3TIsfucw7wsDCrwlZRfn_vkFGn8wZMIeI1uF7LFTEJ1iAum6kfQi1j_QQtyX2=w320-h210&quot; width=&quot;320&quot; /&gt;&lt;/a&gt;&lt;/div&gt;There is no longer any tail of processing.&amp;nbsp; Instead, load drops quickly at the end of the batch, and the batch as a whole finishes earlier.&lt;p&gt;&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;&lt;p&gt;&lt;/p&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;/div&gt;&lt;div class=&quot;blogger-post-footer&quot;&gt;&lt;a href=&quot;http://www.go-faster.co.uk/&quot;&gt;©David Kurtz&lt;/a&gt;&lt;/div&gt;</description><link>https://blog.psftdba.com/2023/11/prioritising-scheduled-processes-by.html</link><author>noreply@blogger.com (David Kurtz)</author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/a/AVvXsEiggKvNhw0KjfvuxFQfGwu7QCK3zlFFmvN6ISImdpay5JqUrcx367iGgdNJtsc8c2yg1Y7HkZoSIXOteaMSR2kxD5gGtlO5NILnhNpYljhFG5FCSx8EnaTCs5Yhio1YzzwZZS8w3NlTaEs6URis_s0UGU1UJKDwFzHPO_KYdcS2dRtLoeBzpGpI=s72-w320-h210-c" height="72" width="72"/><thr:total>0</thr:total></item></channel></rss>