Files
ulthon_admin/extend/base/tools/controller/timer/XhprofRunCleanBase.php

178 lines
6.5 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
declare(strict_types=1);
namespace base\tools\controller\timer;
use app\common\controller\TimerController;
use think\facade\Db;
use think\facade\Log;
/**
* XHProf 采样数据清理定时任务Base 层).
*
* 清理范围(三表,均按入库时间 create_time 早于 N 天前):
* 1. ul_xhprof_run_watch函数监控关联表按 create_time 分批删除
* 2. ul_xhprof_run_detail采样明细LONGTEXT profile_data
* 该表是豁免表,无 create_time 约定字段——先收集过期 run id
* 再按主键 id INdetail.id = run.id1:1分批删除
* 3. ul_xhprof_run采样主表按 create_time 分批删除
*
* 永不清理:
* - ul_xhprof_log_position导入位置基准删了会全量重读 jsonl
* - ul_xhprof_watch函数监控配置用户手动维护的业务数据
* - xhgui_* 表XHGui 自建表,不属于本框架管理范围
*
* 控制参数(.env [XHPROF] KEEP_DAYS默认 30<= 0 = 永久跳过防误删全表):
* - xhprof.keep_days 采样保留天数
*
* 业务侧如需定制清理逻辑,重写 app/tools/controller/timer/XhprofRunClean.php 对应方法即可拦截。
*/
class XhprofRunCleanBase extends TimerController
{
/**
* 防刷间隔(秒).
* 仅做控制器侧防刷,不影响定时器侧 frequency 节流。
*/
protected $frequency = 600;
/**
* 并发上限 cap单分片id=0。纯 DB 清理任务无需并发分片。
*/
protected $concurrency = 1;
/**
* 分批删除的批量大小(避免单条 DELETE 锁表太久)。
*/
protected const RAW_BATCH_SIZE = 5000;
/**
* 每批 DELETE 之间的微秒休眠(避免主从延迟、降低 DB 压力)。
*/
protected const RAW_BATCH_USLEEP = 100000;
/**
* 执行清理.
*
* @param bool $dryRun true 时只统计将删数量不执行删除(用于验证保留策略)
* @return string 删除计数 json三表各自计数 + dryRun 标记keep_days <= 0 时返回 disabled 标记)
*/
public function do(bool $dryRun = false)
{
// 1. 读保留天数intval 强转:.env 值是字符串,默认值是 int
$keepDays = (int) env('xhprof.keep_days', 30);
// 2. keep_days <= 0 永久跳过(防误删全表),返回 disabled 标记
if ($keepDays <= 0) {
Log::debug('xhprof keep_days <= 0, skip clean');
return json_encode([
'disabled' => true,
'keep_days' => $keepDays,
'dry_run' => $dryRun,
], JSON_UNESCAPED_UNICODE);
}
$threshold = time() - ($keepDays * 86400);
Log::info("xhprof_clean: 阈值 create_time<" . date('Y-m-d H:i:s', $threshold) . " ({$keepDays} 天)" . ($dryRun ? ' [dry-run]' : ''));
// 3. 先收集过期 run id必须在删 run 之前detail 豁免表无 create_time只能经主键关联删
$expiredRunIds = Db::name('xhprof_run')
->where('create_time', '<', $threshold)
->column('id');
// 4. 按依赖顺序清理run_watch → detail → run
$runWatchDeleted = $this->cleanByCreateTime('xhprof_run_watch', $threshold, $dryRun);
$detailDeleted = $this->cleanDetailByRunIds($expiredRunIds, $dryRun);
$runDeleted = $dryRun ? count($expiredRunIds) : $this->cleanByCreateTime('xhprof_run', $threshold, false);
Log::info("xhprof_clean: run_watch {$runWatchDeleted} 行, detail {$detailDeleted} 行, run {$runDeleted}" . ($dryRun ? ' [dry-run]' : ''));
return json_encode([
'run_watch_deleted' => $runWatchDeleted,
'detail_deleted' => $detailDeleted,
'run_deleted' => $runDeleted,
'dry_run' => $dryRun,
], JSON_UNESCAPED_UNICODE);
}
/**
* 按 create_time 分批清理有约定字段的表run_watch / run.
*
* 每批 5000 行 + usleep 100ms避免长事务锁表照抄 nginx 清理模板的批量模式)。
*
* @param string $table 表名(不含前缀,由 Db::name 自动补)
* @param int $threshold 过期阈值create_time 早于该时间戳删除)
* @param bool $dryRun true 时只统计将删数量不执行删除
* @return int 累计删除行数dryRun 为将删行数)
*/
protected function cleanByCreateTime(string $table, int $threshold, bool $dryRun): int
{
$query = Db::name($table)->where('create_time', '<', $threshold);
if ($dryRun) {
return (int) $query->count();
}
$total = 0;
// 循环分批 DELETE每批 limit 5000直到该阈值区间内无残留
while (true) {
$deleted = Db::name($table)
->where('create_time', '<', $threshold)
->limit(self::RAW_BATCH_SIZE)
->delete();
if ($deleted <= 0) {
break;
}
$total += $deleted;
if ($deleted === self::RAW_BATCH_SIZE) {
// 批次间休眠,降低 DB 压力(避免主从延迟)
usleep(self::RAW_BATCH_USLEEP);
} else {
// 末批(< 5000说明已清完该阈值区间
break;
}
}
return $total;
}
/**
* 清理 detail 豁免表(按 run id 主键关联删).
*
* ul_xhprof_run_detail 无 create_timeT1 豁免表设计),无法按时间条件删;
* detail.id = run.id1:1 非自增主键),把过期 run id 分批 IN 删除。
*
* @param array $expiredRunIds 过期的 run 主键列表(删 run 之前收集)
* @param bool $dryRun true 时只统计将删数量不执行删除
* @return int 累计删除行数dryRun 为将删行数)
*/
protected function cleanDetailByRunIds(array $expiredRunIds, bool $dryRun): int
{
if (empty($expiredRunIds)) {
return 0;
}
$total = 0;
// 按 RAW_BATCH_SIZE 分块 IN 删除,避免单条 DELETE ... IN (...) 过大
foreach (array_chunk($expiredRunIds, self::RAW_BATCH_SIZE) as $chunk) {
$query = Db::name('xhprof_run_detail')->whereIn('id', $chunk);
if ($dryRun) {
$total += (int) $query->count();
continue;
}
$total += $query->delete();
usleep(self::RAW_BATCH_USLEEP);
}
return $total;
}
}