commit 78331ef5e3af9e455b8f311e3fe7a78080819ef5
parent 303198a05b6c12480bbe6f13537dfecb52c10f2b
Author: Rolf Rando <rrando@mozilla.com>
Date: Tue, 7 Oct 2025 18:23:41 +0000
Bug 1980666 - Cull inferred personalization places older history r=nbarrett
Periodically (every 5 days) checks and deletes older personalization stats (raw topic impression/click data) that is older than 6 months.
Clears data when places is cleared
Adds an experimenter pref
newtabInferredPersonalization {
"history_cull_days": <integer>
}
be used to change the expiration date from 6 months to something less.
Differential Revision: https://phabricator.services.mozilla.com/D267133
Diffstat:
2 files changed, 48 insertions(+), 1 deletion(-)
diff --git a/browser/extensions/newtab/lib/InferredPersonalizationFeed.sys.mjs b/browser/extensions/newtab/lib/InferredPersonalizationFeed.sys.mjs
@@ -44,6 +44,9 @@ const CLICK_TABLE = "moz_newtab_story_click";
const IMPRESSION_TABLE = "moz_newtab_story_impression";
const TEST_MODEL_ID = "TEST";
+const OLD_DATA_PRESERVE_DAYS_DEFAULT = 30 * 6;
+const OLD_DATA_CLEAR_CHECK_FREQUENCY_MS = 5 * 3600 * 24 * 1000; // 5 days
+
/**
* A feature that periodically generates a interest vector for inferred personalization.
*/
@@ -193,9 +196,20 @@ export class InferredPersonalizationFeed {
interestVectorRefreshHours * HOURS_TO_MS
)
) {
+ let lastClearedDB = interest_vector?.lastClearedDB ?? this.Date().now();
+ const needsCleanup =
+ this.Date().now() - lastClearedDB >= OLD_DATA_CLEAR_CHECK_FREQUENCY_MS;
+ if (needsCleanup) {
+ await this.clearOldData(
+ values?.inferredPersonalizationConfig?.history_cull_days ||
+ OLD_DATA_PRESERVE_DAYS_DEFAULT
+ );
+ lastClearedDB = this.Date().now();
+ }
interest_vector = {
data: await this.generateInterestVector(),
lastUpdated: this.Date().now(),
+ lastClearedDB,
};
}
await this.cache.set("interest_vector", interest_vector);
@@ -283,6 +297,35 @@ export class InferredPersonalizationFeed {
return interactions;
}
+ /**
+ * Deletes older data from a table
+ * @param {int} preserveAgeDays Number of days to preserve
+ * @param {*} table Table to clear
+ */
+ async clearOldDataOfTable(preserveAgeDays, table) {
+ let sql = `DELETE FROM ${table}
+ WHERE timestamp_s < ${timeMSToSeconds(this.Date().now()) - preserveAgeDays * 60 * 24}`;
+ try {
+ await lazy.PlacesUtils.withConnectionWrapper(
+ "newtab/lib/InferredPersonalizationFeed.sys.mjs: clearOldDataOfTable",
+ async db => {
+ await db.execute(sql);
+ }
+ );
+ } catch (ex) {
+ console.error(`Error clearning places data ${ex}`);
+ }
+ }
+
+ /**
+ * Deletes older data from impression and click tables
+ * @param {int} preserveAgeDays Number of days to preserve (defaults to 6 months)
+ */
+ async clearOldData(preserveAgeDays) {
+ await this.clearOldDataOfTable(preserveAgeDays, IMPRESSION_TABLE);
+ await this.clearOldDataOfTable(preserveAgeDays, CLICK_TABLE);
+ }
+
async recordInferredPersonalizationInteraction(
table,
tile,
@@ -385,7 +428,7 @@ export class InferredPersonalizationFeed {
}
break;
case at.PLACES_HISTORY_CLEARED:
- // TODO Handle places history clear
+ await this.clearOldData(0);
break;
case at.DISCOVERY_STREAM_IMPRESSION_STATS:
if (this.loaded && this.isEnabled()) {
diff --git a/toolkit/components/nimbus/FeatureManifest.yaml b/toolkit/components/nimbus/FeatureManifest.yaml
@@ -1428,6 +1428,10 @@ newtabInferredPersonalization:
type: boolean
description: >-
Whether to use Unary encoding with differential privacy in the interest vector. If false or not set, we default to laplace noise with real values
+ history_cull_days:
+ type: int
+ description: >-
+ Number of days local inferred feature history to preserve
normalized_time_zone_offset:
type: boolean
description: >-