Commit caf626b2 authored by Tom Lane's avatar Tom Lane

Convert [autovacuum_]vacuum_cost_delay into floating-point GUCs.

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
parent 28a65fc3
...@@ -91,7 +91,9 @@ ...@@ -91,7 +91,9 @@
<listitem> <listitem>
<para> <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>s</literal> (seconds), <literal>min</literal> (minutes),
<literal>h</literal> (hours), and <literal>d</literal> (days). <literal>h</literal> (hours), and <literal>d</literal> (days).
</para> </para>
...@@ -1845,7 +1847,7 @@ include_dir 'conf.d' ...@@ -1845,7 +1847,7 @@ include_dir 'conf.d'
<variablelist> <variablelist>
<varlistentry id="guc-vacuum-cost-delay" xreflabel="vacuum_cost_delay"> <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> <indexterm>
<primary><varname>vacuum_cost_delay</varname> configuration parameter</primary> <primary><varname>vacuum_cost_delay</varname> configuration parameter</primary>
</indexterm> </indexterm>
...@@ -1856,18 +1858,19 @@ include_dir 'conf.d' ...@@ -1856,18 +1858,19 @@ include_dir 'conf.d'
when the cost limit has been exceeded. when the cost limit has been exceeded.
The default value is zero, which disables the cost-based vacuum The default value is zero, which disables the cost-based vacuum
delay feature. Positive values enable cost-based vacuuming. 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>
<para> <para>
When using cost-based vacuuming, appropriate values for When using cost-based vacuuming, appropriate values for
<varname>vacuum_cost_delay</varname> are usually quite small, perhaps <varname>vacuum_cost_delay</varname> are usually quite small, perhaps
10 or 20 milliseconds. Adjusting vacuum's resource consumption less than 1 millisecond. While <varname>vacuum_cost_delay</varname>
is best done by changing the other vacuum cost parameters. 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> </para>
</listitem> </listitem>
</varlistentry> </varlistentry>
...@@ -7020,7 +7023,7 @@ COPY postgres_log FROM '/full/path/to/logfile.csv' WITH csv; ...@@ -7020,7 +7023,7 @@ COPY postgres_log FROM '/full/path/to/logfile.csv' WITH csv;
</varlistentry> </varlistentry>
<varlistentry id="guc-autovacuum-vacuum-cost-delay" xreflabel="autovacuum_vacuum_cost_delay"> <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> <indexterm>
<primary><varname>autovacuum_vacuum_cost_delay</varname> configuration parameter</primary> <primary><varname>autovacuum_vacuum_cost_delay</varname> configuration parameter</primary>
</indexterm> </indexterm>
......
...@@ -1385,7 +1385,7 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM ...@@ -1385,7 +1385,7 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
</varlistentry> </varlistentry>
<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> <listitem>
<para> <para>
Per-table value for <xref linkend="guc-autovacuum-vacuum-cost-delay"/> Per-table value for <xref linkend="guc-autovacuum-vacuum-cost-delay"/>
......
...@@ -1198,7 +1198,7 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, ...@@ -1198,7 +1198,7 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
{ {
relopt_real *optreal = (relopt_real *) option->gen; 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) if (validate && !parsed)
ereport(ERROR, ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE), (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
...@@ -1359,8 +1359,6 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) ...@@ -1359,8 +1359,6 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, vacuum_threshold)}, offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, vacuum_threshold)},
{"autovacuum_analyze_threshold", RELOPT_TYPE_INT, {"autovacuum_analyze_threshold", RELOPT_TYPE_INT,
offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, analyze_threshold)}, 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, {"autovacuum_vacuum_cost_limit", RELOPT_TYPE_INT,
offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, vacuum_cost_limit)}, offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, vacuum_cost_limit)},
{"autovacuum_freeze_min_age", RELOPT_TYPE_INT, {"autovacuum_freeze_min_age", RELOPT_TYPE_INT,
...@@ -1379,6 +1377,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) ...@@ -1379,6 +1377,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, log_min_duration)}, offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, log_min_duration)},
{"toast_tuple_target", RELOPT_TYPE_INT, {"toast_tuple_target", RELOPT_TYPE_INT,
offsetof(StdRdOptions, toast_tuple_target)}, 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, {"autovacuum_vacuum_scale_factor", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, vacuum_scale_factor)}, offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, vacuum_scale_factor)},
{"autovacuum_analyze_scale_factor", RELOPT_TYPE_REAL, {"autovacuum_analyze_scale_factor", RELOPT_TYPE_REAL,
......
...@@ -1834,13 +1834,13 @@ vacuum_delay_point(void) ...@@ -1834,13 +1834,13 @@ vacuum_delay_point(void)
if (VacuumCostActive && !InterruptPending && if (VacuumCostActive && !InterruptPending &&
VacuumCostBalance >= VacuumCostLimit) VacuumCostBalance >= VacuumCostLimit)
{ {
int msec; double msec;
msec = VacuumCostDelay * VacuumCostBalance / VacuumCostLimit; msec = VacuumCostDelay * VacuumCostBalance / VacuumCostLimit;
if (msec > VacuumCostDelay * 4) if (msec > VacuumCostDelay * 4)
msec = VacuumCostDelay * 4; msec = VacuumCostDelay * 4;
pg_usleep(msec * 1000L); pg_usleep((long) (msec * 1000));
VacuumCostBalance = 0; VacuumCostBalance = 0;
......
...@@ -120,7 +120,7 @@ double autovacuum_anl_scale; ...@@ -120,7 +120,7 @@ double autovacuum_anl_scale;
int autovacuum_freeze_max_age; int autovacuum_freeze_max_age;
int autovacuum_multixact_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 autovacuum_vac_cost_limit;
int Log_autovacuum_min_duration = -1; int Log_autovacuum_min_duration = -1;
...@@ -189,7 +189,7 @@ typedef struct autovac_table ...@@ -189,7 +189,7 @@ typedef struct autovac_table
Oid at_relid; Oid at_relid;
int at_vacoptions; /* bitmask of VacuumOption */ int at_vacoptions; /* bitmask of VacuumOption */
VacuumParams at_params; VacuumParams at_params;
int at_vacuum_cost_delay; double at_vacuum_cost_delay;
int at_vacuum_cost_limit; int at_vacuum_cost_limit;
bool at_dobalance; bool at_dobalance;
bool at_sharedrel; bool at_sharedrel;
...@@ -225,7 +225,7 @@ typedef struct WorkerInfoData ...@@ -225,7 +225,7 @@ typedef struct WorkerInfoData
TimestampTz wi_launchtime; TimestampTz wi_launchtime;
bool wi_dobalance; bool wi_dobalance;
bool wi_sharedrel; bool wi_sharedrel;
int wi_cost_delay; double wi_cost_delay;
int wi_cost_limit; int wi_cost_limit;
int wi_cost_limit_base; int wi_cost_limit_base;
} WorkerInfoData; } WorkerInfoData;
...@@ -1785,7 +1785,7 @@ autovac_balance_cost(void) ...@@ -1785,7 +1785,7 @@ autovac_balance_cost(void)
*/ */
int vac_cost_limit = (autovacuum_vac_cost_limit > 0 ? int vac_cost_limit = (autovacuum_vac_cost_limit > 0 ?
autovacuum_vac_cost_limit : VacuumCostLimit); 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); autovacuum_vac_cost_delay : VacuumCostDelay);
double cost_total; double cost_total;
double cost_avail; double cost_avail;
...@@ -1840,7 +1840,7 @@ autovac_balance_cost(void) ...@@ -1840,7 +1840,7 @@ autovac_balance_cost(void)
} }
if (worker->wi_proc != NULL) 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_proc->pid, worker->wi_dboid, worker->wi_tableoid,
worker->wi_dobalance ? "yes" : "no", worker->wi_dobalance ? "yes" : "no",
worker->wi_cost_limit, worker->wi_cost_limit_base, worker->wi_cost_limit, worker->wi_cost_limit_base,
...@@ -2302,7 +2302,7 @@ do_autovacuum(void) ...@@ -2302,7 +2302,7 @@ do_autovacuum(void)
autovac_table *tab; autovac_table *tab;
bool isshared; bool isshared;
bool skipit; bool skipit;
int stdVacuumCostDelay; double stdVacuumCostDelay;
int stdVacuumCostLimit; int stdVacuumCostLimit;
dlist_iter iter; dlist_iter iter;
...@@ -2831,7 +2831,7 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map, ...@@ -2831,7 +2831,7 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map,
int multixact_freeze_min_age; int multixact_freeze_min_age;
int multixact_freeze_table_age; int multixact_freeze_table_age;
int vac_cost_limit; int vac_cost_limit;
int vac_cost_delay; double vac_cost_delay;
int log_min_duration; int log_min_duration;
/* /*
...@@ -2993,7 +2993,7 @@ relation_needs_vacanalyze(Oid relid, ...@@ -2993,7 +2993,7 @@ relation_needs_vacanalyze(Oid relid,
* table), or the autovacuum GUC variables. * 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) vac_scale_factor = (relopts && relopts->vacuum_scale_factor >= 0)
? relopts->vacuum_scale_factor ? relopts->vacuum_scale_factor
: autovacuum_vac_scale; : autovacuum_vac_scale;
......
...@@ -138,7 +138,7 @@ int VacuumCostPageHit = 1; /* GUC parameters for vacuum */ ...@@ -138,7 +138,7 @@ int VacuumCostPageHit = 1; /* GUC parameters for vacuum */
int VacuumCostPageMiss = 10; int VacuumCostPageMiss = 10;
int VacuumCostPageDirty = 20; int VacuumCostPageDirty = 20;
int VacuumCostLimit = 2000; int VacuumCostLimit = 2000;
int VacuumCostDelay = 0; double VacuumCostDelay = 0;
int VacuumPageHit = 0; int VacuumPageHit = 0;
int VacuumPageMiss = 0; int VacuumPageMiss = 0;
......
...@@ -755,13 +755,13 @@ const char *const config_type_names[] = ...@@ -755,13 +755,13 @@ const char *const config_type_names[] =
* For each supported conversion from one unit to another, we have an entry * For each supported conversion from one unit to another, we have an entry
* in the table. * 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 * conversions are never chained. There needs to be a direct conversion
* between all units (of the same type). * between all units (of the same type).
* *
* The conversions from each base unit must be kept in order from greatest * The conversions for each base unit must be kept in order from greatest to
* to smallest unit; convert_from_base_unit() relies on that. (The order of * smallest human-friendly unit; convert_xxx_from_base_unit() rely on that.
* the base units does not matter.) * (The order of the base-unit groups does not matter.)
*/ */
#define MAX_UNIT_LEN 3 /* length of longest recognized unit string */ #define MAX_UNIT_LEN 3 /* length of longest recognized unit string */
...@@ -770,9 +770,7 @@ typedef struct ...@@ -770,9 +770,7 @@ typedef struct
char unit[MAX_UNIT_LEN + 1]; /* unit, as a string, like "kB" or char unit[MAX_UNIT_LEN + 1]; /* unit, as a string, like "kB" or
* "min" */ * "min" */
int base_unit; /* GUC_UNIT_XXX */ int base_unit; /* GUC_UNIT_XXX */
int64 multiplier; /* If positive, multiply the value with this double multiplier; /* Factor for converting unit -> base_unit */
* for unit -> base_unit conversion. If
* negative, divide (with the absolute value) */
} unit_conversion; } unit_conversion;
/* Ensure that the constants in the tables don't overflow or underflow */ /* 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 ...@@ -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[] = static const unit_conversion memory_unit_conversion_table[] =
{ {
/* {"TB", GUC_UNIT_BYTE, 1024.0 * 1024.0 * 1024.0 * 1024.0},
* TB -> bytes conversion always overflows 32-bit integer, so this always {"GB", GUC_UNIT_BYTE, 1024.0 * 1024.0 * 1024.0},
* produces an error. Include it nevertheless for completeness, and so {"MB", GUC_UNIT_BYTE, 1024.0 * 1024.0},
* that you get an "out of range" error, rather than "invalid unit". {"kB", GUC_UNIT_BYTE, 1024.0},
*/ {"B", GUC_UNIT_BYTE, 1.0},
{"TB", GUC_UNIT_BYTE, INT64CONST(1024) * 1024 * 1024 * 1024},
{"GB", GUC_UNIT_BYTE, 1024 * 1024 * 1024}, {"TB", GUC_UNIT_KB, 1024.0 * 1024.0 * 1024.0},
{"MB", GUC_UNIT_BYTE, 1024 * 1024}, {"GB", GUC_UNIT_KB, 1024.0 * 1024.0},
{"kB", GUC_UNIT_BYTE, 1024}, {"MB", GUC_UNIT_KB, 1024.0},
{"B", GUC_UNIT_BYTE, 1}, {"kB", GUC_UNIT_KB, 1.0},
{"B", GUC_UNIT_KB, 1.0 / 1024.0},
{"TB", GUC_UNIT_KB, 1024 * 1024 * 1024},
{"GB", GUC_UNIT_KB, 1024 * 1024}, {"TB", GUC_UNIT_MB, 1024.0 * 1024.0},
{"MB", GUC_UNIT_KB, 1024}, {"GB", GUC_UNIT_MB, 1024.0},
{"kB", GUC_UNIT_KB, 1}, {"MB", GUC_UNIT_MB, 1.0},
{"B", GUC_UNIT_KB, -1024}, {"kB", GUC_UNIT_MB, 1.0 / 1024.0},
{"B", GUC_UNIT_MB, 1.0 / (1024.0 * 1024.0)},
{"TB", GUC_UNIT_MB, 1024 * 1024},
{"GB", GUC_UNIT_MB, 1024}, {"TB", GUC_UNIT_BLOCKS, (1024.0 * 1024.0 * 1024.0) / (BLCKSZ / 1024)},
{"MB", GUC_UNIT_MB, 1}, {"GB", GUC_UNIT_BLOCKS, (1024.0 * 1024.0) / (BLCKSZ / 1024)},
{"kB", GUC_UNIT_MB, -1024}, {"MB", GUC_UNIT_BLOCKS, 1024.0 / (BLCKSZ / 1024)},
{"B", GUC_UNIT_MB, -(1024 * 1024)}, {"kB", GUC_UNIT_BLOCKS, 1.0 / (BLCKSZ / 1024)},
{"B", GUC_UNIT_BLOCKS, 1.0 / BLCKSZ},
{"TB", GUC_UNIT_BLOCKS, (1024 * 1024 * 1024) / (BLCKSZ / 1024)},
{"GB", GUC_UNIT_BLOCKS, (1024 * 1024) / (BLCKSZ / 1024)}, {"TB", GUC_UNIT_XBLOCKS, (1024.0 * 1024.0 * 1024.0) / (XLOG_BLCKSZ / 1024)},
{"MB", GUC_UNIT_BLOCKS, 1024 / (BLCKSZ / 1024)}, {"GB", GUC_UNIT_XBLOCKS, (1024.0 * 1024.0) / (XLOG_BLCKSZ / 1024)},
{"kB", GUC_UNIT_BLOCKS, -(BLCKSZ / 1024)}, {"MB", GUC_UNIT_XBLOCKS, 1024.0 / (XLOG_BLCKSZ / 1024)},
{"B", GUC_UNIT_BLOCKS, -BLCKSZ}, {"kB", GUC_UNIT_XBLOCKS, 1.0 / (XLOG_BLCKSZ / 1024)},
{"B", GUC_UNIT_XBLOCKS, 1.0 / XLOG_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},
{""} /* end of table marker */ {""} /* 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[] = static const unit_conversion time_unit_conversion_table[] =
{ {
...@@ -834,18 +827,21 @@ 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}, {"min", GUC_UNIT_MS, 1000 * 60},
{"s", GUC_UNIT_MS, 1000}, {"s", GUC_UNIT_MS, 1000},
{"ms", GUC_UNIT_MS, 1}, {"ms", GUC_UNIT_MS, 1},
{"us", GUC_UNIT_MS, 1.0 / 1000},
{"d", GUC_UNIT_S, 60 * 60 * 24}, {"d", GUC_UNIT_S, 60 * 60 * 24},
{"h", GUC_UNIT_S, 60 * 60}, {"h", GUC_UNIT_S, 60 * 60},
{"min", GUC_UNIT_S, 60}, {"min", GUC_UNIT_S, 60},
{"s", GUC_UNIT_S, 1}, {"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}, {"d", GUC_UNIT_MIN, 60 * 24},
{"h", GUC_UNIT_MIN, 60}, {"h", GUC_UNIT_MIN, 60},
{"min", GUC_UNIT_MIN, 1}, {"min", GUC_UNIT_MIN, 1},
{"s", GUC_UNIT_MIN, -60}, {"s", GUC_UNIT_MIN, 1.0 / 60},
{"ms", GUC_UNIT_MIN, -1000 * 60}, {"ms", GUC_UNIT_MIN, 1.0 / (1000 * 60)},
{"us", GUC_UNIT_MIN, 1.0 / (1000 * 1000 * 60)},
{""} /* end of table marker */ {""} /* end of table marker */
}; };
...@@ -2273,28 +2269,6 @@ static struct config_int ConfigureNamesInt[] = ...@@ -2273,28 +2269,6 @@ static struct config_int ConfigureNamesInt[] =
NULL, NULL, NULL 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, {"autovacuum_vacuum_cost_limit", PGC_SIGHUP, AUTOVACUUM,
gettext_noop("Vacuum cost amount available before napping, for autovacuum."), gettext_noop("Vacuum cost amount available before napping, for autovacuum."),
...@@ -3320,6 +3294,28 @@ static struct config_real ConfigureNamesReal[] = ...@@ -3320,6 +3294,28 @@ static struct config_real ConfigureNamesReal[] =
check_random_seed, assign_random_seed, show_random_seed 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, {"autovacuum_vacuum_scale_factor", PGC_SIGHUP, AUTOVACUUM,
gettext_noop("Number of tuple updates or deletes prior to vacuum as a fraction of reltuples."), 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) ...@@ -5960,17 +5956,35 @@ ReportGUCOption(struct config_generic *record)
/* /*
* Convert a value from one of the human-friendly units ("kB", "min" etc.) * 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 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. * Returns true on success, false if the input unit is not recognized.
*/ */
static bool static bool
convert_to_base_unit(int64 value, const char *unit, convert_to_base_unit(double value, const char *unit,
int base_unit, int64 *base_value) int base_unit, double *base_value)
{ {
char unitstr[MAX_UNIT_LEN + 1];
int unitlen;
const unit_conversion *table; const unit_conversion *table;
int i; 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) if (base_unit & GUC_UNIT_MEMORY)
table = memory_unit_conversion_table; table = memory_unit_conversion_table;
else else
...@@ -5979,11 +5993,8 @@ convert_to_base_unit(int64 value, const char *unit, ...@@ -5979,11 +5993,8 @@ convert_to_base_unit(int64 value, const char *unit,
for (i = 0; *table[i].unit; i++) for (i = 0; *table[i].unit; i++)
{ {
if (base_unit == table[i].base_unit && 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; return true;
} }
...@@ -5992,13 +6003,14 @@ convert_to_base_unit(int64 value, const char *unit, ...@@ -5992,13 +6003,14 @@ convert_to_base_unit(int64 value, const char *unit,
} }
/* /*
* Convert a value in some base unit to a human-friendly unit. The output * Convert an integer value in some base unit to a human-friendly unit.
* 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 * The output unit is chosen so that it's the greatest unit that can represent
* converted to 1 MB, but 1025 is represented as 1025 kB. * 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 static void
convert_from_base_unit(int64 base_value, int base_unit, convert_int_from_base_unit(int64 base_value, int base_unit,
int64 *value, const char **unit) int64 *value, const char **unit)
{ {
const unit_conversion *table; const unit_conversion *table;
...@@ -6020,20 +6032,60 @@ convert_from_base_unit(int64 base_value, int base_unit, ...@@ -6020,20 +6032,60 @@ convert_from_base_unit(int64 base_value, int base_unit,
* assume that the conversions for each base unit are ordered from * assume that the conversions for each base unit are ordered from
* greatest unit to the smallest! * 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; *unit = table[i].unit;
break; break;
} }
else if (base_value % table[i].multiplier == 0) }
}
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; *value = base_value / table[i].multiplier;
*unit = table[i].unit; *unit = table[i].unit;
if (*value > 0 &&
fabs((rint(*value) / *value) - 1.0) <= 1e-8)
break; break;
} }
} }
}
Assert(*unit != NULL); Assert(*unit != NULL);
} }
...@@ -6130,26 +6182,14 @@ 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 */ /* Handle possible unit */
if (*endptr != '\0') if (*endptr != '\0')
{ {
char unit[MAX_UNIT_LEN + 1]; double cval;
int unitlen;
bool converted = false;
if ((flags & GUC_UNIT) == 0) if ((flags & GUC_UNIT) == 0)
return false; /* this setting does not accept a unit */ return false; /* this setting does not accept a unit */
unitlen = 0; if (!convert_to_base_unit((double) val,
while (*endptr != '\0' && !isspace((unsigned char) *endptr) && endptr, (flags & GUC_UNIT),
unitlen < MAX_UNIT_LEN) &cval))
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)
{ {
/* invalid unit, or garbage after the unit; set hint and fail. */ /* invalid unit, or garbage after the unit; set hint and fail. */
if (hintmsg) if (hintmsg)
...@@ -6162,13 +6202,15 @@ parse_int(const char *value, int *result, int flags, const char **hintmsg) ...@@ -6162,13 +6202,15 @@ parse_int(const char *value, int *result, int flags, const char **hintmsg)
return false; return false;
} }
/* Check for overflow due to units conversion */ /* Round to int, then check for overflow due to units conversion */
if (val != (int64) ((int32) val)) cval = rint(cval);
if (cval > INT_MAX || cval < INT_MIN)
{ {
if (hintmsg) if (hintmsg)
*hintmsg = gettext_noop("Value exceeds integer range."); *hintmsg = gettext_noop("Value exceeds integer range.");
return false; return false;
} }
val = (int64) cval;
} }
if (result) if (result)
...@@ -6180,32 +6222,59 @@ parse_int(const char *value, int *result, int flags, const char **hintmsg) ...@@ -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. * Try to parse value as a floating point number in the usual format.
*
* If the string parses okay, return true, else false. * If the string parses okay, return true, else false.
* If okay and result is not NULL, return the value in *result. * 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 bool
parse_real(const char *value, double *result) parse_real(const char *value, double *result, int flags, const char **hintmsg)
{ {
double val; double val;
char *endptr; char *endptr;
/* To suppress compiler warnings, always set output params */
if (result) if (result)
*result = 0; /* suppress compiler warning */ *result = 0;
if (hintmsg)
*hintmsg = NULL;
errno = 0; errno = 0;
val = strtod(value, &endptr); val = strtod(value, &endptr);
if (endptr == value || errno == ERANGE) if (endptr == value || errno == ERANGE)
return false; return false; /* no HINT for these cases */
/* reject NaN (infinities will fail range checks later) */ /* reject NaN (infinities will fail range checks later) */
if (isnan(val)) 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)) while (isspace((unsigned char) *endptr))
endptr++; endptr++;
/* Handle possible unit */
if (*endptr != '\0') if (*endptr != '\0')
{
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; return false;
}
}
if (result) if (result)
*result = val; *result = val;
...@@ -6393,13 +6462,16 @@ parse_and_validate_value(struct config_generic *record, ...@@ -6393,13 +6462,16 @@ parse_and_validate_value(struct config_generic *record,
case PGC_REAL: case PGC_REAL:
{ {
struct config_real *conf = (struct config_real *) record; 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, ereport(elevel,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE), (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("parameter \"%s\" requires a numeric value", errmsg("invalid value for parameter \"%s\": \"%s\"",
name))); name, value),
hintmsg ? errhint("%s", _(hintmsg)) : 0));
return false; return false;
} }
...@@ -9278,10 +9350,9 @@ _ShowOption(struct config_generic *record, bool use_units) ...@@ -9278,10 +9350,9 @@ _ShowOption(struct config_generic *record, bool use_units)
const char *unit; const char *unit;
if (use_units && result > 0 && (record->flags & GUC_UNIT)) if (use_units && result > 0 && (record->flags & GUC_UNIT))
{ convert_int_from_base_unit(result,
convert_from_base_unit(result, record->flags & GUC_UNIT, record->flags & GUC_UNIT,
&result, &unit); &result, &unit);
}
else else
unit = ""; unit = "";
...@@ -9300,8 +9371,18 @@ _ShowOption(struct config_generic *record, bool use_units) ...@@ -9300,8 +9371,18 @@ _ShowOption(struct config_generic *record, bool use_units)
val = conf->show_hook(); val = conf->show_hook();
else else
{ {
snprintf(buffer, sizeof(buffer), "%g", double result = *conf->variable;
*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; val = buffer;
} }
} }
......
...@@ -155,7 +155,7 @@ ...@@ -155,7 +155,7 @@
# - Cost-Based Vacuum Delay - # - 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_hit = 1 # 0-10000 credits
#vacuum_cost_page_miss = 10 # 0-10000 credits #vacuum_cost_page_miss = 10 # 0-10000 credits
#vacuum_cost_page_dirty = 20 # 0-10000 credits #vacuum_cost_page_dirty = 20 # 0-10000 credits
......
...@@ -53,6 +53,6 @@ ...@@ -53,6 +53,6 @@
*/ */
/* yyyymmddN */ /* yyyymmddN */
#define CATALOG_VERSION_NO 201903091 #define CATALOG_VERSION_NO 201903101
#endif #endif
...@@ -250,7 +250,7 @@ extern int VacuumCostPageHit; ...@@ -250,7 +250,7 @@ extern int VacuumCostPageHit;
extern int VacuumCostPageMiss; extern int VacuumCostPageMiss;
extern int VacuumCostPageDirty; extern int VacuumCostPageDirty;
extern int VacuumCostLimit; extern int VacuumCostLimit;
extern int VacuumCostDelay; extern double VacuumCostDelay;
extern int VacuumPageHit; extern int VacuumPageHit;
extern int VacuumPageMiss; extern int VacuumPageMiss;
......
...@@ -37,7 +37,7 @@ extern int autovacuum_anl_thresh; ...@@ -37,7 +37,7 @@ extern int autovacuum_anl_thresh;
extern double autovacuum_anl_scale; extern double autovacuum_anl_scale;
extern int autovacuum_freeze_max_age; extern int autovacuum_freeze_max_age;
extern int autovacuum_multixact_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; extern int autovacuum_vac_cost_limit;
/* autovacuum launcher PID, only valid when worker is shutting down */ /* autovacuum launcher PID, only valid when worker is shutting down */
......
...@@ -361,7 +361,8 @@ extern void BeginReportingGUCOptions(void); ...@@ -361,7 +361,8 @@ extern void BeginReportingGUCOptions(void);
extern void ParseLongOption(const char *string, char **name, char **value); extern void ParseLongOption(const char *string, char **name, char **value);
extern bool parse_int(const char *value, int *result, int flags, extern bool parse_int(const char *value, int *result, int flags,
const char **hintmsg); 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, extern int set_config_option(const char *name, const char *value,
GucContext context, GucSource source, GucContext context, GucSource source,
GucAction action, bool changeVal, int elevel, GucAction action, bool changeVal, int elevel,
......
...@@ -243,7 +243,6 @@ typedef struct AutoVacOpts ...@@ -243,7 +243,6 @@ typedef struct AutoVacOpts
bool enabled; bool enabled;
int vacuum_threshold; int vacuum_threshold;
int analyze_threshold; int analyze_threshold;
int vacuum_cost_delay;
int vacuum_cost_limit; int vacuum_cost_limit;
int freeze_min_age; int freeze_min_age;
int freeze_max_age; int freeze_max_age;
...@@ -252,6 +251,7 @@ typedef struct AutoVacOpts ...@@ -252,6 +251,7 @@ typedef struct AutoVacOpts
int multixact_freeze_max_age; int multixact_freeze_max_age;
int multixact_freeze_table_age; int multixact_freeze_table_age;
int log_min_duration; int log_min_duration;
float8 vacuum_cost_delay;
float8 vacuum_scale_factor; float8 vacuum_scale_factor;
float8 analyze_scale_factor; float8 analyze_scale_factor;
} AutoVacOpts; } AutoVacOpts;
......
...@@ -149,11 +149,11 @@ SELECT '2006-08-13 12:34:56'::timestamptz; ...@@ -149,11 +149,11 @@ SELECT '2006-08-13 12:34:56'::timestamptz;
(1 row) (1 row)
SAVEPOINT first_sp; SAVEPOINT first_sp;
SET vacuum_cost_delay TO 80; SET vacuum_cost_delay TO 80.1;
SHOW vacuum_cost_delay; SHOW vacuum_cost_delay;
vacuum_cost_delay vacuum_cost_delay
------------------- -------------------
80ms 80100us
(1 row) (1 row)
SET datestyle = 'German, DMY'; SET datestyle = 'German, DMY';
...@@ -183,7 +183,7 @@ SELECT '2006-08-13 12:34:56'::timestamptz; ...@@ -183,7 +183,7 @@ SELECT '2006-08-13 12:34:56'::timestamptz;
(1 row) (1 row)
SAVEPOINT second_sp; SAVEPOINT second_sp;
SET vacuum_cost_delay TO 90; SET vacuum_cost_delay TO '900us';
SET datestyle = 'SQL, YMD'; SET datestyle = 'SQL, YMD';
SHOW datestyle; SHOW datestyle;
DateStyle DateStyle
...@@ -222,7 +222,7 @@ ROLLBACK TO third_sp; ...@@ -222,7 +222,7 @@ ROLLBACK TO third_sp;
SHOW vacuum_cost_delay; SHOW vacuum_cost_delay;
vacuum_cost_delay vacuum_cost_delay
------------------- -------------------
90ms 900us
(1 row) (1 row)
SHOW datestyle; SHOW datestyle;
...@@ -508,7 +508,7 @@ SELECT '2006-08-13 12:34:56'::timestamptz; ...@@ -508,7 +508,7 @@ SELECT '2006-08-13 12:34:56'::timestamptz;
-- Test some simple error cases -- Test some simple error cases
SET seq_page_cost TO 'NaN'; 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'; SET vacuum_cost_delay TO '10s';
ERROR: 10000 ms is outside the valid range for parameter "vacuum_cost_delay" (0 .. 100) ERROR: 10000 ms is outside the valid range for parameter "vacuum_cost_delay" (0 .. 100)
SET geqo_selection_bias TO '-infinity'; SET geqo_selection_bias TO '-infinity';
......
...@@ -47,7 +47,7 @@ SET datestyle = 'MDY'; ...@@ -47,7 +47,7 @@ SET datestyle = 'MDY';
SHOW datestyle; SHOW datestyle;
SELECT '2006-08-13 12:34:56'::timestamptz; SELECT '2006-08-13 12:34:56'::timestamptz;
SAVEPOINT first_sp; SAVEPOINT first_sp;
SET vacuum_cost_delay TO 80; SET vacuum_cost_delay TO 80.1;
SHOW vacuum_cost_delay; SHOW vacuum_cost_delay;
SET datestyle = 'German, DMY'; SET datestyle = 'German, DMY';
SHOW datestyle; SHOW datestyle;
...@@ -56,7 +56,7 @@ ROLLBACK TO first_sp; ...@@ -56,7 +56,7 @@ ROLLBACK TO first_sp;
SHOW datestyle; SHOW datestyle;
SELECT '2006-08-13 12:34:56'::timestamptz; SELECT '2006-08-13 12:34:56'::timestamptz;
SAVEPOINT second_sp; SAVEPOINT second_sp;
SET vacuum_cost_delay TO 90; SET vacuum_cost_delay TO '900us';
SET datestyle = 'SQL, YMD'; SET datestyle = 'SQL, YMD';
SHOW datestyle; SHOW datestyle;
SELECT '2006-08-13 12:34:56'::timestamptz; SELECT '2006-08-13 12:34:56'::timestamptz;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment