136 lines
6.5 KiB
Markdown
136 lines
6.5 KiB
Markdown
# Spec Round 2 — Scraper → mytheclipse Deep Infra Migration (thread/async/queue)
|
|||
|
|
|
||
|
|
Date: 2026-08-30
|
||
|
|
Repo: /home/code/scraper (scraper-service)
|
||
|
|
Library: /home/code/mytheclipse (mytheclipse crates v1.20+ local path)
|
||
|
|
|
||
|
|
## Scope
|
||
|
|
|
||
|
|
Round 1 (committed c7ffd29) migrated retry, redis cache, event bus, ratelimit,
|
||
|
|
config. Round 2 targets the remaining **runtime/concurrency/queue/observability**
|
||
|
|
infrastructure — the parts the user explicitly asked to push "sampai tingkat
|
||
|
|
thread, async, queue dan lainnya".
|
||
|
|
|
||
|
|
## 1. Runtime thread sizing — `mytheclipse::runtime_auto::RuntimeConfig`
|
||
|
|
|
||
|
|
**Current**: bootstrap logs `std::thread::available_parallelism()` manually;
|
||
|
|
tokio runtime is `#[tokio::main(flavor = "multi_thread")]` with default
|
||
|
|
worker counts.
|
||
|
|
|
||
|
|
**Change**:
|
||
|
|
- Add `mytheclipse = { features = ["lifecycle", ...] }` (add `lifecycle` feature).
|
||
|
|
- In `bootstrap/mod.rs`, compute `RuntimeConfig::auto()` once at build, log
|
||
|
|
worker/blocking/compute/io counts, and keep the tokio runtime default
|
||
|
|
(already multi_thread). Optionally pass `worker_threads` via a `#[tokio::main]`
|
||
|
|
alternative — but since `main` uses the macro, we keep the macro form and
|
||
|
|
simply surface the auto-derived counts in logs + store them in `AppState`
|
||
|
|
for future pool sizing.
|
||
|
|
- Replace the manual `available_parallelism()` log with `RuntimeConfig::auto()`.
|
||
|
|
|
||
|
|
## 2. Async I/O + background task spawns — `mytheclipse::spawn_io` / `spawn_bg`
|
||
|
|
|
||
|
|
**Current**:
|
||
|
|
- `proxy_fetch.rs:114` uses `tokio::spawn(...)` for the request-coalescing leader
|
||
|
|
- 30+ `tokio::task::spawn_blocking(...)` sites across otakudesu.rs, anime2
|
||
|
|
use_cases, komik use_cases, proxy_fetch (gzip decompress)
|
||
|
|
- No bounded background task pool
|
||
|
|
|
||
|
|
**Change**:
|
||
|
|
- `proxy_fetch.rs` leader task → `mytheclipse::spawn_io(...)` (same semantics,
|
||
|
|
adds tracing span instrumentation). No behavior change.
|
||
|
|
- Add `mytheclipse = { features = ["io", "bg"] }`.
|
||
|
|
- The `spawn_blocking` sites stay as-is **this round** (they're CPU-bound
|
||
|
|
parser calls and `spawn_blocking` is the correct tokio primitive; mytheclipse
|
||
|
|
`compute()` would replace them but that's a 2k+ LOC parser sweep — deferred
|
||
|
|
as before). Document this in the spec's residual section.
|
||
|
|
- Add `bg` feature and expose a bounded background executor via
|
||
|
|
`mytheclipse::spawn_bg` for the **cache-write / relay-fallback** background
|
||
|
|
tasks that currently fire-and-forget (if any are found in use_cases).
|
||
|
|
|
||
|
|
## 3. Worker queue — `mytheclipse-queue` `InMemoryQueue` + `WorkerPool`
|
||
|
|
|
||
|
|
**Current**: no queue abstraction; the only "background" work is the cache
|
||
|
|
batch writes (`cache_image_urls_batch_lazy` in the proxy image-cache path —
|
||
|
|
but proxy routes were removed, so that path may be dead). Event bus has no
|
||
|
|
worker pool.
|
||
|
|
|
||
|
|
**Change**:
|
||
|
|
- Add `mytheclipse-queue = { path = ..., features = ["in-memory"] }`.
|
||
|
|
- Wire a small application-level `JobQueue` in `src/infrastructure/queue/`:
|
||
|
|
- `InMemoryQueue` + `WorkerPool` consuming a `scrape:repair` topic for
|
||
|
|
background image-cache repair/re-sync jobs.
|
||
|
|
- Graceful shutdown: on app shutdown, stop workers.
|
||
|
|
- This is additive infra (no existing behavior replaced), so it's low-risk
|
||
|
|
and demonstrates the queue crate in the scraper.
|
||
|
|
- Actually **use** it: the `image_cache` repair path (cache cleanup at 2 AM)
|
||
|
|
is currently absent (scheduler dir missing). Introduce a small
|
||
|
|
`src/infrastructure/scheduler.rs` using `mytheclipse::cron` (lifecycle
|
||
|
|
feature) OR `tokio::time::interval` for the daily 02:00 cleanup that
|
||
|
|
enqueues repair jobs onto the queue. Given the original CLAUDE.md mentions
|
||
|
|
"scheduler — Cron jobs (daily cache cleanup at 2 AM)", recreating it on
|
||
|
|
mytheclipse-queue is a faithful, additive migration.
|
||
|
|
|
||
|
|
## 4. Backpressure / concurrency — `mytheclipse::ConcurrencyLimiter` / `BackpressureQueue`
|
||
|
|
|
||
|
|
**Current**: proxy_fetch has request coalescing via DashMap + broadcast (kept —
|
||
|
|
it's application logic, not infra). No app-level backpressure.
|
||
|
|
|
||
|
|
**Change**:
|
||
|
|
- Add `mytheclipse = { features = ["traffic"] }` (already enabled for
|
||
|
|
RateLimiter in round 1).
|
||
|
|
- Wrap the **fetch-with-proxy** path with a `ConcurrencyLimiter` (bound
|
||
|
|
concurrent outbound scrapes, default e.g. 16) so burst traffic can't
|
||
|
|
saturate the upstream. This is a real, central hot path (every scrape goes
|
||
|
|
through it).
|
||
|
|
- Keep the RateLimiter middleware (round 1) for inbound.
|
||
|
|
|
||
|
|
## 5. Observability — `mytheclipse-tracing` `TracingLayer` (+ keep OTel metrics)
|
||
|
|
|
||
|
|
**Current**: `tracing_subscriber::fmt().with_env_filter(...)` in bootstrap;
|
||
|
|
opentelemetry metrics in `observability/metrics.rs` (kept — `mytheclipse-tracing`
|
||
|
|
has no metrics exporter, round-1 decision).
|
||
|
|
|
||
|
|
**Change**:
|
||
|
|
- Add `mytheclipse-tracing = { path = ..., features = ["env"] }`.
|
||
|
|
- Replace `tracing_subscriber::fmt()` in bootstrap with
|
||
|
|
`mytheclipse_tracing::TracingLayer::install()` (same RUST_LOG env-filter
|
||
|
|
semantics, adds tracing span layer compatibility).
|
||
|
|
- Keep `init_otel_metrics()` untouched (OTel exporter stays).
|
||
|
|
|
||
|
|
## 6. HTTP client — `mytheclipse-http` `HttpClient` (optional, re-evaluate)
|
||
|
|
|
||
|
|
Round 1 deferred this because mytheclipse-http's client "loses header/UA/status
|
||
|
|
control". Re-check: `mytheclipse-http::client::HttpClient` — if it exposes
|
||
|
|
`with_timeout`, `get_text`, `get_json`, `post_json`, and header injection,
|
||
|
|
use it for the non-critical relay fetches (`fetch_via_relays`). If not
|
||
|
|
header-capable, skip (kept as residual).
|
||
|
|
|
||
|
|
## Files touched
|
||
|
|
|
||
|
|
- Cargo.toml (features: add lifecycle, io, bg; add mytheclipse-queue, mytheclipse-tracing)
|
||
|
|
- src/bootstrap/mod.rs (RuntimeConfig log + TracingLayer + scheduler start)
|
||
|
|
- src/infrastructure/scraping/proxy_fetch.rs (spawn_io + ConcurrencyLimiter)
|
||
|
|
- src/infrastructure/queue/mod.rs (NEW — InMemoryQueue + WorkerPool + repair topic)
|
||
|
|
- src/infrastructure/scheduler.rs (NEW — daily 02:00 cleanup → queue enqueue)
|
||
|
|
- src/infrastructure/cache/mytheclipse.rs (maybe wire limiter through cache path)
|
||
|
|
- src/presentation/state.rs (add queue handle / limiter if needed)
|
||
|
|
|
||
|
|
## Verification
|
||
|
|
|
||
|
|
1. `cargo check` — green after each batch
|
||
|
|
2. `cargo test` — all pass
|
||
|
|
3. `cargo build --release` — green
|
||
|
|
4. `cargo fmt --check` on touched files
|
||
|
|
5. Runtime smoke: boot server, hit `/health`, confirm logs show RuntimeConfig
|
||
|
|
and scheduler started; queue jobs process (test harness)
|
||
|
|
|
||
|
|
## Residual (deliberately deferred)
|
||
|
|
|
||
|
|
- `spawn_blocking` → `mytheclipse::compute()` sweep (2k+ LOC parser files)
|
||
|
|
- OTel metrics → mytheclipse-tracing (no metrics exporter in library yet)
|
||
|
|
- rayon in komik_parser
|
||
|
|
- mytheclipse-http client if header control is insufficient
|
||
|
|
|
||
|
|
## Commit
|
||
|
|
|
||
|
|
Single commit at end: `refactor: migrate runtime/queue/concurrency infra to mytheclipse (round 2)`
|