Re: is this trigger safe and efective? - locking (caching via triiggers)

From: Erik Jones <erik(at)myemma(dot)com>
To: Pavel Stehule <pavel(dot)stehule(at)gmail(dot)com>
Cc: pgsql-general <pgsql-general(at)postgresql(dot)org>
Subject: Re: is this trigger safe and efective? - locking (caching via triiggers)
Date: 2007-08-15 16:49:40
Message-ID: B32E502D-58B3-4D12-9280-87838C8075A8@myemma.com
Views: Raw Message | Whole Thread | Download mbox | Resend email
Thread:
Lists: pgsql-general pgsql-hackers

On Aug 15, 2007, at 11:14 AM, Pavel Stehule wrote:

> 2007/8/15, Erik Jones <erik(at)myemma(dot)com>:
>> On Aug 15, 2007, at 4:57 AM, Pavel Stehule wrote:
>>
>>> I write sample about triggers and i have question. is my solution
>>> correct and exists better solution?
>>>
>>> Regards
>>> Pavel Stehule
>>>
>>> DROP SCHEMA safecache CASCADE;
>>>
>>> CREATE SCHEMA safecache;
>>>
>>> CREATE TABLE safecache.source_tbl(category int, int_value int);
>>>
>>> CREATE TABLE safecache.cache(category int, sum_val int);
>>>
>>> CREATE OR REPLACE FUNCTION safecache.source_tbl_trg_fce()
>>> RETURNS trigger AS
>>> $$
>>> BEGIN
>>> IF TG_OP = 'INSERT' THEN
>>> -- row cannot exists in cache -- complication
>>> -- I would to finish these transaction without conflict
>>> IF NOT EXISTS(SELECT category
>>> FROM safecache.cache
>>> WHERE category = NEW.category) THEN
>>> LOCK TABLE safecache.cache IN SHARE ROW EXCLUSIVE MODE;
>>> -- I have to repeat test
>>> IF NOT EXISTS(SELECT category
>>> FROM safecache.cache
>>> WHERE category = NEW.category) THEN
>>> INSERT INTO safecache.cache
>>> VALUES(NEW.category, NEW.int_value);
>>> END IF;
>>> ELSE
>>> -- simple
>>> UPDATE safecache.cache
>>> SET sum_val = sum_val + NEW.int_value
>>> WHERE category = NEW.category;
>>> END IF;
>>> ELSEIF TG_OP = 'UPDATE' THEN
>>> -- if category is without change simple
>>> IF NEW.category = OLD.category THEN
>>> UPDATE safecache.cache
>>> SET sum_val = sum_val + (NEW.int_value - OLD.int_value)
>>> WHERE category = OLD.category;
>>> ELSE
>>> -- old category has to exists
>>> UPDATE safecache.cache
>>> SET sum_val = sum_val - OLD.int_value
>>> WHERE category = OLD.category;
>>> -- new category is maybe problem
>>> IF NOT EXISTS(SELECT category
>>> FROM safecache.cache
>>> WHERE category = NEW.category) THEN
>>> LOCK TABLE safecache.cache IN SHARE ROW EXCLUSIVE MODE;
>>> -- I have to repeat test
>>> IF NOT EXISTS(SELECT category
>>> FROM safecache.cache
>>> WHERE category = NEW.category) THEN
>>> INSERT INTO safecache.cache
>>> VALUES(NEW.category, NEW.int_value);
>>> END IF;
>>> ELSE
>>> -- simple, new category exists
>>> UPDATE safecache.cache
>>> SET sum_val = sum_val + OLD.int_value
>>> WHERE category = NEW.category;
>>> END IF;
>>> END IF;
>>> ELSE -- DELETE
>>> -- value have to exist in cache, simple
>>> UPDATE safecache.cache
>>> SET sum_val = sum_val - OLD.int_value
>>> WHERE category = OLD.category;
>>> END IF;
>>> RETURN NEW;
>>> END
>>> $$ LANGUAGE plpgsql;
>>>
>>> CREATE TRIGGER actualise_cache
>>> AFTER INSERT OR UPDATE OR DELETE
>>> ON safecache.source_tbl
>>> FOR EACH ROW EXECUTE PROCEDURE safecache.source_tbl_trg_fce();
>>
>> From what I can tell from your example it's "correct" and should
>> work under light loads. However, if that trigger will fire a lot,
>> you might see those updates "stacking" due to the necessary locking
>> (both your explicit locks and those take out by the updates). What
>> I've done in that case (this is actually a pretty standard setup), is
>> to have the trigger just make inserts into another table of the
>> category that needs to be updated and by how much. The you have some
>> other (probably user-land) process periodically sweep that table,
>> aggregate the updates to the cache table, then delete the interim
>> entries just processed. Oh yeah, you could simplify that function a
>> lot by simply initializing your cache table with a row for each
>> category with sum_val = 0. Then it's all updates and you don't need
>> those locks to determine if the category exists there.
>
> I know it, but I don't know all posible category numbers, and others.
> I sent sample with minimum (one) pk attributies.

One workaround is to make an on insert trigger that fires before this
one and checks to see if this is a new category and sets up the row
with value 0 in the cache table.

>>
>> Erik Jones
>
> I have 98% of SELECTs and 2% of INSERTs and UPDATE

Sounds like you should be ok then and you may not need to go with the
suggestions I've outlined. However, be sure to keep a close eye on
pg_locks when you push that trigger into production.

Erik Jones

Software Developer | Emma®
erik(at)myemma(dot)com
800.595.4401 or 615.292.5888
615.292.0777 (fax)

Emma helps organizations everywhere communicate & market in style.
Visit us online at http://www.myemma.com

In response to

Browse pgsql-general by date

  From Date Subject
Next Message Tom Lane 2007-08-15 16:57:48 Re: Select time jump after adding filter; please help me figure out what I'm doing wrong.
Previous Message Richard Huxton 2007-08-15 16:34:11 Re: Select time jump after adding filter; please help me figure out what I'm doing wrong.

Browse pgsql-hackers by date

  From Date Subject
Next Message Tom Lane 2007-08-15 16:49:52 Re: XID wraparound and busy databases
Previous Message Dawid Kuroczko 2007-08-15 16:39:46 Index Tuple Compression Approach?