Re: [patch] BUG #15005: ANALYZE can make pg_class.reltuples inaccurate.

Поиск
Список
Период
Сортировка
От Tom Lane
Тема Re: [patch] BUG #15005: ANALYZE can make pg_class.reltuples inaccurate.
Дата
Msg-id 6981.1520871694@sss.pgh.pa.us
обсуждение исходный текст
Ответ на Re: [patch] BUG #15005: ANALYZE can make pg_class.reltuples inaccurate.  (Tom Lane <tgl@sss.pgh.pa.us>)
Ответы Re: [patch] BUG #15005: ANALYZE can make pg_class.reltuplesinaccurate.
Список pgsql-hackers
I wrote:
> Maybe this type of situation is an argument for trusting an ANALYZE-based
> estimate more than the VACUUM-based estimate.  I remain uncomfortable with
> that in cases where VACUUM looked at much more of the table than ANALYZE
> did, though.  Maybe we need some heuristic based on the number of pages
> actually visited by each pass?

I looked into doing something like that.  It's possible, but it's fairly
invasive; there's no clean way to compare those page counts without
altering the API of acquire_sample_rows() to pass back the number of pages
it visited.  That would mean a change in FDW-visible APIs.  We could do
that, but I don't want to insist on it when there's nothing backing it up
except a fear that *maybe* ANALYZE's estimate will be wrong often enough
to worry about.

So at this point I'm prepared to go forward with your patch, though not
to risk back-patching it.  Field experience will tell us if we need to
do more.  I propose the attached cosmetic refactoring, though.

            regards, tom lane

diff --git a/contrib/pgstattuple/pgstatapprox.c b/contrib/pgstattuple/pgstatapprox.c
index 3cfbc08..474c3bd 100644
*** a/contrib/pgstattuple/pgstatapprox.c
--- b/contrib/pgstattuple/pgstatapprox.c
*************** statapprox_heap(Relation rel, output_typ
*** 184,190 ****

      stat->table_len = (uint64) nblocks * BLCKSZ;

!     stat->tuple_count = vac_estimate_reltuples(rel, false, nblocks, scanned,
                                                 stat->tuple_count + misc_count);

      /*
--- 184,190 ----

      stat->table_len = (uint64) nblocks * BLCKSZ;

!     stat->tuple_count = vac_estimate_reltuples(rel, nblocks, scanned,
                                                 stat->tuple_count + misc_count);

      /*
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 5f21fcb..ef93fb4 100644
*** a/src/backend/commands/analyze.c
--- b/src/backend/commands/analyze.c
*************** acquire_sample_rows(Relation onerel, int
*** 1249,1267 ****
          qsort((void *) rows, numrows, sizeof(HeapTuple), compare_rows);

      /*
!      * Estimate total numbers of rows in relation.  For live rows, use
!      * vac_estimate_reltuples; for dead rows, we have no source of old
!      * information, so we have to assume the density is the same in unseen
!      * pages as in the pages we scanned.
       */
-     *totalrows = vac_estimate_reltuples(onerel, true,
-                                         totalblocks,
-                                         bs.m,
-                                         liverows);
      if (bs.m > 0)
          *totaldeadrows = floor((deadrows / bs.m) * totalblocks + 0.5);
      else
          *totaldeadrows = 0.0;

      /*
       * Emit some interesting relation info
--- 1249,1270 ----
          qsort((void *) rows, numrows, sizeof(HeapTuple), compare_rows);

      /*
!      * Estimate total numbers of live and dead rows in relation, extrapolating
!      * on the assumption that the average tuple density in pages we didn't
!      * scan is the same as in the pages we did scan.  Since what we scanned is
!      * a random sample of the pages in the relation, this should be a good
!      * assumption.
       */
      if (bs.m > 0)
+     {
+         *totalrows = floor((liverows / bs.m) * totalblocks + 0.5);
          *totaldeadrows = floor((deadrows / bs.m) * totalblocks + 0.5);
+     }
      else
+     {
+         *totalrows = 0.0;
          *totaldeadrows = 0.0;
+     }

      /*
       * Emit some interesting relation info
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7aca69a..b50c554 100644
*** a/src/backend/commands/vacuum.c
--- b/src/backend/commands/vacuum.c
*************** vacuum_set_xid_limits(Relation rel,
*** 766,781 ****
   * vac_estimate_reltuples() -- estimate the new value for pg_class.reltuples
   *
   *        If we scanned the whole relation then we should just use the count of
!  *        live tuples seen; but if we did not, we should not trust the count
!  *        unreservedly, especially not in VACUUM, which may have scanned a quite
!  *        nonrandom subset of the table.  When we have only partial information,
!  *        we take the old value of pg_class.reltuples as a measurement of the
   *        tuple density in the unscanned pages.
-  *
-  *        This routine is shared by VACUUM and ANALYZE.
   */
  double
! vac_estimate_reltuples(Relation relation, bool is_analyze,
                         BlockNumber total_pages,
                         BlockNumber scanned_pages,
                         double scanned_tuples)
--- 766,779 ----
   * vac_estimate_reltuples() -- estimate the new value for pg_class.reltuples
   *
   *        If we scanned the whole relation then we should just use the count of
!  *        live tuples seen; but if we did not, we should not blindly extrapolate
!  *        from that number, since VACUUM may have scanned a quite nonrandom
!  *        subset of the table.  When we have only partial information, we take
!  *        the old value of pg_class.reltuples as a measurement of the
   *        tuple density in the unscanned pages.
   */
  double
! vac_estimate_reltuples(Relation relation,
                         BlockNumber total_pages,
                         BlockNumber scanned_pages,
                         double scanned_tuples)
*************** vac_estimate_reltuples(Relation relation
*** 783,791 ****
      BlockNumber old_rel_pages = relation->rd_rel->relpages;
      double        old_rel_tuples = relation->rd_rel->reltuples;
      double        old_density;
!     double        new_density;
!     double        multiplier;
!     double        updated_density;

      /* If we did scan the whole table, just use the count as-is */
      if (scanned_pages >= total_pages)
--- 781,788 ----
      BlockNumber old_rel_pages = relation->rd_rel->relpages;
      double        old_rel_tuples = relation->rd_rel->reltuples;
      double        old_density;
!     double        unscanned_pages;
!     double        total_tuples;

      /* If we did scan the whole table, just use the count as-is */
      if (scanned_pages >= total_pages)
*************** vac_estimate_reltuples(Relation relation
*** 809,839 ****

      /*
       * Okay, we've covered the corner cases.  The normal calculation is to
!      * convert the old measurement to a density (tuples per page), then update
!      * the density using an exponential-moving-average approach, and finally
!      * compute reltuples as updated_density * total_pages.
!      *
!      * For ANALYZE, the moving average multiplier is just the fraction of the
!      * table's pages we scanned.  This is equivalent to assuming that the
!      * tuple density in the unscanned pages didn't change.  Of course, it
!      * probably did, if the new density measurement is different. But over
!      * repeated cycles, the value of reltuples will converge towards the
!      * correct value, if repeated measurements show the same new density.
!      *
!      * For VACUUM, the situation is a bit different: we have looked at a
!      * nonrandom sample of pages, but we know for certain that the pages we
!      * didn't look at are precisely the ones that haven't changed lately.
!      * Thus, there is a reasonable argument for doing exactly the same thing
!      * as for the ANALYZE case, that is use the old density measurement as the
!      * value for the unscanned pages.
!      *
!      * This logic could probably use further refinement.
       */
      old_density = old_rel_tuples / old_rel_pages;
!     new_density = scanned_tuples / scanned_pages;
!     multiplier = (double) scanned_pages / (double) total_pages;
!     updated_density = old_density + (new_density - old_density) * multiplier;
!     return floor(updated_density * total_pages + 0.5);
  }


--- 806,819 ----

      /*
       * Okay, we've covered the corner cases.  The normal calculation is to
!      * convert the old measurement to a density (tuples per page), then
!      * estimate the number of tuples in the unscanned pages using that figure,
!      * and finally add on the number of tuples in the scanned pages.
       */
      old_density = old_rel_tuples / old_rel_pages;
!     unscanned_pages = (double) total_pages - (double) scanned_pages;
!     total_tuples = old_density * unscanned_pages + scanned_tuples;
!     return floor(total_tuples + 0.5);
  }


diff --git a/src/backend/commands/vacuumlazy.c b/src/backend/commands/vacuumlazy.c
index cf7f5e1..9ac84e8 100644
*** a/src/backend/commands/vacuumlazy.c
--- b/src/backend/commands/vacuumlazy.c
*************** lazy_scan_heap(Relation onerel, int opti
*** 1286,1292 ****
      vacrelstats->new_dead_tuples = nkeep;

      /* now we can compute the new value for pg_class.reltuples */
!     vacrelstats->new_rel_tuples = vac_estimate_reltuples(onerel, false,
                                                           nblocks,
                                                           vacrelstats->tupcount_pages,
                                                           num_tuples);
--- 1286,1292 ----
      vacrelstats->new_dead_tuples = nkeep;

      /* now we can compute the new value for pg_class.reltuples */
!     vacrelstats->new_rel_tuples = vac_estimate_reltuples(onerel,
                                                           nblocks,
                                                           vacrelstats->tupcount_pages,
                                                           num_tuples);
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index 797b6df..85d472f 100644
*** a/src/include/commands/vacuum.h
--- b/src/include/commands/vacuum.h
*************** extern void vacuum(int options, List *re
*** 162,168 ****
  extern void vac_open_indexes(Relation relation, LOCKMODE lockmode,
                   int *nindexes, Relation **Irel);
  extern void vac_close_indexes(int nindexes, Relation *Irel, LOCKMODE lockmode);
! extern double vac_estimate_reltuples(Relation relation, bool is_analyze,
                         BlockNumber total_pages,
                         BlockNumber scanned_pages,
                         double scanned_tuples);
--- 162,168 ----
  extern void vac_open_indexes(Relation relation, LOCKMODE lockmode,
                   int *nindexes, Relation **Irel);
  extern void vac_close_indexes(int nindexes, Relation *Irel, LOCKMODE lockmode);
! extern double vac_estimate_reltuples(Relation relation,
                         BlockNumber total_pages,
                         BlockNumber scanned_pages,
                         double scanned_tuples);

В списке pgsql-hackers по дате отправления:

Предыдущее
От: Pavel Stehule
Дата:
Сообщение: Re: [HACKERS] proposal: schema variables
Следующее
От: Narendra Pradeep U U
Дата:
Сообщение: Ambigous Plan - Larger Table on Hash Side