From df0afa12ede3b5acb82188f927838f483fa3deaf Mon Sep 17 00:00:00 2001 From: Nolan Lawson Date: Sat, 7 Sep 2019 17:49:58 -0700 Subject: [PATCH] perf: periodically clean up old compose drafts (#1469) fixes #1419 --- src/routes/_database/cleanup.js | 7 ++--- src/routes/_static/database.js | 2 ++ src/routes/_store/mixins/instanceMixins.js | 6 +++- src/routes/_store/observers/cleanup.js | 31 +++++++++++++++++++ .../_store/observers/loggedInObservers.js | 2 ++ tests/unit/test-database.js | 5 +-- 6 files changed, 46 insertions(+), 7 deletions(-) create mode 100644 src/routes/_static/database.js create mode 100644 src/routes/_store/observers/cleanup.js diff --git a/src/routes/_database/cleanup.js b/src/routes/_database/cleanup.js index 04c0f815..787e7548 100644 --- a/src/routes/_database/cleanup.js +++ b/src/routes/_database/cleanup.js @@ -16,10 +16,9 @@ import { deleteAll } from './utils' import { createPinnedStatusKeyRange, createThreadKeyRange } from './keys' import { getKnownInstances } from './knownInstances' import noop from 'lodash-es/noop' +import { CLEANUP_DELAY, CLEANUP_TIME_AGO } from '../_static/database' const BATCH_SIZE = 20 -export const TIME_AGO = 5 * 24 * 60 * 60 * 1000 // five days ago -const DELAY = 5 * 60 * 1000 // five minutes function batchedGetAll (callGetAll, callback) { function nextBatch () { @@ -124,7 +123,7 @@ export async function cleanup (instanceName) { pinnedStatusesStore ] = stores - const cutoff = Date.now() - TIME_AGO + const cutoff = Date.now() - CLEANUP_TIME_AGO cleanupStatuses(statusesStore, statusTimelinesStore, threadsStore, cutoff) cleanupNotifications(notificationsStore, notificationTimelinesStore, cutoff) @@ -148,4 +147,4 @@ async function scheduledCleanup () { } // we have unit tests that test indexedDB; we don't want this thing to run forever -export const scheduleCleanup = process.browser ? debounce(scheduledCleanup, DELAY) : noop +export const scheduleCleanup = process.browser ? debounce(scheduledCleanup, CLEANUP_DELAY) : noop diff --git a/src/routes/_static/database.js b/src/routes/_static/database.js new file mode 100644 index 00000000..ee302bfd --- /dev/null +++ b/src/routes/_static/database.js @@ -0,0 +1,2 @@ +export const CLEANUP_TIME_AGO = 5 * 24 * 60 * 60 * 1000 // five days ago +export const CLEANUP_DELAY = 5 * 60 * 1000 // five minutes diff --git a/src/routes/_store/mixins/instanceMixins.js b/src/routes/_store/mixins/instanceMixins.js index 8a61c384..e07da488 100644 --- a/src/routes/_store/mixins/instanceMixins.js +++ b/src/routes/_store/mixins/instanceMixins.js @@ -4,7 +4,11 @@ export function instanceMixins (Store) { Store.prototype.setComposeData = function (realm, obj) { const { composeData, currentInstance } = this.get() const instanceNameData = composeData[currentInstance] = composeData[currentInstance] || {} - instanceNameData[realm] = Object.assign(instanceNameData[realm] || {}, obj) + instanceNameData[realm] = Object.assign( + instanceNameData[realm] || {}, + { ts: Date.now() }, + obj + ) this.set({ composeData }) } diff --git a/src/routes/_store/observers/cleanup.js b/src/routes/_store/observers/cleanup.js new file mode 100644 index 00000000..d2e8279a --- /dev/null +++ b/src/routes/_store/observers/cleanup.js @@ -0,0 +1,31 @@ +import { store } from '../store' +import { scheduleIdleTask } from '../../_utils/scheduleIdleTask' +import { CLEANUP_DELAY, CLEANUP_TIME_AGO } from '../../_static/database' + +function doCleanup () { + // Periodically clean up drafts in localStorage, so they don't grow without bound. + // Only do this for replies, so not for the home timeline or the compose modal. + const now = Date.now() + let changeCount = 0 + const { composeData } = store.get() + for (const instanceComposeData of Object.values(composeData)) { + for (const [realm, timelineComposeData] of Object.entries(instanceComposeData)) { + if (realm === 'home' || realm === 'dialog') { + continue + } + const ts = timelineComposeData.ts || 0 // if no timestamp set, just assume it's very old (migration behavior) + if (now - ts > CLEANUP_TIME_AGO) { + delete instanceComposeData[realm] + changeCount++ + } + } + } + console.log('deleted', changeCount, 'old drafts') + if (changeCount) { + store.set({ composeData }) + } +} + +export function cleanup () { + setInterval(() => scheduleIdleTask(doCleanup), CLEANUP_DELAY) +} diff --git a/src/routes/_store/observers/loggedInObservers.js b/src/routes/_store/observers/loggedInObservers.js index 35261f59..0f8d4a82 100644 --- a/src/routes/_store/observers/loggedInObservers.js +++ b/src/routes/_store/observers/loggedInObservers.js @@ -4,6 +4,7 @@ import { notificationObservers } from './notificationObservers' import { autosuggestObservers } from './autosuggestObservers' import { notificationPermissionObservers } from './notificationPermissionObservers' import { customScrollbarObservers } from './customScrollbarObservers' +import { cleanup } from './cleanup' // These observers can be lazy-loaded when the user is actually logged in. // Prevents circular dependencies and reduces the size of main.js @@ -14,4 +15,5 @@ export default function loggedInObservers () { autosuggestObservers() notificationPermissionObservers() customScrollbarObservers() + cleanup() } diff --git a/tests/unit/test-database.js b/tests/unit/test-database.js index 17c8a2b0..4a515372 100644 --- a/tests/unit/test-database.js +++ b/tests/unit/test-database.js @@ -10,7 +10,8 @@ import { TIMESTAMP, ACCOUNT_ID, STATUS_ID, REBLOG_ID, USERNAME_LOWERCASE, CURRENT_TIME, DB_VERSION_CURRENT, DB_VERSION_SEARCH_ACCOUNTS, DB_VERSION_SNOWFLAKE_IDS } from '../../src/routes/_database/constants' -import { cleanup, TIME_AGO } from '../../src/routes/_database/cleanup' +import { cleanup } from '../../src/routes/_database/cleanup' +import { CLEANUP_TIME_AGO } from '../../src/routes/_static/database' const INSTANCE_NAME = 'localhost:3000' @@ -91,7 +92,7 @@ describe('test-database.js', function () { // set a timestamp based on the *current* date when the status is inserted, // not the date that the status was composed. - const longAgo = Date.now() - (TIME_AGO * 2) + const longAgo = Date.now() - (CLEANUP_TIME_AGO * 2) const oldStatus = { id: '1',