@@ -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