perf(api): ретенция статистики, устранение N+1 и пагинация списков
- retention: ежедневный cron (03:17) + запуск на старте удаляет сырые
agent_stats_samples старше STATS_RETENTION_DAYS (по умолчанию 30);
lifetime-итоги на агенте и агрегаты сохраняются
- N+1 на поллинг-пути агента: evaluateAgentPolicy теперь делает по одному
батч-запросу на записи списков / резолвы hostname / имена списков вместо
запроса на каждое правило; mapPolicyRules и port-rules GET — аналогично
- пагинация: GET /agents, /lists, /rules, /install-links принимают
limit/offset и возвращают total; без параметров — прежнее поведение
- openapi.yaml: approve-bulk, PUT /policy-sets/{id}/agents, параметры
пагинации (redocly lint OK); тесты (47 passed)
This commit is contained in:
@@ -14,6 +14,8 @@ export {
|
||||
updateIpList,
|
||||
deleteIpList,
|
||||
listIpListEntries,
|
||||
mapIpListEntriesByListIds,
|
||||
mapIpListNames,
|
||||
countEntriesByListIds,
|
||||
replaceIpListEntries,
|
||||
} from './lists.js'
|
||||
@@ -45,6 +47,7 @@ export {
|
||||
deletePolicyRule,
|
||||
listHostnameRules,
|
||||
listResolvedForRule,
|
||||
mapResolvedCidrsByRuleIds,
|
||||
replaceResolvedForRule,
|
||||
listOverrides,
|
||||
insertOverride,
|
||||
@@ -57,6 +60,7 @@ export {
|
||||
listStatsSamples,
|
||||
listRecentStats,
|
||||
deleteStatsSamplesForAgent,
|
||||
deleteStatsSamplesBefore,
|
||||
upsertIpBlockStats,
|
||||
listIpBlockStats,
|
||||
deleteIpBlockStatsForAgent,
|
||||
@@ -121,6 +125,8 @@ import {
|
||||
updateIpList,
|
||||
deleteIpList,
|
||||
listIpListEntries,
|
||||
mapIpListEntriesByListIds,
|
||||
mapIpListNames,
|
||||
countEntriesByListIds,
|
||||
replaceIpListEntries,
|
||||
} from './lists.js'
|
||||
@@ -151,6 +157,7 @@ import {
|
||||
deletePolicyRule,
|
||||
listHostnameRules,
|
||||
listResolvedForRule,
|
||||
mapResolvedCidrsByRuleIds,
|
||||
replaceResolvedForRule,
|
||||
listOverrides,
|
||||
insertOverride,
|
||||
@@ -162,6 +169,7 @@ import {
|
||||
listStatsSamples,
|
||||
listRecentStats,
|
||||
deleteStatsSamplesForAgent,
|
||||
deleteStatsSamplesBefore,
|
||||
upsertIpBlockStats,
|
||||
listIpBlockStats,
|
||||
deleteIpBlockStatsForAgent,
|
||||
@@ -216,6 +224,8 @@ export const repos = {
|
||||
updateIpList,
|
||||
deleteIpList,
|
||||
listIpListEntries,
|
||||
mapIpListEntriesByListIds,
|
||||
mapIpListNames,
|
||||
countEntriesByListIds,
|
||||
replaceIpListEntries,
|
||||
listPolicySets,
|
||||
@@ -240,6 +250,7 @@ export const repos = {
|
||||
deletePolicyRule,
|
||||
listHostnameRules,
|
||||
listResolvedForRule,
|
||||
mapResolvedCidrsByRuleIds,
|
||||
replaceResolvedForRule,
|
||||
listOverrides,
|
||||
insertOverride,
|
||||
@@ -248,6 +259,7 @@ export const repos = {
|
||||
listStatsSamples,
|
||||
listRecentStats,
|
||||
deleteStatsSamplesForAgent,
|
||||
deleteStatsSamplesBefore,
|
||||
upsertIpBlockStats,
|
||||
listIpBlockStats,
|
||||
deleteIpBlockStatsForAgent,
|
||||
|
||||
@@ -39,6 +39,34 @@ export function listIpListEntries(db: Db, listId: string) {
|
||||
.all()
|
||||
}
|
||||
|
||||
/** Entries for many lists in one query (agent policy hot path). */
|
||||
export function mapIpListEntriesByListIds(
|
||||
db: Db,
|
||||
listIds: string[],
|
||||
): Map<string, string[]> {
|
||||
const map = new Map<string, string[]>()
|
||||
for (const id of listIds) map.set(id, [])
|
||||
if (listIds.length === 0) return map
|
||||
const rows = db
|
||||
.select({ listId: ipListEntries.listId, cidr: ipListEntries.cidr })
|
||||
.from(ipListEntries)
|
||||
.where(inArray(ipListEntries.listId, listIds))
|
||||
.all()
|
||||
for (const r of rows) map.get(r.listId)?.push(r.cidr)
|
||||
return map
|
||||
}
|
||||
|
||||
/** List names for many ids in one query. */
|
||||
export function mapIpListNames(db: Db, ids: string[]): Map<string, string> {
|
||||
if (ids.length === 0) return new Map()
|
||||
const rows = db
|
||||
.select({ id: ipLists.id, name: ipLists.name })
|
||||
.from(ipLists)
|
||||
.where(inArray(ipLists.id, ids))
|
||||
.all()
|
||||
return new Map(rows.map((r) => [r.id, r.name]))
|
||||
}
|
||||
|
||||
/** Entry counts for many lists in one query. */
|
||||
export function countEntriesByListIds(
|
||||
db: Db,
|
||||
|
||||
@@ -319,6 +319,26 @@ export function listResolvedForRule(db: Db, ruleId: string) {
|
||||
.all()
|
||||
}
|
||||
|
||||
/** Resolved CIDRs for many hostname rules in one query. */
|
||||
export function mapResolvedCidrsByRuleIds(
|
||||
db: Db,
|
||||
ruleIds: string[],
|
||||
): Map<string, string[]> {
|
||||
const map = new Map<string, string[]>()
|
||||
for (const id of ruleIds) map.set(id, [])
|
||||
if (ruleIds.length === 0) return map
|
||||
const rows = db
|
||||
.select({
|
||||
ruleId: policyRuleResolved.ruleId,
|
||||
cidr: policyRuleResolved.cidr,
|
||||
})
|
||||
.from(policyRuleResolved)
|
||||
.where(inArray(policyRuleResolved.ruleId, ruleIds))
|
||||
.all()
|
||||
for (const r of rows) map.get(r.ruleId)?.push(r.cidr)
|
||||
return map
|
||||
}
|
||||
|
||||
export function replaceResolvedForRule(db: Db, ruleId: string, cidrs: string[]) {
|
||||
db.transaction((tx) => {
|
||||
tx.delete(policyRuleResolved)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { and, eq, desc, sql, inArray } from 'drizzle-orm'
|
||||
import { and, eq, desc, sql, inArray, lt } from 'drizzle-orm'
|
||||
import type { Db } from '../client.js'
|
||||
import {
|
||||
agentIpBlockStats,
|
||||
@@ -38,6 +38,18 @@ export function deleteStatsSamplesForAgent(db: Db, agentId: string) {
|
||||
.run()
|
||||
}
|
||||
|
||||
/**
|
||||
* Retention: drop raw samples recorded before the cutoff ISO timestamp.
|
||||
* Lifetime totals live on the agent row; per-IP/per-port aggregates are kept.
|
||||
*/
|
||||
export function deleteStatsSamplesBefore(db: Db, cutoffIso: string): number {
|
||||
const result = db
|
||||
.delete(agentStatsSamples)
|
||||
.where(lt(agentStatsSamples.recordedAt, cutoffIso))
|
||||
.run()
|
||||
return result.changes
|
||||
}
|
||||
|
||||
export type IpHitInput = { ip: string; packets: number }
|
||||
|
||||
/** Gap after which a new presence report counts as a re-hit (left EVOFW_HITS). */
|
||||
|
||||
Reference in New Issue
Block a user