Server configuration
That much is clear: the default settings are very conservative and intended to work for small installations with limited resources out of the box. For a dedicated DB server, some default settings are just inadequate. You have to tune your settings.
To start with, if you have enough RAM to cache all or most of your DB, set random_page_cost
drastically lower. And increase the relative cost of CPU operations. Something like (this is pure guesswork!):
seq_page_cost: 1 random_page_cost: 1.2 cpu_tuple_cost: .02 cpu_index_tuple_cost: .02 cpu_operator_cost: .005
And effective_cache_size
is regularly much too low. For a dedicated DB server this can be as high as three quarters of your total RAM.
@Craig has assembled a long list of advice for performance tuning:
Optimise PostgreSQL for fast testing
The Postgres Wiki has even more.
Query
Too many redundant parentheses, too hard to read. Use table aliases and format before trying to debug - much less presenting to the general public. After untangling:
SELECT count(*) AS count_all
FROM events e
JOIN tickets t ON t.event_id = e.id
JOIN access_rights a ON a.ticket_id = t.id
LEFT JOIN departments d ON d.id = a.target_id
AND a.target_type = 'Department'
WHERE e.activity = 'f'
AND (e.canceled_at IS NULL OR e.canceled_at > '2013-10-29 23:11:37')
AND (t.hold_until IS NULL OR t.hold_until <= '2013-10-29')
AND t.company_id = 173;
AND a.section = 'shop'
AND (a.target_type = 'Company' AND a.target_id = 173
OR a.target_type = 'User' AND a.target_id = 11654
OR a.target_type = 'UserGroup' AND a.target_id IN (126)
OR d.lft <= 7 AND d.rgt >= 8
-- a.target_type = 'Department' is redundant
)
AND (a.frozen_activation = 'active'
OR a.active_on <= '2013-10-29'
AND (a.inactive_on IS NULL OR a.inactive_on > '2013-10-29')
AND a.frozen_activation IS NULL
)
Major points
Redundant:
AND a.active_on IS NOT NULL
, since you also haveAND a.active_on <= '2013-10-29'
AND a.target_id IN ('126')
should beAND a.target_id = 126
or at leastAND a.target_id IN (126)
(numeric constant).a.target_type = 'Department'
is redundant, since its already in theLEFT JOIN
AND a.section = 'shop'
is redundant many times.target_type_id
should most probably be anenum
orinteger
referencing a tabletarget_type
instead of avarchar(255)
.CREATE TABLE access_rights ( ... ,target_type_id integer NOT NULL REFERENCES target_type(target_type_id) ... );
Similar for
a.frozen_activation
anda.section
.
That would also make the index I am going to propose more effective.
Indices
Add a few multicolumn / partial indices. Tailor yourself, I don't know cardinalities and data distribution. Note the DESC
clauses in strategic places.
CREATE INDEX e_idx ON events (company_id, event_id, hold_until)
WHERE activity = FALSE;
CREATE INDEX t_idx ON tickets (company_id, event_id, hold_until DESC);
CREATE INDEX a_idx1 ON access_rights (target_type_id, target_id)
WHERE section = 'shop';
CREATE INDEX a_idx2 ON access_rights
(frozen_activation, active_on DESC, inactive_on)
WHERE section = 'shop';
CREATE INDEX d_idx ON departments (target_type, lft DESC, rgt);
Other than that you only need the primary keys and indices on foreign keys. All other indices you display would then be useless for this query. Delete some if they are not needed elsewhere.
For details on how to tailor these indices consider the related answer on dba.SE: