Allow setting a limit for simultaneous face clustering runs: ClusterFacesJob runs for different users currently stack up until the server runs out of memory
还没有人认领这个 Issue。
评估
- 难度
- 4/5
- 预计耗时
- 3-5 天
- 新手友好度
- 35/100
- Issue 类型
- 功能
- 描述清晰度
- 基本清楚
- 活跃度
- 活跃
- 技术栈
- php
调研方向
Read ClusterFacesJob::run() and the job scheduling paths in TaskResultListener.php and MaintenanceJob.php; the issue names these as the sources of overlapping runs. Check how Nextcloud exposes locks and IJobList::scheduleAfter(), then compare a local lock with a distributed cache lock for multi-server cron. Done means clustering concurrency is configurable and competing jobs defer without blocking cron; the issue provides a local patch and measured single-server results.
由索引模型根据 Issue 内容生成。
描述
Describe the feature you'd like to request
An admin-side limit on how many ClusterFacesJob runs can execute at once, across all users. The default can be super conservative (e.g. one or two).
I'm running Recognize 13.1.0 with recognize_backend 1.0.6 (CUDA), Nextcloud 35.0.2 AIO (in an LXC Container on Proxmox 9.2.21).
All my photos are in one group folder that 39 users can see. Detection runs on an Nvidia T1000 8GB and is fast, but clustering runs in PHP inside cron.php, once per user, and a face detection is stored once for every user with a mount. Here 13,961 detections from 4,506 photos became 544,456 rows, so a batch of new faces can mean up to 39 clustering runs of about 14,000 detections each.
Overnight these runs piled up. Cron starts a new cron.php every 5 minutes, and each one picked up the next user's ClusterFacesJob while the earlier ones were still going. By morning 12 clustering runs were active at 0.4 to 0.7 GB each, the oldest almost 10 hours old, the container was at its 4 GiB memory limit with swap full, and Nextcloud had been dropping out all night. I asked Claude Code to trace it, and this is what it found:
Nothing stops the runs from overlapping:
ClusterFacesJobis aQueuedJob, andQueuedJob::start()deletes the job's row beforerun()is called (QueuedJob.php#L26-L33). Core'ssetAllowParallelRuns(false)wouldn't help either, becauseJobList::getNext()decides that throughhasReservedJob(), which looks for a reserved row of the same class (JobList.php#L203, #L402-L407). A runningClusterFacesJobhas no row left to find.TaskResultListenerqueues aClusterFacesJobfor each user that got a new detection, after every face task result (TaskResultListener.php#L243-L246).MaintenanceJobqueues one for every user returned bygetUsersForUnclustered(), every 12 hours (MaintenanceJob.php#L41-L43). A full run doesn't take a user off that list: one user went from 7,883 detections without a cluster to 5,672, so all 39 users are queued again twice a day even when no new photos arrive.
The classifier jobs already have a guard: ClassifierJob is a TimedJob and disallows parallel runs unless concurrency.enabled is set. Clustering has nothing similar.
The out-of-memory half of #1558, and #1577, are about the size of one run. The fix for #1558 (80ad6a15) derives the batch size from memory_limit and is already in 13.1.0, the version running here. #1577 shows that formula still overflows at higher limits, and mentions that two runs for the same user can overlap. This request is about the number of runs. Even when each run fits in memory_limit, a new run starting every 5 minutes beside the ones still going will use up the server's memory sooner or later.
Describe the solution you'd like
Check a lock at the start of ClusterFacesJob::run(). If another clustering run holds it, put the job back with IJobList::scheduleAfter() a few minutes out and return, instead of starting a second run. A setting such as clusterFaces.concurrency could allow more than one for admins with the memory to spare.
I'm running this as a local patch (made by Claude Code) now. It uses flock on a file in the temp directory, because the kernel drops the lock if the process dies, so an out-of-memory kill can't leave it stuck:
$lockPath = sys_get_temp_dir() . '/recognize-cluster-faces.lock';
$lock = fopen($lockPath, 'c');
if ($lock === false || !flock($lock, LOCK_EX | LOCK_NB)) {
$this->jobList->scheduleAfter(self::class, $this->time->getTime() + 600, $argument);
// log and return
return;
}
try {
// unchanged: batch size, calculateClusters()
} finally {
flock($lock, LOCK_UN);
fclose($lock);
}
With it, only one clustering run is active at a time. Nextcloud's own job run records for the first 90 minutes show 16 clustering runs of 2 to 5.5 minutes each, peaking between 0.7 and 1.8 GB, and never two at once. Before, a run could go on for 10 hours with a dozen of them competing for memory and 4 cores. The other 258 starts in that window found the lock taken and deferred in under a second. Because getNext() skips jobs whose last_checked is in the future, the deferred jobs don't spin the cron loop. A file lock only covers one server, so an instance with cron on several servers would need a lock in the distributed cache instead.
Describe alternatives you've considered
- Make clustering a single
TimedJobthat works through the users needing clustering one by one, instead of oneQueuedJobper user. Then the existingsetAllowParallelRuns(false)would work, and the per-user queueing inTaskResultListenerandMaintenanceJobcould become a flag. - Run
occ recognize:cluster-facesby hand. It clusters users one after another, but the background jobs keep being queued and start alongside it. - Give the server more memory. I raised the container from 4 to 12 GiB to get it back up; with the runs still stacking, memory went from 7.3 to 9.1 GB in four minutes and I had to stop them.
- A smaller batch, as in #1577. It shrinks each run but doesn't limit how many run at once.
🤖 Generated with Claude Code
- 主要语言
- PHP
- 星标
- 700
- 派生
- 70
- 平均合并
- 4 天 15 小时
- 30 天内合并 PR
- 5
环境准备
- 没有 Dockerfile 或 Docker Compose 文件
- 没有 Pull Request 模板
- 阅读贡献指南
从这里开始
- 先读完整个 Issue,再读项目的贡献指南。
- 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
- Fork 仓库,在一个分支上完成修改。
- 提交 Pull Request,并在描述里引用这个 Issue 编号。
nextcloud/recognize 的其他 Issue
-
Store each face detection once per file: in a group folder, every detection (incl. vector) is currently copied to every user who can see it causing the table to grow with the number of users可能已有人在做 @marcelklehr 于 1 天前认领。 未关闭enhancement
难度 5/5 一周以上 新手友好度 35/100
-
bug
难度 4/5 3-5 天 新手友好度 45/100
-
bug
难度 5/5 一周以上 新手友好度 15/100
-
enhancement
难度 3/5 1-2 天 新手友好度 65/100
-
enhancement
难度 4/5 3-5 天 新手友好度 45/100
查看 nextcloud/recognize 的全部 Issue
相似的 Issue
-
GD decoder discards the finfo media type in mediaTypeByBinary() since 4.4.0可能已有人在做 @lazerg 今天认领。 未关闭
难度 2/5 1-3 小时 新手友好度 77/100
Intervention/image#1556 ·
维护者通常 1 天内回复
-
bug No Code Attached Yet
难度 2/5 1-3 小时 新手友好度 79/100
joomla/joomla-cms#48730 ·
维护者通常 1 天内回复
-
sync-en
难度 1/5 1-3 小时 新手友好度 76/100
维护者通常 1 天内回复
-
sync-en
难度 2/5 1-3 小时 新手友好度 72/100
维护者通常 1 天内回复
-
Перевод устарел
难度 1/5 1 小时以内 新手友好度 65/100