]> granicus.if.org Git - postgresql/commitdiff
Convert [autovacuum_]vacuum_cost_delay into floating-point GUCs.
authorTom Lane <tgl@sss.pgh.pa.us>
Sun, 10 Mar 2019 19:01:39 +0000 (15:01 -0400)
committerTom Lane <tgl@sss.pgh.pa.us>
Sun, 10 Mar 2019 19:01:39 +0000 (15:01 -0400)
This change makes it possible to specify sub-millisecond delays,
which work well on most modern platforms, though that was not true
when the cost-delay feature was designed.

To support this without breaking existing configuration entries,
improve guc.c to allow floating-point GUCs to have units.  Also,
allow "us" (microseconds) as an input/output unit for time-unit GUCs.
(It's not allowed as a base unit, at least not yet.)

Likewise change the autovacuum_vacuum_cost_delay reloption to be
floating-point; this forces a catversion bump because the layout of
StdRdOptions changes.

This patch doesn't in itself change the default values or allowed
ranges for these parameters, and it should not affect the behavior
for any already-allowed setting for them.

Discussion: https://postgr.es/m/1798.1552165479@sss.pgh.pa.us

15 files changed:
doc/src/sgml/config.sgml
doc/src/sgml/ref/create_table.sgml
src/backend/access/common/reloptions.c
src/backend/commands/vacuum.c
src/backend/postmaster/autovacuum.c
src/backend/utils/init/globals.c
src/backend/utils/misc/guc.c
src/backend/utils/misc/postgresql.conf.sample
src/include/catalog/catversion.h
src/include/miscadmin.h
src/include/postmaster/autovacuum.h
src/include/utils/guc.h
src/include/utils/rel.h
src/test/regress/expected/guc.out
src/test/regress/sql/guc.sql

index 7bbe8f590b6f222fd11723a21446fd1d42965830..c12170b4b63dcfd79d3f5e6d7d012026708c1f3d 100644 (file)
@@ -91,7 +91,9 @@
 
         <listitem>
          <para>
-          Valid time units are <literal>ms</literal> (milliseconds),
+          Valid time units are
+          <literal>us</literal> (microseconds),
+          <literal>ms</literal> (milliseconds),
           <literal>s</literal> (seconds), <literal>min</literal> (minutes),
           <literal>h</literal> (hours), and <literal>d</literal> (days).
          </para>
@@ -1845,7 +1847,7 @@ include_dir 'conf.d'
 
      <variablelist>
       <varlistentry id="guc-vacuum-cost-delay" xreflabel="vacuum_cost_delay">
-       <term><varname>vacuum_cost_delay</varname> (<type>integer</type>)
+       <term><varname>vacuum_cost_delay</varname> (<type>floating point</type>)
        <indexterm>
         <primary><varname>vacuum_cost_delay</varname> configuration parameter</primary>
        </indexterm>
@@ -1856,18 +1858,19 @@ include_dir 'conf.d'
          when the cost limit has been exceeded.
          The default value is zero, which disables the cost-based vacuum
          delay feature.  Positive values enable cost-based vacuuming.
-         Note that on many systems, the effective resolution
-         of sleep delays is 10 milliseconds; setting
-         <varname>vacuum_cost_delay</varname> to a value that is
-         not a multiple of 10 might have the same results as setting it
-         to the next higher multiple of 10.
         </para>
 
         <para>
          When using cost-based vacuuming, appropriate values for
          <varname>vacuum_cost_delay</varname> are usually quite small, perhaps
-         10 or 20 milliseconds.  Adjusting vacuum's resource consumption
-         is best done by changing the other vacuum cost parameters.
+         less than 1 millisecond.  While <varname>vacuum_cost_delay</varname>
+         can be set to fractional-millisecond values, such delays may not be
+         measured accurately on older platforms.  On such platforms,
+         increasing <command>VACUUM</command>'s throttled resource consumption
+         above what you get at 1ms will require changing the other vacuum cost
+         parameters.  You should, nonetheless,
+         keep <varname>vacuum_cost_delay</varname> as small as your platform
+         will consistently measure; large delays are not helpful.
         </para>
        </listitem>
       </varlistentry>
@@ -7020,7 +7023,7 @@ COPY postgres_log FROM '/full/path/to/logfile.csv' WITH csv;
      </varlistentry>
 
      <varlistentry id="guc-autovacuum-vacuum-cost-delay" xreflabel="autovacuum_vacuum_cost_delay">
-      <term><varname>autovacuum_vacuum_cost_delay</varname> (<type>integer</type>)
+      <term><varname>autovacuum_vacuum_cost_delay</varname> (<type>floating point</type>)
       <indexterm>
        <primary><varname>autovacuum_vacuum_cost_delay</varname> configuration parameter</primary>
       </indexterm>
index 22dbc07b2381865184b4a5b42d49fdd30d3a7aa5..e94fe2c3b67772963f5824492a7d06c85dfbf0a8 100644 (file)
@@ -1385,7 +1385,7 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
    </varlistentry>
 
    <varlistentry>
-    <term><literal>autovacuum_vacuum_cost_delay</literal>, <literal>toast.autovacuum_vacuum_cost_delay</literal> (<type>integer</type>)</term>
+    <term><literal>autovacuum_vacuum_cost_delay</literal>, <literal>toast.autovacuum_vacuum_cost_delay</literal> (<type>floating point</type>)</term>
     <listitem>
      <para>
       Per-table value for <xref linkend="guc-autovacuum-vacuum-cost-delay"/>
index cdf1f4af62dfdcac24ba663ced1d6a5a8e574ad2..3b0b138f247c1f75dacd2c003b9bd9f56581c086 100644 (file)
@@ -1198,7 +1198,7 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
                        {
                                relopt_real *optreal = (relopt_real *) option->gen;
 
-                               parsed = parse_real(value, &option->values.real_val);
+                               parsed = parse_real(value, &option->values.real_val, 0, NULL);
                                if (validate && !parsed)
                                        ereport(ERROR,
                                                        (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1359,8 +1359,6 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
                offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, vacuum_threshold)},
                {"autovacuum_analyze_threshold", RELOPT_TYPE_INT,
                offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, analyze_threshold)},
-               {"autovacuum_vacuum_cost_delay", RELOPT_TYPE_INT,
-               offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, vacuum_cost_delay)},
                {"autovacuum_vacuum_cost_limit", RELOPT_TYPE_INT,
                offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, vacuum_cost_limit)},
                {"autovacuum_freeze_min_age", RELOPT_TYPE_INT,
@@ -1379,6 +1377,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
                offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, log_min_duration)},
                {"toast_tuple_target", RELOPT_TYPE_INT,
                offsetof(StdRdOptions, toast_tuple_target)},
+               {"autovacuum_vacuum_cost_delay", RELOPT_TYPE_REAL,
+               offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, vacuum_cost_delay)},
                {"autovacuum_vacuum_scale_factor", RELOPT_TYPE_REAL,
                offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, vacuum_scale_factor)},
                {"autovacuum_analyze_scale_factor", RELOPT_TYPE_REAL,
index e91df2171e0f5b55c5070ab230bd3bb9fb4a6601..da13a5a61974cc7066deb83e56f36573d7fa6714 100644 (file)
@@ -1834,13 +1834,13 @@ vacuum_delay_point(void)
        if (VacuumCostActive && !InterruptPending &&
                VacuumCostBalance >= VacuumCostLimit)
        {
-               int                     msec;
+               double          msec;
 
                msec = VacuumCostDelay * VacuumCostBalance / VacuumCostLimit;
                if (msec > VacuumCostDelay * 4)
                        msec = VacuumCostDelay * 4;
 
-               pg_usleep(msec * 1000L);
+               pg_usleep((long) (msec * 1000));
 
                VacuumCostBalance = 0;
 
index 347f91e937b08cfe984e97bb8b12da3fc04dff46..e9fe0a6e1fb76961a3223391c7566490492d840e 100644 (file)
@@ -120,7 +120,7 @@ double              autovacuum_anl_scale;
 int                    autovacuum_freeze_max_age;
 int                    autovacuum_multixact_freeze_max_age;
 
-int                    autovacuum_vac_cost_delay;
+double         autovacuum_vac_cost_delay;
 int                    autovacuum_vac_cost_limit;
 
 int                    Log_autovacuum_min_duration = -1;
@@ -189,7 +189,7 @@ typedef struct autovac_table
        Oid                     at_relid;
        int                     at_vacoptions;  /* bitmask of VacuumOption */
        VacuumParams at_params;
-       int                     at_vacuum_cost_delay;
+       double          at_vacuum_cost_delay;
        int                     at_vacuum_cost_limit;
        bool            at_dobalance;
        bool            at_sharedrel;
@@ -225,7 +225,7 @@ typedef struct WorkerInfoData
        TimestampTz wi_launchtime;
        bool            wi_dobalance;
        bool            wi_sharedrel;
-       int                     wi_cost_delay;
+       double          wi_cost_delay;
        int                     wi_cost_limit;
        int                     wi_cost_limit_base;
 } WorkerInfoData;
@@ -1785,7 +1785,7 @@ autovac_balance_cost(void)
         */
        int                     vac_cost_limit = (autovacuum_vac_cost_limit > 0 ?
                                                                  autovacuum_vac_cost_limit : VacuumCostLimit);
-       int                     vac_cost_delay = (autovacuum_vac_cost_delay >= 0 ?
+       double          vac_cost_delay = (autovacuum_vac_cost_delay >= 0 ?
                                                                  autovacuum_vac_cost_delay : VacuumCostDelay);
        double          cost_total;
        double          cost_avail;
@@ -1840,7 +1840,7 @@ autovac_balance_cost(void)
                }
 
                if (worker->wi_proc != NULL)
-                       elog(DEBUG2, "autovac_balance_cost(pid=%u db=%u, rel=%u, dobalance=%s cost_limit=%d, cost_limit_base=%d, cost_delay=%d)",
+                       elog(DEBUG2, "autovac_balance_cost(pid=%u db=%u, rel=%u, dobalance=%s cost_limit=%d, cost_limit_base=%d, cost_delay=%g)",
                                 worker->wi_proc->pid, worker->wi_dboid, worker->wi_tableoid,
                                 worker->wi_dobalance ? "yes" : "no",
                                 worker->wi_cost_limit, worker->wi_cost_limit_base,
@@ -2302,7 +2302,7 @@ do_autovacuum(void)
                autovac_table *tab;
                bool            isshared;
                bool            skipit;
-               int                     stdVacuumCostDelay;
+               double          stdVacuumCostDelay;
                int                     stdVacuumCostLimit;
                dlist_iter      iter;
 
@@ -2831,7 +2831,7 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map,
                int                     multixact_freeze_min_age;
                int                     multixact_freeze_table_age;
                int                     vac_cost_limit;
-               int                     vac_cost_delay;
+               double          vac_cost_delay;
                int                     log_min_duration;
 
                /*
@@ -2993,7 +2993,7 @@ relation_needs_vacanalyze(Oid relid,
         * table), or the autovacuum GUC variables.
         */
 
-       /* -1 in autovac setting means use plain vacuum_cost_delay */
+       /* -1 in autovac setting means use plain vacuum_scale_factor */
        vac_scale_factor = (relopts && relopts->vacuum_scale_factor >= 0)
                ? relopts->vacuum_scale_factor
                : autovacuum_vac_scale;
index a6ce184537261fdfbcacb028fc9f78abd286b5ed..6d1e94f81717cb5aa898464bb17b85387d187dea 100644 (file)
@@ -138,7 +138,7 @@ int                 VacuumCostPageHit = 1;  /* GUC parameters for vacuum */
 int                    VacuumCostPageMiss = 10;
 int                    VacuumCostPageDirty = 20;
 int                    VacuumCostLimit = 2000;
-int                    VacuumCostDelay = 0;
+double         VacuumCostDelay = 0;
 
 int                    VacuumPageHit = 0;
 int                    VacuumPageMiss = 0;
index 712c821dfb068ca8e2f56d9a52c0063f1dfcb188..6e396f1598f99b92ab32855765179e24e6767323 100644 (file)
@@ -755,13 +755,13 @@ const char *const config_type_names[] =
  * For each supported conversion from one unit to another, we have an entry
  * in the table.
  *
- * To keep things simple, and to avoid intermediate-value overflows,
+ * To keep things simple, and to avoid possible roundoff error,
  * conversions are never chained.  There needs to be a direct conversion
  * between all units (of the same type).
  *
- * The conversions from each base unit must be kept in order from greatest
- * to smallest unit; convert_from_base_unit() relies on that.  (The order of
- * the base units does not matter.)
+ * The conversions for each base unit must be kept in order from greatest to
+ * smallest human-friendly unit; convert_xxx_from_base_unit() rely on that.
+ * (The order of the base-unit groups does not matter.)
  */
 #define MAX_UNIT_LEN           3       /* length of longest recognized unit string */
 
@@ -770,9 +770,7 @@ typedef struct
        char            unit[MAX_UNIT_LEN + 1]; /* unit, as a string, like "kB" or
                                                                                 * "min" */
        int                     base_unit;              /* GUC_UNIT_XXX */
-       int64           multiplier;             /* If positive, multiply the value with this
-                                                                * for unit -> base_unit conversion.  If
-                                                                * negative, divide (with the absolute value) */
+       double          multiplier;             /* Factor for converting unit -> base_unit */
 } unit_conversion;
 
 /* Ensure that the constants in the tables don't overflow or underflow */
@@ -787,45 +785,40 @@ static const char *memory_units_hint = gettext_noop("Valid units for this parame
 
 static const unit_conversion memory_unit_conversion_table[] =
 {
-       /*
-        * TB -> bytes conversion always overflows 32-bit integer, so this always
-        * produces an error.  Include it nevertheless for completeness, and so
-        * that you get an "out of range" error, rather than "invalid unit".
-        */
-       {"TB", GUC_UNIT_BYTE, INT64CONST(1024) * 1024 * 1024 * 1024},
-       {"GB", GUC_UNIT_BYTE, 1024 * 1024 * 1024},
-       {"MB", GUC_UNIT_BYTE, 1024 * 1024},
-       {"kB", GUC_UNIT_BYTE, 1024},
-       {"B", GUC_UNIT_BYTE, 1},
-
-       {"TB", GUC_UNIT_KB, 1024 * 1024 * 1024},
-       {"GB", GUC_UNIT_KB, 1024 * 1024},
-       {"MB", GUC_UNIT_KB, 1024},
-       {"kB", GUC_UNIT_KB, 1},
-       {"B", GUC_UNIT_KB, -1024},
-
-       {"TB", GUC_UNIT_MB, 1024 * 1024},
-       {"GB", GUC_UNIT_MB, 1024},
-       {"MB", GUC_UNIT_MB, 1},
-       {"kB", GUC_UNIT_MB, -1024},
-       {"B", GUC_UNIT_MB, -(1024 * 1024)},
-
-       {"TB", GUC_UNIT_BLOCKS, (1024 * 1024 * 1024) / (BLCKSZ / 1024)},
-       {"GB", GUC_UNIT_BLOCKS, (1024 * 1024) / (BLCKSZ / 1024)},
-       {"MB", GUC_UNIT_BLOCKS, 1024 / (BLCKSZ / 1024)},
-       {"kB", GUC_UNIT_BLOCKS, -(BLCKSZ / 1024)},
-       {"B", GUC_UNIT_BLOCKS, -BLCKSZ},
-
-       {"TB", GUC_UNIT_XBLOCKS, (1024 * 1024 * 1024) / (XLOG_BLCKSZ / 1024)},
-       {"GB", GUC_UNIT_XBLOCKS, (1024 * 1024) / (XLOG_BLCKSZ / 1024)},
-       {"MB", GUC_UNIT_XBLOCKS, 1024 / (XLOG_BLCKSZ / 1024)},
-       {"kB", GUC_UNIT_XBLOCKS, -(XLOG_BLCKSZ / 1024)},
-       {"B", GUC_UNIT_XBLOCKS, -XLOG_BLCKSZ},
+       {"TB", GUC_UNIT_BYTE, 1024.0 * 1024.0 * 1024.0 * 1024.0},
+       {"GB", GUC_UNIT_BYTE, 1024.0 * 1024.0 * 1024.0},
+       {"MB", GUC_UNIT_BYTE, 1024.0 * 1024.0},
+       {"kB", GUC_UNIT_BYTE, 1024.0},
+       {"B", GUC_UNIT_BYTE, 1.0},
+
+       {"TB", GUC_UNIT_KB, 1024.0 * 1024.0 * 1024.0},
+       {"GB", GUC_UNIT_KB, 1024.0 * 1024.0},
+       {"MB", GUC_UNIT_KB, 1024.0},
+       {"kB", GUC_UNIT_KB, 1.0},
+       {"B", GUC_UNIT_KB, 1.0 / 1024.0},
+
+       {"TB", GUC_UNIT_MB, 1024.0 * 1024.0},
+       {"GB", GUC_UNIT_MB, 1024.0},
+       {"MB", GUC_UNIT_MB, 1.0},
+       {"kB", GUC_UNIT_MB, 1.0 / 1024.0},
+       {"B", GUC_UNIT_MB, 1.0 / (1024.0 * 1024.0)},
+
+       {"TB", GUC_UNIT_BLOCKS, (1024.0 * 1024.0 * 1024.0) / (BLCKSZ / 1024)},
+       {"GB", GUC_UNIT_BLOCKS, (1024.0 * 1024.0) / (BLCKSZ / 1024)},
+       {"MB", GUC_UNIT_BLOCKS, 1024.0 / (BLCKSZ / 1024)},
+       {"kB", GUC_UNIT_BLOCKS, 1.0 / (BLCKSZ / 1024)},
+       {"B", GUC_UNIT_BLOCKS, 1.0 / BLCKSZ},
+
+       {"TB", GUC_UNIT_XBLOCKS, (1024.0 * 1024.0 * 1024.0) / (XLOG_BLCKSZ / 1024)},
+       {"GB", GUC_UNIT_XBLOCKS, (1024.0 * 1024.0) / (XLOG_BLCKSZ / 1024)},
+       {"MB", GUC_UNIT_XBLOCKS, 1024.0 / (XLOG_BLCKSZ / 1024)},
+       {"kB", GUC_UNIT_XBLOCKS, 1.0 / (XLOG_BLCKSZ / 1024)},
+       {"B", GUC_UNIT_XBLOCKS, 1.0 / XLOG_BLCKSZ},
 
        {""}                                            /* end of table marker */
 };
 
-static const char *time_units_hint = gettext_noop("Valid units for this parameter are \"ms\", \"s\", \"min\", \"h\", and \"d\".");
+static const char *time_units_hint = gettext_noop("Valid units for this parameter are \"us\", \"ms\", \"s\", \"min\", \"h\", and \"d\".");
 
 static const unit_conversion time_unit_conversion_table[] =
 {
@@ -834,18 +827,21 @@ static const unit_conversion time_unit_conversion_table[] =
        {"min", GUC_UNIT_MS, 1000 * 60},
        {"s", GUC_UNIT_MS, 1000},
        {"ms", GUC_UNIT_MS, 1},
+       {"us", GUC_UNIT_MS, 1.0 / 1000},
 
        {"d", GUC_UNIT_S, 60 * 60 * 24},
        {"h", GUC_UNIT_S, 60 * 60},
        {"min", GUC_UNIT_S, 60},
        {"s", GUC_UNIT_S, 1},
-       {"ms", GUC_UNIT_S, -1000},
+       {"ms", GUC_UNIT_S, 1.0 / 1000},
+       {"us", GUC_UNIT_S, 1.0 / (1000 * 1000)},
 
        {"d", GUC_UNIT_MIN, 60 * 24},
        {"h", GUC_UNIT_MIN, 60},
        {"min", GUC_UNIT_MIN, 1},
-       {"s", GUC_UNIT_MIN, -60},
-       {"ms", GUC_UNIT_MIN, -1000 * 60},
+       {"s", GUC_UNIT_MIN, 1.0 / 60},
+       {"ms", GUC_UNIT_MIN, 1.0 / (1000 * 60)},
+       {"us", GUC_UNIT_MIN, 1.0 / (1000 * 1000 * 60)},
 
        {""}                                            /* end of table marker */
 };
@@ -2273,28 +2269,6 @@ static struct config_int ConfigureNamesInt[] =
                NULL, NULL, NULL
        },
 
-       {
-               {"vacuum_cost_delay", PGC_USERSET, RESOURCES_VACUUM_DELAY,
-                       gettext_noop("Vacuum cost delay in milliseconds."),
-                       NULL,
-                       GUC_UNIT_MS
-               },
-               &VacuumCostDelay,
-               0, 0, 100,
-               NULL, NULL, NULL
-       },
-
-       {
-               {"autovacuum_vacuum_cost_delay", PGC_SIGHUP, AUTOVACUUM,
-                       gettext_noop("Vacuum cost delay in milliseconds, for autovacuum."),
-                       NULL,
-                       GUC_UNIT_MS
-               },
-               &autovacuum_vac_cost_delay,
-               20, -1, 100,
-               NULL, NULL, NULL
-       },
-
        {
                {"autovacuum_vacuum_cost_limit", PGC_SIGHUP, AUTOVACUUM,
                        gettext_noop("Vacuum cost amount available before napping, for autovacuum."),
@@ -3320,6 +3294,28 @@ static struct config_real ConfigureNamesReal[] =
                check_random_seed, assign_random_seed, show_random_seed
        },
 
+       {
+               {"vacuum_cost_delay", PGC_USERSET, RESOURCES_VACUUM_DELAY,
+                       gettext_noop("Vacuum cost delay in milliseconds."),
+                       NULL,
+                       GUC_UNIT_MS
+               },
+               &VacuumCostDelay,
+               0, 0, 100,
+               NULL, NULL, NULL
+       },
+
+       {
+               {"autovacuum_vacuum_cost_delay", PGC_SIGHUP, AUTOVACUUM,
+                       gettext_noop("Vacuum cost delay in milliseconds, for autovacuum."),
+                       NULL,
+                       GUC_UNIT_MS
+               },
+               &autovacuum_vac_cost_delay,
+               20, -1, 100,
+               NULL, NULL, NULL
+       },
+
        {
                {"autovacuum_vacuum_scale_factor", PGC_SIGHUP, AUTOVACUUM,
                        gettext_noop("Number of tuple updates or deletes prior to vacuum as a fraction of reltuples."),
@@ -5960,17 +5956,35 @@ ReportGUCOption(struct config_generic *record)
 /*
  * Convert a value from one of the human-friendly units ("kB", "min" etc.)
  * to the given base unit.  'value' and 'unit' are the input value and unit
- * to convert from.  The converted value is stored in *base_value.
+ * to convert from (there can be trailing spaces in the unit string).
+ * The converted value is stored in *base_value.
+ * It's caller's responsibility to round off the converted value as necessary
+ * and check for out-of-range.
  *
  * Returns true on success, false if the input unit is not recognized.
  */
 static bool
-convert_to_base_unit(int64 value, const char *unit,
-                                        int base_unit, int64 *base_value)
+convert_to_base_unit(double value, const char *unit,
+                                        int base_unit, double *base_value)
 {
+       char            unitstr[MAX_UNIT_LEN + 1];
+       int                     unitlen;
        const unit_conversion *table;
        int                     i;
 
+       /* extract unit string to compare to table entries */
+       unitlen = 0;
+       while (*unit != '\0' && !isspace((unsigned char) *unit) &&
+                  unitlen < MAX_UNIT_LEN)
+               unitstr[unitlen++] = *(unit++);
+       unitstr[unitlen] = '\0';
+       /* allow whitespace after unit */
+       while (isspace((unsigned char) *unit))
+               unit++;
+       if (*unit != '\0')
+               return false;                   /* unit too long, or garbage after it */
+
+       /* now search the appropriate table */
        if (base_unit & GUC_UNIT_MEMORY)
                table = memory_unit_conversion_table;
        else
@@ -5979,12 +5993,9 @@ convert_to_base_unit(int64 value, const char *unit,
        for (i = 0; *table[i].unit; i++)
        {
                if (base_unit == table[i].base_unit &&
-                       strcmp(unit, table[i].unit) == 0)
+                       strcmp(unitstr, table[i].unit) == 0)
                {
-                       if (table[i].multiplier < 0)
-                               *base_value = value / (-table[i].multiplier);
-                       else
-                               *base_value = value * table[i].multiplier;
+                       *base_value = value * table[i].multiplier;
                        return true;
                }
        }
@@ -5992,14 +6003,15 @@ convert_to_base_unit(int64 value, const char *unit,
 }
 
 /*
- * Convert a value in some base unit to a human-friendly unit.  The output
- * unit is chosen so that it's the greatest unit that can represent the value
- * without loss.  For example, if the base unit is GUC_UNIT_KB, 1024 is
- * converted to 1 MB, but 1025 is represented as 1025 kB.
+ * Convert an integer value in some base unit to a human-friendly unit.
+ *
+ * The output unit is chosen so that it's the greatest unit that can represent
+ * the value without loss.  For example, if the base unit is GUC_UNIT_KB, 1024
+ * is converted to 1 MB, but 1025 is represented as 1025 kB.
  */
 static void
-convert_from_base_unit(int64 base_value, int base_unit,
-                                          int64 *value, const char **unit)
+convert_int_from_base_unit(int64 base_value, int base_unit,
+                                                  int64 *value, const char **unit)
 {
        const unit_conversion *table;
        int                     i;
@@ -6016,22 +6028,62 @@ convert_from_base_unit(int64 base_value, int base_unit,
                if (base_unit == table[i].base_unit)
                {
                        /*
-                        * Accept the first conversion that divides the value evenly. We
+                        * Accept the first conversion that divides the value evenly.  We
                         * assume that the conversions for each base unit are ordered from
                         * greatest unit to the smallest!
                         */
-                       if (table[i].multiplier < 0)
+                       if (table[i].multiplier <= 1.0 ||
+                               base_value % (int64) table[i].multiplier == 0)
                        {
-                               *value = base_value * (-table[i].multiplier);
+                               *value = (int64) rint(base_value / table[i].multiplier);
                                *unit = table[i].unit;
                                break;
                        }
-                       else if (base_value % table[i].multiplier == 0)
-                       {
-                               *value = base_value / table[i].multiplier;
-                               *unit = table[i].unit;
+               }
+       }
+
+       Assert(*unit != NULL);
+}
+
+/*
+ * Convert a floating-point value in some base unit to a human-friendly unit.
+ *
+ * Same as above, except we have to do the math a bit differently, and
+ * there's a possibility that we don't find any exact divisor.
+ */
+static void
+convert_real_from_base_unit(double base_value, int base_unit,
+                                                       double *value, const char **unit)
+{
+       const unit_conversion *table;
+       int                     i;
+
+       *unit = NULL;
+
+       if (base_unit & GUC_UNIT_MEMORY)
+               table = memory_unit_conversion_table;
+       else
+               table = time_unit_conversion_table;
+
+       for (i = 0; *table[i].unit; i++)
+       {
+               if (base_unit == table[i].base_unit)
+               {
+                       /*
+                        * Accept the first conversion that divides the value evenly; or
+                        * if there is none, use the smallest (last) target unit.
+                        *
+                        * What we actually care about here is whether snprintf with "%g"
+                        * will print the value as an integer, so the obvious test of
+                        * "*value == rint(*value)" is too strict; roundoff error might
+                        * make us choose an unreasonably small unit.  As a compromise,
+                        * accept a divisor that is within 1e-8 of producing an integer.
+                        */
+                       *value = base_value / table[i].multiplier;
+                       *unit = table[i].unit;
+                       if (*value > 0 &&
+                               fabs((rint(*value) / *value) - 1.0) <= 1e-8)
                                break;
-                       }
                }
        }
 
@@ -6095,7 +6147,7 @@ get_config_unit_name(int flags)
  * If the string parses okay, return true, else false.
  * If okay and result is not NULL, return the value in *result.
  * If not okay and hintmsg is not NULL, *hintmsg is set to a suitable
- *     HINT message, or NULL if no hint provided.
+ * HINT message, or NULL if no hint provided.
  */
 bool
 parse_int(const char *value, int *result, int flags, const char **hintmsg)
@@ -6130,26 +6182,14 @@ parse_int(const char *value, int *result, int flags, const char **hintmsg)
        /* Handle possible unit */
        if (*endptr != '\0')
        {
-               char            unit[MAX_UNIT_LEN + 1];
-               int                     unitlen;
-               bool            converted = false;
+               double          cval;
 
                if ((flags & GUC_UNIT) == 0)
                        return false;           /* this setting does not accept a unit */
 
-               unitlen = 0;
-               while (*endptr != '\0' && !isspace((unsigned char) *endptr) &&
-                          unitlen < MAX_UNIT_LEN)
-                       unit[unitlen++] = *(endptr++);
-               unit[unitlen] = '\0';
-               /* allow whitespace after unit */
-               while (isspace((unsigned char) *endptr))
-                       endptr++;
-
-               if (*endptr == '\0')
-                       converted = convert_to_base_unit(val, unit, (flags & GUC_UNIT),
-                                                                                        &val);
-               if (!converted)
+               if (!convert_to_base_unit((double) val,
+                                                                 endptr, (flags & GUC_UNIT),
+                                                                 &cval))
                {
                        /* invalid unit, or garbage after the unit; set hint and fail. */
                        if (hintmsg)
@@ -6162,13 +6202,15 @@ parse_int(const char *value, int *result, int flags, const char **hintmsg)
                        return false;
                }
 
-               /* Check for overflow due to units conversion */
-               if (val != (int64) ((int32) val))
+               /* Round to int, then check for overflow due to units conversion */
+               cval = rint(cval);
+               if (cval > INT_MAX || cval < INT_MIN)
                {
                        if (hintmsg)
                                *hintmsg = gettext_noop("Value exceeds integer range.");
                        return false;
                }
+               val = (int64) cval;
        }
 
        if (result)
@@ -6180,32 +6222,59 @@ parse_int(const char *value, int *result, int flags, const char **hintmsg)
 
 /*
  * Try to parse value as a floating point number in the usual format.
+ *
  * If the string parses okay, return true, else false.
  * If okay and result is not NULL, return the value in *result.
+ * If not okay and hintmsg is not NULL, *hintmsg is set to a suitable
+ * HINT message, or NULL if no hint provided.
  */
 bool
-parse_real(const char *value, double *result)
+parse_real(const char *value, double *result, int flags, const char **hintmsg)
 {
        double          val;
        char       *endptr;
 
+       /* To suppress compiler warnings, always set output params */
        if (result)
-               *result = 0;                    /* suppress compiler warning */
+               *result = 0;
+       if (hintmsg)
+               *hintmsg = NULL;
 
        errno = 0;
        val = strtod(value, &endptr);
+
        if (endptr == value || errno == ERANGE)
-               return false;
+               return false;                   /* no HINT for these cases */
 
        /* reject NaN (infinities will fail range checks later) */
        if (isnan(val))
-               return false;
+               return false;                   /* treat same as syntax error; no HINT */
 
-       /* allow whitespace after number */
+       /* allow whitespace between number and unit */
        while (isspace((unsigned char) *endptr))
                endptr++;
+
+       /* Handle possible unit */
        if (*endptr != '\0')
-               return false;
+       {
+               if ((flags & GUC_UNIT) == 0)
+                       return false;           /* this setting does not accept a unit */
+
+               if (!convert_to_base_unit(val,
+                                                                 endptr, (flags & GUC_UNIT),
+                                                                 &val))
+               {
+                       /* invalid unit, or garbage after the unit; set hint and fail. */
+                       if (hintmsg)
+                       {
+                               if (flags & GUC_UNIT_MEMORY)
+                                       *hintmsg = memory_units_hint;
+                               else
+                                       *hintmsg = time_units_hint;
+                       }
+                       return false;
+               }
+       }
 
        if (result)
                *result = val;
@@ -6393,13 +6462,16 @@ parse_and_validate_value(struct config_generic *record,
                case PGC_REAL:
                        {
                                struct config_real *conf = (struct config_real *) record;
+                               const char *hintmsg;
 
-                               if (!parse_real(value, &newval->realval))
+                               if (!parse_real(value, &newval->realval,
+                                                               conf->gen.flags, &hintmsg))
                                {
                                        ereport(elevel,
                                                        (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
-                                                        errmsg("parameter \"%s\" requires a numeric value",
-                                                                       name)));
+                                                        errmsg("invalid value for parameter \"%s\": \"%s\"",
+                                                                       name, value),
+                                                        hintmsg ? errhint("%s", _(hintmsg)) : 0));
                                        return false;
                                }
 
@@ -9278,10 +9350,9 @@ _ShowOption(struct config_generic *record, bool use_units)
                                        const char *unit;
 
                                        if (use_units && result > 0 && (record->flags & GUC_UNIT))
-                                       {
-                                               convert_from_base_unit(result, record->flags & GUC_UNIT,
-                                                                                          &result, &unit);
-                                       }
+                                               convert_int_from_base_unit(result,
+                                                                                                  record->flags & GUC_UNIT,
+                                                                                                  &result, &unit);
                                        else
                                                unit = "";
 
@@ -9300,8 +9371,18 @@ _ShowOption(struct config_generic *record, bool use_units)
                                        val = conf->show_hook();
                                else
                                {
-                                       snprintf(buffer, sizeof(buffer), "%g",
-                                                        *conf->variable);
+                                       double          result = *conf->variable;
+                                       const char *unit;
+
+                                       if (use_units && result > 0 && (record->flags & GUC_UNIT))
+                                               convert_real_from_base_unit(result,
+                                                                                                       record->flags & GUC_UNIT,
+                                                                                                       &result, &unit);
+                                       else
+                                               unit = "";
+
+                                       snprintf(buffer, sizeof(buffer), "%g%s",
+                                                        result, unit);
                                        val = buffer;
                                }
                        }
index 99f1666eef9052cca490cf3a80f2d92222fa00fa..417f00a8419fed8bf1edfd78bfc2ece8e58134f6 100644 (file)
 
 # - Cost-Based Vacuum Delay -
 
-#vacuum_cost_delay = 0                 # 0-100 milliseconds
+#vacuum_cost_delay = 0                 # 0-100 milliseconds (0 disables)
 #vacuum_cost_page_hit = 1              # 0-10000 credits
 #vacuum_cost_page_miss = 10            # 0-10000 credits
 #vacuum_cost_page_dirty = 20           # 0-10000 credits
index baf34a3b6fe66880641207a3503fb8dc3074eb43..47ad6a9a8e19cc4c7beb6aa9fe4798945b91a051 100644 (file)
@@ -53,6 +53,6 @@
  */
 
 /*                                                     yyyymmddN */
-#define CATALOG_VERSION_NO     201903091
+#define CATALOG_VERSION_NO     201903101
 
 #endif
index c9e35003a578fc01108519d4bde73d6dfa8be515..b677c7e82134afa79fa650f6f5b4b91ce4b2bcc0 100644 (file)
@@ -250,7 +250,7 @@ extern int  VacuumCostPageHit;
 extern int     VacuumCostPageMiss;
 extern int     VacuumCostPageDirty;
 extern int     VacuumCostLimit;
-extern int     VacuumCostDelay;
+extern double VacuumCostDelay;
 
 extern int     VacuumPageHit;
 extern int     VacuumPageMiss;
index 79e99f01b5d5140dd8acc49cbc9da1d153bc7827..722ef1cec9ab8d2082996d108c456268dcd6f1c9 100644 (file)
@@ -37,7 +37,7 @@ extern int    autovacuum_anl_thresh;
 extern double autovacuum_anl_scale;
 extern int     autovacuum_freeze_max_age;
 extern int     autovacuum_multixact_freeze_max_age;
-extern int     autovacuum_vac_cost_delay;
+extern double autovacuum_vac_cost_delay;
 extern int     autovacuum_vac_cost_limit;
 
 /* autovacuum launcher PID, only valid when worker is shutting down */
index c07e7b945e89dcafd1e08c907eaa5ddb93363589..2712a774f7d9cdae8501b4b17faf1cee42b111c0 100644 (file)
@@ -361,7 +361,8 @@ extern void BeginReportingGUCOptions(void);
 extern void ParseLongOption(const char *string, char **name, char **value);
 extern bool parse_int(const char *value, int *result, int flags,
                  const char **hintmsg);
-extern bool parse_real(const char *value, double *result);
+extern bool parse_real(const char *value, double *result, int flags,
+                  const char **hintmsg);
 extern int set_config_option(const char *name, const char *value,
                                  GucContext context, GucSource source,
                                  GucAction action, bool changeVal, int elevel,
index 9d805ca23d25cf195c7f9ada8716e68b021ad744..54028515a7c8e7783c3cf34a362ce42bb4eb9c28 100644 (file)
@@ -243,7 +243,6 @@ typedef struct AutoVacOpts
        bool            enabled;
        int                     vacuum_threshold;
        int                     analyze_threshold;
-       int                     vacuum_cost_delay;
        int                     vacuum_cost_limit;
        int                     freeze_min_age;
        int                     freeze_max_age;
@@ -252,6 +251,7 @@ typedef struct AutoVacOpts
        int                     multixact_freeze_max_age;
        int                     multixact_freeze_table_age;
        int                     log_min_duration;
+       float8          vacuum_cost_delay;
        float8          vacuum_scale_factor;
        float8          analyze_scale_factor;
 } AutoVacOpts;
index bef40d47177bc97280fc8baa201413ab923092c6..f2590ee6769b1e91af072c4cd736a218f803f0c9 100644 (file)
@@ -149,11 +149,11 @@ SELECT '2006-08-13 12:34:56'::timestamptz;
 (1 row)
 
 SAVEPOINT first_sp;
-SET vacuum_cost_delay TO 80;
+SET vacuum_cost_delay TO 80.1;
 SHOW vacuum_cost_delay;
  vacuum_cost_delay 
 -------------------
- 80ms
+ 80100us
 (1 row)
 
 SET datestyle = 'German, DMY';
@@ -183,7 +183,7 @@ SELECT '2006-08-13 12:34:56'::timestamptz;
 (1 row)
 
 SAVEPOINT second_sp;
-SET vacuum_cost_delay TO 90;
+SET vacuum_cost_delay TO '900us';
 SET datestyle = 'SQL, YMD';
 SHOW datestyle;
  DateStyle 
@@ -222,7 +222,7 @@ ROLLBACK TO third_sp;
 SHOW vacuum_cost_delay;
  vacuum_cost_delay 
 -------------------
- 90ms
+ 900us
 (1 row)
 
 SHOW datestyle;
@@ -508,7 +508,7 @@ SELECT '2006-08-13 12:34:56'::timestamptz;
 
 -- Test some simple error cases
 SET seq_page_cost TO 'NaN';
-ERROR:  parameter "seq_page_cost" requires a numeric value
+ERROR:  invalid value for parameter "seq_page_cost": "NaN"
 SET vacuum_cost_delay TO '10s';
 ERROR:  10000 ms is outside the valid range for parameter "vacuum_cost_delay" (0 .. 100)
 SET geqo_selection_bias TO '-infinity';
index 6d3fca933580c559bc36f8472a41679d328ab2f1..b3ca59c09b1c3fa12a03aab028826e68301d0442 100644 (file)
@@ -47,7 +47,7 @@ SET datestyle = 'MDY';
 SHOW datestyle;
 SELECT '2006-08-13 12:34:56'::timestamptz;
 SAVEPOINT first_sp;
-SET vacuum_cost_delay TO 80;
+SET vacuum_cost_delay TO 80.1;
 SHOW vacuum_cost_delay;
 SET datestyle = 'German, DMY';
 SHOW datestyle;
@@ -56,7 +56,7 @@ ROLLBACK TO first_sp;
 SHOW datestyle;
 SELECT '2006-08-13 12:34:56'::timestamptz;
 SAVEPOINT second_sp;
-SET vacuum_cost_delay TO 90;
+SET vacuum_cost_delay TO '900us';
 SET datestyle = 'SQL, YMD';
 SHOW datestyle;
 SELECT '2006-08-13 12:34:56'::timestamptz;