mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-27 07:52:24 +00:00
Lazy-load recommendation indexes
This commit is contained in:
1 parent
fe976b3ab7
commit
5f439d6863
5 files changed
+104
-20
No files matched your search
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
Reference in new issue
Block a user