@quereus/quereus 4.21.1 → 4.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/core/database-cardinality-feedback.d.ts +146 -0
- package/dist/src/core/database-cardinality-feedback.d.ts.map +1 -0
- package/dist/src/core/database-cardinality-feedback.js +274 -0
- package/dist/src/core/database-cardinality-feedback.js.map +1 -0
- package/dist/src/core/database-transaction.d.ts +9 -2
- package/dist/src/core/database-transaction.d.ts.map +1 -1
- package/dist/src/core/database-transaction.js +27 -9
- package/dist/src/core/database-transaction.js.map +1 -1
- package/dist/src/core/database.d.ts +4 -0
- package/dist/src/core/database.d.ts.map +1 -1
- package/dist/src/core/database.js +78 -7
- package/dist/src/core/database.js.map +1 -1
- package/dist/src/core/statement.d.ts +11 -2
- package/dist/src/core/statement.d.ts.map +1 -1
- package/dist/src/core/statement.js +46 -2
- package/dist/src/core/statement.js.map +1 -1
- package/dist/src/func/builtins/cardinality-feedback.d.ts +9 -0
- package/dist/src/func/builtins/cardinality-feedback.d.ts.map +1 -0
- package/dist/src/func/builtins/cardinality-feedback.js +54 -0
- package/dist/src/func/builtins/cardinality-feedback.js.map +1 -0
- package/dist/src/func/builtins/index.d.ts.map +1 -1
- package/dist/src/func/builtins/index.js +2 -0
- package/dist/src/func/builtins/index.js.map +1 -1
- package/dist/src/planner/nodes/reference.d.ts +7 -0
- package/dist/src/planner/nodes/reference.d.ts.map +1 -1
- package/dist/src/planner/nodes/reference.js +10 -2
- package/dist/src/planner/nodes/reference.js.map +1 -1
- package/dist/src/planner/optimizer-tuning.d.ts +29 -0
- package/dist/src/planner/optimizer-tuning.d.ts.map +1 -1
- package/dist/src/planner/optimizer-tuning.js +7 -1
- package/dist/src/planner/optimizer-tuning.js.map +1 -1
- package/dist/src/planner/rules/retrieve/rule-grow-retrieve.js +6 -6
- package/dist/src/planner/stats/catalog-stats.d.ts +11 -2
- package/dist/src/planner/stats/catalog-stats.d.ts.map +1 -1
- package/dist/src/planner/stats/catalog-stats.js +14 -5
- package/dist/src/planner/stats/catalog-stats.js.map +1 -1
- package/dist/src/planner/stats/table-cardinality.d.ts +52 -18
- package/dist/src/planner/stats/table-cardinality.d.ts.map +1 -1
- package/dist/src/planner/stats/table-cardinality.js +46 -18
- package/dist/src/planner/stats/table-cardinality.js.map +1 -1
- package/dist/src/runtime/cardinality-observations.d.ts +30 -0
- package/dist/src/runtime/cardinality-observations.d.ts.map +1 -0
- package/dist/src/runtime/cardinality-observations.js +37 -0
- package/dist/src/runtime/cardinality-observations.js.map +1 -0
- package/dist/src/runtime/emit/scan.d.ts.map +1 -1
- package/dist/src/runtime/emit/scan.js +29 -0
- package/dist/src/runtime/emit/scan.js.map +1 -1
- package/dist/src/runtime/parallel-driver.d.ts +3 -0
- package/dist/src/runtime/parallel-driver.d.ts.map +1 -1
- package/dist/src/runtime/parallel-driver.js +4 -0
- package/dist/src/runtime/parallel-driver.js.map +1 -1
- package/dist/src/runtime/types.d.ts +12 -0
- package/dist/src/runtime/types.d.ts.map +1 -1
- package/dist/src/runtime/types.js.map +1 -1
- package/dist/src/schema/manager.d.ts +7 -0
- package/dist/src/schema/manager.d.ts.map +1 -1
- package/dist/src/schema/manager.js +8 -0
- package/dist/src/schema/manager.js.map +1 -1
- package/package.json +3 -3
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The observed row-count overlay: exact base-table sizes seen during execution.
|
|
3
|
+
*
|
|
4
|
+
* One per `Database`. The planner reads it through `plannedRowCount`
|
|
5
|
+
* (`planner/stats/table-cardinality.ts`), where an answering entry outranks the
|
|
6
|
+
* catalog's `ANALYZE` count. This module owns the record policy — which
|
|
7
|
+
* observations are worth keeping, and when one has moved far enough from what
|
|
8
|
+
* plans were costed against to call it a *shift* — and the entries' lifetime.
|
|
9
|
+
*
|
|
10
|
+
* Validity is schema identity: an entry answers only for the exact `TableSchema`
|
|
11
|
+
* object it was observed under. Any swap (`ANALYZE`, `ALTER TABLE`, drop and
|
|
12
|
+
* re-create) silences it, and a schema-change listener evicts it. The identity
|
|
13
|
+
* check is the correctness guard; the listener is memory hygiene.
|
|
14
|
+
*
|
|
15
|
+
* Execution feeds it through {@link CardinalityFeedbackManager.publish}, which admits
|
|
16
|
+
* only counts of committed state: a count taken inside a transaction is held until
|
|
17
|
+
* that transaction commits ({@link CardinalityFeedbackManager.onCommitted}) and
|
|
18
|
+
* dropped if it rolls back ({@link CardinalityFeedbackManager.onRolledBack}).
|
|
19
|
+
*
|
|
20
|
+
* A shift reaches prepared statements through `onShift`; each statement paces its
|
|
21
|
+
* re-plans with a {@link FeedbackReplanState}.
|
|
22
|
+
*
|
|
23
|
+
* Nothing here is persisted: a new `Database` starts empty. See
|
|
24
|
+
* docs/progressive-optimizer.md §3.2–3.6.
|
|
25
|
+
*/
|
|
26
|
+
import type { OptimizerTuning } from '../planner/optimizer-tuning.js';
|
|
27
|
+
import { CardinalityObservations } from '../runtime/cardinality-observations.js';
|
|
28
|
+
import { type ObservedRowCounts } from '../planner/stats/table-cardinality.js';
|
|
29
|
+
import type { TableSchema } from '../schema/table.js';
|
|
30
|
+
import type { Database } from './database.js';
|
|
31
|
+
/** Database internals the manager needs — keeps it constructible without the full `Database`. */
|
|
32
|
+
export interface CardinalityFeedbackManagerContext {
|
|
33
|
+
readonly schemaManager: Database['schemaManager'];
|
|
34
|
+
/** Read on every record, so `Database.updateOptimizerTuning` takes effect immediately. */
|
|
35
|
+
getTuning(): OptimizerTuning;
|
|
36
|
+
/** True when no transaction (explicit or implicit) is open. */
|
|
37
|
+
getAutocommit(): boolean;
|
|
38
|
+
/**
|
|
39
|
+
* A recorded count moved at least `cardinalityFeedback.shiftFactor` away from the
|
|
40
|
+
* entry's baseline: plans costed against the old number may be wrong. `key` is the
|
|
41
|
+
* lowercased `schema.table`.
|
|
42
|
+
*/
|
|
43
|
+
onShift(key: string): void;
|
|
44
|
+
}
|
|
45
|
+
/** By-value view of one valid entry, for tests and diagnostics. */
|
|
46
|
+
export interface ObservedRowCountSnapshot {
|
|
47
|
+
readonly schemaName: string;
|
|
48
|
+
readonly tableName: string;
|
|
49
|
+
readonly rows: number;
|
|
50
|
+
/** What the catalog (`ANALYZE`) says for the same table; `undefined` when never analyzed. */
|
|
51
|
+
readonly catalogRows: number | undefined;
|
|
52
|
+
readonly baselineRows: number;
|
|
53
|
+
readonly observations: number;
|
|
54
|
+
readonly shifts: number;
|
|
55
|
+
}
|
|
56
|
+
/** The overlay's key for a table: lowercased `schema.table`. Shifts are reported under it. */
|
|
57
|
+
export declare function overlayTableKey(schemaName: string, tableName: string): string;
|
|
58
|
+
/**
|
|
59
|
+
* One prepared statement's feedback re-plan cooldown (docs/progressive-optimizer.md §3.6).
|
|
60
|
+
* A shift marks the cached plan; the mark is acted on at an execution start, never
|
|
61
|
+
* mid-execution. The first feedback re-plan is immediate; re-plan `k + 1` waits until the
|
|
62
|
+
* statement has executed `cooldown × 2^min(k − 1, 6)` times since re-plan `k` — a mark
|
|
63
|
+
* arriving sooner stays pending until then.
|
|
64
|
+
*/
|
|
65
|
+
export declare class FeedbackReplanState {
|
|
66
|
+
private marked;
|
|
67
|
+
/** Feedback re-plans so far. */
|
|
68
|
+
private replans;
|
|
69
|
+
/** Executions started since the last feedback re-plan, the re-planning one included. */
|
|
70
|
+
private executionsSinceReplan;
|
|
71
|
+
/** A table the cached plan reads shifted. */
|
|
72
|
+
markShift(): void;
|
|
73
|
+
/** The cached plan was dropped for any reason; whatever compiles next reads the current overlay. */
|
|
74
|
+
onPlanDropped(): void;
|
|
75
|
+
/** Counts one execution; true when the marked plan must be dropped before it runs. */
|
|
76
|
+
onExecutionStart(cooldown: number): boolean;
|
|
77
|
+
private requiredExecutions;
|
|
78
|
+
}
|
|
79
|
+
export declare class CardinalityFeedbackManager implements ObservedRowCounts {
|
|
80
|
+
private readonly ctx;
|
|
81
|
+
/**
|
|
82
|
+
* Lowercased `schema.table` → entry, the change-log / work-counter key convention.
|
|
83
|
+
* `Map` insertion order doubles as recency: a hit or a record re-inserts, so the
|
|
84
|
+
* first key is the least recently used.
|
|
85
|
+
*/
|
|
86
|
+
private readonly entries;
|
|
87
|
+
/**
|
|
88
|
+
* Observations made inside the open transaction, held until it ends; same key as
|
|
89
|
+
* `entries`, last count per table wins. Bounded by the distinct tables one
|
|
90
|
+
* transaction observes.
|
|
91
|
+
*/
|
|
92
|
+
private readonly pending;
|
|
93
|
+
/** {@link onRolledBack} calls so far; each execution's sink records it at start. */
|
|
94
|
+
private rollbacks;
|
|
95
|
+
private unsubscribeSchemaChanges;
|
|
96
|
+
constructor(ctx: CardinalityFeedbackManagerContext);
|
|
97
|
+
observedRows(table: TableSchema): number | undefined;
|
|
98
|
+
/**
|
|
99
|
+
* Offer one exact row count of `table`, observed by a complete enumeration of
|
|
100
|
+
* committed state. Applies the record policy (docs/progressive-optimizer.md §3.4):
|
|
101
|
+
* ignored when the schema object is superseded or when both the observation and the
|
|
102
|
+
* current estimate are small; otherwise recorded, and reported through `onShift`
|
|
103
|
+
* when it has moved `shiftFactor` from the entry's baseline.
|
|
104
|
+
*/
|
|
105
|
+
record(table: TableSchema, rows: number): void;
|
|
106
|
+
/**
|
|
107
|
+
* Offer one finished execution's observations (docs/progressive-optimizer.md §3.5).
|
|
108
|
+
* A committed read (`readCommitted`) or an execution that ended with no transaction
|
|
109
|
+
* open saw committed state and is recorded now. Anything else may include the open
|
|
110
|
+
* transaction's uncommitted writes, so it is held for {@link onCommitted} — unless a
|
|
111
|
+
* rollback ran during the execution (typically a failing statement's own statement
|
|
112
|
+
* savepoint, which fires before publication): then a completed scan may have counted
|
|
113
|
+
* rows that no longer exist, and the whole execution is discarded.
|
|
114
|
+
*/
|
|
115
|
+
publish(observations: CardinalityObservations, options: {
|
|
116
|
+
readCommitted: boolean;
|
|
117
|
+
}): void;
|
|
118
|
+
/** A fresh per-execution sink, stamped so {@link publish} can tell whether a rollback ran under it. */
|
|
119
|
+
beginObservations(): CardinalityObservations;
|
|
120
|
+
/**
|
|
121
|
+
* The open transaction committed: record what it held, except tables it wrote.
|
|
122
|
+
* Such a count was taken mid-transaction, possibly before later writes in the same
|
|
123
|
+
* transaction, and the change log cannot say which came first. `changedCounts` is
|
|
124
|
+
* the change log keyed like `entries` — every table the transaction wrote, even one
|
|
125
|
+
* whose writes cancelled out — and is not built when nothing is held.
|
|
126
|
+
*/
|
|
127
|
+
onCommitted(changedCounts: () => Map<string, number>): void;
|
|
128
|
+
/** The open transaction, or some of its work, was undone: forget everything held. */
|
|
129
|
+
onRolledBack(): void;
|
|
130
|
+
/** Forget every observation, held ones included; later compiles read the catalog only. */
|
|
131
|
+
clear(): void;
|
|
132
|
+
/** Valid entries, by value, least recently used first. Does not refresh recency. */
|
|
133
|
+
snapshot(): ObservedRowCountSnapshot[];
|
|
134
|
+
dispose(): void;
|
|
135
|
+
private subscribeToSchemaChanges;
|
|
136
|
+
private recordAll;
|
|
137
|
+
/** The entry under `key` if it was observed against `table`; a stale one is replaced on record. */
|
|
138
|
+
private validEntry;
|
|
139
|
+
private touch;
|
|
140
|
+
/**
|
|
141
|
+
* Drop least-recently-used entries past the cap. No `onShift`: plans already built
|
|
142
|
+
* keep their snapshot, and the next compile falls back to catalog or default numbers.
|
|
143
|
+
*/
|
|
144
|
+
private evictBeyond;
|
|
145
|
+
}
|
|
146
|
+
//# sourceMappingURL=database-cardinality-feedback.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"database-cardinality-feedback.d.ts","sourceRoot":"","sources":["../../../src/core/database-cardinality-feedback.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The observed row-count overlay: exact base-table sizes seen during execution.
|
|
3
|
+
*
|
|
4
|
+
* One per `Database`. The planner reads it through `plannedRowCount`
|
|
5
|
+
* (`planner/stats/table-cardinality.ts`), where an answering entry outranks the
|
|
6
|
+
* catalog's `ANALYZE` count. This module owns the record policy — which
|
|
7
|
+
* observations are worth keeping, and when one has moved far enough from what
|
|
8
|
+
* plans were costed against to call it a *shift* — and the entries' lifetime.
|
|
9
|
+
*
|
|
10
|
+
* Validity is schema identity: an entry answers only for the exact `TableSchema`
|
|
11
|
+
* object it was observed under. Any swap (`ANALYZE`, `ALTER TABLE`, drop and
|
|
12
|
+
* re-create) silences it, and a schema-change listener evicts it. The identity
|
|
13
|
+
* check is the correctness guard; the listener is memory hygiene.
|
|
14
|
+
*
|
|
15
|
+
* Execution feeds it through {@link CardinalityFeedbackManager.publish}, which admits
|
|
16
|
+
* only counts of committed state: a count taken inside a transaction is held until
|
|
17
|
+
* that transaction commits ({@link CardinalityFeedbackManager.onCommitted}) and
|
|
18
|
+
* dropped if it rolls back ({@link CardinalityFeedbackManager.onRolledBack}).
|
|
19
|
+
*
|
|
20
|
+
* A shift reaches prepared statements through `onShift`; each statement paces its
|
|
21
|
+
* re-plans with a {@link FeedbackReplanState}.
|
|
22
|
+
*
|
|
23
|
+
* Nothing here is persisted: a new `Database` starts empty. See
|
|
24
|
+
* docs/progressive-optimizer.md §3.2–3.6.
|
|
25
|
+
*/
|
|
26
|
+
import { createLogger } from '../common/logger.js';
|
|
27
|
+
import { CardinalityObservations } from '../runtime/cardinality-observations.js';
|
|
28
|
+
import { catalogRowCount } from '../planner/stats/table-cardinality.js';
|
|
29
|
+
const log = createLogger('core:cardinality-feedback');
|
|
30
|
+
const debugLog = log.extend('debug');
|
|
31
|
+
/**
|
|
32
|
+
* Distance between two counts as a ratio ≥ 1. The `+ 1` on both sides keeps a zero
|
|
33
|
+
* count finite and makes tiny counts compare sanely (0 vs 3 is 4×, not infinite).
|
|
34
|
+
*/
|
|
35
|
+
function rowCountSpread(a, b) {
|
|
36
|
+
return (Math.max(a, b) + 1) / (Math.min(a, b) + 1);
|
|
37
|
+
}
|
|
38
|
+
/** The overlay's key for a table: lowercased `schema.table`. Shifts are reported under it. */
|
|
39
|
+
export function overlayTableKey(schemaName, tableName) {
|
|
40
|
+
return `${schemaName}.${tableName}`.toLowerCase();
|
|
41
|
+
}
|
|
42
|
+
function tableKey(table) {
|
|
43
|
+
return overlayTableKey(table.schemaName, table.name);
|
|
44
|
+
}
|
|
45
|
+
/** Cap on the cooldown's doubling: re-plans past the seventh wait `64 × replanCooldown` executions. */
|
|
46
|
+
const MAX_COOLDOWN_DOUBLINGS = 6;
|
|
47
|
+
/**
|
|
48
|
+
* One prepared statement's feedback re-plan cooldown (docs/progressive-optimizer.md §3.6).
|
|
49
|
+
* A shift marks the cached plan; the mark is acted on at an execution start, never
|
|
50
|
+
* mid-execution. The first feedback re-plan is immediate; re-plan `k + 1` waits until the
|
|
51
|
+
* statement has executed `cooldown × 2^min(k − 1, 6)` times since re-plan `k` — a mark
|
|
52
|
+
* arriving sooner stays pending until then.
|
|
53
|
+
*/
|
|
54
|
+
export class FeedbackReplanState {
|
|
55
|
+
marked = false;
|
|
56
|
+
/** Feedback re-plans so far. */
|
|
57
|
+
replans = 0;
|
|
58
|
+
/** Executions started since the last feedback re-plan, the re-planning one included. */
|
|
59
|
+
executionsSinceReplan = 0;
|
|
60
|
+
/** A table the cached plan reads shifted. */
|
|
61
|
+
markShift() {
|
|
62
|
+
this.marked = true;
|
|
63
|
+
}
|
|
64
|
+
/** The cached plan was dropped for any reason; whatever compiles next reads the current overlay. */
|
|
65
|
+
onPlanDropped() {
|
|
66
|
+
this.marked = false;
|
|
67
|
+
}
|
|
68
|
+
/** Counts one execution; true when the marked plan must be dropped before it runs. */
|
|
69
|
+
onExecutionStart(cooldown) {
|
|
70
|
+
const due = this.marked && (this.replans === 0 || this.executionsSinceReplan >= this.requiredExecutions(cooldown));
|
|
71
|
+
if (due) {
|
|
72
|
+
this.marked = false;
|
|
73
|
+
this.replans++;
|
|
74
|
+
this.executionsSinceReplan = 0;
|
|
75
|
+
}
|
|
76
|
+
this.executionsSinceReplan++;
|
|
77
|
+
return due;
|
|
78
|
+
}
|
|
79
|
+
requiredExecutions(cooldown) {
|
|
80
|
+
return cooldown * 2 ** Math.min(this.replans - 1, MAX_COOLDOWN_DOUBLINGS);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
export class CardinalityFeedbackManager {
|
|
84
|
+
ctx;
|
|
85
|
+
/**
|
|
86
|
+
* Lowercased `schema.table` → entry, the change-log / work-counter key convention.
|
|
87
|
+
* `Map` insertion order doubles as recency: a hit or a record re-inserts, so the
|
|
88
|
+
* first key is the least recently used.
|
|
89
|
+
*/
|
|
90
|
+
entries = new Map();
|
|
91
|
+
/**
|
|
92
|
+
* Observations made inside the open transaction, held until it ends; same key as
|
|
93
|
+
* `entries`, last count per table wins. Bounded by the distinct tables one
|
|
94
|
+
* transaction observes.
|
|
95
|
+
*/
|
|
96
|
+
pending = new Map();
|
|
97
|
+
/** {@link onRolledBack} calls so far; each execution's sink records it at start. */
|
|
98
|
+
rollbacks = 0;
|
|
99
|
+
unsubscribeSchemaChanges = null;
|
|
100
|
+
constructor(ctx) {
|
|
101
|
+
this.ctx = ctx;
|
|
102
|
+
this.subscribeToSchemaChanges();
|
|
103
|
+
}
|
|
104
|
+
observedRows(table) {
|
|
105
|
+
const key = tableKey(table);
|
|
106
|
+
const entry = this.entries.get(key);
|
|
107
|
+
if (!entry || entry.schema !== table)
|
|
108
|
+
return undefined;
|
|
109
|
+
this.touch(key, entry);
|
|
110
|
+
return entry.rows;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Offer one exact row count of `table`, observed by a complete enumeration of
|
|
114
|
+
* committed state. Applies the record policy (docs/progressive-optimizer.md §3.4):
|
|
115
|
+
* ignored when the schema object is superseded or when both the observation and the
|
|
116
|
+
* current estimate are small; otherwise recorded, and reported through `onShift`
|
|
117
|
+
* when it has moved `shiftFactor` from the entry's baseline.
|
|
118
|
+
*/
|
|
119
|
+
record(table, rows) {
|
|
120
|
+
if (this.ctx.schemaManager.findTable(table.name, table.schemaName) !== table) {
|
|
121
|
+
debugLog('Ignored %d rows for %s.%s: observed under a superseded schema', rows, table.schemaName, table.name);
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
const tuning = this.ctx.getTuning();
|
|
125
|
+
const policy = tuning.cardinalityFeedback;
|
|
126
|
+
const key = tableKey(table);
|
|
127
|
+
const existing = this.validEntry(key, table);
|
|
128
|
+
const before = existing?.rows ?? catalogRowCount(table) ?? tuning.defaultRowEstimate;
|
|
129
|
+
if (Math.max(before, rows) <= policy.minRows)
|
|
130
|
+
return;
|
|
131
|
+
const entry = existing ?? { schema: table, rows, baselineRows: before, observations: 0, shifts: 0 };
|
|
132
|
+
entry.rows = rows;
|
|
133
|
+
entry.observations++;
|
|
134
|
+
this.touch(key, entry);
|
|
135
|
+
this.evictBeyond(policy.maxTables);
|
|
136
|
+
const shifted = rowCountSpread(rows, entry.baselineRows) >= policy.shiftFactor;
|
|
137
|
+
debugLog('Recorded %d rows for %s (was %d, baseline %d)%s', rows, key, before, entry.baselineRows, shifted ? ' — shift' : '');
|
|
138
|
+
if (!shifted)
|
|
139
|
+
return;
|
|
140
|
+
log('Shift on %s: baseline %d -> %d rows', key, entry.baselineRows, rows);
|
|
141
|
+
entry.baselineRows = rows;
|
|
142
|
+
entry.shifts++;
|
|
143
|
+
this.ctx.onShift(key);
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Offer one finished execution's observations (docs/progressive-optimizer.md §3.5).
|
|
147
|
+
* A committed read (`readCommitted`) or an execution that ended with no transaction
|
|
148
|
+
* open saw committed state and is recorded now. Anything else may include the open
|
|
149
|
+
* transaction's uncommitted writes, so it is held for {@link onCommitted} — unless a
|
|
150
|
+
* rollback ran during the execution (typically a failing statement's own statement
|
|
151
|
+
* savepoint, which fires before publication): then a completed scan may have counted
|
|
152
|
+
* rows that no longer exist, and the whole execution is discarded.
|
|
153
|
+
*/
|
|
154
|
+
publish(observations, options) {
|
|
155
|
+
if (options.readCommitted) {
|
|
156
|
+
this.recordAll(observations);
|
|
157
|
+
}
|
|
158
|
+
else if (observations.rollbacksAtStart !== this.rollbacks) {
|
|
159
|
+
debugLog('Discarded observations: work was rolled back during the execution');
|
|
160
|
+
}
|
|
161
|
+
else if (this.ctx.getAutocommit()) {
|
|
162
|
+
this.recordAll(observations);
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
for (const [table, rows] of observations.entries()) {
|
|
166
|
+
this.pending.set(tableKey(table), { table, rows });
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
/** A fresh per-execution sink, stamped so {@link publish} can tell whether a rollback ran under it. */
|
|
171
|
+
beginObservations() {
|
|
172
|
+
return new CardinalityObservations(this.rollbacks);
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* The open transaction committed: record what it held, except tables it wrote.
|
|
176
|
+
* Such a count was taken mid-transaction, possibly before later writes in the same
|
|
177
|
+
* transaction, and the change log cannot say which came first. `changedCounts` is
|
|
178
|
+
* the change log keyed like `entries` — every table the transaction wrote, even one
|
|
179
|
+
* whose writes cancelled out — and is not built when nothing is held.
|
|
180
|
+
*/
|
|
181
|
+
onCommitted(changedCounts) {
|
|
182
|
+
// NOTE: external-change ingestion with `captureChanges: false` writes rows the change
|
|
183
|
+
// log never sees, so a count held from before such a batch is applied. Only an estimate
|
|
184
|
+
// lags; if those batches ever routinely shrink or grow tables, have them veto here too.
|
|
185
|
+
if (this.pending.size === 0)
|
|
186
|
+
return;
|
|
187
|
+
try {
|
|
188
|
+
const changed = changedCounts();
|
|
189
|
+
for (const [key, { table, rows }] of this.pending) {
|
|
190
|
+
if (changed.has(key)) {
|
|
191
|
+
debugLog('Dropped %d rows for %s: the committing transaction wrote it', rows, key);
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
this.record(table, rows);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
finally {
|
|
198
|
+
this.pending.clear();
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
/** The open transaction, or some of its work, was undone: forget everything held. */
|
|
202
|
+
onRolledBack() {
|
|
203
|
+
this.pending.clear();
|
|
204
|
+
this.rollbacks++;
|
|
205
|
+
}
|
|
206
|
+
/** Forget every observation, held ones included; later compiles read the catalog only. */
|
|
207
|
+
clear() {
|
|
208
|
+
this.entries.clear();
|
|
209
|
+
this.pending.clear();
|
|
210
|
+
}
|
|
211
|
+
/** Valid entries, by value, least recently used first. Does not refresh recency. */
|
|
212
|
+
snapshot() {
|
|
213
|
+
const out = [];
|
|
214
|
+
for (const entry of this.entries.values()) {
|
|
215
|
+
const { schema } = entry;
|
|
216
|
+
if (this.ctx.schemaManager.findTable(schema.name, schema.schemaName) !== schema)
|
|
217
|
+
continue;
|
|
218
|
+
out.push({
|
|
219
|
+
schemaName: schema.schemaName,
|
|
220
|
+
tableName: schema.name,
|
|
221
|
+
rows: entry.rows,
|
|
222
|
+
catalogRows: catalogRowCount(schema),
|
|
223
|
+
baselineRows: entry.baselineRows,
|
|
224
|
+
observations: entry.observations,
|
|
225
|
+
shifts: entry.shifts,
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
return out;
|
|
229
|
+
}
|
|
230
|
+
dispose() {
|
|
231
|
+
if (this.unsubscribeSchemaChanges) {
|
|
232
|
+
this.unsubscribeSchemaChanges();
|
|
233
|
+
this.unsubscribeSchemaChanges = null;
|
|
234
|
+
}
|
|
235
|
+
this.clear();
|
|
236
|
+
}
|
|
237
|
+
subscribeToSchemaChanges() {
|
|
238
|
+
const notifier = this.ctx.schemaManager.getChangeNotifier();
|
|
239
|
+
this.unsubscribeSchemaChanges = notifier.addListener((event) => {
|
|
240
|
+
// Keyed by `oldObject`, not the event's name: a rename reports the NEW name.
|
|
241
|
+
// `DETACH` fires no per-table events; its tables' entries never match again
|
|
242
|
+
// and age out under the LRU cap.
|
|
243
|
+
if (event.type === 'table_modified' || event.type === 'table_removed') {
|
|
244
|
+
this.entries.delete(tableKey(event.oldObject));
|
|
245
|
+
}
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
recordAll(observations) {
|
|
249
|
+
for (const [table, rows] of observations.entries()) {
|
|
250
|
+
this.record(table, rows);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
/** The entry under `key` if it was observed against `table`; a stale one is replaced on record. */
|
|
254
|
+
validEntry(key, table) {
|
|
255
|
+
const entry = this.entries.get(key);
|
|
256
|
+
return entry?.schema === table ? entry : undefined;
|
|
257
|
+
}
|
|
258
|
+
touch(key, entry) {
|
|
259
|
+
this.entries.delete(key);
|
|
260
|
+
this.entries.set(key, entry);
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* Drop least-recently-used entries past the cap. No `onShift`: plans already built
|
|
264
|
+
* keep their snapshot, and the next compile falls back to catalog or default numbers.
|
|
265
|
+
*/
|
|
266
|
+
evictBeyond(maxTables) {
|
|
267
|
+
for (const key of this.entries.keys()) {
|
|
268
|
+
if (this.entries.size <= maxTables)
|
|
269
|
+
return;
|
|
270
|
+
this.entries.delete(key);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
//# sourceMappingURL=database-cardinality-feedback.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"database-cardinality-feedback.js","sourceRoot":"","sources":["../../../src/core/database-cardinality-feedback.ts"],"names":[],"mappings":"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"}
|
|
@@ -41,6 +41,9 @@ export interface TransactionManagerContext {
|
|
|
41
41
|
/** Called after a successful commit, before the change log is cleared. The callback
|
|
42
42
|
* materializes per-table committed row counts only if a consumer wants them. */
|
|
43
43
|
recordCommittedChangeCounts(counts: () => Map<string, number>): void;
|
|
44
|
+
/** Called when work of the open transaction is undone: a rollback, a failed commit,
|
|
45
|
+
* or a `rollback to savepoint`. Bookkeeping only — errors are logged, never thrown. */
|
|
46
|
+
transactionRolledBack(): void;
|
|
44
47
|
}
|
|
45
48
|
/**
|
|
46
49
|
* Op kind for a captured row change.
|
|
@@ -126,6 +129,8 @@ export declare class TransactionManager {
|
|
|
126
129
|
* Rolls back the current transaction on all connections.
|
|
127
130
|
*/
|
|
128
131
|
rollbackTransaction(): Promise<void>;
|
|
132
|
+
/** Tell the context work was undone, isolated like commit bookkeeping. */
|
|
133
|
+
private notifyRolledBack;
|
|
129
134
|
/**
|
|
130
135
|
* Ensures we're in a transaction. If in autocommit mode, starts an implicit transaction.
|
|
131
136
|
*/
|
|
@@ -220,8 +225,10 @@ export declare class TransactionManager {
|
|
|
220
225
|
* so the whole reading is O(tables × layers) with no scan.
|
|
221
226
|
*
|
|
222
227
|
* Inherits the change log's coalescing — ten updates to one row count as one,
|
|
223
|
-
* and an insert followed by a delete of the same key
|
|
224
|
-
*
|
|
228
|
+
* and an insert followed by a delete of the same key counts zero. That makes
|
|
229
|
+
* this a *distinct rows touched* measure. A table written whose changes all
|
|
230
|
+
* cancelled out still appears, with count 0: the key set is every table the
|
|
231
|
+
* transaction wrote, which held cardinality observations rely on.
|
|
225
232
|
*
|
|
226
233
|
* Deliberately approximate across layers: the same key changed in two live
|
|
227
234
|
* savepoint layers is counted twice. Deduplicating would cost a scan of every
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"database-transaction.d.ts","sourceRoot":"","sources":["../../../src/core/database-transaction.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,OAAO,KAAK,EAAE,GAAG,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAIxD,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AACpE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AACjE,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,yCAAyC,CAAC;AACvF,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAMnE;;GAEG;AACH,MAAM,MAAM,iBAAiB,GAAG,UAAU,GAAG,UAAU,CAAC;AAExD;;;GAGG;AACH,MAAM,WAAW,yBAAyB;IACzC,+CAA+C;IAC/C,iBAAiB,IAAI,sBAAsB,EAAE,CAAC;IAC9C,qCAAqC;IACrC,eAAe,IAAI,oBAAoB,CAAC;IACxC,wCAAwC;IACxC,sBAAsB,IAAI,uBAAuB,CAAC;IAClD;;8CAE0C;IAC1C,mBAAmB,CAAC,IAAI,CAAC,EAAE,kBAAkB,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAChE,iDAAiD;IACjD,yBAAyB,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3C;;sBAEkB;IAClB,qBAAqB,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvC;qFACiF;IACjF,2BAA2B,CAAC,MAAM,EAAE,MAAM,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"database-transaction.d.ts","sourceRoot":"","sources":["../../../src/core/database-transaction.ts"],"names":[],"mappings":"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"}
|
|
@@ -107,6 +107,11 @@ export class TransactionManager {
|
|
|
107
107
|
// Upgrade implicit to explicit
|
|
108
108
|
debugLog('Upgrading implicit transaction to explicit (BEGIN encountered).');
|
|
109
109
|
this.transactionSource = 'explicit';
|
|
110
|
+
// NOTE: held cardinality observations survive the upgrade (same transaction),
|
|
111
|
+
// but this clear forgets which tables were written before it, so a pre-upgrade
|
|
112
|
+
// write after an observation would no longer veto that observation at commit.
|
|
113
|
+
// Unreached today — every implicit transaction ends with its statement — if
|
|
114
|
+
// BEGIN can ever run inside one that wrote, keep the written-table set here.
|
|
110
115
|
this.clearChangeLog();
|
|
111
116
|
return;
|
|
112
117
|
}
|
|
@@ -189,15 +194,16 @@ export class TransactionManager {
|
|
|
189
194
|
catch (err) {
|
|
190
195
|
errorLog('Post-commit watcher dispatch threw: %O', err);
|
|
191
196
|
}
|
|
192
|
-
//
|
|
193
|
-
//
|
|
194
|
-
//
|
|
195
|
-
// bookkeeping error must never fail a
|
|
197
|
+
// Commit bookkeeping (auto-analyze staleness, held cardinality
|
|
198
|
+
// observations), same window: the change log is still alive. The counts
|
|
199
|
+
// map is thunked so it is never materialized when no consumer wants it,
|
|
200
|
+
// and the whole call is isolated — a bookkeeping error must never fail a
|
|
201
|
+
// transaction that has committed.
|
|
196
202
|
try {
|
|
197
203
|
this.ctx.recordCommittedChangeCounts(() => this.getChangedRowCounts());
|
|
198
204
|
}
|
|
199
205
|
catch (err) {
|
|
200
|
-
errorLog('
|
|
206
|
+
errorLog('Commit bookkeeping threw: %O', err);
|
|
201
207
|
}
|
|
202
208
|
// Materialized views are NOT a post-commit consumer: each is row-time
|
|
203
209
|
// maintained synchronously at the DML boundary, so its backing table is
|
|
@@ -207,6 +213,7 @@ export class TransactionManager {
|
|
|
207
213
|
// On pre-commit assertion failure (or commit error), rollback all connections
|
|
208
214
|
const conns = this.ctx.getAllConnections();
|
|
209
215
|
await Promise.allSettled(conns.map(c => c.rollback()));
|
|
216
|
+
this.notifyRolledBack();
|
|
210
217
|
throw e;
|
|
211
218
|
}
|
|
212
219
|
finally {
|
|
@@ -257,6 +264,16 @@ export class TransactionManager {
|
|
|
257
264
|
this.isAutocommit = true;
|
|
258
265
|
this.transactionSource = null;
|
|
259
266
|
this.clearChangeLog();
|
|
267
|
+
this.notifyRolledBack();
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
/** Tell the context work was undone, isolated like commit bookkeeping. */
|
|
271
|
+
notifyRolledBack() {
|
|
272
|
+
try {
|
|
273
|
+
this.ctx.transactionRolledBack();
|
|
274
|
+
}
|
|
275
|
+
catch (err) {
|
|
276
|
+
errorLog('Rollback bookkeeping threw: %O', err);
|
|
260
277
|
}
|
|
261
278
|
}
|
|
262
279
|
/**
|
|
@@ -542,8 +559,10 @@ export class TransactionManager {
|
|
|
542
559
|
* so the whole reading is O(tables × layers) with no scan.
|
|
543
560
|
*
|
|
544
561
|
* Inherits the change log's coalescing — ten updates to one row count as one,
|
|
545
|
-
* and an insert followed by a delete of the same key
|
|
546
|
-
*
|
|
562
|
+
* and an insert followed by a delete of the same key counts zero. That makes
|
|
563
|
+
* this a *distinct rows touched* measure. A table written whose changes all
|
|
564
|
+
* cancelled out still appears, with count 0: the key set is every table the
|
|
565
|
+
* transaction wrote, which held cardinality observations rely on.
|
|
547
566
|
*
|
|
548
567
|
* Deliberately approximate across layers: the same key changed in two live
|
|
549
568
|
* savepoint layers is counted twice. Deduplicating would cost a scan of every
|
|
@@ -553,8 +572,6 @@ export class TransactionManager {
|
|
|
553
572
|
const result = new Map();
|
|
554
573
|
const collect = (m) => {
|
|
555
574
|
for (const [t, rowMap] of m) {
|
|
556
|
-
if (rowMap.size === 0)
|
|
557
|
-
continue;
|
|
558
575
|
result.set(t, (result.get(t) ?? 0) + rowMap.size);
|
|
559
576
|
}
|
|
560
577
|
};
|
|
@@ -721,6 +738,7 @@ export class TransactionManager {
|
|
|
721
738
|
this.savepointStack.length = index + 1;
|
|
722
739
|
// Re-create a fresh layer for the preserved savepoint
|
|
723
740
|
this.beginSavepointLayer();
|
|
741
|
+
this.notifyRolledBack();
|
|
724
742
|
return index;
|
|
725
743
|
}
|
|
726
744
|
// ============================================================================
|