Bound the originals cache with adaptive LRU eviction (closes #47)
check / check (push) Successful in 35s
check / check (push) Successful in 35s
Bounds cacheDirectory/originals to an adaptive limit: min(configured max, bytesUsed + bytesFree - reserve), bytesFree from statfs, so it tracks disk pressure (status().originalsLimitBytes exposes it). Each original write evicts least-recently-used originals until usage fits; last-use is the file mtime, bumped on every read that returns a path, so order survives restarts. Never evicted: pinned originals and every original whose write overlaps the evicting one — its own and any concurrent sibling's, since content writes run in parallel. So an over-budget fetch keeps the path it returns, and no write can delete a sibling's file before that fetch returns it; such files become eligible on a later, non-overlapping write. Defaults: 100 GiB limit, 50 GiB reserve. Model: opus-4-8
This commit is contained in:
@@ -132,6 +132,14 @@ export interface LibraryOptions {
|
||||
// Overrides the client's own `contentSource()`; mainly for tests that drive
|
||||
// the cache with a stand-in source.
|
||||
contentSource?: ContentSource;
|
||||
// Bound on `cacheDirectory/originals` (default 100 GiB) and the free space
|
||||
// to protect on its volume (default 50 GiB). The effective limit adapts
|
||||
// down as the disk fills; `status().originalsLimitBytes` reports it.
|
||||
cacheOriginalsMaxBytes?: number;
|
||||
freeBelowBytes?: number;
|
||||
// Whether an original is pinned and so never evicted (favorites + latest
|
||||
// week; the precache unit #48 supplies the set).
|
||||
isOriginalPinned?: (fileID: number) => boolean;
|
||||
}
|
||||
|
||||
export interface LibraryStatus {
|
||||
@@ -153,6 +161,10 @@ export interface LibraryStatus {
|
||||
// when ML fetching is disabled.
|
||||
mlStored?: number;
|
||||
mlIndexed?: number;
|
||||
// Bytes stored in the originals cache and the effective size limit as of the
|
||||
// last write or open; undefined when no content cache is open.
|
||||
originalsUsedBytes?: number;
|
||||
originalsLimitBytes?: number;
|
||||
closed: boolean;
|
||||
}
|
||||
|
||||
@@ -289,6 +301,9 @@ export class Library {
|
||||
cacheDirectory,
|
||||
downloadDirectory: opts.downloadDirectory,
|
||||
getFile: (fileID) => store.getFileByID(fileID),
|
||||
cacheOriginalsMaxBytes: opts.cacheOriginalsMaxBytes,
|
||||
freeBelowBytes: opts.freeBelowBytes,
|
||||
isPinned: opts.isOriginalPinned,
|
||||
});
|
||||
await cache.open();
|
||||
}
|
||||
@@ -364,6 +379,7 @@ export class Library {
|
||||
files += this.store.listFiles(c.id).length;
|
||||
}
|
||||
const ml = this.mldata?.stats();
|
||||
const originals = this.cache?.originalsStatus();
|
||||
return {
|
||||
userID: this.store.userID,
|
||||
collections: collections.length,
|
||||
@@ -374,6 +390,8 @@ export class Library {
|
||||
lastMLError: this.lastMLError,
|
||||
mlStored: ml?.stored,
|
||||
mlIndexed: ml?.indexed,
|
||||
originalsUsedBytes: originals?.usedBytes,
|
||||
originalsLimitBytes: originals?.limitBytes,
|
||||
closed: this.closed,
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user