Skip to content

Commit 24d7ead

Browse files
committed
use a projected window instead of read/write
1 parent 8f55d96 commit 24d7ead

3 files changed

Lines changed: 61 additions & 48 deletions

File tree

server/src/repositories/monitors/IMonitorsRepository.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@ export interface IMonitorsRepository {
3535
status: boolean,
3636
checkSnapshot: CheckSnapshot,
3737
windowSize: number,
38-
maxRecentChecks: number
38+
maxRecentChecks: number,
39+
statusPatch?: Partial<Monitor>
3940
): Promise<Monitor>;
4041
togglePauseById(monitorId: string, teamId: string): Promise<Monitor>;
4142
// delete

server/src/repositories/monitors/MongoMonitorsRepository.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,8 @@ class MongoMonitorsRepository implements IMonitorsRepository {
188188
status: boolean,
189189
checkSnapshot: CheckSnapshot,
190190
windowSize: number,
191-
maxRecentChecks: number
191+
maxRecentChecks: number,
192+
statusPatch?: Partial<Monitor>
192193
): Promise<Monitor> => {
193194
const updatedMonitor = await MonitorModel.findOneAndUpdate(
194195
{ _id: monitorId, teamId },
@@ -197,6 +198,7 @@ class MongoMonitorsRepository implements IMonitorsRepository {
197198
statusWindow: { $each: [status], $slice: -windowSize },
198199
recentChecks: { $each: [checkSnapshot], $slice: -maxRecentChecks },
199200
},
201+
...(statusPatch && { $set: statusPatch }),
200202
},
201203
{ returnDocument: "after" }
202204
);

server/src/service/infrastructure/statusService.ts

Lines changed: 56 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -200,18 +200,6 @@ export class StatusService implements IStatusService {
200200
return { nextStatus, transitioned, breaches, nextCounters };
201201
};
202202

203-
private updateStatusWindowAndRecentChecks = async (monitor: Monitor, check: Check) => {
204-
const checkSnapshot = this.toCheckSnapshot(check);
205-
return await this.monitorsRepository.updateStatusWindowAndChecks(
206-
monitor.id,
207-
monitor.teamId,
208-
check.status,
209-
checkSnapshot,
210-
monitor.statusWindowSize,
211-
MAX_RECENT_CHECKS
212-
);
213-
};
214-
215203
updateMonitorStatus = async (
216204
statusResponse: MonitorStatusResponse<
217205
| PingStatusPayload
@@ -232,13 +220,32 @@ export class StatusService implements IStatusService {
232220

233221
// Update running stats
234222
await this.tryUpdateRunningStats(monitor, statusResponse);
235-
const updatedMonitor = await this.updateStatusWindowAndRecentChecks(monitor, check);
236-
const prevStatus = updatedMonitor.status;
237223

238-
// Return early if not enough data points
239-
if (updatedMonitor.statusWindow.length < updatedMonitor.statusWindowSize) {
240-
updatedMonitor.status = status === true ? "up" : "down";
241-
const updated = await this.monitorsRepository.updateById(updatedMonitor.id, updatedMonitor.teamId, { status: updatedMonitor.status });
224+
const prevStatus = monitor.status;
225+
const checkSnapshot = this.toCheckSnapshot(check);
226+
227+
// Project the window as it will look after updating DB
228+
// This is done because we need the updated status window to compute new status, but we don't
229+
// want an an extra DB write just to get the window.
230+
const projectedWindow = [...(monitor.statusWindow || []), check.status].slice(-monitor.statusWindowSize);
231+
232+
// Build the status patch — computed against the projected window
233+
const patch: Partial<Monitor> = {};
234+
235+
// Not enough data points yet
236+
if (projectedWindow.length < monitor.statusWindowSize) {
237+
patch.status = status === true ? "up" : "down";
238+
239+
const updated = await this.monitorsRepository.updateStatusWindowAndChecks(
240+
monitor.id,
241+
monitor.teamId,
242+
check.status,
243+
checkSnapshot,
244+
monitor.statusWindowSize,
245+
MAX_RECENT_CHECKS,
246+
patch
247+
);
248+
242249
return {
243250
monitor: updated,
244251
statusChanged: false,
@@ -248,18 +255,20 @@ export class StatusService implements IStatusService {
248255
};
249256
}
250257

251-
// With a full window, a single raw check must not change UNLESS we are initializing. Otherwise, only the sliding-window threshold can trigger a transition.
258+
// With a full window, a single raw check must not change UNLESS we are initializing.
259+
// Otherwise, only the sliding-window threshold can trigger a transition.
252260
let newStatus: MonitorStatus;
253-
if (updatedMonitor.status === "initializing") {
261+
if (monitor.status === "initializing") {
254262
newStatus = status === true ? "up" : "down";
255263
} else {
256-
newStatus = updatedMonitor.status;
264+
newStatus = monitor.status;
257265
}
258266

259267
let statusChanged = false;
260268

261-
// First evaluate reachability-based status changes, which apply to all monitor types and take precedence over hardware breaches.
262-
const reachabilityResult = this.computeReachability(newStatus, updatedMonitor.statusWindow, updatedMonitor.statusWindowThreshold);
269+
// First evaluate reachability-based status changes, which apply to all monitor types
270+
// and take precedence over hardware breaches.
271+
const reachabilityResult = this.computeReachability(newStatus, projectedWindow, monitor.statusWindowThreshold);
263272
if (reachabilityResult.transitioned) {
264273
newStatus = reachabilityResult.nextStatus;
265274
statusChanged = true;
@@ -268,47 +277,48 @@ export class StatusService implements IStatusService {
268277
// Evaluate hardware threshold breaches (only for hardware monitors with metrics payload)
269278
let thresholdBreaches: HardwareBreaches | undefined;
270279
const hardwarePayload = statusResponse.payload as HardwareStatusPayload | undefined;
271-
if (updatedMonitor.type === "hardware" && hardwarePayload?.data) {
280+
if (monitor.type === "hardware" && hardwarePayload?.data) {
272281
const hardware = this.computeHardwareStatus({
273282
currentStatus: newStatus,
274283
reachabilityDown: newStatus === "down",
275284
metrics: hardwarePayload.data,
276285
thresholds: {
277-
cpu: updatedMonitor.cpuAlertThreshold,
278-
memory: updatedMonitor.memoryAlertThreshold,
279-
disk: updatedMonitor.diskAlertThreshold,
280-
temp: updatedMonitor.tempAlertThreshold,
286+
cpu: monitor.cpuAlertThreshold,
287+
memory: monitor.memoryAlertThreshold,
288+
disk: monitor.diskAlertThreshold,
289+
temp: monitor.tempAlertThreshold,
281290
},
282291
counters: {
283-
cpu: updatedMonitor.cpuAlertCounter,
284-
memory: updatedMonitor.memoryAlertCounter,
285-
disk: updatedMonitor.diskAlertCounter,
286-
temp: updatedMonitor.tempAlertCounter,
292+
cpu: monitor.cpuAlertCounter,
293+
memory: monitor.memoryAlertCounter,
294+
disk: monitor.diskAlertCounter,
295+
temp: monitor.tempAlertCounter,
287296
},
288297
});
289298

290-
updatedMonitor.cpuAlertCounter = hardware.nextCounters.cpu;
291-
updatedMonitor.memoryAlertCounter = hardware.nextCounters.memory;
292-
updatedMonitor.diskAlertCounter = hardware.nextCounters.disk;
293-
updatedMonitor.tempAlertCounter = hardware.nextCounters.temp;
299+
patch.cpuAlertCounter = hardware.nextCounters.cpu;
300+
patch.memoryAlertCounter = hardware.nextCounters.memory;
301+
patch.diskAlertCounter = hardware.nextCounters.disk;
302+
patch.tempAlertCounter = hardware.nextCounters.temp;
294303
thresholdBreaches = hardware.breaches;
295304
if (hardware.transitioned) {
296305
newStatus = hardware.nextStatus;
297306
statusChanged = true;
298307
}
299308
}
300309

301-
// Apply the final status — only write fields computed by application logic,
302-
// not statusWindow/recentChecks which are owned by the atomic push.
303-
const patch: Partial<Monitor> = { status: newStatus };
304-
if (updatedMonitor.type === "hardware" && hardwarePayload?.data) {
305-
patch.cpuAlertCounter = updatedMonitor.cpuAlertCounter;
306-
patch.memoryAlertCounter = updatedMonitor.memoryAlertCounter;
307-
patch.diskAlertCounter = updatedMonitor.diskAlertCounter;
308-
patch.tempAlertCounter = updatedMonitor.tempAlertCounter;
309-
}
310+
patch.status = newStatus;
310311

311-
const updated = await this.monitorsRepository.updateById(updatedMonitor.id, updatedMonitor.teamId, patch);
312+
// Single atomic write: push arrays + set status/counters
313+
const updated = await this.monitorsRepository.updateStatusWindowAndChecks(
314+
monitor.id,
315+
monitor.teamId,
316+
check.status,
317+
checkSnapshot,
318+
monitor.statusWindowSize,
319+
MAX_RECENT_CHECKS,
320+
patch
321+
);
312322

313323
return {
314324
monitor: updated,

0 commit comments

Comments
 (0)