On 2014-11-01 18:23:47 +0100, Andres Freund wrote: > On 2014-11-01 12:57:40 -0400, Tom Lane wrote: > > Andres Freund <and...@2ndquadrant.com> writes: > > > On 2014-10-31 18:48:45 -0400, Tom Lane wrote: > > >> While the basic idea is sound, this particular implementation seems > > >> pretty bizarre. What's with the "md_seg_no" stuff, and why is that > > >> array typed size_t? > > > > > It stores the length of the array of _MdfdVec entries. > > > > Oh. "seg_no" seems like not a very good choice of name then. > > Perhaps "md_seg_count" or something like that would be more intelligible. > > That's fine with me.
Went with md_num_open_segs - reading the code that was easier to understand. > So I'll repost a version with those fixes. Took a while. But here we go. The attached version is a significantly revised version of my earlier patch. Notably I've pretty much entirely revised the code in _mdfd_getseg() to be more similar to the state in master. Also some comment policing. As it's more than a bit painful to test with large numbers of 1GB segments, I've rebuilt my local postgres with 100kb segments. Running a pgbench -s 300 with 128MB shared buffers clearly shows the efficiency differnces: 320tps vs 4900 in an assertion enabled build. Obviously that's kind of an artificial situation... But I've also verified this with a) fake relations built out of sparse files, b) actually large relations. The latter shows performance benefits as well, but my patience limits the amount of testing I was willing to do... Kevin, Robert: I've CCed you because Robert commented in the recent mdnblocks() blocks thread that Kevin lamented the O(n)'ness of md.c... Andres
>From a0b30154714df3e13bb668fa9590f7be40e4de35 Mon Sep 17 00:00:00 2001 From: Andres Freund <and...@anarazel.de> Date: Tue, 15 Dec 2015 19:01:36 +0100 Subject: [PATCH 1/2] Faster PageIsVerified() for the all zeroes case. --- src/backend/storage/page/bufpage.c | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/backend/storage/page/bufpage.c b/src/backend/storage/page/bufpage.c index df77bb2..de9800f 100644 --- a/src/backend/storage/page/bufpage.c +++ b/src/backend/storage/page/bufpage.c @@ -81,7 +81,7 @@ bool PageIsVerified(Page page, BlockNumber blkno) { PageHeader p = (PageHeader) page; - char *pagebytes; + size_t *pagebytes; int i; bool checksum_failure = false; bool header_sane = false; @@ -118,10 +118,17 @@ PageIsVerified(Page page, BlockNumber blkno) return true; } - /* Check all-zeroes case */ + /* + * Check all-zeroes case. Luckily BLCKSZ is guaranteed to always be a + * multiple of size_t - and it's much faster compare memory using the + * processor's native word size. + */ + StaticAssertStmt(BLCKSZ == (BLCKSZ / sizeof(size_t)) * sizeof(size_t), + "BLCKSZ has to be a multiple of sizeof(size_t)"); + all_zeroes = true; - pagebytes = (char *) page; - for (i = 0; i < BLCKSZ; i++) + pagebytes = (size_t *) page; + for (i = 0; i < (BLCKSZ / sizeof(size_t)); i++) { if (pagebytes[i] != 0) { -- 2.6.0.rc3
>From 15150d1ba7f17d3a241be3073a03e6bb9a36a937 Mon Sep 17 00:00:00 2001 From: Andres Freund <and...@anarazel.de> Date: Tue, 15 Dec 2015 19:01:36 +0100 Subject: [PATCH 2/2] Improve scalability of md.c for large relations. Previously several routines in md.c were O(#segments) - a problem these days, where it's not uncommon to have tables in the multi TB range. Replace the linked list of segments hanging of SMgrRelationData with an array of opened segments. That allows O(1) access to individual segments, if they've previously been opened. --- src/backend/storage/smgr/md.c | 337 +++++++++++++++++++++++----------------- src/backend/storage/smgr/smgr.c | 4 +- src/include/storage/smgr.h | 8 +- 3 files changed, 200 insertions(+), 149 deletions(-) diff --git a/src/backend/storage/smgr/md.c b/src/backend/storage/smgr/md.c index 42a43bb..cb163f4 100644 --- a/src/backend/storage/smgr/md.c +++ b/src/backend/storage/smgr/md.c @@ -92,27 +92,23 @@ * out to an unlinked old copy of a segment file that will eventually * disappear. * - * The file descriptor pointer (md_fd field) stored in the SMgrRelation - * cache is, therefore, just the head of a list of MdfdVec objects, one - * per segment. But note the md_fd pointer can be NULL, indicating - * relation not open. + * File descriptors are stored in md_seg_fds arrays inside SMgrRelation. The + * length of these arrays is stored in md_num_open_segs. Note that + * md_num_open_segs having a specific value does not necessarily mean the + * relation doesn't have additional segments; we may just not have opened the + * next segment yet. (We could not have "all segments are in the array" as + * an invariant anyway, since another backend could extend the relation while + * we aren't looking.) We do not have entries for inactive segments, + * however; as soon as we find a partial segment, we assume that any + * subsequent segments are inactive. * - * Also note that mdfd_chain == NULL does not necessarily mean the relation - * doesn't have another segment after this one; we may just not have - * opened the next segment yet. (We could not have "all segments are - * in the chain" as an invariant anyway, since another backend could - * extend the relation when we weren't looking.) We do not make chain - * entries for inactive segments, however; as soon as we find a partial - * segment, we assume that any subsequent segments are inactive. - * - * All MdfdVec objects are palloc'd in the MdCxt memory context. + * The entire MdfdVec array is palloc'd in the MdCxt memory context. */ typedef struct _MdfdVec { File mdfd_vfd; /* fd number in fd.c's pool */ BlockNumber mdfd_segno; /* segment number, from 0 */ - struct _MdfdVec *mdfd_chain; /* next segment, or NULL */ } MdfdVec; static MemoryContext MdCxt; /* context for all MdfdVec objects */ @@ -178,7 +174,9 @@ static MdfdVec *mdopen(SMgrRelation reln, ForkNumber forknum, static void register_dirty_segment(SMgrRelation reln, ForkNumber forknum, MdfdVec *seg); static void register_unlink(RelFileNodeBackend rnode); -static MdfdVec *_fdvec_alloc(void); +static void _fdvec_resize(SMgrRelation reln, + ForkNumber forknum, + int nseg); static char *_mdfd_segpath(SMgrRelation reln, ForkNumber forknum, BlockNumber segno); static MdfdVec *_mdfd_openseg(SMgrRelation reln, ForkNumber forkno, @@ -287,13 +285,14 @@ mdexists(SMgrRelation reln, ForkNumber forkNum) void mdcreate(SMgrRelation reln, ForkNumber forkNum, bool isRedo) { + MdfdVec *mdfd; char *path; File fd; - if (isRedo && reln->md_fd[forkNum] != NULL) + if (isRedo && reln->md_num_open_segs[forkNum] > 0) return; /* created and opened already... */ - Assert(reln->md_fd[forkNum] == NULL); + Assert(reln->md_num_open_segs[forkNum] == 0); path = relpath(reln->smgr_rnode, forkNum); @@ -323,11 +322,10 @@ mdcreate(SMgrRelation reln, ForkNumber forkNum, bool isRedo) pfree(path); - reln->md_fd[forkNum] = _fdvec_alloc(); - - reln->md_fd[forkNum]->mdfd_vfd = fd; - reln->md_fd[forkNum]->mdfd_segno = 0; - reln->md_fd[forkNum]->mdfd_chain = NULL; + _fdvec_resize(reln, forkNum, 1); + mdfd = &reln->md_seg_fds[forkNum][0]; + mdfd->mdfd_vfd = fd; + mdfd->mdfd_segno = 0; } /* @@ -572,8 +570,8 @@ mdopen(SMgrRelation reln, ForkNumber forknum, ExtensionBehavior behavior) File fd; /* No work if already open */ - if (reln->md_fd[forknum]) - return reln->md_fd[forknum]; + if (reln->md_num_open_segs[forknum] > 0) + return &reln->md_seg_fds[forknum][0]; path = relpath(reln->smgr_rnode, forknum); @@ -605,11 +603,11 @@ mdopen(SMgrRelation reln, ForkNumber forknum, ExtensionBehavior behavior) pfree(path); - reln->md_fd[forknum] = mdfd = _fdvec_alloc(); - + _fdvec_resize(reln, forknum, 1); + mdfd = &reln->md_seg_fds[forknum][0]; mdfd->mdfd_vfd = fd; mdfd->mdfd_segno = 0; - mdfd->mdfd_chain = NULL; + Assert(_mdnblocks(reln, forknum, mdfd) <= ((BlockNumber) RELSEG_SIZE)); return mdfd; @@ -621,25 +619,26 @@ mdopen(SMgrRelation reln, ForkNumber forknum, ExtensionBehavior behavior) void mdclose(SMgrRelation reln, ForkNumber forknum) { - MdfdVec *v = reln->md_fd[forknum]; + int nopensegs = reln->md_num_open_segs[forknum]; /* No work if already closed */ - if (v == NULL) + if (nopensegs == 0) return; - reln->md_fd[forknum] = NULL; /* prevent dangling pointer after error */ - - while (v != NULL) + /* close segments starting from the end */ + while (nopensegs > 0) { - MdfdVec *ov = v; + MdfdVec *v = &reln->md_seg_fds[forknum][nopensegs - 1]; /* if not closed already */ if (v->mdfd_vfd >= 0) FileClose(v->mdfd_vfd); - /* Now free vector */ - v = v->mdfd_chain; - pfree(ov); + + nopensegs--; } + + /* resize just once, avoids pointless reallocations */ + _fdvec_resize(reln, forknum, 0); } /* @@ -802,9 +801,9 @@ mdwrite(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, * mdnblocks() -- Get the number of blocks stored in a relation. * * Important side effect: all active segments of the relation are opened - * and added to the mdfd_chain list. If this routine has not been + * and added to the mdfd_seg_fds array. If this routine has not been * called, then only segments up to the last one actually touched - * are present in the chain. + * are present in the array. */ BlockNumber mdnblocks(SMgrRelation reln, ForkNumber forknum) @@ -813,24 +812,25 @@ mdnblocks(SMgrRelation reln, ForkNumber forknum) BlockNumber nblocks; BlockNumber segno = 0; + /* mdopen has opened the first segment */ + Assert(reln->md_num_open_segs[forknum] > 0); + /* - * Skip through any segments that aren't the last one, to avoid redundant - * seeks on them. We have previously verified that these segments are - * exactly RELSEG_SIZE long, and it's useless to recheck that each time. + * Start from the last open segments, to avoid redundant seeks. We have + * previously verified that these segments are exactly RELSEG_SIZE long, + * and it's useless to recheck that each time. XXX: No, we really + * haven't, _mdfd_getseg() doesn't do such checks. * * NOTE: this assumption could only be wrong if another backend has * truncated the relation. We rely on higher code levels to handle that * scenario by closing and re-opening the md fd, which is handled via * relcache flush. (Since the checkpointer doesn't participate in - * relcache flush, it could have segment chain entries for inactive - * segments; that's OK because the checkpointer never needs to compute - * relation size.) + * relcache flush, it could have segment entries for inactive segments; + * that's OK because the checkpointer never needs to compute relation + * size.) */ - while (v->mdfd_chain != NULL) - { - segno++; - v = v->mdfd_chain; - } + segno = reln->md_num_open_segs[forknum] - 1; + v = &reln->md_seg_fds[forknum][segno]; for (;;) { @@ -845,23 +845,17 @@ mdnblocks(SMgrRelation reln, ForkNumber forknum) */ segno++; - if (v->mdfd_chain == NULL) - { - /* - * Because we pass O_CREAT, we will create the next segment (with - * zero length) immediately, if the last segment is of length - * RELSEG_SIZE. While perhaps not strictly necessary, this keeps - * the logic simple. - */ - v->mdfd_chain = _mdfd_openseg(reln, forknum, segno, O_CREAT); - if (v->mdfd_chain == NULL) - ereport(ERROR, - (errcode_for_file_access(), - errmsg("could not open file \"%s\": %m", - _mdfd_segpath(reln, forknum, segno)))); - } - - v = v->mdfd_chain; + /* + * Because we pass O_CREAT, we will create the next segment (with zero + * length) immediately, if the last segment is of length RELSEG_SIZE. + * While perhaps not strictly necessary, this keeps the logic simple. + */ + v = _mdfd_openseg(reln, forknum, segno, O_CREAT); + if (v == NULL) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not open file \"%s\": %m", + _mdfd_segpath(reln, forknum, segno)))); } } @@ -871,9 +865,9 @@ mdnblocks(SMgrRelation reln, ForkNumber forknum) void mdtruncate(SMgrRelation reln, ForkNumber forknum, BlockNumber nblocks) { - MdfdVec *v; BlockNumber curnblk; BlockNumber priorblocks; + int curopensegs; /* * NOTE: mdnblocks makes sure we have opened all active segments, so that @@ -893,19 +887,24 @@ mdtruncate(SMgrRelation reln, ForkNumber forknum, BlockNumber nblocks) if (nblocks == curnblk) return; /* no work */ - v = mdopen(reln, forknum, EXTENSION_FAIL); - - priorblocks = 0; - while (v != NULL) + /* + * Truncate segments, starting at the last one. Starting at the end makes + * managing the memory for the fd array easier should there be errors. + */ + curopensegs = reln->md_num_open_segs[forknum]; + while (curopensegs > 0) { - MdfdVec *ov = v; + MdfdVec *v; + + priorblocks = (curopensegs - 1) * RELSEG_SIZE; + + v = &reln->md_seg_fds[forknum][curopensegs - 1]; if (priorblocks > nblocks) { /* - * This segment is no longer active (and has already been unlinked - * from the mdfd_chain). We truncate the file, but do not delete - * it, for reasons explained in the header comments. + * This segment is no longer active. We truncate the file, but do + * not delete it, for reasons explained in the header comments. */ if (FileTruncate(v->mdfd_vfd, 0) < 0) ereport(ERROR, @@ -915,20 +914,20 @@ mdtruncate(SMgrRelation reln, ForkNumber forknum, BlockNumber nblocks) if (!SmgrIsTemp(reln)) register_dirty_segment(reln, forknum, v); - v = v->mdfd_chain; - Assert(ov != reln->md_fd[forknum]); /* we never drop the 1st - * segment */ - pfree(ov); + + /* we never drop the 1st segment */ + Assert(v != &reln->md_seg_fds[forknum][0]); + + _fdvec_resize(reln, forknum, curopensegs - 1); } else if (priorblocks + ((BlockNumber) RELSEG_SIZE) > nblocks) { /* * This is the last segment we want to keep. Truncate the file to - * the right length, and clear chain link that points to any - * remaining segments (which we shall zap). NOTE: if nblocks is - * exactly a multiple K of RELSEG_SIZE, we will truncate the K+1st - * segment to 0 length but keep it. This adheres to the invariant - * given in the header comments. + * the right length. NOTE: if nblocks is exactly a multiple K of + * RELSEG_SIZE, we will truncate the K+1st segment to 0 length but + * keep it. This adheres to the invariant given in the header + * comments. */ BlockNumber lastsegblocks = nblocks - priorblocks; @@ -940,18 +939,16 @@ mdtruncate(SMgrRelation reln, ForkNumber forknum, BlockNumber nblocks) nblocks))); if (!SmgrIsTemp(reln)) register_dirty_segment(reln, forknum, v); - v = v->mdfd_chain; - ov->mdfd_chain = NULL; } else { /* - * We still need this segment and 0 or more blocks beyond it, so - * nothing to do here. + * We still need this segment, so nothing to do for this and any + * earlier segment. */ - v = v->mdfd_chain; + break; } - priorblocks += RELSEG_SIZE; + curopensegs--; } } @@ -964,7 +961,7 @@ mdtruncate(SMgrRelation reln, ForkNumber forknum, BlockNumber nblocks) void mdimmedsync(SMgrRelation reln, ForkNumber forknum) { - MdfdVec *v; + int segno; /* * NOTE: mdnblocks makes sure we have opened all active segments, so that @@ -972,16 +969,18 @@ mdimmedsync(SMgrRelation reln, ForkNumber forknum) */ mdnblocks(reln, forknum); - v = mdopen(reln, forknum, EXTENSION_FAIL); + segno = reln->md_num_open_segs[forknum]; - while (v != NULL) + while (segno > 0) { + MdfdVec *v = &reln->md_seg_fds[forknum][segno - 1]; + if (FileSync(v->mdfd_vfd) < 0) ereport(ERROR, (errcode_for_file_access(), errmsg("could not fsync file \"%s\": %m", FilePathName(v->mdfd_vfd)))); - v = v->mdfd_chain; + segno--; } } @@ -1642,12 +1641,34 @@ ForgetDatabaseFsyncRequests(Oid dbid) /* - * _fdvec_alloc() -- Make a MdfdVec object. + * _fdvec_resize() -- Resize the fork's open segments array */ -static MdfdVec * -_fdvec_alloc(void) +static void +_fdvec_resize(SMgrRelation reln, + ForkNumber forknum, + int nseg) { - return (MdfdVec *) MemoryContextAlloc(MdCxt, sizeof(MdfdVec)); + if (nseg == 0) + { + if (reln->md_num_open_segs[forknum] > 0) + { + pfree(reln->md_seg_fds[forknum]); + reln->md_seg_fds[forknum] = NULL; + } + } + else if (reln->md_num_open_segs[forknum] == 0) + { + reln->md_seg_fds[forknum] = + MemoryContextAlloc(MdCxt, sizeof(MdfdVec) * nseg); + } + else + { + reln->md_seg_fds[forknum] = + repalloc(reln->md_seg_fds[forknum], + sizeof(MdfdVec) * nseg); + } + + reln->md_num_open_segs[forknum] = nseg; } /* @@ -1695,13 +1716,14 @@ _mdfd_openseg(SMgrRelation reln, ForkNumber forknum, BlockNumber segno, if (fd < 0) return NULL; - /* allocate an mdfdvec entry for it */ - v = _fdvec_alloc(); + if (segno <= reln->md_num_open_segs[forknum]) + _fdvec_resize(reln, forknum, segno + 1); /* fill the entry */ + v = &reln->md_seg_fds[forknum][segno]; v->mdfd_vfd = fd; v->mdfd_segno = segno; - v->mdfd_chain = NULL; + Assert(_mdnblocks(reln, forknum, v) <= ((BlockNumber) RELSEG_SIZE)); /* all done */ @@ -1720,66 +1742,91 @@ static MdfdVec * _mdfd_getseg(SMgrRelation reln, ForkNumber forknum, BlockNumber blkno, bool skipFsync, ExtensionBehavior behavior) { - MdfdVec *v = mdopen(reln, forknum, behavior); + MdfdVec *v; BlockNumber targetseg; BlockNumber nextsegno; - if (!v) - return NULL; /* only possible if EXTENSION_RETURN_NULL */ - targetseg = blkno / ((BlockNumber) RELSEG_SIZE); - for (nextsegno = 1; nextsegno <= targetseg; nextsegno++) + + /* if an existing and opened segment, we're done */ + if (targetseg < reln->md_num_open_segs[forknum]) + { + v = &reln->md_seg_fds[forknum][targetseg]; + return v; + } + + /* + * The target segment is not yet open. Iterate over all the segments + * between the last opened and the target segment. This way missing + * segments either raise an error, or get created (according to + * 'behavior'). Start with either the last opened, or the first segment if + * none was opened before. + */ + if (reln->md_num_open_segs[forknum] > 0) + v = &reln->md_seg_fds[forknum][reln->md_num_open_segs[forknum] - 1]; + else + { + v = mdopen(reln, forknum, behavior); + if (!v) + return NULL; /* only possible if EXTENSION_RETURN_NULL */ + } + + for (nextsegno = reln->md_num_open_segs[forknum]; + nextsegno <= targetseg; nextsegno++) { Assert(nextsegno == v->mdfd_segno + 1); - if (v->mdfd_chain == NULL) + /* + * Normally we will create new segments only if authorized by the + * caller (i.e., we are doing mdextend()). But when doing WAL + * recovery, create segments anyway; this allows cases such as + * replaying WAL data that has a write into a high-numbered segment of + * a relation that was later deleted. We want to go ahead and create + * the segments so we can finish out the replay. + * + * We have to maintain the invariant that segments before the last + * active segment are of size RELSEG_SIZE; therefore, pad them out + * with zeroes if needed. (This only matters if caller is extending + * the relation discontiguously, but that can happen in hash indexes.) + */ + if (behavior == EXTENSION_CREATE || InRecovery) { - /* - * Normally we will create new segments only if authorized by the - * caller (i.e., we are doing mdextend()). But when doing WAL - * recovery, create segments anyway; this allows cases such as - * replaying WAL data that has a write into a high-numbered - * segment of a relation that was later deleted. We want to go - * ahead and create the segments so we can finish out the replay. - * - * We have to maintain the invariant that segments before the last - * active segment are of size RELSEG_SIZE; therefore, pad them out - * with zeroes if needed. (This only matters if caller is - * extending the relation discontiguously, but that can happen in - * hash indexes.) - */ - if (behavior == EXTENSION_CREATE || InRecovery) + if (_mdnblocks(reln, forknum, v) < RELSEG_SIZE) { - if (_mdnblocks(reln, forknum, v) < RELSEG_SIZE) - { - char *zerobuf = palloc0(BLCKSZ); + char *zerobuf = palloc0(BLCKSZ); - mdextend(reln, forknum, - nextsegno * ((BlockNumber) RELSEG_SIZE) - 1, - zerobuf, skipFsync); - pfree(zerobuf); - } - v->mdfd_chain = _mdfd_openseg(reln, forknum, +nextsegno, O_CREAT); + mdextend(reln, forknum, + nextsegno * ((BlockNumber) RELSEG_SIZE) - 1, + zerobuf, skipFsync); + pfree(zerobuf); } - else - { - /* We won't create segment if not existent */ - v->mdfd_chain = _mdfd_openseg(reln, forknum, nextsegno, 0); - } - if (v->mdfd_chain == NULL) - { - if (behavior == EXTENSION_RETURN_NULL && - FILE_POSSIBLY_DELETED(errno)) - return NULL; - ereport(ERROR, - (errcode_for_file_access(), + + v = _mdfd_openseg(reln, forknum, nextsegno, O_CREAT); + } + else + { + /* + * XXX: we should really be checking the segment size of 'v' to be + * RELSEG_SIZE here, before jumping to the next segment. + */ + + /* We won't create segment if not existent */ + v = _mdfd_openseg(reln, forknum, nextsegno, 0); + } + + if (v == NULL) + { + if (behavior == EXTENSION_RETURN_NULL && + FILE_POSSIBLY_DELETED(errno)) + return NULL; + ereport(ERROR, + (errcode_for_file_access(), errmsg("could not open file \"%s\" (target block %u): %m", _mdfd_segpath(reln, forknum, nextsegno), blkno))); - } } - v = v->mdfd_chain; } + return v; } diff --git a/src/backend/storage/smgr/smgr.c b/src/backend/storage/smgr/smgr.c index 244b4ea..92964dc 100644 --- a/src/backend/storage/smgr/smgr.c +++ b/src/backend/storage/smgr/smgr.c @@ -172,7 +172,7 @@ smgropen(RelFileNode rnode, BackendId backend) /* mark it not open */ for (forknum = 0; forknum <= MAX_FORKNUM; forknum++) - reln->md_fd[forknum] = NULL; + reln->md_num_open_segs[forknum] = 0; /* it has no owner yet */ add_to_unowned_list(reln); @@ -377,7 +377,7 @@ smgrcreate(SMgrRelation reln, ForkNumber forknum, bool isRedo) * Exit quickly in WAL replay mode if we've already opened the file. If * it's open, it surely must exist. */ - if (isRedo && reln->md_fd[forknum] != NULL) + if (isRedo && reln->md_num_open_segs[forknum] > 0) return; /* diff --git a/src/include/storage/smgr.h b/src/include/storage/smgr.h index 69a624f..ba09aee 100644 --- a/src/include/storage/smgr.h +++ b/src/include/storage/smgr.h @@ -64,8 +64,12 @@ typedef struct SMgrRelationData */ int smgr_which; /* storage manager selector */ - /* for md.c; NULL for forks that are not open */ - struct _MdfdVec *md_fd[MAX_FORKNUM + 1]; + /* + * for md.c; per-fork arrays of the number of open segments + * (md_num_open_segs) and the segments themselves (md_seg_fds). + */ + int md_num_open_segs[MAX_FORKNUM + 1]; + struct _MdfdVec *md_seg_fds[MAX_FORKNUM + 1]; /* if unowned, list link in list of all unowned SMgrRelations */ struct SMgrRelationData *next_unowned_reln; -- 2.6.0.rc3
-- Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org) To make changes to your subscription: http://www.postgresql.org/mailpref/pgsql-hackers