PostgreSQL Smart Optimizer: how analyzing your real workload automatically configures your database
18 min read
How PWR's Tuning Advisor works: 30 days of real metrics analyzed to automatically recommend the 7 most decisive PostgreSQL parameters (shared_buffers, work_mem, effective_cache_size, max_connections, maintenance_work_mem, random_page_cost, wal_buffers), with formula, SQL code, severity and confidence score for each.
Introduction: PostgreSQL configuration as a science, not an art
PostgreSQL configuration is an art too often neglected: most administrators leave the default parameters, without knowing that this alone can slow performance down by 50 to 80%.
PWR's Tuning Advisor (Smart Optimizer) changes that by analyzing your real 30-day workload data and proposing optimal configurations, with no manual intervention and no dedicated DBA.
The Optimizer's architecture: how does it work?
The recommendation engine follows a 5-step pipeline, entirely based on your database's real history (perfhist.*_hist tables fed by the snapshots) rather than on generic assumptions.
Raw data (30 days)
|
v
Metric collection (perfhist.*_hist)
|
v
Smart analysis (trend, thresholds, workload type)
|
v
7 PostgreSQL parameters analyzed
|
v
Recommendations with confidence score
|
v
Proposed actions (to validate before applying)1) shared_buffers — the main cache
shared_buffers is PostgreSQL's shared cache: it stores the most frequently used data blocks in RAM — a bit like your server's CPU cache, but for PostgreSQL.
Reference Pgtune formula: shared_buffers = RAM × 25%. Example: for 16 GB of RAM, the recommended value is 4 GB (16 × 0.25).
- 99% cache hit ratio = excellent, almost everything is served from cache
- 90% = good, few disk accesses
- 70% = problematic, many disk accesses
- Below 50% = critical, configuration too small
- Typical impact when going from 256 MB to 4 GB: cache hit ratio 80% → 95%, queries up to +150% faster, disk I/O -70%, CPU -40%
SELECT shared_buffers INTO v_current_setting
FROM perfhist.settings_hist
ORDER BY date_extract DESC LIMIT 1;Cache Hit Ratio = blks_hit / (blks_hit + blks_read) x 100Trend UP (95% -> 98%) -> optimal config, no change
Trend DOWN (98% -> 70%) -> URGENT: increase by 35%
Trend STABLE (90%) -> recommend +15%
INSUFFICIENT (fewer than 200 snapshots) -> wait for 30 days of collection-- If cache hit > 93% and stable
severity = 'low'
confidence = 96%
reason = 'Optimal config, no change'
-- If cache hit is declining
v_ideal_mb = v_ideal_mb * 1.35 -- +35%
severity = 'critical'
reason = 'Declining performance, urgent increase'2) work_mem — sort operation memory
work_mem controls the memory allocated to each sort (ORDER BY) or join operation. If it's insufficient, PostgreSQL falls back to disk — 100 to 1000 times slower than in memory.
Pgtune formula: work_mem = (RAM × 3%) / number of CPU cores. Example for 16 GB of RAM and 8 cores: (16 × 1024 × 0.03) / 8 ≈ 61.44 MB.
- 0 temporary files/day = excellent
- 1 to 100 files/day = acceptable
- 100 to 5000 files/day = problematic
- More than 5000 files/day = critical
- Typical impact when going from 4 MB to 61 MB: temp_files 1000/day → 0/day, queries +30% faster, disk I/O -90%, better concurrency (less contention)
SELECT AVG(temp_files) INTO v_avg_metric
FROM perfhist.pg_stat_database_hist
WHERE datname = v_db_name
AND date_extract >= NOW() - INTERVAL '30 days';v_increase_pct = (v_ideal_mb - v_current_mb) / v_current_mb * 100
-- Example: current 4 MB, ideal 61.44 MB
-- increase = ((61.44 - 4) / 4) * 100 = +1436%-- CASE 1: many temp_files (more than 5000/day)
IF v_avg_metric > 5000 THEN
v_ideal_mb = v_ideal_mb * 1.5; -- +50%
severity = 'high';
reason = 'Disk spill detected. Increase by 50%';
-- CASE 2: some temp_files (100 to 5000/day)
ELSIF v_avg_metric > 100 THEN
v_ideal_mb = v_ideal_mb * 1.1; -- +10%
severity = 'medium';
reason = 'Occasional spills. Increase by 10%';
-- CASE 3: no temp_files
ELSE
IF v_increase_pct > 50 THEN
severity = 'low';
confidence = 90;
reason = 'No spill. Conservative config, safe to increase.';
ELSE
severity = 'low';
confidence = 95;
reason = 'Config adequate for the current workload';
END IF;
END IF;3) effective_cache_size — the planner's indicator
effective_cache_size reserves no memory: it tells the query planner how much RAM is really available for caching (shared_buffers + OS disk cache). The planner uses it to choose between a sequential scan and an index scan.
Pgtune formula: effective_cache_size = RAM × 75%. Example for 16 GB: 12 GB. Rationale: shared_buffers covers 25% (data cache), effective_cache_size covers 75% (total cache + OS), the rest serves the OS, connections and the rest of the system.
- Typical impact when going from 1 GB to 12 GB: better query plans, index usage +40%, queries +20% faster, CPU -25%
SELECT effective_cache_size INTO v_current_setting
FROM perfhist.settings_hist
ORDER BY date_extract DESC LIMIT 1;v_ideal_mb = p_ram_gb * 1024 * 0.75;
v_severity = CASE
WHEN v_current_mb < (v_ideal_mb * 0.9) THEN 'medium'
ELSE 'low'
END;
-- reason: 'Pgtune recommends 75% of RAM. This parameter tells
-- the planner how much memory is available for caching.
-- Underestimated, the planner picks slower plans.'4) max_connections — concurrency capacity
max_connections is the maximum number of simultaneous connections to PostgreSQL. A poorly tuned value causes either connection rejections ("too many connections"), or wasted memory on unused connections.
The Pgtune formula depends on the observed workload type, not a fixed value: it's based on the number of CPU cores and the daily transaction volume.
- Real example: 8,302,204 transactions/day ≈ 96 transactions/second → classified as Heavy OLTP
- Well configured: no "too many connections" errors, no wasted resources, stable performance, better scalability
Heavy OLTP (more than 100k trans/day) : max_connections = CPU cores x 25
Moderate OLTP (50k to 100k trans/day) : max_connections = CPU cores x 12
Light OLTP (10k to 50k trans/day) : max_connections = CPU cores x 6
Very light (less than 10k trans/day) : max_connections = CPU cores x 4SELECT AVG(xact_commit + xact_rollback) INTO v_avg_metric
FROM perfhist.pg_stat_database_hist
WHERE datname = v_db_name
AND date_extract >= NOW() - INTERVAL '30 days';v_ideal_mb = p_cpu_cores * 25; -- Heavy OLTP example: 8 cores x 25 = 200 connections
IF v_current_mb > (v_ideal_mb * 2) THEN
severity = 'medium';
reason = 'OVERSIZED: ' || v_current_mb || ' vs ' || v_ideal_mb || ' needed. Reduce to save memory.';
ELSIF v_current_mb < (v_ideal_mb * 0.8) THEN
severity = 'high';
reason = 'RISK: ' || v_current_mb || ' vs ' || v_ideal_mb || ' recommended. Risk of connection rejection.';
ELSE
severity = 'low';
reason = 'Appropriate configuration.';
END IF;5) maintenance_work_mem — VACUUM and CREATE INDEX speed
maintenance_work_mem is the memory used by VACUUM, CREATE INDEX and ANALYZE. The higher it is, the faster these maintenance operations run.
Pgtune formula: maintenance_work_mem = RAM × 15%. Example for 16 GB: 2.4 GB.
- Typical impact: VACUUM -70% time, CREATE INDEX -60%, ANALYZE -50%, much faster nightly maintenance
v_ideal_mb = p_ram_gb * 1024 * 0.15;
-- reason: 'Pgtune recommends 15% of RAM for maintenance operations.
-- Used by VACUUM FULL (removes dead rows), CREATE INDEX
-- (builds indexes), ANALYZE (collects statistics).
-- Higher values speed up maintenance.'
v_severity = CASE
WHEN v_current_mb < (v_ideal_mb * 0.8) THEN 'medium'
ELSE 'low'
END;6) random_page_cost — index usage
random_page_cost is the relative cost of a random disk access compared to a sequential access; it directly influences the query planner's choices.
By default, random_page_cost is 4.0: a random access is considered 4 times more expensive than a sequential access. If the planner too often favors sequential scans over indexes, lowering this value (1.1 to 1.5) forces index usage and can speed up queries by 15 to 40%.
- Typical impact when going from 4.0 to 1.1: index usage +40%, sequential scans -50%, queries +15 to 40% faster, CPU -25%
SELECT
SUM(seq_scan)::NUMERIC / NULLIF(SUM(idx_scan)::NUMERIC, 0)
INTO v_first_metric
FROM perfhist.pg_stat_tables_hist
WHERE date_extract >= NOW() - INTERVAL '30 days';
-- Interpretation:
-- 2:1 = balanced (good)
-- 5:1 = needs improvement
-- 10:1 = high pressure (critical)IF v_first_metric > 10 THEN
v_ideal_mb = 1.1;
severity = 'high';
reason = 'Heavy sequential scan pressure. Reduce to ' || v_ideal_mb || ' to force index usage. Expected gain: +15 to 40%.';
ELSIF v_first_metric > 5 THEN
v_ideal_mb = 1.5;
severity = 'medium';
reason = 'Moderate scan usage. Reduce to ' || v_ideal_mb || '.';
ELSE
v_ideal_mb = 4.0;
severity = 'low';
reason = 'Balanced distribution. No action needed.';
END IF;7) wal_buffers — transactional throughput
wal_buffers is the buffer used for the Write-Ahead Log (WAL). It batches writes together (fewer disk flushes), which improves transactional throughput.
- Example 1 — load 8.3M transactions/day (Heavy OLTP), current 16 MB, ideal 16 MB, gap 0 MB → "Already optimal"
- Example 2 — same load, current 4 MB, ideal 16 MB, gap 12 MB → "Increase to 16 MB"
- Typical impact under Heavy OLTP: better-batched writes, disk flushes -50%, throughput +10 to 20%, latency -5 ms
Heavy OLTP : 16 MB
Moderate OLTP : 4 MB
Light OLTP : auto (default value)IF v_ideal_mb = -1 THEN
-- light load -> 'auto' is fine
recommended = 'auto';
ELSIF ABS(v_current_mb - v_ideal_mb) < 0.5 THEN
-- already optimal (gap < 0.5 MB)
recommended = 'Already optimal';
severity = 'low';
confidence = 95;
reason = 'wal_buffers is ALREADY optimally configured. No change needed.';
ELSE
recommended = ROUND(v_ideal_mb, 2) || 'MB';
severity = 'high or medium';
reason = 'Change from ' || v_current_mb || ' to ' || v_ideal_mb || 'MB';
END IF;The general rule: "already optimal?"
Without this rule, a recommendation can look illogical: an identical current and ideal value, but shown with a "high" severity. The Optimizer therefore systematically applies a gap threshold before recommending a change, for each of the 7 parameters.
BEFORE (inconsistent):
wal_buffers
Current : 16MB
Recommended : 16MB
Severity : HIGH <- contradiction
AFTER (consistent):
wal_buffers
Current : 16MB
Recommended : Already optimal
Severity : LOW
Confidence : 95%v_change_pct = ABS(v_current - v_ideal) / v_ideal * 100;
IF v_change_pct < 5 THEN
-- gap < 5% = already optimal
recommended = 'Already optimal';
severity = 'low';
confidence = confidence + 5;
reason = 'Config already optimal. ' || reason;
ELSE
-- gap >= 5% = change recommended
recommended = v_ideal;
-- original severity and reason kept
END IF;Severity and confidence score
Each recommendation comes with a severity level (how urgent it is to act) and a confidence score (how reliable the recommendation is given the available data).
- Critical — the system is at risk, action recommended the same day (example: max_connections too low for a Heavy OLTP workload)
- High — significant performance impact, action recommended within a week (example: work_mem with thousands of temporary files per day)
- Medium — noticeable but non-urgent impact, action within the next few weeks (example: poorly sized shared_buffers)
- Low — minimal impact, not urgent (example: configuration already adequate)
Confidence = f(data volume, trend stability, formula used)
95%+ : 200+ snapshots, stable configuration, proven Pgtune formula
-> apply the recommendation now
80-95% : 150+ snapshots, clear trend, Pgtune formula
-> apply then monitor
60-80% : insufficient data or variable trend, ad hoc estimate
-> test in a dev environment first
<60% : fewer than 100 snapshots, inconsistent trend
-> wait for 30 more days of collectionReal-world use case
Example on a server with 16 GB of RAM and 8 CPU cores, with a load of 8.3 million transactions per day (Heavy OLTP) — the 7 recommendations generated by the Optimizer:
Parameter Current Recommended Severity Confidence Action
shared_buffers 128MB 4096MB MEDIUM 80% Increase
work_mem 4MB 61.44MB LOW 90% Consider
effective_cache_size 1GB 12GB LOW 80% Increase
max_connections 150 200 LOW 80% OK
maintenance_work_mem 256MB 2400MB MEDIUM 75% Increase
random_page_cost 4.0 1.1 HIGH 85% Reduce
wal_buffers 16MB Already optimal LOW 95% No changeBefore:
Cache hit : 70%
Temp files : 1000/day
Queries : 100 TPS
Latency : 500ms
After (7 days):
Cache hit : 94%
Temp files : 0/day
Queries : 300 TPS (+200%)
Latency : 150ms (-70%)The benefits of the Tuning Advisor
Beyond the measured performance gain, the Smart Optimizer changes how PostgreSQL configuration is approached day to day.
- No need for a dedicated DBA for first-level tuning: the analysis is automatic and included in PWR
- Based on your real data, not on assumptions or "trial and error" — every recommendation relies on 30 days of actual history
- Full transparency: every recommendation shows its technical justification, the formula used, its confidence score and its estimated impact
- Controlled risk: every recommendation remains a proposal to validate — the recommended usage is to apply it first in a test environment, then in production, after checking backups
Conclusion
PWR's Tuning Advisor turns PostgreSQL configuration into a measurable process rather than an intuition: by analyzing 30 days of real data, it recommends precise, justified and quantified changes for 7 of the parameters most decisive for performance.
Observed result: databases 2 to 5 times faster, with no need for a dedicated DBA for this first level of optimization.
