Lazy-load recommendation indexes

This commit is contained in:
thePR0M3TH3AN committed 2026-07-26 17:59:45 -04:00
1 parent fe976b3ab7
commit 5f439d6863
5 files changed
+104 -20

No files matched your search

+9 -1
View File
@@ -1,6 +1,6 @@
# BitVid Performance & Efficiency Plan
Status: **Stage 0 in progress — local-only measurement harness added; no runtime optimization changes implemented**
Status: **Stage 2 in progress — startup hardening validated locally; recommendation indexes now activate on demand**
## Goal
@@ -173,6 +173,14 @@ old diagnostic behavior.
## Stage 2 — Lazy recommendation indexes
Implemented first slice: `ExploreDataService` is no longer created during
ordinary bootstrap. Home/Profile sessions therefore create no recommendation
worker or recommendation intervals. Opening For You or Explore creates and
activates the service; events received while inactive mark it dirty and are
included in the activation refresh. Focused lifecycle, feed-coordinator, and
build checks pass; browser validation of both recommendation views remains the
next gate before further Stage 2 coalescing work.
### Change
Do not initialize `ExploreDataService` globally in application bootstrap.
+8
View File
@@ -1014,6 +1014,14 @@ export function createFeedCoordinator(deps) {
devLogger.log(`Starting loadFeedVideos(${feedType})... (forceFetch =`, forceFetch, ")");
this.setFeedTelemetryContext(feedType);
const recommendationFeed =
feedType === FEED_TYPES.FOR_YOU || feedType === FEED_TYPES.EXPLORE;
if (recommendationFeed && typeof this.ensureExploreDataService === "function") {
await this.ensureExploreDataService();
} else if (this.exploreDataService?.setActive) {
this.exploreDataService.setActive(false);
}
let includeTags = false;
let loadingMessage = "Fetching videos\u2026";
let refreshMethod = null;
+36 -7
View File
@@ -144,26 +144,47 @@ export default class ExploreDataService {
this.tagIdfInterval = null;
this.unsubscribeHandlers = [];
this.handleVisibility = this.handleVisibility.bind(this);
this.initialized = false;
this.active = false;
this.historyDirty = true;
this.idfDirty = true;
}
initialize() {
this.refreshWatchHistoryTagCounts({ force: true, reason: "init" });
this.refreshTagIdf({ force: true, reason: "init" });
initialize({ active = false } = {}) {
if (this.initialized) {
return this.setActive(active);
}
this.initialized = true;
this.subscribeToUpdates();
if (typeof document !== "undefined") {
document.addEventListener("visibilitychange", this.handleVisibility);
}
return this.setActive(active);
}
async setActive(active) {
this.active = active === true;
if (!this.active || (typeof document !== "undefined" && document.hidden)) {
this.clearIntervals();
return;
}
this.startIntervals();
const forceHistory = this.historyDirty || !this.watchHistoryTagCountsUpdatedAt;
const forceIdf = this.idfDirty || !this.tagIdfUpdatedAt;
this.historyDirty = false;
this.idfDirty = false;
await Promise.all([
this.refreshWatchHistoryTagCounts({ force: forceHistory, reason: "active" }),
this.refreshTagIdf({ force: forceIdf, reason: "active" }),
]);
}
handleVisibility() {
if (typeof document === "undefined") return;
if (document.hidden) {
this.clearIntervals();
} else {
this.refreshWatchHistoryTagCounts({ reason: "visibility" });
this.refreshTagIdf({ reason: "visibility" });
this.startIntervals();
} else if (this.active) {
void this.setActive(true);
}
}
@@ -226,6 +247,10 @@ export default class ExploreDataService {
}
queueWatchHistoryRefresh(reason) {
if (!this.active) {
this.historyDirty = true;
return;
}
if (this.watchHistoryRefreshHandle) {
return;
}
@@ -236,6 +261,10 @@ export default class ExploreDataService {
}
queueTagIdfRefresh(reason) {
if (!this.active) {
this.idfDirty = true;
return;
}
if (this.tagIdfRefreshHandle) {
return;
}
+16 -12
View File
@@ -360,18 +360,22 @@ export default class ApplicationBootstrap {
onViewLogged: (detail) => app.handleFeedViewTelemetry(detail),
});
app.exploreDataService =
this.services.exploreDataService ||
new ExploreDataService({
watchHistoryService,
nostrService: app.nostrService,
getActiveActor: () =>
typeof app.pubkey === "string" && app.pubkey ? app.pubkey : "",
logger: devLogger,
});
if (app.exploreDataService && typeof app.exploreDataService.initialize === "function") {
app.exploreDataService.initialize();
}
app.exploreDataService = this.services.exploreDataService || null;
app.ensureExploreDataService = async () => {
if (!app.exploreDataService) {
app.exploreDataService = new ExploreDataService({
watchHistoryService,
nostrService: app.nostrService,
getActiveActor: () =>
typeof app.pubkey === "string" && app.pubkey ? app.pubkey : "",
logger: devLogger,
});
}
if (typeof app.exploreDataService.initialize === "function") {
await app.exploreDataService.initialize({ active: true });
}
return app.exploreDataService;
};
const playbackDependencies = {
torrentClient: this.services.torrentClient || torrentClient,
@@ -0,0 +1,35 @@
import assert from "node:assert/strict";
import test from "node:test";
import ExploreDataService from "../../js/services/exploreDataService.js";
test("ExploreDataService stays idle until a recommendation feed activates it", async () => {
const service = new ExploreDataService({
historyRefreshIntervalMs: 0,
idfRefreshIntervalMs: 0,
});
let historyRefreshes = 0;
let idfRefreshes = 0;
service.refreshWatchHistoryTagCounts = async () => {
historyRefreshes += 1;
return new Map();
};
service.refreshTagIdf = async () => {
idfRefreshes += 1;
return new Map();
};
await service.initialize();
assert.equal(historyRefreshes, 0);
assert.equal(idfRefreshes, 0);
service.queueWatchHistoryRefresh("note");
service.queueTagIdfRefresh("note");
await service.setActive(true);
assert.equal(historyRefreshes, 1, "dirty history is refreshed on activation");
assert.equal(idfRefreshes, 1, "dirty IDF is refreshed on activation");
await service.setActive(false);
service.queueTagIdfRefresh("inactive-note");
assert.equal(idfRefreshes, 1, "inactive feeds do not rebuild recommendation indexes");
service.destroy();
});