mirror of
https://github.com/screentinker/screentinker.git
synced 2026-06-14 18:22:46 -06:00
fix(proof-of-play): throttle play_logs writes to prevent runaway bloat
A player stuck in a tight loop (playlist with 0-second item durations) fires device:play-event 'play_start' ~3x/sec, inserting a play_logs row each time. Three web players doing this generated ~909k rows (99.9% with duration_sec=0) and grew the prod DB to 265 MB. Throttle proof-of-play inserts to at most one per device per 2s (in-memory lastPlayLogAt map). Skipped cycles create no row; the live dashboard progress event still fires every time, so the UI is unaffected. The play_end UPDATE only closes open rows, so throttling play_start is safe. (Junk rows already pruned in prod: 909k deleted, DB 265 MB -> 9.8 MB.) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
cbe00d6c85
commit
890ec5790f
|
|
@ -18,6 +18,15 @@ const commandQueue = require('../lib/command-queue');
|
|||
// next checker sweep within heartbeatInterval).
|
||||
const pendingOfflines = new Map();
|
||||
const OFFLINE_DEBOUNCE_MS = 5000;
|
||||
|
||||
// Proof-of-play write throttle. A player stuck in a tight loop (e.g. a playlist
|
||||
// with 0-second item durations) fires device:play-event 'play_start' several
|
||||
// times per second; unthrottled this once bloated play_logs to ~900k rows
|
||||
// (~3 inserts/sec from a single web player). Cap proof-of-play inserts to at
|
||||
// most one per device per PLAY_LOG_MIN_GAP_MS. The live dashboard progress
|
||||
// event is still forwarded every time, so the UI is unaffected. In-memory only.
|
||||
const lastPlayLogAt = new Map();
|
||||
const PLAY_LOG_MIN_GAP_MS = 2000;
|
||||
const { getUserPlan, getUserDeviceCount } = require('../middleware/subscription');
|
||||
// Phase 2.3: deviceRoom() resolves a device_id to its workspace room so
|
||||
// dashboardNs.emit can be scoped instead of broadcast platform-wide.
|
||||
|
|
@ -517,10 +526,18 @@ module.exports = function setupDeviceSocket(io) {
|
|||
if (device_id !== currentDeviceId) return;
|
||||
try {
|
||||
if (event === 'play_start') {
|
||||
db.prepare(`
|
||||
INSERT INTO play_logs (device_id, content_id, zone_id, content_name, started_at, trigger_type)
|
||||
VALUES (?, ?, ?, ?, strftime('%s','now'), 'playlist')
|
||||
`).run(device_id, content_id || null, zone_id || null, content_name || 'Unknown');
|
||||
// Throttle proof-of-play inserts per device so a runaway player
|
||||
// (0-second items) can't flood play_logs. Skipped cycles simply
|
||||
// don't create a row; the dashboard progress event below still fires.
|
||||
const nowMs = Date.now();
|
||||
const lastMs = lastPlayLogAt.get(device_id) || 0;
|
||||
if (nowMs - lastMs >= PLAY_LOG_MIN_GAP_MS) {
|
||||
lastPlayLogAt.set(device_id, nowMs);
|
||||
db.prepare(`
|
||||
INSERT INTO play_logs (device_id, content_id, zone_id, content_name, started_at, trigger_type)
|
||||
VALUES (?, ?, ?, ?, strftime('%s','now'), 'playlist')
|
||||
`).run(device_id, content_id || null, zone_id || null, content_name || 'Unknown');
|
||||
}
|
||||
// Forward to dashboard so it can render a per-device progress bar.
|
||||
// Server-side timestamp avoids clock-skew between player and dashboard.
|
||||
emitToDeviceWorkspace(dashboardNs, device_id, 'dashboard:playback-progress', {
|
||||
|
|
|
|||
Loading…
Reference in a new issue