mirror of
https://github.com/postgres/postgres.git
synced 2025-11-09 06:21:09 +03:00
Too allow table accesses to be not directly dependent on heap, several
new abstractions are needed. Specifically:
1) Heap scans need to be generalized into table scans. Do this by
introducing TableScanDesc, which will be the "base class" for
individual AMs. This contains the AM independent fields from
HeapScanDesc.
The previous heap_{beginscan,rescan,endscan} et al. have been
replaced with a table_ version.
There's no direct replacement for heap_getnext(), as that returned
a HeapTuple, which is undesirable for a other AMs. Instead there's
table_scan_getnextslot(). But note that heap_getnext() lives on,
it's still used widely to access catalog tables.
This is achieved by new scan_begin, scan_end, scan_rescan,
scan_getnextslot callbacks.
2) The portion of parallel scans that's shared between backends need
to be able to do so without the user doing per-AM work. To achieve
that new parallelscan_{estimate, initialize, reinitialize}
callbacks are introduced, which operate on a new
ParallelTableScanDesc, which again can be subclassed by AMs.
As it is likely that several AMs are going to be block oriented,
block oriented callbacks that can be shared between such AMs are
provided and used by heap. table_block_parallelscan_{estimate,
intiialize, reinitialize} as callbacks, and
table_block_parallelscan_{nextpage, init} for use in AMs. These
operate on a ParallelBlockTableScanDesc.
3) Index scans need to be able to access tables to return a tuple, and
there needs to be state across individual accesses to the heap to
store state like buffers. That's now handled by introducing a
sort-of-scan IndexFetchTable, which again is intended to be
subclassed by individual AMs (for heap IndexFetchHeap).
The relevant callbacks for an AM are index_fetch_{end, begin,
reset} to create the necessary state, and index_fetch_tuple to
retrieve an indexed tuple. Note that index_fetch_tuple
implementations need to be smarter than just blindly fetching the
tuples for AMs that have optimizations similar to heap's HOT - the
currently alive tuple in the update chain needs to be fetched if
appropriate.
Similar to table_scan_getnextslot(), it's undesirable to continue
to return HeapTuples. Thus index_fetch_heap (might want to rename
that later) now accepts a slot as an argument. Core code doesn't
have a lot of call sites performing index scans without going
through the systable_* API (in contrast to loads of heap_getnext
calls and working directly with HeapTuples).
Index scans now store the result of a search in
IndexScanDesc->xs_heaptid, rather than xs_ctup->t_self. As the
target is not generally a HeapTuple anymore that seems cleaner.
To be able to sensible adapt code to use the above, two further
callbacks have been introduced:
a) slot_callbacks returns a TupleTableSlotOps* suitable for creating
slots capable of holding a tuple of the AMs
type. table_slot_callbacks() and table_slot_create() are based
upon that, but have additional logic to deal with views, foreign
tables, etc.
While this change could have been done separately, nearly all the
call sites that needed to be adapted for the rest of this commit
also would have been needed to be adapted for
table_slot_callbacks(), making separation not worthwhile.
b) tuple_satisfies_snapshot checks whether the tuple in a slot is
currently visible according to a snapshot. That's required as a few
places now don't have a buffer + HeapTuple around, but a
slot (which in heap's case internally has that information).
Additionally a few infrastructure changes were needed:
I) SysScanDesc, as used by systable_{beginscan, getnext} et al. now
internally uses a slot to keep track of tuples. While
systable_getnext() still returns HeapTuples, and will so for the
foreseeable future, the index API (see 1) above) now only deals with
slots.
The remainder, and largest part, of this commit is then adjusting all
scans in postgres to use the new APIs.
Author: Andres Freund, Haribabu Kommi, Alvaro Herrera
Discussion:
https://postgr.es/m/20180703070645.wchpu5muyto5n647@alap3.anarazel.de
https://postgr.es/m/20160812231527.GA690404@alvherre.pgsql
261 lines
7.2 KiB
C
261 lines
7.2 KiB
C
/*-------------------------------------------------------------------------
|
|
*
|
|
* system.c
|
|
* support routines for SYSTEM tablesample method
|
|
*
|
|
* To ensure repeatability of samples, it is necessary that selection of a
|
|
* given tuple be history-independent; otherwise syncscanning would break
|
|
* repeatability, to say nothing of logically-irrelevant maintenance such
|
|
* as physical extension or shortening of the relation.
|
|
*
|
|
* To achieve that, we proceed by hashing each candidate block number together
|
|
* with the active seed, and then selecting it if the hash is less than the
|
|
* cutoff value computed from the selection probability by BeginSampleScan.
|
|
*
|
|
*
|
|
* Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group
|
|
* Portions Copyright (c) 1994, Regents of the University of California
|
|
*
|
|
* IDENTIFICATION
|
|
* src/backend/access/tablesample/system.c
|
|
*
|
|
*-------------------------------------------------------------------------
|
|
*/
|
|
|
|
#include "postgres.h"
|
|
|
|
#include <math.h>
|
|
|
|
#include "access/heapam.h"
|
|
#include "access/relscan.h"
|
|
#include "access/tsmapi.h"
|
|
#include "catalog/pg_type.h"
|
|
#include "optimizer/optimizer.h"
|
|
#include "utils/builtins.h"
|
|
#include "utils/hashutils.h"
|
|
|
|
|
|
/* Private state */
|
|
typedef struct
|
|
{
|
|
uint64 cutoff; /* select blocks with hash less than this */
|
|
uint32 seed; /* random seed */
|
|
BlockNumber nextblock; /* next block to consider sampling */
|
|
OffsetNumber lt; /* last tuple returned from current block */
|
|
} SystemSamplerData;
|
|
|
|
|
|
static void system_samplescangetsamplesize(PlannerInfo *root,
|
|
RelOptInfo *baserel,
|
|
List *paramexprs,
|
|
BlockNumber *pages,
|
|
double *tuples);
|
|
static void system_initsamplescan(SampleScanState *node,
|
|
int eflags);
|
|
static void system_beginsamplescan(SampleScanState *node,
|
|
Datum *params,
|
|
int nparams,
|
|
uint32 seed);
|
|
static BlockNumber system_nextsampleblock(SampleScanState *node);
|
|
static OffsetNumber system_nextsampletuple(SampleScanState *node,
|
|
BlockNumber blockno,
|
|
OffsetNumber maxoffset);
|
|
|
|
|
|
/*
|
|
* Create a TsmRoutine descriptor for the SYSTEM method.
|
|
*/
|
|
Datum
|
|
tsm_system_handler(PG_FUNCTION_ARGS)
|
|
{
|
|
TsmRoutine *tsm = makeNode(TsmRoutine);
|
|
|
|
tsm->parameterTypes = list_make1_oid(FLOAT4OID);
|
|
tsm->repeatable_across_queries = true;
|
|
tsm->repeatable_across_scans = true;
|
|
tsm->SampleScanGetSampleSize = system_samplescangetsamplesize;
|
|
tsm->InitSampleScan = system_initsamplescan;
|
|
tsm->BeginSampleScan = system_beginsamplescan;
|
|
tsm->NextSampleBlock = system_nextsampleblock;
|
|
tsm->NextSampleTuple = system_nextsampletuple;
|
|
tsm->EndSampleScan = NULL;
|
|
|
|
PG_RETURN_POINTER(tsm);
|
|
}
|
|
|
|
/*
|
|
* Sample size estimation.
|
|
*/
|
|
static void
|
|
system_samplescangetsamplesize(PlannerInfo *root,
|
|
RelOptInfo *baserel,
|
|
List *paramexprs,
|
|
BlockNumber *pages,
|
|
double *tuples)
|
|
{
|
|
Node *pctnode;
|
|
float4 samplefract;
|
|
|
|
/* Try to extract an estimate for the sample percentage */
|
|
pctnode = (Node *) linitial(paramexprs);
|
|
pctnode = estimate_expression_value(root, pctnode);
|
|
|
|
if (IsA(pctnode, Const) &&
|
|
!((Const *) pctnode)->constisnull)
|
|
{
|
|
samplefract = DatumGetFloat4(((Const *) pctnode)->constvalue);
|
|
if (samplefract >= 0 && samplefract <= 100 && !isnan(samplefract))
|
|
samplefract /= 100.0f;
|
|
else
|
|
{
|
|
/* Default samplefract if the value is bogus */
|
|
samplefract = 0.1f;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* Default samplefract if we didn't obtain a non-null Const */
|
|
samplefract = 0.1f;
|
|
}
|
|
|
|
/* We'll visit a sample of the pages ... */
|
|
*pages = clamp_row_est(baserel->pages * samplefract);
|
|
|
|
/* ... and hopefully get a representative number of tuples from them */
|
|
*tuples = clamp_row_est(baserel->tuples * samplefract);
|
|
}
|
|
|
|
/*
|
|
* Initialize during executor setup.
|
|
*/
|
|
static void
|
|
system_initsamplescan(SampleScanState *node, int eflags)
|
|
{
|
|
node->tsm_state = palloc0(sizeof(SystemSamplerData));
|
|
}
|
|
|
|
/*
|
|
* Examine parameters and prepare for a sample scan.
|
|
*/
|
|
static void
|
|
system_beginsamplescan(SampleScanState *node,
|
|
Datum *params,
|
|
int nparams,
|
|
uint32 seed)
|
|
{
|
|
SystemSamplerData *sampler = (SystemSamplerData *) node->tsm_state;
|
|
double percent = DatumGetFloat4(params[0]);
|
|
double dcutoff;
|
|
|
|
if (percent < 0 || percent > 100 || isnan(percent))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_TABLESAMPLE_ARGUMENT),
|
|
errmsg("sample percentage must be between 0 and 100")));
|
|
|
|
/*
|
|
* The cutoff is sample probability times (PG_UINT32_MAX + 1); we have to
|
|
* store that as a uint64, of course. Note that this gives strictly
|
|
* correct behavior at the limits of zero or one probability.
|
|
*/
|
|
dcutoff = rint(((double) PG_UINT32_MAX + 1) * percent / 100);
|
|
sampler->cutoff = (uint64) dcutoff;
|
|
sampler->seed = seed;
|
|
sampler->nextblock = 0;
|
|
sampler->lt = InvalidOffsetNumber;
|
|
|
|
/*
|
|
* Bulkread buffer access strategy probably makes sense unless we're
|
|
* scanning a very small fraction of the table. The 1% cutoff here is a
|
|
* guess. We should use pagemode visibility checking, since we scan all
|
|
* tuples on each selected page.
|
|
*/
|
|
node->use_bulkread = (percent >= 1);
|
|
node->use_pagemode = true;
|
|
}
|
|
|
|
/*
|
|
* Select next block to sample.
|
|
*/
|
|
static BlockNumber
|
|
system_nextsampleblock(SampleScanState *node)
|
|
{
|
|
SystemSamplerData *sampler = (SystemSamplerData *) node->tsm_state;
|
|
TableScanDesc scan = node->ss.ss_currentScanDesc;
|
|
HeapScanDesc hscan = (HeapScanDesc) scan;
|
|
BlockNumber nextblock = sampler->nextblock;
|
|
uint32 hashinput[2];
|
|
|
|
/*
|
|
* We compute the hash by applying hash_any to an array of 2 uint32's
|
|
* containing the block number and seed. This is efficient to set up, and
|
|
* with the current implementation of hash_any, it gives
|
|
* machine-independent results, which is a nice property for regression
|
|
* testing.
|
|
*
|
|
* These words in the hash input are the same throughout the block:
|
|
*/
|
|
hashinput[1] = sampler->seed;
|
|
|
|
/*
|
|
* Loop over block numbers until finding suitable block or reaching end of
|
|
* relation.
|
|
*/
|
|
for (; nextblock < hscan->rs_nblocks; nextblock++)
|
|
{
|
|
uint32 hash;
|
|
|
|
hashinput[0] = nextblock;
|
|
|
|
hash = DatumGetUInt32(hash_any((const unsigned char *) hashinput,
|
|
(int) sizeof(hashinput)));
|
|
if (hash < sampler->cutoff)
|
|
break;
|
|
}
|
|
|
|
if (nextblock < hscan->rs_nblocks)
|
|
{
|
|
/* Found a suitable block; remember where we should start next time */
|
|
sampler->nextblock = nextblock + 1;
|
|
return nextblock;
|
|
}
|
|
|
|
/* Done, but let's reset nextblock to 0 for safety. */
|
|
sampler->nextblock = 0;
|
|
return InvalidBlockNumber;
|
|
}
|
|
|
|
/*
|
|
* Select next sampled tuple in current block.
|
|
*
|
|
* In block sampling, we just want to sample all the tuples in each selected
|
|
* block.
|
|
*
|
|
* It is OK here to return an offset without knowing if the tuple is visible
|
|
* (or even exists); nodeSamplescan.c will deal with that.
|
|
*
|
|
* When we reach end of the block, return InvalidOffsetNumber which tells
|
|
* SampleScan to go to next block.
|
|
*/
|
|
static OffsetNumber
|
|
system_nextsampletuple(SampleScanState *node,
|
|
BlockNumber blockno,
|
|
OffsetNumber maxoffset)
|
|
{
|
|
SystemSamplerData *sampler = (SystemSamplerData *) node->tsm_state;
|
|
OffsetNumber tupoffset = sampler->lt;
|
|
|
|
/* Advance to next possible offset on page */
|
|
if (tupoffset == InvalidOffsetNumber)
|
|
tupoffset = FirstOffsetNumber;
|
|
else
|
|
tupoffset++;
|
|
|
|
/* Done? */
|
|
if (tupoffset > maxoffset)
|
|
tupoffset = InvalidOffsetNumber;
|
|
|
|
sampler->lt = tupoffset;
|
|
|
|
return tupoffset;
|
|
}
|