changes to task scheduling and readability change on downloads view

This commit is contained in:
Bryan Van Deusen
2026-01-29 19:33:04 -05:00
parent 575e20f58c
commit fcccfb24ee
18 changed files with 527 additions and 72 deletions
+66 -13
View File
@@ -1,6 +1,6 @@
# GallerySubscriber - Project Summary
**Updated:** 2026-01-28 (settings integration, orphan cleanup, error categorization fixes)
**Updated:** 2026-01-29 (task routing, scheduler/worker separation, 8-hour default interval)
## Overview
@@ -95,7 +95,8 @@ GallerySubscriber/
| Component | Entry Point | Command |
|-----------|-------------|---------|
| Backend API | `backend/app/main.py` | `hypercorn app.main:app --bind 0.0.0.0:8080` |
| Celery Worker | `backend/app/tasks/celery_app.py` | `celery -A app.tasks.celery_app worker -B` |
| Scheduler | `backend/app/tasks/celery_app.py` | `celery -A app.tasks.celery_app worker --queues=scheduling --beat --concurrency=1` |
| Download Workers | `backend/app/tasks/celery_app.py` | `celery -A app.tasks.celery_app worker --queues=downloads` |
| Frontend | `frontend/src/main.js` | Built with Vite to `/static` |
## Database Schema
@@ -142,6 +143,7 @@ Download (1) ──→ (many) ContentItem
| `/api/downloads/stats` | GET | Download counts by status/platform |
| `/api/downloads/recent-activity` | GET | Recent failures and completions |
| `/api/downloads/reset-orphaned` | POST | Reset stuck running jobs |
| `/api/downloads/requeue-stale` | POST | Re-queue lost queued jobs |
| `/api/credentials` | GET, POST | List/upload credentials |
| `/api/credentials/<platform>` | DELETE | Remove credential |
| `/api/settings` | GET, PUT | App configuration |
@@ -175,17 +177,18 @@ Download (1) ──→ (many) ContentItem
3. **Service Layer** - GalleryDLService wraps gallery-dl subprocess
4. **Encrypted Credentials** - Fernet symmetric encryption with PBKDF2 key derivation
5. **Global Schedule Interval** - All sources checked at the same interval (configured in Settings)
6. **Celery Beat** - Runs `scheduled_check` every 60s to trigger due downloads
7. **Database-Driven Settings** - Worker reads rate_limit, retry_count, parallel_limit from DB
8. **Orphan Job Cleanup** - Worker startup + periodic task resets stuck "running" jobs
6. **Task Routing** - Separate queues for scheduling vs download tasks
7. **Database-Driven Settings** - Worker reads rate_limit, retry_count, parallel_limit, schedule_interval from DB
8. **Orphan Job Cleanup** - Scheduler startup + periodic task resets stuck "running" jobs
## Docker Services
```yaml
app: Backend API + Frontend (port 8080)
celery: Background worker with scheduler
db: PostgreSQL 15-Alpine
redis: Redis 7-Alpine
app: Backend API + Frontend (port 8080)
scheduler: Celery beat + scheduling worker (lightweight tasks)
worker: Celery download workers (heavy gallery-dl tasks)
db: PostgreSQL 15-Alpine
redis: Redis 7-Alpine (task queue + result backend)
Volumes:
downloads: /data/downloads (downloaded files)
@@ -193,6 +196,39 @@ Volumes:
postgres_data: Database persistence
```
## Task Routing Architecture
Tasks are routed to separate queues for clean separation of concerns:
```
┌─────────────────────────────────────────────────────────────────┐
│ SCHEDULER (1 container) │
│ - Runs Celery Beat (triggers periodic tasks) │
│ - 1 worker consuming from "scheduling" queue │
│ - Handles: scheduled_check, cleanup, storage_stats, etc. │
│ - Resets orphaned jobs on startup (only here, not workers) │
└─────────────────────────────────────────────────────────────────┘
│ Queues download tasks
┌─────────────────────────────────────────────────────────────────┐
│ Redis Queues │
│ - "scheduling" queue → scheduler only │
│ - "downloads" queue → download workers only │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ WORKERS (scalable, 1+ containers) │
│ - Consume from "downloads" queue only │
│ - Handles: download_source, process_download │
│ - Heavy work (running gallery-dl) │
└─────────────────────────────────────────────────────────────────┘
```
**Task Routing Config** (in `celery_app.py`):
- `scheduled_check`, `cleanup_old_downloads`, `update_storage_stats`, `reset_orphaned_jobs` → "scheduling" queue
- `download_source`, `process_download` → "downloads" queue
## Common Operations
**Run migrations:**
@@ -207,7 +243,7 @@ docker compose up -d --build
**View logs:**
```bash
docker compose logs -f app celery
docker compose logs -f app scheduler worker
```
**Development mode:**
@@ -233,22 +269,39 @@ docker compose -f docker-compose.dev.yml up
5. Download records and content items stored in database
6. Real-time progress broadcast via WebSocket
## Source Scheduling Rules
A source gets queued for download when ALL of these conditions are met:
1. `source.enabled = True`
2. `subscription.enabled = True`
3. `(now - source.last_check) >= schedule_interval` OR `source.last_check is NULL` (never checked)
4. No existing QUEUED or RUNNING download for this source
**Note:** `last_check` is updated when a download **completes** (success or failure), not when queued. This means the interval is measured from completion to completion.
## Scheduled Tasks (Celery Beat)
| Task | Schedule | Purpose |
|------|----------|---------|
| `scheduled_check` | Every 60s | Check if sources are due (based on global schedule_interval) |
| `scheduled_check` | Every 15 min | Check if sources are due (based on global schedule_interval) |
| `cleanup_old_downloads` | Daily 3 AM | Remove old download records (30d completed, 7d failed) |
| `update_storage_stats` | Every 30 min | Calculate downloads folder size |
| `reset_orphaned_jobs` | Every 15 min | Reset jobs stuck in "running" > 90 min |
| `requeue_stale_jobs` | Every 15 min | Re-queue jobs stuck in "queued" > 30 min (lost Celery tasks) |
**Worker Startup Behavior:** When Celery worker starts, it resets ALL "running" jobs to "queued" (handles crash recovery).
**Scheduler Startup Behavior:** When the scheduler container starts:
1. Resets ALL "running" jobs to "queued" (handles crash recovery)
2. Re-queues ALL "queued" jobs (recovers from Redis DB mismatch or lost tasks)
3. Triggers an immediate `scheduled_check` (don't wait 15 min for first check)
Download workers do NOT perform these startup tasks to avoid duplicates.
## Settings (Database-Driven)
| Key | Default | Purpose |
|-----|---------|---------|
| `download.schedule_interval` | 3600 | Seconds between source checks (applies to all sources) |
| `download.schedule_interval` | 28800 | Seconds between source checks (8 hours, applies to all sources) |
| `download.rate_limit` | 3.0 | Seconds between gallery-dl requests |
| `download.parallel_limit` | 3 | Concurrent Celery workers (requires restart) |
| `download.retry_count` | 3 | Max retries for failed downloads |