Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 19 additions & 4 deletions src/remote/stats-drift.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,11 +105,26 @@ describe("detectDrift — Size Drift", () => {
expect(verdict.drifted).toBe(true);
});

it("fires on a quarter-sized move", () => {
// A table a quarter bigger than the snapshot has every query against it
// costed a quarter light, which is well past what a planner shrugs off.
const baseline = baselineFromDump([table("users", BIG)]);

const verdict = detectDrift(baseline, {
reltuples: reltuples({ users: Math.round(BIG * 1.25) }),
});

expect(verdict.drifted).toBe(true);
});

it("ignores a move below the ratio", () => {
// One autoanalyze tick. `pg_class.reltuples` only moves when autovacuum
// analyzes the table, so a move this size is as likely to be the sampling
// catching up as the data actually growing.
const baseline = baselineFromDump([table("users", BIG)]);

const verdict = detectDrift(baseline, {
reltuples: reltuples({ users: Math.round(BIG * 1.2) }),
reltuples: reltuples({ users: Math.round(BIG * 1.1) }),
});

expect(verdict.drifted).toBe(false);
Expand Down Expand Up @@ -190,14 +205,14 @@ describe("detectDrift — the closest table that did not drift", () => {
const baseline = baselineFromDump([table("users", BIG), table("teams", BIG)]);

const verdict = detectDrift(baseline, {
reltuples: reltuples({ users: BIG * 0.6, teams: BIG * 0.95 }),
reltuples: reltuples({ users: BIG * 0.85, teams: BIG * 0.95 }),
});

expect(verdict.drifted).toBe(false);
// users moved 40%, teams 5%. The threshold is 50%, so neither fires.
// users moved 15%, teams 5%. The threshold is 20%, so neither fires.
expect(verdict.drifted === false && verdict.closest).toEqual({
table: "public.users",
ratio: expect.closeTo(0.4, 5),
ratio: expect.closeTo(0.15, 5),
});
});

Expand Down
18 changes: 14 additions & 4 deletions src/remote/stats-drift.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,21 @@ export type DriftVerdict =
| { drifted: true; kind: "shape" | "size"; reason: string };

/**
* Ratio a table's `reltuples` must move by before Size Drift fires. 0.5 means
* the table has to halve or grow by 50%; below that the planner's choices are
* unlikely to change enough to be worth a full dump.
* Ratio a table's `reltuples` must move by before Size Drift fires. 0.2 means
* the table has to grow or shrink by a fifth.
*
* The floor under this number is the resolution of the signal it reads.
* `pg_class.reltuples` is only rewritten when autovacuum analyzes the table,
* which by default happens once it has changed by `autovacuum_analyze_scale_
* factor` — 10%. A threshold below that would be reading sampling noise rather
* than the data moving.
*
* The ceiling is how wrong a cost may be before it stops being worth
* reporting. At the old 0.5 a table could sit half again its snapshot size and
* still be called current, so every query against it was costed a third light
* — a bigger error than most of what we flag.
*/
export const DEFAULT_SIZE_DRIFT_RATIO = 0.5;
export const DEFAULT_SIZE_DRIFT_RATIO = 0.2;

/**
* Tables smaller than this are exempt from Size Drift. A table going from 2 to
Expand Down