Files
zoneminder/web/api/app/Model/Event_Summary.php
Ben Dailey a87e2d03a5 feat: replace Event_Summaries view with SWR snapshot table
Replace the Event_Summaries view with a physical snapshot table
refreshed via Stale-While-Revalidate (SWR) pattern. A stored
procedure (Refresh_Summaries_SWR) uses GET_LOCK for non-blocking
concurrency and atomic table rename for zero-downtime refresh.

Database changes (db/views.sql):
- Rename Event_Summaries view to VIEW_Event_Summaries (source view)
- Add Event_Summaries snapshot table and Event_Summaries_Metadata table
- Add Refresh_Summaries_SWR stored procedure with GET_LOCK and
  atomic rename pattern to prevent thundering herd
- Add MySQL EVENT for background refresh every 600 seconds

PHP call sites (web/):
- Add ensureSummariesFresh() helper in database.php with static
  per-request dedup and 60s staleness check
- Call ensureSummariesFresh() before Event_Summaries queries in
  console.php, _monitor_filters.php, and Monitor.php
- Add beforeFind() hook in CakePHP Event_Summary model

Perl call sites (scripts/):
- Add ensureSummariesFresh() sub in Event_Summary.pm with
  per-process 60s rate-limiting
- Call ensureSummariesFresh() in Monitor.pm Event_Summary accessor

Upgrade path (db/zm_update-1.37.78.sql.in):
- Drop any prior Event_Summaries view or table before recreating

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-04-07 12:53:42 -04:00

67 lines
1.3 KiB
PHP

<?php
App::uses('AppModel', 'Model');
/**
* Event_Summary Model
*
*/
class Event_Summary extends AppModel {
/**
* Use table
*
* @var mixed False or table name
*/
public $useTable = 'Event_Summaries';
/**
* Primary key field
*
* @var string
*/
public $primaryKey = 'MonitorId';
/**
* Display field
*
* @var string
*/
public $displayField = 'MonitorId';
public $recursive = -1;
/**
* Validation rules
*
* @var array
*/
public $validate = array(
'MonitorId' => array(
'numeric' => array(
'rule' => array('numeric'),
//'message' => 'Your custom message here',
//'allowEmpty' => false,
//'required' => false,
//'last' => false, // Stop validation after this rule
//'on' => 'create', // Limit validation to 'create' or 'update' operations
),
),
);
public function beforeFind($queryData) {
$db = $this->getDataSource();
$result = $db->fetchAll(
"SELECT last_updated FROM Event_Summaries_Metadata WHERE table_name='Event_Summaries'"
);
if ($result) {
$row = $result[0];
$last_updated = isset($row['Event_Summaries_Metadata'])
? $row['Event_Summaries_Metadata']['last_updated']
: (isset($row[0]) ? $row[0]['last_updated'] : null);
if ($last_updated && (time() - strtotime($last_updated)) >= 60) {
$db->rawQuery("CALL Refresh_Summaries_SWR()");
}
}
return $queryData;
}
}