feat(prefetch/faz1): tam-derinlik (MAX_DEPTH 2→12) + no-op children job üretimini durdur
Hedef = decode edilmiş tüm araçların TAM kategori+parça ağacı DB'de. Cap artık hedef değil güvenlik tavanı (gerçek ağaçlar ~5-7 derin; doğal yaprak tespiti durduruyor). queueCategoryJob non-leaf dalı `depth < MAX_DEPTH` ile kapılandı: processChildren'ın early-return'ünü aynalar → kuyruğun ~%85'ini oluşturan boş depth-job'ları (rate-limit slotu harcayan no-op) hiç üretilmez. +spec: depth>=cap'te children enqueue YOK, cap altında VAR. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -175,4 +175,35 @@ describe("PrefetchWorkerService — fast lane (lifo) + backlog gating", () => {
|
||||
expect(queue.add).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("queueCategoryJob — depth cap (no-op guard)", () => {
|
||||
// pl24 linkPath with no leaf marker (/bom/, /partinfo/ …) → treated as a
|
||||
// non-leaf folder that would normally queue a prefetch-children job.
|
||||
const nonLeaf = {
|
||||
id: "c1",
|
||||
linkPath: "/catalog/groups/x",
|
||||
source: "pl24",
|
||||
unavailable: false,
|
||||
hasSubgroups: false,
|
||||
};
|
||||
type QCJ = {
|
||||
queueCategoryJob: (c: unknown, v: string, s: string, d: number, f: boolean) => Promise<void>;
|
||||
};
|
||||
|
||||
it("does NOT enqueue a children job at depth >= MAX_DEPTH (avoids a no-op)", async () => {
|
||||
const { service, queue } = makeDeps({ waiting: 0, limitResults: [] });
|
||||
// depth 12 == default MAX_DEPTH → processChildren would early-return, so the
|
||||
// job must never be created.
|
||||
await (service as never as QCJ).queueCategoryJob(nonLeaf, "v1", "pl24", 12, false);
|
||||
expect(queue.add).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("enqueues a children job for a non-leaf within the cap", async () => {
|
||||
// childCheck query (.limit(1)) returns no rows → enqueue a children fetch.
|
||||
const { service, queue } = makeDeps({ waiting: 0, limitResults: [[]] });
|
||||
await (service as never as QCJ).queueCategoryJob(nonLeaf, "v1", "pl24", 1, false);
|
||||
expect(queue.add).toHaveBeenCalledTimes(1);
|
||||
expect(queue.add.mock.calls[0][0]).toBe("prefetch-children");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -27,17 +27,18 @@ import { PrefetchCategoryJobData, PrefetchInitJobData } from "./prefetch.types";
|
||||
import { CATALOG_PREFETCH_QUEUE } from "./queues/catalog-prefetch.queue";
|
||||
|
||||
/**
|
||||
* How deep the background prefetch BFS drills the category tree.
|
||||
* Depth cap for the prefetch BFS — a safety ceiling, NOT the target depth.
|
||||
*
|
||||
* Kept SHALLOW (2) on purpose: pre-warming the top ~2 levels for every decoded
|
||||
* vehicle is bounded work that finishes in days, kills empty catalog pages, and
|
||||
* makes the first clicks instant. Deeper categories are fetched lazily on-view
|
||||
* (getCategoryTree in CategoriesService) and cached permanently — so the long
|
||||
* tail nobody opens never costs an upstream request. Exhaustive depth-5
|
||||
* pre-warm produced an unbounded BFS fan-out (~460k jobs) that never drained
|
||||
* and starved the useful shallow + reactive work. Env-tunable.
|
||||
* Goal is COMPLETE coverage: every decoded vehicle's full category tree + all
|
||||
* parts in the DB so viewing is instant (DB read). The BFS terminates naturally
|
||||
* at real leaves (isLeafLinkPath); this cap only guards against pathological /
|
||||
* cyclic trees running away. Real PL24/EMEX/pcat trees are ~5-7 deep, so 12 is
|
||||
* comfortably beyond every genuine leaf while still bounding a runaway.
|
||||
* Efficiency is handled separately: queueCategoryJob never enqueues a children
|
||||
* job past the cap (no early-return no-ops), and completed vehicles are skipped
|
||||
* by the scan. Env-tunable (PREFETCH_MAX_DEPTH).
|
||||
*/
|
||||
const MAX_DEPTH = Number(process.env.PREFETCH_MAX_DEPTH) || 2;
|
||||
const MAX_DEPTH = Number(process.env.PREFETCH_MAX_DEPTH) || 12;
|
||||
|
||||
// ── Backfill scan (hourly cron) tuning ──
|
||||
/**
|
||||
@@ -592,8 +593,11 @@ export class PrefetchWorkerService implements OnModuleInit, OnModuleDestroy {
|
||||
fast,
|
||||
});
|
||||
}
|
||||
} else if (cat.linkPath) {
|
||||
// Check if children already exist
|
||||
} else if (cat.linkPath && depth < MAX_DEPTH) {
|
||||
// Non-leaf within the depth cap — explore children. The `depth < MAX_DEPTH`
|
||||
// gate mirrors processChildren's early-return: without it we'd enqueue a
|
||||
// prefetch-children job that processChildren just drops, burning a rate-limit
|
||||
// slot on a no-op (this was ~85% of the queue at MAX_DEPTH=2).
|
||||
const [childCheck] = await this.db
|
||||
.select({ id: categories.id })
|
||||
.from(categories)
|
||||
|
||||
Reference in New Issue
Block a user