Thread: BUG #1756: PQexec eats huge amounts of memory

BUG #1756: PQexec eats huge amounts of memory

From
"Denis Vlasenko"
Date:
The following bug has been logged online:

Bug reference:      1756
Logged by:          Denis Vlasenko
Email address:      vda@ilport.com.ua
PostgreSQL version: 8.0.1
Operating system:   Linux
Description:        PQexec eats huge amounts of memory
Details:

Verbatim from http://bugs.php.net/bug.php?id=33587:

Description:
------------
Seen on php-4.3.4RC2. Since I was just testing how good
PG fares compared to Oracle, and I am not feeling any
real pain from this (IOW: not my itch to scratch),
I do not research this in depth, apart from submitting
bug report. Sorry.

Symptom: even the simplest query
$result = pg_query($db, "SELECT * FROM big_table");
eats enormous amounts of memory on server
(proportional to table size).

I think this is a problem with PostgreSQL client libs.
php's source is included for easy reference.

PHP_FUNCTION(pg_query)
{

...
        pgsql_result = PQexec(pgsql, Z_STRVAL_PP(query));
        if ((PGG(auto_reset_persistent) & 2) && PQstatus(pgsql) !=
CONNECTION_OK) {
                PQclear(pgsql_result);
                PQreset(pgsql);
                pgsql_result = PQexec(pgsql, Z_STRVAL_PP(query));
        }

        if (pgsql_result) {
                status = PQresultStatus(pgsql_result);
        } else {
                status = (ExecStatusType) PQstatus(pgsql);
        }

        switch (status) {
                case PGRES_EMPTY_QUERY:
                case PGRES_BAD_RESPONSE:
                case PGRES_NONFATAL_ERROR:
                case PGRES_FATAL_ERROR:
                        php_error_docref(NULL TSRMLS_CC, E_WARNING,
"Query failed: %s.", PQerrorMessage(pgsql));
                        PQclear(pgsql_result);
                        RETURN_FALSE;
                        break;
                case PGRES_COMMAND_OK: /* successful command that did
not return rows */
                default:
                        if (pgsql_result) {
                                pg_result = (pgsql_result_handle *)
emalloc(sizeof(pgsql_result_handle));
                                pg_result->conn = pgsql;
                                pg_result->result = pgsql_result;
                                pg_result->row = 0;
                                ZEND_REGISTER_RESOURCE(return_value,
pg_result, le_result);
                        } else {
                                PQclear(pgsql_result);
                                RETURN_FALSE;
                        }
                        break;
        }
}

Re: BUG #1756: PQexec eats huge amounts of memory

From
Harald Armin Massa
Date:
Denis,

$result =3D pg_query($db, "SELECT * FROM big_table");
>=20

you are reading a big result (as I suspect from big_table) into memory. It=
=20
is perfectly normal that this uses large amounts of memory.

[it would be rather suspicious if loading a big file / big resultset would=
=20
not use big amounts of memory]

Harald


--=20
GHUM Harald Massa
persuasion python postgresql
Harald Armin Massa
Reinsburgstra=DFe 202b
70197 Stuttgart
0173/9409607

Re: BUG #1756: PQexec eats huge amounts of memory

From
Neil Conway
Date:
Denis Vlasenko wrote:
> Symptom: even the simplest query
> $result = pg_query($db, "SELECT * FROM big_table");
> eats enormous amounts of memory on server
> (proportional to table size).

Right, which is exactly what you would expect. The entire result set is
sent to the client and stored in local memory; if you only want to
process part of the result set at a time, use a cursor.

(And I'm a little suspicious that the performance of "SELECT * FROM
big_table" will contribute to a meaningful comparison between database
systems.)

-Neil

Re: BUG #1756: PQexec eats huge amounts of memory

From
Neil Conway
Date:
Denis Vlasenko wrote:
> The same php script but done against Oracle does not have this
> behaviour.

Perhaps; presumably Oracle is essentially creating a cursor for you
behind the scenes. libpq does not attempt to do this automatically; if
you need a cursor, you can create one by hand.

-Neil

Re: BUG #1756: PQexec eats huge amounts of memory

From
Denis Vlasenko
Date:
On Wednesday 06 July 2005 16:52, Harald Armin Massa wrote:
> Denis,
>
> $result = pg_query($db, "SELECT * FROM big_table");
>
> you are reading a big result (as I suspect from big_table) into memory. It
> is perfectly normal that this uses large amounts of memory.

No, I am not reading it into memory. I am executing query _on the server_,
fetching result row-by-row and discarding rows as prey are processed
(i.e. without accumulating all rows in _client's memory_) in the part
of php script which you snipped off.

Similar construct with Oracle, with 10x larger table,
does not use Apache (php) memory significantly.

php's pg_query() calls PQuery(), a Postgresql client library function,
which is likely implemented so that it fetches all rows and stores them
in client's RAM before completion.

Oracle OCI8 does not work this way, it keeps result set
on db server (in a form of a cursor or something like that).

> [it would be rather suspicious if loading a big file / big resultset would
> not use big amounts of memory]
--
vda

Re: BUG #1756: PQexec eats huge amounts of memory

From
Denis Vlasenko
Date:
On Thursday 07 July 2005 08:54, Neil Conway wrote:
> Denis Vlasenko wrote:
> > Symptom: even the simplest query
> > $result = pg_query($db, "SELECT * FROM big_table");
> > eats enormous amounts of memory on server
> > (proportional to table size).
>
> Right, which is exactly what you would expect. The entire result set is
> sent to the client and stored in local memory; if you only want to
> process part of the result set at a time, use a cursor.

The same php script but done against Oracle does not have this
behaviour.

> (And I'm a little suspicious that the performance of "SELECT * FROM
> big_table" will contribute to a meaningful comparison between database
> systems.)

I wanted to show colleagues which are Oracle admins that peak
data fetch rate of PostgreSQL is way better than Oracle one.

While it turned out to be true (Oracle+WinNT = 2kb TCP output buffer,
~1Mb/s over 100Mbit; PostgreSQL+Linux = 8kb buffer, ~2.6Mb/s),
I was ridiculed instead when my php script failed miserably,
crashing Apache with OOM condition, while alanogous script for Oracle
ran to completion just fine.
--
vda

Re: BUG #1756: PQexec eats huge amounts of memory

From
Alvaro Herrera
Date:
On Thu, Jul 07, 2005 at 09:51:54AM +0300, Denis Vlasenko wrote:

> I wanted to show colleagues which are Oracle admins that peak
> data fetch rate of PostgreSQL is way better than Oracle one.
>
> While it turned out to be true (Oracle+WinNT = 2kb TCP output buffer,
> ~1Mb/s over 100Mbit; PostgreSQL+Linux = 8kb buffer, ~2.6Mb/s),
> I was ridiculed instead when my php script failed miserably,
> crashing Apache with OOM condition, while alanogous script for Oracle
> ran to completion just fine.

You should have tested the script before showing off :-)  You may want
to convert it to manually use a cursor, at least the Postgres version.
That would alleviate the memory problem.

--
Alvaro Herrera (<alvherre[a]alvh.no-ip.org>)
"Some men are heterosexual, and some are bisexual, and some
men don't think about sex at all... they become lawyers" (Woody Allen)

Re: BUG #1756: PQexec eats huge amounts of memory

From
John R Pierce
Date:
Neil Conway wrote:
> Denis Vlasenko wrote:
>
>> The same php script but done against Oracle does not have this
>> behaviour.
>
>
> Perhaps; presumably Oracle is essentially creating a cursor for you
> behind the scenes. libpq does not attempt to do this automatically; if
> you need a cursor, you can create one by hand.

I do not understand how a cursor could be autocreated by a query like

    $result = pg_query($db, "SELECT * FROM big_table");

php will expect $result to contain the entire table (yuck!).

Re: BUG #1756: PQexec eats huge amounts of memory

From
Alvaro Herrera
Date:
On Thu, Jul 07, 2005 at 08:17:23AM -0700, John R Pierce wrote:
> Neil Conway wrote:
> >Denis Vlasenko wrote:
> >
> >>The same php script but done against Oracle does not have this
> >>behaviour.
> >
> >
> >Perhaps; presumably Oracle is essentially creating a cursor for you
> >behind the scenes. libpq does not attempt to do this automatically; if
> >you need a cursor, you can create one by hand.
>
> I do not understand how a cursor could be autocreated by a query like
>
>     $result = pg_query($db, "SELECT * FROM big_table");
>
> php will expect $result to contain the entire table (yuck!).

Really?  I thought what really happened is you had to get the results
one at a time using the pg_fetch family of functions.  If that is true,
then it's possible to make the driver fake having the whole table by
using a cursor.  (Even if PHP doesn't do it, it's possible for OCI to do
it behind the scenes.)

--
Alvaro Herrera (<alvherre[a]alvh.no-ip.org>)
Officer Krupke, what are we to do?
Gee, officer Krupke, Krup you! (West Side Story, "Gee, Officer Krupke")

Re: BUG #1756: PQexec eats huge amounts of memory

From
Volkan YAZICI
Date:
Hi,

A similar topic has been discussed before on pgsql-sql mailing list:
Subject: SELECT very slow Thomas Kellerer
URL: http://archives.postgresql.org/pgsql-sql/2005-06/msg00118.php

Regards.

On 7/6/05, Denis Vlasenko <vda@ilport.com.ua> wrote:
> Bug reference:      1756
> Logged by:          Denis Vlasenko
> Email address:      vda@ilport.com.ua
> PostgreSQL version: 8.0.1
> Operating system:   Linux
> Description:        PQexec eats huge amounts of memory

Re: BUG #1756: PQexec eats huge amounts of memory

From
Alvaro Herrera
Date:
On Sun, Jul 10, 2005 at 01:05:10PM +0300, Denis Vlasenko wrote:
> On Thursday 07 July 2005 20:43, Alvaro Herrera wrote:

> > Really?  I thought what really happened is you had to get the results
> > one at a time using the pg_fetch family of functions.  If that is true,
> > then it's possible to make the driver fake having the whole table by
> > using a cursor.  (Even if PHP doesn't do it, it's possible for OCI to do
> > it behind the scenes.)
>
> Even without cursor, result can be read incrementally.
>
> I mean, query result is transferred over network, right?
> We just can stop read()'ing before we reached the end of result set,
> and continue at pg_fetch as needed.

It's not that simple.  libpq is designed to read whole result sets at a
time; there's no support for reading incrementally from the server.
Other problem is that neither libpq nor the server know how many tuples
the query will return, until the whole query is executed.  Thus,
pg_numrows (for example) wouldn't work at all, which is a showstopper
for many PHP scripts.

In short, it can be made to work, but it's not as simple as you put it.

--
Alvaro Herrera (<alvherre[a]alvh.no-ip.org>)
"Industry suffers from the managerial dogma that for the sake of stability
and continuity, the company should be independent of the competence of
individual employees."                                      (E. Dijkstra)

Re: BUG #1756: PQexec eats huge amounts of memory

From
Oliver Jowett
Date:
Alvaro Herrera wrote:
> On Sun, Jul 10, 2005 at 01:05:10PM +0300, Denis Vlasenko wrote:
>
>>On Thursday 07 July 2005 20:43, Alvaro Herrera wrote:
>
>
>>>Really?  I thought what really happened is you had to get the results
>>>one at a time using the pg_fetch family of functions.  If that is true,
>>>then it's possible to make the driver fake having the whole table by
>>>using a cursor.  (Even if PHP doesn't do it, it's possible for OCI to do
>>>it behind the scenes.)
>>
>>Even without cursor, result can be read incrementally.
>>
>>I mean, query result is transferred over network, right?
>>We just can stop read()'ing before we reached the end of result set,
>>and continue at pg_fetch as needed.
>
>
> It's not that simple. [...]

It also requires that you assume there is only one set of query results
outstanding at a time. I know that you can't assume that in JDBC, and by
the sounds of it PHP's interface is similar in that you can have
multiple query result objects active at the same time.

-O

Re: BUG #1756: PQexec eats huge amounts of memory

From
Denis Vlasenko
Date:
On Thursday 07 July 2005 20:43, Alvaro Herrera wrote:
> On Thu, Jul 07, 2005 at 08:17:23AM -0700, John R Pierce wrote:
> > Neil Conway wrote:
> > >Denis Vlasenko wrote:
> > >
> > >>The same php script but done against Oracle does not have this
> > >>behaviour.
> > >
> > >
> > >Perhaps; presumably Oracle is essentially creating a cursor for you
> > >behind the scenes. libpq does not attempt to do this automatically; if
> > >you need a cursor, you can create one by hand.
> >
> > I do not understand how a cursor could be autocreated by a query like
> >
> >     $result = pg_query($db, "SELECT * FROM big_table");
> >
> > php will expect $result to contain the entire table (yuck!).
>
> Really?  I thought what really happened is you had to get the results
> one at a time using the pg_fetch family of functions.  If that is true,
> then it's possible to make the driver fake having the whole table by
> using a cursor.  (Even if PHP doesn't do it, it's possible for OCI to do
> it behind the scenes.)

Even without cursor, result can be read incrementally.

I mean, query result is transferred over network, right?
We just can stop read()'ing before we reached the end of result set,
and continue at pg_fetch as needed.

This way server does not need to do any of cursor
creation/destruction work. Not a big win, but combined with
reduced memory usage at client side, it is a win-win situation.
--
vda

Re: BUG #1756: PQexec eats huge amounts of memory

From
Denis Vlasenko
Date:
On Monday 11 July 2005 03:38, Alvaro Herrera wrote:
> On Sun, Jul 10, 2005 at 01:05:10PM +0300, Denis Vlasenko wrote:
> > On Thursday 07 July 2005 20:43, Alvaro Herrera wrote:
>
> > > Really?  I thought what really happened is you had to get the results
> > > one at a time using the pg_fetch family of functions.  If that is true,
> > > then it's possible to make the driver fake having the whole table by
> > > using a cursor.  (Even if PHP doesn't do it, it's possible for OCI to do
> > > it behind the scenes.)
> >
> > Even without cursor, result can be read incrementally.
> >
> > I mean, query result is transferred over network, right?
> > We just can stop read()'ing before we reached the end of result set,
> > and continue at pg_fetch as needed.
>
> It's not that simple.  libpq is designed to read whole result sets at a
> time; there's no support for reading incrementally from the server.
> Other problem is that neither libpq nor the server know how many tuples
> the query will return, until the whole query is executed.  Thus,
> pg_numrows (for example) wouldn't work at all, which is a showstopper
> for many PHP scripts.
>
> In short, it can be made to work, but it's not as simple as you put it.

This sounds reasonable.

Consider my posts in this thread as user wish to

* libpq and network protocol to be changed to allow for incremental reads
  of executed queries and for multiple outstanding result sets,

    or, if above thing looks unsurmountable at the moment,

* libpq-only change as to allow incremental reads of single outstanding
  result set. Attempt to use pg_numrows, etc, or attempt to execute
  another query forces libpq to read and store all remaining rows
  in client's memory (i.e. current behaviour).
--
vda

Re: BUG #1756: PQexec eats huge amounts of memory

From
Tom Lane
Date:
Denis Vlasenko <vda@ilport.com.ua> writes:
> Consider my posts in this thread as user wish to
> * libpq and network protocol to be changed to allow for incremental reads
>   of executed queries and for multiple outstanding result sets,
>     or, if above thing looks unsurmountable at the moment,
> * libpq-only change as to allow incremental reads of single outstanding
>   result set. Attempt to use pg_numrows, etc, or attempt to execute
>   another query forces libpq to read and store all remaining rows
>   in client's memory (i.e. current behaviour).

This isn't going to happen because it would be a fundamental change in
libpq's behavior and would undoubtedly break a lot of applications.
The reason it cannot be done transparently is that you would lose the
guarantee that a query either succeeds or fails: it would be entirely
possible to return some rows to the application and only later get a
failure.

You can have this behavior today, though, as long as you are willing to
work a little harder at it --- just declare some cursors and then FETCH
in convenient chunks from the cursors.

            regards, tom lane

Re: BUG #1756: PQexec eats huge amounts of memory

From
Denis Vlasenko
Date:
On Wednesday 13 July 2005 17:43, Tom Lane wrote:
> Denis Vlasenko <vda@ilport.com.ua> writes:
> > Consider my posts in this thread as user wish to
> > * libpq and network protocol to be changed to allow for incremental reads
> >   of executed queries and for multiple outstanding result sets,
> >     or, if above thing looks unsurmountable at the moment,
> > * libpq-only change as to allow incremental reads of single outstanding
> >   result set. Attempt to use pg_numrows, etc, or attempt to execute
> >   another query forces libpq to read and store all remaining rows
> >   in client's memory (i.e. current behaviour).
>
> This isn't going to happen because it would be a fundamental change in
> libpq's behavior and would undoubtedly break a lot of applications.
> The reason it cannot be done transparently is that you would lose the
> guarantee that a query either succeeds or fails: it would be entirely
> possible to return some rows to the application and only later get a
> failure.
>
> You can have this behavior today, though, as long as you are willing to
> work a little harder at it --- just declare some cursors and then FETCH
> in convenient chunks from the cursors.

Thanks, I already tried that. It works.
--
vda

Re: BUG #1756: PQexec eats huge amounts of memory

From
Tom Lane
Date:
Denis Vlasenko <vda@ilport.com.ua> writes:
> On Wednesday 13 July 2005 17:43, Tom Lane wrote:
>> The reason it cannot be done transparently is that you would lose the
>> guarantee that a query either succeeds or fails: it would be entirely
>> possible to return some rows to the application and only later get a
>> failure.

> What failures are likely?

Consider
    select x, 1/x from foo;

where x is zero in the 10,000'th row ...

            regards, tom lane

Re: BUG #1756: PQexec eats huge amounts of memory

From
"Kevin Grittner"
Date:
Are you saying that if I execute a statement like:

select * from "TableWithHundredsOfMillionsOfRows"

that the entire table will be copied to a result set before returning
the first row?  Is this result set built in RAM on the server side?

I am interested primarily in the behavior under JDBC, although psql is
also significant.  If streaming results to the client as they are
obtained is considered to be something which can only be done by a
cursor (an interpretation I haven't seen in other products), is there
any way to get a SELECT statement to be treated as a cursor without
explicitly issuing DECLARE CURSOR and FETCH statements?  (For example,
some other products take an invocation of the JDBC
Statement.setCursorName method as a clue to use a cursor for a SELECT
using that Statement object, while this method is a no-op for
PostgreSQL's JDBC driver.)

I am fixing some portability flaws in software which has been using
Sybase so that is will also support PostgreSQL.  Under Sybase, results
are always returned as soon as possible (and it is therefore entirely
possible to receive some rows and then receive an exception).  Since our
software needs to be portable enough to support Sybase's optimistic
"streaming" technique, there is no particular benefit for us in the
strategy used by PostgreSQL in this regard, and it may cause problems
with a few of the larger results sets.  I suspect that overall
performance for most queries will be about the same either way, as
PostgreSQL will reduce concurrency on a single request, but is likely to
make up for it by reducing task switching and blocking.

Thanks for any info.

-Kevin


>>> Tom Lane <tgl@sss.pgh.pa.us> 07/13/05 9:43 AM >>>
Denis Vlasenko <vda@ilport.com.ua> writes:
> Consider my posts in this thread as user wish to
> * libpq and network protocol to be changed to allow for incremental
reads
>   of executed queries and for multiple outstanding result sets,
>     or, if above thing looks unsurmountable at the moment,
> * libpq-only change as to allow incremental reads of single
outstanding
>   result set. Attempt to use pg_numrows, etc, or attempt to execute
>   another query forces libpq to read and store all remaining rows
>   in client's memory (i.e. current behaviour).

This isn't going to happen because it would be a fundamental change in
libpq's behavior and would undoubtedly break a lot of applications.
The reason it cannot be done transparently is that you would lose the
guarantee that a query either succeeds or fails: it would be entirely
possible to return some rows to the application and only later get a
failure.

You can have this behavior today, though, as long as you are willing to
work a little harder at it --- just declare some cursors and then FETCH
in convenient chunks from the cursors.

            regards, tom lane

Re: BUG #1756: PQexec eats huge amounts of memory

From
Tom Lane
Date:
"Kevin Grittner" <Kevin.Grittner@wicourts.gov> writes:
> Are you saying that if I execute a statement like:
> select * from "TableWithHundredsOfMillionsOfRows"

> that the entire table will be copied to a result set before returning
> the first row?

libpq does things that way, yes.

> Is this result set built in RAM on the server side?

No.

> I am interested primarily in the behavior under JDBC, although psql is
> also significant.

JDBC does things its own way; I believe it is possible to fetch a
resultset in a streaming fashion in JDBC, but you'd have to ask them.

The point of this discussion is simply that the current libpq API does
not permit streaming resultsets.  This is not a limitation of the
server, nor of the protocol, nor even really of libpq itself if you
were willing to invent a suitable new set of API definitions for a
streaming-resultset interface.  But we aren't going to change the
existing API to do streaming behind the client's back, because it won't
be transparent.

            regards, tom lane

Re: BUG #1756: PQexec eats huge amounts of memory

From
Kris Jurka
Date:
On Mon, 18 Jul 2005, Kevin Grittner wrote:

> Are you saying that if I execute a statement like:
> select * from "TableWithHundredsOfMillionsOfRows"
> that the entire table will be copied to a result set before returning
> the first row?  Is this result set built in RAM on the server side?
>
> I am interested primarily in the behavior under JDBC, although psql is
> also significant.

http://jdbc.postgresql.org/documentation/80/query.html#query-with-cursor

Kris Jurka