= $timeLimitSeconds) { break; } $result = Redis::sscan(OnlineVisitorRepository::INDEX_KEY, $cursor, ['count' => $scanCount]); if (! is_array($result) || count($result) < 2) { break; } $cursor = (int) $result[0]; $members = $result[1]; $batches++; if ($members === []) { if ($cursor === 0) { break; } continue; } $recordKeys = []; foreach ($members as $member) { $member = (string) $member; $recordKeys[$member] = OnlineVisitorRepository::KEY_PREFIX.':'.$member; } $exists = Redis::pipeline(function ($pipe) use ($recordKeys): void { foreach ($recordKeys as $recordKey) { $pipe->exists($recordKey); } }); if (! is_array($exists)) { $exists = []; } $i = 0; foreach ($recordKeys as $member => $recordKey) { $scanned++; $isLive = (int) ($exists[$i] ?? 0) === 1; $i++; if ($isLive) { $live++; continue; } $stale++; if ($dryRun) { continue; } $removed += (int) Redis::eval( self::SREM_IF_EXPIRED, 2, $recordKey, OnlineVisitorRepository::INDEX_KEY, $member, ); } if ($sleepMs > 0) { usleep($sleepMs * 1000); } if ($cursor === 0) { break; } } return [ 'scanned' => $scanned, 'stale' => $stale, 'removed' => $removed, 'live' => $live, 'batches' => $batches, 'dry_run' => $dryRun, ]; } }