Discussion:
Performance on Bulk Insert to Partitioned Table
(too old to reply)
Charles Gomes
2012-12-20 17:29:19 UTC
Permalink
Hello guys

 

I’m doing 1.2 Billion inserts into a table partitioned in
15.

 

When I target the MASTER table on all the inserts and let
the trigger decide what partition to choose from it takes 4 hours.

If I target the partitioned table directly during the
insert I can get 4 times better performance. It takes 1 hour.

 

 

I’m trying to get more performance while still using the
trigger to choose the table, so partitions can be changed without changing the
application that inserts the data.

 

What I noticed that iostat is not showing an I/O bottle
neck.

iostat –xN 1

Device:        
rrqm/s   wrqm/s     r/s    
w/s   rsec/s   wsec/s avgrq-sz avgqu-sz  
await  svctm  %util

Pgresql--data    
0.00     0.00    0.00
8288.00     0.00 66304.00    
8.00    60.92    7.35  
0.01   4.30

 

iostat –m 1

Device:           
tps    MB_read/s    MB_wrtn/s   
MB_read    MB_wrtn

dm-2          
4096.00        
0.00       
16.00         
0         16

 

I also don’t see a CPU bottleneck or context switching
bottle neck.

 

Postgresql does not seem to write more than 16MB/s or 4K
transactions per second unless I target each individual partition.

 

Did anybody have done some studies on partitioning bulk
insert performance?

 

Any suggestions on a way to accelerate it ?

 

 

Running pgsql 9.2.2 on RHEL 6.3

 

My trigger is pretty straight forward:

CREATE OR REPLACE FUNCTION quotes_insert_trigger()

RETURNS trigger AS $$

DECLARE

tablename varchar(24);

bdate varchar(10);

edate varchar(10);

BEGIN

tablename = 'quotes_' ||
to_char(new.received_time,'YYYY_MM_DD');

EXECUTE 'INSERT INTO '|| tablename ||' VALUES (($1).*)'
USING NEW ;

RETURN NULL;

END;

$$

LANGUAGE plpgsql;

 

CREATE TRIGGER quotes_insert_trigger

BEFORE INSERT ON quotes

FOR EACH ROW EXECUTE PROCEDURE quotes_insert_trigger();

 

 

Thanks

Charles
--
Sent via pgsql-performance mailing list (pgsql-***@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-performance
Scott Marlowe
2012-12-20 17:39:25 UTC
Permalink
Post by Charles Gomes
Hello guys
I’m doing 1.2 Billion inserts into a table partitioned in
15.
When I target the MASTER table on all the inserts and let
the trigger decide what partition to choose from it takes 4 hours.
If I target the partitioned table directly during the
insert I can get 4 times better performance. It takes 1 hour.
I’m trying to get more performance while still using the
trigger to choose the table, so partitions can be changed without changing the
application that inserts the data.
What I noticed that iostat is not showing an I/O bottle
neck.
SNIP
Post by Charles Gomes
I also don’t see a CPU bottleneck or context switching
bottle neck.
Are you sure? How are you measuring CPU usage? If you've got > 1
core, you might need to look at individual cores in which case you
should see a single core maxed out.

Without writing your trigger in C you're not likely to do much better
than you're doing now.
--
Sent via pgsql-performance mailing list (pgsql-***@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-performance
Charles Gomes
2012-12-20 18:55:29 UTC
Permalink
None of the cores went to 100%. Looking at top during the inserts I can see several cores working, but never more than 60% busy. The machine has 8 cores (16 in HT).
The load is spread through the cores, didn't have a single maxed out. However with HT on, technically it is overloaded.

top - 13:14:07 up 7 days, 3:10, 3 users, load average: 0.25, 0.12, 0.10
Tasks: 871 total, 13 running, 858 sleeping, 0 stopped, 0 zombie
Cpu(s): 60.6%us, 5.0%sy, 0.0%ni, 34.1%id, 0.0%wa, 0.0%hi, 0.2%si, 0.0%st
Mem: 49282716k total, 9311612k used, 39971104k free, 231116k buffers
Swap: 44354416k total, 171308k used, 44183108k free, 2439608k cached

PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
21832 postgres 20 0 22.7g 93m 90m S 15 0.2 0:19.91 postgres: cgomes historical_ticks 10.254.109.10(44093) COPY
21817 postgres 20 0 22.7g 92m 89m S 15 0.2 0:20.24 postgres: cgomes historical_ticks 10.254.109.10(44077) idle
21842 postgres 20 0 22.7g 96m 93m S 15 0.2 0:20.39 postgres: cgomes historical_ticks 10.254.109.10(44103) COPY
21792 postgres 20 0 22.7g 93m 90m R 15 0.2 0:20.34 postgres: cgomes historical_ticks 10.254.109.10(44045) COPY
21793 postgres 20 0 22.7g 90m 88m S 15 0.2 0:20.13 postgres: cgomes historical_ticks 10.254.109.10(44048) COPY
21806 postgres 20 0 22.7g 94m 91m S 15 0.2 0:20.14 postgres: cgomes historical_ticks 10.254.109.10(44066) COPY
21809 postgres 20 0 22.7g 92m 89m S 15 0.2 0:19.82 postgres: cgomes historical_ticks 10.254.109.10(44069) COPY
21813 postgres 20 0 22.7g 92m 89m S 15 0.2 0:19.98 postgres: cgomes historical_ticks 10.254.109.10(44073) COPY
21843 postgres 20 0 22.7g 95m 92m S 15 0.2 0:20.56 postgres: cgomes historical_ticks 10.254.109.10(44104) COPY
21854 postgres 20 0 22.7g 91m 88m S 15 0.2 0:20.08 postgres: cgomes historical_ticks 10.254.109.10(44114) COPY
21796 postgres 20 0 22.7g 89m 86m S 14 0.2 0:20.03 postgres: cgomes historical_ticks 10.254.109.10(44056) COPY
21797 postgres 20 0 22.7g 92m 90m R 14 0.2 0:20.18 postgres: cgomes historical_ticks 10.254.109.10(44057) COPY
21804 postgres 20 0 22.7g 95m 92m S 14 0.2 0:20.28 postgres: cgomes historical_ticks 10.254.109.10(44064) COPY
21807 postgres 20 0 22.7g 94m 91m S 14 0.2 0:20.15 postgres: cgomes historical_ticks 10.254.109.10(44067) COPY
21808 postgres 20 0 22.7g 92m 89m S 14 0.2 0:20.05 postgres: cgomes historical_ticks 10.254.109.10(44068) COPY
21815 postgres 20 0 22.7g 90m 88m S 14 0.2 0:20.13 postgres: cgomes historical_ticks 10.254.109.10(44075) COPY
21818 postgres 20 0 22.7g 91m 88m S 14 0.2 0:20.01 postgres: cgomes historical_ticks 10.254.109.10(44078) COPY
21825 postgres 20 0 22.7g 92m 89m S 14 0.2 0:20.00 postgres: cgomes historical_ticks 10.254.109.10(44085) COPY
21836 postgres 20 0 22.7g 91m 88m R 14 0.2 0:20.22 postgres: cgomes historical_ticks 10.254.109.10(44097) COPY
21857 postgres 20 0 22.7g 89m 86m R 14 0.2 0:19.92 postgres: cgomes historical_ticks 10.254.109.10(44118) COPY
21858 postgres 20 0 22.7g 95m 93m S 14 0.2 0:20.36 postgres: cgomes historical_ticks 10.254.109.10(44119) COPY
21789 postgres 20 0 22.7g 92m 89m S 14 0.2 0:20.05 postgres: cgomes historical_ticks 10.254.109.10(44044) COPY
21795 postgres 20 0 22.7g 93m 90m S 14 0.2 0:20.27 postgres: cgomes historical_ticks 10.254.109.10(44055) COPY
21798 postgres 20 0 22.7g 89m 86m S 14 0.2 0:20.06 postgres: cgomes historical_ticks 10.254.109.10(44058) COPY
21800 postgres 20 0 22.7g 93m 90m S 14 0.2 0:20.04 postgres: cgomes historical_ticks 10.254.109.10(44060) COPY
21802 postgres 20 0 22.7g 89m 87m S 14 0.2 0:20.10 postgres: cgomes historical_ticks 10.254.109.10(44062) COPY


Looks like I will have to disable HT.


I've been looking at converting the trigger to C, but could not find
a good example trigger for partitions written in C to start from. Have
you heard of anyone implementing the partitioning trigger in C ?

----------------------------------------
Date: Thu, 20 Dec 2012 10:39:25 -0700
Subject: Re: [PERFORM] Performance on Bulk Insert to Partitioned Table
Post by Charles Gomes
Hello guys
I’m doing 1.2 Billion inserts into a table partitioned in
15.
When I target the MASTER table on all the inserts and let
the trigger decide what partition to choose from it takes 4 hours.
If I target the partitioned table directly during the
insert I can get 4 times better performance. It takes 1 hour.
I’m trying to get more performance while still using the
trigger to choose the table, so partitions can be changed without changing the
application that inserts the data.
What I noticed that iostat is not showing an I/O bottle
neck.
SNIP
Post by Charles Gomes
I also don’t see a CPU bottleneck or context switching
bottle neck.
Are you sure? How are you measuring CPU usage? If you've got > 1
core, you might need to look at individual cores in which case you
should see a single core maxed out.
Without writing your trigger in C you're not likely to do much better
than you're doing now.
--
http://www.postgresql.org/mailpref/pgsql-performance
--
Sent via pgsql-performance mailing list (pgsql-***@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-performance
Charles Gomes
2012-12-20 20:08:33 UTC
Permalink
Without hyperthreading CPU still not a bottleneck, while I/O is only 10% utilization.

top - 14:55:01 up 27 min,  2 users,  load average: 0.17, 0.19, 0.14
Tasks: 614 total,  17 running, 597 sleeping,   0 stopped,   0 zombie
Cpu(s): 73.8%us,  4.3%sy,  0.0%ni, 21.6%id,  0.1%wa,  0.0%hi,  0.1%si,  0.0%st
Mem:  49282716k total,  5855492k used, 43427224k free,    37400k buffers
Swap: 44354416k total,        0k used, 44354416k free,  1124900k cached

  PID USER      PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND
19903 postgres  20   0 22.7g  34m  32m S  9.6  0.1   0:02.66 postgres: cgomes historical_ticks 10.254.109.10(46103) COPY
19934 postgres  20   0 22.7g  34m  32m S  9.6  0.1   0:02.61 postgres: cgomes historical_ticks 10.254.109.10(46134) COPY
19947 postgres  20   0 22.7g  34m  31m S  9.6  0.1   0:02.64 postgres: cgomes historical_ticks 10.254.109.10(46147) COPY
19910 postgres  20   0 22.7g  34m  32m S  9.2  0.1   0:02.67 postgres: cgomes historical_ticks 10.254.109.10(46110) COPY
19924 postgres  20   0 22.7g  33m  31m S  9.2  0.1   0:02.65 postgres: cgomes historical_ticks 10.254.109.10(46124) COPY
19952 postgres  20   0 22.7g  34m  32m R  9.2  0.1   0:02.71 postgres: cgomes historical_ticks 10.254.109.10(46152) COPY
19964 postgres  20   0 22.7g  34m  32m R  9.2  0.1   0:02.59 postgres: cgomes historical_ticks 10.254.109.10(46164) COPY
19901 postgres  20   0 22.7g  35m  32m S  8.9  0.1   0:02.66 postgres: cgomes historical_ticks 10.254.109.10(46101) COPY
19914 postgres  20   0 22.7g  34m  31m S  8.9  0.1   0:02.62 postgres: cgomes historical_ticks 10.254.109.10(46114) COPY
19923 postgres  20   0 22.7g  34m  31m S  8.9  0.1   0:02.74 postgres: cgomes historical_ticks 10.254.109.10(46123) COPY
19925 postgres  20   0 22.7g  34m  31m R  8.9  0.1   0:02.65 postgres: cgomes historical_ticks 10.254.109.10(46125) COPY
19926 postgres  20   0 22.7g  34m  32m S  8.9  0.1   0:02.79 postgres: cgomes historical_ticks 10.254.109.10(46126) COPY
19929 postgres  20   0 22.7g  34m  31m S  8.9  0.1   0:02.64 postgres: cgomes historical_ticks 10.254.109.10(46129) COPY
19936 postgres  20   0 22.7g  34m  32m S  8.9  0.1   0:02.72 postgres: cgomes historical_ticks 10.254.109.10(46136) COPY

I believe the bottleneck may be that pgsql has fight with it's siblings to update the indexes. Is there a way good way to add probes to check where things are slowing down ?


----------------------------------------
Subject: Re: [PERFORM] Performance on Bulk Insert to Partitioned Table
Date: Thu, 20 Dec 2012 13:55:29 -0500
None of the cores went to 100%. Looking at top during the inserts I can see several cores working, but never more than 60% busy. The machine has 8 cores (16 in HT).
The load is spread through the cores, didn't have a single maxed out. However with HT on, technically it is overloaded.
top - 13:14:07 up 7 days, 3:10, 3 users, load average: 0.25, 0.12, 0.10
Tasks: 871 total, 13 running, 858 sleeping, 0 stopped, 0 zombie
Cpu(s): 60.6%us, 5.0%sy, 0.0%ni, 34.1%id, 0.0%wa, 0.0%hi, 0.2%si, 0.0%st
Mem: 49282716k total, 9311612k used, 39971104k free, 231116k buffers
Swap: 44354416k total, 171308k used, 44183108k free, 2439608k cached
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
21832 postgres 20 0 22.7g 93m 90m S 15 0.2 0:19.91 postgres: cgomes historical_ticks 10.254.109.10(44093) COPY
21817 postgres 20 0 22.7g 92m 89m S 15 0.2 0:20.24 postgres: cgomes historical_ticks 10.254.109.10(44077) idle
21842 postgres 20 0 22.7g 96m 93m S 15 0.2 0:20.39 postgres: cgomes historical_ticks 10.254.109.10(44103) COPY
21792 postgres 20 0 22.7g 93m 90m R 15 0.2 0:20.34 postgres: cgomes historical_ticks 10.254.109.10(44045) COPY
21793 postgres 20 0 22.7g 90m 88m S 15 0.2 0:20.13 postgres: cgomes historical_ticks 10.254.109.10(44048) COPY
21806 postgres 20 0 22.7g 94m 91m S 15 0.2 0:20.14 postgres: cgomes historical_ticks 10.254.109.10(44066) COPY
21809 postgres 20 0 22.7g 92m 89m S 15 0.2 0:19.82 postgres: cgomes historical_ticks 10.254.109.10(44069) COPY
21813 postgres 20 0 22.7g 92m 89m S 15 0.2 0:19.98 postgres: cgomes historical_ticks 10.254.109.10(44073) COPY
21843 postgres 20 0 22.7g 95m 92m S 15 0.2 0:20.56 postgres: cgomes historical_ticks 10.254.109.10(44104) COPY
21854 postgres 20 0 22.7g 91m 88m S 15 0.2 0:20.08 postgres: cgomes historical_ticks 10.254.109.10(44114) COPY
21796 postgres 20 0 22.7g 89m 86m S 14 0.2 0:20.03 postgres: cgomes historical_ticks 10.254.109.10(44056) COPY
21797 postgres 20 0 22.7g 92m 90m R 14 0.2 0:20.18 postgres: cgomes historical_ticks 10.254.109.10(44057) COPY
21804 postgres 20 0 22.7g 95m 92m S 14 0.2 0:20.28 postgres: cgomes historical_ticks 10.254.109.10(44064) COPY
21807 postgres 20 0 22.7g 94m 91m S 14 0.2 0:20.15 postgres: cgomes historical_ticks 10.254.109.10(44067) COPY
21808 postgres 20 0 22.7g 92m 89m S 14 0.2 0:20.05 postgres: cgomes historical_ticks 10.254.109.10(44068) COPY
21815 postgres 20 0 22.7g 90m 88m S 14 0.2 0:20.13 postgres: cgomes historical_ticks 10.254.109.10(44075) COPY
21818 postgres 20 0 22.7g 91m 88m S 14 0.2 0:20.01 postgres: cgomes historical_ticks 10.254.109.10(44078) COPY
21825 postgres 20 0 22.7g 92m 89m S 14 0.2 0:20.00 postgres: cgomes historical_ticks 10.254.109.10(44085) COPY
21836 postgres 20 0 22.7g 91m 88m R 14 0.2 0:20.22 postgres: cgomes historical_ticks 10.254.109.10(44097) COPY
21857 postgres 20 0 22.7g 89m 86m R 14 0.2 0:19.92 postgres: cgomes historical_ticks 10.254.109.10(44118) COPY
21858 postgres 20 0 22.7g 95m 93m S 14 0.2 0:20.36 postgres: cgomes historical_ticks 10.254.109.10(44119) COPY
21789 postgres 20 0 22.7g 92m 89m S 14 0.2 0:20.05 postgres: cgomes historical_ticks 10.254.109.10(44044) COPY
21795 postgres 20 0 22.7g 93m 90m S 14 0.2 0:20.27 postgres: cgomes historical_ticks 10.254.109.10(44055) COPY
21798 postgres 20 0 22.7g 89m 86m S 14 0.2 0:20.06 postgres: cgomes historical_ticks 10.254.109.10(44058) COPY
21800 postgres 20 0 22.7g 93m 90m S 14 0.2 0:20.04 postgres: cgomes historical_ticks 10.254.109.10(44060) COPY
21802 postgres 20 0 22.7g 89m 87m S 14 0.2 0:20.10 postgres: cgomes historical_ticks 10.254.109.10(44062) COPY
Looks like I will have to disable HT.
I've been looking at converting the trigger to C, but could not find
a good example trigger for partitions written in C to start from. Have
you heard of anyone implementing the partitioning trigger in C ?
----------------------------------------
Date: Thu, 20 Dec 2012 10:39:25 -0700
Subject: Re: [PERFORM] Performance on Bulk Insert to Partitioned Table
Post by Charles Gomes
Hello guys
I’m doing 1.2 Billion inserts into a table partitioned in
15.
When I target the MASTER table on all the inserts and let
the trigger decide what partition to choose from it takes 4 hours.
If I target the partitioned table directly during the
insert I can get 4 times better performance. It takes 1 hour.
I’m trying to get more performance while still using the
trigger to choose the table, so partitions can be changed without changing the
application that inserts the data.
What I noticed that iostat is not showing an I/O bottle
neck.
SNIP
Post by Charles Gomes
I also don’t see a CPU bottleneck or context switching
bottle neck.
Are you sure? How are you measuring CPU usage? If you've got > 1
core, you might need to look at individual cores in which case you
should see a single core maxed out.
Without writing your trigger in C you're not likely to do much better
than you're doing now.
--
http://www.postgresql.org/mailpref/pgsql-performance
--
http://www.postgresql.org/mailpref/pgsql-performance
--
Sent via pgsql-performance mailing list (pgsql-***@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-performance
Jeff Janes
2012-12-23 22:55:15 UTC
Permalink
Post by Charles Gomes
Without hyperthreading CPU still not a bottleneck, while I/O is only 10% utilization.
top - 14:55:01 up 27 min, 2 users, load average: 0.17, 0.19, 0.14
Tasks: 614 total, 17 running, 597 sleeping, 0 stopped, 0 zombie
Cpu(s): 73.8%us, 4.3%sy, 0.0%ni, 21.6%id, 0.1%wa, 0.0%hi, 0.1%si,
0.0%st
...
Post by Charles Gomes
I believe the bottleneck may be that pgsql has fight with it's siblings to
update the indexes.
I think that should mostly show up as idle or wait, not as user time.
Since your at 75% user already, you couldn't expect more than a 33%
improvement by fixing that, assuming that that were the problem.
Post by Charles Gomes
Is there a way good way to add probes to check where things are slowing
down ?

What happens if you just drop the indexes as an experiment? That should
put a hard limit on the amount the indexes can be slowing you down.

I like oprofile to give you good bottom level profile of where CPU time is
going. Unfortunately, it will probably just show you "AllocSetAlloc".
Also, I don't trust it on virtualized systems, if you are on one of those.

There are many other ways of approaching it, but mostly you have to already
have a good theory about what is going on in order know which one to use or
to interpret the results, and many of them require you to make custom
compiles of the postgres server code.


Cheers,

Jeff
Stephen Frost
2012-12-20 20:02:34 UTC
Permalink
Charles,
I’m doing 1.2 Billion inserts into a table partitioned in
15.
Do you end up having multiple threads writing to the same, underlying,
tables..? If so, I've seen that problem before. Look at pg_locks while
things are running and see if there are 'extend' locks that aren't being
immediately granted.

Basically, there's a lock that PG has on a per-relation basis to extend
the relation (by a mere 8K..) which will block other writers. If
there's a lot of contention around that lock, you'll get poor
performance and it'll be faster to have independent threads writing
directly to the underlying tables. I doubt rewriting the trigger in C
will help if the problem is the extent lock.

If you do get this working well, I'd love to hear what you did to
accomplish that. Note also that you can get bottle-necked on the WAL
data, unless you've taken steps to avoid that WAL.

Thanks,

Stephen
Charles Gomes
2012-12-20 22:43:22 UTC
Permalink
Yes, I'm doing multiple threads inserting to the same tables.
I don't think the WAL is the issue as I even tried going ASYNC (non acid), disabled sync after writes, however still didn't got able to push full performance.

I've checked the locks and I see lots of ExclusiveLock's with:
select  * from pg_locks order by mode


   locktype    | database | relation | page | tuple | virtualxid | transactionid | classid | objid | objsubid | virtualtransaction |  pid  |           mode           | granted | fastpath
---------------+----------+----------+------+-------+------------+---------------+---------+-------+----------+--------------------+-------+--------------------------+---------+----------
 relation      |    16385 |    19295 |      |       |            |               |         |       |          | 72/18              | 19879 | AccessShareLock          | t       | t
 relation      |    16385 |    11069 |      |       |            |               |         |       |          | 76/32              | 19881 | AccessShareLock          | t       | t
 virtualxid    |          |          |      |       | 56/34      |               |         |       |          | 56/34              | 17952 | ExclusiveLock            | t       | t
 virtualxid    |          |          |      |       | 27/33      |               |         |       |          | 27/33              | 17923 | ExclusiveLock            | t       | t
 virtualxid    |          |          |      |       | 6/830      |               |         |       |          | 6/830              | 17902 | ExclusiveLock            | t       | t
 virtualxid    |          |          |      |       | 62/34      |               |         |       |          | 62/34              | 17959 | ExclusiveLock            | t       | t
 virtualxid    |          |          |      |       | 51/34      |               |         |       |          | 51/34              | 17947 | ExclusiveLock            | t       | t
 virtualxid    |          |          |      |       | 36/34      |               |         |       |          | 36/34              | 17932 | ExclusiveLock            | t       | t
 virtualxid    |          |          |      |       | 10/830     |               |         |       |          | 10/830             | 17906 |
.................(about 56 of those)
ExclusiveLock            | t       | t
transactionid |          |          |      |       |            |         30321 |         |       |          | 55/33              | 17951 | ExclusiveLock            | t       | f
 transactionid |          |          |      |       |            |         30344 |         |       |          | 19/34              | 17912 | ExclusiveLock            | t       | f
 transactionid |          |          |      |       |            |         30354 |         |       |          | 3/834              | 17898 | ExclusiveLock            | t       | f
 transactionid |          |          |      |       |            |         30359 |         |       |          | 50/34              | 17946 | ExclusiveLock            | t       | f
 transactionid |          |          |      |       |            |         30332 |         |       |          | 9/830              | 17905 | ExclusiveLock            | t       | f
 transactionid |          |          |      |       |            |         30294 |         |       |          | 37/33              | 17933 | ExclusiveLock            | t       | f
 transactionid |          |          |      |       |            |         30351 |         |       |          | 38/34              | 17934 | ExclusiveLock            | t       | f
 transactionid |          |          |      |       |            |         30326 |         |       |          | 26/33              | 17922 | ExclusiveLock            | t       | f
.................(about 52 of those)
 relation      |    16385 |    19291 |      |       |            |               |         |       |          | 72/18              | 19879 | ShareUpdateExclusiveLock | t       | f
(3 of those)
 relation      |    16385 |    19313 |      |       |            |               |         |       |          | 33/758             | 17929 | RowExclusiveLock         | t       | t
(211 of those)


However I don't see any of the EXTEND locks mentioned.

I would give a try translating the trigger to C but I can't code it without a good sample to start from, if anyone has one and would like to share I would love to start from it and share with other people so everyone can benefit.

----------------------------------------
Date: Thu, 20 Dec 2012 15:02:34 -0500
Subject: Re: [PERFORM] Performance on Bulk Insert to Partitioned Table
Charles,
Post by Charles Gomes
I’m doing 1.2 Billion inserts into a table partitioned in
15.
Do you end up having multiple threads writing to the same, underlying,
tables..? If so, I've seen that problem before. Look at pg_locks while
things are running and see if there are 'extend' locks that aren't being
immediately granted.
Basically, there's a lock that PG has on a per-relation basis to extend
the relation (by a mere 8K..) which will block other writers. If
there's a lot of contention around that lock, you'll get poor
performance and it'll be faster to have independent threads writing
directly to the underlying tables. I doubt rewriting the trigger in C
will help if the problem is the extent lock.
If you do get this working well, I'd love to hear what you did to
accomplish that. Note also that you can get bottle-necked on the WAL
data, unless you've taken steps to avoid that WAL.
Thanks,
Stephen
--
Sent via pgsql-performance mailing list (pgsql-***@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-performance
Jeff Janes
2012-12-20 22:31:44 UTC
Permalink
Post by Charles Gomes
Hello guys
I’m doing 1.2 Billion inserts into a table partitioned in
15.
When I target the MASTER table on all the inserts and let
the trigger decide what partition to choose from it takes 4 hours.
If I target the partitioned table directly during the
insert I can get 4 times better performance. It takes 1 hour.
How do you target them directly? By implementing the
"trigger-equivalent-code" in the application code tuple by tuple, or
by pre-segregating the tuples and then bulk loading each segment to
its partition?

What if you get rid of the partitioning and just load data to the
master, is that closer to 4 hours or to 1 hour?

...
Post by Charles Gomes
What I noticed that iostat is not showing an I/O bottle
neck.
iostat –xN 1
rrqm/s wrqm/s r/s
w/s rsec/s wsec/s avgrq-sz avgqu-sz
await svctm %util
Pgresql--data
0.00 0.00 0.00
8288.00 0.00 66304.00
8.00 60.92 7.35
0.01 4.30
8288 randomly scattered writes per second sound like enough to
bottleneck a pretty impressive RAID. Or am I misreading that?

Cheers,

Jeff
--
Sent via pgsql-performance mailing list (pgsql-***@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-performance
Charles Gomes
2012-12-20 22:56:24 UTC
Permalink
Jeff,

The 8288 writes are fine, as the array has a BBU, it's fine. You see about 4% of the utilization.

To target directly instead of doing :
INSERT INTO TABLE VALUES ()
I use:
INSERT INTO TABLE_PARTITION_01 VALUES()

By targeting it I see a huge performance increase.

I haven't tested using 1Billion rows in a single table. The issue is that in the future it will grow to more than 1 billion rows, it will get to about 4Billion rows and that's when I believe partition would be a major improvement.


----------------------------------------
Date: Thu, 20 Dec 2012 14:31:44 -0800
Subject: Re: [PERFORM] Performance on Bulk Insert to Partitioned Table
Post by Charles Gomes
Hello guys
I’m doing 1.2 Billion inserts into a table partitioned in
15.
When I target the MASTER table on all the inserts and let
the trigger decide what partition to choose from it takes 4 hours.
If I target the partitioned table directly during the
insert I can get 4 times better performance. It takes 1 hour.
How do you target them directly? By implementing the
"trigger-equivalent-code" in the application code tuple by tuple, or
by pre-segregating the tuples and then bulk loading each segment to
its partition?
What if you get rid of the partitioning and just load data to the
master, is that closer to 4 hours or to 1 hour?
...
Post by Charles Gomes
What I noticed that iostat is not showing an I/O bottle
neck.
iostat –xN 1
rrqm/s wrqm/s r/s
w/s rsec/s wsec/s avgrq-sz avgqu-sz
await svctm %util
Pgresql--data
0.00 0.00 0.00
8288.00 0.00 66304.00
8.00 60.92 7.35
0.01 4.30
8288 randomly scattered writes per second sound like enough to
bottleneck a pretty impressive RAID. Or am I misreading that?
Cheers,
Jeff
--
http://www.postgresql.org/mailpref/pgsql-performance
--
Sent via pgsql-performance mailing list (pgsql-***@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-performance
Jeff Janes
2012-12-21 03:24:09 UTC
Permalink
Post by Charles Gomes
Jeff,
The 8288 writes are fine, as the array has a BBU, it's fine. You see about
4% of the utilization.
BBU is great for latency, but it doesn't do much for throughput, unless it
is doing write combining behind the scenes. Is it HDD or SSD behind the
BBU? Have you bench-marked it on randomly scattered 8k writes?

I've seen %util reports that were low while watching a strace showed
obvious IO freezes. So I don't know how much faith to put into low %util.
Post by Charles Gomes
INSERT INTO TABLE VALUES ()
INSERT INTO TABLE_PARTITION_01 VALUES()
But how is it deciding what partition to use? Does it have to re-decide
for every row, or does each thread serve only one partition throughout its
life and so makes the decision only once?
Post by Charles Gomes
By targeting it I see a huge performance increase.
But is that because by targeting you are by-passing the the over-head of
triggers, or is it because you are loading the rows in an order which leads
to more efficient index maintenance?
Post by Charles Gomes
I haven't tested using 1Billion rows in a single table. The issue is that
in the future it will grow to more than 1 billion rows, it will get to
about 4Billion rows and that's when I believe partition would be a major
improvement.
The way that partitioning gives you performance improvements is by you
embracing the partitioning, for example by targeting the loading to just
one partition without any indexes, creating indexes, and then atomically
attaching it to the table. If you wish to have partitions, but want to use
triggers to hide that partitioning from you, then I don't think you can
expect to get much of a speed up through using partitions.

Any way, the way I would approach it would be to load to a single
un-partitioned table, and also load to a single dummy-partitioned table
which uses a trigger that looks like the one you want to use for real, but
directs all rows to a single partition. If these loads take the same time,
you know it is not the trigger which is limiting.

Cheers,

Jeff
Charles Gomes
2012-12-21 14:30:07 UTC
Permalink
The BBU does combines the writes.

I've benchmarked using a single table and it took 1:34:21.549959 to insert 1188000000 rows. (70 writers to a single table)

I've also benchmarked having writers targeting individual partitions and they get the same job done in 1 Hour.

I/O is definitely not the botleneck.

Without changing hardware it accelerates things almost 4 times, looks like to be a delay on the way Postgresql handles the partitions or the time taking for the trigger to select what partition to insert.


When targeting I issue commands that insert directly into the partition "INSERT INTO quotes_DATE VALUES() ..,..,...,.., " 10k rows at time.
When not targeting I leave to the trigger to decide:



CREATE OR REPLACE FUNCTION quotes_insert_trigger()RETURNS trigger AS $$

DECLARE

tablename varchar(24);

bdate varchar(10);

edate varchar(10);

BEGIN

tablename = 'quotes_' || to_char(new.received_time,'YYYY_MM_DD');

EXECUTE 'INSERT INTO '|| tablename ||' VALUES (($1).*)'
USING NEW ;

RETURN NULL;

END;

$$

LANGUAGE plpgsql;


Maybe translating this trigger to C could help. But I haven't heart anyone that did use partitioning with a trigger in C and I don't have the know how on it without examples.

________________________________
Date: Thu, 20 Dec 2012 19:24:09 -0800
Subject: Re: [PERFORM] Performance on Bulk Insert to Partitioned Table
Jeff,
The 8288 writes are fine, as the array has a BBU, it's fine. You see
about 4% of the utilization.
BBU is great for latency, but it doesn't do much for throughput, unless
it is doing write combining behind the scenes. Is it HDD or SSD behind
the BBU? Have you bench-marked it on randomly scattered 8k writes?
I've seen %util reports that were low while watching a strace showed
obvious IO freezes. So I don't know how much faith to put into low
%util.
INSERT INTO TABLE VALUES ()
INSERT INTO TABLE_PARTITION_01 VALUES()
But how is it deciding what partition to use? Does it have to
re-decide for every row, or does each thread serve only one partition
throughout its life and so makes the decision only once?
By targeting it I see a huge performance increase.
But is that because by targeting you are by-passing the the over-head
of triggers, or is it because you are loading the rows in an order
which leads to more efficient index maintenance?
I haven't tested using 1Billion rows in a single table. The issue is
that in the future it will grow to more than 1 billion rows, it will
get to about 4Billion rows and that's when I believe partition would be
a major improvement.
The way that partitioning gives you performance improvements is by you
embracing the partitioning, for example by targeting the loading to
just one partition without any indexes, creating indexes, and then
atomically attaching it to the table. If you wish to have partitions,
but want to use triggers to hide that partitioning from you, then I
don't think you can expect to get much of a speed up through using
partitions.
Any way, the way I would approach it would be to load to a single
un-partitioned table, and also load to a single dummy-partitioned table
which uses a trigger that looks like the one you want to use for real,
but directs all rows to a single partition. If these loads take the
same time, you know it is not the trigger which is limiting.
Cheers,
Jeff
--
Sent via pgsql-performance mailing list (pgsql-***@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-performance
Ondrej Ivanič
2012-12-20 22:50:49 UTC
Permalink
Hi,
Post by Charles Gomes
When I target the MASTER table on all the inserts and let
the trigger decide what partition to choose from it takes 4 hours.
If I target the partitioned table directly during the
insert I can get 4 times better performance. It takes 1 hour.
Yes, that's my experience as well. Triggers are the slowest.
Performance of "DO INSTEAD" rule is close to direct inserts but rule
setup is complex (each partition needs one):

create or replace rule <master_table>_insert_<partition_name> as on
insert to <master_table>
where new.<part_column> >= ... and
new.<part_column> < ....
do instead
insert into <master_table>_<partition_name>
values (new.*)

The best is used to direct inserts (into partition) if you can.

--
Ondrej Ivanic
(http://www.linkedin.com/in/ondrejivanic)
--
Sent via pgsql-performance mailing list (pgsql-***@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-performance
Charles Gomes
2012-12-20 22:59:18 UTC
Permalink
True, that's the same I feel, I will be looking to translate the trigger to C if I can find good examples, that should accelerate.
Using rules would be totally bad as I'm partitioning daily and after one year having 365 lines of IF won't be fun to maintain.

----------------------------------------
Date: Fri, 21 Dec 2012 09:50:49 +1100
Subject: Re: [PERFORM] Performance on Bulk Insert to Partitioned Table
Hi,
Post by Charles Gomes
When I target the MASTER table on all the inserts and let
the trigger decide what partition to choose from it takes 4 hours.
If I target the partitioned table directly during the
insert I can get 4 times better performance. It takes 1 hour.
Yes, that's my experience as well. Triggers are the slowest.
Performance of "DO INSTEAD" rule is close to direct inserts but rule
create or replace rule <master_table>_insert_<partition_name> as on
insert to <master_table>
where new.<part_column> >= ... and
new.<part_column> < ....
do instead
insert into <master_table>_<partition_name>
values (new.*)
The best is used to direct inserts (into partition) if you can.
--
Ondrej Ivanic
(http://www.linkedin.com/in/ondrejivanic)
--
http://www.postgresql.org/mailpref/pgsql-performance
--
Sent via pgsql-performance mailing list (pgsql-***@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-performance
Tom Lane
2012-12-20 23:39:07 UTC
Permalink
Post by Charles Gomes
Using rules would be totally bad as I'm partitioning daily and after one year having 365 lines of IF won't be fun to maintain.
You should probably rethink that plan anyway. The existing support for
partitioning is not meant to support hundreds of partitions; you're
going to be bleeding performance in a lot of places if you insist on
doing that.

regards, tom lane
--
Sent via pgsql-performance mailing list (pgsql-***@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-performance
Scott Marlowe
2012-12-21 00:19:52 UTC
Permalink
Post by Tom Lane
Post by Charles Gomes
Using rules would be totally bad as I'm partitioning daily and after one year having 365 lines of IF won't be fun to maintain.
You should probably rethink that plan anyway. The existing support for
partitioning is not meant to support hundreds of partitions; you're
going to be bleeding performance in a lot of places if you insist on
doing that.
A couple of points:

1: In my experience hundreds is OK performance wise, but as you
approach thousands you fall off a cliff, and performance is terrible.
So at the 3 to 4 year mark daily partition tables will definitely be
having problems.

2: A good way around this is to have partitions for the last x days,
last x weeks or months before that, and x years going however far
back. This keeps the number of partitions low. Just dump the oldest
day into a weekly partition, til the next week starts, then dump the
oldest week into monthly etc. As long as you have lower traffic times
of day or enough bandwidth it works pretty well. Or you can just use
daily partitions til things start going boom and fix it all at a later
date. It's probably better to be proactive tho.

3: Someone above mentioned rules being faster than triggers. In my
experience they're WAY slower than triggers but maybe that was just on
the older pg versions (8.3 and lower) we were doing this on. I'd be
interested in seeing some benchmarks if rules have gotten faster or I
was just doing it wrong.
--
Sent via pgsql-performance mailing list (pgsql-***@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-performance
Jeff Janes
2012-12-28 16:30:30 UTC
Permalink
Post by Scott Marlowe
3: Someone above mentioned rules being faster than triggers. In my
experience they're WAY slower than triggers but maybe that was just on
the older pg versions (8.3 and lower) we were doing this on. I'd be
interested in seeing some benchmarks if rules have gotten faster or I
was just doing it wrong.
It apparently depends on how you use them.

To load 1e6 rows into the parent, redistributing to 100 partitions (rows
evenly distributed over partitions) using RULEs, it took 14.5 seconds using
a "insert into foo select * from foo_tmp" (not counting the time it took to
prepopulate the foo_tmp via \copy).

This is about 25% faster than the 18.4 seconds it took to load the same
data via \copy using a plpgsql trigger which was structured with nested IF
... ELSE...END IF that do a binary search over the partitions.

However if I didn't use \copy or "insert into...select", but rather used a
Perl loop invoking normal single-row inserts (but all in a single
transaction) with DBD::Pg, then the RULEs took 596 seconds, an astonishing
seven times slower than the 83 seconds it took the previously mentioned
plpgsql trigger to do the same thing.

This was under 9.1.7.

In 9.2.2, it seems to get 3 times worse yet for RULEs in the insert loop.
But that result seems hard to believe, so I am repeating it.

Cheers

Jeff
Luciano Ernesto da Silva
2012-12-28 18:00:29 UTC
Permalink
UNSUBSCRIBE



De: pgsql-performance-***@postgresql.org [mailto:pgsql-performance-***@postgresql.org] Em nome de Jeff Janes
Enviada em: sexta-feira, 28 de dezembro de 2012 14:31
Para: Scott Marlowe
Cc: Tom Lane; Charles Gomes; Ondrej Ivanič; pgsql-***@postgresql.org
Assunto: Re: [PERFORM] Performance on Bulk Insert to Partitioned Table





On Thursday, December 20, 2012, Scott Marlowe wrote:


3: Someone above mentioned rules being faster than triggers. In my
experience they're WAY slower than triggers but maybe that was just on
the older pg versions (8.3 and lower) we were doing this on. I'd be
interested in seeing some benchmarks if rules have gotten faster or I
was just doing it wrong.



It apparently depends on how you use them.



To load 1e6 rows into the parent, redistributing to 100 partitions (rows evenly distributed over partitions) using RULEs, it took 14.5 seconds using a "insert into foo select * from foo_tmp" (not counting the time it took to prepopulate the foo_tmp via \copy).



This is about 25% faster than the 18.4 seconds it took to load the same data via \copy using a plpgsql trigger which was structured with nested IF ... ELSE...END IF that do a binary search over the partitions.

However if I didn't use \copy or "insert into...select", but rather used a Perl loop invoking normal single-row inserts (but all in a single transaction) with DBD::Pg, then the RULEs took 596 seconds, an astonishing seven times slower than the 83 seconds it took the previously mentioned plpgsql trigger to do the same thing.



This was under 9.1.7.



In 9.2.2, it seems to get 3 times worse yet for RULEs in the insert loop. But that result seems hard to believe, so I am repeating it.



Cheers



Jeff
Pavel Stehule
2012-12-28 18:05:04 UTC
Permalink
Hello
Post by Luciano Ernesto da Silva
UNSUBSCRIBE
Enviada em: sexta-feira, 28 de dezembro de 2012 14:31
Para: Scott Marlowe
Assunto: Re: [PERFORM] Performance on Bulk Insert to Partitioned Table
3: Someone above mentioned rules being faster than triggers. In my
experience they're WAY slower than triggers but maybe that was just on
the older pg versions (8.3 and lower) we were doing this on. I'd be
interested in seeing some benchmarks if rules have gotten faster or I
was just doing it wrong.
I am not sure, but I expect so speed or slowness of rules depends
primary on number of partitions. More significantly than triggers.

Regards

Pavel
Post by Luciano Ernesto da Silva
It apparently depends on how you use them.
To load 1e6 rows into the parent, redistributing to 100 partitions (rows
evenly distributed over partitions) using RULEs, it took 14.5 seconds using
a "insert into foo select * from foo_tmp" (not counting the time it took to
prepopulate the foo_tmp via \copy).
This is about 25% faster than the 18.4 seconds it took to load the same data
via \copy using a plpgsql trigger which was structured with nested IF ...
ELSE...END IF that do a binary search over the partitions.
However if I didn't use \copy or "insert into...select", but rather used a
Perl loop invoking normal single-row inserts (but all in a single
transaction) with DBD::Pg, then the RULEs took 596 seconds, an astonishing
seven times slower than the 83 seconds it took the previously mentioned
plpgsql trigger to do the same thing.
This was under 9.1.7.
In 9.2.2, it seems to get 3 times worse yet for RULEs in the insert loop.
But that result seems hard to believe, so I am repeating it.
Cheers
Jeff
--
Sent via pgsql-performance mailing list (pgsql-***@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-performance
Charles Gomes
2012-12-21 14:14:46 UTC
Permalink
Tom, I may have to rethink it, so I'm going to have about 100 Million rows per day (5 days a week) 2 Billion per month. My point on partitioning was to be able to store 6 months of data in a single machine. About 132 partitions in a total of 66 billion rows.


----------------------------------------
Subject: Re: [PERFORM] Performance on Bulk Insert to Partitioned Table
Date: Thu, 20 Dec 2012 18:39:07 -0500
Post by Charles Gomes
Using rules would be totally bad as I'm partitioning daily and after one year having 365 lines of IF won't be fun to maintain.
You should probably rethink that plan anyway. The existing support for
partitioning is not meant to support hundreds of partitions; you're
going to be bleeding performance in a lot of places if you insist on
doing that.
regards, tom lane
--
http://www.postgresql.org/mailpref/pgsql-performance
--
Sent via pgsql-performance mailing list (pgsql-***@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-performance
Jeff Janes
2012-12-23 22:55:16 UTC
Permalink
Post by Charles Gomes
True, that's the same I feel, I will be looking to translate the trigger
to C if I can find good examples, that should accelerate.
I think your performance bottleneck is almost certainly the dynamic SQL.
Using C to generate that dynamic SQL isn't going to help much, because it
is still the SQL engine that has to parse, plan, and execute it.

Are the vast majority if your inserts done on any given day for records
from that same day or the one before; or are they evenly spread over the
preceding year? If the former, you could use static SQL in IF and ELSIF
for those days, and fall back on the dynamic SQL for the exceptions in the
ELSE block. Of course that means you have to update the trigger every day.
Post by Charles Gomes
Using rules would be totally bad as I'm partitioning daily and after one
year having 365 lines of IF won't be fun to maintain.
Maintaining 365 lines of IF is what Perl was invented for. That goes for
triggers w/ static SQL as well as for rules.

If you do the static SQL in a trigger and the dates of the records are
evenly scattered over the preceding year, make sure your IFs are nested
like a binary search, not a linear search. And if they are mostly for
"today's" date, then make sure you search backwards.

Cheers,

Jeff
Charles Gomes
2012-12-24 15:51:12 UTC
Permalink
________________________________
Date: Sun, 23 Dec 2012 14:55:16 -0800
Subject: Re: [PERFORM] Performance on Bulk Insert to Partitioned Table
True, that's the same I feel, I will be looking to translate the
trigger to C if I can find good examples, that should accelerate.
I think your performance bottleneck is almost certainly the dynamic
SQL. Using C to generate that dynamic SQL isn't going to help much,
because it is still the SQL engine that has to parse, plan, and execute
it.
Are the vast majority if your inserts done on any given day for records
from that same day or the one before; or are they evenly spread over
the preceding year? If the former, you could use static SQL in IF and
ELSIF for those days, and fall back on the dynamic SQL for the
exceptions in the ELSE block. Of course that means you have to update
the trigger every day.
Using rules would be totally bad as I'm partitioning daily and after
one year having 365 lines of IF won't be fun to maintain.
Maintaining 365 lines of IF is what Perl was invented for. That goes
for triggers w/ static SQL as well as for rules.
If you do the static SQL in a trigger and the dates of the records are
evenly scattered over the preceding year, make sure your IFs are nested
like a binary search, not a linear search. And if they are mostly for
"today's" date, then make sure you search backwards.
Cheers,
Jeff
Jeff, I've changed the code from dynamic to:

CREATE OR REPLACE FUNCTION quotes_insert_trigger()
RETURNS trigger AS $$
DECLARE
r_date text;
BEGIN
r_date = to_char(new.received_time, 'YYYY_MM_DD');
case r_date
    when '2012_09_10' then
        insert into quotes_2012_09_10 values (NEW.*) using new;
        return;
    when '2012_09_11' then
        insert into quotes_2012_09_11 values (NEW.*) using new;
        return;
    when '2012_09_12' then
        insert into quotes_2012_09_12 values (NEW.*) using new;
        return;
    when '2012_09_13' then
        insert into quotes_2012_09_13 values (NEW.*) using new;
        return;
    when '2012_09_14' then
        insert into quotes_2012_09_14 values (NEW.*) using new;
        return;
    when '2012_09_15' then
        insert into quotes_2012_09_15 values (NEW.*) using new;
        return;
    when '2012_09_16' then
        insert into quotes_2012_09_16 values (NEW.*) using new;
        return;
    when '2012_09_17' then
        insert into quotes_2012_09_17 values (NEW.*) using new;
        return;
    when '2012_09_18' then
        insert into quotes_2012_09_18 values (NEW.*) using new;
        return;
    when '2012_09_19' then
        insert into quotes_2012_09_19 values (NEW.*) using new;
        return;
    when '2012_09_20' then
        insert into quotes_2012_09_20 values (NEW.*) using new;
        return;
    when '2012_09_21' then
        insert into quotes_2012_09_21 values (NEW.*) using new;
        return;
    when '2012_09_22' then
        insert into quotes_2012_09_22 values (NEW.*) using new;
        return;
    when '2012_09_23' then
        insert into quotes_2012_09_23 values (NEW.*) using new;
        return;
    when '2012_09_24' then
        insert into quotes_2012_09_24 values (NEW.*) using new;
        return;
end case
RETURN NULL;
END;
$$
LANGUAGE plpgsql;


However I've got no speed improvement.
I need to keep two weeks worth of partitions at a time, that's why all the WHEN statements.
Wish postgres could automate the partition process natively like the other sql db.

Thank you guys for your help.
--
Sent via pgsql-performance mailing list (pgsql-***@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-performance
Charles Gomes
2012-12-24 17:07:09 UTC
Permalink
By the way, I've just re-wrote the code to target the partitions individually and I've got almost 4 times improvement.
Shouldn't it be faster to process the trigger, I would understand if there was no CPU left, but there is lots of cpu to chew.
It seems that there will be no other way to speedup unless the insert code is partition aware.

----------------------------------------
Subject: Re: [PERFORM] Performance on Bulk Insert to Partitioned Table
Date: Mon, 24 Dec 2012 10:51:12 -0500
________________________________
Date: Sun, 23 Dec 2012 14:55:16 -0800
Subject: Re: [PERFORM] Performance on Bulk Insert to Partitioned Table
True, that's the same I feel, I will be looking to translate the
trigger to C if I can find good examples, that should accelerate.
I think your performance bottleneck is almost certainly the dynamic
SQL. Using C to generate that dynamic SQL isn't going to help much,
because it is still the SQL engine that has to parse, plan, and execute
it.
Are the vast majority if your inserts done on any given day for records
from that same day or the one before; or are they evenly spread over
the preceding year? If the former, you could use static SQL in IF and
ELSIF for those days, and fall back on the dynamic SQL for the
exceptions in the ELSE block. Of course that means you have to update
the trigger every day.
Using rules would be totally bad as I'm partitioning daily and after
one year having 365 lines of IF won't be fun to maintain.
Maintaining 365 lines of IF is what Perl was invented for. That goes
for triggers w/ static SQL as well as for rules.
If you do the static SQL in a trigger and the dates of the records are
evenly scattered over the preceding year, make sure your IFs are nested
like a binary search, not a linear search. And if they are mostly for
"today's" date, then make sure you search backwards.
Cheers,
Jeff
CREATE OR REPLACE FUNCTION quotes_insert_trigger()
RETURNS trigger AS $$
DECLARE
r_date text;
BEGIN
r_date = to_char(new.received_time, 'YYYY_MM_DD');
case r_date
when '2012_09_10' then
insert into quotes_2012_09_10 values (NEW.*) using new;
return;
when '2012_09_11' then
insert into quotes_2012_09_11 values (NEW.*) using new;
return;
when '2012_09_12' then
insert into quotes_2012_09_12 values (NEW.*) using new;
return;
when '2012_09_13' then
insert into quotes_2012_09_13 values (NEW.*) using new;
return;
when '2012_09_14' then
insert into quotes_2012_09_14 values (NEW.*) using new;
return;
when '2012_09_15' then
insert into quotes_2012_09_15 values (NEW.*) using new;
return;
when '2012_09_16' then
insert into quotes_2012_09_16 values (NEW.*) using new;
return;
when '2012_09_17' then
insert into quotes_2012_09_17 values (NEW.*) using new;
return;
when '2012_09_18' then
insert into quotes_2012_09_18 values (NEW.*) using new;
return;
when '2012_09_19' then
insert into quotes_2012_09_19 values (NEW.*) using new;
return;
when '2012_09_20' then
insert into quotes_2012_09_20 values (NEW.*) using new;
return;
when '2012_09_21' then
insert into quotes_2012_09_21 values (NEW.*) using new;
return;
when '2012_09_22' then
insert into quotes_2012_09_22 values (NEW.*) using new;
return;
when '2012_09_23' then
insert into quotes_2012_09_23 values (NEW.*) using new;
return;
when '2012_09_24' then
insert into quotes_2012_09_24 values (NEW.*) using new;
return;
end case
RETURN NULL;
END;
$$
LANGUAGE plpgsql;
However I've got no speed improvement.
I need to keep two weeks worth of partitions at a time, that's why all the WHEN statements.
Wish postgres could automate the partition process natively like the other sql db.
Thank you guys for your help.
--
http://www.postgresql.org/mailpref/pgsql-performance
--
Sent via pgsql-performance mailing list (pgsql-***@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-performance
Evgeny Shishkin
2012-12-24 17:11:07 UTC
Permalink
Post by Charles Gomes
By the way, I've just re-wrote the code to target the partitions individually and I've got almost 4 times improvement.
Shouldn't it be faster to process the trigger, I would understand if there was no CPU left, but there is lots of cpu to chew.
I saw your 20% idle cpu and raise eyebrows.
Post by Charles Gomes
It seems that there will be no other way to speedup unless the insert code is partition aware.
----------------------------------------
Subject: Re: [PERFORM] Performance on Bulk Insert to Partitioned Table
Date: Mon, 24 Dec 2012 10:51:12 -0500
________________________________
Date: Sun, 23 Dec 2012 14:55:16 -0800
Subject: Re: [PERFORM] Performance on Bulk Insert to Partitioned Table
True, that's the same I feel, I will be looking to translate the
trigger to C if I can find good examples, that should accelerate.
I think your performance bottleneck is almost certainly the dynamic
SQL. Using C to generate that dynamic SQL isn't going to help much,
because it is still the SQL engine that has to parse, plan, and execute
it.
Are the vast majority if your inserts done on any given day for records
from that same day or the one before; or are they evenly spread over
the preceding year? If the former, you could use static SQL in IF and
ELSIF for those days, and fall back on the dynamic SQL for the
exceptions in the ELSE block. Of course that means you have to update
the trigger every day.
Using rules would be totally bad as I'm partitioning daily and after
one year having 365 lines of IF won't be fun to maintain.
Maintaining 365 lines of IF is what Perl was invented for. That goes
for triggers w/ static SQL as well as for rules.
If you do the static SQL in a trigger and the dates of the records are
evenly scattered over the preceding year, make sure your IFs are nested
like a binary search, not a linear search. And if they are mostly for
"today's" date, then make sure you search backwards.
Cheers,
Jeff
CREATE OR REPLACE FUNCTION quotes_insert_trigger()
RETURNS trigger AS $$
DECLARE
r_date text;
BEGIN
r_date = to_char(new.received_time, 'YYYY_MM_DD');
case r_date
when '2012_09_10' then
insert into quotes_2012_09_10 values (NEW.*) using new;
return;
when '2012_09_11' then
insert into quotes_2012_09_11 values (NEW.*) using new;
return;
when '2012_09_12' then
insert into quotes_2012_09_12 values (NEW.*) using new;
return;
when '2012_09_13' then
insert into quotes_2012_09_13 values (NEW.*) using new;
return;
when '2012_09_14' then
insert into quotes_2012_09_14 values (NEW.*) using new;
return;
when '2012_09_15' then
insert into quotes_2012_09_15 values (NEW.*) using new;
return;
when '2012_09_16' then
insert into quotes_2012_09_16 values (NEW.*) using new;
return;
when '2012_09_17' then
insert into quotes_2012_09_17 values (NEW.*) using new;
return;
when '2012_09_18' then
insert into quotes_2012_09_18 values (NEW.*) using new;
return;
when '2012_09_19' then
insert into quotes_2012_09_19 values (NEW.*) using new;
return;
when '2012_09_20' then
insert into quotes_2012_09_20 values (NEW.*) using new;
return;
when '2012_09_21' then
insert into quotes_2012_09_21 values (NEW.*) using new;
return;
when '2012_09_22' then
insert into quotes_2012_09_22 values (NEW.*) using new;
return;
when '2012_09_23' then
insert into quotes_2012_09_23 values (NEW.*) using new;
return;
when '2012_09_24' then
insert into quotes_2012_09_24 values (NEW.*) using new;
return;
end case
RETURN NULL;
END;
$$
LANGUAGE plpgsql;
However I've got no speed improvement.
I need to keep two weeks worth of partitions at a time, that's why all the WHEN statements.
Wish postgres could automate the partition process natively like the other sql db.
Thank you guys for your help.
--
http://www.postgresql.org/mailpref/pgsql-performance
--
http://www.postgresql.org/mailpref/pgsql-performance
--
Sent via pgsql-performance mailing list (pgsql-***@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-performance
Charles Gomes
2012-12-24 18:36:17 UTC
Permalink
I've just found this:
From:
http://archives.postgresql.org/pgsql-hackers/2008-12/msg01221.php

"initial tests to insert 140k rows are as follows:

- direct inserts in a child table: 2 seconds

- pgplsql trigger (IF ... ELSE IF ... blocks) : 14.5 seconds.

- C trigger: 4 seconds (actually the overhead is in the constraint check)

"

This is from 2008 and looks like at that time those folks where already having performance issues with partitions.

Going to copy some folks from the old thread, hopefully 4 years later they may have found a solution. Maybe they've moved on into something more exciting, maybe He is in another world where we don't have database servers. In special the brave Emmanuel for posting his trigger code that I will hack into my own :P
Thanks Emmanuel.






----------------------------------------
Subject: Re: [PERFORM] Performance on Bulk Insert to Partitioned Table
Date: Mon, 24 Dec 2012 21:11:07 +0400
Post by Charles Gomes
By the way, I've just re-wrote the code to target the partitions individually and I've got almost 4 times improvement.
Shouldn't it be faster to process the trigger, I would understand if there was no CPU left, but there is lots of cpu to chew.
I saw your 20% idle cpu and raise eyebrows.
Post by Charles Gomes
It seems that there will be no other way to speedup unless the insert code is partition aware.
----------------------------------------
Subject: Re: [PERFORM] Performance on Bulk Insert to Partitioned Table
Date: Mon, 24 Dec 2012 10:51:12 -0500
________________________________
Date: Sun, 23 Dec 2012 14:55:16 -0800
Subject: Re: [PERFORM] Performance on Bulk Insert to Partitioned Table
True, that's the same I feel, I will be looking to translate the
trigger to C if I can find good examples, that should accelerate.
I think your performance bottleneck is almost certainly the dynamic
SQL. Using C to generate that dynamic SQL isn't going to help much,
because it is still the SQL engine that has to parse, plan, and execute
it.
Are the vast majority if your inserts done on any given day for records
from that same day or the one before; or are they evenly spread over
the preceding year? If the former, you could use static SQL in IF and
ELSIF for those days, and fall back on the dynamic SQL for the
exceptions in the ELSE block. Of course that means you have to update
the trigger every day.
Using rules would be totally bad as I'm partitioning daily and after
one year having 365 lines of IF won't be fun to maintain.
Maintaining 365 lines of IF is what Perl was invented for. That goes
for triggers w/ static SQL as well as for rules.
If you do the static SQL in a trigger and the dates of the records are
evenly scattered over the preceding year, make sure your IFs are nested
like a binary search, not a linear search. And if they are mostly for
"today's" date, then make sure you search backwards.
Cheers,
Jeff
CREATE OR REPLACE FUNCTION quotes_insert_trigger()
RETURNS trigger AS $$
DECLARE
r_date text;
BEGIN
r_date = to_char(new.received_time, 'YYYY_MM_DD');
case r_date
when '2012_09_10' then
insert into quotes_2012_09_10 values (NEW.*) using new;
return;
when '2012_09_11' then
insert into quotes_2012_09_11 values (NEW.*) using new;
return;
when '2012_09_12' then
insert into quotes_2012_09_12 values (NEW.*) using new;
return;
when '2012_09_13' then
insert into quotes_2012_09_13 values (NEW.*) using new;
return;
when '2012_09_14' then
insert into quotes_2012_09_14 values (NEW.*) using new;
return;
when '2012_09_15' then
insert into quotes_2012_09_15 values (NEW.*) using new;
return;
when '2012_09_16' then
insert into quotes_2012_09_16 values (NEW.*) using new;
return;
when '2012_09_17' then
insert into quotes_2012_09_17 values (NEW.*) using new;
return;
when '2012_09_18' then
insert into quotes_2012_09_18 values (NEW.*) using new;
return;
when '2012_09_19' then
insert into quotes_2012_09_19 values (NEW.*) using new;
return;
when '2012_09_20' then
insert into quotes_2012_09_20 values (NEW.*) using new;
return;
when '2012_09_21' then
insert into quotes_2012_09_21 values (NEW.*) using new;
return;
when '2012_09_22' then
insert into quotes_2012_09_22 values (NEW.*) using new;
return;
when '2012_09_23' then
insert into quotes_2012_09_23 values (NEW.*) using new;
return;
when '2012_09_24' then
insert into quotes_2012_09_24 values (NEW.*) using new;
return;
end case
RETURN NULL;
END;
$$
LANGUAGE plpgsql;
However I've got no speed improvement.
I need to keep two weeks worth of partitions at a time, that's why all the WHEN statements.
Wish postgres could automate the partition process natively like the other sql db.
Thank you guys for your help.
--
http://www.postgresql.org/mailpref/pgsql-performance
--
http://www.postgresql.org/mailpref/pgsql-performance
--
http://www.postgresql.org/mailpref/pgsql-performance
--
Sent via pgsql-performance mailing list (pgsql-***@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-performance
Charles Gomes
2012-12-24 19:29:35 UTC
Permalink
Emmanuel, I really appreciate you getting back on this old old topic.
Wish you a very very happy Christmas and happy new year.

I'm
kinda disappointed to see that since 2008 pgsql has not evolved to support native
partitioning. Partitioning with Triggers is so slow.
Looks like pgsql lost some momentum after departure of contributors with initiative like you.

The code I've copied from your post and I'm modifying it for 9.2 and will post it back here.

Thank you very much,
Charles

----------------------------------------
Date: Mon, 24 Dec 2012 13:47:12 -0500
Subject: Re: [PERFORM] Performance on Bulk Insert to Partitioned Table
Hi Charles,
I am not working on Postgres anymore and none of our patches were ever
accepted by the community.
The list of development I made can still be found at
http://wiki.postgresql.org/wiki/Aster%27s_Development_Projects
All the code related to these improvements must still be accessible in
the archive. If you can't find something, let me know, I'll try to find
it in my backups!
Happy holidays
Emmanuel
Post by Charles Gomes
http://archives.postgresql.org/pgsql-hackers/2008-12/msg01221.php
- direct inserts in a child table: 2 seconds
- pgplsql trigger (IF ... ELSE IF ... blocks) : 14.5 seconds.
- C trigger: 4 seconds (actually the overhead is in the constraint check)
"
This is from 2008 and looks like at that time those folks where already having performance issues with partitions.
Going to copy some folks from the old thread, hopefully 4 years later they may have found a solution. Maybe they've moved on into something more exciting, maybe He is in another world where we don't have database servers. In special the brave Emmanuel for posting his trigger code that I will hack into my own :P
Thanks Emmanuel.
----------------------------------------
Subject: Re: [PERFORM] Performance on Bulk Insert to Partitioned Table
Date: Mon, 24 Dec 2012 21:11:07 +0400
Post by Charles Gomes
By the way, I've just re-wrote the code to target the partitions individually and I've got almost 4 times improvement.
Shouldn't it be faster to process the trigger, I would understand if there was no CPU left, but there is lots of cpu to chew.
I saw your 20% idle cpu and raise eyebrows.
Post by Charles Gomes
It seems that there will be no other way to speedup unless the insert code is partition aware.
----------------------------------------
Subject: Re: [PERFORM] Performance on Bulk Insert to Partitioned Table
Date: Mon, 24 Dec 2012 10:51:12 -0500
________________________________
Date: Sun, 23 Dec 2012 14:55:16 -0800
Subject: Re: [PERFORM] Performance on Bulk Insert to Partitioned Table
True, that's the same I feel, I will be looking to translate the
trigger to C if I can find good examples, that should accelerate.
I think your performance bottleneck is almost certainly the dynamic
SQL. Using C to generate that dynamic SQL isn't going to help much,
because it is still the SQL engine that has to parse, plan, and execute
it.
Are the vast majority if your inserts done on any given day for records
from that same day or the one before; or are they evenly spread over
the preceding year? If the former, you could use static SQL in IF and
ELSIF for those days, and fall back on the dynamic SQL for the
exceptions in the ELSE block. Of course that means you have to update
the trigger every day.
Using rules would be totally bad as I'm partitioning daily and after
one year having 365 lines of IF won't be fun to maintain.
Maintaining 365 lines of IF is what Perl was invented for. That goes
for triggers w/ static SQL as well as for rules.
If you do the static SQL in a trigger and the dates of the records are
evenly scattered over the preceding year, make sure your IFs are nested
like a binary search, not a linear search. And if they are mostly for
"today's" date, then make sure you search backwards.
Cheers,
Jeff
CREATE OR REPLACE FUNCTION quotes_insert_trigger()
RETURNS trigger AS $$
DECLARE
r_date text;
BEGIN
r_date = to_char(new.received_time, 'YYYY_MM_DD');
case r_date
when '2012_09_10' then
insert into quotes_2012_09_10 values (NEW.*) using new;
return;
when '2012_09_11' then
insert into quotes_2012_09_11 values (NEW.*) using new;
return;
when '2012_09_12' then
insert into quotes_2012_09_12 values (NEW.*) using new;
return;
when '2012_09_13' then
insert into quotes_2012_09_13 values (NEW.*) using new;
return;
when '2012_09_14' then
insert into quotes_2012_09_14 values (NEW.*) using new;
return;
when '2012_09_15' then
insert into quotes_2012_09_15 values (NEW.*) using new;
return;
when '2012_09_16' then
insert into quotes_2012_09_16 values (NEW.*) using new;
return;
when '2012_09_17' then
insert into quotes_2012_09_17 values (NEW.*) using new;
return;
when '2012_09_18' then
insert into quotes_2012_09_18 values (NEW.*) using new;
return;
when '2012_09_19' then
insert into quotes_2012_09_19 values (NEW.*) using new;
return;
when '2012_09_20' then
insert into quotes_2012_09_20 values (NEW.*) using new;
return;
when '2012_09_21' then
insert into quotes_2012_09_21 values (NEW.*) using new;
return;
when '2012_09_22' then
insert into quotes_2012_09_22 values (NEW.*) using new;
return;
when '2012_09_23' then
insert into quotes_2012_09_23 values (NEW.*) using new;
return;
when '2012_09_24' then
insert into quotes_2012_09_24 values (NEW.*) using new;
return;
end case
RETURN NULL;
END;
$$
LANGUAGE plpgsql;
However I've got no speed improvement.
I need to keep two weeks worth of partitions at a time, that's why all the WHEN statements.
Wish postgres could automate the partition process natively like the other sql db.
Thank you guys for your help.
--
http://www.postgresql.org/mailpref/pgsql-performance
--
http://www.postgresql.org/mailpref/pgsql-performance
--
http://www.postgresql.org/mailpref/pgsql-performance
--
Sent via pgsql-performance mailing list (pgsql-***@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-performance
Emmanuel Cecchet
2012-12-24 18:47:12 UTC
Permalink
Hi Charles,

I am not working on Postgres anymore and none of our patches were ever
accepted by the community.
The list of development I made can still be found at
http://wiki.postgresql.org/wiki/Aster%27s_Development_Projects

All the code related to these improvements must still be accessible in
the archive. If you can't find something, let me know, I'll try to find
it in my backups!

Happy holidays
Emmanuel
Post by Charles Gomes
http://archives.postgresql.org/pgsql-hackers/2008-12/msg01221.php
- direct inserts in a child table: 2 seconds
- pgplsql trigger (IF ... ELSE IF ... blocks) : 14.5 seconds.
- C trigger: 4 seconds (actually the overhead is in the constraint check)
"
This is from 2008 and looks like at that time those folks where already having performance issues with partitions.
Going to copy some folks from the old thread, hopefully 4 years later they may have found a solution. Maybe they've moved on into something more exciting, maybe He is in another world where we don't have database servers. In special the brave Emmanuel for posting his trigger code that I will hack into my own :P
Thanks Emmanuel.
----------------------------------------
Subject: Re: [PERFORM] Performance on Bulk Insert to Partitioned Table
Date: Mon, 24 Dec 2012 21:11:07 +0400
Post by Charles Gomes
By the way, I've just re-wrote the code to target the partitions individually and I've got almost 4 times improvement.
Shouldn't it be faster to process the trigger, I would understand if there was no CPU left, but there is lots of cpu to chew.
I saw your 20% idle cpu and raise eyebrows.
Post by Charles Gomes
It seems that there will be no other way to speedup unless the insert code is partition aware.
----------------------------------------
Subject: Re: [PERFORM] Performance on Bulk Insert to Partitioned Table
Date: Mon, 24 Dec 2012 10:51:12 -0500
________________________________
Date: Sun, 23 Dec 2012 14:55:16 -0800
Subject: Re: [PERFORM] Performance on Bulk Insert to Partitioned Table
True, that's the same I feel, I will be looking to translate the
trigger to C if I can find good examples, that should accelerate.
I think your performance bottleneck is almost certainly the dynamic
SQL. Using C to generate that dynamic SQL isn't going to help much,
because it is still the SQL engine that has to parse, plan, and execute
it.
Are the vast majority if your inserts done on any given day for records
from that same day or the one before; or are they evenly spread over
the preceding year? If the former, you could use static SQL in IF and
ELSIF for those days, and fall back on the dynamic SQL for the
exceptions in the ELSE block. Of course that means you have to update
the trigger every day.
Using rules would be totally bad as I'm partitioning daily and after
one year having 365 lines of IF won't be fun to maintain.
Maintaining 365 lines of IF is what Perl was invented for. That goes
for triggers w/ static SQL as well as for rules.
If you do the static SQL in a trigger and the dates of the records are
evenly scattered over the preceding year, make sure your IFs are nested
like a binary search, not a linear search. And if they are mostly for
"today's" date, then make sure you search backwards.
Cheers,
Jeff
CREATE OR REPLACE FUNCTION quotes_insert_trigger()
RETURNS trigger AS $$
DECLARE
r_date text;
BEGIN
r_date = to_char(new.received_time, 'YYYY_MM_DD');
case r_date
when '2012_09_10' then
insert into quotes_2012_09_10 values (NEW.*) using new;
return;
when '2012_09_11' then
insert into quotes_2012_09_11 values (NEW.*) using new;
return;
when '2012_09_12' then
insert into quotes_2012_09_12 values (NEW.*) using new;
return;
when '2012_09_13' then
insert into quotes_2012_09_13 values (NEW.*) using new;
return;
when '2012_09_14' then
insert into quotes_2012_09_14 values (NEW.*) using new;
return;
when '2012_09_15' then
insert into quotes_2012_09_15 values (NEW.*) using new;
return;
when '2012_09_16' then
insert into quotes_2012_09_16 values (NEW.*) using new;
return;
when '2012_09_17' then
insert into quotes_2012_09_17 values (NEW.*) using new;
return;
when '2012_09_18' then
insert into quotes_2012_09_18 values (NEW.*) using new;
return;
when '2012_09_19' then
insert into quotes_2012_09_19 values (NEW.*) using new;
return;
when '2012_09_20' then
insert into quotes_2012_09_20 values (NEW.*) using new;
return;
when '2012_09_21' then
insert into quotes_2012_09_21 values (NEW.*) using new;
return;
when '2012_09_22' then
insert into quotes_2012_09_22 values (NEW.*) using new;
return;
when '2012_09_23' then
insert into quotes_2012_09_23 values (NEW.*) using new;
return;
when '2012_09_24' then
insert into quotes_2012_09_24 values (NEW.*) using new;
return;
end case
RETURN NULL;
END;
$$
LANGUAGE plpgsql;
However I've got no speed improvement.
I need to keep two weeks worth of partitions at a time, that's why all the WHEN statements.
Wish postgres could automate the partition process natively like the other sql db.
Thank you guys for your help.
--
http://www.postgresql.org/mailpref/pgsql-performance
--
http://www.postgresql.org/mailpref/pgsql-performance
--
http://www.postgresql.org/mailpref/pgsql-performance
--
Sent via pgsql-performance mailing list (pgsql-***@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-performance
Jeff Janes
2012-12-27 18:24:15 UTC
Permalink
Post by Charles Gomes
By the way, I've just re-wrote the code to target the partitions
individually and I've got almost 4 times improvement.
Shouldn't it be faster to process the trigger, I would understand if there
was no CPU left, but there is lots of cpu to chew.
Once you turned off hyperthreading, it was reporting 75% CPU usage.
Assuming that that accounting is perfect, that means you could only get
33% faster if you were to somehow start using all of the CPU. So I don't
think I'd call that a lot of CPU left. And if you have 70 processes
fighting for 8 cores, I'm not surprised you can't get above that CPU usage.
Post by Charles Gomes
It seems that there will be no other way to speedup unless the insert code
is partition aware.
There may be other ways, but that one will probably get you the most gain,
especially if you use COPY or \copy. Since the main goal of partitioning
is to allow your physical storage layout to conspire with your bulk
operations, it is hard to see how you can get the benefits of partitioning
without having your bulk loading participate in that conspiracy.


Cheers,

Jeff
Jeff Janes
2012-12-27 04:03:33 UTC
Permalink
Post by Charles Gomes
________________________________
Post by Jeff Janes
I think your performance bottleneck is almost certainly the dynamic
SQL. Using C to generate that dynamic SQL isn't going to help much,
because it is still the SQL engine that has to parse, plan, and execute
it.
CREATE OR REPLACE FUNCTION quotes_insert_trigger()
RETURNS trigger AS $$
DECLARE
r_date text;
BEGIN
r_date = to_char(new.received_time, 'YYYY_MM_DD');
case r_date
when '2012_09_10' then
insert into quotes_2012_09_10 values (NEW.*) using new;
return;
...
However I've got no speed improvement.
I need to keep two weeks worth of partitions at a time, that's why all the WHEN statements.
The 'using new' and return without argument are syntax errors.

When I do a model system with those fixed, I get about 2 fold improvement
over the dynamic SQL performance. Even if your performance did not go up,
did your CPU usage go down? Perhaps you have multiple bottlenecks all
sitting at about the same place, and so tackling any one of them at a time
doesn't get you anywhere.

How does both the dynamic and the CASE scale with the number of threads? I
think you said you had something like 70 sessions, but only 8 CPUs. That
probably will do bad things with contention, and I don't see how using more
connections than CPUs is going to help you here. If the CASE starts out
faster in single thread but then flat lines and the EXECUTE catches up,
that suggests a different avenue of investigation than they are always the
same.
Post by Charles Gomes
Wish postgres could automate the partition process natively like the other sql db.
More automated would be nice (i.e. one operation to make both the check
constraints and the trigger, so they can't get out of sync), but would not
necessarily mean faster. I don't know what you mean about other db. Last
time I looked at partitioning in mysql, it was only about breaking up the
underlying storage into separate files (without regards to contents of the
rows), so that is the same as what postgres does automatically. And in
Oracle, their partitioning seemed about the same as postgres's as far
as administrative tedium was concerned. I'm not familiar with how the MS
product handles it, and maybe me experience with the other two are out of
date.

Cheers,

Jeff
Pavel Stehule
2012-12-27 05:40:23 UTC
Permalink
Post by Jeff Janes
Post by Charles Gomes
________________________________
Post by Jeff Janes
I think your performance bottleneck is almost certainly the dynamic
SQL. Using C to generate that dynamic SQL isn't going to help much,
because it is still the SQL engine that has to parse, plan, and execute
it.
CREATE OR REPLACE FUNCTION quotes_insert_trigger()
RETURNS trigger AS $$
DECLARE
r_date text;
BEGIN
r_date = to_char(new.received_time, 'YYYY_MM_DD');
case r_date
when '2012_09_10' then
insert into quotes_2012_09_10 values (NEW.*) using new;
return;
...
However I've got no speed improvement.
I need to keep two weeks worth of partitions at a time, that's why all the
WHEN statements.
The 'using new' and return without argument are syntax errors.
When I do a model system with those fixed, I get about 2 fold improvement
over the dynamic SQL performance. Even if your performance did not go up,
did your CPU usage go down? Perhaps you have multiple bottlenecks all
sitting at about the same place, and so tackling any one of them at a time
doesn't get you anywhere.
How does both the dynamic and the CASE scale with the number of threads? I
think you said you had something like 70 sessions, but only 8 CPUs. That
probably will do bad things with contention, and I don't see how using more
connections than CPUs is going to help you here. If the CASE starts out
faster in single thread but then flat lines and the EXECUTE catches up, that
suggests a different avenue of investigation than they are always the same.
Post by Charles Gomes
Wish postgres could automate the partition process natively like the other sql db.
More automated would be nice (i.e. one operation to make both the check
constraints and the trigger, so they can't get out of sync), but would not
necessarily mean faster. I don't know what you mean about other db. Last
time I looked at partitioning in mysql, it was only about breaking up the
underlying storage into separate files (without regards to contents of the
rows), so that is the same as what postgres does automatically. And in
Oracle, their partitioning seemed about the same as postgres's as far as
administrative tedium was concerned. I'm not familiar with how the MS
product handles it, and maybe me experience with the other two are out of
date.
I did simple test - not too precious (just for first orientation) -
tested on 9.3 - compiled without assertions

insert 0.5M rows into empty target table with one trivial trigger and
one index is about 4 sec

same with little bit complex trigger - one IF statement and two assign
statements is about 5 sec

simple forwarding two two tables - 8 sec

using dynamic SQL is significantly slower - 18 sec - probably due
overhead with cached plans

a overhead depends on number of partitions, number of indexes, but I
expect so overhead of redistributed triggers should be about 50-100%
(less on large tables, higher on small tables).

Native implementation should significantly effective evaluate
expressions, mainly simple expressions - (this is significant for
large number of partitions) and probably can do tuple forwarding
faster than is heavy INSERT statement (is question if is possible
decrease some overhead with more sophisticate syntax (by removing
record expand).

So native implementation can carry significant speed up - mainly if we
can distribute tuples without expression evaluating (evaluated by
executor)

Regards

Pavel
Post by Jeff Janes
Cheers,
Jeff
--
Sent via pgsql-performance mailing list (pgsql-***@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-performance
Charles Gomes
2012-12-27 16:16:26 UTC
Permalink
________________________________
Date: Wed, 26 Dec 2012 23:03:33 -0500
Subject: Re: [PERFORM] Performance on Bulk Insert to Partitioned Table
________________________________
Post by Jeff Janes
I think your performance bottleneck is almost certainly the dynamic
SQL. Using C to generate that dynamic SQL isn't going to help much,
because it is still the SQL engine that has to parse, plan, and execute
it.
CREATE OR REPLACE FUNCTION quotes_insert_trigger()
RETURNS trigger AS $$
DECLARE
r_date text;
BEGIN
r_date = to_char(new.received_time, 'YYYY_MM_DD');
case r_date
when '2012_09_10' then
insert into quotes_2012_09_10 values (NEW.*) using new;
return;
...
However I've got no speed improvement.
I need to keep two weeks worth of partitions at a time, that's why all
the WHEN statements.
The 'using new' and return without argument are syntax errors.
When I do a model system with those fixed, I get about 2 fold
improvement over the dynamic SQL performance. Even if your performance
did not go up, did your CPU usage go down? Perhaps you have multiple
bottlenecks all sitting at about the same place, and so tackling any
one of them at a time doesn't get you anywhere.
I’ve run a small test with the fixes you mentioned and it changed from
1H:20M to, 1H:30M to insert 396000000 rows.


 

If there was another bottleneck, performance when targeting the
partitions directly would not be twice as fast. I’ve run another long insert
test and it takes 4H:15M to complete using triggers to distribute the inserts.  When targeting It completes in 1H:55M. That’s
both for 70 simultaneous workers with the same data and 1188000000 rows.


 

The tests that Emmanuel did translating the trigger to C have great
performance improvement. While His code is very general and could work for
anyone using CHECK’s for triggers. I’m still working on fixing it so it’s
compatible with 9.2

So far I’m having a hard time using the C triggers anyway,:

ERROR:  could not load library
"/var/lib/pgsql/pg_trigger_example.so":
/var/lib/pgsql/pg_trigger_example.so: failed to map segment from shared object:
Operation not permitted

I will do more reading on it.

I think having it to work again can bring some value so more people can
be aware of the performance improvement using C instead of PLSQL.
How does both the dynamic and the CASE scale with the number of
threads? I think you said you had something like 70 sessions, but only
8 CPUs. That probably will do bad things with contention, and I don't
see how using more connections than CPUs is going to help you here. If
the CASE starts out faster in single thread but then flat lines and the
EXECUTE catches up, that suggests a different avenue of investigation
than they are always the same.
I didn’t see a significant change in CPU utilization, it seems to be a
bit less, but not that much, however IO is still idling.
Wish postgres could automate the partition process natively like the
other sql db.
More automated would be nice (i.e. one operation to make both the check
constraints and the trigger, so they can't get out of sync), but would
not necessarily mean faster. I don't know what you mean about other
db. Last time I looked at partitioning in mysql, it was only about
breaking up the underlying storage into separate files (without regards
to contents of the rows), so that is the same as what postgres does
automatically. And in Oracle, their partitioning seemed about the same
as postgres's as far as administrative tedium was concerned. I'm not
familiar with how the MS product handles it, and maybe me experience
with the other two are out of date.
The other free sql DB supports a more elaborated scheme, for example:

CREATE
TABLE ti (id INT, amount DECIMAL(7,2), tr_date DATE)

    ENGINE=INNODB

    PARTITION BY HASH( MONTH(tr_date) )

    PARTITIONS 6;

It
also supports partitioning by RANGE, LIST or KEY.


The paid one uses a very similar style:CREATE TABLE dept (deptno NUMBER, deptname VARCHAR(32))
PARTITION BY HASH(deptno) PARTITIONS 16;

Also:
CREATE TABLE sales
( prod_id NUMBER(6)
, cust_id NUMBER
, time_id DATE
, quantity_sold NUMBER(3)
)
PARTITION BY RANGE (time_id)
( PARTITION sales_q1_2006 VALUES LESS THAN (TO_DATE('01-APR-2006','dd-MON-yyyy'))
TABLESPACE tsa
, PARTITION sales_q2_2006 VALUES LESS THAN (TO_DATE('01-JUL-2006','dd-MON-yyyy'))
TABLESPACE tsb
...
Cheers,
Jeff
--
Sent via pgsql-performance mailing list (pgsql-***@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-performance
Jeff Janes
2012-12-27 18:11:49 UTC
Permalink
Post by Pavel Stehule
Post by Jeff Janes
More automated would be nice (i.e. one operation to make both the check
constraints and the trigger, so they can't get out of sync), but would
not
Post by Jeff Janes
necessarily mean faster.
<snip some benchmarking>

Native implementation should significantly effective evaluate

expressions, mainly simple expressions - (this is significant for
Post by Pavel Stehule
large number of partitions) and probably can do tuple forwarding
faster than is heavy INSERT statement (is question if is possible
decrease some overhead with more sophisticate syntax (by removing
record expand).
If the main goal is to make it faster, I'd rather see all of plpgsql get
faster, rather than just a special case of partitioning triggers. For
example, right now a CASE <expression> statement with 100 branches is about
the same speed as an equivalent list of 100 elsif. So it seems to be doing
a linear search, when it could be doing a hash that should be a lot faster.
Post by Pavel Stehule
So native implementation can carry significant speed up - mainly if we
can distribute tuples without expression evaluating (evaluated by
executor)
Making partitioning inserts native does open up other opportunities to make
it faster, and also to make it administratively easier; but do we want to
try to tackle both of those goals simultaneously? I think the
administrative aspects would come first. (But I doubt I will be the one to
implement either, so my vote doesn't count for much here.)


Cheers,

Jeff
Pavel Stehule
2012-12-27 18:46:12 UTC
Permalink
Post by Jeff Janes
Post by Pavel Stehule
Post by Jeff Janes
More automated would be nice (i.e. one operation to make both the check
constraints and the trigger, so they can't get out of sync), but would not
necessarily mean faster.
<snip some benchmarking>
Post by Pavel Stehule
Native implementation should significantly effective evaluate
expressions, mainly simple expressions - (this is significant for
large number of partitions) and probably can do tuple forwarding
faster than is heavy INSERT statement (is question if is possible
decrease some overhead with more sophisticate syntax (by removing
record expand).
If the main goal is to make it faster, I'd rather see all of plpgsql get
faster, rather than just a special case of partitioning triggers. For
example, right now a CASE <expression> statement with 100 branches is about
the same speed as an equivalent list of 100 elsif. So it seems to be doing
a linear search, when it could be doing a hash that should be a lot faster.
a bottleneck is not in PL/pgSQL directly. It is in PostgreSQL
expression executor. Personally I don't see any simple optimization -
maybe some variant of JIT (for expression executor) should to improve
performance.

Any other optimization require significant redesign PL/pgSQL what is
job what I don't would do now - personally, it is not work what I
would to start by self, because using plpgsql triggers for
partitioning is bad usage of plpgsql - and I believe so after native
implementation any this work will be useless. Design some generic C
trigger or really full implementation is better work.

More, there is still expensive INSERT statement - forwarding tuple on
C level should be significantly faster - because it don't be generic.
Post by Jeff Janes
Post by Pavel Stehule
So native implementation can carry significant speed up - mainly if we
can distribute tuples without expression evaluating (evaluated by
executor)
Making partitioning inserts native does open up other opportunities to make
it faster, and also to make it administratively easier; but do we want to
try to tackle both of those goals simultaneously? I think the
administrative aspects would come first. (But I doubt I will be the one to
implement either, so my vote doesn't count for much here.)
Anybody who starts work on native implementation will have my support
(it is feature that lot of customers needs). I have customers that can
support development and I believe so there are others. Actually It
needs only one tenacious man, because it is work for two years.

Regards

Pavel
Post by Jeff Janes
Cheers,
Jeff
--
Sent via pgsql-performance mailing list (pgsql-***@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-performance
Charles Gomes
2012-12-27 19:00:02 UTC
Permalink
Pavel,

I've been trying to port the work of Emmanuel
http://archives.postgresql.org/pgsql-hackers/2008-12/msg01221.php


His implementation is pretty straight forward. Simple trigger doing constrain checks with caching for bulk inserts.
So far that's what I got http://www.widesol.com/~charles/pgsql/partition.c
I had some issues as He uses HeapTuples and on 9.2 I see a Slot.


----------------------------------------
Date: Thu, 27 Dec 2012 19:46:12 +0100
Subject: Re: [PERFORM] Performance on Bulk Insert to Partitioned Table
Post by Jeff Janes
Post by Pavel Stehule
Post by Jeff Janes
More automated would be nice (i.e. one operation to make both the check
constraints and the trigger, so they can't get out of sync), but would
not
necessarily mean faster.
<snip some benchmarking>
Post by Pavel Stehule
Native implementation should significantly effective evaluate
expressions, mainly simple expressions - (this is significant for
large number of partitions) and probably can do tuple forwarding
faster than is heavy INSERT statement (is question if is possible
decrease some overhead with more sophisticate syntax (by removing
record expand).
If the main goal is to make it faster, I'd rather see all of plpgsql get
faster, rather than just a special case of partitioning triggers. For
example, right now a CASE <expression> statement with 100 branches is about
the same speed as an equivalent list of 100 elsif. So it seems to be doing
a linear search, when it could be doing a hash that should be a lot faster.
a bottleneck is not in PL/pgSQL directly. It is in PostgreSQL
expression executor. Personally I don't see any simple optimization -
maybe some variant of JIT (for expression executor) should to improve
performance.
Any other optimization require significant redesign PL/pgSQL what is
job what I don't would do now - personally, it is not work what I
would to start by self, because using plpgsql triggers for
partitioning is bad usage of plpgsql - and I believe so after native
implementation any this work will be useless. Design some generic C
trigger or really full implementation is better work.
More, there is still expensive INSERT statement - forwarding tuple on
C level should be significantly faster - because it don't be generic.
Post by Jeff Janes
Post by Pavel Stehule
So native implementation can carry significant speed up - mainly if we
can distribute tuples without expression evaluating (evaluated by
executor)
Making partitioning inserts native does open up other opportunities to make
it faster, and also to make it administratively easier; but do we want to
try to tackle both of those goals simultaneously? I think the
administrative aspects would come first. (But I doubt I will be the one to
implement either, so my vote doesn't count for much here.)
Anybody who starts work on native implementation will have my support
(it is feature that lot of customers needs). I have customers that can
support development and I believe so there are others. Actually It
needs only one tenacious man, because it is work for two years.
Regards
Pavel
Post by Jeff Janes
Cheers,
Jeff
--
http://www.postgresql.org/mailpref/pgsql-performance
--
Sent via pgsql-performance mailing list (pgsql-***@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-performance
Ali Pouya
2013-01-17 14:38:14 UTC
Permalink
Post by Charles Gomes
So far that's what I got http://www.widesol.com/~charles/pgsql/partition.c
I had some issues as He uses HeapTuples and on 9.2 I see a Slot.
Hi Charles,
I copied your C code partition.c and am trying to test it.

For compiling you suggest :

...
gcc -I "./" -fpic -c trigger.c
...

Where comes the file *trigger.c* from ? Is that the one you find in the
source directory
./src/backend/commands/ ?

Thanks a lot
Best regards
Ali
Charles Gomes
2013-01-17 15:01:31 UTC
Permalink
________________________________
Date: Thu, 17 Jan 2013 15:38:14 +0100
Subject: Re: [PERFORM] Performance on Bulk Insert to Partitioned Table
2012/12/27 Charles Gomes
So far that's what I got http://www.widesol.com/~charles/pgsql/partition.c
I had some issues as He uses HeapTuples and on 9.2 I see a Slot.
Hi Charles,
I copied your C code partition.c and am trying to test it.
...
gcc -I "./" -fpic -c trigger.c
...
Where comes the file trigger.c from ? Is that the one you find in the
source directory
./src/backend/commands/ ?
Thanks a lot
Best regards
Ali
Ali,
You can save the source as partition.c and use:

gcc -O2 -Wall -Wmissing-prototypes -Wpointer-arith -Wdeclaration-after-statement -Wendif-labels -Wmissing-format-attribute -Wformat-security -fno-strict-aliasing -fwrapv -fpic -DREFINT_VERBOSE -I. -I. -I"/usr/pgsql-9.2/include/server/" -D_GNU_SOURCE   -c -o partition.o partition.c
 
gcc -O2 -Wall -Wmissing-prototypes -Wpointer-arith -Wdeclaration-after-statement -Wendif-labels -Wmissing-format-attribute -Wformat-security -fno-strict-aliasing -fwrapv -fpic -Wl,--as-needed -Wl,-rpath,'/usr/local/pgsql/lib',--enable-new-dtags -L/usr/pgsql-9.2/lib -lpgport  -shared -o /usr/pgsql-9.2/lib/partition.so
 
To Compile you must have postgresql-devel packages.

I've added everything to github:
https://github.com/charlesrg/pgsql_partition/blob/master/partition.c

For more info check
http://www.charlesrg.com/linux/71-postgresql-partitioning-the-database-the-fastest-way
--
Sent via pgsql-performance mailing list (pgsql-***@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-performance
Ali Pouya
2013-01-17 16:03:50 UTC
Permalink
Ali,
Post by Charles Gomes
gcc -O2 -Wall -Wmissing-prototypes -Wpointer-arith
-Wdeclaration-after-statement -Wendif-labels -Wmissing-format-attribute
-Wformat-security -fno-strict-aliasing -fwrapv -fpic -DREFINT_VERBOSE -I.
-I. -I"/usr/pgsql-9.2/include/server/" -D_GNU_SOURCE -c -o partition.o
partition.c
gcc -O2 -Wall -Wmissing-prototypes -Wpointer-arith
-Wdeclaration-after-statement -Wendif-labels -Wmissing-format-attribute
-Wformat-security -fno-strict-aliasing -fwrapv -fpic -Wl,--as-needed
-Wl,-rpath,'/usr/local/pgsql/lib',--enable-new-dtags -L/usr/pgsql-9.2/lib
-lpgport -shared -o /usr/pgsql-9.2/lib/partition.so
To Compile you must have postgresql-devel packages.
https://github.com/charlesrg/pgsql_partition/blob/master/partition.c
For more info check
http://www.charlesrg.com/linux/71-postgresql-partitioning-the-database-the-fastest-way
Thanks Charles,
Now the compilation is OK.
I'll test and feed back more information if any.
best regards
Ali

Stephen Frost
2012-12-27 19:00:19 UTC
Permalink
Post by Jeff Janes
If the main goal is to make it faster, I'd rather see all of plpgsql get
faster, rather than just a special case of partitioning triggers. For
example, right now a CASE <expression> statement with 100 branches is about
the same speed as an equivalent list of 100 elsif. So it seems to be doing
a linear search, when it could be doing a hash that should be a lot faster.
That's a nice thought, but I'm not sure that it'd really be practical.
CASE statements in plpgsql are completely general and really behave more
like an if/elsif tree than a C-style switch() statement or similar. For
one thing, the expression need not use the same variables, could be
complex multi-variable conditionals, etc.

Figuring out that you could build a dispatch table for a given CASE
statement and then building it, storing it, and remembering to use it,
wouldn't be cheap.

On the other hand, I've actually *wanted* a simpler syntax on occation.
I have no idea if there'd be a way to make it work, but this would be
kind of nice:

CASE OF x -- or whatever
WHEN 1 THEN blah blah
WHEN 2 THEN blah blah
WHEN 3 THEN blah blah
END

which would be possible to build into a dispatch table by looking at the
type of x and the literals used in the overall CASE statement. Even so,
there would likely be some number of WHEN conditions required before
it'd actually be more efficient to use, though perhaps getting rid of
the expression evaluation (if that'd be possible) would make up for it.

Thanks,

Stephen
Pavel Stehule
2012-12-27 19:21:25 UTC
Permalink
Post by Stephen Frost
Post by Jeff Janes
If the main goal is to make it faster, I'd rather see all of plpgsql get
faster, rather than just a special case of partitioning triggers. For
example, right now a CASE <expression> statement with 100 branches is about
the same speed as an equivalent list of 100 elsif. So it seems to be doing
a linear search, when it could be doing a hash that should be a lot faster.
That's a nice thought, but I'm not sure that it'd really be practical.
CASE statements in plpgsql are completely general and really behave more
like an if/elsif tree than a C-style switch() statement or similar. For
one thing, the expression need not use the same variables, could be
complex multi-variable conditionals, etc.
Figuring out that you could build a dispatch table for a given CASE
statement and then building it, storing it, and remembering to use it,
wouldn't be cheap.
On the other hand, I've actually *wanted* a simpler syntax on occation.
I have no idea if there'd be a way to make it work, but this would be
CASE OF x -- or whatever
WHEN 1 THEN blah blah
WHEN 2 THEN blah blah
WHEN 3 THEN blah blah
END
which would be possible to build into a dispatch table by looking at the
type of x and the literals used in the overall CASE statement. Even so,
there would likely be some number of WHEN conditions required before
it'd actually be more efficient to use, though perhaps getting rid of
the expression evaluation (if that'd be possible) would make up for it.
I understand, but I am not happy with it. CASE is relative complex.
There is SQL CASE too, and this is third variant of CASE. Maybe some
simple CASE statements can be supported by parser and there should be
local optimization (probably only for numeric - without casting) But
it needs relative lot of new code? Will be this code accepted?

Regards

Pavel
Post by Stephen Frost
Thanks,
Stephen
--
Sent via pgsql-performance mailing list (pgsql-***@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-performance
Vitalii Tymchyshyn
2012-12-28 12:35:43 UTC
Permalink
There is switch-like sql case:
39.6.2.4. Simple CASE

CASE search-expression
WHEN expression [, expression [ ... ]] THEN
statements
[ WHEN expression [, expression [ ... ]] THEN
statements
... ]
[ ELSE
statements ]
END CASE;

It should work like C switch statement.

Also, for bulk insert, have you tried "for each statement" triggers instead
of "for each row"?
This would look like a lot of inserts and would not be fast in
single-row-insert case, but can give you benefit for huge inserts.
It should look like
insert into quotes_2012_09_10 select * from new where
cast(new.received_time as date) = '2012-09-10' ;
insert into quotes_2012_09_11 select * from new where
cast(new.received_time as date) = '2012-09-11' ;
...
Post by Jeff Janes
Post by Jeff Janes
If the main goal is to make it faster, I'd rather see all of plpgsql get
faster, rather than just a special case of partitioning triggers. For
example, right now a CASE <expression> statement with 100 branches is
about
Post by Jeff Janes
the same speed as an equivalent list of 100 elsif. So it seems to be
doing
Post by Jeff Janes
a linear search, when it could be doing a hash that should be a lot
faster.
That's a nice thought, but I'm not sure that it'd really be practical.
CASE statements in plpgsql are completely general and really behave more
like an if/elsif tree than a C-style switch() statement or similar. For
one thing, the expression need not use the same variables, could be
complex multi-variable conditionals, etc.
Figuring out that you could build a dispatch table for a given CASE
statement and then building it, storing it, and remembering to use it,
wouldn't be cheap.
On the other hand, I've actually *wanted* a simpler syntax on occation.
I have no idea if there'd be a way to make it work, but this would be
CASE OF x -- or whatever
WHEN 1 THEN blah blah
WHEN 2 THEN blah blah
WHEN 3 THEN blah blah
END
which would be possible to build into a dispatch table by looking at the
type of x and the literals used in the overall CASE statement. Even so,
there would likely be some number of WHEN conditions required before
it'd actually be more efficient to use, though perhaps getting rid of
the expression evaluation (if that'd be possible) would make up for it.
Thanks,
Stephen
--
Best regards,
Vitalii Tymchyshyn
Vitalii Tymchyshyn
2012-12-28 12:39:28 UTC
Permalink
BTW: If "select count(*) from new" is fast, you can even choose the
strategy in trigger depending on insert size.
Post by Vitalii Tymchyshyn
39.6.2.4. Simple CASE
CASE search-expression
WHEN expression [, expression [ ... ]] THEN
statements
[ WHEN expression [, expression [ ... ]] THEN
statements
... ]
[ ELSE
statements ]
END CASE;
It should work like C switch statement.
Also, for bulk insert, have you tried "for each statement" triggers
instead of "for each row"?
This would look like a lot of inserts and would not be fast in
single-row-insert case, but can give you benefit for huge inserts.
It should look like
insert into quotes_2012_09_10 select * from new where
cast(new.received_time as date) = '2012-09-10' ;
insert into quotes_2012_09_11 select * from new where
cast(new.received_time as date) = '2012-09-11' ;
...
Post by Jeff Janes
Post by Jeff Janes
If the main goal is to make it faster, I'd rather see all of plpgsql get
faster, rather than just a special case of partitioning triggers. For
example, right now a CASE <expression> statement with 100 branches is
about
Post by Jeff Janes
the same speed as an equivalent list of 100 elsif. So it seems to be
doing
Post by Jeff Janes
a linear search, when it could be doing a hash that should be a lot
faster.
That's a nice thought, but I'm not sure that it'd really be practical.
CASE statements in plpgsql are completely general and really behave more
like an if/elsif tree than a C-style switch() statement or similar. For
one thing, the expression need not use the same variables, could be
complex multi-variable conditionals, etc.
Figuring out that you could build a dispatch table for a given CASE
statement and then building it, storing it, and remembering to use it,
wouldn't be cheap.
On the other hand, I've actually *wanted* a simpler syntax on occation.
I have no idea if there'd be a way to make it work, but this would be
CASE OF x -- or whatever
WHEN 1 THEN blah blah
WHEN 2 THEN blah blah
WHEN 3 THEN blah blah
END
which would be possible to build into a dispatch table by looking at the
type of x and the literals used in the overall CASE statement. Even so,
there would likely be some number of WHEN conditions required before
it'd actually be more efficient to use, though perhaps getting rid of
the expression evaluation (if that'd be possible) would make up for it.
Thanks,
Stephen
--
Best regards,
Vitalii Tymchyshyn
--
Best regards,
Vitalii Tymchyshyn
Stephen Frost
2012-12-28 12:41:41 UTC
Permalink
Vitalii,
[...]
Post by Vitalii Tymchyshyn
It should work like C switch statement.
It does and it doesn't. It behaves generally like a C switch statement,
but is much more flexible and therefore can't be optimized like a C
switch statement can be.

Thanks,

Stephen
Vitalii Tymchyshyn
2012-12-28 13:18:38 UTC
Permalink
Why so? Basic form "case lvalue when rvalue then out ... end" is much like
switch.
The "case when condition then out ... end" is different, more complex
beast, but first one is essentially a switch. If it is now trnasformed into
"case when lvalue = rvalue1 then out1 when lvalue=rvalue2 then out2 ...
end" then this can be optimized and this would benefit many users, not only
ones that use partitioning.
Post by Stephen Frost
Vitalii,
[...]
Post by Vitalii Tymchyshyn
It should work like C switch statement.
It does and it doesn't. It behaves generally like a C switch statement,
but is much more flexible and therefore can't be optimized like a C
switch statement can be.
Thanks,
Stephen
--
Best regards,
Vitalii Tymchyshyn
Pavel Stehule
2012-12-28 13:41:23 UTC
Permalink
Post by Vitalii Tymchyshyn
Why so? Basic form "case lvalue when rvalue then out ... end" is much like
switch.
The "case when condition then out ... end" is different, more complex beast,
but first one is essentially a switch. If it is now trnasformed into
"case when lvalue = rvalue1 then out1 when lvalue=rvalue2 then out2 ... end"
then this can be optimized and this would benefit many users, not only ones
that use partitioning.
please, look to plpgsql source code. PL/pgSQL is too simply and has
not own arithmetic unit - all is transformed to SELECTs, has not any
optimization. But is really short and maintainable.

These SELECTs are evaluated only when it is necessary - but it is
evaluated by PostgreSQL expression executor - not by PL/pgSQL directly
- PL/pgSQL cannot process constant by self.

So any enhancing needs PL/pgSQL redesign and I am not sure, so this
use case has accurate benefit, because expression bottleneck is only
one part of partitioning triggers bottleneck. More - if you need
really fast code, you can use own code in C - and it be 10x times
faster than any optimized PL/pgSQL code. And using C triggers in
PostgreSQL is not terrible work.

Using plpgsql row triggers for partitioning is not good idea - it is
just work around from my perspective, and we should to solve source of
problem - missing native support.

Regards

Pavel Stehule
Post by Vitalii Tymchyshyn
Post by Stephen Frost
Vitalii,
[...]
Post by Vitalii Tymchyshyn
It should work like C switch statement.
It does and it doesn't. It behaves generally like a C switch statement,
but is much more flexible and therefore can't be optimized like a C
switch statement can be.
Thanks,
Stephen
--
Best regards,
Vitalii Tymchyshyn
--
Sent via pgsql-performance mailing list (pgsql-***@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-performance
Stephen Frost
2012-12-28 14:10:29 UTC
Permalink
Post by Vitalii Tymchyshyn
Why so? Basic form "case lvalue when rvalue then out ... end" is much like
switch.
Sorry, to be honest, I missed that distinction and didn't expect that to
work as-is, yet apparently it does. Does it currently perform the same
as an if/elsif tree or is it implemented to actually use a table lookup?
Post by Vitalii Tymchyshyn
please, look to plpgsql source code. PL/pgSQL is too simply and has
not own arithmetic unit - all is transformed to SELECTs, has not any
optimization. But is really short and maintainable.
I was thinking we'd actually do this for all CASE statements, those in
plpgsql and those in regular SQL, if it's possible to do. Hopefully
it'd be possible to do easily in plpgsql once the SQL-level CASE is
done.
Post by Vitalii Tymchyshyn
These SELECTs are evaluated only when it is necessary - but it is
evaluated by PostgreSQL expression executor - not by PL/pgSQL directly
- PL/pgSQL cannot process constant by self.
Right, but I wonder if we could pass the entire CASE tree to the
executor, with essentially pointers to the code blocks which will be
executed, and get back a function which we can call over and over that
takes whatever the parameter is and returns the 'right' pointer?
Post by Vitalii Tymchyshyn
So any enhancing needs PL/pgSQL redesign and I am not sure, so this
use case has accurate benefit, because expression bottleneck is only
one part of partitioning triggers bottleneck. More - if you need
really fast code, you can use own code in C - and it be 10x times
faster than any optimized PL/pgSQL code. And using C triggers in
PostgreSQL is not terrible work.
It's quite a bit of work for people who don't know C or are
(understandably) concerned about writing things which can easily
segfault the entire backend.
Post by Vitalii Tymchyshyn
Using plpgsql row triggers for partitioning is not good idea - it is
just work around from my perspective, and we should to solve source of
problem - missing native support.
I agree that native partitioning would certainly be nice. I was really
hoping that was going to happen for 9.3, but it seems unlikely now
(unless I've missed something).

Thanks,

Stephen
Pavel Stehule
2012-12-28 14:44:17 UTC
Permalink
Post by Stephen Frost
Post by Vitalii Tymchyshyn
Why so? Basic form "case lvalue when rvalue then out ... end" is much like
switch.
Sorry, to be honest, I missed that distinction and didn't expect that to
work as-is, yet apparently it does. Does it currently perform the same
as an if/elsif tree or is it implemented to actually use a table lookup?
both IF and CASE has very similar implementation - table lookup is not
used - there are not special path for searching constants
Post by Stephen Frost
Post by Vitalii Tymchyshyn
please, look to plpgsql source code. PL/pgSQL is too simply and has
not own arithmetic unit - all is transformed to SELECTs, has not any
optimization. But is really short and maintainable.
I was thinking we'd actually do this for all CASE statements, those in
plpgsql and those in regular SQL, if it's possible to do. Hopefully
it'd be possible to do easily in plpgsql once the SQL-level CASE is
done.
I am not sure - SQL case is not heavy specially optimized too :(

I see only one possible way, do almost work when CASE statement is
parsed and bypass executor - this can work, but I afraid so it can
slowdown first start and some use cases where is not too much paths,
because we have to check all paths before executions.
--
Sent via pgsql-performance mailing list (pgsql-***@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-performance
Pavel Stehule
2012-12-28 12:48:19 UTC
Permalink
Hello
Post by Vitalii Tymchyshyn
Also, for bulk insert, have you tried "for each statement" triggers instead
of "for each row"?
This would look like a lot of inserts and would not be fast in
single-row-insert case, but can give you benefit for huge inserts.
It should look like
insert into quotes_2012_09_10 select * from new where cast(new.received_time
as date) = '2012-09-10' ;
insert into quotes_2012_09_11 select * from new where cast(new.received_time
as date) = '2012-09-11' ;
...
It has only one problem - PostgreSQL has not relations NEW and OLD for
statements triggers.

Regards

Pavel
--
Sent via pgsql-performance mailing list (pgsql-***@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-performance
Vitalii Tymchyshyn
2012-12-28 13:25:41 UTC
Permalink
It's a pity. Why does not it listed in "Compatibility" section of create
trigger documentation? I think, this makes "for each statement" triggers
not compatible with SQL99.
Post by Ondrej Ivanič
Hello
Post by Vitalii Tymchyshyn
Also, for bulk insert, have you tried "for each statement" triggers
instead
Post by Vitalii Tymchyshyn
of "for each row"?
This would look like a lot of inserts and would not be fast in
single-row-insert case, but can give you benefit for huge inserts.
It should look like
insert into quotes_2012_09_10 select * from new where
cast(new.received_time
Post by Vitalii Tymchyshyn
as date) = '2012-09-10' ;
insert into quotes_2012_09_11 select * from new where
cast(new.received_time
Post by Vitalii Tymchyshyn
as date) = '2012-09-11' ;
...
It has only one problem - PostgreSQL has not relations NEW and OLD for
statements triggers.
Regards
Pavel
--
Best regards,
Vitalii Tymchyshyn
Jeff Janes
2012-12-28 14:05:17 UTC
Permalink
Post by Vitalii Tymchyshyn
39.6.2.4. Simple CASE
CASE search-expression
WHEN expression [, expression [ ... ]] THEN
statements
[ WHEN expression [, expression [ ... ]] THEN
statements
... ]
[ ELSE
statements ]
END CASE;
It should work like C switch statement.
I had thought that too, but the catch is that the target expressions do not
need to be constants when the function is created. Indeed, they can even
be volatile.

CREATE OR REPLACE FUNCTION foo(x integer)
RETURNS integer AS $$
BEGIN
case x
when 0 then return -5;
when (random()*10)::integer then return 1;
when (random()*10)::integer then return 2;
when (random()*10)::integer then return 3;
when (random()*10)::integer then return 4;
when (random()*10)::integer then return 5;
when (random()*10)::integer then return 6;
when (random()*10)::integer then return 7;
when (random()*10)::integer then return 8;
when (random()*10)::integer then return 9;
when (random()*10)::integer then return 10;
else return -6;

Cheers,

Jeff
Stephen Frost
2012-12-28 14:11:53 UTC
Permalink
Post by Jeff Janes
I had thought that too, but the catch is that the target expressions do not
need to be constants when the function is created. Indeed, they can even
be volatile.
Right, any optimization in this regard would only work in certain
instances- eg: when the 'WHEN' components are all constants and the data
type is something we can manage, etc, etc.

Thanks,

Stephen
Loading...