Ship production optimization M1-M12.5A: queues, metrics, HTTP observability, and vector search reliability.
Keep similar-ai from tripping the global circuit on a lone URL 502, clamp Qdrant search to 100, and add Server-Timing plus slow-request logging. Studio shared props, Academy S3 exists caching, heat chunking, and Redis/scheduler hygiene stay in this rollout.
This commit is contained in:
@@ -0,0 +1,116 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Services\Traffic;
|
||||
|
||||
use Illuminate\Support\Facades\Redis;
|
||||
|
||||
final class PresenceIndexPruner
|
||||
{
|
||||
private const SREM_IF_EXPIRED = <<<'LUA'
|
||||
if redis.call('EXISTS', KEYS[1]) == 0 then
|
||||
return redis.call('SREM', KEYS[2], ARGV[1])
|
||||
end
|
||||
return 0
|
||||
LUA;
|
||||
|
||||
/**
|
||||
* @return array{scanned:int,stale:int,removed:int,live:int,batches:int,dry_run:bool}
|
||||
*/
|
||||
public function prune(
|
||||
int $scanCount,
|
||||
int $maxBatches,
|
||||
int $maxMembers,
|
||||
int $sleepMs,
|
||||
bool $dryRun,
|
||||
?int $timeLimitSeconds = null,
|
||||
): array {
|
||||
$scanCount = max(10, $scanCount);
|
||||
$maxBatches = max(1, $maxBatches);
|
||||
$maxMembers = max(1, $maxMembers);
|
||||
$started = microtime(true);
|
||||
$cursor = 0;
|
||||
$scanned = 0;
|
||||
$stale = 0;
|
||||
$removed = 0;
|
||||
$live = 0;
|
||||
$batches = 0;
|
||||
|
||||
while ($batches < $maxBatches && $scanned < $maxMembers) {
|
||||
if ($timeLimitSeconds !== null && (microtime(true) - $started) >= $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,
|
||||
];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user