1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
//! Migration adding a composite index for subscription cursor scans.
//!
//! Subscription cursor queries filter on `aggregator_type` (optionally `name`,
//! always with a `routing_key` predicate) and order by
//! `(timestamp, timestamp_subsec, version, id)`. The original `idx_event_type`
//! covers only the filter, so the planner loads every matching row and sorts
//! externally; this dominates polling cost as the event table grows.
//!
//! This migration replaces the single-column `idx_event_type` with one composite
//! index that covers both filter and sort columns, allowing the planner to walk
//! the index in sorted order without an external sort.
use vec_box;
/// Migration that swaps `idx_event_type` for a composite cursor-scan index.
///
/// ## Changes
///
/// - Creates `idx_event_type_routing_cursor` on
/// `(aggregator_type, routing_key, timestamp, timestamp_subsec, version)`.
/// - Drops `idx_event_type` (redundant once the composite index exists — same
/// leading column, planner will use the new one).
///
/// ## Notes
///
/// - `name` is intentionally not part of the index: it is only ever a
/// per-aggregator-branch residual predicate inside an OR, and with the typical
/// small set of event names per aggregator type the residual scan stays cheap.
/// - The `ORDER BY` tail stops at `version`; `(timestamp, timestamp_subsec,
/// version)` is unique in practice, so the trailing `id` adds storage without
/// eliminating any sort work.
///
/// ## Dependencies
///
/// This migration depends on [`M0003`](crate::M0003).
;
sqlite_migration!;
mysql_migration!;
postgres_migration!;