feat(pargar): prune inbound logs via MySQL INSERT trigger
Run one-time overflow cleanup on migrate and keep mande/document success rows capped at 2000 with an AFTER INSERT trigger, without cron or manual SQL. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -148,12 +148,17 @@ class DatabaseLogPruneService
|
||||
*
|
||||
* @return array{mande: int, document: int}
|
||||
*/
|
||||
public function prunePargarInboundOverflow(int $maxDeletePerType = 0): array
|
||||
/** هر بار حداکثر — برای afterResponse؛ تریگر MySQL هم LIMIT 4000 دارد */
|
||||
private const PARAGAR_PRUNE_BATCH = 4000;
|
||||
|
||||
public function prunePargarInboundOverflow(?int $maxDeletePerType = null): array
|
||||
{
|
||||
if (! Schema::hasTable('pargar_sync_logs')) {
|
||||
return ['mande' => 0, 'document' => 0];
|
||||
}
|
||||
|
||||
$batch = $maxDeletePerType ?? self::PARAGAR_PRUNE_BATCH;
|
||||
|
||||
ignore_user_abort(true);
|
||||
if (function_exists('set_time_limit')) {
|
||||
@set_time_limit(180);
|
||||
@@ -168,8 +173,8 @@ class DatabaseLogPruneService
|
||||
|
||||
try {
|
||||
return [
|
||||
'mande' => $this->pruneOldestSuccessBeyond(PargarSyncService::TYPE_MANDE, $maxDeletePerType),
|
||||
'document' => $this->pruneOldestSuccessBeyond(PargarSyncService::TYPE_DOCUMENT, $maxDeletePerType),
|
||||
'mande' => $this->pruneOldestSuccessBeyond(PargarSyncService::TYPE_MANDE, $batch),
|
||||
'document' => $this->pruneOldestSuccessBeyond(PargarSyncService::TYPE_DOCUMENT, $batch),
|
||||
];
|
||||
} finally {
|
||||
if ($locked === true) {
|
||||
@@ -197,11 +202,11 @@ class DatabaseLogPruneService
|
||||
->where('id', '<=', $cutoffId)
|
||||
->orderBy('id');
|
||||
|
||||
if ($maxDelete > 0) {
|
||||
return (int) $query->limit($maxDelete)->delete();
|
||||
if ($maxDelete <= 0) {
|
||||
$maxDelete = self::PARAGAR_PRUNE_BATCH;
|
||||
}
|
||||
|
||||
return (int) $query->delete();
|
||||
return (int) $query->limit($maxDelete)->delete();
|
||||
}
|
||||
|
||||
private function ensureTypeStatusIndex(): void
|
||||
@@ -224,7 +229,7 @@ class DatabaseLogPruneService
|
||||
private function acquirePruneLock(): ?bool
|
||||
{
|
||||
try {
|
||||
$row = DB::selectOne('SELECT GET_LOCK(?, 0) AS l', ['khanegi_pargar_inbound_prune']);
|
||||
$row = DB::selectOne('SELECT GET_LOCK(?, 3) AS l', ['khanegi_pargar_inbound_prune']);
|
||||
|
||||
return (int) ($row->l ?? 0) === 1;
|
||||
} catch (\Throwable) {
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
/**
|
||||
* تریگر MySQL روی INSERT — بدون artisan/cron/SQL دستی.
|
||||
* migrate یکبار: انباشت قدیمی mande/document را میبُرد + تریگر برای نگهداری ۲۰۰۰ تازه.
|
||||
*/
|
||||
return new class extends Migration
|
||||
{
|
||||
private const KEEP = 2000;
|
||||
|
||||
private const TRIGGER = 'pargar_sync_logs_prune_inbound_after_insert';
|
||||
|
||||
public function up(): void
|
||||
{
|
||||
if (! Schema::hasTable('pargar_sync_logs')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->dropHourlyEvents();
|
||||
$this->dropTrigger();
|
||||
$this->pruneExistingOverflow('document');
|
||||
$this->pruneExistingOverflow('mande');
|
||||
$this->createInsertTrigger();
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
$this->dropTrigger();
|
||||
}
|
||||
|
||||
private function dropHourlyEvents(): void
|
||||
{
|
||||
try {
|
||||
DB::unprepared('DROP EVENT IF EXISTS `khanegi_prune_pargar_inbound_docs`');
|
||||
DB::unprepared('DROP EVENT IF EXISTS `khanegi_prune_pargar_inbound_mande`');
|
||||
} catch (\Throwable) {
|
||||
}
|
||||
}
|
||||
|
||||
private function dropTrigger(): void
|
||||
{
|
||||
try {
|
||||
DB::unprepared('DROP TRIGGER IF EXISTS `' . self::TRIGGER . '`');
|
||||
} catch (\Throwable) {
|
||||
}
|
||||
}
|
||||
|
||||
private function pruneExistingOverflow(string $type): void
|
||||
{
|
||||
$keep = self::KEEP;
|
||||
|
||||
try {
|
||||
DB::unprepared(<<<SQL
|
||||
DELETE FROM `pargar_sync_logs`
|
||||
WHERE `type` = '{$type}' AND `sync_status` = 'success'
|
||||
AND `id` <= (
|
||||
SELECT `cutoff` FROM (
|
||||
SELECT `id` AS `cutoff` FROM `pargar_sync_logs`
|
||||
WHERE `type` = '{$type}' AND `sync_status` = 'success'
|
||||
ORDER BY `id` DESC
|
||||
LIMIT 1 OFFSET {$keep}
|
||||
) AS `keep`
|
||||
)
|
||||
SQL);
|
||||
} catch (\Throwable) {
|
||||
// اگر overflow نیست یا دسترسی محدود است، تریگر بعداً نگه میدارد
|
||||
}
|
||||
}
|
||||
|
||||
private function createInsertTrigger(): void
|
||||
{
|
||||
$keep = self::KEEP;
|
||||
$trigger = self::TRIGGER;
|
||||
|
||||
DB::unprepared(<<<SQL
|
||||
CREATE TRIGGER `{$trigger}`
|
||||
AFTER INSERT ON `pargar_sync_logs`
|
||||
FOR EACH ROW
|
||||
BEGIN
|
||||
IF NEW.`type` = 'document' AND NEW.`sync_status` = 'success' THEN
|
||||
DELETE FROM `pargar_sync_logs`
|
||||
WHERE `type` = 'document' AND `sync_status` = 'success'
|
||||
AND `id` <= (
|
||||
SELECT `cutoff` FROM (
|
||||
SELECT `id` AS `cutoff` FROM `pargar_sync_logs`
|
||||
WHERE `type` = 'document' AND `sync_status` = 'success'
|
||||
ORDER BY `id` DESC
|
||||
LIMIT 1 OFFSET {$keep}
|
||||
) AS `keep`
|
||||
)
|
||||
LIMIT 4000;
|
||||
ELSEIF NEW.`type` = 'mande' AND NEW.`sync_status` = 'success' THEN
|
||||
DELETE FROM `pargar_sync_logs`
|
||||
WHERE `type` = 'mande' AND `sync_status` = 'success'
|
||||
AND `id` <= (
|
||||
SELECT `cutoff` FROM (
|
||||
SELECT `id` AS `cutoff` FROM `pargar_sync_logs`
|
||||
WHERE `type` = 'mande' AND `sync_status` = 'success'
|
||||
ORDER BY `id` DESC
|
||||
LIMIT 1 OFFSET {$keep}
|
||||
) AS `keep`
|
||||
)
|
||||
LIMIT 4000;
|
||||
END IF;
|
||||
END
|
||||
SQL);
|
||||
}
|
||||
};
|
||||
@@ -41,47 +41,5 @@ WHERE `type` = 'mande' AND `sync_status` = 'success'
|
||||
) AS `keep`
|
||||
);
|
||||
|
||||
-- Event داخل خود MySQL — بدون کرون سیستم (Navicat → Events)
|
||||
DROP EVENT IF EXISTS `khanegi_prune_pargar_inbound_docs`;
|
||||
DROP EVENT IF EXISTS `khanegi_prune_pargar_inbound_mande`;
|
||||
|
||||
CREATE EVENT `khanegi_prune_pargar_inbound_docs`
|
||||
ON SCHEDULE EVERY 1 HOUR
|
||||
STARTS CURRENT_TIMESTAMP
|
||||
ON COMPLETION PRESERVE
|
||||
ENABLE
|
||||
DO
|
||||
DELETE FROM `pargar_sync_logs`
|
||||
WHERE `type` = 'document' AND `sync_status` = 'success'
|
||||
AND `id` <= (
|
||||
SELECT `cutoff` FROM (
|
||||
SELECT `id` AS `cutoff` FROM `pargar_sync_logs`
|
||||
WHERE `type` = 'document' AND `sync_status` = 'success'
|
||||
ORDER BY `id` DESC
|
||||
LIMIT 1 OFFSET 2000
|
||||
) AS `keep`
|
||||
)
|
||||
LIMIT 4000;
|
||||
|
||||
CREATE EVENT `khanegi_prune_pargar_inbound_mande`
|
||||
ON SCHEDULE EVERY 1 HOUR
|
||||
STARTS CURRENT_TIMESTAMP
|
||||
ON COMPLETION PRESERVE
|
||||
ENABLE
|
||||
DO
|
||||
DELETE FROM `pargar_sync_logs`
|
||||
WHERE `type` = 'mande' AND `sync_status` = 'success'
|
||||
AND `id` <= (
|
||||
SELECT `cutoff` FROM (
|
||||
SELECT `id` AS `cutoff` FROM `pargar_sync_logs`
|
||||
WHERE `type` = 'mande' AND `sync_status` = 'success'
|
||||
ORDER BY `id` DESC
|
||||
LIMIT 1 OFFSET 2000
|
||||
) AS `keep`
|
||||
)
|
||||
LIMIT 4000;
|
||||
|
||||
-- اگر دسترسی SUPER داری:
|
||||
-- SET GLOBAL event_scheduler = ON;
|
||||
-- SHOW VARIABLES LIKE 'event_scheduler';
|
||||
-- SHOW EVENTS;
|
||||
-- جایگزین: مایگریشن 2026_09_03_000002 (تریگر INSERT + prune یکبار روی migrate).
|
||||
-- EVENT ساعتی دیگر لازم نیست.
|
||||
|
||||
Reference in New Issue
Block a user