Chapter 23 · Performance Engineering: Memory, I/O, SQL, Parallelism, and Contention

Benchmarking, Baselines, Capacity Planning, Load Profiles, and Performance Regression Testing

Build reproducible performance tests that record workload, cache state, concurrency, software/hardware limits and statistics; compare throughput, latency and saturation from measured before/after evidence without fabricating results.

Advanced130–150 minutesMeasured regression/capacity baseline labNo fabricated benchmark numbersService/client response telemetry awarenessLast reviewed: August 2026

Learning outcomes

A schema change “improves” one developer's query from 800 ms to 500 ms, so it is promoted. Under production concurrency the release is slower because CPU saturates earlier, hard parses rise and tail latency doubles. A performance claim needs a reproducible benchmark: recorded software/data/hardware limits, workload mix/concurrency/think time, cache/warmup state, statistics, throughput, latency distribution and saturation evidence.

01

Record Oracle RU/COMPATIBLE/parameter/statistics/data-volume/environment fingerprints with every benchmark.

02

Separate latency, throughput and resource saturation and explain why one cannot substitute for the others.

03

Build a Free-compatible before/after micro-workload that records actual elapsed time and database counter deltas without inventing results.

04

Use service/module/action and new 23.26.3 client-request telemetry where available to connect database and end-user response.

05

Define regression acceptance/rollback gates for SQL/schema/config/release changes and distinguish microbenchmarks from capacity tests.

Generation-time baseline, licensing, and measurement boundary

Mandatory examples target Oracle AI Database Free 26ai, reviewed against RU 23.26.3, SQL Developer 26.2, and SQLcl 26.2.1. Free is limited to 2 foreground CPU cores, 2 GB maximum database RAM across SGA/PGA, 12 GB user data, and one installation per logical environment; it receives no Release Update patches or Oracle Support service requests. The course CDB/PDB baseline is FREE/FREEPDB1. The chapter uses core dynamic performance views and runtime plans so mandatory labs do not require AWR/ASH or a management pack. Parallel query/DML is not available in Free, so parallel-pressure examples are entitlement-gated and the Free path remains serial. Exadata Smart Scan is an Exadata Storage Server capability and is never inferred from an ordinary full scan on local/container storage. No lab changes COMPATIBLE or recommends hidden/underscore parameters. Memory, I/O and concurrency changes are measured against before/after workload evidence and include rollback.

1. A benchmark is a controlled experiment

At minimum, record data volume/distribution, concurrency, workload mix, think time, cache/warmup state, bind values, optimizer statistics timestamp, Oracle version/RU, COMPATIBLE, memory/CPU limits, storage, PDB/service, client/driver version and application build. Without those, “before” and “after” may be different experiments.

2. Environment fingerprint

sql · database/software/config fingerprint
SELECT banner_fullFROM v$versionWHERE banner_full LIKE 'Oracle%';SELECT name,open_mode,database_role,platform_nameFROM v$database;SELECT instance_name,host_name,startup_time,statusFROM v$instance;SELECT name,valueFROM v$parameterWHERE name IN (  'compatible',  'statistics_level',  'optimizer_features_enable',  'memory_target',  'sga_target',  'pga_aggregate_target',  'cpu_count',  'parallel_degree_policy')ORDER BY name;
sql · data/statistics fingerprint
SELECT  table_name,  num_rows,  blocks,  last_analyzedFROM user_tablesWHERE table_name IN (  'WORK_ORDERS',  'TECHNICIANS',  'SERVICE_REGIONS')ORDER BY table_name;

Do not change OPTIMIZER_FEATURES_ENABLE merely to make a benchmark stable. Record it; use supported plan-management mechanisms if release stability is needed.

3. Create a disposable benchmark table and run log

sql · setup
BEGIN  EXECUTE IMMEDIATE 'DROP TABLE sh23_bench_work PURGE';EXCEPTION WHEN OTHERS THEN  IF SQLCODE != -942 THEN RAISE; END IF;END;/BEGIN  EXECUTE IMMEDIATE 'DROP TABLE sh23_bench_runs PURGE';EXCEPTION WHEN OTHERS THEN  IF SQLCODE != -942 THEN RAISE; END IF;END;/CREATE TABLE sh23_bench_work ASSELECT  LEVEL AS event_id,  MOD(LEVEL,10000) AS technician_id,  MOD(LEVEL,6) AS status_bucket,  MOD(LEVEL*37,100000)/10 AS amount,  RPAD('benchmark',100,'x') AS payloadFROM dualCONNECT BY LEVEL <= 150000;CREATE TABLE sh23_bench_runs (  run_id NUMBER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,  variant_name VARCHAR2(30) NOT NULL,  started_at TIMESTAMP WITH TIME ZONE NOT NULL,  ended_at TIMESTAMP WITH TIME ZONE NOT NULL,  iterations NUMBER NOT NULL,  elapsed_seconds NUMBER NOT NULL,  logical_reads_delta NUMBER,  physical_reads_delta NUMBER,  cpu_centisecs_delta NUMBER,  result_checksum NUMBER,  notes VARCHAR2(500));BEGIN  DBMS_STATS.GATHER_TABLE_STATS(USER,'SH23_BENCH_WORK');END;/

4. Benchmark harness records actual measurements

sql · one serial microbenchmark run
DECLARE  l_start_cs       NUMBER;  l_end_cs         NUMBER;  l_start_logical  NUMBER;  l_end_logical    NUMBER;  l_start_physical NUMBER;  l_end_physical   NUMBER;  l_start_cpu      NUMBER;  l_end_cpu        NUMBER;  l_sum            NUMBER := 0;  l_v              NUMBER;  l_iterations     CONSTANT PLS_INTEGER := 300;BEGIN  DBMS_APPLICATION_INFO.SET_MODULE('SH23_BENCH','BASELINE');  SELECT MAX(CASE WHEN n.name='session logical reads' THEN s.value END),         MAX(CASE WHEN n.name='physical reads' THEN s.value END),         MAX(CASE WHEN n.name='CPU used by this session' THEN s.value END)  INTO l_start_logical,l_start_physical,l_start_cpu  FROM v$mystat s JOIN v$statname n    ON n.statistic#=s.statistic#  WHERE n.name IN (    'session logical reads','physical reads','CPU used by this session'  );  l_start_cs := DBMS_UTILITY.GET_TIME;  FOR i IN 1..l_iterations LOOP    SELECT SUM(amount)    INTO l_v    FROM sh23_bench_work    WHERE technician_id=MOD(i*37,10000);    l_sum := l_sum + NVL(l_v,0);  END LOOP;  l_end_cs := DBMS_UTILITY.GET_TIME;  SELECT MAX(CASE WHEN n.name='session logical reads' THEN s.value END),         MAX(CASE WHEN n.name='physical reads' THEN s.value END),         MAX(CASE WHEN n.name='CPU used by this session' THEN s.value END)  INTO l_end_logical,l_end_physical,l_end_cpu  FROM v$mystat s JOIN v$statname n    ON n.statistic#=s.statistic#  WHERE n.name IN (    'session logical reads','physical reads','CPU used by this session'  );  INSERT INTO sh23_bench_runs(    variant_name,started_at,ended_at,iterations,elapsed_seconds,    logical_reads_delta,physical_reads_delta,cpu_centisecs_delta,    result_checksum,notes  ) VALUES(    'BASELINE',    SYSTIMESTAMP-NUMTODSINTERVAL((l_end_cs-l_start_cs)/100,'SECOND'),    SYSTIMESTAMP,    l_iterations,    (l_end_cs-l_start_cs)/100,    l_end_logical-l_start_logical,    l_end_physical-l_start_physical,    l_end_cpu-l_start_cpu,    l_sum,    'Serial single-session microbenchmark; record host/cache state separately'  );  COMMIT;END;/

The harness records the values Oracle actually produced. It deliberately does not print a made-up expected latency. DBMS_UTILITY.GET_TIME is suitable for coarse elapsed comparison in this lab; production load tests should also capture per-request latency distribution from the application/load generator.

5. Create one controlled change

sql · candidate index
CREATE INDEX sh23_bench_tech_ixON sh23_bench_work(technician_id);BEGIN  DBMS_STATS.GATHER_TABLE_STATS(    USER,'SH23_BENCH_WORK',cascade=>TRUE  );END;/

Rerun the same PL/SQL harness with variant_name='INDEXED' and module action INDEXED. Keep iterations/data/binds/session mode fixed. If the checksum differs, the experiment is invalid regardless of speed.

6. Compare measured outcomes

sql · actual before/after results
SELECT  run_id,  variant_name,  iterations,  elapsed_seconds,  ROUND(iterations/NULLIF(elapsed_seconds,0),2) AS operations_per_second,  logical_reads_delta,  physical_reads_delta,  cpu_centisecs_delta,  result_checksum,  started_at,  ended_atFROM sh23_bench_runsORDER BY run_id;

A lower single-session elapsed time is evidence only for this microbenchmark. Before promotion, test realistic concurrent sessions and application think time. If CPU is already saturated, a change that uses more CPU per request can improve one-user latency yet reduce total throughput.

7. Latency, throughput and saturation are separate axes

Dimension Examples Failure mode if ignored
Latency median/p95/p99 request elapsed Average hides tail stalls
Throughput requests/sec, commits/sec, rows/sec Fast individual calls but too little total work
CPU saturation DB CPU/wall sec + OS CPU/run queue Scaling stops when CPU exhausted
I/O saturation read/write service time, IOPS/MBPS, queue Latency rises nonlinearly near device limits
Memory/TEMP PGA overalloc, workarea spill, paging Concurrency multiplies memory pressure
Contention lock/mutex/latch waits More workers reduce throughput

8. Build a bounded load profile from system deltas

A load profile describes how much useful/database work occurred during the benchmark window. Capture the same counters immediately before and after the steady interval, then divide deltas by elapsed seconds or transactions as appropriate. Never compare per-second rates from windows with different workload phases without noting the difference.

sql · pack-independent load-profile counters
SELECT name,valueFROM v$sysstatWHERE name IN (  'user calls',  'user commits',  'execute count',  'session logical reads',  'physical reads',  'redo size')ORDER BY name;

Store T1/T2 values with the benchmark run. A change that improves latency by reducing work should usually be visible in one or more per-call/per-operation resource deltas; if not, investigate whether cache/background/concurrency effects explain the result.

9. Service-level database telemetry

sql · pack-independent service metrics
SELECT  service_name,  begin_time,  end_time,  callspersec,  dbtimepercall,  cpupercallFROM v$servicemetricORDER BY end_time DESC,service_name;

Service metrics connect database time/CPU to the workload endpoint. They still do not replace client-side latency because network/middle-tier queues and think time lie outside the database.

10. RU 23.26.3 client request histogram awareness

RU 23.26.3 adds V$CLIENT_REQUEST_HISTOGRAM for service-level client request response-time distribution when compatible current Oracle drivers provide the telemetry. Use it to bridge database and client-request latency, but verify driver support and do not assume older clients populate it.

sql · 23.26.3+ with supporting client/driver
SELECT  s.name AS service_name,  h.bucket01_count,  h.bucket02_count,  h.bucket03_count,  h.bucket04_count,  h.bucket05_count,  h.bucket06_count,  h.bucket07_count,  h.bucket08_count,  h.bucket09_count,  h.bucket10_count,  h.bucket11_count,  h.bucket12_count,  h.bucket13_count,  h.bucket14_count,  h.con_idFROM v$client_request_histogram hJOIN v$services s  ON s.service_id=h.service_id AND s.name_hash=h.service_name_hashORDER BY s.name;

11. Cache state and warm-up must be explicit

Do not flush the shared pool/buffer cache on a shared production database merely to make a “cold cache” benchmark. Flushing can damage every workload and creates a synthetic state. For controlled test systems, define whether runs are cold/warm and how the state is established. Production regression tests should usually model the real warm steady state plus startup/recovery scenarios separately.

Deliberately wrong

Running one query once before and once after a change—and accepting the faster second run—often just compares cold versus warm cache. Repeat, randomize order where useful, capture enough samples, and control the environment.

12. Capacity testing needs concurrency and steady state

A capacity test ramps users/workers, reaches a steady interval, then observes where throughput stops scaling and latency rises. Record connection pool size, worker concurrency, think time, request mix and hardware/container limits. Free's 2 CPU/2 GB ceiling is useful for learning saturation shape, not for extrapolating enterprise capacity linearly.

13. Regression gate and rollback

  • Correctness: result/checksum/business invariants unchanged.
  • Latency: agreed percentile thresholds under representative load.
  • Throughput: meets target at target concurrency.
  • Resources: CPU/I/O/PGA/TEMP/locks stay within approved headroom.
  • Plan/cardinality: expected runtime plan and actual rows stable/understood.
  • Operations: backup/maintenance/recovery windows not materially harmed.
  • Rollback: index/schema/config/application rollback rehearsed before release.

14. Cleanup

sql · cleanup
DROP INDEX sh23_bench_tech_ix;DROP TABLE sh23_bench_runs PURGE;DROP TABLE sh23_bench_work PURGE;BEGIN  DBMS_APPLICATION_INFO.SET_MODULE(NULL,NULL);END;/

15. Production judgment and chapter close

Performance engineering is a loop: define SLO/workload → capture a baseline → identify the dominant mechanism → make one controlled change → repeat the same workload → compare latency/throughput/saturation/correctness → keep or roll back. Memory, I/O, contention and parallelism interact, so isolated folklore can simply move the bottleneck.

Current baseline remains 26ai RU 23.26.3, SQL Developer 26.2 and SQLcl 26.2.1. Free's hard 2-CPU/2-GB/12-GB resource envelope must be recorded with every result. Parallel query/DML is unavailable in Free; no chapter benchmark relies on it. V$CLIENT_REQUEST_HISTOGRAM is a 23.26.3-era capability requiring compatible current client telemetry. No COMPATIBLE change, hidden parameter or management pack is required for the mandatory benchmark.

Check your understanding

  1. Why is one before/after elapsed time not a valid performance regression test?
  2. What three performance dimensions must be separated?
  3. Why record a result checksum?
  4. Why should you not flush production caches just to create a benchmark?
  5. What does a capacity test add beyond a single-session microbenchmark?
Review the answers

It can differ because of cache state, background load, bind/data differences or randomness; it gives no distribution/concurrency evidence.

Latency, throughput and resource saturation.

It proves the compared variants still compute the same business result; faster wrong results are not improvements.

Flushes disturb other workloads and create an artificial state; define cache state on controlled test systems instead.

It increases concurrency/workload toward steady-state saturation so you can see where throughput stops scaling and tail latency/resource queues rise.

Authoritative references

Keep knowledge open

Help the academy stay free and grow.

If these tutorials save you time, a small donation supports new lessons, technical review, diagrams, examples, and long-term maintenance.

ETHEthereum / ERC-20 only
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0

Send only Ethereum or ERC-20 compatible assets to this address.