presseportale/app/Models/AppSetting.php
Kevin Adametz 6a34ab48ee technik: Aufrufe über ein Ereignis-Log zählen statt im Request
Bisher lief pro Seitenaufruf ein UPDATE press_releases SET hits = hits + 1 im
Request. Auf der über 1 GB großen Tabelle blockiert dieser Schreibzugriff,
sobald dort eine Schemaänderung läuft — die wartenden Requests belegten dabei
PHP-FPM-Worker und DB-Verbindungen, bis die ganze Seite stand.

Der Request schreibt jetzt nur noch in die kleine Append-Tabelle
press_release_events, und selbst dabei wird jeder Fehler geschluckt und
gemeldet: Ein Aufruf-Zähler darf eine Seite niemals langsamer oder kaputt
machen. Die Summierung in press_releases.hits übernimmt gebündelt der
Scheduler.

Der Fortschritt läuft über eine Wasserstandsmarke (höchste bereits verbuchte
Event-ID) statt über einen Zwischenspeicher, damit bei einem Neustart kein
Aufruf verloren geht. Zähler und Marke werden je Durchgang gemeinsam
committet — sonst wäre nur der Durchgang wiederanlaufsicher, nicht der Abbruch
darin, und der nächste Lauf verbuchte dieselben Aufrufe doppelt. Eine Migration
setzt die Marke beim Deploy auf den Ist-Stand; ohne sie würde der erste Lauf
den gesamten Altbestand ein zweites Mal aufaddieren.

Weil das Log damit mit dem Traffic wächst und als einzige Log-Tabelle des
Systems keine Bereinigung hatte, begrenzt press-releases:purge-events es auf
einen Aufbewahrungszeitraum (Default 395 Tage = 13 Monate, damit der
Vorjahresmonats-Vergleich möglich bleibt). Gelöscht wird ausschließlich
unterhalb der Wasserstandsmarke — alles darüber ist noch nicht in hits verbucht
und wäre sonst dauerhaft verloren. Fehlt die Marke, bricht der Lauf ab, statt
zu raten.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-01 11:05:28 +00:00

59 lines
1.6 KiB
PHP

<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Cache;
/**
* Laufzeit-Einstellungen mit Admin-UI (Key-Value, JSON-Wert, gecacht).
* Erster Nutzer: Schalter „KI-Prüfung Legacy-API" (10.07.2026).
*/
class AppSetting extends Model
{
protected $primaryKey = 'key';
protected $keyType = 'string';
public $incrementing = false;
protected $fillable = ['key', 'value'];
/**
* @return array<string, string>
*/
protected function casts(): array
{
return ['value' => 'json'];
}
public static function getValue(string $key, mixed $default = null): mixed
{
return Cache::rememberForever(
"app-setting.{$key}",
fn (): mixed => static::query()->find($key)?->value,
) ?? $default;
}
public static function getBool(string $key, bool $default = false): bool
{
return (bool) static::getValue($key, $default);
}
public static function setValue(string $key, mixed $value): void
{
static::query()->updateOrCreate(['key' => $key], ['value' => $value]);
static::forgetCached($key);
}
/**
* Verwirft den zwischengespeicherten Wert. Nötig überall dort, wo an
* `app_settings` vorbei geschrieben wird — insbesondere in Migrationen,
* die den Query Builder benutzen: `getValue()` cacht dauerhaft
* (`rememberForever`), ein Direktschreiber bliebe sonst unsichtbar.
*/
public static function forgetCached(string $key): void
{
Cache::forget("app-setting.{$key}");
}
}