2026-06-08 22:33:34 -05:00
|
|
|
import { upsertClientRows, type ClientDbRow } from "./db";
|
|
|
|
|
import { metrics } from "./metrics";
|
2026-05-29 15:41:08 -05:00
|
|
|
|
2026-07-12 23:14:29 -05:00
|
|
|
function positiveNumberEnv(name: string, fallback: number): number {
|
|
|
|
|
const value = Number(process.env[name] || fallback);
|
|
|
|
|
return Number.isFinite(value) && value > 0 ? value : fallback;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export const CLIENT_DB_SYNC_INTERVAL_MS = positiveNumberEnv("OVERLORD_CLIENT_DB_SYNC_MS", 5000);
|
|
|
|
|
export const CLIENT_PRESENCE_SYNC_INTERVAL_MS = Math.max(
|
|
|
|
|
CLIENT_DB_SYNC_INTERVAL_MS,
|
|
|
|
|
positiveNumberEnv("OVERLORD_CLIENT_PRESENCE_SYNC_MS", 60_000),
|
|
|
|
|
);
|
2026-06-08 22:33:34 -05:00
|
|
|
export const CLIENT_DB_SYNC_BATCH_SIZE = Math.max(
|
|
|
|
|
1,
|
2026-06-23 23:04:01 -05:00
|
|
|
Number(process.env.OVERLORD_CLIENT_DB_SYNC_BATCH_SIZE || 500),
|
|
|
|
|
);
|
|
|
|
|
export const CLIENT_DB_SYNC_FLUSH_DELAY_MS = Math.max(
|
|
|
|
|
1,
|
|
|
|
|
Number(process.env.OVERLORD_CLIENT_DB_SYNC_FLUSH_DELAY_MS || 100),
|
2026-06-08 22:33:34 -05:00
|
|
|
);
|
2026-05-29 15:41:08 -05:00
|
|
|
|
|
|
|
|
const lastClientDbSync = new Map<string, number>();
|
|
|
|
|
const pendingClientDbUpdates = new Map<string, ClientDbRow>();
|
2026-06-08 22:33:34 -05:00
|
|
|
let flushSoonTimer: ReturnType<typeof setTimeout> | null = null;
|
2026-05-29 15:41:08 -05:00
|
|
|
|
|
|
|
|
export function queueClientDbUpdate(partial: ClientDbRow): void {
|
|
|
|
|
const existing = pendingClientDbUpdates.get(partial.id);
|
|
|
|
|
if (!existing) {
|
|
|
|
|
pendingClientDbUpdates.set(partial.id, { ...partial });
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
pendingClientDbUpdates.set(partial.id, {
|
|
|
|
|
...existing,
|
|
|
|
|
...partial,
|
|
|
|
|
id: partial.id,
|
|
|
|
|
lastSeen: partial.lastSeen ?? existing.lastSeen,
|
|
|
|
|
online: partial.online ?? existing.online,
|
|
|
|
|
pingMs: partial.pingMs ?? existing.pingMs,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-23 23:04:01 -05:00
|
|
|
export function scheduleQueuedClientDbFlush(delayMs = CLIENT_DB_SYNC_FLUSH_DELAY_MS): void {
|
|
|
|
|
if (pendingClientDbUpdates.size === 0 || flushSoonTimer) return;
|
|
|
|
|
flushSoonTimer = setTimeout(() => {
|
|
|
|
|
flushSoonTimer = null;
|
|
|
|
|
flushQueuedClientDbUpdates();
|
|
|
|
|
}, Math.max(1, Math.floor(delayMs)));
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-29 15:41:08 -05:00
|
|
|
export function flushQueuedClientDbUpdates(): void {
|
|
|
|
|
if (pendingClientDbUpdates.size === 0) return;
|
2026-06-08 22:33:34 -05:00
|
|
|
|
|
|
|
|
const startedAt = Date.now();
|
|
|
|
|
let processed = 0;
|
|
|
|
|
const updates: ClientDbRow[] = [];
|
|
|
|
|
for (const [clientId, update] of pendingClientDbUpdates.entries()) {
|
|
|
|
|
updates.push(update);
|
|
|
|
|
pendingClientDbUpdates.delete(clientId);
|
|
|
|
|
processed += 1;
|
|
|
|
|
if (processed >= CLIENT_DB_SYNC_BATCH_SIZE) {
|
|
|
|
|
break;
|
|
|
|
|
}
|
2026-05-29 15:41:08 -05:00
|
|
|
}
|
2026-06-08 22:33:34 -05:00
|
|
|
upsertClientRows(updates);
|
|
|
|
|
|
2026-06-23 23:04:01 -05:00
|
|
|
if (pendingClientDbUpdates.size > 0) {
|
|
|
|
|
scheduleQueuedClientDbFlush(25);
|
2026-06-08 22:33:34 -05:00
|
|
|
}
|
|
|
|
|
metrics.recordInternalTask("client-db-flush", Date.now() - startedAt);
|
2026-05-29 15:41:08 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
setInterval(flushQueuedClientDbUpdates, CLIENT_DB_SYNC_INTERVAL_MS);
|
|
|
|
|
|
|
|
|
|
export function shouldSyncClientToDb(clientId: string, now: number): boolean {
|
|
|
|
|
const last = lastClientDbSync.get(clientId) || 0;
|
2026-07-12 23:14:29 -05:00
|
|
|
if (now - last < CLIENT_PRESENCE_SYNC_INTERVAL_MS) return false;
|
2026-05-29 15:41:08 -05:00
|
|
|
lastClientDbSync.set(clientId, now);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function markClientDbSynced(clientId: string, now: number): void {
|
|
|
|
|
lastClientDbSync.set(clientId, now);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function clearClientSyncState(clientId: string): void {
|
|
|
|
|
lastClientDbSync.delete(clientId);
|
|
|
|
|
pendingClientDbUpdates.delete(clientId);
|
|
|
|
|
}
|
2026-06-08 22:33:34 -05:00
|
|
|
|
|
|
|
|
export function getClientDbSyncStats(): {
|
|
|
|
|
trackedClients: number;
|
|
|
|
|
pendingUpdates: number;
|
|
|
|
|
flushScheduled: boolean;
|
|
|
|
|
} {
|
|
|
|
|
return {
|
|
|
|
|
trackedClients: lastClientDbSync.size,
|
|
|
|
|
pendingUpdates: pendingClientDbUpdates.size,
|
|
|
|
|
flushScheduled: flushSoonTimer !== null,
|
|
|
|
|
};
|
|
|
|
|
}
|