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:
2026-08-25 07:58:47 +02:00
parent f52879edbb
commit 8a80aae21e
114 changed files with 9293 additions and 284 deletions
@@ -0,0 +1,139 @@
<?php
declare(strict_types=1);
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Log;
use Predis\Client as PredisClient;
class RedisCleanupLegacyPrefixCommand extends Command
{
protected $signature = 'skinbase:redis-cleanup-legacy-prefix
{--dry-run : SCAN and report only}
{--execute : UNLINK matching obsolete keys}
{--max-keys=500 : Cap keys this run}';
protected $description = 'UNLINK obsolete skinbasenova-database-* and skinbasenova_horizon:* keys only';
private const LEGACY_PREFIXES = [
'skinbasenova-database-',
'skinbasenova_horizon:',
];
private const PROTECTED_PREFIXES = [
'skinbase-database-',
'skinbase_horizon:',
];
public function handle(): int
{
$dryRun = (bool) $this->option('dry-run');
$execute = (bool) $this->option('execute');
if ($dryRun === $execute) {
$this->error('Pass exactly one of --dry-run or --execute.');
return self::FAILURE;
}
$maxKeys = max(1, (int) $this->option('max-keys'));
$currentPrefix = (string) config('database.redis.options.prefix');
$horizonPrefix = (string) config('horizon.prefix');
$this->info(sprintf(
'[redis-cleanup-legacy-prefix] current_prefix=%s horizon_prefix=%s max_keys=%d %s',
$currentPrefix,
$horizonPrefix,
$maxKeys,
$dryRun ? 'dry-run' : 'execute'
));
if (in_array($currentPrefix, self::LEGACY_PREFIXES, true) || in_array($horizonPrefix, self::LEGACY_PREFIXES, true)) {
$this->error('Current process still uses a legacy prefix. Aborting.');
return self::FAILURE;
}
$client = $this->unprefixedClient();
$found = [];
$cursor = '0';
do {
[$cursor, $keys] = $client->scan($cursor, ['COUNT' => 200, 'MATCH' => '*']);
foreach ($keys as $key) {
$key = (string) $key;
if ($this->isProtected($key)) {
continue;
}
if (! $this->isLegacy($key)) {
continue;
}
$found[] = $key;
if (count($found) >= $maxKeys) {
$cursor = '0';
break;
}
}
} while ($cursor !== '0' && $cursor !== 0);
$this->info('matched='.count($found));
foreach (array_slice($found, 0, 20) as $key) {
$this->line('key='.$key);
}
if (count($found) > 20) {
$this->line('... truncated listing');
}
$unlinked = 0;
if (! $dryRun && $found !== []) {
foreach (array_chunk($found, 50) as $chunk) {
$unlinked += (int) $client->unlink(...$chunk);
}
}
$this->info('unlinked='.$unlinked);
Log::info('[redis-cleanup-legacy-prefix] completed', [
'matched' => count($found),
'unlinked' => $unlinked,
'dry_run' => $dryRun,
]);
return self::SUCCESS;
}
private function isLegacy(string $key): bool
{
foreach (self::LEGACY_PREFIXES as $prefix) {
if (str_starts_with($key, $prefix)) {
return true;
}
}
return false;
}
private function isProtected(string $key): bool
{
foreach (self::PROTECTED_PREFIXES as $prefix) {
if (str_starts_with($key, $prefix)) {
return true;
}
}
return false;
}
private function unprefixedClient(): PredisClient
{
$redis = config('database.redis.default', []);
return new PredisClient([
'scheme' => 'tcp',
'host' => $redis['host'] ?? '127.0.0.1',
'port' => (int) ($redis['port'] ?? 6379),
'password' => $redis['password'] ?? null,
'database' => (int) ($redis['database'] ?? 0),
]);
}
}