@quereus/quereus 4.15.2 → 4.17.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-auto-analyze.d.ts +258 -0
- package/dist/src/core/database-auto-analyze.d.ts.map +1 -0
- package/dist/src/core/database-auto-analyze.js +616 -0
- package/dist/src/core/database-auto-analyze.js.map +1 -0
- package/dist/src/core/database-materialized-views.d.ts +42 -1
- package/dist/src/core/database-materialized-views.d.ts.map +1 -1
- package/dist/src/core/database-materialized-views.js +76 -3
- package/dist/src/core/database-materialized-views.js.map +1 -1
- package/dist/src/core/database-transaction.d.ts +17 -0
- package/dist/src/core/database-transaction.d.ts.map +1 -1
- package/dist/src/core/database-transaction.js +37 -0
- package/dist/src/core/database-transaction.js.map +1 -1
- package/dist/src/core/database.d.ts +8 -1
- package/dist/src/core/database.d.ts.map +1 -1
- package/dist/src/core/database.js +135 -5
- package/dist/src/core/database.js.map +1 -1
- package/dist/src/index.d.ts +1 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +1 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/planner/analysis/change-scope.d.ts.map +1 -1
- package/dist/src/planner/analysis/change-scope.js +8 -8
- package/dist/src/planner/analysis/change-scope.js.map +1 -1
- package/dist/src/planner/analysis/constraint-extractor.d.ts.map +1 -1
- package/dist/src/planner/analysis/constraint-extractor.js +27 -14
- package/dist/src/planner/analysis/constraint-extractor.js.map +1 -1
- package/dist/src/planner/analysis/expression-fingerprint.d.ts.map +1 -1
- package/dist/src/planner/analysis/expression-fingerprint.js +23 -2
- package/dist/src/planner/analysis/expression-fingerprint.js.map +1 -1
- package/dist/src/planner/analysis/mv-body-functions.d.ts +46 -0
- package/dist/src/planner/analysis/mv-body-functions.d.ts.map +1 -0
- package/dist/src/planner/analysis/mv-body-functions.js +65 -0
- package/dist/src/planner/analysis/mv-body-functions.js.map +1 -0
- package/dist/src/planner/analysis/predicate-shape.d.ts +32 -4
- package/dist/src/planner/analysis/predicate-shape.d.ts.map +1 -1
- package/dist/src/planner/analysis/predicate-shape.js +37 -5
- package/dist/src/planner/analysis/predicate-shape.js.map +1 -1
- package/dist/src/planner/analysis/query-rewrite-matcher.d.ts +2 -2
- package/dist/src/planner/analysis/query-rewrite-matcher.d.ts.map +1 -1
- package/dist/src/planner/analysis/query-rewrite-matcher.js +46 -6
- package/dist/src/planner/analysis/query-rewrite-matcher.js.map +1 -1
- package/dist/src/planner/analysis/sat-checker.js +2 -5
- package/dist/src/planner/analysis/sat-checker.js.map +1 -1
- package/dist/src/planner/building/select-aggregates.d.ts +17 -5
- package/dist/src/planner/building/select-aggregates.d.ts.map +1 -1
- package/dist/src/planner/building/select-aggregates.js +122 -79
- package/dist/src/planner/building/select-aggregates.js.map +1 -1
- package/dist/src/planner/building/select-modifiers.d.ts +1 -1
- package/dist/src/planner/building/select.d.ts.map +1 -1
- package/dist/src/planner/building/select.js +73 -55
- package/dist/src/planner/building/select.js.map +1 -1
- package/dist/src/planner/cost/index.d.ts +14 -0
- package/dist/src/planner/cost/index.d.ts.map +1 -1
- package/dist/src/planner/cost/index.js +19 -0
- package/dist/src/planner/cost/index.js.map +1 -1
- package/dist/src/planner/nodes/limit-offset.d.ts.map +1 -1
- package/dist/src/planner/nodes/limit-offset.js +3 -2
- package/dist/src/planner/nodes/limit-offset.js.map +1 -1
- package/dist/src/planner/nodes/table-access-nodes.d.ts.map +1 -1
- package/dist/src/planner/nodes/table-access-nodes.js +31 -7
- package/dist/src/planner/nodes/table-access-nodes.js.map +1 -1
- package/dist/src/planner/optimizer-tuning.d.ts +10 -6
- package/dist/src/planner/optimizer-tuning.d.ts.map +1 -1
- package/dist/src/planner/optimizer-tuning.js.map +1 -1
- package/dist/src/planner/optimizer.d.ts.map +1 -1
- package/dist/src/planner/optimizer.js +24 -0
- package/dist/src/planner/optimizer.js.map +1 -1
- package/dist/src/planner/rules/access/rule-monotonic-range-access.d.ts.map +1 -1
- package/dist/src/planner/rules/access/rule-monotonic-range-access.js +8 -4
- package/dist/src/planner/rules/access/rule-monotonic-range-access.js.map +1 -1
- package/dist/src/planner/rules/access/rule-select-access-path.d.ts +14 -0
- package/dist/src/planner/rules/access/rule-select-access-path.d.ts.map +1 -1
- package/dist/src/planner/rules/access/rule-select-access-path.js +5 -1
- package/dist/src/planner/rules/access/rule-select-access-path.js.map +1 -1
- package/dist/src/planner/rules/aggregate/rule-groupby-fd-simplification.d.ts +3 -1
- package/dist/src/planner/rules/aggregate/rule-groupby-fd-simplification.d.ts.map +1 -1
- package/dist/src/planner/rules/aggregate/rule-groupby-fd-simplification.js +8 -3
- package/dist/src/planner/rules/aggregate/rule-groupby-fd-simplification.js.map +1 -1
- package/dist/src/planner/rules/aggregate/rule-minmax-index-boundary.d.ts +47 -0
- package/dist/src/planner/rules/aggregate/rule-minmax-index-boundary.d.ts.map +1 -0
- package/dist/src/planner/rules/aggregate/rule-minmax-index-boundary.js +200 -0
- package/dist/src/planner/rules/aggregate/rule-minmax-index-boundary.js.map +1 -0
- package/dist/src/planner/rules/cache/rule-nested-loop-right-cache.d.ts +29 -0
- package/dist/src/planner/rules/cache/rule-nested-loop-right-cache.d.ts.map +1 -1
- package/dist/src/planner/rules/cache/rule-nested-loop-right-cache.js +55 -16
- package/dist/src/planner/rules/cache/rule-nested-loop-right-cache.js.map +1 -1
- package/dist/src/planner/rules/join/index-nested-loop.d.ts +96 -22
- package/dist/src/planner/rules/join/index-nested-loop.d.ts.map +1 -1
- package/dist/src/planner/rules/join/index-nested-loop.js +401 -82
- package/dist/src/planner/rules/join/index-nested-loop.js.map +1 -1
- package/dist/src/planner/rules/join/rule-fanout-batched-outer.d.ts +20 -0
- package/dist/src/planner/rules/join/rule-fanout-batched-outer.d.ts.map +1 -1
- package/dist/src/planner/rules/join/rule-fanout-batched-outer.js +23 -4
- package/dist/src/planner/rules/join/rule-fanout-batched-outer.js.map +1 -1
- package/dist/src/planner/rules/join/rule-join-greedy-commute.d.ts.map +1 -1
- package/dist/src/planner/rules/join/rule-join-greedy-commute.js +7 -0
- package/dist/src/planner/rules/join/rule-join-greedy-commute.js.map +1 -1
- package/dist/src/planner/rules/join/rule-join-physical-selection.d.ts +19 -2
- package/dist/src/planner/rules/join/rule-join-physical-selection.d.ts.map +1 -1
- package/dist/src/planner/rules/join/rule-join-physical-selection.js +262 -40
- package/dist/src/planner/rules/join/rule-join-physical-selection.js.map +1 -1
- package/dist/src/planner/rules/predicate/rule-sargable-range-rewrite.d.ts.map +1 -1
- package/dist/src/planner/rules/predicate/rule-sargable-range-rewrite.js +2 -1
- package/dist/src/planner/rules/predicate/rule-sargable-range-rewrite.js.map +1 -1
- package/dist/src/planner/rules/retrieve/rule-grow-retrieve.d.ts +21 -0
- package/dist/src/planner/rules/retrieve/rule-grow-retrieve.d.ts.map +1 -1
- package/dist/src/planner/rules/retrieve/rule-grow-retrieve.js +24 -6
- package/dist/src/planner/rules/retrieve/rule-grow-retrieve.js.map +1 -1
- package/dist/src/planner/rules/shared/access-leaf.d.ts +12 -12
- package/dist/src/planner/rules/shared/access-leaf.d.ts.map +1 -1
- package/dist/src/planner/rules/shared/access-leaf.js +3 -14
- package/dist/src/planner/rules/shared/access-leaf.js.map +1 -1
- package/dist/src/planner/stats/analyze.d.ts.map +1 -1
- package/dist/src/planner/stats/analyze.js +9 -3
- package/dist/src/planner/stats/analyze.js.map +1 -1
- package/dist/src/planner/stats/catalog-stats.d.ts.map +1 -1
- package/dist/src/planner/stats/catalog-stats.js +6 -10
- package/dist/src/planner/stats/catalog-stats.js.map +1 -1
- package/dist/src/planner/type-utils.d.ts.map +1 -1
- package/dist/src/planner/type-utils.js +3 -5
- package/dist/src/planner/type-utils.js.map +1 -1
- package/dist/src/planner/util/fd-utils.d.ts.map +1 -1
- package/dist/src/planner/util/fd-utils.js +4 -7
- package/dist/src/planner/util/fd-utils.js.map +1 -1
- package/dist/src/runtime/emit/alter-table.d.ts.map +1 -1
- package/dist/src/runtime/emit/alter-table.js +58 -4
- package/dist/src/runtime/emit/alter-table.js.map +1 -1
- package/dist/src/runtime/emit/analyze.d.ts.map +1 -1
- package/dist/src/runtime/emit/analyze.js +1 -2
- package/dist/src/runtime/emit/analyze.js.map +1 -1
- package/dist/src/runtime/emit/binary.d.ts.map +1 -1
- package/dist/src/runtime/emit/binary.js +5 -3
- package/dist/src/runtime/emit/binary.js.map +1 -1
- package/dist/src/runtime/emit/fanout-lookup-join.d.ts.map +1 -1
- package/dist/src/runtime/emit/fanout-lookup-join.js +9 -1
- package/dist/src/runtime/emit/fanout-lookup-join.js.map +1 -1
- package/dist/src/runtime/emit/limit-offset.d.ts.map +1 -1
- package/dist/src/runtime/emit/limit-offset.js +50 -24
- package/dist/src/runtime/emit/limit-offset.js.map +1 -1
- package/dist/src/runtime/emit/materialized-view-helpers.d.ts +33 -2
- package/dist/src/runtime/emit/materialized-view-helpers.d.ts.map +1 -1
- package/dist/src/runtime/emit/materialized-view-helpers.js +61 -5
- package/dist/src/runtime/emit/materialized-view-helpers.js.map +1 -1
- package/dist/src/runtime/emit/materialized-view.d.ts.map +1 -1
- package/dist/src/runtime/emit/materialized-view.js +6 -1
- package/dist/src/runtime/emit/materialized-view.js.map +1 -1
- package/dist/src/runtime/emit/ordinal-slice.d.ts.map +1 -1
- package/dist/src/runtime/emit/ordinal-slice.js +8 -0
- package/dist/src/runtime/emit/ordinal-slice.js.map +1 -1
- package/dist/src/schema/derivation.d.ts +15 -1
- package/dist/src/schema/derivation.d.ts.map +1 -1
- package/dist/src/schema/derivation.js.map +1 -1
- package/dist/src/schema/function.d.ts +9 -4
- package/dist/src/schema/function.d.ts.map +1 -1
- package/dist/src/schema/function.js +11 -9
- package/dist/src/schema/function.js.map +1 -1
- package/dist/src/schema/schema.d.ts +14 -3
- package/dist/src/schema/schema.d.ts.map +1 -1
- package/dist/src/schema/schema.js +20 -2
- package/dist/src/schema/schema.js.map +1 -1
- package/dist/src/schema/table.d.ts +27 -0
- package/dist/src/schema/table.d.ts.map +1 -1
- package/dist/src/schema/table.js +55 -0
- package/dist/src/schema/table.js.map +1 -1
- package/dist/src/schema/unique-enforcement.d.ts +6 -13
- package/dist/src/schema/unique-enforcement.d.ts.map +1 -1
- package/dist/src/schema/unique-enforcement.js +8 -17
- package/dist/src/schema/unique-enforcement.js.map +1 -1
- package/dist/src/util/comparison.d.ts +19 -0
- package/dist/src/util/comparison.d.ts.map +1 -1
- package/dist/src/util/comparison.js +22 -0
- package/dist/src/util/comparison.js.map +1 -1
- package/dist/src/vtab/best-access-plan.d.ts +34 -0
- package/dist/src/vtab/best-access-plan.d.ts.map +1 -1
- package/dist/src/vtab/best-access-plan.js +64 -0
- package/dist/src/vtab/best-access-plan.js.map +1 -1
- package/dist/src/vtab/index-descriptor.d.ts +9 -0
- package/dist/src/vtab/index-descriptor.d.ts.map +1 -1
- package/dist/src/vtab/index-descriptor.js +15 -0
- package/dist/src/vtab/index-descriptor.js.map +1 -1
- package/dist/src/vtab/memory/layer/manager.d.ts +11 -9
- package/dist/src/vtab/memory/layer/manager.d.ts.map +1 -1
- package/dist/src/vtab/memory/layer/manager.js +12 -43
- package/dist/src/vtab/memory/layer/manager.js.map +1 -1
- package/dist/src/vtab/memory/module.d.ts +18 -0
- package/dist/src/vtab/memory/module.d.ts.map +1 -1
- package/dist/src/vtab/memory/module.js +176 -24
- package/dist/src/vtab/memory/module.js.map +1 -1
- package/dist/src/vtab/memory/table.d.ts +27 -3
- package/dist/src/vtab/memory/table.d.ts.map +1 -1
- package/dist/src/vtab/memory/table.js +28 -59
- package/dist/src/vtab/memory/table.js.map +1 -1
- package/package.json +3 -3
|
@@ -0,0 +1,616 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auto-analyze staleness bookkeeping and background statistics refresh.
|
|
3
|
+
*
|
|
4
|
+
* Tracks, per base table, how many distinct rows committed transactions have
|
|
5
|
+
* changed since statistics were last collected for that table, decides when that
|
|
6
|
+
* drift is large enough to call the table's statistics stale, and — off the write
|
|
7
|
+
* path, from a debounced timer — refreshes them by running the ordinary `ANALYZE`
|
|
8
|
+
* statement for that one table.
|
|
9
|
+
*
|
|
10
|
+
* The counts come from the transaction change log (`TransactionManager`), read
|
|
11
|
+
* post-commit while the log is still alive. That log is maintained
|
|
12
|
+
* unconditionally on every write path already, so counting costs one `Map.size`
|
|
13
|
+
* read per table per savepoint layer and nothing on the write path itself.
|
|
14
|
+
*
|
|
15
|
+
* The refresh goes through `db.exec('analyze …')` rather than reaching into the
|
|
16
|
+
* collection internals. Two things fall out of that:
|
|
17
|
+
*
|
|
18
|
+
* - `db.exec` acquires the database's execution mutex, which is what serializes
|
|
19
|
+
* all statement execution. A refresh driven from a timer therefore queues
|
|
20
|
+
* behind whatever is running instead of racing it. It cannot deadlock, because
|
|
21
|
+
* it is never started from inside a statement.
|
|
22
|
+
* - There is exactly one implementation of "collect and record statistics for a
|
|
23
|
+
* table" (`runtime/emit/analyze.ts`), including its per-table `try` that logs
|
|
24
|
+
* and continues.
|
|
25
|
+
*
|
|
26
|
+
* A refresh cannot trigger itself: `ANALYZE` performs no DML, and it publishes
|
|
27
|
+
* its result by swapping the `TableSchema` and firing the internal catalog
|
|
28
|
+
* notifier rather than the data-change channel. Its implicit transaction commits
|
|
29
|
+
* with an empty change log, so no counter advances.
|
|
30
|
+
*/
|
|
31
|
+
import { createLogger } from '../common/logger.js';
|
|
32
|
+
import { quoteIdentifier } from '../emit/ast-stringify.js';
|
|
33
|
+
import { catalogRowCount } from '../planner/stats/table-cardinality.js';
|
|
34
|
+
import { splitBaseKey } from '../util/qualified-name.js';
|
|
35
|
+
const log = createLogger('core:auto-analyze');
|
|
36
|
+
const debugLog = log.extend('debug');
|
|
37
|
+
const warnLog = log.extend('warn');
|
|
38
|
+
/**
|
|
39
|
+
* How long a crossing waits before its refresh starts. Long enough that a burst
|
|
40
|
+
* of small commits collapses into one refresh, short enough that a test or an
|
|
41
|
+
* interactive session does not notice it.
|
|
42
|
+
*/
|
|
43
|
+
const AUTO_ANALYZE_DEBOUNCE_MS = 50;
|
|
44
|
+
/**
|
|
45
|
+
* A table may spend at most `1 / AUTO_ANALYZE_DUTY_CYCLE` of wall-clock time in
|
|
46
|
+
* automatic `ANALYZE`: after a refresh that took `t` ms, the next one for that
|
|
47
|
+
* table may not start for `t × AUTO_ANALYZE_DUTY_CYCLE` ms.
|
|
48
|
+
*
|
|
49
|
+
* NOTE: this cooldown is load-bearing, not decoration. Without it a bulk load
|
|
50
|
+
* re-analyzes on a geometric ladder — a refresh at 50k rows records
|
|
51
|
+
* `analyzedRowCount = 50k`, so the next threshold is `ratio × 50k = 10k` further
|
|
52
|
+
* rows, and the scan that follows visits 60k, then 72k, then 86k… Each scan is
|
|
53
|
+
* O(rows) and they compound. Capping the duty cycle bounds the background cost to
|
|
54
|
+
* a fixed fraction of one core regardless of write rate, without having to model
|
|
55
|
+
* the workload. Do not remove it as "an unnecessary timer".
|
|
56
|
+
*/
|
|
57
|
+
const AUTO_ANALYZE_DUTY_CYCLE = 10;
|
|
58
|
+
/** Floor on the cooldown after a *failed* refresh, so a permanently unreadable
|
|
59
|
+
* table cannot spin (a fast failure has a near-zero duty-cycle cooldown). */
|
|
60
|
+
const AUTO_ANALYZE_FAILURE_BACKOFF_MS = 5000;
|
|
61
|
+
/**
|
|
62
|
+
* First delay before retrying a refresh that was deferred because a transaction
|
|
63
|
+
* was open, doubling on each further retry (250, 500, 1000, 2000 ms).
|
|
64
|
+
*
|
|
65
|
+
* NOT the debounce: the debounce exists to let a burst of commits collapse, and
|
|
66
|
+
* 50 ms is far too soon to expect a statement that was in flight a moment ago to
|
|
67
|
+
* have finished. Starting an order of magnitude higher means the common case — a
|
|
68
|
+
* timer landing inside one ordinary `UPDATE` — is served by the first retry
|
|
69
|
+
* instead of burning the whole budget on wakeups that all land inside the same
|
|
70
|
+
* statement.
|
|
71
|
+
*
|
|
72
|
+
* NOTE: 250 ms is reasoning, not measurement — nothing has measured how long a
|
|
73
|
+
* statement actually holds its implicit transaction, and a memory-backed one
|
|
74
|
+
* frequently never yields to the timer queue at all (a 400-row `insert … values`
|
|
75
|
+
* ran to completion without a single `setTimeout(0)` firing), so this mostly
|
|
76
|
+
* governs store-backed workloads and explicit `BEGIN`s. If the debug log shows
|
|
77
|
+
* crossings routinely spending the whole budget, measure a real workload before
|
|
78
|
+
* raising the retry count — a longer first delay is likelier to be the fix.
|
|
79
|
+
*/
|
|
80
|
+
export const AUTO_ANALYZE_DEFER_RETRY_MS = 250;
|
|
81
|
+
/**
|
|
82
|
+
* How many times one crossing may reschedule itself after being deferred. The
|
|
83
|
+
* geometric backoff above makes that about 3.75 s of total patience.
|
|
84
|
+
*
|
|
85
|
+
* Bounded on purpose: a user may park an explicit transaction open indefinitely,
|
|
86
|
+
* and an unbounded retry would then cost a wakeup per stale table forever. Once
|
|
87
|
+
* the budget is spent the crossing is dropped exactly as it was before retries
|
|
88
|
+
* existed — the counter stays over threshold and the next commit that touches the
|
|
89
|
+
* table re-arms.
|
|
90
|
+
*
|
|
91
|
+
* Must stay at or below `AUTO_ANALYZE_IDLE_MAX_PASSES - 2`: {@link
|
|
92
|
+
* AutoAnalyzeManager.whenIdle} spends one settle pass per retry plus one for the
|
|
93
|
+
* initial attempt and one to observe that nothing is left armed.
|
|
94
|
+
*/
|
|
95
|
+
export const AUTO_ANALYZE_MAX_DEFER_RETRIES = 4;
|
|
96
|
+
/**
|
|
97
|
+
* Safety bound on {@link AutoAnalyzeManager.whenIdle}'s settle loop. Exported so the
|
|
98
|
+
* inequality against {@link AUTO_ANALYZE_MAX_DEFER_RETRIES} is checked by a test
|
|
99
|
+
* rather than only implied by one failing if it were ever violated.
|
|
100
|
+
*/
|
|
101
|
+
export const AUTO_ANALYZE_IDLE_MAX_PASSES = 10;
|
|
102
|
+
/**
|
|
103
|
+
* Delay for a debounce timer armed at `now` for a table not eligible until
|
|
104
|
+
* `nextEligibleAt`: the debounce, or the remaining duty-cycle cooldown when that
|
|
105
|
+
* is longer. Exported for the same reason {@link isStaleCount} is — the cooldown
|
|
106
|
+
* arithmetic is otherwise only observable through timing.
|
|
107
|
+
*/
|
|
108
|
+
export function armDelayMs(nextEligibleAt, now) {
|
|
109
|
+
return Math.max(AUTO_ANALYZE_DEBOUNCE_MS, nextEligibleAt - now);
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* The staleness threshold in changed rows:
|
|
113
|
+
*
|
|
114
|
+
* ```
|
|
115
|
+
* stale ⟺ changedSinceAnalyze >= max(minMutations, ratio × knownRowCount)
|
|
116
|
+
* ```
|
|
117
|
+
*
|
|
118
|
+
* `knownRowCount` is 0 for a never-analyzed table (`SchemaManager` hardcodes
|
|
119
|
+
* `estimatedRows` to 0 at create), so the absolute floor governs exactly the
|
|
120
|
+
* case that matters most: bulk-loading rows into a fresh table trips
|
|
121
|
+
* `minMutations` long before any percentage of zero could.
|
|
122
|
+
*
|
|
123
|
+
* Precedent for the defaults: SQL Server's auto-update-stats uses 500 + 20% of
|
|
124
|
+
* rows; PostgreSQL's autoanalyze uses 50 + 10%.
|
|
125
|
+
*/
|
|
126
|
+
export function stalenessThreshold(minMutations, ratio, knownRowCount) {
|
|
127
|
+
return Math.max(minMutations, ratio * knownRowCount);
|
|
128
|
+
}
|
|
129
|
+
/** Pure form of the staleness predicate — see {@link stalenessThreshold}. */
|
|
130
|
+
export function isStaleCount(changedSinceAnalyze, minMutations, ratio, knownRowCount) {
|
|
131
|
+
return changedSinceAnalyze >= stalenessThreshold(minMutations, ratio, knownRowCount);
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Tracks committed-mutation drift per base table for a single `Database`, and
|
|
135
|
+
* schedules the background refresh that clears it.
|
|
136
|
+
*/
|
|
137
|
+
export class AutoAnalyzeManager {
|
|
138
|
+
ctx;
|
|
139
|
+
/**
|
|
140
|
+
* Lowercased `schema.table` → staleness bookkeeping. Same key convention the
|
|
141
|
+
* change log uses, so no re-derivation and no second key convention.
|
|
142
|
+
*
|
|
143
|
+
* NOTE: nothing here is persisted. After a restart the store backend's saved
|
|
144
|
+
* statistics still exist, but staleness accumulation restarts from zero — a
|
|
145
|
+
* table that drifted while the process was down looks fresh until it drifts
|
|
146
|
+
* again. Accepted for v1 (a restart is rare relative to the mutation
|
|
147
|
+
* threshold). If it ever matters, persist `changedSinceAnalyze` beside the
|
|
148
|
+
* table's statistics entry and reload it when the catalog loads.
|
|
149
|
+
*/
|
|
150
|
+
entries = new Map();
|
|
151
|
+
unsubscribeSchemaChanges = null;
|
|
152
|
+
/** Set by {@link dispose}; every scheduled path checks it before doing work. */
|
|
153
|
+
disposed = false;
|
|
154
|
+
/** Refreshes that actually reached the `ANALYZE` statement. Test instrumentation. */
|
|
155
|
+
refreshes = 0;
|
|
156
|
+
constructor(ctx) {
|
|
157
|
+
this.ctx = ctx;
|
|
158
|
+
this.subscribeToSchemaChanges();
|
|
159
|
+
}
|
|
160
|
+
subscribeToSchemaChanges() {
|
|
161
|
+
const notifier = this.ctx.schemaManager.getChangeNotifier();
|
|
162
|
+
this.unsubscribeSchemaChanges = notifier.addListener((event) => {
|
|
163
|
+
// Only `table_removed`, so a table re-created under a dropped name starts
|
|
164
|
+
// clean. Deliberately NOT `table_modified`: a statistics refresh fires that
|
|
165
|
+
// event itself (runtime/emit/analyze.ts), so reacting to it would couple this
|
|
166
|
+
// manager to its own output.
|
|
167
|
+
//
|
|
168
|
+
// That decoupling has a cost, taken knowingly: a hand-typed `analyze t` also
|
|
169
|
+
// arrives only as `table_modified`, so it does NOT reset `changedSinceAnalyze`.
|
|
170
|
+
// A user who analyzes an already-over-threshold table by hand leaves the
|
|
171
|
+
// counter where it was, and this manager then re-scans a table whose
|
|
172
|
+
// statistics are seconds old — one wasted O(rows) scan, after which the reset
|
|
173
|
+
// in `refresh` makes it self-correcting. The reset path keys off THIS
|
|
174
|
+
// MANAGER'S OWN refresh only. Distinguishing "a successful ANALYZE happened"
|
|
175
|
+
// from any other schema edit would need a signal the notifier does not carry;
|
|
176
|
+
// adding one to save a single redundant scan is not worth re-coupling to the
|
|
177
|
+
// channel the refresh itself fires.
|
|
178
|
+
//
|
|
179
|
+
// NOTE: `DETACH` removes a whole schema WITHOUT firing per-table events
|
|
180
|
+
// (`SchemaManager.removeSchema`), so entries for a detached schema's tables
|
|
181
|
+
// outlive it. Harmless today — each is a handful of numbers, and
|
|
182
|
+
// `knownRowCountOrDrop` evicts one the next time it is consulted — but if a
|
|
183
|
+
// host ever attaches and detaches schemas in a loop, drop entries whose
|
|
184
|
+
// schema segment matches the detached name.
|
|
185
|
+
if (event.type === 'table_removed') {
|
|
186
|
+
const key = `${event.schemaName}.${event.objectName}`.toLowerCase();
|
|
187
|
+
this.dropEntry(key);
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Fold one committed transaction's per-table changed-row counts into the
|
|
193
|
+
* staleness map and re-evaluate the threshold for every table it touched.
|
|
194
|
+
* Entries are created lazily on first touch.
|
|
195
|
+
*/
|
|
196
|
+
recordCommit(counts) {
|
|
197
|
+
for (const [key, count] of counts) {
|
|
198
|
+
if (count <= 0)
|
|
199
|
+
continue;
|
|
200
|
+
let entry = this.entries.get(key);
|
|
201
|
+
if (!entry) {
|
|
202
|
+
entry = {
|
|
203
|
+
changedSinceAnalyze: 0,
|
|
204
|
+
analyzedRowCount: undefined,
|
|
205
|
+
staleLogged: false,
|
|
206
|
+
timer: undefined,
|
|
207
|
+
running: undefined,
|
|
208
|
+
nextEligibleAt: 0,
|
|
209
|
+
oversizeLogged: false,
|
|
210
|
+
deferRetries: 0,
|
|
211
|
+
};
|
|
212
|
+
this.entries.set(key, entry);
|
|
213
|
+
}
|
|
214
|
+
entry.changedSinceAnalyze += count;
|
|
215
|
+
// A commit is proof the transaction that kept deferring this table's refresh
|
|
216
|
+
// has ended, so whatever retry budget it burned is refunded. Zeroed BEFORE
|
|
217
|
+
// `evaluate`, so the crossing this commit may arm starts with a full budget.
|
|
218
|
+
entry.deferRetries = 0;
|
|
219
|
+
this.evaluate(key, entry);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Whether the named table (lowercased `schema.table`) has drifted past the
|
|
224
|
+
* threshold. Not a pure predicate: a table that has since disappeared answers
|
|
225
|
+
* `false` and has its entry dropped — see {@link knownRowCountOrDrop}.
|
|
226
|
+
*/
|
|
227
|
+
isStale(key) {
|
|
228
|
+
const entry = this.entries.get(key);
|
|
229
|
+
if (!entry)
|
|
230
|
+
return false;
|
|
231
|
+
const known = this.knownRowCountOrDrop(key, entry);
|
|
232
|
+
if (known === undefined)
|
|
233
|
+
return false;
|
|
234
|
+
return isStaleCount(entry.changedSinceAnalyze, this.minMutations(), this.ratio(), known);
|
|
235
|
+
}
|
|
236
|
+
/** @internal Current bookkeeping for a table; `undefined` when untracked. */
|
|
237
|
+
getEntry(key) {
|
|
238
|
+
return this.entries.get(key);
|
|
239
|
+
}
|
|
240
|
+
/** @internal Every table currently tracked (lowercased `schema.table`). */
|
|
241
|
+
trackedTables() {
|
|
242
|
+
return [...this.entries.keys()];
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* @internal How many refreshes have reached the `ANALYZE` statement since this
|
|
246
|
+
* manager was created. Lets a test assert coalescing by counting work rather than
|
|
247
|
+
* by timing it.
|
|
248
|
+
*/
|
|
249
|
+
refreshCount() {
|
|
250
|
+
return this.refreshes;
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* @internal Resolve once no table has an armed timer or an in-flight refresh.
|
|
254
|
+
*
|
|
255
|
+
* Any armed timer is fired IMMEDIATELY — the debounce, the duty-cycle cooldown and
|
|
256
|
+
* the deferral backoff are all bypassed — rather than waited out, so tests never
|
|
257
|
+
* sleep.
|
|
258
|
+
*
|
|
259
|
+
* It deliberately does NOT bypass the open-transaction deferral: a test that wants
|
|
260
|
+
* a refresh to happen must be in autocommit, which is what real callers face.
|
|
261
|
+
*
|
|
262
|
+
* A retry timer is an armed timer like any other, so driving this while a
|
|
263
|
+
* transaction is open SPENDS the whole deferral budget in one call — fire, defer,
|
|
264
|
+
* re-arm, fire… until `AUTO_ANALYZE_MAX_DEFER_RETRIES` is reached, then settle.
|
|
265
|
+
* That is the intended behaviour, and it is what makes the budget observable
|
|
266
|
+
* without sleeping. It costs one settle pass per retry, which is why the budget
|
|
267
|
+
* must stay at or below `AUTO_ANALYZE_IDLE_MAX_PASSES - 2`.
|
|
268
|
+
*/
|
|
269
|
+
async whenIdle() {
|
|
270
|
+
for (let pass = 0; pass < AUTO_ANALYZE_IDLE_MAX_PASSES; pass++) {
|
|
271
|
+
const pending = [];
|
|
272
|
+
for (const [key, entry] of [...this.entries]) {
|
|
273
|
+
if (entry.timer !== undefined) {
|
|
274
|
+
clearTimeout(entry.timer);
|
|
275
|
+
entry.timer = undefined;
|
|
276
|
+
entry.nextEligibleAt = 0;
|
|
277
|
+
this.start(key, entry);
|
|
278
|
+
}
|
|
279
|
+
if (entry.running !== undefined)
|
|
280
|
+
pending.push(entry.running);
|
|
281
|
+
}
|
|
282
|
+
if (pending.length === 0)
|
|
283
|
+
return;
|
|
284
|
+
await Promise.all(pending);
|
|
285
|
+
}
|
|
286
|
+
// Loud rather than hanging: a bounded self-re-arm (the deferral retry) is expected
|
|
287
|
+
// and fits inside the pass budget, so reaching here means an UNBOUNDED one.
|
|
288
|
+
warnLog('Auto-analyze did not settle after %d passes; still tracking: %s', AUTO_ANALYZE_IDLE_MAX_PASSES, [...this.entries.keys()].join(', '));
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* @internal Fire one table's armed timer NOW, bypassing its delay, and resolve once
|
|
292
|
+
* that SINGLE attempt has settled and its outcome has been applied. No-op when
|
|
293
|
+
* nothing is armed for `key`.
|
|
294
|
+
*
|
|
295
|
+
* {@link whenIdle} cannot stand in for this: it loops until nothing is armed, so
|
|
296
|
+
* driven while a transaction is open it spends the entire deferral budget in one
|
|
297
|
+
* call. A test that wants to watch one deferral reschedule itself needs one attempt.
|
|
298
|
+
*/
|
|
299
|
+
async fireArmedRefresh(key) {
|
|
300
|
+
const entry = this.entries.get(key);
|
|
301
|
+
if (!entry || entry.timer === undefined)
|
|
302
|
+
return;
|
|
303
|
+
this.clearTimer(entry);
|
|
304
|
+
entry.nextEligibleAt = 0;
|
|
305
|
+
this.start(key, entry);
|
|
306
|
+
await entry.running;
|
|
307
|
+
}
|
|
308
|
+
dispose() {
|
|
309
|
+
this.disposed = true;
|
|
310
|
+
if (this.unsubscribeSchemaChanges) {
|
|
311
|
+
this.unsubscribeSchemaChanges();
|
|
312
|
+
this.unsubscribeSchemaChanges = null;
|
|
313
|
+
}
|
|
314
|
+
// Armed timers must not outlive the database — both to avoid holding a Node
|
|
315
|
+
// process open and to avoid a refresh firing against a closed database. An
|
|
316
|
+
// already-running refresh is left alone: `db.exec` throws once the database is
|
|
317
|
+
// closed, and the failure path logs and continues rather than rejecting.
|
|
318
|
+
for (const entry of this.entries.values()) {
|
|
319
|
+
this.clearTimer(entry);
|
|
320
|
+
}
|
|
321
|
+
this.entries.clear();
|
|
322
|
+
}
|
|
323
|
+
/** Cancel any armed timer for `entry`. Safe to call when none is armed. */
|
|
324
|
+
clearTimer(entry) {
|
|
325
|
+
if (entry.timer === undefined)
|
|
326
|
+
return;
|
|
327
|
+
clearTimeout(entry.timer);
|
|
328
|
+
entry.timer = undefined;
|
|
329
|
+
}
|
|
330
|
+
/** Forget a table, cancelling any refresh armed for it. */
|
|
331
|
+
dropEntry(key) {
|
|
332
|
+
const entry = this.entries.get(key);
|
|
333
|
+
if (!entry)
|
|
334
|
+
return;
|
|
335
|
+
this.clearTimer(entry);
|
|
336
|
+
this.entries.delete(key);
|
|
337
|
+
}
|
|
338
|
+
/**
|
|
339
|
+
* Row count the ratio arm of the threshold applies to: what the last refresh
|
|
340
|
+
* this process observed reported, else whatever the catalog knows.
|
|
341
|
+
*
|
|
342
|
+
* Named for its side effect: a table that is gone (dropped between the commit
|
|
343
|
+
* and this lookup, or carried in from a detached schema) answers `undefined`
|
|
344
|
+
* AND has its entry evicted here, so every consumer of a row count doubles as
|
|
345
|
+
* the eviction path for stale keys. That is why {@link isStale} mutates.
|
|
346
|
+
*/
|
|
347
|
+
knownRowCountOrDrop(key, entry) {
|
|
348
|
+
if (entry.analyzedRowCount !== undefined)
|
|
349
|
+
return entry.analyzedRowCount;
|
|
350
|
+
const table = this.findTable(key);
|
|
351
|
+
if (!table) {
|
|
352
|
+
this.dropEntry(key);
|
|
353
|
+
return undefined;
|
|
354
|
+
}
|
|
355
|
+
return catalogRowCount(table) ?? 0;
|
|
356
|
+
}
|
|
357
|
+
findTable(key) {
|
|
358
|
+
const [schemaName, tableName] = splitBaseKey(key);
|
|
359
|
+
return this.ctx._findTable(tableName, schemaName);
|
|
360
|
+
}
|
|
361
|
+
/**
|
|
362
|
+
* Re-evaluate the threshold for one table after its counter moved: log the first
|
|
363
|
+
* crossing (a busy table must not flood the log) and arm a refresh.
|
|
364
|
+
*/
|
|
365
|
+
evaluate(key, entry) {
|
|
366
|
+
// Absorbed: a pending refresh already covers whatever this commit added. The
|
|
367
|
+
// counter keeps climbing, and the NEXT commit after the refresh settles is what
|
|
368
|
+
// re-arms — nothing re-evaluates on the refresh's own completion. That is what
|
|
369
|
+
// makes N commits past the threshold cost O(1) refreshes rather than O(N), and
|
|
370
|
+
// it is checked first so the common busy-table case does no catalog lookup at
|
|
371
|
+
// all. Its cost: a burst that stops immediately after a refresh leaves a
|
|
372
|
+
// still-over-threshold counter with nothing scheduled until the next write.
|
|
373
|
+
if (entry.timer !== undefined || entry.running !== undefined)
|
|
374
|
+
return;
|
|
375
|
+
const known = this.knownRowCountOrDrop(key, entry);
|
|
376
|
+
if (known === undefined)
|
|
377
|
+
return;
|
|
378
|
+
const threshold = stalenessThreshold(this.minMutations(), this.ratio(), known);
|
|
379
|
+
if (entry.changedSinceAnalyze < threshold)
|
|
380
|
+
return;
|
|
381
|
+
if (!entry.staleLogged) {
|
|
382
|
+
entry.staleLogged = true;
|
|
383
|
+
debugLog('Statistics for %s are stale: %d rows changed since last analyze (threshold %d over %d known rows)', key, entry.changedSinceAnalyze, threshold, known);
|
|
384
|
+
}
|
|
385
|
+
this.arm(key, entry);
|
|
386
|
+
}
|
|
387
|
+
/** Schedule the debounced refresh for a table that has just crossed the threshold. */
|
|
388
|
+
arm(key, entry) {
|
|
389
|
+
if (this.disposed)
|
|
390
|
+
return;
|
|
391
|
+
if (entry.timer !== undefined || entry.running !== undefined)
|
|
392
|
+
return;
|
|
393
|
+
// A plain view stores no rows and `ANALYZE` rejects it by name, so never arm one.
|
|
394
|
+
// (A materialized view is a real backing table and is refreshed like any other.)
|
|
395
|
+
const table = this.findTable(key);
|
|
396
|
+
if (!table) {
|
|
397
|
+
this.dropEntry(key);
|
|
398
|
+
return;
|
|
399
|
+
}
|
|
400
|
+
if (table.isView)
|
|
401
|
+
return;
|
|
402
|
+
const delay = armDelayMs(entry.nextEligibleAt, Date.now());
|
|
403
|
+
const timer = setTimeout(() => {
|
|
404
|
+
entry.timer = undefined;
|
|
405
|
+
this.start(key, entry);
|
|
406
|
+
}, delay);
|
|
407
|
+
// `unref` is Node-only — browsers and React Native return a numeric handle with
|
|
408
|
+
// no such method. Where it exists, a pending refresh must not hold the process open.
|
|
409
|
+
const unref = timer.unref;
|
|
410
|
+
if (typeof unref === 'function')
|
|
411
|
+
unref.call(timer);
|
|
412
|
+
entry.timer = timer;
|
|
413
|
+
}
|
|
414
|
+
/**
|
|
415
|
+
* Begin a refresh, publish its settle promise on the entry, and act on how it
|
|
416
|
+
* ended. This is the single place that turns a {@link RefreshOutcome} into a
|
|
417
|
+
* scheduling decision — `refresh` itself only reports.
|
|
418
|
+
*/
|
|
419
|
+
start(key, entry) {
|
|
420
|
+
if (entry.running !== undefined)
|
|
421
|
+
return;
|
|
422
|
+
// `running` must never reject: nothing awaits it in production, so a rejection
|
|
423
|
+
// from a timer callback is a process-level crash for a background chore. `refresh`
|
|
424
|
+
// has a total try/catch, and the trailing `catch` covers the rescheduling that
|
|
425
|
+
// follows it. Logged and dropped; the next commit re-arms.
|
|
426
|
+
const run = this.refresh(key, entry).then(outcome => {
|
|
427
|
+
// `running` must be cleared BEFORE the outcome is applied: `arm` early-returns
|
|
428
|
+
// while a refresh is in flight, so a retry armed ahead of this clear would be
|
|
429
|
+
// silently dropped. Sequenced, not raced.
|
|
430
|
+
if (entry.running === run)
|
|
431
|
+
entry.running = undefined;
|
|
432
|
+
this.applyOutcome(key, entry, outcome);
|
|
433
|
+
}).catch(e => {
|
|
434
|
+
warnLog('Auto-analyze rescheduling for %s failed: %s', key, e);
|
|
435
|
+
});
|
|
436
|
+
entry.running = run;
|
|
437
|
+
}
|
|
438
|
+
/** Decide what a finished refresh means for scheduling. */
|
|
439
|
+
applyOutcome(key, entry, outcome) {
|
|
440
|
+
switch (outcome) {
|
|
441
|
+
case 'analyzed':
|
|
442
|
+
// The crossing was served, so the next one starts with a full retry budget.
|
|
443
|
+
entry.deferRetries = 0;
|
|
444
|
+
break;
|
|
445
|
+
case 'deferred':
|
|
446
|
+
this.armDeferRetry(key, entry);
|
|
447
|
+
break;
|
|
448
|
+
case 'declined':
|
|
449
|
+
case 'failed':
|
|
450
|
+
// Nothing to reschedule. `declined` is a deliberate refusal that a retry would
|
|
451
|
+
// only repeat; `failed` already recorded its own backoff in `refresh`'s catch.
|
|
452
|
+
// Both wait for the next commit, as they did before retries existed.
|
|
453
|
+
break;
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
/**
|
|
457
|
+
* Reschedule a refusal that was purely about timing, on a geometric backoff and
|
|
458
|
+
* within a fixed budget. Once the budget is spent the crossing is dropped and the
|
|
459
|
+
* next commit that touches the table is what revives it.
|
|
460
|
+
*/
|
|
461
|
+
armDeferRetry(key, entry) {
|
|
462
|
+
// The table may have been dropped, or its entry replaced, while the deferred
|
|
463
|
+
// refresh ran — do not resurrect a detached object. (`arm` re-checks the table
|
|
464
|
+
// itself; this checks the entry's identity, which `arm` cannot see.)
|
|
465
|
+
if (this.entries.get(key) !== entry)
|
|
466
|
+
return;
|
|
467
|
+
if (entry.deferRetries >= AUTO_ANALYZE_MAX_DEFER_RETRIES) {
|
|
468
|
+
debugLog('Auto-analyze of %s deferred %d times by an open transaction; dropping the crossing ' +
|
|
469
|
+
'until the next commit touches the table', key, entry.deferRetries);
|
|
470
|
+
// Drop the backoff with it: the spent retries must not also delay the refresh
|
|
471
|
+
// the next commit arms. `deferRetries` itself stays at the cap until a commit
|
|
472
|
+
// or a successful refresh refunds it, so the spent budget stays observable.
|
|
473
|
+
//
|
|
474
|
+
// NOTE: zeroing is safe only because every refresh reaches here through `arm`,
|
|
475
|
+
// whose timer cannot fire before `nextEligibleAt` — so the duty-cycle cooldown
|
|
476
|
+
// this discards has already elapsed. If a future path ever starts a refresh
|
|
477
|
+
// without going through `arm`, this silently drops a live cooldown; carry the
|
|
478
|
+
// pre-deferral value instead of zeroing at that point.
|
|
479
|
+
entry.nextEligibleAt = 0;
|
|
480
|
+
return;
|
|
481
|
+
}
|
|
482
|
+
const delay = AUTO_ANALYZE_DEFER_RETRY_MS * 2 ** entry.deferRetries;
|
|
483
|
+
entry.deferRetries++;
|
|
484
|
+
// Expressed as a cooldown rather than a second timer concept, so `arm` and
|
|
485
|
+
// `armDelayMs` remain the only places a delay is computed.
|
|
486
|
+
entry.nextEligibleAt = Date.now() + delay;
|
|
487
|
+
this.arm(key, entry);
|
|
488
|
+
}
|
|
489
|
+
/**
|
|
490
|
+
* Refresh one table's statistics, reporting how it ended. Never throws: a failed
|
|
491
|
+
* automatic refresh must not surface as an error on an unrelated user statement,
|
|
492
|
+
* and nothing awaits this.
|
|
493
|
+
*
|
|
494
|
+
* It decides nothing about scheduling — every exit names a {@link RefreshOutcome}
|
|
495
|
+
* and {@link start} decides what that means. A new early return therefore has to
|
|
496
|
+
* say whether it is a refusal or a deferral, which is what keeps "abandoned
|
|
497
|
+
* crossing" from being writable by accident.
|
|
498
|
+
*/
|
|
499
|
+
async refresh(key, entry) {
|
|
500
|
+
const startedAt = Date.now();
|
|
501
|
+
try {
|
|
502
|
+
if (this.disposed)
|
|
503
|
+
return 'declined';
|
|
504
|
+
// Re-read the switch at fire time: `pragma auto_analyze = false` between the
|
|
505
|
+
// arming and now must abandon the refresh, leaving the counter intact.
|
|
506
|
+
if (!this.enabled())
|
|
507
|
+
return 'declined';
|
|
508
|
+
// Deferred while a transaction is open. A memory table's `ANALYZE` adopts the
|
|
509
|
+
// connection already registered for the table INCLUDING its pending transaction
|
|
510
|
+
// layer (see `getStatistics` in `vtab/memory/table.ts`), so refreshing
|
|
511
|
+
// mid-transaction would bake uncommitted rows into the statistics.
|
|
512
|
+
//
|
|
513
|
+
// The counter is left untouched and the crossing is RETRIED on a backoff —
|
|
514
|
+
// `start` → `armDeferRetry`, up to AUTO_ANALYZE_MAX_DEFER_RETRIES times. That
|
|
515
|
+
// matters because this check is not only about an explicit `BEGIN`: a writing
|
|
516
|
+
// statement (`update`, `delete`, `insert … select`, any DDL) opens an implicit
|
|
517
|
+
// transaction too, so a timer for table `t` can land inside a write to some
|
|
518
|
+
// unrelated table. Without the retry that crossing was abandoned outright, and
|
|
519
|
+
// only the next commit touching `t` itself would ever revive it.
|
|
520
|
+
//
|
|
521
|
+
// NOTE: the retry budget is deliberately finite — see
|
|
522
|
+
// AUTO_ANALYZE_MAX_DEFER_RETRIES. A transaction held open past it drops the
|
|
523
|
+
// crossing and the pre-retry behaviour resumes: wait for the next commit.
|
|
524
|
+
//
|
|
525
|
+
// NOTE: accepted tradeoff — a `begin` landing between this check and `exec`'s
|
|
526
|
+
// mutex acquisition lets the refresh run inside that transaction after all.
|
|
527
|
+
// Worst outcome is statistics that include uncommitted rows, which is exactly
|
|
528
|
+
// what a user typing `begin; insert …; analyze;` gets today, and the store's
|
|
529
|
+
// `saveStatistics` already carries an accepted-tradeoff NOTE for the persisted
|
|
530
|
+
// half of the same situation. Closing it would mean holding the execution mutex
|
|
531
|
+
// across the check, i.e. blocking user statements on a background scan. Revisit
|
|
532
|
+
// only if statistics polluted by uncommitted rows show up in practice.
|
|
533
|
+
if (!this.ctx.getAutocommit())
|
|
534
|
+
return 'deferred';
|
|
535
|
+
// NOTE: a never-analyzed table reports 0 known rows (`SchemaManager` hardcodes
|
|
536
|
+
// `estimatedRows` to 0 at CREATE), so its FIRST automatic refresh is not size
|
|
537
|
+
// gated — a table bulk-loaded to 10M rows in a single transaction gets one
|
|
538
|
+
// unbounded scan before `analyzedRowCount` starts gating the rest. Deliberate:
|
|
539
|
+
// the obvious tightening — treating `changedSinceAnalyze` as a size proxy —
|
|
540
|
+
// deadlocks, because a table skipped as oversize never resets its counter and
|
|
541
|
+
// would therefore be skipped forever. If that first scan ever matters, give the
|
|
542
|
+
// gate a real size source (a module-reported row count) rather than a proxy.
|
|
543
|
+
const known = entry.analyzedRowCount ?? this.catalogRowCount(key) ?? 0;
|
|
544
|
+
const limit = this.rowLimit();
|
|
545
|
+
// 0 disables the cap (documented with the option), hence `limit > 0 &&` rather
|
|
546
|
+
// than a bare comparison — otherwise 0 would mean "never auto-analyze anything".
|
|
547
|
+
if (limit > 0 && known > limit) {
|
|
548
|
+
if (!entry.oversizeLogged) {
|
|
549
|
+
entry.oversizeLogged = true;
|
|
550
|
+
log('Statistics for %s are stale but the table is too large for auto-analyze ' +
|
|
551
|
+
'(%d known rows > auto_analyze_row_limit %d) — run ANALYZE manually', key, known, limit);
|
|
552
|
+
}
|
|
553
|
+
return 'declined';
|
|
554
|
+
}
|
|
555
|
+
const table = this.findTable(key);
|
|
556
|
+
if (!table || table.isView) {
|
|
557
|
+
this.dropEntry(key);
|
|
558
|
+
return 'declined';
|
|
559
|
+
}
|
|
560
|
+
// Snapshot rather than zero: mutations that commit between here and the reset
|
|
561
|
+
// below stay accounted for.
|
|
562
|
+
const snapshot = entry.changedSinceAnalyze;
|
|
563
|
+
this.refreshes++;
|
|
564
|
+
const [schemaName, tableName] = splitBaseKey(key);
|
|
565
|
+
await this.ctx.exec(`analyze ${quoteIdentifier(schemaName)}.${quoteIdentifier(tableName)}`);
|
|
566
|
+
// The table may have been dropped while the refresh ran, in which case
|
|
567
|
+
// `dropEntry` already discarded this object — do not resurrect it.
|
|
568
|
+
if (this.entries.get(key) !== entry)
|
|
569
|
+
return 'declined';
|
|
570
|
+
entry.changedSinceAnalyze = Math.max(0, entry.changedSinceAnalyze - snapshot);
|
|
571
|
+
const refreshed = this.findTable(key);
|
|
572
|
+
entry.analyzedRowCount = refreshed ? catalogRowCount(refreshed) : undefined;
|
|
573
|
+
entry.staleLogged = false;
|
|
574
|
+
entry.oversizeLogged = false;
|
|
575
|
+
const elapsed = Date.now() - startedAt;
|
|
576
|
+
entry.nextEligibleAt = Date.now() + elapsed * AUTO_ANALYZE_DUTY_CYCLE;
|
|
577
|
+
debugLog('Auto-analyzed %s in %d ms (%s rows)', key, elapsed, entry.analyzedRowCount);
|
|
578
|
+
return 'analyzed';
|
|
579
|
+
}
|
|
580
|
+
catch (e) {
|
|
581
|
+
// The staleness is real, so the counter is left alone and the next commit
|
|
582
|
+
// re-arms — behind a backoff, so an unreadable table cannot spin.
|
|
583
|
+
warnLog('Automatic ANALYZE of %s failed: %s', key, e);
|
|
584
|
+
entry.nextEligibleAt = Date.now() + Math.max(AUTO_ANALYZE_FAILURE_BACKOFF_MS, (Date.now() - startedAt) * AUTO_ANALYZE_DUTY_CYCLE);
|
|
585
|
+
return 'failed';
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
/** Catalog row count for a table, ignoring the entry's own record. */
|
|
589
|
+
catalogRowCount(key) {
|
|
590
|
+
const table = this.findTable(key);
|
|
591
|
+
return table ? catalogRowCount(table) : undefined;
|
|
592
|
+
}
|
|
593
|
+
enabled() {
|
|
594
|
+
return this.ctx.options.getBooleanOption('auto_analyze');
|
|
595
|
+
}
|
|
596
|
+
minMutations() {
|
|
597
|
+
return this.ctx.options.getNumberOption('auto_analyze_min_mutations');
|
|
598
|
+
}
|
|
599
|
+
ratio() {
|
|
600
|
+
return this.ctx.options.getNumberOption('auto_analyze_ratio');
|
|
601
|
+
}
|
|
602
|
+
/**
|
|
603
|
+
* Largest table (in known rows) an automatic refresh will scan; 0 disables the cap.
|
|
604
|
+
*
|
|
605
|
+
* NOTE: the gate is purely on row count. An earlier design also exempted modules
|
|
606
|
+
* whose `getStatistics()` answers the whole question cheaply, but no shipped module
|
|
607
|
+
* does — both the store backend (`store-table-base.ts`) and the memory backend
|
|
608
|
+
* (`vtab/memory/table.ts`) deliberately return an EMPTY `columnStats`, which
|
|
609
|
+
* `ANALYZE` reads as "size answered, scan for the rest". The exemption would be dead
|
|
610
|
+
* code. If a module that reports complete statistics ever appears, exempt it here.
|
|
611
|
+
*/
|
|
612
|
+
rowLimit() {
|
|
613
|
+
return this.ctx.options.getNumberOption('auto_analyze_row_limit');
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
//# sourceMappingURL=database-auto-analyze.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"database-auto-analyze.js","sourceRoot":"","sources":["../../../src/core/database-auto-analyze.ts"],"names":[],"mappings":"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"}
|