首页
/ Coolify 的 Horizon 指标与快照:让 Metrics 面板不空白的 horizon:snapshot 与 trim_snapshots 配置

Coolify 的 Horizon 指标与快照:让 Metrics 面板不空白的 horizon:snapshot 与 trim_snapshots 配置

2026-09-05 17:09:44作者:温玫谨Lighthearted

本文以 Coolify 仓库中关于 Laravel Horizon 指标(Metrics & Snapshots)的参考文档为主体,讲清楚三件事:为什么 Horizon 的指标图表默认是空白的、horizon:snapshot 必须通过 Laravel Scheduler 注册而非手动执行、以及 config/horizon.phpmetrics.trim_snapshots 的真实语义(快照个数而非时间长度)。读完本文,你可以直接在 Coolify 或任何 Horizon 项目中定位指标面板空白的根因、复用 Coolify 的调度注册写法,并按需调整指标历史保留时长与 Redis 内存开销之间的平衡。

一、指标图表为空是"设计使然":Horizon 不会自动产出指标

参考文档给出的第一个关键事实是:运行 horizon artisan 命令本身并不会自动填充指标数据。Horizon 的 metrics 图表并不是由 worker 在消费任务时实时写入的,而是完全由快照(snapshot)构建的——只有 horizon:snapshot 命令执行后,才会生成一条用于绘图的数据点。

这意味着一个常见误区:只要 php artisan horizon 起来了,dashboard 上就"应该"有曲线。实际上如果调度器从未触发 horizon:snapshot,Metrics 面板会一直保持空白,且不会报任何错误。

Coolify 项目完整集成了 Horizon(composer.json 中依赖 laravel/horizon: ^5.48.2),其生产镜像通过 s6-overlay 服务启动 Horizon:docker/production/etc/s6-overlay/s6-rc.d/horizon/run 中最终执行 exec php artisan horizon。可以注意到,这个脚本只负责"启动 Horizon 主进程",整个仓库里没有任何地方在进程启动时顺手做快照——这正是文档强调"metrics 与 horizon 进程解耦"的实际体现:指标数据是调度侧的职责,不是 worker 侧的副作用

二、正确做法:把 horizon:snapshot 注册进 Scheduler,而不是手动跑

文档第二个要点:手动执行一次 horizon:snapshot 只会让 dashboard 短暂出现一个数据点,随后立刻"过期",因为它不会持续更新。正确做法是把快照命令注册进 Laravel 调度器,每 5 分钟执行一次,与 trim_snapshots 的计数配合形成连续的时间序列。

Coolify 仓库中恰好给出了该命令的真实注册位置——应用 Console Kernelschedule(Schedule $schedule) 方法,并且按环境区分了两种频率:

if (isDev()) {
    // 开发环境:每分钟一次,便于调试时快速看到曲线变化
    $this->scheduleInstance->command('horizon:snapshot')->everyMinute();
    // ...
} else {
    // 生产环境:标准做法,每 5 分钟一次
    $this->scheduleInstance->command('horizon:snapshot')->everyFiveMinutes();
    // ...
}

两个细节值得注意:

  1. 调度器必须真正在运行。在 Coolify 的容器化部署里,schedule:work 由独立的 s6 服务进程承担,见 scheduler-worker 服务脚本(开发环境见 docker/development 下的同名脚本),其最终执行 php artisan schedule:work。如果只启动了 horizon 服务而漏掉 scheduler-workerhorizon:snapshot 永远不会触发,症状就是"进程都活着,但指标图是空的"——排查时应优先确认调度器进程存在,而不是怀疑 Horizon 本身。
  2. 参考文档还提醒:调度注册的写法在 Laravel 10 与 11+ 之间存在差异。上例中 Coolify 采用的是新版写法(schedule(Schedule $schedule) 直接接收 Schedule 实例)。如果你要在旧版 Laravel 10 的项目里复制这段逻辑,请对照对应版本的调度器文档确认注册语法,不要跨版本盲抄。

补充一个 Coolify 特有的开关:config/constants.php 中定义了 is_horizon_enabled => env('HORIZON_ENABLED', true)config/constants.php),而生产 s6 脚本会在启动前检查 .env 是否包含 HORIZON_ENABLED=false,若是则让服务睡眠不启动 Horizon。也就是说在 Coolify 中,"指标图空白"还有一种前置原因:Horizon 整体被环境变量关掉了。

三、trim_snapshots 是"快照个数",不是"时长"——保留历史的标准算法

文档第三个要点,也是最容易误读的参数:config/horizon.phpmetrics.trim_snapshots 下的 jobqueue 值是要保留的快照数量,而不是分钟数或小时数。

Coolify 仓库的实际配置位于 config/horizon.php 的 Metrics 段落

'metrics' => [
    'trim_snapshots' => [
        'job' => 24,    // 保留 24 个 job 维度快照
        'queue' => 24, // 保留 24 个 queue 维度快照
    ],
],

其注释原文也点明了语义:这个配置"将和 horizon:snapshot 的调度一起使用,决定指标保留多久"。由此可以推出保留历史的标准计算公式:

历史时长 = 快照个数 × 快照间隔
       = 24 个 × 5 分钟
       = 2 小时

即 Coolify 默认配置下,Metrics 面板上的 job/queue 曲线最多展示最近 2 小时的数据。要延长曲线跨度,唯一正确的方式是调大 trim_snapshots 的数值(例如 48 个 → 4 小时),而不是去改 horizon:snapshot 之外的任何"时间"参数。

代价是 Redis 内存:每个快照都会写入 Horizon 使用的 Redis 连接(Coolify 中为 'use' => 'default',数据键统一带前缀,见 config/horizon.phpprefix 配置,默认形如 {APP_NAME}_horizon:)。保留的快照越多,累积的 key 越多,因此调大该值前应在 Redis 容量上留有余量。文档的结论可以概括为一条权衡规则:在 Redis 内存可接受的前提下增大 trim_snapshots,换取更长的指标历史;反之维持 24 的默认值以节省内存。

顺带一提,同一份配置中的 waits 段('redis:default' => 60config/horizon.php)定义的是队列等待阈值(秒),与快照保留无关,注意不要混淆这两个"时间型"参数。

四、Coolify 的配套:s6 监督器配置如何影响指标曲线的内容

理解 trim_snapshots 的作用对象(job 与 queue 两个维度)后,再结合 Coolify 的监督器(supervisor)配置,就能读懂指标图里"曲线"的来源。Coolify 在 config/horizon.php 的 defaults 段 只定义了一个名为 s6 的监督器:

's6' => [
    'connection' => 'redis',
    'balance' => env('HORIZON_BALANCE', 'false'),
    'queue' => env('HORIZON_QUEUES', 'high,default'),
    'maxTime' => env('HORIZON_MAX_TIME', 0),
    'maxJobs' => 400,
    'memory' => 128,
    'tries' => 1,
    'nice' => 0,
    'sleep' => 3,
    'timeout' => min(
        max((int) env('HORIZON_TIMEOUT', 39600), ScheduledVolumeBackup::DEFAULT_TIMEOUT + 600),
        85800,
    ),
],

其中 environments 段为 production/local 两个环境补充了自动扩缩参数(autoScalingStrategy => 'size'minProcesses/maxProcesses 默认 1~4、balanceMaxShiftbalanceCooldown 默认均为 1)。从源码结构看,队列维度默认覆盖 highdefault 两条队列,可用环境变量 HORIZON_QUEUES 调整——当你新增自定义队列后,需要确认它被纳入了某个监督器,否则该队列的任务不会出现在 queue 维度的指标快照中(快照只统计被 Horizon 监督的工作负载)。

五、排查清单:Metrics 面板空白时的检查顺序

综合参考文档与 Coolify 仓库的实际实现,按以下顺序排查可以覆盖绝大多数"指标不显示"场景:

  1. horizon:snapshot 是否已注册进调度器:在 schedule() 方法中搜索 horizon:snapshot(Coolify 位于 app/Console/Kernel.php#L72),且间隔应 ≤ 5 分钟;
  2. 调度器进程是否存活:Coolify 容器化部署中即 scheduler-worker 服务(php artisan schedule:work),手动执行快照无法替代它;
  3. 间隔与保留数是否匹配:快照间隔 × trim_snapshots 个数 = 可见历史长度(默认 24 × 5 分钟 = 2 小时);
  4. Horizon 是否被整体关闭:检查 .envHORIZON_ENABLED=falseconfig/constants.php 的默认值;
  5. Redis 容量:调大 trim_snapshots 前先评估 prefix 命名空间下的键增长。

最后需要强调文档反复提示的一条限制:metrics.trim_snapshots 的语义是计数,不是时长;任何把它当"保留分钟数"来调的写法都是错误的,这也是本文与参考文档最想让读者记住的一点。

登录后查看全文
热门项目推荐
相关项目推荐