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}
|
* @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')) {
|
if (! Schema::hasTable('pargar_sync_logs')) {
|
||||||
return ['mande' => 0, 'document' => 0];
|
return ['mande' => 0, 'document' => 0];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$batch = $maxDeletePerType ?? self::PARAGAR_PRUNE_BATCH;
|
||||||
|
|
||||||
ignore_user_abort(true);
|
ignore_user_abort(true);
|
||||||
if (function_exists('set_time_limit')) {
|
if (function_exists('set_time_limit')) {
|
||||||
@set_time_limit(180);
|
@set_time_limit(180);
|
||||||
@@ -168,8 +173,8 @@ class DatabaseLogPruneService
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
return [
|
return [
|
||||||
'mande' => $this->pruneOldestSuccessBeyond(PargarSyncService::TYPE_MANDE, $maxDeletePerType),
|
'mande' => $this->pruneOldestSuccessBeyond(PargarSyncService::TYPE_MANDE, $batch),
|
||||||
'document' => $this->pruneOldestSuccessBeyond(PargarSyncService::TYPE_DOCUMENT, $maxDeletePerType),
|
'document' => $this->pruneOldestSuccessBeyond(PargarSyncService::TYPE_DOCUMENT, $batch),
|
||||||
];
|
];
|
||||||
} finally {
|
} finally {
|
||||||
if ($locked === true) {
|
if ($locked === true) {
|
||||||
@@ -197,11 +202,11 @@ class DatabaseLogPruneService
|
|||||||
->where('id', '<=', $cutoffId)
|
->where('id', '<=', $cutoffId)
|
||||||
->orderBy('id');
|
->orderBy('id');
|
||||||
|
|
||||||
if ($maxDelete > 0) {
|
if ($maxDelete <= 0) {
|
||||||
return (int) $query->limit($maxDelete)->delete();
|
$maxDelete = self::PARAGAR_PRUNE_BATCH;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (int) $query->delete();
|
return (int) $query->limit($maxDelete)->delete();
|
||||||
}
|
}
|
||||||
|
|
||||||
private function ensureTypeStatusIndex(): void
|
private function ensureTypeStatusIndex(): void
|
||||||
@@ -224,7 +229,7 @@ class DatabaseLogPruneService
|
|||||||
private function acquirePruneLock(): ?bool
|
private function acquirePruneLock(): ?bool
|
||||||
{
|
{
|
||||||
try {
|
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;
|
return (int) ($row->l ?? 0) === 1;
|
||||||
} catch (\Throwable) {
|
} 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`
|
) AS `keep`
|
||||||
);
|
);
|
||||||
|
|
||||||
-- Event داخل خود MySQL — بدون کرون سیستم (Navicat → Events)
|
-- جایگزین: مایگریشن 2026_09_03_000002 (تریگر INSERT + prune یکبار روی migrate).
|
||||||
DROP EVENT IF EXISTS `khanegi_prune_pargar_inbound_docs`;
|
-- EVENT ساعتی دیگر لازم نیست.
|
||||||
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;
|
|
||||||
|
|||||||
Reference in New Issue
Block a user