@secondlayer/shared 6.28.0 → 6.28.1
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.
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { type Kysely, sql } from "kysely";
|
|
2
|
+
import { onChainPlane } from "../src/db/migration-role.ts";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Streams read-path indexes on `events` (chain plane / SOURCE).
|
|
6
|
+
*
|
|
7
|
+
* The Streams firehose filters raw `events` by `data->>'sender'`,
|
|
8
|
+
* `data->>'recipient'`, and `data->>'asset_identifier'` (see
|
|
9
|
+
* `streamsFilterPredicate` in packages/indexer/src/streams-events.ts). Without
|
|
10
|
+
* these the filtered scan is a `Seq Scan on events` over the cursor range —
|
|
11
|
+
* millions of rows on mainnet — and the response window times out. The
|
|
12
|
+
* `(block_height, type)` composite backs both the candidate-row scan and the
|
|
13
|
+
* per-block all-types ordinal CTE that replaces the old per-row correlated
|
|
14
|
+
* COUNT(*).
|
|
15
|
+
*
|
|
16
|
+
* Partial predicate is `(data->>'<field>') IS NOT NULL`, NOT `type IN (...)`:
|
|
17
|
+
* the query filters on equality (`data->>'sender' = $1`), which Postgres can
|
|
18
|
+
* prove implies `IS NOT NULL`, so the index is usable even when the caller
|
|
19
|
+
* filters by sender/recipient WITHOUT a `types=` (a `type IN (transfer types)`
|
|
20
|
+
* predicate would not be provably implied by the unfiltered `type IN (all)`
|
|
21
|
+
* candidate scan, and the planner would skip the index). Keeps the index
|
|
22
|
+
* compact (only rows that carry the field are indexed) while staying usable.
|
|
23
|
+
*
|
|
24
|
+
* `CREATE INDEX CONCURRENTLY` cannot run inside the migrate runner's tx; in
|
|
25
|
+
* prod build these manually with CONCURRENTLY first (matching the index name
|
|
26
|
+
* EXACTLY), then this migration is a no-op there via `IF NOT EXISTS`. On
|
|
27
|
+
* dev/staging the events table is small, so the brief lock is acceptable.
|
|
28
|
+
* `events` is a chain-plane table → DDL no-ops on the control DB under the
|
|
29
|
+
* source/target split.
|
|
30
|
+
*/
|
|
31
|
+
export async function up(db: Kysely<unknown>): Promise<void> {
|
|
32
|
+
await onChainPlane(async () => {
|
|
33
|
+
await sql`
|
|
34
|
+
CREATE INDEX IF NOT EXISTS events_height_type_idx
|
|
35
|
+
ON events (block_height, type)
|
|
36
|
+
`.execute(db);
|
|
37
|
+
await sql`
|
|
38
|
+
CREATE INDEX IF NOT EXISTS events_sender_height_idx
|
|
39
|
+
ON events ((data->>'sender'), block_height)
|
|
40
|
+
WHERE (data->>'sender') IS NOT NULL
|
|
41
|
+
`.execute(db);
|
|
42
|
+
await sql`
|
|
43
|
+
CREATE INDEX IF NOT EXISTS events_recipient_height_idx
|
|
44
|
+
ON events ((data->>'recipient'), block_height)
|
|
45
|
+
WHERE (data->>'recipient') IS NOT NULL
|
|
46
|
+
`.execute(db);
|
|
47
|
+
await sql`
|
|
48
|
+
CREATE INDEX IF NOT EXISTS events_asset_identifier_height_idx
|
|
49
|
+
ON events ((data->>'asset_identifier'), block_height)
|
|
50
|
+
WHERE (data->>'asset_identifier') IS NOT NULL
|
|
51
|
+
`.execute(db);
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export async function down(db: Kysely<unknown>): Promise<void> {
|
|
56
|
+
await onChainPlane(async () => {
|
|
57
|
+
await sql`DROP INDEX IF EXISTS events_asset_identifier_height_idx`.execute(
|
|
58
|
+
db,
|
|
59
|
+
);
|
|
60
|
+
await sql`DROP INDEX IF EXISTS events_recipient_height_idx`.execute(db);
|
|
61
|
+
await sql`DROP INDEX IF EXISTS events_sender_height_idx`.execute(db);
|
|
62
|
+
await sql`DROP INDEX IF EXISTS events_height_type_idx`.execute(db);
|
|
63
|
+
});
|
|
64
|
+
}
|