I have implemented data denormalization strategy using postgresql RULEs. I picked rules instead of triggers for performance reasons.
Schema is structured like this:
- Application has many clients
- Client has many projects
- Project has many users
One part of the system is storing hits
for every user in stats
table. Hit is an imaginary metric, it is not really relevant. System can collect many of these metrics. There are a lot of records in stats table (> 1,000,000 per day).
I want to know how many hits are per user, per project, per client and per application for given day.
To make it work fast, I've groupped stats by day and stored the output into user_hits table. During this process, also the application_id, client_id and project_id has been added (as columns), and appropriate indexes created.
I want to further optimise the process by grouping things by project_id, client_id and finally application_id. The data pipeline is like this:
stats -> user_hits -> project_hits -> client_hits -> application_hits
I want to make sure when I delete the data from user_hits
for given day, that the data in project_hits
for that same date is also deleted. This process should propagate to last table in chain.
I defined these simple rules:
CREATE RULE delete_children AS ON DELETE TO user_hits
DO ALSO
DELETE FROM project_hits WHERE day = OLD.day;
CREATE RULE delete_children AS ON DELETE TO project_hits
DO ALSO
DELETE FROM client_hits WHERE day = OLD.day;
CREATE RULE delete_children AS ON DELETE TO client_hits
DO ALSO
DELETE FROM application_hits WHERE day = OLD.day;
However, when I issue statement like this:
DELETE FROM user_hits WHERE day = current_date;
I expect it to run these 3 queries in return:
DELETE FROM project_hits WHERE day = current_date;
DELETE FROM client_hits WHERE day = current_date;
DELETE FROM application_hits WHERE day = current_date;
However, it doesn't.
It completes the operation, but it takes couple of minutes to do that (with test data). With real data it takes hours, while running those 3 queries by hand takes couple of milliseconds. The time it takes seems proportional to number of combinations (users x projects x clients x applications).
What is the problem here? Am I missing something? Can this be implemented with triggers in an optimised way?
Included sample script which reproduces the problem:
https://gist.github.com/assembler/5151102
UPDATE: Transition from user_hits
to project_hits
(and so forth) is done by worker process in background (since it involves contacting 3rd party services for additional info). It is smart enough to recalculate everything for missing dates. So the only thing i need is a way to DELETE records cascadingly in optimised way.
UPDATE: stats
table is filled on daily basis. The only possible scenario is to unconditionally delete the data for whole day and then replace it with new values.
UPDATE: I noticed that the number of affected rows (extracted from explain
statement) is exactly equal to the product of affected rows in user_hits
, project_hits
, client_hits
, and application_hits
tables (hundreds of millions of rows).
It turns out that it works like this:
- I run
DELETE FROM user_hits WHERE day = current_date;
- For each row in
user_hits
table, RULE is triggered which deletes EVERY row fromproject_hits
- For each row of
project_hits
, RULE is triggered which deletes EVERY row fromclient_hits
- For each row of
client_hits
, RULE is triggered which deletes EVERY row fromapplication_hits
So, the number of operations is equal to the product of count of affected rows in these tables.
pg_locks
? What's the state of your statement inpg_stat_activity
?pg_locks
withpg_class
I found that all of the relations that should be affected (per_day, per_week, per_month...) are indeed in pg_locks.