Queue the smallest backfill jobs first and show queued state

This commit is contained in:
Fisto
2026-09-17 09:05:22 +02:00
parent 6ae2ff5ab3
commit c6a16a63f7
4 changed files with 22 additions and 8 deletions
+4 -3
View File
@@ -158,9 +158,10 @@ Previews run after every walk:
before their previews were cached. One job per chat groups every paused, done, or failed scrape before their previews were cached. One job per chat groups every paused, done, or failed scrape
of that chat, so a paused-and-resumed scrape stays in one job. Jobs run one at a time, in of that chat, so a paused-and-resumed scrape stays in one job. Jobs run one at a time, in
batches, and report pending/cached/failed counts, per-kind pending, throughput, and an ETA. The batches, and report pending/cached/failed counts, per-kind pending, throughput, and an ETA. The
Bot tab drives them: **Start backfill**, per-job Start/Pause, and **Pause all**. On daemon boot Bot tab drives them: **Start backfill**, per-job Start/Pause, and **Pause all**. Jobs with the
jobs start automatically when the Backfilling setting **Start automatically on daemon boot** is least pending work start first, so small chats clear instead of waiting behind a long one; a job
on. that is started but waiting for the queue reads `queued`. On daemon boot jobs start automatically
when the Backfilling setting **Start automatically on daemon boot** is on.
- Transient fetch failures keep the row pending and count up `preview_attempts`. After the - Transient fetch failures keep the row pending and count up `preview_attempts`. After the
**Tries before giving up** setting (default 3) the row is marked `unreachable after N attempt(s)`, **Tries before giving up** setting (default 3) the row is marked `unreachable after N attempt(s)`,
so dead hosts (Gfycat, Streamable) stop burning batches forever. so dead hosts (Gfycat, Streamable) stop burning batches forever.
+12 -4
View File
@@ -40,7 +40,8 @@ export interface BackfillMember {
export interface BackfillJobStatus { export interface BackfillJobStatus {
chatId: string; chatId: string;
guildId: string | null; guildId: string | null;
state: "idle" | "running" | "paused" | "done"; /** `queued` means started, but another job holds the queue. */
state: "idle" | "queued" | "running" | "paused" | "done";
members: BackfillMember[]; members: BackfillMember[];
pending: number; pending: number;
cached: number; cached: number;
@@ -188,6 +189,8 @@ export class BackfillQueue {
const groups = this.plan(); const groups = this.plan();
const jobs: BackfillJobStatus[] = []; const jobs: BackfillJobStatus[] = [];
const anyClaimed = [...this.jobs.values()].some(candidate => candidate.claimed);
for (const [chatId, group] of groups) { for (const [chatId, group] of groups) {
// Hide chats that are fully backfilled unless their job did work this session. // Hide chats that are fully backfilled unless their job did work this session.
const tracked = this.jobs.get(chatId); const tracked = this.jobs.get(chatId);
@@ -196,11 +199,13 @@ export class BackfillQueue {
const perMinute = tracked?.perMinute ?? this.estimatedPerMinute(settings, gaps); const perMinute = tracked?.perMinute ?? this.estimatedPerMinute(settings, gaps);
const etaMs = pending > 0 && perMinute > 0 ? Math.round((pending / perMinute) * 60_000) : null; const etaMs = pending > 0 && perMinute > 0 ? Math.round((pending / perMinute) * 60_000) : null;
const state =
tracked?.state === "running" && anyClaimed && !tracked.claimed ? "queued" : (tracked?.state ?? "idle");
jobs.push({ jobs.push({
chatId, chatId,
guildId: group.guildId, guildId: group.guildId,
state: tracked?.state ?? "idle", state,
members: group.members, members: group.members,
pending, pending,
cached: group.members.reduce((sum, member) => sum + member.cached, 0), cached: group.members.reduce((sum, member) => sum + member.cached, 0),
@@ -280,11 +285,14 @@ export class BackfillQueue {
return perRow > 0 ? 60_000 / perRow : 6000; return perRow > 0 ? 60_000 / perRow : 6000;
} }
/** Start every job with pending rows, or one chat's job. */ /** Start every job with pending rows, or one chat's job. Smallest jobs first: they finish. */
start(chatId?: string): void { start(chatId?: string): void {
const status = this.list(); const status = this.list();
const order = chatId
? status.jobs
: [...status.jobs].sort((a, b) => a.pending - b.pending);
for (const job of status.jobs) { for (const job of order) {
if (chatId && job.chatId !== chatId) continue; if (chatId && job.chatId !== chatId) continue;
if (job.pending <= 0) continue; if (job.pending <= 0) continue;
+5
View File
@@ -740,3 +740,8 @@
color: color-mix(in srgb, var(--color-neutral-400) 55%, transparent); color: color-mix(in srgb, var(--color-neutral-400) 55%, transparent);
font-style: italic; font-style: italic;
} }
.vk-bot-state-queued {
background: color-mix(in srgb, var(--color-neutral-400) 18%, transparent);
color: var(--color-neutral-300);
}
+1 -1
View File
@@ -615,7 +615,7 @@
row.append(progress); row.append(progress);
const actions = el("div", "vk-bot-job-actions"); const actions = el("div", "vk-bot-job-actions");
if (job.state === "running") { if (job.state === "running" || job.state === "queued") {
actions.append(button("Pause", () => void backfillAction("pause", job.chatId))); actions.append(button("Pause", () => void backfillAction("pause", job.chatId)));
} else if (job.pending > 0) { } else if (job.pending > 0) {
actions.append(button("Start", () => void backfillAction("start", job.chatId))); actions.append(button("Start", () => void backfillAction("start", job.chatId)));