Quick Summary - I suggest we add the following two indexes:
CREATE INDEX dbmail_headervalue_physmsg_id on dbmail_headervalue
(physmessage_id );
CREATE INDEX dbmail_headername_lower_headername on
dbmail_headername(lower(headername));
Details:
I was getting reports of serious performance problems from some of our
users using Squirrelmail, so I took a quick look. I saw this query
popping up faily often in my PostgreSQL logs:
SELECT message_idnr,headername,headervalue FROM dbmail_headervalue v
JOIN dbmail_messages m ON v.physmessage_id=m.physmessage_id JOIN
dbmail_mailboxes b ON m.mailbox_idnr=b.mailbox_idnr JOIN
dbmail_headername n ON v.headername_id=n.id WHERE m.mailbox_idnr = 484
AND message_idnr BETWEEN 1539669 AND 2432588 AND lower(headername) IN
('date','to','cc','from','subject','x-priority','importance','priority','content-type');
Before the indexes, this query was taking nearly two minutes to return
16 rows. After the two new Indexes, it's taking approx 1.0 seconds.
The big difference is that there was a Sequential Scan on both
lower(headername) and dbmail_headervalue.physmessage_id.
The Explain Analyze Output for this query before the new indexes was:
QUERY PLAN
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Nested Loop (cost=274.40..357370.72 rows=537 width=97) (actual
time=65041.129..133927.163 rows=670 loops=1)
-> Index Scan using mailboxes_pkey on dbmail_mailboxes b
(cost=0.00..3.47 rows=1 width=8) (actual time=0.031..0.038 rows=1 loops=1)
Index Cond: (484 = mailbox_idnr)
-> Hash Join (cost=274.40..357361.88 rows=537 width=105) (actual
time=65041.082..133920.843 rows=670 loops=1)
Hash Cond: ("outer".headername_id = "inner".id)
-> Hash Join (cost=75.47..357096.58 rows=12202 width=91)
(actual time=64958.937..133821.885 rows=2802 loops=1)
Hash Cond: ("outer".physmessage_id = "inner".physmessage_id)
-> Seq Scan on dbmail_headervalue v
(cost=0.00..298019.39 rows=11775939 width=83) (actual
time=0.007..77345.065 rows=11776512 loops=1)
-> Hash (cost=74.73..74.73 rows=296 width=24) (actual
time=80.875..80.875 rows=103 loops=1)
-> Index Scan using dbmail_messages_mailbox_idx
on dbmail_messages m (cost=0.00..74.73 rows=296 width=24) (actual
time=13.484..80.347 rows=103 loops=1)
Index Cond: (mailbox_idnr = 484)
Filter: ((message_idnr >= 1539669) AND
(message_idnr <= 2432588))
-> Hash (cost=198.58..198.58 rows=139 width=30) (actual
time=82.106..82.106 rows=21 loops=1)
-> Seq Scan on dbmail_headername n (cost=0.00..198.58
rows=139 width=30) (actual time=0.075..81.985 rows=21 loops=1)
Filter: ((lower((headername)::text) =
'date'::text) OR (lower((headername)::text) = 'to'::text) OR
(lower((headername)::text) = 'cc'::text) OR (lower((headername)::text) =
'from'::text) OR (lower((headername)::text) = 'subject'::text) OR
(lower((headername)::text) = 'x-priority'::text) OR
(lower((headername)::text) = 'importance'::text) OR
(lower((headername)::text) = 'priority'::text) OR
(lower((headername)::text) = 'content-type'::text))
Total runtime: 133930.438 ms
(16 rows)
The Explain Analyze Output for this query with the new indexes is:
QUERY PLAN
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Nested Loop (cost=51.68..41742.66 rows=537 width=97) (actual
time=192.164..1016.753 rows=670 loops=1)
-> Index Scan using mailboxes_pkey on dbmail_mailboxes b
(cost=0.00..3.47 rows=1 width=8) (actual time=0.047..0.053 rows=1 loops=1)
Index Cond: (484 = mailbox_idnr)
-> Hash Join (cost=51.68..41733.82 rows=537 width=105) (actual
time=192.099..1006.412 rows=670 loops=1)
Hash Cond: ("outer".headername_id = "inner".id)
-> Nested Loop (cost=0.00..41615.76 rows=12203 width=91)
(actual time=91.344..888.840 rows=2802 loops=1)
-> Index Scan using dbmail_messages_mailbox_idx on
dbmail_messages m (cost=0.00..74.73 rows=296 width=24) (actual
time=91.286..566.275 rows=103 loops=1)
Index Cond: (mailbox_idnr = 484)
Filter: ((message_idnr >= 1539669) AND
(message_idnr <= 2432588))
-> Index Scan using dbmail_headervalue_physmsg_id on
dbmail_headervalue v (cost=0.00..121.47 rows=1510 width=83) (actual
time=1.709..2.866 rows=27 loops=103)
Index Cond: (v.physmessage_id =
"outer".physmessage_id)
-> Hash (cost=51.33..51.33 rows=139 width=30) (actual
time=100.717..100.717 rows=21 loops=1)
-> Bitmap Heap Scan on dbmail_headername n
(cost=18.52..51.33 rows=139 width=30) (actual time=59.706..100.593
rows=21 loops=1)
Recheck Cond: ((lower((headername)::text) =
'date'::text) OR (lower((headername)::text) = 'to'::text) OR
(lower((headername)::text) = 'cc'::text) OR (lower((headername)::text) =
'from'::text) OR (lower((headername)::text) = 'subject'::text) OR
(lower((headername)::text) = 'x-priority'::text) OR
(lower((headername)::text) = 'importance'::text) OR
(lower((headername)::text) = 'priority'::text) OR
(lower((headername)::text) = 'content-type'::text))
-> BitmapOr (cost=18.52..18.52 rows=142 width=0)
(actual time=59.687..59.687 rows=0 loops=1)
-> Bitmap Index Scan on
dbmail_headername_lower_headername (cost=0.00..2.06 rows=16 width=0)
(actual time=47.204..47.204 rows=2 loops=1)
Index Cond: (lower((headername)::text)
= 'date'::text)
-> Bitmap Index Scan on
dbmail_headername_lower_headername (cost=0.00..2.06 rows=16 width=0)
(actual time=0.030..0.030 rows=3 loops=1)
Index Cond: (lower((headername)::text)
= 'to'::text)
-> Bitmap Index Scan on
dbmail_headername_lower_headername (cost=0.00..2.06 rows=16 width=0)
(actual time=0.012..0.012 rows=3 loops=1)
Index Cond: (lower((headername)::text)
= 'cc'::text)
-> Bitmap Index Scan on
dbmail_headername_lower_headername (cost=0.00..2.06 rows=16 width=0)
(actual time=0.013..0.013 rows=3 loops=1)
Index Cond: (lower((headername)::text)
= 'from'::text)
-> Bitmap Index Scan on
dbmail_headername_lower_headername (cost=0.00..2.06 rows=16 width=0)
(actual time=0.027..0.027 rows=3 loops=1)
Index Cond: (lower((headername)::text)
= 'subject'::text)
-> Bitmap Index Scan on
dbmail_headername_lower_headername (cost=0.00..2.06 rows=16 width=0)
(actual time=12.270..12.270 rows=3 loops=1)
Index Cond: (lower((headername)::text)
= 'x-priority'::text)
-> Bitmap Index Scan on
dbmail_headername_lower_headername (cost=0.00..2.06 rows=16 width=0)
(actual time=0.015..0.015 rows=2 loops=1)
Index Cond: (lower((headername)::text)
= 'importance'::text)
-> Bitmap Index Scan on
dbmail_headername_lower_headername (cost=0.00..2.06 rows=16 width=0)
(actual time=0.012..0.012 rows=1 loops=1)
Index Cond: (lower((headername)::text)
= 'priority'::text)
-> Bitmap Index Scan on
dbmail_headername_lower_headername (cost=0.00..2.06 rows=16 width=0)
(actual time=0.013..0.013 rows=1 loops=1)
Index Cond: (lower((headername)::text)
= 'content-type'::text)
Total runtime: 1020.206 ms
(34 rows)