fix(db): индексировать историю для очистки планировщика
Docker images / prepare-release (push) Successful in 14s
Docker images / backend-test (push) Failing after 2m49s
Docker images / backend-image (push) Skipped
Docker images / frontend-image (push) Successful in 2m39s
Docker images / notify-webhook (push) Skipped
Docker images / updater-image (push) Successful in 42s
Docker images / publish-release (push) Skipped

This commit is contained in:
Denozordec committed 2026-10-15 22:04:38 +07:00
1 parent fda7f353ad
commit b2cf71fcc7
7 files changed
+136 -2

No files matched your search

@@ -0,0 +1,4 @@
-- Retention runs after each scheduler receipt; avoid scanning the complete history.
-- On a busy existing installation, pre-create this same index CONCURRENTLY
-- outside a transaction before deploying (deploy/PRODUCTION-RETENTION-INDEX.md).
CREATE INDEX IF NOT EXISTS idx_scheduler_runs_finished_at ON scheduler_runs (finished_at);
+1 -1
View File
@@ -22,7 +22,7 @@
"test:traffic-rate": "tsx src/services/traffic-rate.test.ts && tsx src/services/traffic-observation.test.ts && tsx src/services/traffic-freshness.test.ts && tsx src/services/traffic-live.test.ts && tsx src/services/traffic-observation-sql.test.ts && tsx src/services/traffic-history-sql.test.ts && tsx src/services/traffic-live-sql.test.ts && tsx src/services/traffic-volume-sql.test.ts && tsx src/services/traffic-binding-status.test.ts && tsx src/services/traffic-status-sql.test.ts && tsx src/services/traffic-gap.test.ts && tsx src/services/traffic-identity.test.ts && tsx src/services/traffic-membership.test.ts && tsx src/services/traffic-aggregate-peak.test.ts && tsx src/services/traffic-users-cache.test.ts && tsx src/services/traffic-collection-lock.test.ts && tsx src/services/traffic-concurrency-sql.test.ts",
"test:traffic-flow": "tsx src/routes/traffic-flow-query.test.ts && tsx src/services/traffic-flow-month.test.ts && tsx src/services/traffic-flow-parse.test.ts && tsx src/services/traffic-flow-ipv6-pipeline.test.ts && tsx src/services/traffic-flow-map-exporter.test.ts && tsx src/services/traffic-flow-ifaces.test.ts && tsx src/services/traffic-flow-ifindex.test.ts && tsx src/services/traffic-flow-dedup.test.ts && tsx src/services/traffic-flow-planes.test.ts && tsx src/services/traffic-flow-ip.test.ts && tsx src/services/traffic-flow-dest.test.ts && tsx src/services/traffic-flow-classify.test.ts && tsx src/services/traffic-flow-ripe.test.ts && tsx src/services/traffic-flow-brands.test.ts && tsx src/services/traffic-flow-ingest.test.ts && tsx src/services/traffic-flow-analytics.test.ts && tsx src/services/traffic-flow-map-hops.test.ts && tsx src/services/traffic-flow-purge.test.ts && tsx src/services/geoip-download.test.ts && tsx src/services/traffic-flow-geoip.test.ts && tsx src/services/traffic-flow-facts.test.ts && tsx src/services/traffic-flow-facts-filter.test.ts && tsx src/services/traffic-flow-facts-rebuild.test.ts && tsx src/services/statistics-aggregate.test.ts",
"test:users": "tsx src/modules/users/iface-type.test.ts && tsx src/modules/users/bindings.test.ts && tsx src/modules/users/interface-inventory.test.ts && tsx src/routes/users-catalog.test.ts",
"test:pg": "tsx src/db/pg-pool.test.ts && tsx src/db/sql-bind.test.ts && tsx src/db/sqlite-json.test.ts && tsx src/db/traffic-flags.test.ts && tsx src/db/pg-schema.test.ts && tsx src/services/config-revisions.test.ts",
"test:pg": "tsx src/db/pg-pool.test.ts && tsx src/db/sql-bind.test.ts && tsx src/db/sqlite-json.test.ts && tsx src/db/traffic-flags.test.ts && tsx src/db/pg-schema.test.ts && tsx src/db/scheduler-retention-index.test.ts && tsx src/services/config-revisions.test.ts",
"test:recursive-routes": "tsx src/routes/recursive-routes.test.ts && tsx src/services/recursive-route-catalog.test.ts && tsx src/services/recursive-apply.test.ts",
"test:config-sync": "tsx src/utils/server-id.test.ts && tsx src/services/config-apply-plan.test.ts && tsx src/services/entity-snapshots.test.ts && tsx src/services/gre-native-identity.test.ts && tsx src/services/gre-ipv6.test.ts && tsx src/routes/gre-ipv6-validation.test.ts && tsx src/services/network-disabled-server.test.ts",
"test:ros-file": "tsx src/services/ros-file-contents.test.ts && tsx src/services/mikrotik-response.test.ts",
@@ -0,0 +1,33 @@
import assert from "node:assert/strict"
import { readFileSync } from "node:fs"
import pg from "pg"
import { env } from "../config.js"
// No bootstrap/migrations against public tables: this test needs only a connection.
const pool = new pg.Pool({ connectionString: env.DATABASE_URL, connectionTimeoutMillis: 8000, statement_timeout: 10000 })
try {
const client = await pool.connect()
try {
await client.query("BEGIN")
// Temporary shadow table: public scheduler history is never changed.
await client.query("CREATE TEMP TABLE scheduler_runs(id integer PRIMARY KEY, finished_at timestamptz NOT NULL) ON COMMIT DROP")
await client.query(`INSERT INTO scheduler_runs SELECT i, now() - CASE WHEN i=1 THEN interval '60 days' ELSE i * interval '1 second' END FROM generate_series(1,20000) AS i`)
await client.query("ANALYZE scheduler_runs")
const cutoff = new Date(Date.now() - 30 * 86400_000).toISOString()
const plan = async () => JSON.stringify((await client.query("EXPLAIN (FORMAT JSON) SELECT id FROM scheduler_runs WHERE finished_at < $1", [cutoff])).rows)
assert.match(await plan(), /Seq Scan/)
const migration = readFileSync(new URL("../../drizzle/0013_scheduler_retention_index.sql", import.meta.url), "utf8")
await client.query(migration)
await client.query(migration)
assert.match(await plan(), /Index Scan|Bitmap Index Scan/)
assert.deepEqual((await client.query("SELECT id FROM scheduler_runs WHERE finished_at < $1", [cutoff])).rows, [{ id: 1 }])
const index = (await client.query("SELECT indisvalid FROM pg_index WHERE indrelid = 'pg_temp.scheduler_runs'::regclass AND indexrelid::regclass::text LIKE '%idx_scheduler_runs_finished_at'")).rows
assert.deepEqual(index, [{ indisvalid: true }])
console.log("Scheduler retention: actual migration/idempotence/valid index/selective plan/result preservation passed on 20,000 temporary rows")
} finally {
await client.query("ROLLBACK")
client.release()
}
} finally {
await pool.end()
}
+1
View File
@@ -649,6 +649,7 @@ export const schedulerRuns = pgTable("scheduler_runs", {
resultJson: jsonb("result_json"),
}, (t) => [
index("idx_scheduler_runs_job_time").on(t.jobKey, t.startedAt),
index("idx_scheduler_runs_finished_at").on(t.finishedAt),
])
export const events = pgTable("events", {