Title
Page icon
Create new category
Edit page index title
Edit category
Edit link
Tuning high-volume PostgreSQL deployments
MetaDefender Email Gateway Security stores email history, quarantine data, statistics, and the email content itself in PostgreSQL. On high-volume deployments, the database server becomes the component that determines overall mail throughput: every stage of the processing pipeline (history recording, content storage, quarantine, reporting) issues database operations, so a saturated database slows down or stalls mail flow itself, not just the management UI.
A default PostgreSQL installation is tuned for small databases and light workloads. This page describes how to recognize a database-bound deployment and which PostgreSQL settings to change.
Always use a PostgreSQL version listed in the system requirements for your Email Gateway Security release. Apply configuration changes in a maintenance window and verify them afterwards — several settings below require a service restart.
When this page applies to you
Consider your deployment high-volume if it matches some of these characteristics:
Characteristic | Typical values |
|---|---|
Mail volume | 50,000–100,000+ emails per business day; sustained peaks of 5,000–7,000 emails/hour |
Traffic pattern | Strong business-hours profile: volume ramps 5–10× within one hour in the morning |
Email history size | Millions of email records (1–10M+); classification rows typically 2–3× the email count; quarantine in the hundreds of thousands to millions |
Database size | Several hundred GB to multiple TB, depending on daily volume and retention |
Database write volume | Tens of GB of WAL per hour during business hours |
Topology | External PostgreSQL server, possibly shared with MetaDefender Core databases |
Symptoms of an undersized or default-configured database
You are likely database-bound if you observe any of the following:
Emails accumulate in Pending state during business hours and drain outside them; an Email Gateway Security service restart does not help.
The Email History or Quarantine pages time out, load extremely slowly or only load when filters are applied.
The Email Gateway Security log contains
Failed to acquire Pool Object, because none is available,Email history query failed, or filestore read/write errors during busy hours.The PostgreSQL log contains repeated
checkpoints are occurring too frequentlyhints.Queries spill large amounts of temporary files to disk:
pg_stat_database.temp_bytesgrows quickly, or (withlog_temp_filesenabled) the PostgreSQL log shows frequent large temporary-file entries — a sign thatwork_memis too small for the workload. Iftemp_file_limitis set on your server, the same condition surfaces asERROR: temporary file size exceeds temp_file_limitin the PostgreSQL log, with the affected query cancelled.MetaDefender Core instances sharing the same database server return HTTP 503 to scan requests during the same periods.
On default settings, business-hours load can produce a self-reinforcing write amplification: once the WAL volume exceeds max_wal_size (default 2 GB) faster than the checkpoint interval, PostgreSQL checkpoints every 30–60 seconds, and each checkpoint forces full-page images for every touched page. In our load tests this roughly quadrupled the write cost at identical mail volume — enough to saturate the disk and stall both Email Gateway Security and a co-hosted MetaDefender Core database. Removing the amplification (first row group below) is the highest-impact change.
Recommended settings
All values assume a dedicated database server. Percentages refer to the database server's physical RAM — check it before applying. Example absolute values are given for a 64 GB server.
Checkpoints and WAL — highest impact
Setting | PostgreSQL default | Recommended | Restart? | Why |
|---|---|---|---|---|
| 2GB | 32–64GB | No (reload) | Prevents WAL-triggered checkpoints under load. At tens of GB of WAL per hour, 2 GB forces a checkpoint every 30–60 s. Requires matching free space on the WAL volume (See |
| 5min | 15–30min | No | Fewer checkpoints → fewer full-page re-images → several times less WAL at identical mail volume. |
| off | zstd (or | No | Compresses full-page images — typically 50–70% WAL reduction on this workload profile, at low CPU cost. |
| 80MB–512MB | 4GB | No | Keeps recycled WAL segments between traffic bursts, avoiding file create/delete churn. |
| 0.9 | keep 0.9 | — | Already appropriate. |
Raising checkpoint_timeout and max_wal_size increases the amount of WAL PostgreSQL must replay after a crash or unclean shutdown — recovery can take several minutes instead of seconds before the database accepts connections again (and mail flow resumes). Choose values that keep the worst-case recovery time acceptable for your availability requirements.
Memory
Setting | PostgreSQL default | Recommended | Restart? | Why |
|---|---|---|---|---|
| 128MB | 25% of RAM (e.g. 16GB) | Yes | The default forces constant page eviction and re-reads against a large database. |
| 4GB | ~70% of RAM (e.g. 45GB) | No | Planner hint; too low makes PostgreSQL choose unnecessarily pessimistic plans. |
| 4MB | 32–64MB | No | 4 MB makes even mid-size sorts and hash joins spill to temporary files on disk. Do not set much higher globally — the limit applies per sort/hash operation per backend. |
| 64MB | 1–2GB | No | Vacuum and index builds on multi-hundred-GB tables are severely slowed at 64 MB (repeated index passes). |
| -1 | 1GB | No | Bounds the same for autovacuum workers. |
Autovacuum — keep large tables healthy
With default thresholds (autovacuum_vacuum_scale_factor = 0.2), a multi-million-row history table is only vacuumed after 20% of it is dead — rare, enormous vacuum runs that then compete with production traffic for hours. Prefer frequent small vacuums:
Setting | PostgreSQL default | Recommended | Restart? |
|---|---|---|---|
| 3 | 4–6 | Yes |
| 200 (via | 1000–2000 | No |
| off | 0 (log all runs) | No |
Additionally, set per-table thresholds on the large Email Gateway Security tables so they receive small, frequent vacuums:
If the deployment has been running on defaults for a long time, schedule a one-time VACUUM (VERBOSE) of these tables in an off-hours window to clear accumulated dead rows first.
Guard rails — contain runaway queries and stuck sessions
Set these per database rather than globally, so maintenance operations are not affected:
temp_file_limit (default: unlimited) caps how much temporary file space a single process may use and is a reasonable safety net (e.g. 2GB). Two cautions:
The limit is per process: a query using parallel workers can consume a multiple of the limit before being cancelled.
Mind the unit when changing it. The value accepts
kB,MBandGBsuffixes — a typo such as2000kBinstead of2000MBmakes practically every reporting query and index build fail withtemporary file size exceeds temp_file_limit.
Observability — cheap settings that make the next incident diagnosable
Setting | Recommended | Effect |
|---|---|---|
| on | Logs any session waiting on a lock longer than |
| 102400 (100 MB) | Logs every large temporary-file spill together with the query that caused it. |
| 5000–30000 (ms) | Logs slow statements. |
| on | Adds real I/O timings to |
| pg_stat_statements | Per-query statistics; on PostgreSQL 15+ includes |
To see which database and which queries generate the write load:
Reset the statistics at the start of a business day (SELECT pg_stat_statements_reset();) and snapshot them at the end for a clean measurement window.
What not to change
full_page_writes— leave on. Disabling it also removes checkpoint write amplification, but risks unrecoverable corruption after a crash unless the storage guarantees atomic 8 kB writes. Usewal_compressionand a largermax_wal_sizeinstead.synchronous_commit— leave on unless you can accept losing the most recent commits on a crash.fsync— never disable.
Beyond postgresql.conf
These architectural measures matter as much as the configuration settings:
Do not share one PostgreSQL instance between Email Gateway Security and MetaDefender Core at high volume. Both products are write-heavy; on a shared instance they saturate the same disk and connection pool, and degrade together. Use separate instances — ideally separate servers.
Place
pg_walon a separate physical disk from the data files. Every commit waits on a WAL flush; on a shared disk, checkpoint bursts and temporary-file spills inflate commit latency for every transaction.Size retention to storage. Email history and quarantine retention multiplied by daily volume determines the database size; long retention at a high daily volume quickly produces a multi-TB database. Smaller tables mean faster queries, faster vacuum, and faster backups.
Watch connection counts.
max_connections(default 100) is shared by all Email Gateway Security instances, MetaDefender Core instances, and tools. Post-restart reconnection storms can exhaust it; a connection pooler in front of high-churn clients removes both the storm risk and the per-connection process overhead (significant on Windows).Verify disk headroom after tuning: during business hours, disk write latency and queue length (Windows Performance Monitor, or
iostaton Linux) should stay well below saturation. The tuning above reduces the write volume several-fold, but the underlying disk still has to carry the remainder.
Quick verification checklist
After applying the changes, during a normal business day:
PostgreSQL log contains no
checkpoints are occurring too frequentlyhints; checkpoints are timed (checkpoint starting: time), not WAL-triggered.No
temporary file size exceeds temp_file_limiterrors;log_temp_filesshows no unexpectedly large spills.SELECT * FROM pg_stat_database WHERE datname = 'mdemailsecurity';showstemp_bytesgrowing slowly or not at all.Emails do not accumulate in Pending state at peak hour; Email History and Quarantine pages load without filters.
Autovacuum log lines show regular short runs on the large tables instead of rare multi-hour ones.