EBS Performance Review Copy link
- Category
- Performance Tuning
- Plugin
- Blitz Report Development
ebs-performance-review - Works with
- Claude Code
Server-wide Oracle EBS performance review for a customer from Blitz Report DBA extracts. Covers memory and parameter configuration, the top AWR SQLs, and literal-SQL hard parsing, and delivers corrected package code plus a one-table HTML summary.
When to use it
- Use when a customer sends DBA AWR/SGA SQL Performance Summary, SGA+PGA Memory, Parameter Validation and Text Search extracts.
Review these DBA AWR and SGA extracts and correct the custom code behind the top SQL.
Which Blitz Reports do I run first for a server-wide performance review?
Check the memory and database parameters of this instance against the EBS requirements.
Claude Code
- Add the Enginatics marketplace in Claude Code.
/plugin marketplace add enginatics/ai-skills - Install the plugin.
/plugin install blitz-report-ai-skills@enginatics - Run the environment setup once; it describes your EBS development instance (never production).
/blitz-report-ai-skills:blitz-environment-setup - Run the skill, or just describe the task and Claude picks it up.
/blitz-report-ai-skills:ebs-performance-review - Keep it current: enable auto-update for the enginatics marketplace, or update manually.
/plugin marketplace update enginatics
Requirements: Blitz Report installed on your Oracle EBS development instance, and database access from Claude Code to that instance (Oracle SQLcl MCP server, SQLcl or SQL*Plus). Never point the skills at production.
Skill instructions what the AI follows, 126 lines
The deliverable is corrections, not a description of the load. Everything in the output must answer "what do we change, and is the change supplied or only described".
Inputs to ask the customer for
All are Blitz Reports. Ask for them in one go, as XLSX, into one folder.
| Area | Report | Parameters |
|---|---|---|
| Memory | DBA SGA+PGA Memory Configuration | none. Also ask for free -h and grep -i huge /proc/meminfo from each node: the report's Hugepages column is its own recommendation (SGA GB x 512 + 5), not the current setting |
| Parameters | DBA Database Parameter Validation, DBA Parameters | none |
| Maintenance tasks | DBA Automated Maintenance Tasks | none |
| AWR settings | DBA AWR Settings | none |
| System load | DBA AWR Wait Event Summary, DBA AWR System Time Summary, DBA AWR System Metrics Summary | the full AWR window |
| Top SQL | DBA AWR SQL Performance Summary | the full AWR window (default retention is 8 days), top 200 by CPU |
| Literals | DBA SGA SQL Performance Summary | Literals Dupl Count >= 50 |
| Source | DBA Text Search | one run per package list; the report exports dba_source line by line for the packages named in the AWR Code column and the SGA module column |
| Waits, cache, result cache | DBA SGA Wait Event Summary (ASH), DBA SGA Buffer Cache Object Usage, DBA Result Cache Statistics, DBA Result Cache Objects and Dependencies, DBA Alert Log | none |
| Follow-up as needed | DBA AWR SQL Execution Plan History (per sql_id, plan hash blank), DBA AWR Active Session History, FND Concurrent Requests (per program), DBA Segments, DBA Table Modifications |
On a multitenant database the AWR reports read the PDB only when container_data is CURRENT or CURRENT_DICTIONARY, and a PDB records no AWR data unless awr_pdb_autoflush_enabled=true is set in it. Tell the customer before the AWR runs: an empty or near-empty AWR extract means the parameter is off, and the data starts only from the next snapshot after it is set, so ask for it a few days ahead. With it, set the snapshot interval to 60 minutes (background in References, PDB AWR).
alter session set container=<PDB>;
alter system set awr_pdb_autoflush_enabled=true;
exec dbms_workload_repository.modify_snapshot_settings(interval=>60);Lists of values for a report parameter (package names, sql_ids, program short names) are given as one semicolon-separated line with no spaces, ready to paste: XXA_PKG;XXB_PKG. Never split the values of one run into several lists (by program type, priority or module); explain any grouping in a sentence after the single line.
Do not ask the customer for anything you can answer from your own instances. Standard EBS index definitions, table types (global temporary or not), and column datatypes come from a 12.2 instance (see your environment file, blitz-environment.md), or from a local Vision instance. Standard dba_source for unwrapped Oracle packages likewise. The database parameters (compatible, optimizer_features_enable, every set parameter) are in the customer's Blitz Report installation logs (database_parameters.log): read them there instead of asking.
When the customer gives frontend access to a recent test clone, read source, view DDL, indexes, table statistics, data counts and the Blitz Report run history there yourself rather than asking for them: check its data dates first, since a clone refreshed recently answers the same questions as production. A clone restored from a production backup or a storage snapshot also keeps production's DBID and AWR repository, so it serves the AWR plans and history too: check dba_hist_snapshot per DBID and instance, and read only snapshots before the clone's v$database.resetlogs_time, since the clone keeps writing its own snapshots under the same DBID. Its v$database.created is production's creation date, not the clone date.
Method
1. Configuration
- SGA: compare the SGA size row (the allocated SGA),
sga_max_sizeandpga_aggregate_targetto physical memory. In a pluggable databasesga_target0 means no limit of the PDB's own, not that automatic shared memory management is off; the container's value is the SGA size row. Most customers leave a third or more unallocated; the report's memory-unused figure isphysical - SGA size - pga_aggregate_target, not free RAM, so confirm withfree -h. Recommend using the memory the customer has already paid for, sized by calculation: physical memory minus about 5 GB for the OS, minus the memory of everything else on the host (other databases such as ASCP, ASM, clusterware), minus the expected maximum PGA (the PGA high-water mark over a few months plus 50%). Example: 128 GB, no RAC, ASM, ASCP or other database, PGA high-water mark 10 GB: 128 - 5 - 15 = 108 GB, so an SGA of up to about 100 GB where I/O waits are high. Most customers are constrained by memory, so go close to that limit; only where memory is plentiful and the other consumers are not yet known, stay well below it (1 TB of 1.5 TB). The calculation is always confirmed with thefree -handgrep -i huge /proc/meminfooutput of each node, which also shows whether hugepages need correcting. Never size byv$sga_target_adviceor the SGA advice columns: a small estimated gain costs nothing on paid hardware and may be an infrequent but important online query. Recommend thesga_targetincrease up tosga_max_sizefirst (dynamic, no restart), then a hugepage-backed increase with a rolling restart. Withuse_large_pages=ONLYthe instance will not start if hugepages are short, so hugepages are sized before the SGA is raised (step by step in References, hugepages). - PGA: never recommend a
pga_aggregate_limit. EBS requires it at 0 (unlimited) so that no EBS process is terminated; raisepga_aggregate_targetonly. - System statistics: the memory report shows
sys.aux_stats$(record date, CPUSPEEDNW, IOSEEKTIM, IOTFRSPEED, MBRC). CPUSPEEDNW at the default 4096 means never gathered, or storage too fast for the NOWORKLOAD method; a record date before the last hardware or storage move means stale. Recommendexec dbms_stats.gather_system_stats('noworkload'),'EXADATA'on Exadata, or setting IOSEEKTIM and IOTFRSPEED withdbms_stats.set_system_statswhen gathering leaves the defaults. - System load: from the AWR wait event, time and metrics summaries over the full window. High
log file sync/log file parallel writetimes mean redo on slow storage; highdb file sequential read/scattered readtime supports the SGA increase; very high logical reads against modest physical reads point back to the top SQL, low physical read throughput with high read waits to slow disks. - Parameters: check every set parameter against MOS KA1002 (formerly 396009.1, "Database Initialization Parameters for Oracle E-Business Suite Release 12"). Typical findings:
result_cache_max_size0 or too small,_optim_peek_user_binds=FALSE(never an EBS requirement; disables bind peeking database-wide and is behind most plan instability), parameters on the removal list still set, hidden parameters. Every parameter DBA Database Parameter Validation flags gets its statement in the configuration script and a row in a table in the review (parameter, current, required, action): set the mandatory ones,alter system resetevery one on the removal list and every hidden parameter of section 3 (required value "Do not set hidden parameters"), with no "keep it if an SR asked for it" hedging, as the note and the analyzer say.cursor_sharing=EXACTis mandatory for EBS, soFORCEis never the fix for literals. - Automated maintenance tasks: from DBA Automated Maintenance Tasks, every client enabled in a maintenance window. EBS requires
_optimizer_autostats_job=falseand theauto optimizer stats collectionclient disabled, with statistics gathered only by the Gather Schema Statistics concurrent request (FND), never by a database scheduler job; name the concurrent request explicitly in the review so it cannot be read as the database job (KA1002); auto space advisor and sql tuning advisor add IO and CPU with no usable result on EBS. Recommenddbms_auto_task_admin.disableper client, and_addm_auto_enable=falsewhere no DBA reads ADDM findings. Check the AWR top SQL forORA$AT_*jobs andDBMS_STATS/DBMS_SQLTUNEmodules to show what they cost. - Result cache: from the statistics report, invalidations per dependency object. A high count on
FND_PROFILE_OPTION_VALUESorFND_LOOKUP_VALUESmeans something writes setup at runtime. Ask forFND Profile Option Valuesfiltered on last update date in the last two days: Excel4apps stores each user's option blob inXXE4A_OPTIONS00_*profile values and rewrites it every session, and each write invalidates every cached profile result. The recommendation is then to replace it with Blitz Report. - Editions:
select count(*) from dba_editions. Online patching leaves one edition per cycle; above five,SYS.OBJ$grows by one row per editioned object per edition (33 editions gave 9.8 million rows against 432,000 on a one-edition instance) and every dictionary query the optimizer full-scans slows in proportion. Recommendadop phase=prepare; actualize_all; finalize finalize_mode=full; cutover; cleanup cleanup_mode=full, thencleanup_mode=fullafter every cycle. - Which patch delivered a file version: on a 12.2 instance join
ad_files,ad_file_versions,ad_patch_run_bug_actions,ad_patch_run_bugs,ad_bugs,ad_patch_runs,ad_patch_drivers,ad_applied_patcheson the file name. The odf that defines a standard index is under$PROD_TOP/patch/115/odf/. A customer on an older release update pack can create the index with Oracle's own name and definition; the pack later finds it in place.
2. Top SQL
Work from the AWR extract's Percentage column (share of total DB CPU). Rules for reading it:
- One sql_id has one row per plan hash. Sum
Percentageper sql_id; compute buffer gets per execution execution-weighted across plans, never as the worst plan's figure. - AWR keeps only the top
topnsqlstatements per category per snapshot (default 30 each by elapsed time, CPU, parse calls, sharable memory and version count; DBA AWR Settings shows it with the interval and retention). Frequent cheap statements can be missing; look for them in the SGA extract. - Rows with null
Executionsand plan hash 0 are real cost with unavailable per-execution metrics; say so, do not print zero. Blocks Execis buffer gets per execution;Buffer IOis MB (db_block_size * buffer_gets / 1e6).- The
CodeandCode Line#columns name the PL/SQL unit and line; use them to find the statement in the Text Search extract. TheModulecolumn can be misattributed (workflow and forms personalisations set it and do not restore it). - Verify every sql_id you cite exists in the extract. Transcription errors happen; one script over the final deliverables catches them.
For each statement in the top 100, classify it before doing anything else:
- Custom PL/SQL with source supplied: correct the code. Read the whole procedure, not just the statement: the fix is often call volume (a lookup inside a per-row loop, a poll interval, a cursor that selects everything and rebuilds a table), not the statement's shape.
- Custom PL/SQL without source: list the package for the next Text Search run. For every custom concurrent program whose source we lack, hand the customer ONE semicolon-separated list of concurrent program short names for the Blitz Report Custom Reports Converter in the same message as the other runs, rather than marking rows Needs input one by one; take the short names from the AWR
Module(e:<app>:cp:<appl>/<SHORT_NAME>). Reports Builder runs showrwrun@<host>without the program: identify them with FND Concurrent Requests (Execution Method Oracle Reports, Started within Days = the AWR window, Running longer than x Minutes) and add their short names to the list. When the AWRModuleis a concurrent program short name (e:PO:cp:xxpo/XXPO_...) rather than a package, the source comes through the Blitz Report Custom Reports Converter instead, which also gathers the report definitions, parameters and dependent objects. A program that is a report (Reports Builder, BI Publisher) is classified first. A data report (account analysis, revaluation, aging, any listing) always moves to a tuned Blitz Report: a library report where one covers it, else a conversion with themigrate-custom-reportsskill, never a corrected rdf or data template. Only a printed document (pick and pack slip, commercial invoice, purchase order print) is corrected in place; a PL/SQL interface is treated as a package (for anEXECUTION_METHOD_CODE='I'program the converter gathers the package named in the executable'sEXECUTION_FILE_NAME, whatever its prefix, plus its custom dependencies). - Oracle code: never edit. A plan baseline, an index check, a purge, or an SR.
- External client (
.exemodules): the SQL text is in AWR; supply the corrected predicate for the integration owner. - Oracle Alerts and Forms personalisations: setup changes, no package.
Patterns that recur:
col = NVL(:bind, col)plus(… AND :bind IS NULL) OR :bind IS NOT NULL: two queries in one cursor. Rewrite as(:b IS NOT NULL AND col = :b) OR (:b IS NULL AND col IS NOT NULL AND <range>); verify equivalence by truth table under three-valued logic, keep theIS NOT NULLthat reproduces the null rejection.DECODE(...)on the inner side of an anti-join or a join: no index use. Split into one branch per case with a plain equality.UPPER(col) = UPPER(:b)on a dictionary or indexed column: drop the function on the column side.- A self-join of
MTL_SUPPLYonSUPPLY_SOURCE_IDalone: not unique withoutSUPPLY_TYPE_CODE. - Setup or dictionary lookups inside a per-row loop: cache per request in a package associative array, cleared when
fnd_global.conc_request_idchanges. Same statement, same plan, fewer executions. fnd_concurrent.wait_for_requestwithinterval => 1.DELETE ... WHERE NVL(group_id,0) < 0onBOM_EXPLOSION_TEMP: disables its indexes. It is a session-duration GTT: never recommend TRUNCATE.- Twenty-plus plans for one sql_id: pin the cheapest observed plan with
dbms_spm.load_plans_from_cursor_cacheas a holding action; drop the baseline when the text is corrected. - Copy-pasted statements: one sql_id with many source locations. Replace all copies; recommend one function as a follow-up.
3. Literals
From the SGA extract, Literals Dupl Count is count(distinct sql_id) per force_matching_signature: parent cursors, not child versions. Group families by owner (custom package, Oracle package, external client, form). For custom PL/SQL the fix is a bind in the USING clause; identifiers (table and column names) stay concatenated. Check the alert log for too many parse errors on the same families: a null id concatenated into text produces a statement that fails to parse on every call.
Deliverables
Everything goes into one deliverables folder per review. Working files stay in a separate working folder.
<Customer>_Performance_Review_<date>.html. Structure, in this order and nothing else before it:- Section 1, one table: sql_id(s) colour-coded by status, % DB CPU, where, the defect (one to three sentences), the change (one to three sentences, file name if supplied), status. Statuses: Corrected (file supplied), Recommended (described, no file: DBA, setup, external), Needs input (say which run), Not analysed (source not supplied). Rows ordered by cost.
- Section 2, how to deploy: files, order, what to test for each.
- Section 3, what we still need: the runs, why, ranked. Never ask for what your own instances can answer.
- Then one section per row: heading with sql_ids and %, one line on where it lives,
Defect,Solution. Code excerpts single-spaced, in the customer's own formatting. - No load statistics sections, no module or theme rankings, no "what not to do" boxes, nothing about settings or code that stay as they are (a parameter left unlimited, a job that stays off), no "how to measure afterwards", no MOS citations in the body, no second numbering scheme (no "Finding N" alongside section numbers).
package_code/: for every corrected package,<PKG>.pkb(verbatim from the Text Search extract) and<PKG>_corrected.pkb, never a.diff(the reader compares with their own tool). CRLF, no BOM. Generate withscripts/gen_pairs.py; edits are line-range replacements so the original is provably untouched elsewhere. Match the customer's code style, not your own. Comments state why the code is shaped as it is, never what changed.notes_and_scripts/: only what the DBA applies: the parameter changes, the index DDL, corrected statements for external clients. No check scripts, verification or test queries, and nothing that asks for what is already known (current parameters come from the installation log and DBA SGA+PGA Memory Configuration); what is genuinely missing is asked for as a Blitz Report run in section 3.<Customer>_SQL_Findings_<date>.xlsx: every statement with full metrics, for the customer to filter.blitz_reports/: the import XML of every custom report rebuilt as a Blitz Report, with a note (notes_and_scripts/NN) listing per report what changed, the parameter mapping and the row comparison against the original SQL on your test instance.
The zip handed to the customer holds only what they deploy or run: the review html, the corrected files each next to its original, and the scripts. Its root is the folder level of the html and the subfolders, with no wrapping folder; no diffs, no README, no working files.
The covering email opens with the CPU the customer saves, summed by script from the section 1 rows: "In short, the corrections and recommendations in it address the statements behind X% of the database's total CPU time (Y% with corrected code supplied, Z% with the fix described); applying them should remove most of that load." Y is the Corrected rows, Z the Recommended rows, X their sum; the points that follow quote the same figures.
Before publishing, run the checks in scripts/verify_deliverables.py: every cited sql_id exists in the extracts, no control characters, no BOM, CRLF on .pkb, no .diff file, every section reference resolves, no banned words.
Verification without a database
The customer's instance is not reachable. Compensate:
- Bracket balance and BEGIN/END counts on every corrected body.
- Truth tables for predicate rewrites, under Oracle three-valued logic, over every combination of bind and column nullness.
- Standard index and table definitions from your own 12.2 instance.
- Every numeric claim recomputed from the extract by script, not typed.
- Adversarial review of any rewrite: a second pass whose only job is to refute it.
- Match the SQL syntax to the customer's
compatiblesetting from the installation log: EBS 12.2 sites on 19c often runcompatible=12.1.0, which rejectsvalidate_conversion,default ... on conversion error,listagg(distinct ...)andlistagg ... on overflowwithORA-00907. Corrected code, check scripts and test queries must use 12.1 syntax there (regexp_liketests,case when regexp_like(x,'^[0-9]+$') then to_number(x) end).
Scripts
scripts/extract_xlsx.py turns the Blitz XLSX extracts into JSON. scripts/gen_pairs.py is the original/corrected generator with the line-range edit model. scripts/master_table.py builds the section-1 table from a row list and the AWR JSON. scripts/verify_deliverables.py is the pre-publish check. Copy them into the working folder and adapt the paths and row lists.
References
Blog posts to send the customer with the recommendation they support.
- Hugepages: https://www.enginatics.com/blog/how-to-configure-hugepages-for-oracle-ebs/ — sizing,
sysctland memlock settings,use_large_pages=ONLY, CDB and PDB SGA parameters, and large pages on non-Linux platforms. - PDB AWR: https://www.enginatics.com/blog/updated-dba-awr-blitz-reports-now-work-with-plugglable-databases/ —
awr_pdb_autoflush_enabled, snapshot interval,container_dataand the reports' container parameter.