When handling massive outbound email volumes (~1M messages/day), Postfix can become bottlenecked even on servers with spare CPU and memory capacity. The iostat output reveals the classic signs:
Device: rrqm/s wrqm/s r/s w/s rsec/s wsec/s avgrq-sz avgqu-sz await svctm %util
sdb 1.49 22.28 72.28 42.57 629.70 1041.58 14.55 135.56 834.31 8.71 100.00
With 100% disk utilization and 135+ I/O requests queued, we're seeing pathological queue shuffling between incoming→active→deferred states.
The hardware specs tell part of the story:
- Quad-core Xeon E5405 @ 2.0GHz (2008-era architecture)
- 4GB RAM (minimal for modern mail volumes)
- Single HDD for Postfix queues (sdb)
But even with these constraints, we can optimize significantly.
Queue Filesystem Optimization:
# /etc/postfix/main.cf
queue_directory = /var/spool/postfix
# Convert to tmpfs for in-memory queue
# mount -t tmpfs -o size=1024m tmpfs /var/spool/postfix
Concurrency Control:
default_process_limit = 100
maximal_queue_lifetime = 5d
qmgr_message_active_limit = 200000
SSD Migration Path:
# Benchmark SSD vs HDD for queue operations
hdparm -tT /dev/sdb
fio --filename=/dev/sdb --rw=randrw --bs=4k --ioengine=libaio --iodepth=64 --runtime=120 --numjobs=4 --time_based --group_reporting --name=randomrw
Queue Partitioning:
# Separate queues by priority
fast_queue = defer,hold
slow_queue = incoming,active,maildrop
# Use separate physical devices
Create a real-time monitoring script:
#!/bin/bash
watch -n 5 "postqueue -p | awk 'BEGIN { RS = \"\" } { print \$1 \" \" \$5 }' | sort | uniq -c | sort -n"
For sustained high-volume sending:
- Implement RabbitMQ or Redis as a frontend queue
- Deploy multiple Postfix instances with DNS-based load balancing
- Consider dedicated mail delivery appliances like Haraka
main.cf Tweaks:
# Reduce disk sync overhead
disable_dns_lookups = yes
smtp_skip_5xx_greeting = yes
smtp_destination_concurrency_limit = 20
smtp_destination_rate_delay = 1s
When handling ~1 million daily outbound messages on Ubuntu with Postfix, the iostat output tells a clear story:
avg-cpu: %user %nice %system %iowait %steal %idle
0.00 0.00 0.12 99.88 0.00 0.00
Device: rrqm/s wrqm/s r/s w/s rsec/s wsec/s avgrq-sz avgqu-sz await svctm %util
sda 0.00 12.38 0.00 2.48 0.00 118.81 48.00 0.00 0.00 0.00 0.00
sdb 1.49 22.28 72.28 42.57 629.70 1041.58 14.55 135.56 834.31 8.71 100.00
That 100% disk utilization on sdb (Postfix's dedicated disk) with 834ms await time is murdering your throughput.
Your Quad core Xeon @ 2.00GHz with 4GB RAM should handle this load easily if not for the IO bottleneck. The 400+ load average with minimal CPU usage confirms this.
First, let's optimize your main.cf:
# Queue processing
qmgr_message_active_limit = 100000
qmgr_message_recipient_limit = 20000
default_process_limit = 50
initial_destination_concurrency = 20
default_destination_concurrency_limit = 20
# IO optimizations
mailbox_size_limit = 0
message_size_limit = 10240000
disable_vrfy_command = yes
For your dedicated Postfix disk (/var/spool/postfix):
# Mount options in /etc/fstab:
UUID=[your-sdb-uuid] /var/spool/postfix ext4 noatime,nodiratime,data=writeback,barrier=0 0 2
# Then run:
sudo tune2fs -o journal_data_writeback /dev/sdb
sudo tune2fs -O ^has_journal /dev/sdb
Implement staggered queue processing to reduce disk contention:
# In master.cf:
smtp unix - - n - - smtp -o smtp_destination_concurrency_limit=10
relay unix - - n - - smtp -o smtp_destination_concurrency_limit=5
Consider moving some queue operations to tmpfs:
# Add to /etc/fstab:
tmpfs /var/spool/postfix/incoming tmpfs defaults,size=1G 0 0
tmpfs /var/spool/postfix/active tmpfs defaults,size=2G 0 0
Implement these cron jobs for queue maintenance:
# Hourly queue cleanup
0 * * * * postfix flush >/dev/null 2>&1
# Daily queue optimization
0 3 * * * postfix qmgr -c >/dev/null 2>&1
If budget allows, replacing the spinning disk with an SSD will give you the most dramatic improvement. Even a consumer-grade SSD can handle 50,000+ IOPS compared to ~150 IOPS for your current disk.
For ultimate performance, split Postfix queues across multiple disks:
# In main.cf:
queue_directory = /var/spool/postfix
alternate_config_directories = /mnt/disk1/postfix /mnt/disk2/postfix