Thread: vacuumlo - use a cursor
vacuumlo is rather simpleminded about dealing with the list of LOs to be removed - it just fetches them as a straight resultset. For one of my our this resulted in an out of memory condition. The attached patch tries to remedy that by using a cursor instead. If this is wanted I will add it to the next commitfest. The actualy changes are very small - most of the patch is indentation changes due to the introduction of an extra loop. cheers andrew
Attachment
Is there a reason this patch was not applied? --------------------------------------------------------------------------- On Mon, Nov 12, 2012 at 05:14:57PM -0500, Andrew Dunstan wrote: > vacuumlo is rather simpleminded about dealing with the list of LOs > to be removed - it just fetches them as a straight resultset. For > one of my our this resulted in an out of memory condition. The > attached patch tries to remedy that by using a cursor instead. If > this is wanted I will add it to the next commitfest. The actualy > changes are very small - most of the patch is indentation changes > due to the introduction of an extra loop. > > cheers > > andrew > *** a/contrib/vacuumlo/vacuumlo.c > --- b/contrib/vacuumlo/vacuumlo.c > *************** > *** 290,362 **** vacuumlo(const char *database, const struct _param * param) > PQclear(res); > > buf[0] = '\0'; > ! strcat(buf, "SELECT lo FROM vacuum_l"); > ! res = PQexec(conn, buf); > ! if (PQresultStatus(res) != PGRES_TUPLES_OK) > ! { > ! fprintf(stderr, "Failed to read temp table:\n"); > ! fprintf(stderr, "%s", PQerrorMessage(conn)); > ! PQclear(res); > PQfinish(conn); > return -1; > ! } > > - matched = PQntuples(res); > deleted = 0; > ! for (i = 0; i < matched; i++) > { > ! Oid lo = atooid(PQgetvalue(res, i, 0)); > > ! if (param->verbose) > { > ! fprintf(stdout, "\rRemoving lo %6u ", lo); > ! fflush(stdout); > } > > ! if (param->dry_run == 0) > { > ! if (lo_unlink(conn, lo) < 0) > { > ! fprintf(stderr, "\nFailed to remove lo %u: ", lo); > ! fprintf(stderr, "%s", PQerrorMessage(conn)); > ! if (PQtransactionStatus(conn) == PQTRANS_INERROR) > { > ! success = false; > ! break; > } > } > else > deleted++; > ! } > ! else > ! deleted++; > ! if (param->transaction_limit > 0 && > ! (deleted % param->transaction_limit) == 0) > ! { > ! res2 = PQexec(conn, "commit"); > ! if (PQresultStatus(res2) != PGRES_COMMAND_OK) > { > ! fprintf(stderr, "Failed to commit transaction:\n"); > ! fprintf(stderr, "%s", PQerrorMessage(conn)); > PQclear(res2); > ! PQclear(res); > ! PQfinish(conn); > ! return -1; > ! } > ! PQclear(res2); > ! res2 = PQexec(conn, "begin"); > ! if (PQresultStatus(res2) != PGRES_COMMAND_OK) > ! { > ! fprintf(stderr, "Failed to start transaction:\n"); > ! fprintf(stderr, "%s", PQerrorMessage(conn)); > PQclear(res2); > - PQclear(res); > - PQfinish(conn); > - return -1; > } > - PQclear(res2); > } > } > PQclear(res); > > /* > --- 290,389 ---- > PQclear(res); > > buf[0] = '\0'; > ! strcat(buf, > ! "DECLARE myportal CURSOR WITH HOLD FOR SELECT lo FROM vacuum_l"); > ! res = PQexec(conn, buf); > ! if (PQresultStatus(res) != PGRES_COMMAND_OK) > ! { > ! fprintf(stderr, "DECLARE CURSOR failed: %s", PQerrorMessage(conn)); > ! PQclear(res); > PQfinish(conn); > return -1; > ! } > ! PQclear(res); > ! > ! snprintf(buf, BUFSIZE, "FETCH FORWARD " INT64_FORMAT " IN myportal", > ! param->transaction_limit > 0 ? param->transaction_limit : 1000); > > deleted = 0; > ! > ! while (1) > { > ! res = PQexec(conn, buf); > ! if (PQresultStatus(res) != PGRES_TUPLES_OK) > ! { > ! fprintf(stderr, "Failed to read temp table:\n"); > ! fprintf(stderr, "%s", PQerrorMessage(conn)); > ! PQclear(res); > ! PQfinish(conn); > ! return -1; > ! } > > ! matched = PQntuples(res); > ! > ! if (matched <= 0) > { > ! /* at end of resultset */ > ! break; > } > > ! for (i = 0; i < matched; i++) > { > ! Oid lo = atooid(PQgetvalue(res, i, 0)); > ! > ! if (param->verbose) > ! { > ! fprintf(stdout, "\rRemoving lo %6u ", lo); > ! fflush(stdout); > ! } > ! > ! if (param->dry_run == 0) > { > ! if (lo_unlink(conn, lo) < 0) > { > ! fprintf(stderr, "\nFailed to remove lo %u: ", lo); > ! fprintf(stderr, "%s", PQerrorMessage(conn)); > ! if (PQtransactionStatus(conn) == PQTRANS_INERROR) > ! { > ! success = false; > ! break; > ! } > } > + else > + deleted++; > } > else > deleted++; > ! > ! if (param->transaction_limit > 0 && > ! (deleted % param->transaction_limit) == 0) > { > ! res2 = PQexec(conn, "commit"); > ! if (PQresultStatus(res2) != PGRES_COMMAND_OK) > ! { > ! fprintf(stderr, "Failed to commit transaction:\n"); > ! fprintf(stderr, "%s", PQerrorMessage(conn)); > ! PQclear(res2); > ! PQclear(res); > ! PQfinish(conn); > ! return -1; > ! } > PQclear(res2); > ! res2 = PQexec(conn, "begin"); > ! if (PQresultStatus(res2) != PGRES_COMMAND_OK) > ! { > ! fprintf(stderr, "Failed to start transaction:\n"); > ! fprintf(stderr, "%s", PQerrorMessage(conn)); > ! PQclear(res2); > ! PQclear(res); > ! PQfinish(conn); > ! return -1; > ! } > PQclear(res2); > } > } > } > + > PQclear(res); > > /* > > -- > Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org) > To make changes to your subscription: > http://www.postgresql.org/mailpref/pgsql-hackers -- Bruce Momjian <bruce@momjian.us> http://momjian.us EnterpriseDB http://enterprisedb.com + It's impossible for everything to be true. +
Nobody seemed interested. But I do think it's a good idea still. cheers andrew On 06/29/2013 11:23 AM, Bruce Momjian wrote: > Is there a reason this patch was not applied? > > --------------------------------------------------------------------------- > > On Mon, Nov 12, 2012 at 05:14:57PM -0500, Andrew Dunstan wrote: >> vacuumlo is rather simpleminded about dealing with the list of LOs >> to be removed - it just fetches them as a straight resultset. For >> one of my our this resulted in an out of memory condition. The >> attached patch tries to remedy that by using a cursor instead. If >> this is wanted I will add it to the next commitfest. The actualy >> changes are very small - most of the patch is indentation changes >> due to the introduction of an extra loop. >> >> cheers >> >> andrew >> *** a/contrib/vacuumlo/vacuumlo.c >> --- b/contrib/vacuumlo/vacuumlo.c >> *************** >> *** 290,362 **** vacuumlo(const char *database, const struct _param * param) >> PQclear(res); >> >> buf[0] = '\0'; >> ! strcat(buf, "SELECT lo FROM vacuum_l"); >> ! res = PQexec(conn, buf); >> ! if (PQresultStatus(res) != PGRES_TUPLES_OK) >> ! { >> ! fprintf(stderr, "Failed to read temp table:\n"); >> ! fprintf(stderr, "%s", PQerrorMessage(conn)); >> ! PQclear(res); >> PQfinish(conn); >> return -1; >> ! } >> >> - matched = PQntuples(res); >> deleted = 0; >> ! for (i = 0; i < matched; i++) >> { >> ! Oid lo = atooid(PQgetvalue(res, i, 0)); >> >> ! if (param->verbose) >> { >> ! fprintf(stdout, "\rRemoving lo %6u ", lo); >> ! fflush(stdout); >> } >> >> ! if (param->dry_run == 0) >> { >> ! if (lo_unlink(conn, lo) < 0) >> { >> ! fprintf(stderr, "\nFailed to remove lo %u: ", lo); >> ! fprintf(stderr, "%s", PQerrorMessage(conn)); >> ! if (PQtransactionStatus(conn) == PQTRANS_INERROR) >> { >> ! success = false; >> ! break; >> } >> } >> else >> deleted++; >> ! } >> ! else >> ! deleted++; >> ! if (param->transaction_limit > 0 && >> ! (deleted % param->transaction_limit) == 0) >> ! { >> ! res2 = PQexec(conn, "commit"); >> ! if (PQresultStatus(res2) != PGRES_COMMAND_OK) >> { >> ! fprintf(stderr, "Failed to commit transaction:\n"); >> ! fprintf(stderr, "%s", PQerrorMessage(conn)); >> PQclear(res2); >> ! PQclear(res); >> ! PQfinish(conn); >> ! return -1; >> ! } >> ! PQclear(res2); >> ! res2 = PQexec(conn, "begin"); >> ! if (PQresultStatus(res2) != PGRES_COMMAND_OK) >> ! { >> ! fprintf(stderr, "Failed to start transaction:\n"); >> ! fprintf(stderr, "%s", PQerrorMessage(conn)); >> PQclear(res2); >> - PQclear(res); >> - PQfinish(conn); >> - return -1; >> } >> - PQclear(res2); >> } >> } >> PQclear(res); >> >> /* >> --- 290,389 ---- >> PQclear(res); >> >> buf[0] = '\0'; >> ! strcat(buf, >> ! "DECLARE myportal CURSOR WITH HOLD FOR SELECT lo FROM vacuum_l"); >> ! res = PQexec(conn, buf); >> ! if (PQresultStatus(res) != PGRES_COMMAND_OK) >> ! { >> ! fprintf(stderr, "DECLARE CURSOR failed: %s", PQerrorMessage(conn)); >> ! PQclear(res); >> PQfinish(conn); >> return -1; >> ! } >> ! PQclear(res); >> ! >> ! snprintf(buf, BUFSIZE, "FETCH FORWARD " INT64_FORMAT " IN myportal", >> ! param->transaction_limit > 0 ? param->transaction_limit : 1000); >> >> deleted = 0; >> ! >> ! while (1) >> { >> ! res = PQexec(conn, buf); >> ! if (PQresultStatus(res) != PGRES_TUPLES_OK) >> ! { >> ! fprintf(stderr, "Failed to read temp table:\n"); >> ! fprintf(stderr, "%s", PQerrorMessage(conn)); >> ! PQclear(res); >> ! PQfinish(conn); >> ! return -1; >> ! } >> >> ! matched = PQntuples(res); >> ! >> ! if (matched <= 0) >> { >> ! /* at end of resultset */ >> ! break; >> } >> >> ! for (i = 0; i < matched; i++) >> { >> ! Oid lo = atooid(PQgetvalue(res, i, 0)); >> ! >> ! if (param->verbose) >> ! { >> ! fprintf(stdout, "\rRemoving lo %6u ", lo); >> ! fflush(stdout); >> ! } >> ! >> ! if (param->dry_run == 0) >> { >> ! if (lo_unlink(conn, lo) < 0) >> { >> ! fprintf(stderr, "\nFailed to remove lo %u: ", lo); >> ! fprintf(stderr, "%s", PQerrorMessage(conn)); >> ! if (PQtransactionStatus(conn) == PQTRANS_INERROR) >> ! { >> ! success = false; >> ! break; >> ! } >> } >> + else >> + deleted++; >> } >> else >> deleted++; >> ! >> ! if (param->transaction_limit > 0 && >> ! (deleted % param->transaction_limit) == 0) >> { >> ! res2 = PQexec(conn, "commit"); >> ! if (PQresultStatus(res2) != PGRES_COMMAND_OK) >> ! { >> ! fprintf(stderr, "Failed to commit transaction:\n"); >> ! fprintf(stderr, "%s", PQerrorMessage(conn)); >> ! PQclear(res2); >> ! PQclear(res); >> ! PQfinish(conn); >> ! return -1; >> ! } >> PQclear(res2); >> ! res2 = PQexec(conn, "begin"); >> ! if (PQresultStatus(res2) != PGRES_COMMAND_OK) >> ! { >> ! fprintf(stderr, "Failed to start transaction:\n"); >> ! fprintf(stderr, "%s", PQerrorMessage(conn)); >> ! PQclear(res2); >> ! PQclear(res); >> ! PQfinish(conn); >> ! return -1; >> ! } >> PQclear(res2); >> } >> } >> } >> + >> PQclear(res); >> >> /* >> -- >> Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org) >> To make changes to your subscription: >> http://www.postgresql.org/mailpref/pgsql-hackers >
On Sat, Jun 29, 2013 at 11:33:54AM -0400, Andrew Dunstan wrote: > > Nobody seemed interested. But I do think it's a good idea still. Well, if no one replied, and you thought it was a good idea, then it was a good idea. ;-) -- Bruce Momjian <bruce@momjian.us> http://momjian.us EnterpriseDB http://enterprisedb.com + It's impossible for everything to be true. +
On 06/29/2013 08:35 AM, Bruce Momjian wrote: > > On Sat, Jun 29, 2013 at 11:33:54AM -0400, Andrew Dunstan wrote: >> >> Nobody seemed interested. But I do think it's a good idea still. > > Well, if no one replied, and you thought it was a good idea, then it was > a good idea. ;-) > I think it is a good idea just of limited use. I only have one customer that still uses large objects. Which is a shame really as they are more efficient that bytea. JD -- Command Prompt, Inc. - http://www.commandprompt.com/ 509-416-6579 PostgreSQL Support, Training, Professional Services and Development High Availability, Oracle Conversion, Postgres-XC, @cmdpromptinc For my dreams of your image that blossoms a rose in the deeps of my heart. - W.B. Yeats
On 06/29/2013 11:35 AM, Bruce Momjian wrote: > On Sat, Jun 29, 2013 at 11:33:54AM -0400, Andrew Dunstan wrote: >> Nobody seemed interested. But I do think it's a good idea still. > Well, if no one replied, and you thought it was a good idea, then it was > a good idea. ;-) > I try not to assume that even if I think it's a good idea it will be generally wanted unless at least one other person speaks up. But now that Joshua has I will revive it and add it to the next commitfest. cheers andrew
On Sat, Jun 29, 2013 at 12:21:11PM -0400, Andrew Dunstan wrote: > > On 06/29/2013 11:35 AM, Bruce Momjian wrote: > >On Sat, Jun 29, 2013 at 11:33:54AM -0400, Andrew Dunstan wrote: > >>Nobody seemed interested. But I do think it's a good idea still. > >Well, if no one replied, and you thought it was a good idea, then it was > >a good idea. ;-) > > > > > I try not to assume that even if I think it's a good idea it will be > generally wanted unless at least one other person speaks up. But now > that Joshua has I will revive it and add it to the next commitfest. Great. -- Bruce Momjian <bruce@momjian.us> http://momjian.us EnterpriseDB http://enterprisedb.com + It's impossible for everything to be true. +
On Mon, Nov 12, 2012 at 5:14 PM, Andrew Dunstan <andrew@dunslane.net> wrote: > vacuumlo is rather simpleminded about dealing with the list of LOs to be > removed - it just fetches them as a straight resultset. For one of my our > this resulted in an out of memory condition. Wow, they must have had a ton of LOs. With about 2M entries to pull from vacuum_l, I observed unpatched vacuumlo using only about 45MB RES. Still, the idea of using a cursor for the main loop seems like a reasonable idea. > The attached patch tries to > remedy that by using a cursor instead. If this is wanted I will add it to > the next commitfest. The actualy changes are very small - most of the patch > is indentation changes due to the introduction of an extra loop. I had some time to review this, some comments about the patch: 1. I see this new compiler warning: vacuumlo.c: In function ‘vacuumlo’: vacuumlo.c:306:5: warning: format ‘%lld’ expects argument of type ‘long long int’, but argument 4 has type ‘long int’ [-Wformat] 2. It looks like the the patch causes all the intermediate result-sets fetched from the cursor to be leaked, rather negating its stated purpose ;-) The PQclear() call should be moved up into the main loop. With this fixed, I confirmed that vacuumlo now consumes a negligible amount of memory when chewing through millions of entries. 3. A few extra trailing whitespaces were added. 4. The FETCH FORWARD count comes from transaction_limit. That seems like a good-enough guesstimate, but maybe a comment could be added to rationalize? Some suggested changes attached with v2 patch (all except #4). Josh
Attachment
On Sun, Jul 7, 2013 at 9:30 AM, Josh Kupershmidt <schmiddy@gmail.com> wrote: > On Mon, Nov 12, 2012 at 5:14 PM, Andrew Dunstan <andrew@dunslane.net> wrote: >> vacuumlo is rather simpleminded about dealing with the list of LOs to be >> removed - it just fetches them as a straight resultset. For one of my our >> this resulted in an out of memory condition. > > Wow, they must have had a ton of LOs. With about 2M entries to pull > from vacuum_l, I observed unpatched vacuumlo using only about 45MB > RES. Still, the idea of using a cursor for the main loop seems like a > reasonable idea. > >> The attached patch tries to >> remedy that by using a cursor instead. If this is wanted I will add it to >> the next commitfest. The actualy changes are very small - most of the patch >> is indentation changes due to the introduction of an extra loop. > > I had some time to review this, some comments about the patch: > > 1. I see this new compiler warning: > > vacuumlo.c: In function ‘vacuumlo’: > vacuumlo.c:306:5: warning: format ‘%lld’ expects argument of type > ‘long long int’, but argument 4 has type ‘long int’ [-Wformat] > > 2. It looks like the the patch causes all the intermediate result-sets > fetched from the cursor to be leaked, rather negating its stated > purpose ;-) The PQclear() call should be moved up into the main loop. > With this fixed, I confirmed that vacuumlo now consumes a negligible > amount of memory when chewing through millions of entries. > > 3. A few extra trailing whitespaces were added. > > 4. The FETCH FORWARD count comes from transaction_limit. That seems > like a good-enough guesstimate, but maybe a comment could be added to > rationalize? > > Some suggested changes attached with v2 patch (all except #4). I've committed this version of the patch, with a slight change to one of the error messages. -- Robert Haas EnterpriseDB: http://www.enterprisedb.com The Enterprise PostgreSQL Company