You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

tsm_system_rows.c 10KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394
  1. /*-------------------------------------------------------------------------
  2. *
  3. * tsm_system_rows.c
  4. * support routines for SYSTEM_ROWS tablesample method
  5. *
  6. * The desire here is to produce a random sample with a given number of rows
  7. * (or the whole relation, if that is fewer rows). We use a block-sampling
  8. * approach. To ensure that the whole relation will be visited if necessary,
  9. * we start at a randomly chosen block and then advance with a stride that
  10. * is randomly chosen but is relatively prime to the relation's nblocks.
  11. *
  12. * Because of the dependence on nblocks, this method cannot be repeatable
  13. * across queries. (Even if the user hasn't explicitly changed the relation,
  14. * maintenance activities such as autovacuum might change nblocks.) However,
  15. * we can at least make it repeatable across scans, by determining the
  16. * sampling pattern only once on the first scan. This means that rescans
  17. * won't visit blocks added after the first scan, but that is fine since
  18. * such blocks shouldn't contain any visible tuples anyway.
  19. *
  20. * Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group
  21. * Portions Copyright (c) 1994, Regents of the University of California
  22. *
  23. * IDENTIFICATION
  24. * contrib/tsm_system_rows/tsm_system_rows.c
  25. *
  26. *-------------------------------------------------------------------------
  27. */
  28. #include "postgres.h"
  29. #include "access/relscan.h"
  30. #include "access/tsmapi.h"
  31. #include "catalog/pg_type.h"
  32. #include "miscadmin.h"
  33. #include "optimizer/clauses.h"
  34. #include "optimizer/cost.h"
  35. #include "utils/sampling.h"
  36. PG_MODULE_MAGIC;
  37. PG_FUNCTION_INFO_V1(tsm_system_rows_handler);
  38. /* Private state */
  39. typedef struct
  40. {
  41. uint32 seed; /* random seed */
  42. int64 ntuples; /* number of tuples to return */
  43. int64 donetuples; /* number of tuples already returned */
  44. OffsetNumber lt; /* last tuple returned from current block */
  45. BlockNumber doneblocks; /* number of already-scanned blocks */
  46. BlockNumber lb; /* last block visited */
  47. /* these three values are not changed during a rescan: */
  48. BlockNumber nblocks; /* number of blocks in relation */
  49. BlockNumber firstblock; /* first block to sample from */
  50. BlockNumber step; /* step size, or 0 if not set yet */
  51. } SystemRowsSamplerData;
  52. static void system_rows_samplescangetsamplesize(PlannerInfo *root,
  53. RelOptInfo *baserel,
  54. List *paramexprs,
  55. BlockNumber *pages,
  56. double *tuples);
  57. static void system_rows_initsamplescan(SampleScanState *node,
  58. int eflags);
  59. static void system_rows_beginsamplescan(SampleScanState *node,
  60. Datum *params,
  61. int nparams,
  62. uint32 seed);
  63. static BlockNumber system_rows_nextsampleblock(SampleScanState *node);
  64. static OffsetNumber system_rows_nextsampletuple(SampleScanState *node,
  65. BlockNumber blockno,
  66. OffsetNumber maxoffset);
  67. static bool SampleOffsetVisible(OffsetNumber tupoffset, HeapScanDesc scan);
  68. static uint32 random_relative_prime(uint32 n, SamplerRandomState randstate);
  69. /*
  70. * Create a TsmRoutine descriptor for the SYSTEM_ROWS method.
  71. */
  72. Datum
  73. tsm_system_rows_handler(PG_FUNCTION_ARGS)
  74. {
  75. TsmRoutine *tsm = makeNode(TsmRoutine);
  76. tsm->parameterTypes = list_make1_oid(INT8OID);
  77. /* See notes at head of file */
  78. tsm->repeatable_across_queries = false;
  79. tsm->repeatable_across_scans = true;
  80. tsm->SampleScanGetSampleSize = system_rows_samplescangetsamplesize;
  81. tsm->InitSampleScan = system_rows_initsamplescan;
  82. tsm->BeginSampleScan = system_rows_beginsamplescan;
  83. tsm->NextSampleBlock = system_rows_nextsampleblock;
  84. tsm->NextSampleTuple = system_rows_nextsampletuple;
  85. tsm->EndSampleScan = NULL;
  86. PG_RETURN_POINTER(tsm);
  87. }
  88. /*
  89. * Sample size estimation.
  90. */
  91. static void
  92. system_rows_samplescangetsamplesize(PlannerInfo *root,
  93. RelOptInfo *baserel,
  94. List *paramexprs,
  95. BlockNumber *pages,
  96. double *tuples)
  97. {
  98. Node *limitnode;
  99. int64 ntuples;
  100. double npages;
  101. /* Try to extract an estimate for the limit rowcount */
  102. limitnode = (Node *) linitial(paramexprs);
  103. limitnode = estimate_expression_value(root, limitnode);
  104. if (IsA(limitnode, Const) &&
  105. !((Const *) limitnode)->constisnull)
  106. {
  107. ntuples = DatumGetInt64(((Const *) limitnode)->constvalue);
  108. if (ntuples < 0)
  109. {
  110. /* Default ntuples if the value is bogus */
  111. ntuples = 1000;
  112. }
  113. }
  114. else
  115. {
  116. /* Default ntuples if we didn't obtain a non-null Const */
  117. ntuples = 1000;
  118. }
  119. /* Clamp to the estimated relation size */
  120. if (ntuples > baserel->tuples)
  121. ntuples = (int64) baserel->tuples;
  122. ntuples = clamp_row_est(ntuples);
  123. if (baserel->tuples > 0 && baserel->pages > 0)
  124. {
  125. /* Estimate number of pages visited based on tuple density */
  126. double density = baserel->tuples / (double) baserel->pages;
  127. npages = ntuples / density;
  128. }
  129. else
  130. {
  131. /* For lack of data, assume one tuple per page */
  132. npages = ntuples;
  133. }
  134. /* Clamp to sane value */
  135. npages = clamp_row_est(Min((double) baserel->pages, npages));
  136. *pages = npages;
  137. *tuples = ntuples;
  138. }
  139. /*
  140. * Initialize during executor setup.
  141. */
  142. static void
  143. system_rows_initsamplescan(SampleScanState *node, int eflags)
  144. {
  145. node->tsm_state = palloc0(sizeof(SystemRowsSamplerData));
  146. /* Note the above leaves tsm_state->step equal to zero */
  147. }
  148. /*
  149. * Examine parameters and prepare for a sample scan.
  150. */
  151. static void
  152. system_rows_beginsamplescan(SampleScanState *node,
  153. Datum *params,
  154. int nparams,
  155. uint32 seed)
  156. {
  157. SystemRowsSamplerData *sampler = (SystemRowsSamplerData *) node->tsm_state;
  158. int64 ntuples = DatumGetInt64(params[0]);
  159. if (ntuples < 0)
  160. ereport(ERROR,
  161. (errcode(ERRCODE_INVALID_TABLESAMPLE_ARGUMENT),
  162. errmsg("sample size must not be negative")));
  163. sampler->seed = seed;
  164. sampler->ntuples = ntuples;
  165. sampler->donetuples = 0;
  166. sampler->lt = InvalidOffsetNumber;
  167. sampler->doneblocks = 0;
  168. /* lb will be initialized during first NextSampleBlock call */
  169. /* we intentionally do not change nblocks/firstblock/step here */
  170. /*
  171. * We *must* use pagemode visibility checking in this module, so force
  172. * that even though it's currently default.
  173. */
  174. node->use_pagemode = true;
  175. }
  176. /*
  177. * Select next block to sample.
  178. *
  179. * Uses linear probing algorithm for picking next block.
  180. */
  181. static BlockNumber
  182. system_rows_nextsampleblock(SampleScanState *node)
  183. {
  184. SystemRowsSamplerData *sampler = (SystemRowsSamplerData *) node->tsm_state;
  185. HeapScanDesc scan = node->ss.ss_currentScanDesc;
  186. /* First call within scan? */
  187. if (sampler->doneblocks == 0)
  188. {
  189. /* First scan within query? */
  190. if (sampler->step == 0)
  191. {
  192. /* Initialize now that we have scan descriptor */
  193. SamplerRandomState randstate;
  194. /* If relation is empty, there's nothing to scan */
  195. if (scan->rs_nblocks == 0)
  196. return InvalidBlockNumber;
  197. /* We only need an RNG during this setup step */
  198. sampler_random_init_state(sampler->seed, randstate);
  199. /* Compute nblocks/firstblock/step only once per query */
  200. sampler->nblocks = scan->rs_nblocks;
  201. /* Choose random starting block within the relation */
  202. /* (Actually this is the predecessor of the first block visited) */
  203. sampler->firstblock = sampler_random_fract(randstate) *
  204. sampler->nblocks;
  205. /* Find relative prime as step size for linear probing */
  206. sampler->step = random_relative_prime(sampler->nblocks, randstate);
  207. }
  208. /* Reinitialize lb */
  209. sampler->lb = sampler->firstblock;
  210. }
  211. /* If we've read all blocks or returned all needed tuples, we're done */
  212. if (++sampler->doneblocks > sampler->nblocks ||
  213. sampler->donetuples >= sampler->ntuples)
  214. return InvalidBlockNumber;
  215. /*
  216. * It's probably impossible for scan->rs_nblocks to decrease between scans
  217. * within a query; but just in case, loop until we select a block number
  218. * less than scan->rs_nblocks. We don't care if scan->rs_nblocks has
  219. * increased since the first scan.
  220. */
  221. do
  222. {
  223. /* Advance lb, using uint64 arithmetic to forestall overflow */
  224. sampler->lb = ((uint64) sampler->lb + sampler->step) % sampler->nblocks;
  225. } while (sampler->lb >= scan->rs_nblocks);
  226. return sampler->lb;
  227. }
  228. /*
  229. * Select next sampled tuple in current block.
  230. *
  231. * In block sampling, we just want to sample all the tuples in each selected
  232. * block.
  233. *
  234. * When we reach end of the block, return InvalidOffsetNumber which tells
  235. * SampleScan to go to next block.
  236. */
  237. static OffsetNumber
  238. system_rows_nextsampletuple(SampleScanState *node,
  239. BlockNumber blockno,
  240. OffsetNumber maxoffset)
  241. {
  242. SystemRowsSamplerData *sampler = (SystemRowsSamplerData *) node->tsm_state;
  243. HeapScanDesc scan = node->ss.ss_currentScanDesc;
  244. OffsetNumber tupoffset = sampler->lt;
  245. /* Quit if we've returned all needed tuples */
  246. if (sampler->donetuples >= sampler->ntuples)
  247. return InvalidOffsetNumber;
  248. /*
  249. * Because we should only count visible tuples as being returned, we need
  250. * to search for a visible tuple rather than just let the core code do it.
  251. */
  252. /* We rely on the data accumulated in pagemode access */
  253. Assert(scan->rs_pageatatime);
  254. for (;;)
  255. {
  256. /* Advance to next possible offset on page */
  257. if (tupoffset == InvalidOffsetNumber)
  258. tupoffset = FirstOffsetNumber;
  259. else
  260. tupoffset++;
  261. /* Done? */
  262. if (tupoffset > maxoffset)
  263. {
  264. tupoffset = InvalidOffsetNumber;
  265. break;
  266. }
  267. /* Found a candidate? */
  268. if (SampleOffsetVisible(tupoffset, scan))
  269. {
  270. sampler->donetuples++;
  271. break;
  272. }
  273. }
  274. sampler->lt = tupoffset;
  275. return tupoffset;
  276. }
  277. /*
  278. * Check if tuple offset is visible
  279. *
  280. * In pageatatime mode, heapgetpage() already did visibility checks,
  281. * so just look at the info it left in rs_vistuples[].
  282. */
  283. static bool
  284. SampleOffsetVisible(OffsetNumber tupoffset, HeapScanDesc scan)
  285. {
  286. int start = 0,
  287. end = scan->rs_ntuples - 1;
  288. while (start <= end)
  289. {
  290. int mid = (start + end) / 2;
  291. OffsetNumber curoffset = scan->rs_vistuples[mid];
  292. if (tupoffset == curoffset)
  293. return true;
  294. else if (tupoffset < curoffset)
  295. end = mid - 1;
  296. else
  297. start = mid + 1;
  298. }
  299. return false;
  300. }
  301. /*
  302. * Compute greatest common divisor of two uint32's.
  303. */
  304. static uint32
  305. gcd(uint32 a, uint32 b)
  306. {
  307. uint32 c;
  308. while (a != 0)
  309. {
  310. c = a;
  311. a = b % a;
  312. b = c;
  313. }
  314. return b;
  315. }
  316. /*
  317. * Pick a random value less than and relatively prime to n, if possible
  318. * (else return 1).
  319. */
  320. static uint32
  321. random_relative_prime(uint32 n, SamplerRandomState randstate)
  322. {
  323. uint32 r;
  324. /* Safety check to avoid infinite loop or zero result for small n. */
  325. if (n <= 1)
  326. return 1;
  327. /*
  328. * This should only take 2 or 3 iterations as the probability of 2 numbers
  329. * being relatively prime is ~61%; but just in case, we'll include a
  330. * CHECK_FOR_INTERRUPTS in the loop.
  331. */
  332. do
  333. {
  334. CHECK_FOR_INTERRUPTS();
  335. r = (uint32) (sampler_random_fract(randstate) * n);
  336. } while (r == 0 || gcd(r, n) > 1);
  337. return r;
  338. }