Re: [PERFORM] Slow query: table iteration (8.3)

2010-02-05 Thread Glenn Maynard
On Fri, Feb 5, 2010 at 6:17 AM, Yeb Havinga yebhavi...@gmail.com wrote:
 and the cache is used between each row of test_users. The plan is with a
 parameter, that means the optimizer could not make use of an actual value
 during planning. However, your test case is clever in the sense that there
 is an index on users and score and the sql function has an order by that
 matches the index, so the planner can avoid a sort by accessing the test
 table using the index.

That's why the index exists.  The point is that the window function
doesn't use the index in this way, and (I think) does a complete index
scan.

It's not just about avoiding a sort, but avoiding touching all of the
irrelevant data in the index and just index searching for each
user_id.  The window function appears to scan the entire index.  In
principle, it could skip all of the rank()  1 data with an index
search, which I'd expect to help many uses of rank(); I assume that's
just hard to implement.

I'll probably be implementing the temporary functions approach
tonight, to help Postgres optimize the function.  Maybe some day,
Postgres will be able to inline functions in this case and that won't
be needed...

-- 
Glenn Maynard

-- 
Sent via pgsql-performance mailing list (pgsql-performance@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-performance


Re: [PERFORM] Slow query: table iteration (8.3)

2010-02-04 Thread Glenn Maynard
2010/2/4 Grzegorz Jaśkiewicz gryz...@gmail.com:
 isn't that possible with window functions and cte ?
 rank, and limit ?

It is, but again I tried that when I originally designed this and I
think it ended up using seq scans, or at least being slow for some
reason or other.

But I'll be dropping this db into 8.4 soon to see if it helps
anything, and I'll check again (and if it's still slow I'll post more
details).  It's been a while and I might just have been doing
something wrong.

-- 
Glenn Maynard

-- 
Sent via pgsql-performance mailing list (pgsql-performance@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-performance


Re: [PERFORM] Slow query: table iteration (8.3)

2010-02-04 Thread Glenn Maynard
On Thu, Feb 4, 2010 at 4:09 AM, Glenn Maynard gl...@zewt.org wrote:
 But I'll be dropping this db into 8.4 soon to see if it helps
 anything, and I'll check again (and if it's still slow I'll post more
 details).  It's been a while and I might just have been doing
 something wrong.

Windowing doesn't want to scale for this case.  I'll simplify to give
an actual test case.

create table test_users (id serial primary key);
insert into test_users (id) select generate_series(1, 1000);
create table test (id serial primary key, score integer, user_id integer);
insert into test (user_id, score) select s.id, random() * 100 from
(select generate_series(1, 1000) as id) as s, generate_series(1,
1000);
create index test_1 on test (score);
create index test_2 on test (user_id, score desc);
analyze;

This generates a thousand users, with a thousand scores each.  This
finds the top score for each user (ignoring the detail of duplicate
scores; easy to deal with):

SELECT sub.id FROM (
SELECT t.id, rank() OVER (PARTITION BY t.user_id ORDER BY score
DESC) AS rank
FROM test t
) AS sub WHERE rank = 1;

This does use the test_2 index (as intended), but it's still very
slow: 11 seconds on my system.

It seems like it's doing a *complete* scan of the index, generating
ranks for every row, and then filters out all but the first of each
rank.  That means it scales linearly with the total number of rows.
All it really needs to do is jump to each user in the index and pull
out the first entry (according to the score desc part of the test_2
index), which would make it scale linearly with the number of users.

The function version:

CREATE FUNCTION high_score_for_user(user_id int) RETURNS SETOF INTEGER
LANGUAGE SQL AS $$
   SELECT t.id FROM test t
   WHERE t.user_id = $1
   ORDER BY t.score DESC LIMIT 1
$$;
SELECT high_score_for_user(u.id) FROM test_users u;

runs in 100ms.

I think I'm stuck with either creating temporary functions with the
constants already replaced, or creating an SQL function that evaluates
a brand new query as a string as Yeb suggested.

-- 
Glenn Maynard

-- 
Sent via pgsql-performance mailing list (pgsql-performance@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-performance


Re: [PERFORM] Slow query: table iteration (8.3)

2010-02-01 Thread Glenn Maynard
On Mon, Feb 1, 2010 at 6:15 AM, Yeb Havinga yhavi...@gmail.com wrote:
 Glenn Maynard wrote:
 SELECT highscores_for_steps_and_card(s.id, 591, 1) FROM stomp_steps s;
 Seq Scan on stomp_steps s  (cost=0.00..793.52 rows=2902 width=4)
 (actual time=26509.919..26509.919 rows=0 loops=1)
 Total runtime: 26509.972 ms

 Stomp_steps is analyzed to 2902 rows but when you run the query the actual
 rows are 0. This means that the highscore function is not called or the
 number 0 is incorrect.

This SELECT returns 0 rows: it calls the function 1500 times, and each
time it returns no data, because there simply aren't any results for
these parameters.

 below. The truth might be that you probably got that result by explaining
 the query in the function with actual parameter values. This plan differs
 from the one that is made when the function is called from sql and is
 planned (once) without parameters, and in that case the plan is probably
 different.

Yeah.  It would help a lot if EXPLAIN could show query plans of
functions used by the statement and not just the top-level query.

 A way to check the plan of that query is to turn on
 debug_print_plan and watch the server log. It takes a bit getting used. The
 plan starts with CONTEXT:  SQL function functionname during startup and is
 also recognized because in the opexpr (operator expression) one of the
 operands is a parameter. Important is the total cost of the top plan node
 (the limit).

Thanks.

SELECT highscores_for_steps_and_card(s.id, 591, 1) FROM stomp_steps s:

Squinting at the output, it definitely looks like a less optimized
plan; it's using a SEQSCAN instead of BITMAPHEAPSCAN.  (I've attached
the output.)

Does the planner not optimize functions based on context?  That seems
like a huge class of optimizations.  The first NULLTEST can be
optimized away, since that parameter comes from a NOT NULL source (a
PK).  The second NULLTEST can also be optimized away, since it's a
constant value (591).  The search could be a BITMAPHEAPSCAN,
substituting the s.id value for each call, instead of a SEQSCAN.  (Not
that I'm concerned about a few cheap NULLTESTs, I'm just surprised at
it using such a generic plan.)

If I create a new function with the constant parameters hard-coded,
it's back to BITMAPHEAPSCAN: 175ms.  This suggests a horrible
workaround: creating temporary functions every time I make this type
of query, with the fixed values substituted textually.  I'd really
love to know a less awful fix.

 I know 8.3 is mentioned in the subject, but I think that a WITH query
 (http://www.postgresql.org/docs/8.4/interactive/queries-with.html) could be
 a good solution to your problem and may be worth trying out, if you have the
 possibility to try out 8.4.

I can't see how to apply WITH to this.  Non-recursive WITH seems like
syntax sugar that doesn't do anything a plain SELECT can't do, and I
don't think what I'm doing here can be done with a regular SELECT.

-- 
Glenn Maynard
SELECT highscores_for_steps_and_card(s.id, 591, 1) FROM stomp_steps s;
 QUERY PLAN

 Seq Scan on stomp_steps s  (cost=0.00..793.52 rows=2902 width=4) (actual 
time=1726.998..26729.717 rows=17 loops=1)
 Total runtime: 26729.822 ms

DEBUG:  plan:
DETAIL: {PLANNEDSTMT
   :commandType 1
   :canSetTag true
   :planTree
  {SEQSCAN
  :startup_cost 0.00
  :total_cost 793.52
  :plan_rows 2902
  :plan_width 4
  :targetlist (
 {TARGETENTRY
 :expr
{FUNCEXPR
:funcid 240532
:funcresulttype 23
:funcretset true
:funcformat 0
:args (
   {VAR
   :varno 1
   :varattno 1
   :vartype 23
   :vartypmod -1
   :varlevelsup 0
   :varnoold 1
   :varoattno 1
   }
   {CONST
   :consttype 23
   :consttypmod -1
   :constlen 4
   :constbyval true
   :constisnull false
   :constvalue 4 [ 79 2 0 0 ]
   }
   {CONST
   :consttype 23
   :consttypmod -1
   :constlen 4
   :constbyval true
   :constisnull false
   :constvalue 4 [ 1 0 0 0 ]
   }
)
}
 :resno 1
 :resname highscores_for_steps_and_card
 :ressortgroupref 0
 :resorigtbl 0
 :resorigcol 0
 :resjunk false
 }
  )
  :qual 
  :lefttree 
  :righttree 
  :initPlan 
  :extParam (b)
  :allParam (b)
  :scanrelid 1
  }
   :rtable (
  {RTE
  :alias
 {ALIAS
 :aliasname s
 :colnames 
 }
  :eref
 {ALIAS
 :aliasname s
 :colnames (id

[PERFORM] Slow query: table iteration (8.3)

2010-01-29 Thread Glenn Maynard
Hitting a performance issues that I'm not sure how to diagnose.

SELECT highscores_for_steps_and_card(s.id, 591, 1) FROM stomp_steps s;
Seq Scan on stomp_steps s  (cost=0.00..793.52 rows=2902 width=4)
(actual time=26509.919..26509.919 rows=0 loops=1)
Total runtime: 26509.972 ms

The inner function looks like this:

CREATE FUNCTION highscores_for_steps_and_card(steps_id int, card_id
int, count int) RETURNS SETOF INTEGER LANGUAGE SQL AS $$
SELECT r.id FROM stomp_round r
WHERE ($1 IS NULL OR r.steps_id = $1) AND ($2 IS NULL OR
r.user_card_id = $2)
ORDER BY r.score DESC LIMIT $3
$$

 Limit  (cost=13.12..13.12 rows=1 width=8) (actual time=0.054..0.054
rows=0 loops=1)
   -  Sort  (cost=13.12..13.12 rows=1 width=8) (actual
time=0.051..0.051 rows=0 loops=1)
 Sort Key: score
 Sort Method:  quicksort  Memory: 17kB
 -  Bitmap Heap Scan on stomp_round r  (cost=9.09..13.11
rows=1 width=8) (actual time=0.036..0.036 rows=0 loops=1)
   Recheck Cond: ((280 = steps_id) AND (user_card_id = 591))
   -  BitmapAnd  (cost=9.09..9.09 rows=1 width=0) (actual
time=0.032..0.032 rows=0 loops=1)
 -  Bitmap Index Scan on stomp_round_steps_id
(cost=0.00..4.40 rows=20 width=0) (actual time=0.030..0.030 rows=0
loops=1)
   Index Cond: (280 = steps_id)
 -  Bitmap Index Scan on stomp_round_user_card_id
 (cost=0.00..4.44 rows=25 width=0) (never executed)
   Index Cond: (user_card_id = 591)
 Total runtime: 0.153 ms
(12 rows)

stomp_steps has about 1500 rows, so it finds 1500 high scores, one for
each stage.

I expected scalability issues from this on a regular drive, since
it'll be doing a ton of index seeking when not working out of cache,
so I expected to need to change to an SSD at some point (when it no
longer easily fits in cache).  However, I/O doesn't seem to be the
bottleneck yet.  If I run it several times, it consistently takes 26
seconds.  The entire database is in OS cache (find | xargs cat:
250ms).

I'm not sure why the full query (26s) is orders of magnitude slower
than 1500*0.150ms (225ms).  It's not a very complex query, and I'd
hope it's not being re-planned every iteration through the loop.  Any
thoughts?  Using SELECT to iterate over a table like this is very
useful (and I don't know any practical alternative), but it's
difficult to profile since it doesn't play nice with EXPLAIN ANALYZE.

-- 
Glenn Maynard

-- 
Sent via pgsql-performance mailing list (pgsql-performance@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-performance


Re: [PERFORM] Any better plan for this query?..

2009-05-12 Thread Glenn Maynard
I'm sorry, but I'm confused.  Everyone keeps talking about connection
pooling, but Dimitri has said repeatedly that each client makes a
single connection and then keeps it open until the end of the test,
not that it makes a single connection per SQL query.  Connection
startup costs shouldn't be an issue.  Am I missing something here?
test(N) starts N clients, each client creates a single connection and
hammers the server for a while on that connection.  test(N) is run for
N=1,2,4,8...256.  This seems like a very reasonable test scenario.

--
Glenn Maynard

-- 
Sent via pgsql-performance mailing list (pgsql-performance@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-performance


Re: [PERFORM] performance for high-volume log insertion

2009-05-01 Thread Glenn Maynard
On Fri, May 1, 2009 at 8:29 PM, PFC li...@peufeu.com wrote:
        Roundtrips can be quite fast but they have a hidden problem, which is
 that everything gets serialized.

The client and server will serialize, but what usually matters most is
avoiding serializing against disk I/O--and that's why write-back
caching exists.  There's still a benefit to pipelining (not everything
the db might need to read to complete the write will always be in
cache), but if everything was being serialized it'd be an order of
magnitude worse.  That's why running each insert in a separate
transaction is so much slower; in that case, it *will* serialize
against the disk (by default).

-- 
Glenn Maynard

-- 
Sent via pgsql-performance mailing list (pgsql-performance@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-performance


Re: [PERFORM] performance for high-volume log insertion

2009-04-22 Thread Glenn Maynard
On Wed, Apr 22, 2009 at 8:19 AM, Stephen Frost sfr...@snowman.net wrote:
 Yes, as I beleive was mentioned already, planning time for inserts is
 really small.  Parsing time for inserts when there's little parsing that
 has to happen also isn't all *that* expensive and the same goes for
 conversions from textual representations of data to binary.

 We're starting to re-hash things, in my view.  The low-hanging fruit is
 doing multiple things in a single transaction, either by using COPY,
 multi-value INSERTs, or just multiple INSERTs in a single transaction.
 That's absolutely step one.

This is all well-known, covered information, but perhaps some numbers
will help drive this home.  4 inserts into a single-column,
unindexed table; with predictable results:

separate inserts, no transaction: 21.21s
separate inserts, same transaction: 1.89s
40 inserts, 100 rows/insert: 0.18s
one 4-value insert: 0.16s
40 prepared inserts, 100 rows/insert: 0.15s
COPY (text): 0.10s
COPY (binary): 0.10s

Of course, real workloads will change the weights, but this is more or
less the magnitude of difference I always see--batch your inserts into
single statements, and if that's not enough, skip to COPY.

-- 
Glenn Maynard

-- 
Sent via pgsql-performance mailing list (pgsql-performance@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-performance


Re: [PERFORM] performance for high-volume log insertion

2009-04-22 Thread Glenn Maynard
On Wed, Apr 22, 2009 at 4:07 PM,  da...@lang.hm wrote:
 are these done as seperate round trips?

 i.e.
 begin send
 insert send
 insert send
 ..
 end send

 or as one round trip?

All tests were done by constructing a file and using time psql -f 

 40 inserts, 100 rows/insert: 0.18s
 one 4-value insert: 0.16s
 40 prepared inserts, 100 rows/insert: 0.15s

 are one of these missing a 0?

Sorry, 400 * 100.  All cases inserted 4 rows, and I deleted all
rows between tests (but did not recreate the table).

-- 
Glenn Maynard

-- 
Sent via pgsql-performance mailing list (pgsql-performance@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-performance


Re: [PERFORM] performance for high-volume log insertion

2009-04-22 Thread Glenn Maynard
On Wed, Apr 22, 2009 at 4:37 PM, Stephen Frost sfr...@snowman.net wrote:
 Thanks for doing the work.  I had been intending to but hadn't gotten to
 it yet.

I'd done similar tests recently, for some batch import code, so it was
just a matter of recreating it.

 separate inserts, no transaction: 21.21s
 separate inserts, same transaction: 1.89s
 40 inserts, 100 rows/insert: 0.18s
 one 4-value insert: 0.16s
 40 prepared inserts, 100 rows/insert: 0.15s
 COPY (text): 0.10s
 COPY (binary): 0.10s

 What about 4 individual prepared inserts?  Just curious about it.

4 inserts, one prepared statement each (constructing the prepared
statement only once), in a single transaction: 1.68s

I'm surprised that there's any win here at all.

 Also, kind of suprised about COPY text vs. binary.  What was the data
 type in the table..?  If text, that makes sense, if it was an integer or
 something else, I'm kind of suprised.

Each row had one integer column.  I expect strings to be harder to
parse, since it's allocating buffers and parsing escapes, which is
usually more expensive than parsing an integer out of a string.  I'd
expect the difference to be negligible either way, though, and I'd be
interested in hearing about use cases where binary copying is enough
of a win to be worth the development cost of maintaining the feature.

On Wed, Apr 22, 2009 at 4:49 PM, Tom Lane t...@sss.pgh.pa.us wrote:
 Also, just to be clear: were the 40 insert cases 40 separate
 transactions or one transaction?  I think the latter was meant but
 it's not 100% clear.

One transaction--the only case where I ran more than one transaction
was the first.

-- 
Glenn Maynard

-- 
Sent via pgsql-performance mailing list (pgsql-performance@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-performance


Re: [PERFORM] performance for high-volume log insertion

2009-04-22 Thread Glenn Maynard
On Wed, Apr 22, 2009 at 4:53 PM, James Mansion
ja...@mansionfamily.plus.com wrote:
 And I'm disagreeing with that.  Single row is a given, but I think you'll
 find it pays to have one
 round trip if at all possible and invoking multiple prepared statements can
 work against this.

You're talking about round-trips to a *local* server, on the same
system, not a dedicated server with network round-trips, right?

Blocking round trips to another process on the same server should be
fairly cheap--that is, writing to a socket (or pipe, or localhost TCP
connection) where the other side is listening for it; and then
blocking in return for the response.  The act of writing to an FD that
another process is waiting for will make the kernel mark the process
as ready to wake up immediately, and the act of blocking for the
response will kick the scheduler to some waiting process, so as long
as there isn't something else to compete for CPU for, each write/read
will wake up the other process instantly.  There's a task switching
cost, but that's too small to be relevant here.

Doing 100 local round trips, over a pipe: 5.25s (5 *microseconds*
each), code attached.  The cost *should* be essentially identical for
any local transport (pipes, named pipes, local TCP connections), since
the underlying scheduler mechanisms are the same.

That's not to say that batching inserts doesn't make a difference--it
clearly does, and it would probably be a much larger difference with
actual network round-trips--but round-trips to a local server aren't
inherently slow.  I'd be (casually) interested in knowing what the
actual costs are behind an SQL command round-trip (where the command
isn't blocking on I/O, eg. an INSERT inside a transaction, with no I/O
for things like constraint checks that need to be done before the
command can return success).

-- 
Glenn Maynard
#include stdio.h
#include unistd.h
#include assert.h

main()
{
int parent[2];
int ret = pipe(parent);
assert(ret != -1);

int child[2];
ret = pipe(child);
assert(ret != -1);

int pid = fork();
assert(pid != -1);

if(pid != 0)
{
// parent
close(parent[0]);
close(child[1]);
int wfd = parent[1];
int rfd = child[0];

printf(go\n);
int i = 100;
while(i--)
{
char c = 1;
ret = write(wfd, c, 1);
assert(ret == 1);
ret = read(rfd, c, 1);
assert(ret == 1);
}
printf(done\n);
}
else
{
// child
close(parent[1]);
close(child[0]);
int wfd = child[1];
int rfd = parent[0];
int i = 100;
while(i--)
{
char c = 1;
ret = read(rfd, c, 1);
assert(ret == 1);
ret = write(wfd, c, 1);
assert(ret == 1);
}
}
}


-- 
Sent via pgsql-performance mailing list (pgsql-performance@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-performance


Re: [PERFORM] performance for high-volume log insertion

2009-04-22 Thread Glenn Maynard
On Wed, Apr 22, 2009 at 5:51 PM, Stephen Frost sfr...@snowman.net wrote:
 For a single column table, I wouldn't expect much either.  With more
 columns I think it would be a larger improvement.

Maybe.  I'm not sure why parsing (1,2,3,4,5) in an EXECUTE parameter
should be faster than parsing the exact same thing in an INSERT,
though.

 I've seen it help, but I was sending everything as binary (I figured,
 once I'm doing it, might as well do it all), which included dates,
 timestamps, IP addresses, integers, and some text.  It may have more of
 an impact on dates and timestamps than on simple integers.

Of course, you still need to get it in that format.  Be careful to
include any parsing you're doing to create the binary date in the
benchmarks.  Inevitably, at least part of the difference will be costs
simply moving from the psql process to your own.

-- 
Glenn Maynard

-- 
Sent via pgsql-performance mailing list (pgsql-performance@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-performance


Re: [PERFORM] Nested query performance issue

2009-04-14 Thread Glenn Maynard
On Tue, Apr 14, 2009 at 5:33 AM, Matthew Wakeling matt...@flymine.org wrote:
 It's about 10% faster for me.  I'm surprised the planner can't figure
 out that this join is redundant.

 Because the join isn't redundant? You're making the assumption that for
 every score.game_id there is exactly one game.id that matches. Of course,
 you may have a unique constraint and foreign key/trigger that ensures this.

That's the definition of the tables I gave.

CREATE TABLE game (id SERIAL NOT NULL PRIMARY KEY); -- pk implies unique
CREATE TABLE score (id SERIAL NOT NULL PRIMARY KEY, score REAL,
game_id INTEGER REFERENCES game (id));

(I don't think it makes any difference to whether this can be
optimized, but adding NOT NULL back to game_id doesn't change it,
either.)

-- 
Glenn Maynard

-- 
Sent via pgsql-performance mailing list (pgsql-performance@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-performance


Re: [PERFORM] Nested query performance issue

2009-04-10 Thread Glenn Maynard
On Thu, Apr 9, 2009 at 7:29 AM, Heikki Linnakangas
heikki.linnakan...@enterprisedb.com wrote:
 CREATE FUNCTION topnscores(game_id int , n int) RETURNS SETOF score LANGUAGE
 SQL AS $$
 SELECT * FROM score s WHERE s.game_id = $1 ORDER BY score DESC LIMIT $2
 $$;

 SELECT (sub.ts).id, (sub.ts).score, (sub.ts).game_id
 FROM (SELECT topnscores(g.id, 5) ts FROM game g) sub;

The inner query:

SELECT topnscores(g.id, 5) ts FROM game g

http://www.postgresql.org/docs/8.3/static/xfunc-sql.html says this is
deprecated (though no deprecation warning is being generated):

 Currently, functions returning sets can also be called in the select list of 
 a query. For each row that the query generates by itself, the function 
 returning set is invoked, and an output row is generated for each element of 
 the function's result set. Note, however, that this capability is deprecated 
 and might be removed in future releases.

It doesn't say how else to write this, though, and it's not obvious to
me.  SELECT ts.* FROM topnscores(g.id, 5) AS ts, game g doesn't work
(function expression in FROM cannot refer to other relations of same
query level).  Is there an equivalent way to do this so I won't have
deprecation looming over my back?  I'm likely to become very dependent
on this pattern.

-- 
Glenn Maynard

-- 
Sent via pgsql-performance mailing list (pgsql-performance@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-performance


Re: [PERFORM] Nested query performance issue

2009-04-09 Thread Glenn Maynard
On Thu, Apr 9, 2009 at 7:29 AM, Heikki Linnakangas
heikki.linnakan...@enterprisedb.com wrote:
 SELECT s.* FROM score s, game g
 WHERE s.game_id = g.id AND
  s.id IN (
    SELECT s2.id FROM score s2 WHERE s2.game_id=g.id ORDER BY s2.score
 DESC LIMIT 1
  );

 You don't really need the join with game here, simplifying this into:

 SELECT s.* FROM score s
 WHERE s.id IN (
    SELECT s2.id FROM score s2 WHERE s2.game_id=s.game_id ORDER BY s2.score
 DESC LIMIT 1
 );

 I don't think it makes it any faster, though.

It's about 10% faster for me.  I'm surprised the planner can't figure
out that this join is redundant.

 SELECT * FROM (
  SELECT s.*, rank() OVER (PARTITION BY s.game_id ORDER BY score DESC) AS
 rank FROM score s
 ) AS sub WHERE rank = 5;

 but I'm not sure how much faster it is. At least here on my laptop it does a
 full index scan on score, which may or may not be faster than just picking
 the top N values for each game using the index.

I'll definitely check this out when 8.4 is released.

 You can do that approach with a SQL function:

 CREATE FUNCTION topnscores(game_id int , n int) RETURNS SETOF score LANGUAGE
 SQL AS $$
 SELECT * FROM score s WHERE s.game_id = $1 ORDER BY score DESC LIMIT $2
 $$;

 SELECT (sub.ts).id, (sub.ts).score, (sub.ts).game_id
 FROM (SELECT topnscores(g.id, 5) ts FROM game g) sub;
(as ts, for anyone trying this at home)

Thanks--this one runs in 32ms, which seems about right compared
against the original fast LIMIT 1 version.

I see a slight improvement if I mark the function stable: 31.9ms to
31.2; minor but consistent.  Just out of curiosity, any explanations
for this difference?  I don't see any change in the resulting query
plan, but the plan doesn't enter the function call.

-- 
Glenn Maynard

-- 
Sent via pgsql-performance mailing list (pgsql-performance@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-performance


Re: [PERFORM] Nested query performance issue

2009-04-09 Thread Glenn Maynard
2009/4/9 Віталій Тимчишин tiv...@gmail.com:
 create or replace function explode_array(in_array anyarray) returns setof
 anyelement as
 $$
     select ($1)[s] from generate_series(1,array_upper($1, 1)) as s;
 $$
 language sql immutable;

I tried using an ARRAY like this, but didn't quite figure out the
explode_array piece.

-- 
Glenn Maynard

-- 
Sent via pgsql-performance mailing list (pgsql-performance@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-performance


[PERFORM] Nested query performance issue

2009-04-08 Thread Glenn Maynard
(This is related to an earlier post on -sql.)

I'm querying for the N high scores for each game, with two tables:
scores and games.

CREATE TABLE game (id SERIAL NOT NULL PRIMARY KEY);
CREATE TABLE score (id SERIAL NOT NULL PRIMARY KEY, score REAL,
game_id INTEGER REFERENCES game (id));
-- test data: 1000 games, 10 scores
INSERT INTO game (id) select generate_series(1,1000);
INSERT INTO score (game_id, score) select game.id, random() from game,
generate_series(1,100);
CREATE INDEX score_idx1 ON score (game_id, score desc);
ANALYZE;

This query retrieves the single highest score for each game, but
doesn't allow any more than that--I can't get the top five scores for
each game.  However, it's very fast: on the above test data, it runs
in 25ms on my system.  With 100 scores, it takes 40ms.

SELECT s.* FROM score s
WHERE s.id IN (
   -- Get the high scoring score ID for each game:
   SELECT
   (
   -- Get the high score for game g:
   SELECT s2.id FROM score s2 WHERE s2.game_id = g.id ORDER BY
s2.score DESC LIMIT 1
   )
   FROM game g
);


This rewrite allows getting the top N scores.  Unfortunately, this one
takes 950ms for the same data.  With 100 scores, it takes 14800ms.

SELECT s.* FROM score s, game g
WHERE s.game_id = g.id AND
 s.id IN (
SELECT s2.id FROM score s2 WHERE s2.game_id=g.id ORDER BY s2.score
DESC LIMIT 1
 );


This seems simple: for each game, search for the highest score, and
then scan the tree to get the next N-1 highest scores.  The first
version does just that, but the second one is doing a seq scan over
score.

I do want to be able to use a LIMIT higher than 1, which only works
with the second form.  Any suggestions of how to get the efficient
scanning of the first while being able to use a LIMIT greater than 1?

(It'd even be faster to make several calls to the first version,
varying an OFFSET to get each high score--but that's terrible.)

-- 
Glenn Maynard

-- 
Sent via pgsql-performance mailing list (pgsql-performance@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-performance


Re: [PERFORM] Nested query performance issue

2009-04-08 Thread Glenn Maynard
(I didn't notice that I ended up with score.score in this test case.  Oops.)

2009/4/8 Віталій Тимчишин tiv...@gmail.com:
 How about

 select s1.*
 from score s1 join score s2 on s1.game_id=s2.game_id and s2.score =
 s1.score
 group by s1.*
 having count(s2.*) = N

I can see what this is doing, but I'm getting:

ERROR:  could not identify an ordering operator for type score
HINT:  Use an explicit ordering operator or modify the query.

I'm not sure why; if I replace s1.* and s2.* with s1.id and s2.id it
works, but then I only get IDs.

Unfortunately, with N = 1 this takes 8100ms (vs. 950ms and 25ms)...

-- 
Glenn Maynard

-- 
Sent via pgsql-performance mailing list (pgsql-performance@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-performance