fix(netflow): изолировать коллектор IPFIX и срезать раздувание базы
Docker images / prepare-release (push) Successful in 8s
Docker images / backend-image (push) Successful in 2m23s
Docker images / frontend-image (push) Successful in 3m16s
Docker images / notify-webhook (push) Skipped
Docker images / updater-image (push) Successful in 57s
Docker images / publish-release (push) Successful in 12s

Co-authored-by: Cursor <[email protected]>
This commit is contained in:
Denozordec
2026-09-07 09:50:24 +07:00
co-authored by Cursor
parent e0ddb17539
commit cb799da13a
24 changed files with 1884 additions and 530 deletions
+62 -6
View File
@@ -7,11 +7,23 @@ import { drizzle } from "drizzle-orm/better-sqlite3"
import { env } from "../config.js"
import * as schema from "./schema.js"
const sqlite = new Database(env.DATABASE_PATH)
export const SQLITE_BUSY_TIMEOUT_MS = 5000
export function applySqlitePragmas(handle: SqliteHandle): void {
handle.pragma("journal_mode = WAL")
handle.pragma("foreign_keys = ON")
handle.pragma(`busy_timeout = ${SQLITE_BUSY_TIMEOUT_MS}`)
handle.pragma("synchronous = NORMAL")
}
function openSqlite(): SqliteHandle {
const handle = new Database(env.DATABASE_PATH)
applySqlitePragmas(handle)
return handle
}
let sqlite = openSqlite()
// WAL mode for better concurrent read performance
sqlite.pragma("journal_mode = WAL")
sqlite.pragma("foreign_keys = ON")
sqlite.exec(`
CREATE TABLE IF NOT EXISTS servers (
id INTEGER PRIMARY KEY AUTOINCREMENT,
@@ -162,6 +174,39 @@ CREATE UNIQUE INDEX IF NOT EXISTS idx_flow_buckets_unique
CREATE INDEX IF NOT EXISTS idx_flow_buckets_server_time
ON flow_buckets(server_id, bucket_at);
CREATE TABLE IF NOT EXISTS flow_minute_stats (
server_id INTEGER NOT NULL,
bucket_at TEXT NOT NULL,
bytes INTEGER NOT NULL DEFAULT 0,
packets INTEGER NOT NULL DEFAULT 0,
unique_src INTEGER NOT NULL DEFAULT 0,
unique_dst INTEGER NOT NULL DEFAULT 0,
conversations INTEGER NOT NULL DEFAULT 0,
PRIMARY KEY (server_id, bucket_at)
);
CREATE TABLE IF NOT EXISTS flow_minute_dims (
server_id INTEGER NOT NULL,
bucket_at TEXT NOT NULL,
dim TEXT NOT NULL,
key TEXT NOT NULL,
bytes INTEGER NOT NULL DEFAULT 0,
packets INTEGER NOT NULL DEFAULT 0,
PRIMARY KEY (server_id, bucket_at, dim, key)
);
CREATE INDEX IF NOT EXISTS idx_flow_minute_dims_time ON flow_minute_dims(bucket_at, dim);
CREATE TABLE IF NOT EXISTS flow_daily_dims (
server_id INTEGER NOT NULL,
day TEXT NOT NULL,
dim TEXT NOT NULL,
key TEXT NOT NULL,
bytes INTEGER NOT NULL DEFAULT 0,
packets INTEGER NOT NULL DEFAULT 0,
PRIMARY KEY (server_id, day, dim, key)
);
CREATE INDEX IF NOT EXISTS idx_flow_daily_dims_day ON flow_daily_dims(day, dim);
CREATE TABLE IF NOT EXISTS flow_ip_meta (
prefix TEXT PRIMARY KEY,
asn INTEGER NOT NULL DEFAULT 0,
@@ -902,7 +947,18 @@ if (backupEntryCount.c === 0) {
}
}
export const db = drizzle(sqlite, { schema })
export let db = drizzle(sqlite, { schema })
/** Прямой доступ к better-sqlite3 для сложных read-only запросов (напр. /api/alerts). */
export const sqliteDatabase: SqliteHandle = sqlite
export let sqliteDatabase: SqliteHandle = sqlite
export function reopenSqlite(): void {
try {
sqlite.close()
} catch {
/* already closed */
}
sqlite = openSqlite()
sqliteDatabase = sqlite
db = drizzle(sqlite, { schema })
}
+34
View File
@@ -182,6 +182,40 @@ export const trafficFlowSettings = sqliteTable("traffic_flow_settings", {
updatedAt: text("updated_at").notNull().default(sql`(datetime('now'))`),
})
export const flowMinuteStats = sqliteTable("flow_minute_stats", {
serverId: integer("server_id").notNull(),
bucketAt: text("bucket_at").notNull(),
bytes: integer("bytes").notNull().default(0),
packets: integer("packets").notNull().default(0),
uniqueSrc: integer("unique_src").notNull().default(0),
uniqueDst: integer("unique_dst").notNull().default(0),
conversations: integer("conversations").notNull().default(0),
}, (t) => [
uniqueIndex("idx_flow_minute_stats_pk").on(t.serverId, t.bucketAt),
])
export const flowMinuteDims = sqliteTable("flow_minute_dims", {
serverId: integer("server_id").notNull(),
bucketAt: text("bucket_at").notNull(),
dim: text("dim").notNull(),
key: text("key").notNull(),
bytes: integer("bytes").notNull().default(0),
packets: integer("packets").notNull().default(0),
}, (t) => [
uniqueIndex("idx_flow_minute_dims_pk").on(t.serverId, t.bucketAt, t.dim, t.key),
])
export const flowDailyDims = sqliteTable("flow_daily_dims", {
serverId: integer("server_id").notNull(),
day: text("day").notNull(),
dim: text("dim").notNull(),
key: text("key").notNull(),
bytes: integer("bytes").notNull().default(0),
packets: integer("packets").notNull().default(0),
}, (t) => [
uniqueIndex("idx_flow_daily_dims_pk").on(t.serverId, t.day, t.dim, t.key),
])
export const flowBuckets = sqliteTable("flow_buckets", {
id: integer("id").primaryKey({ autoIncrement: true }),
serverId: integer("server_id")