[Bug]: Thumbnail generation throughput collapses at ~125k images (5–8 img/s, idle CPU/share) vs ~30 img/s at 3.9k on the same NAS #184
Labels
No labels
Kind/Bug
Kind/Feature
Priority/High
Priority/Medium
Reviewed/Confirmed
Compat/Breaking
Kind/Bug
Kind/Documentation
Kind/Enhancement
Kind/Feature
Kind/Security
Kind/Testing
Priority
Critical
Priority
High
Priority
Low
Priority
Medium
Reviewed
Confirmed
Reviewed
Duplicate
Reviewed
Invalid
Reviewed
Won't Fix
Status
Abandoned
Status
Blocked
Status
Need More Info
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
ai-collab/bulk-image-organizer#184
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Affected area
Directory-open Stage 3 — Generating Thumbnails (
scan_metadata_ordered_and_upsertincore/image_scanner.py). Live scan, not the separate missing-thumbnail catch-up worker.App / environment
0.9.1+bug.176.178 Beta(Windows 11)Report
Opening a ~125,000-image workspace on the NAS, thumbnail generation runs at 5–8 images/s with occasional hangs. The NAS only shows occasional ~100 Mbps read spikes. CPU and RAM are not the limit.
Switching to a ~3,900-image directory on the same share, the same stage runs at ~30 images/s and pulls 200–300 Mbps. That rules out the NIC, NAS, and host as the bottleneck: the same decode/network path is fine until workspace size grows.
At 5 img/s, 125k remaining thumbnails is on the order of 7 hours. At the 3.9k rate it would be ~70 minutes. The gap is bookkeeping that scales with workspace size, not image I/O.
Related but not the same bug:
busy_timeout/ connection churn. Item 1 (tag-write stall) and item 3 (reuse one pending-queue connection) are on this build. Item 2 (Pillow GIL) was deferred and assumed CPU-bound decode; observed CPU is 6%, so GIL is not what is starving throughput here.Steps to reproduce
0.9.1+bug.176.178 Beta.Expected
Actual
Code notes (not a measured profile — matches the A/B)
scan_metadata_ordered_and_upsert()(DR-014 / DR-030) keeps the re-query + re-sort remaining pending rows every dispatch chunk so a mid-scan Sort By change reorders leftover work. Chunk size isthread_count(default 2).Each outer-loop iteration does:
ctx.get_discovery_paths()— copy the entire 125k path set.get_images_pending_enrichment_as_dicts()— fullimagesscan withLEFT JOIN image_tags/tags,GROUP BY i.id, andSELECT … i.thumbnail_blobonly to sethas_thumbnail. NoLIMIT. No pending-condition index. On the 176/178 branch this reuses one read connection ( #178 item 3 ) but still returns every remaining pending row.discovery_pathsin Python.sort_image_dicts()over all remaining pending dicts.enrich_image_record()jobs, emit progress per image,on_batchper chunk.That is O(n² log n) queue work for n pending images. At ~4k the pending query/sort is cheap and decode/network dominate (~30 img/s). At 125k the bookkeeping dominates: worker threads sit idle, CPU stays ~6%, the share barely moves, and a 125k-row query+sort looks like a hang.
Secondary costs on the same loop (not the 3.9k vs 125k smoking gun, but they add):
upsert_imagesflush (batch_size=64).upsert_images+apply_thumbnail_batch) into a 125k-row model._share_sync_timer); a growing 125k-thumbnail cache file can stall the UI/writer if a sync overlaps generation. User-reported share activity was reads, so this is a hang candidate, not the steady 5–8 img/s.DR-029 already found that a 300-row pending query was only ~2.24 ms/call. That does not stay cheap at 125k, and it is paid per 2 images, not once.
Suggested fix direction
Keep DR-014 (enrich in current UI sort order; reorder when Sort By / Sort Order changes). Drop the “re-read and re-sort the entire pending set every chunk” implementation:
thumbnail_blob IS NOT NULLas a flag, no BLOB payload, no tag JOIN unless the active sort is tag-priority).thread_countrows. Do not re-query SQLite to learn what is left.MetadataScanContextsort fields change (dirty flag), not every chunk.width/height/thumbnail_blobIS NULL).PREF_THUMBNAIL_THREADS/ process-pool ( #178 item 2 ). This report is idle-CPU queue work, not decode saturation.Acceptance criteria