diff options
-rw-r--r-- | doc/src/sgml/config.sgml | 23 | ||||
-rw-r--r-- | doc/src/sgml/ref/create_table.sgml | 2 | ||||
-rw-r--r-- | src/backend/access/common/reloptions.c | 6 | ||||
-rw-r--r-- | src/backend/commands/vacuum.c | 4 | ||||
-rw-r--r-- | src/backend/postmaster/autovacuum.c | 16 | ||||
-rw-r--r-- | src/backend/utils/init/globals.c | 2 | ||||
-rw-r--r-- | src/backend/utils/misc/guc.c | 327 | ||||
-rw-r--r-- | src/backend/utils/misc/postgresql.conf.sample | 2 | ||||
-rw-r--r-- | src/include/catalog/catversion.h | 2 | ||||
-rw-r--r-- | src/include/miscadmin.h | 2 | ||||
-rw-r--r-- | src/include/postmaster/autovacuum.h | 2 | ||||
-rw-r--r-- | src/include/utils/guc.h | 3 | ||||
-rw-r--r-- | src/include/utils/rel.h | 2 | ||||
-rw-r--r-- | src/test/regress/expected/guc.out | 10 | ||||
-rw-r--r-- | src/test/regress/sql/guc.sql | 4 |
15 files changed, 246 insertions, 161 deletions
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml index 7bbe8f590b6..c12170b4b63 100644 --- a/doc/src/sgml/config.sgml +++ b/doc/src/sgml/config.sgml @@ -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> diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml index 22dbc07b238..e94fe2c3b67 100644 --- a/doc/src/sgml/ref/create_table.sgml +++ b/doc/src/sgml/ref/create_table.sgml @@ -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"/> diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index cdf1f4af62d..3b0b138f247 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -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, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index e91df2171e0..da13a5a6197 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -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; diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c index 347f91e937b..e9fe0a6e1fb 100644 --- a/src/backend/postmaster/autovacuum.c +++ b/src/backend/postmaster/autovacuum.c @@ -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; diff --git a/src/backend/utils/init/globals.c b/src/backend/utils/init/globals.c index a6ce1845372..6d1e94f8171 100644 --- a/src/backend/utils/init/globals.c +++ b/src/backend/utils/init/globals.c @@ -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; diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c index 712c821dfb0..6e396f1598f 100644 --- a/src/backend/utils/misc/guc.c +++ b/src/backend/utils/misc/guc.c @@ -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 */ }; @@ -2274,28 +2270,6 @@ static struct config_int ConfigureNamesInt[] = }, { - {"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."), NULL @@ -3321,6 +3295,28 @@ static struct config_real ConfigureNamesReal[] = }, { + {"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."), NULL @@ -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; } } diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample index 99f1666eef9..417f00a8419 100644 --- a/src/backend/utils/misc/postgresql.conf.sample +++ b/src/backend/utils/misc/postgresql.conf.sample @@ -155,7 +155,7 @@ # - 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 diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h index baf34a3b6fe..47ad6a9a8e1 100644 --- a/src/include/catalog/catversion.h +++ b/src/include/catalog/catversion.h @@ -53,6 +53,6 @@ */ /* yyyymmddN */ -#define CATALOG_VERSION_NO 201903091 +#define CATALOG_VERSION_NO 201903101 #endif diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h index c9e35003a57..b677c7e8213 100644 --- a/src/include/miscadmin.h +++ b/src/include/miscadmin.h @@ -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; diff --git a/src/include/postmaster/autovacuum.h b/src/include/postmaster/autovacuum.h index 79e99f01b5d..722ef1cec9a 100644 --- a/src/include/postmaster/autovacuum.h +++ b/src/include/postmaster/autovacuum.h @@ -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 */ diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h index c07e7b945e8..2712a774f7d 100644 --- a/src/include/utils/guc.h +++ b/src/include/utils/guc.h @@ -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, diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index 9d805ca23d2..54028515a7c 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -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; diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out index bef40d47177..f2590ee6769 100644 --- a/src/test/regress/expected/guc.out +++ b/src/test/regress/expected/guc.out @@ -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'; diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql index 6d3fca93358..b3ca59c09b1 100644 --- a/src/test/regress/sql/guc.sql +++ b/src/test/regress/sql/guc.sql @@ -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; |