Maintain scoped operation history totals without table scans
This commit is contained in:
@@ -0,0 +1,42 @@
|
||||
import type Database from 'better-sqlite3';
|
||||
import { importantOperationSql } from './observations.js';
|
||||
|
||||
const important = (alias: string) => importantOperationSql.replaceAll('o.', `${alias}.`);
|
||||
const visible = (alias: string) => `${alias}.history_expired_at IS NULL`;
|
||||
|
||||
/** Durable totals for global, host, server, and host/server filters; receipts update only changed membership. */
|
||||
export function installHistoryCounts(handle: Database.Database): void {
|
||||
if (handle.prepare("SELECT 1 FROM ec_host_schema_migrations WHERE name='history-counts-v1'").get()) return;
|
||||
handle.transaction(() => {
|
||||
handle.exec(`CREATE TABLE ec_host_operation_history_counts (
|
||||
host_id TEXT NOT NULL, server_name TEXT NOT NULL, total INTEGER NOT NULL,
|
||||
routine INTEGER NOT NULL, important INTEGER NOT NULL, PRIMARY KEY(host_id,server_name)
|
||||
) WITHOUT ROWID;
|
||||
DROP INDEX ec_host_operations_important_history;
|
||||
CREATE INDEX ec_host_operations_important_history ON ec_host_operations(created_at)
|
||||
WHERE history_expired_at IS NULL AND ${importantOperationSql.replaceAll('o.', '')};`);
|
||||
for (const [host, server] of [['', ''], ['o.host_id', ''], ['', 'o.server_name'], ['o.host_id', 'o.server_name']]) {
|
||||
const groups = [host, server].filter(Boolean);
|
||||
handle.exec(`INSERT INTO ec_host_operation_history_counts
|
||||
SELECT ${host || "''"},${server || "''"},COUNT(*),COALESCE(SUM(o.routine=1),0),COALESCE(SUM(${important('o')}),0)
|
||||
FROM ec_host_operations o WHERE ${visible('o')} ${server ? 'AND o.server_name IS NOT NULL' : ''}
|
||||
${groups.length ? 'GROUP BY ' + groups.join(',') : ''}`);
|
||||
}
|
||||
const adjust = (alias: string, direction: 1 | -1) => `
|
||||
INSERT INTO ec_host_operation_history_counts(host_id,server_name,total,routine,important)
|
||||
SELECT host_id,server_name,${direction},${direction}*(${alias}.routine=1),${direction}*${important(alias)} FROM (
|
||||
SELECT '' host_id,'' server_name UNION ALL SELECT ${alias}.host_id,''
|
||||
UNION ALL SELECT '',${alias}.server_name WHERE ${alias}.server_name IS NOT NULL
|
||||
UNION ALL SELECT ${alias}.host_id,${alias}.server_name WHERE ${alias}.server_name IS NOT NULL
|
||||
) WHERE ${visible(alias)}
|
||||
ON CONFLICT(host_id,server_name) DO UPDATE SET total=total+excluded.total,
|
||||
routine=routine+excluded.routine,important=important+excluded.important;`;
|
||||
handle.exec(`CREATE TRIGGER operation_history_count_insert AFTER INSERT ON ec_host_operations BEGIN ${adjust('NEW', 1)} END;
|
||||
CREATE TRIGGER operation_history_count_delete AFTER DELETE ON ec_host_operations BEGIN ${adjust('OLD', -1)} END;
|
||||
CREATE TRIGGER operation_history_count_update AFTER UPDATE OF host_id,server_name,routine,state,observation_success,error,history_expired_at ON ec_host_operations
|
||||
WHEN OLD.host_id IS NOT NEW.host_id OR OLD.server_name IS NOT NEW.server_name OR OLD.routine IS NOT NEW.routine
|
||||
OR (${visible('OLD')}) IS NOT (${visible('NEW')}) OR ${important('OLD')} IS NOT ${important('NEW')}
|
||||
BEGIN ${adjust('OLD', -1)} ${adjust('NEW', 1)} END;`);
|
||||
handle.prepare("INSERT INTO ec_host_schema_migrations(name) VALUES('history-counts-v1')").run();
|
||||
})();
|
||||
}
|
||||
@@ -426,7 +426,9 @@ export function listHostOperationHistory(page = 1, filter: HostOperationFilter =
|
||||
if (scope.server) { conditions.push('o.server_name=?'); parameters.push(scope.server); }
|
||||
const where = conditions.length ? `WHERE ${conditions.join(' AND ')}` : '';
|
||||
return handle.transaction((): HostOperationHistoryPage => {
|
||||
const { total } = handle.prepare(`SELECT COUNT(*) AS total FROM ec_host_operations o ${where}`).get(...parameters) as { total: number };
|
||||
const countColumn = filter === 'all' ? 'total' : filter;
|
||||
const total = (handle.prepare(`SELECT ${countColumn} AS total FROM ec_host_operation_history_counts WHERE host_id=? AND server_name=?`)
|
||||
.get(scope.host ?? '', scope.server ?? '') as { total: number } | undefined)?.total ?? 0;
|
||||
const totalPages = Math.max(1, Math.ceil(total / pageSize));
|
||||
const currentPage = Math.min(totalPages, Number.isSafeInteger(page) && page > 0 ? page : 1);
|
||||
const operations = handle.prepare(`SELECT o.id,o.host_id AS hostId,h.name AS hostName,o.kind,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { createHash } from 'node:crypto';
|
||||
import { ensureFileBlobSchema, migrationRows, packFileJson, referenceBlobs } from '../db/file-blobs.js';
|
||||
import { retentionClocks } from '../db/retention-clocks.js';
|
||||
import { installHistoryCounts } from './history-counts.js';
|
||||
import type Database from 'better-sqlite3';
|
||||
import { successfulObservationSql, routineObservationSql, operationTargetSql, importantOperationSql } from './observations.js';
|
||||
|
||||
@@ -187,5 +188,6 @@ export function ensureHostSchema(handle: Database.Database): void {
|
||||
WHEN NEW.history_expired_at IS NOT NULL BEGIN
|
||||
DELETE FROM file_blob_references WHERE owner_type IN ('operation','operation-result') AND owner_id=NEW.id; END;`);
|
||||
retentionClocks(handle, { ec_host_operations: ['created_at', 'updated_at'] });
|
||||
installHistoryCounts(handle);
|
||||
initialized.add(handle);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user