Skip to content

Commit

Permalink
Split out the static cache into separate module
Browse files Browse the repository at this point in the history
  • Loading branch information
bmingles committed Mar 14, 2023
1 parent 401e7bc commit d45fec6
Show file tree
Hide file tree
Showing 4 changed files with 142 additions and 54 deletions.
71 changes: 71 additions & 0 deletions packages/pouch-storage/src/PouchCommandHistoryCache.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { CommandHistoryStorageItem } from '@deephaven/console';
import Log from '@deephaven/log';
import PouchCommandHistoryTable from './PouchCommandHistoryTable';
import { PouchStorageItem } from './PouchStorageTable';

type CommandHistoryStorageItemFindResponse = PouchDB.Find.FindResponse<
CommandHistoryStorageItem & PouchStorageItem
>;

type DatabaseName = string;

const log = Log.module('PouchCommandHistoryCache');

/**
* Static cache for tracking things shared across multiople
* `PouchCommandHistoryTable` instances.
*/
class PouchCommandHistoryCache {
/**
* Keep track of pruning status for a database. This helps ensure only 1
* pruning operation gets executed if multiple instances of a PouchCommandHistory
* table load data at the same time.
*/
static isPruning: Map<DatabaseName, boolean> = new Map();

/**
* Cache for command history query results keyed by db name. The cached data
* will be shared across all `PouchCommandHistoryTable` instances that have
* the same db name.
*/
static response: Map<
DatabaseName,
Promise<CommandHistoryStorageItemFindResponse> | null
> = new Map();

/**
* Keeps track of all `PouchCommandHistoryTable` instances.
*/
static tableRegistry: Map<
DatabaseName,
Set<PouchCommandHistoryTable>
> = new Map();

/**
* Pauses PouchDB change listeners for any `PouchCommandHistoryTables` with
* the given database name. This will cancel existing subscriptions and
* return a callback that can be used to re-subscribe them.
* @param dbName
*/
static pauseChangeListeners(dbName: DatabaseName): () => void {
const pausedTables: PouchCommandHistoryTable[] = [];

this.tableRegistry.get(dbName)?.forEach(table => {
if (table.changes) {
log.debug(`Pausing event listeners on '${dbName}' table`, table);
table.changes.cancel();

pausedTables.push(table);
}
});

return () => {
pausedTables.forEach(table => {
log.debug(`Resuming event listeners on '${dbName}' table`, table);
table.listenForChanges();
});
};
}
}

export default PouchCommandHistoryCache;
2 changes: 1 addition & 1 deletion packages/pouch-storage/src/PouchCommandHistoryStorage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export class PouchCommandHistoryStorage implements CommandHistoryStorage {
}

async getTable(language: string): Promise<PouchCommandHistoryTable> {
return this.getUpdateTable(language);
return new PouchCommandHistoryTable(language);
}

async addItem(
Expand Down
121 changes: 69 additions & 52 deletions packages/pouch-storage/src/PouchCommandHistoryTable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
StorageUtils,
ViewportData,
} from '@deephaven/storage';
import PouchCommandHistoryCache from './PouchCommandHistoryCache';
import { siftPrunableItems } from './pouchCommandHistoryUtils';
import PouchStorageTable, {
PouchDBSort,
Expand All @@ -20,10 +21,6 @@ const COMMAND_HISTORY_ITEMS_PRUNE = 2000;

const log = Log.module('PouchCommandHistoryTable');

type CommandHistoryStorageItemFindRespose = PouchDB.Find.FindResponse<
CommandHistoryStorageItem & PouchStorageItem
>;

type CommandHistoryDoc = PouchDB.Core.ExistingDocument<
CommandHistoryStorageItem & PouchStorageItem & PouchDB.Core.AllDocsMeta
>;
Expand All @@ -39,17 +36,13 @@ export class PouchCommandHistoryTable
auto_compaction: true,
revs_limit: 1,
} as unknown) as PouchDB.HttpAdapter.HttpAdapterConfiguration);
}

/**
* Cache for command history query results keyed by db name. The cached data
* will be shared across all `PouchCommandHistoryTable` instances that have
* the same db name.
*/
static cache: Record<
string,
Promise<CommandHistoryStorageItemFindRespose> | null
> = {};
// Add this table instance to `allTables`
if (!PouchCommandHistoryCache.tableRegistry.has(this.cacheKey)) {
PouchCommandHistoryCache.tableRegistry.set(this.cacheKey, new Set());
}
PouchCommandHistoryCache.tableRegistry.get(this.cacheKey)?.add(this);
}

private searchText?: string;

Expand Down Expand Up @@ -84,18 +77,21 @@ export class PouchCommandHistoryTable
}, 500);
}

dbUpdate(
// Our current version of eslint + prettier doesn't like `override` + `async` keyword.
// We should be able to remove this whenever we upgrade.
// eslint-disable-next-line prettier/prettier
override dbUpdate(
event: PouchDB.Core.ChangesResponseChange<CommandHistoryStorageItem>
): void {
log.debug('Clearing cache and refreshing data', event);

PouchCommandHistoryTable.cache[this.cacheKey] = null;
PouchCommandHistoryCache.response.delete(this.cacheKey);

super.dbUpdate(event);
}

/**
* Fetch command history data from `PouchCommandHistoryTable.cache` or from
* Fetch command history data from `PouchCommandHistoryCache.cache` or from
* PouchDB if data is not found in the cache. If the number of total items in
* the db exceeds COMMAND_HISTORY_ITEMS_MAX, the database will be pruned down
* to COMMAND_HISTORY_ITEMS_PRUNE total items. Note that PouchDB doesn't
Expand All @@ -108,43 +104,46 @@ export class PouchCommandHistoryTable
): Promise<
PouchDB.Find.FindResponse<CommandHistoryStorageItem & PouchStorageItem>
> {
if (PouchCommandHistoryTable.cache[this.cacheKey]) {
if (PouchCommandHistoryCache.response.has(this.cacheKey)) {
log.debug('Fetching from cache', this.searchText, this.viewport);
} else {
log.debug('Fetching from PouchDB', this.searchText, this.viewport);

PouchCommandHistoryTable.cache[this.cacheKey] = this.db
.allDocs({
include_docs: true,
})
.then(result => {
const allItems = result.rows
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
.map(row => row.doc!)
.filter(({ name }) => name);

log.debug(`Fetched ${allItems.length} command history items`);

const { toKeep, toPrune } = siftPrunableItems(
allItems,
COMMAND_HISTORY_ITEMS_MAX,
COMMAND_HISTORY_ITEMS_PRUNE
);

// If number of items in PouchDB has exceeded COMMAND_HISTORY_ITEMS_MAX
// prune them down so we have COMMAND_HISTORY_ITEMS_PRUNE left
if (toPrune.length) {
this.pruneItems(toPrune);
}

return {
docs: toKeep,
};
});
PouchCommandHistoryCache.response.set(
this.cacheKey,
this.db
.allDocs({
include_docs: true,
})
.then(result => {
const allItems = result.rows
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
.map(row => row.doc!)
.filter(({ name }) => name);

log.debug(`Fetched ${allItems.length} command history items`);

const { toKeep, toPrune } = siftPrunableItems(
allItems,
COMMAND_HISTORY_ITEMS_MAX,
COMMAND_HISTORY_ITEMS_PRUNE
);

// If number of items in PouchDB has exceeded COMMAND_HISTORY_ITEMS_MAX
// prune them down so we have COMMAND_HISTORY_ITEMS_PRUNE left
if (toPrune.length) {
this.pruneItems(toPrune);
}

return {
docs: toKeep,
};
})
);
}

// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const result = PouchCommandHistoryTable.cache[this.cacheKey]!;
const result = PouchCommandHistoryCache.response.get(this.cacheKey)!;

if (this.searchText == null || this.searchText === '') {
return result;
Expand All @@ -161,10 +160,13 @@ export class PouchCommandHistoryTable

/**
* Override `PouchStorageTable.fetchInfo` so we can make use of
* `PouchCommandHistoryTable.cache`
* `PouchCommandHistoryCache.cache`
* @param selector
*/
async fetchInfo(
// Our current version of eslint + prettier doesn't like `override` + `async` keyword.
// We should be able to remove this whenever we upgrade.
// eslint-disable-next-line prettier/prettier
override async fetchInfo(
selector: PouchDB.Find.Selector
): Promise<
PouchDB.Find.FindResponse<CommandHistoryStorageItem & PouchStorageItem>
Expand All @@ -187,7 +189,7 @@ export class PouchCommandHistoryTable
* @param _sort
* @returns Promise to array of command history storage items
*/
async fetchViewportData(
override async fetchViewportData(
viewport: StorageTableViewport,
selector: PouchDB.Find.Selector,
_sort: PouchDBSort
Expand All @@ -206,20 +208,35 @@ export class PouchCommandHistoryTable
* @param items
*/
async pruneItems(items: CommandHistoryDoc[]) {
if (PouchCommandHistoryCache.isPruning.has(this.cacheKey)) {
return;
}

try {
log.debug(`Pruning ${items.length} command history items`);

// Disable change notifications while we bulk delete to avoid locking up
// the app
this.changes?.cancel();
const resumeListeners = PouchCommandHistoryCache.pauseChangeListeners(
this.cacheKey
);

PouchCommandHistoryCache.isPruning.set(this.cacheKey, true);
await this.db.bulkDocs(items.map(item => ({ ...item, _deleted: true })));
this.listenForChanges();
PouchCommandHistoryCache.isPruning.set(this.cacheKey, false);

resumeListeners();

log.debug('Finished pruning command history items');
} catch (err) {
log.error('An error occurred while pruning db', err);
}
}

override close(): void {
PouchCommandHistoryCache.tableRegistry.get(this.cacheKey)?.delete(this);
super.close();
}
}

export default PouchCommandHistoryTable;
2 changes: 1 addition & 1 deletion packages/pouch-storage/src/PouchStorageTable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ export class PouchStorageTable<T extends StorageItem = StorageItem>
implements StorageTable<T> {
protected db: PouchDB.Database<T & PouchStorageItem>;

protected changes?: PouchDB.Core.Changes<T & PouchStorageItem>;
changes?: PouchDB.Core.Changes<T & PouchStorageItem>;

private listeners: ViewportUpdateCallback<T>[] = [];

Expand Down

0 comments on commit d45fec6

Please sign in to comment.