mirror of
https://github.com/ZoneMinder/zoneminder.git
synced 2026-10-04 00:15:20 -04:00
A DateTime lower bound was written as COALESCE(E.EndDateTime, '9999-12-31 23:59:59') >= T1 reading "an event that has not ended yet never ends". EndDateTime is NULL for an event that is still recording, but also for one zmc was killed part way through, and that stays NULL forever - so every abandoned event matched every window from then on. Wrapping the column in a function also meant no index could range over it: with the only MonitorId key being MonitorId alone, every montage review request read all of that monitor's events and filtered them in memory, however narrow the window. Length tells the two cases apart. It is flushed during recording, so a live event's effective end keeps advancing while an abandoned one's is frozen at whatever was recorded. Use the same CASE the SELECT list in ajax/events.php already uses, and bound it below by StartDateTime. The lower bound now emits three conjuncts: E.StartDateTime >= DATE_SUB(T1, INTERVAL 1 DAY) AND (E.EndDateTime IS NULL OR E.EndDateTime >= T1) AND <effective end> >= T1 The floor bounds the scan for a window in the past and drops events abandoned more than a day earlier - events do not outlive the nightly logrotate SIGHUP, which stops and restarts them, so a day is comfortably beyond any real event. The middle conjunct is implied by the third and exists only to give the optimiser a second indexable handle: it is narrow exactly when the floor is wide, so between them a window at either end of the retention period has something cheap to range over. Verified the optimiser picks correctly for both, unprompted. The third is the residual that gets the semantics right. Add the two composite keys those conjuncts need. Two range columns cannot both narrow one B-tree, and these are mirror images - EndDateTime >= T1 is open-ended upwards, StartDateTime <= T2 downwards: Events_MonitorId_StartDateTime_idx (MonitorId, StartDateTime) Events_EndDateTime_MonitorId_idx (EndDateTime, MonitorId) MonitorId leads the first because it is the equality; past a range column later columns can no longer narrow the scan, and (StartDateTime, MonitorId) measured 3.5x slower for the same rows. EndDateTime leads the second so it also covers zmaudit's hunt for events that were never closed, which has no monitor to scope it - 9 rows with a key against a 22,948 row table scan without. Both replacements are added before the keys they supersede are dropped: Events_MonitorId_idx (MonitorId) is now a leftmost prefix of the new key. Events_EndDateTime_DiskSpace (EndDateTime, DiskSpace) existed for the scan that hunted events with no DiskSpace set; DiskSpace is set when the event is finalised in C++, so nothing scans for it any more. Net index count on Events is unchanged. Measured on a 7,167 event monitor. Default one hour window: 7,055 rows examined / 25.4ms -> 173 rows / 4ms. Window scrubbed a week back, which persists in the zmFilter_StartDateTime cookie: 6,995 rows / 28ms -> 19 rows / 0.15ms. Adds migration db/zm_update-1.39.22.sql. Extends t/filter_sql.t; the PHP and Perl were checked to emit identical SQL, and the semantics checked against a table of seven event shapes - abandoned long ago, abandoned recently, overlapping, inside, before, still recording, and still recording for 17 hours.